Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,38 @@
/*
* 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.
*
*/
#include "AzToolsFramework_precompiled.h"
#include "ComponentPaletteModel.hxx"
namespace AzToolsFramework
{
ComponentPaletteModel::ComponentPaletteModel(QObject* parent)
: QStandardItemModel(parent)
{
}
ComponentPaletteModel::~ComponentPaletteModel()
{
}
QVariant ComponentPaletteModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole && section == 0 && orientation == Qt::Horizontal)
{
return tr("Components");
}
return QStandardItemModel::headerData(section, orientation, role);
}
}
#include "UI/ComponentPalette/moc_ComponentPaletteModel.cpp"
@@ -0,0 +1,34 @@
/*
* 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.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // class 'QScopedPointer<QStandardItemPrivate,QScopedPointerDeleter<T>>' needs to have dll-interface to be used by clients of class 'QStandardItem'
#include <QStandardItemModel>
AZ_POP_DISABLE_WARNING
#endif
namespace AzToolsFramework
{
class ComponentPaletteModel
: public QStandardItemModel
{
Q_OBJECT
public:
ComponentPaletteModel(QObject* parent = 0);
~ComponentPaletteModel() override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
};
}
@@ -0,0 +1,58 @@
/*
* 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.
*
*/
#include "AzToolsFramework_precompiled.h"
#include "ComponentPaletteModelFilter.hxx"
#include <AzCore/Serialization/SerializeContext.h>
namespace AzToolsFramework
{
ComponentPaletteModelFilter::ComponentPaletteModelFilter(QObject* parent)
: QSortFilterProxyModel(parent)
{
}
bool ComponentPaletteModelFilter::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
if (!index.isValid())
{
return false;
}
if (!filterRegExp().isValid())
{
return true;
}
auto componentClass = reinterpret_cast<const AZ::SerializeContext::ClassData*>(sourceModel()->data(index, Qt::ItemDataRole::UserRole + 1).toULongLong());
if (componentClass)
{
const QString componentName = sourceModel()->data(index, Qt::DisplayRole).toString();
return componentName.contains(filterRegExp());
}
const int childRowCount = sourceModel()->rowCount(index);
for (int childRow = 0; childRow < childRowCount; ++childRow)
{
if (filterAcceptsRow(childRow, index))
{
return true;
}
}
return false;
}
}
#include "UI/ComponentPalette/moc_ComponentPaletteModelFilter.cpp"
@@ -0,0 +1,33 @@
/*
* 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.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QSortFilterProxyModel>
#endif
namespace AzToolsFramework
{
class ComponentPaletteModelFilter : public QSortFilterProxyModel
{
Q_OBJECT
public:
ComponentPaletteModelFilter(QObject* parent = nullptr);
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
protected:
QRegExp m_filterRegExp;
};
}
@@ -0,0 +1,254 @@
/*
* 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.
*
*/
#include "AzToolsFramework_precompiled.h"
#include "ComponentPaletteUtil.hxx"
#include <AzCore/Debug/Profiler.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'QLayoutItem::align': class 'QFlags<Qt::AlignmentFlag>' needs to have dll-interface to be used by clients of class 'QLayoutItem'
#include <AzToolsFramework/UI/SearchWidget/SearchCriteriaWidget.hxx>
AZ_POP_DISABLE_WARNING
namespace AzToolsFramework
{
namespace ComponentPaletteUtil
{
bool OffersRequiredServices(
const AZ::SerializeContext::ClassData* componentClass,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter,
const AZStd::vector<AZ::ComponentServiceType>& incompatibleServiceFilter
)
{
AZ_Assert(componentClass, "Component class must not be null");
if (!componentClass)
{
return false;
}
AZ::ComponentDescriptor* componentDescriptor = nullptr;
EBUS_EVENT_ID_RESULT(componentDescriptor, componentClass->m_typeId, AZ::ComponentDescriptorBus, GetDescriptor);
if (!componentDescriptor)
{
return false;
}
// If no services are provided, this function returns true
if (serviceFilter.empty())
{
return true;
}
AZ::ComponentDescriptor::DependencyArrayType providedServices;
componentDescriptor->GetProvidedServices(providedServices, nullptr);
//reject this component if it does not offer any of the required services
if (AZStd::find_first_of(
providedServices.begin(),
providedServices.end(),
serviceFilter.begin(),
serviceFilter.end()) == providedServices.end())
{
return false;
}
//reject this component if it does offer any of the incompatible services
if (AZStd::find_first_of(
providedServices.begin(),
providedServices.end(),
incompatibleServiceFilter.begin(),
incompatibleServiceFilter.end()) != providedServices.end())
{
return false;
}
return true;
}
bool OffersRequiredServices(
const AZ::SerializeContext::ClassData* componentClass,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter
)
{
const AZStd::vector<AZ::ComponentServiceType> incompatibleServices;
return OffersRequiredServices(componentClass, serviceFilter, incompatibleServices);
}
bool IsAddableByUser(const AZ::SerializeContext::ClassData* componentClass)
{
AZ_Assert(componentClass, "component class must not be null");
if (!componentClass)
{
return false;
}
auto editorDataElement = componentClass->m_editData->FindElementData(AZ::Edit::ClassElements::EditorData);
if (!editorDataElement)
{
return false;
}
auto attribute = editorDataElement->FindAttribute(AZ::Edit::Attributes::AddableByUser);
if (attribute)
{
auto data = azdynamic_cast<AZ::Edit::AttributeData<bool>*>(attribute);
if (data)
{
if (!data->Get(nullptr))
{
return false;
}
}
}
return true;
}
void BuildComponentTables(
AZ::SerializeContext* serializeContext,
const AzToolsFramework::ComponentFilter& componentFilter,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter,
const AZStd::vector<AZ::ComponentServiceType>& incompatibleServiceFilter,
ComponentDataTable &componentDataTable,
ComponentIconTable &componentIconTable)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
serializeContext->EnumerateDerived<AZ::Component>(
[&](const AZ::SerializeContext::ClassData* componentClass, const AZ::Uuid& knownType) -> bool
{
AZ_UNUSED(knownType);
if (componentFilter(*componentClass) && componentClass->m_editData)
{
QString categoryName = QString::fromUtf8("Miscellaneous");
QString componentName = QString::fromUtf8(componentClass->m_editData->m_name);
// If none of the required services are offered by this component, or the component
// can not be added by the user, skip to the next component
if (!OffersRequiredServices(componentClass, serviceFilter, incompatibleServiceFilter) || !IsAddableByUser(componentClass))
{
return true;
}
if (auto editorDataElement = componentClass->m_editData->FindElementData(AZ::Edit::ClassElements::EditorData))
{
if (auto attribute = editorDataElement->FindAttribute(AZ::Edit::Attributes::Category))
{
if (auto data = azdynamic_cast<AZ::Edit::AttributeData<const char*>*>(attribute))
{
categoryName = QString::fromUtf8(data->Get(nullptr));
}
}
AZStd::string componentIconPath;
EBUS_EVENT_RESULT(componentIconPath, AzToolsFramework::EditorRequests::Bus, GetComponentEditorIcon, componentClass->m_typeId, nullptr);
componentIconTable[componentClass] = QString::fromUtf8(componentIconPath.c_str());
}
componentDataTable[categoryName][componentName] = componentClass;
}
return true;
});
}
void BuildComponentTables(
AZ::SerializeContext* serializeContext,
const AzToolsFramework::ComponentFilter& componentFilter,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter,
ComponentDataTable& componentDataTable,
ComponentIconTable& componentIconTable)
{
const AZStd::vector<AZ::ComponentServiceType> incompatibleServices;
BuildComponentTables(serializeContext, componentFilter, serviceFilter, incompatibleServices, componentDataTable, componentIconTable);
}
bool ContainsEditableComponents(
AZ::SerializeContext* serializeContext,
const AzToolsFramework::ComponentFilter& componentFilter,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter,
const AZStd::vector<AZ::ComponentServiceType>& incompatibleServiceFilter
)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
bool containsEditable = false;
serializeContext->EnumerateDerived<AZ::Component>(
[&](const AZ::SerializeContext::ClassData* componentClass, const AZ::Uuid& knownType) -> bool
{
AZ_UNUSED(knownType);
if (componentFilter(*componentClass) && componentClass->m_editData)
{
// If none of the required services are offered by this component, or the component
// can not be added by the user, skip to the next component
if (!OffersRequiredServices(componentClass, serviceFilter, incompatibleServiceFilter) || !IsAddableByUser(componentClass))
{
return true;
}
containsEditable = true;
}
// We can stop enumerating if we've found an editable component
return !containsEditable;
});
return containsEditable;
}
bool ContainsEditableComponents(
AZ::SerializeContext* serializeContext,
const AzToolsFramework::ComponentFilter& componentFilter,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter
)
{
const AZStd::vector<AZ::ComponentServiceType> incompatibleServices;
return ContainsEditableComponents(serializeContext, componentFilter, serviceFilter, incompatibleServices);
}
QRegExp BuildFilterRegExp(QStringList& criteriaList, AzToolsFramework::FilterOperatorType filterOperator)
{
// Go through the list of items and show/hide as needed due to filter.
QString filter;
for (const auto& criteria : criteriaList)
{
QString tag, text;
AzToolsFramework::SearchCriteriaButton::SplitTagAndText(criteria, tag, text);
if (filterOperator == AzToolsFramework::FilterOperatorType::Or)
{
if (filter.isEmpty())
{
filter = text;
}
else
{
filter += "|" + text;
}
}
else if (filterOperator == AzToolsFramework::FilterOperatorType::And)
{
filter += "(?=.*" + text + ")";
}
}
return QRegExp(filter, Qt::CaseInsensitive, QRegExp::RegExp);
}
}
}
@@ -0,0 +1,82 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/std/containers/map.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/UI/SearchWidget/SearchWidgetTypes.hxx>
#include <QString>
namespace AZ
{
class SerializeContext;
};
namespace AzToolsFramework
{
namespace ComponentPaletteUtil
{
using ComponentDataTable = AZStd::map <QString, AZStd::map <QString, const AZ::SerializeContext::ClassData* > >;
using ComponentIconTable = AZStd::map<const AZ::SerializeContext::ClassData*, QString>;
// Returns true if the given component provides at least one of the services specified or no services are provided
bool OffersRequiredServices(
const AZ::SerializeContext::ClassData* componentClass,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter,
const AZStd::vector<AZ::ComponentServiceType>& incompatibleServiceFilter
);
bool OffersRequiredServices(
const AZ::SerializeContext::ClassData* componentClass,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter
);
// Returns true if the given component is addable by the user
bool IsAddableByUser(const AZ::SerializeContext::ClassData* componentClass);
void BuildComponentTables(
AZ::SerializeContext* serializeContext,
const AzToolsFramework::ComponentFilter& componentFilter,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter,
const AZStd::vector<AZ::ComponentServiceType>& incompatibleServiceFilter,
ComponentDataTable &componentDataTable,
ComponentIconTable &componentIconTable
);
void BuildComponentTables(
AZ::SerializeContext* serializeContext,
const AzToolsFramework::ComponentFilter& componentFilter,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter,
ComponentDataTable &componentDataTable,
ComponentIconTable &componentIconTable
);
// Returns true if any components in the given filter provide any of the services
// specified and are addable/editable by the user
bool ContainsEditableComponents(
AZ::SerializeContext* serializeContext,
const AzToolsFramework::ComponentFilter& componentFilter,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter,
const AZStd::vector<AZ::ComponentServiceType>& incompatibleServiceFilter
);
bool ContainsEditableComponents(
AZ::SerializeContext* serializeContext,
const AzToolsFramework::ComponentFilter& componentFilter,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter
);
QRegExp BuildFilterRegExp(QStringList& criteriaList, AzToolsFramework::FilterOperatorType filterOperator);
}
}
@@ -0,0 +1,458 @@
/*
* 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.
*
*/
#include "AzToolsFramework_precompiled.h"
#include "ComponentPaletteModel.hxx"
#include "ComponentPaletteUtil.hxx"
#include "ComponentPaletteWidget.hxx"
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/Debug/Profiler.h>
#include <AzFramework/Components/DeprecatedComponentsBus.h>
#include <AzToolsFramework/API/EntityCompositionRequestBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzQtComponents/Components/Widgets/LineEdit.h>
AZ_PUSH_DISABLE_WARNING(4244 4251, "-Wunknown-warning-option") // 4244: conversion from 'int' to 'float', possible loss of data
// 4251: class '...' needs to have dll-interface to be used by clients of class '...'
#include <QAction>
#include <QAbstractItemView>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLineEdit>
#include <QPushButton>
#include <QScrollBar>
#include <QStandardItem>
#include <QStandardItem>
#include <QTimer>
#include <QTreeView>
#include <QVBoxLayout>
#include <QKeyEvent>
#include <QToolButton>
AZ_POP_DISABLE_WARNING
namespace AzToolsFramework
{
ComponentPaletteWidget::ComponentPaletteWidget(QWidget* parent, bool enableSearch)
: QFrame(parent)
{
setWindowFlags(Qt::FramelessWindowHint | Qt::Popup);
setFrameShadow(QFrame::Shadow::Raised);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setAcceptDrops(false);
auto outerLayout = new QVBoxLayout(this);
outerLayout->setSizeConstraint(QLayout::SetNoConstraint);
setLayout(outerLayout);
// Search filter
m_searchFrame = new QFrame(this);
m_searchFrame->setObjectName("SearchFrame");
m_searchFrame->setVisible(enableSearch);
auto searchLayout = new QHBoxLayout(m_searchFrame);
searchLayout->setSizeConstraint(QLayout::SetMinimumSize);
m_searchText = new QLineEdit(m_searchFrame);
m_searchText->setObjectName("SearchText");
m_searchText->setText("");
m_searchText->setPlaceholderText("Search...");
m_searchText->setClearButtonEnabled(true);
AzQtComponents::LineEdit::applySearchStyle(m_searchText);
m_searchRegExp = QRegExp("", Qt::CaseInsensitive, QRegExp::RegExp);
searchLayout->addWidget(m_searchText);
m_searchFrame->setLayout(searchLayout);
outerLayout->addWidget(m_searchFrame);
m_componentModel = new ComponentPaletteModel(this);
m_componentTree = new QTreeView(this);
m_componentTree->setObjectName("Tree");
m_componentTree->setModel(m_componentModel);
m_componentTree->setEditTriggers(QAbstractItemView::NoEditTriggers);
outerLayout->addWidget(m_componentTree);
//hide header for dropdown-style, single-column, tree
m_componentTree->header()->hide();
connect(m_searchText, &QLineEdit::textChanged, this, &ComponentPaletteWidget::QueueUpdateSearch);
QToolButton* clearButton = AzQtComponents::LineEdit::getClearButton(m_searchText);
assert(clearButton);
connect(clearButton, &QToolButton::clicked, this, &ComponentPaletteWidget::ClearSearch);
connect(m_componentTree, &QTreeView::activated, this, &ComponentPaletteWidget::ActivateSelection);
connect(m_componentTree, &QTreeView::clicked, this, &ComponentPaletteWidget::ActivateSelection);
connect(m_componentTree, &QTreeView::doubleClicked, this, &ComponentPaletteWidget::ActivateSelection);
connect(m_componentTree, &QTreeView::expanded, this, &ComponentPaletteWidget::ExpandCategory);
connect(m_componentTree, &QTreeView::collapsed, this, &ComponentPaletteWidget::CollapseCategory);
auto actionToHideWindow = new QAction(tr("Hide Window"), this);
actionToHideWindow->setShortcut(QKeySequence::Cancel);
actionToHideWindow->setShortcutContext(Qt::WidgetWithChildrenShortcut);
connect(actionToHideWindow, &QAction::triggered, this, &ComponentPaletteWidget::OnAddComponentCancel);
connect(actionToHideWindow, &QAction::triggered, this, &ComponentPaletteWidget::hide);
addAction(actionToHideWindow);
auto actionToFocusSearchBox = new QAction(tr("Focus Search Box"), this);
actionToFocusSearchBox->setShortcut(QKeySequence::Find);
actionToFocusSearchBox->setShortcutContext(Qt::WidgetWithChildrenShortcut);
connect(actionToFocusSearchBox, &QAction::triggered, this, &ComponentPaletteWidget::FocusSearchBox);
addAction(actionToFocusSearchBox);
installEventFilter(this);
}
void ComponentPaletteWidget::Populate(
AZ::SerializeContext* serializeContext,
const AzToolsFramework::EntityIdList& selectedEntityIds,
const AzToolsFramework::ComponentFilter& componentFilter,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter,
const AZStd::vector<AZ::ComponentServiceType>& incompatibleServiceFilter)
{
m_serializeContext = serializeContext;
m_selectedEntityIds = selectedEntityIds;
m_componentFilter = componentFilter;
m_serviceFilter = serviceFilter;
m_incompatibleServiceFilter = incompatibleServiceFilter;
UpdateContent();
Present();
}
void ComponentPaletteWidget::UpdateContent()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
m_componentModel->clear();
bool applyRegExFilter = !m_searchRegExp.isEmpty();
// Gather all components that match our filter and group by category.
ComponentPaletteUtil::ComponentDataTable componentDataTable;
ComponentPaletteUtil::ComponentIconTable componentIconTable;
ComponentPaletteUtil::BuildComponentTables(
m_serializeContext,
m_componentFilter,
m_serviceFilter,
m_incompatibleServiceFilter,
componentDataTable,
componentIconTable);
AzFramework::Components::DeprecatedComponentsList deprecatedList;
AzFramework::Components::DeprecatedComponentsRequestBus::Broadcast(&AzFramework::Components::DeprecatedComponentsRequestBus::Events::EnumerateDeprecatedComponents, deprecatedList);
AZ::Entity::ComponentArrayType componentsOnEntity;
// Get all components on all selected entities so we can display a count of used components by type
AZStd::unordered_set<AZ::Component*> allComponentsOnSelectedEntities;
for (AZ::EntityId entityId : m_selectedEntityIds)
{
componentsOnEntity.clear();
AzToolsFramework::GetAllComponentsForEntity(entityId, componentsOnEntity);
allComponentsOnSelectedEntities.insert(componentsOnEntity.begin(), componentsOnEntity.end());
}
// Populate the context menu.
AZStd::map<QString, QStandardItem*> categoryItemMap;
for (const auto& categoryPair : componentDataTable)
{
//get the full category name/path and split it by separators for iteration
const QString& categoryPath = categoryPair.first;
const QStringList& categoryPathSegments = categoryPath.split('/', Qt::SkipEmptyParts);
QString categoryPathBuilder;
//for every segment of the category path, create an expandable header
auto parentItem = m_componentModel->invisibleRootItem();
for (const QString& categoryName : categoryPathSegments)
{
categoryPathBuilder += categoryName + "/";
QStandardItem* categoryItem = nullptr;
auto categoryItemItr = categoryItemMap.find(categoryPathBuilder);
if (categoryItemItr == categoryItemMap.end())
{
categoryItem = new QStandardItem(categoryName);
categoryItem->setCheckable(false);
categoryItem->setEditable(false);
categoryItem->setSelectable(true);
categoryItem->setData((qulonglong)nullptr, Qt::ItemDataRole::UserRole + 1);
//make groups bold
QFont font = categoryItem->font();
font.setBold(true);
categoryItem->setFont(font);
parentItem->appendRow(categoryItem);
categoryItemMap[categoryPathBuilder] = categoryItem;
}
else
{
categoryItem = categoryItemItr->second;
}
parentItem = categoryItem;
}
}
for (const auto& categoryPair : componentDataTable)
{
auto categoryItemItr = categoryItemMap.find(categoryPair.first + "/");
auto parentItem = categoryItemItr != categoryItemMap.end() ? categoryItemItr->second : m_componentModel->invisibleRootItem();
const auto& componentMap = categoryPair.second;
for (const auto& componentPair : componentMap)
{
auto componentClass = componentPair.second;
const QString& componentName = componentPair.first;
const QString& componentIconName = componentIconTable[componentClass];
auto deprecatedInfo = deprecatedList.find(componentClass->m_typeId);
bool componentIsDeprecated = deprecatedInfo != deprecatedList.end();
if ((!applyRegExFilter || componentName.contains(m_searchRegExp)) && (!componentIsDeprecated || !deprecatedInfo->second.m_hideComponent))
{
//count the number of components on selected entities that match this type
auto componentCount = AZStd::count_if(allComponentsOnSelectedEntities.begin(), allComponentsOnSelectedEntities.end(), [componentClass](const AZ::Component* component) {
return componentClass->m_typeId == component->GetUnderlyingComponentType();
});
//generate the display name for the component
QString displayName = componentName;
if (componentCount) //<append count if count > 0
{
displayName += QObject::tr(" (%1)").arg(componentCount);
}
if (componentIsDeprecated) //< append deprecation strings
{
displayName += deprecatedInfo->second.m_deprecationString.c_str();
}
auto componentItem = new QStandardItem(QIcon(componentIconName), displayName);
componentItem->setToolTip(componentClass->m_editData->m_description);
componentItem->setCheckable(false);
componentItem->setEditable(false);
componentItem->setSelectable(true);
componentItem->setData((qulonglong)componentClass, Qt::ItemDataRole::UserRole + 1);
parentItem->appendRow(componentItem);
}
}
}
// If we have removed component items from the visible tree we need to prune the hanging titles.
// We also need to auto expand all the items that contain children.
for (int i = m_componentModel->rowCount() - 1; i >= 0; --i)
{
if (BranchHasNoChildren(m_componentModel->item(i)))
{
m_componentModel->removeRow(i);
}
else
{
SetExpanded(m_componentModel->index(i, 0));
}
}
m_componentModel->sort(0);
}
bool ComponentPaletteWidget::BranchHasNoChildren(QStandardItem* item)
{
bool returnVal = true;
// We have to check all items to completely remove all non-parent items. Hence no early-exit.
for (int i = item->rowCount() - 1; i >= 0 ; --i)
{
QStandardItem* it = item->child(i);
// Check in reverse so removing a row doesn't affect remaining items.
if (it->data(Qt::ItemDataRole::UserRole + 1).toULongLong())
{
// if this node has data then the parent has children.
returnVal = false;
}
else
{
// If this item has no children, prune it.
if (BranchHasNoChildren(it))
{
item->removeRow(i);
}
else
{
SetExpanded(it->index());
returnVal = false;
}
}
}
return returnVal;
}
void ComponentPaletteWidget::SetExpanded(QModelIndex itemIndex)
{
auto label = m_componentModel->data(itemIndex, Qt::ItemDataRole::DisplayRole).toString();
auto stateItr = m_categoryExpandedState.find(label);
auto expand = stateItr == m_categoryExpandedState.end() || stateItr->second;
m_componentTree->setExpanded(itemIndex, expand);
}
void ComponentPaletteWidget::Present()
{
layout()->setEnabled(true);
layout()->update();
layout()->activate();
raise();
show();
FocusSearchBox();
}
void ComponentPaletteWidget::QueueUpdateSearch()
{
QTimer::singleShot(1, this, &ComponentPaletteWidget::UpdateSearch);
}
void ComponentPaletteWidget::UpdateSearch()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
m_searchRegExp = QRegExp(m_searchText->text(), Qt::CaseInsensitive, QRegExp::RegExp);
m_searchText->setFocus();
UpdateContent();
}
void ComponentPaletteWidget::ClearSearch()
{
m_searchText->setText("");
QueueUpdateSearch();
}
void ComponentPaletteWidget::ActivateSelection(const QModelIndex& index)
{
if (index.isValid())
{
auto componentClass = reinterpret_cast<const AZ::SerializeContext::ClassData*>(m_componentModel->data(index, Qt::ItemDataRole::UserRole + 1).toULongLong());
if (componentClass)
{
emit OnAddComponentBegin();
EntityCompositionRequestBus::Broadcast(&EntityCompositionRequests::AddComponentsToEntities, m_selectedEntityIds, AZ::ComponentTypeList{ componentClass->m_typeId });
emit OnAddComponentEnd();
ClearSearch();
hide();
}
}
}
void ComponentPaletteWidget::ExpandCategory(const QModelIndex& index)
{
if (index.isValid())
{
auto label = m_componentModel->data(index, Qt::ItemDataRole::DisplayRole).toString();
m_categoryExpandedState[label] = true;
}
}
void ComponentPaletteWidget::CollapseCategory(const QModelIndex& index)
{
if (index.isValid())
{
auto label = m_componentModel->data(index, Qt::ItemDataRole::DisplayRole).toString();
m_categoryExpandedState[label] = false;
}
}
void ComponentPaletteWidget::focusOutEvent(QFocusEvent *event)
{
hide();
QFrame::focusOutEvent(event);
}
void ComponentPaletteWidget::FocusSearchBox()
{
if (m_searchFrame->isVisible())
{
if (!m_searchText->hasFocus())
{
m_searchText->setFocus();
}
}
else
{
FocusComponentTree();
}
}
void ComponentPaletteWidget::FocusComponentTree()
{
if (!m_componentTree->hasFocus())
{
m_componentTree->setFocus();
// Focus the first actual component (leaf node)
QModelIndex indexToSelect = m_componentModel->index(0, 0);
while (indexToSelect.isValid() && m_componentModel->rowCount(indexToSelect) > 0)
{
indexToSelect = indexToSelect.model()->index(0, 0, indexToSelect);
}
m_componentTree->setCurrentIndex(indexToSelect);
}
}
//overridden to intercept key events to move between filter and tree
bool ComponentPaletteWidget::eventFilter(QObject* object, QEvent* event)
{
if (event->type() != QEvent::KeyPress)
{
return false;
}
if (object != this &&
object != m_searchText &&
object != m_componentTree)
{
return false;
}
if (!hasFocus() &&
!m_searchText->hasFocus() &&
!m_componentTree->hasFocus())
{
return false;
}
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Down ||
keyEvent->key() == Qt::Key_Return ||
keyEvent->key() == Qt::Key_Enter)
{
FocusComponentTree();
return false;
}
if (keyEvent->key() == Qt::Key_Up)
{
if (!m_componentTree->hasFocus() ||
m_componentTree->selectionModel()->selectedIndexes().empty() ||
m_componentTree->selectionModel()->selectedIndexes().front().row() == 0)
{
FocusSearchBox();
return false;
}
}
return false;
}
}
#include "UI/ComponentPalette/moc_ComponentPaletteWidget.cpp"
@@ -0,0 +1,95 @@
/*
* 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.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/std/containers/map.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/UI/SearchWidget/SearchWidgetTypes.hxx>
AZ_PUSH_DISABLE_WARNING(4244 4251, "-Wunknown-warning-option") // 4244: conversion from 'int' to 'float', possible loss of data
// 4251: class '...' needs to have dll-interface to be used by clients of class '...'
#include <QFrame>
#include <QString>
AZ_POP_DISABLE_WARNING
#endif
class QLineEdit;
class QPushButton;
class QSortFilterProxyModel;
class QTreeView;
namespace AZ
{
class SerializeContext;
};
namespace AzToolsFramework
{
class ComponentPaletteModel;
class ComponentPaletteWidget
: public QFrame
{
Q_OBJECT
public:
ComponentPaletteWidget(QWidget* parent, bool enableSearch);
void Populate(
AZ::SerializeContext* serializeContext,
const AzToolsFramework::EntityIdList& selectedEntityIds,
const AzToolsFramework::ComponentFilter& componentFilter,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter,
const AZStd::vector<AZ::ComponentServiceType>& incompatibleServiceFilter);
void Present();
Q_SIGNALS:
void OnAddComponentBegin();
void OnAddComponentEnd();
void OnAddComponentCancel();
protected:
void focusOutEvent(QFocusEvent *event) override;
private slots:
void UpdateContent();
void QueueUpdateSearch();
void UpdateSearch();
void ClearSearch();
void ActivateSelection(const QModelIndex& index);
void ExpandCategory(const QModelIndex& index);
void CollapseCategory(const QModelIndex& index);
void FocusSearchBox();
void FocusComponentTree();
private:
bool eventFilter(QObject* object, QEvent* event) override;
bool BranchHasNoChildren(QStandardItem* item);
void SetExpanded(QModelIndex itemIndex);
QRegExp m_searchRegExp;
QFrame* m_searchFrame = nullptr;
QLineEdit* m_searchText = nullptr;
QTreeView* m_componentTree = nullptr;
ComponentPaletteModel* m_componentModel = nullptr;
AZ::SerializeContext* m_serializeContext = nullptr;
EntityIdList m_selectedEntityIds;
ComponentFilter m_componentFilter;
AZStd::vector<AZ::ComponentServiceType> m_serviceFilter;
AZStd::vector<AZ::ComponentServiceType> m_incompatibleServiceFilter;
AZStd::map<QString, bool> m_categoryExpandedState;
};
}
@@ -0,0 +1,350 @@
/*
* 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.
*
*/
#include <AzToolsFramework/UI/Docking/DockWidgetUtils.h>
AZ_PUSH_DISABLE_WARNING(4251 4244 4458, "-Wunknown-warning-option") // 4251: 'QTextStream::d_ptr': class 'QScopedPointer<QTextStreamPrivate,QScopedPointerDeleter<T>>' needs to have dll-interface to be used by clients of class 'QTextStream'
// 4244: '=': conversion from 'int' to 'qint8', possible loss of data
// 4458: declaration of 'parent' hides class member
#include <QTimer>
#include <QTabBar>
#include <QMainWindow>
#include <QDockWidget>
#include <QDebug>
#include <QDataStream>
#include <QtWidgets/private/qdockarealayout_p.h>
#include <QtWidgets/private/qtoolbararealayout_p.h>
#include <QtWidgets/private/qmainwindowlayout_p.h>
AZ_POP_DISABLE_WARNING
namespace AzToolsFramework
{
bool DockWidgetUtils::containsDockWidget(QObject *o)
{
if (!o)
{
return false;
}
if (qobject_cast<QDockWidget*>(o))
{
return true;
}
const auto children = o->children();
for (auto child : children)
{
if (containsDockWidget(child))
{
return true;
}
}
return false;
}
QList<QWidget*> DockWidgetUtils::getDockWindowGroups(QMainWindow *mainWindow)
{
const QObjectList children = mainWindow->children();
QList<QWidget*> result;
for (auto child : children)
{
if (auto w = qobject_cast<QWidget*>(child))
{
if (QString(w->metaObject()->className()) == QStringLiteral("QDockWidgetGroupWindow"))
{
result.append(w);
}
}
}
return result;
}
void DockWidgetUtils::deleteWindowGroups(QMainWindow *mainWindow, bool onlyGhosts)
{
const QList<QWidget*> dockWindowGroups = getDockWindowGroups(mainWindow);
for (auto dwgw : dockWindowGroups)
{
const bool isGhost = !containsDockWidget(dwgw);
if (!onlyGhosts || isGhost)
{
for (auto c2 : dwgw->children()) {
if (auto dock = qobject_cast<QDockWidget*>(c2))
{
//qDebug() << "Reparenting one " << dock->windowTitle();
dock->setParent(mainWindow);
}
else if (auto tb = qobject_cast<QTabBar*>(c2))
{
//qDebug() << "Reparenting a tab bar. Visible= " << tb->isVisible();
tb->setParent(mainWindow);
}
}
//qDebug() << "Deleting dwgw";
delete dwgw;
}
}
}
void DockWidgetUtils::dumpDockWidgets(QMainWindow *mainWindow)
{
Q_ASSERT(mainWindow);
qDebug() << "dumpDockWidgets START";
const QList<QWidget*> dockWindowGroups = DockWidgetUtils::getDockWindowGroups(mainWindow);
for (auto dwgw : dockWindowGroups)
{
qDebug() << " Got one QDockWidgetGroupWindow. visible="
<< dwgw->isVisible()
<< "; enabled =" << dwgw->isEnabled()
<< (!containsDockWidget(dwgw) ? "; ghost" : "");
for (auto c : dwgw->children()) {
if (auto w = qobject_cast<QWidget*>(c))
{
qDebug() << " * " << w
<< "visible=" << w->isVisible()
<< "enabled=" << w->isEnabled();
}
if (auto dock = qobject_cast<QDockWidget*>(c))
{
qDebug() << " "
<< "geometry=" << dock->geometry()
<< "title=" << dock->windowTitle()
<< "isFloating=" << dock->isFloating()
<< "area=" << mainWindow->dockWidgetArea(dock);
}
}
}
for (auto c : mainWindow->children())
{
if (auto dock = qobject_cast<QDockWidget*>(c))
{
qDebug() << " Got one QDockWidget. Visible="
<< dock->isVisible()
<< "geometry=" << dock->geometry()
<< "title=" << dock->windowTitle()
<< "isFloating=" << dock->isFloating()
<< "enabled=" << dock->isEnabled()
<< "area=" << mainWindow->dockWidgetArea(dock);
}
}
qDebug() << "dumpDockWidgets END";
}
static bool processQDockAreaLayoutInfo(QDataStream &stream, QStringList &dockNames)
{
uchar marker;
stream >> marker;
if (marker != QDockAreaLayoutInfo::TabMarker && marker != QDockAreaLayoutInfo::SequenceMarker)
{
return false;
}
const bool tabbed = marker == QDockAreaLayoutInfo::TabMarker;
int index = -1;
if (tabbed)
{
stream >> index;
}
uchar orientation;
stream >> orientation;
int cnt;
stream >> cnt;
for (int i = 0; i < cnt; ++i)
{
uchar nextMarker;
stream >> nextMarker;
if (nextMarker == QDockAreaLayoutInfo::WidgetMarker)
{
QString name;
uchar flags;
stream >> name >> flags;
qDebug() << " DockWidgetUtils::processSavedState WidgetMarker name="
<< name << "; floating=" << !!(flags & 2) << "; visible=" << !!(flags & 1);
dockNames << name;
int dummy;
stream >> dummy >> dummy >> dummy >> dummy;
}
else if (nextMarker == QDockAreaLayoutInfo::SequenceMarker)
{
qDebug() << "DockWidgetUtils::processSavedState SequenceMarker";
int dummy;
stream >> dummy >> dummy >> dummy >> dummy;
if (!processQDockAreaLayoutInfo(stream, dockNames))
{
return false;
}
}
}
return true;
}
bool DockWidgetUtils::processSavedState(const QByteArray &data, QStringList &dockNames)
{
if (data.isEmpty())
{
return false;
}
qDebug() << "DockWidgetUtils::processSavedState";
QByteArray sd = data;
QDataStream stream(&sd, QIODevice::ReadOnly);
int m, v;
stream >> m >> v;
if (stream.status() != QDataStream::Ok || m != QMainWindowLayout::VersionMarker || v != 0)
{
return false;
}
while (!stream.atEnd())
{
uchar marker;
stream >> marker;
switch (marker)
{
case QDockAreaLayout::DockWidgetStateMarker:
{
qDebug() << "DockWidgetUtils::processSavedState DockWidgetStateMarker";
int cnt;
stream >> cnt;
for (int i = 0; i < cnt; ++i) {
int pos;
stream >> pos;
QSize size;
stream >> size;
if (!processQDockAreaLayoutInfo(stream, dockNames))
{
return false;
}
}
QSize size;
stream >> size;
bool ok = stream.status() == QDataStream::Ok;
if (ok)
{
int cornerData[4];
for (int i = 0; i < 4; ++i)
{
stream >> cornerData[i];
}
}
else
{
return false;
}
break;
}
case QDockAreaLayout::FloatingDockWidgetTabMarker:
{
qDebug() << "DockWidgetUtils::processSavedState FloatingDockWidgetTabMarker";
QRect geometry;
stream >> geometry;
if (!processQDockAreaLayoutInfo(stream, dockNames))
{
return false;
}
break;
}
case QToolBarAreaLayout::ToolBarStateMarker:
case QToolBarAreaLayout::ToolBarStateMarkerEx:
{
qDebug() << "DockWidgetUtils::processSavedState ToolbarMarker";
int dummyInt;
int lines;
stream >> lines;
for (int j = 0; j < lines; ++j)
{
int pos;
stream >> pos;
if (pos < 0 || pos >= QInternal::DockCount)
{
return false;
}
int cnt;
stream >> cnt;
for (int k = 0; k < cnt; ++k)
{
QString dummyString;
uchar dummyUChar;
stream >> dummyString >> dummyUChar >> dummyInt >> dummyInt >> dummyInt;
if (marker == QToolBarAreaLayout::ToolBarStateMarkerEx)
{
stream >> dummyInt;
}
}
}
break;
}
default:
qDebug() << "Error" << marker;
return false;
}
}
qDebug() << "DockWidgetUtils::processSavedState END";
return true;
}
bool DockWidgetUtils::isDockWidgetWindowGroup(QWidget* w)
{
return w && QString(w->metaObject()->className()) == QStringLiteral("QDockWidgetGroupWindow");
}
bool DockWidgetUtils::isInDockWidgetWindowGroup(QDockWidget* w)
{
return w && isDockWidgetWindowGroup(w->parentWidget());
}
void DockWidgetUtils::correctVisibility(QDockWidget* dw)
{
if (isInDockWidgetWindowGroup(dw) && !dw->parentWidget()->isVisible())
{
dw->parentWidget()->show();
}
}
void DockWidgetUtils::startPeriodicDebugDump(QMainWindow *mainWindow)
{
const auto t = new QTimer{ mainWindow };
t->start(5000);
QObject::connect(t, &QTimer::timeout, mainWindow, [mainWindow]
{
DockWidgetUtils::dumpDockWidgets(mainWindow);
});
}
bool DockWidgetUtils::hasInvalidDockWidgets(QMainWindow *mainWindow)
{
for (auto c : mainWindow->children())
{
if (auto dock = qobject_cast<QDockWidget*>(c))
{
if (mainWindow->dockWidgetArea(dock) == Qt::NoDockWidgetArea && !dock->isFloating())
{
return true;
}
}
}
return false;
}
}
@@ -0,0 +1,91 @@
/*
* 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.
*
*/
#pragma once
// This is a collection of methods to change QDockWidget internals to workaround bugs present in
// 5.6.2, mainly related to restoring floating tabbed windows
#include <AzCore/PlatformDef.h>
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'QTextStream::d_ptr': class 'QScopedPointer<QTextStreamPrivate,QScopedPointerDeleter<T>>' needs to have dll-interface to be used by clients of class 'QTextStream'
#include <QList>
AZ_POP_DISABLE_WARNING
class QObject;
class QWidget;
class QMainWindow;
class QDataStream;
class QDockWidget;
namespace AzToolsFramework
{
namespace DockWidgetUtils
{
/**
* Returns true if w is a QDockWidgetGroupWindow.
*/
bool isDockWidgetWindowGroup(QWidget* w);
/**
* Returns true if the dockwidget dw is inside QDockWidgetGroupWindow.
*/
bool isInDockWidgetWindowGroup(QDockWidget* dw);
/**
* After calling QMainWindow::restoreDockWidget(dw) it can happen that
* the dockwidget is inside an hidden QDockWidgetGroupWindow, which needs to be shown.
*/
void correctVisibility(QDockWidget *dw);
/**
* Returns true if either obj or one of its children is a QDockWidget.
* Useful to check if a QDockWidgetGroupWindow has any QDockWidgets.
*/
bool containsDockWidget(QObject *obj);
/**
* Returns a list of QDockWidgetGroupWindow that are direct children of mainWindow.
*/
QList<QWidget*> getDockWindowGroups(QMainWindow *mainWindow);
/**
* Deletes all QDockWidgetGroupWindow.
* If onlyGhosts is true, then only the ones with no QDockWidget are deleted
*/
void deleteWindowGroups(QMainWindow *mainWindow, bool onlyGhosts = false);
/**
* Prints a list of dock widgets and QDockWidgetGroupWindows to stderr.
*/
void dumpDockWidgets(QMainWindow *mainWindow);
/**
* Calls dumpDockWidgets() every 5 seconds.
*/
void startPeriodicDebugDump(QMainWindow *mainWindow);
/**
* This method is for debugging purposes.
* Processes the bytearray that contains the saved docking layout, as outputed from QMainWindow::saveState().
* Returns the dock names that would be restored. Eventually we can think of editing the saved data
* to fix bugs.
*/
bool processSavedState(const QByteArray &savedData, QStringList &dockNames);
/**
* Looks for non-floating dock widgets that aren't in any dock area because QMainWindow wasn't able to restore it.
* Should only happen in case of a crash.
*/
bool hasInvalidDockWidgets(QMainWindow *mainWindow);
}
}
@@ -0,0 +1,272 @@
/*
* 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.
*
*/
#include "AzToolsFramework_precompiled.h"
#include "AddToLayerMenu.h"
#include <AzCore/Component/TransformBus.h>
#include <AzCore/std/containers/stack.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/containers/stack.h>
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <AzQtComponents/Components/Style.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 'QLayoutItem::align': class 'QFlags<Qt::AlignmentFlag>' needs to have dll-interface to be used by clients of class 'QLayoutItem'
#include <QHBoxLayout>
#include <QMenu>
#include <QWidgetAction>
AZ_POP_DISABLE_WARNING
namespace AzToolsFramework
{
struct LayerMenuRow;
typedef AZStd::unordered_map<AZ::EntityId, AZStd::shared_ptr<LayerMenuRow>> EntityIdToLayerMenuRow;
struct LayerMenuRow
{
public:
LayerMenuRow() { }
QString m_rowLabel;
EntityIdToLayerMenuRow m_rowChildren;
};
void AssignParentToAllEntities(
const AZ::EntityId& newParent,
const AzToolsFramework::EntityIdSet &entities)
{
for (const AZ::EntityId& entityToReparent : entities)
{
AZ::TransformBus::Event(
entityToReparent,
&AZ::TransformBus::Events::SetParent,
newParent);
}
}
void BuildAddToLayerRows(
QMenu* assignToLayerMenu,
const AzToolsFramework::EntityIdSet &entitySelectionWithFlatHierarchy,
bool isHierarchyActive,
const EntityIdToLayerMenuRow& layerRows,
int indent)
{
for (auto& layerRow : layerRows)
{
AZ::EntityId currentLayerId(layerRow.first);
QColor layerColor;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
layerColor,
currentLayerId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::GetLayerColor);
QWidgetAction* assignToLayerAction = new QWidgetAction(assignToLayerMenu);
QWidget* assignToLayerWidget = new QWidget(assignToLayerMenu);
// Special behavior and properties are defined in NewEditorStyleSheet.qss based on this name.
assignToLayerWidget->setObjectName("LayerHierarchyMenuItem");
// Add class to fix hover state styling for WidgetAction
AzQtComponents::Style::addClass(assignToLayerWidget, "WidgetAction");
QHBoxLayout* assignToLayerLayout = new QHBoxLayout(assignToLayerWidget);
assignToLayerAction->setDefaultWidget(assignToLayerWidget);
// If the menu is too thin, it feels awkward to use.
assignToLayerWidget->setMinimumWidth(200);
const int layerIconSize = 8;
QPixmap layerPixmap(layerIconSize, layerIconSize);
layerPixmap.fill(layerColor);
// If this layer is a child of another layer, then indent it in under the parent
if (indent > 0)
{
const int indentPerLevel = 20;
const int spacingWidth = indent * indentPerLevel;
QLabel* indentLabel = new QLabel(assignToLayerMenu);
indentLabel->setFixedWidth(spacingWidth);
assignToLayerLayout->addWidget(indentLabel);
}
// Create a square icon of the layer's color.
QLabel* iconLabel = new QLabel(assignToLayerMenu);
iconLabel->setPixmap(layerPixmap);
iconLabel->setFixedSize(QSize(layerIconSize, layerIconSize));
assignToLayerLayout->addWidget(iconLabel);
QLabel* iconPaddingLabel = new QLabel(assignToLayerMenu);
const int layerIconPadding = 4;
iconPaddingLabel->setFixedWidth(layerIconPadding);
assignToLayerLayout->addWidget(iconPaddingLabel);
// Use just the layer's entity name, and not full file path, to keep the menu simple.
QLabel* layerLabel = new QLabel(layerRow.second->m_rowLabel, assignToLayerMenu);
assignToLayerLayout->addWidget(layerLabel);
// Once a parent layer is an invalid target for this selection, all children will be invalid, too.
// Siblings may still be fine, which is why a helper bool is used here instead of just flipping isHierarchyACtive.
bool childrenHierarchyActive = isHierarchyActive;
// Check if it's safe to re-assign the selection to this entity.
if (!childrenHierarchyActive)
{
assignToLayerWidget->setEnabled(false);
assignToLayerWidget->setToolTip(QObject::tr("The selected layer can't be moved to one of its child layers."));
}
else if (entitySelectionWithFlatHierarchy.find(currentLayerId) != entitySelectionWithFlatHierarchy.end())
{
assignToLayerWidget->setEnabled(false);
assignToLayerWidget->setToolTip(QObject::tr("The selected layer can't be moved to itself."));
childrenHierarchyActive = false;
}
else
{
assignToLayerWidget->setToolTip(QObject::tr("Move the selection to this layer."));
QObject::connect(assignToLayerAction, &QAction::triggered,
[entitySelectionWithFlatHierarchy, currentLayerId]
{
AssignParentToAllEntities(currentLayerId, entitySelectionWithFlatHierarchy);
});
}
assignToLayerMenu->addAction(assignToLayerAction);
// Build the add to layer rows for all of this layer's children.
BuildAddToLayerRows(
assignToLayerMenu,
entitySelectionWithFlatHierarchy,
childrenHierarchyActive,
layerRow.second->m_rowChildren,
indent + 1);
}
}
void BuildLayerAcenstry(AZStd::stack<AZ::EntityId>& layerAncestry, AZ::EntityId layerEntityId)
{
do
{
if (layerEntityId.IsValid())
{
layerAncestry.push(layerEntityId);
}
AZ::EntityId parentId;
AZ::TransformBus::EventResult(
parentId,
layerEntityId,
&AZ::TransformBus::Events::GetParentId);
if (layerEntityId == parentId)
{
break;
}
layerEntityId = parentId;
} while (layerEntityId.IsValid());
}
void BuildNewLayerRow(QMenu* assignToLayerMenu,
const AzToolsFramework::EntityIdSet& entitySelectionWithFlatHierarchy,
NewLayerFunction newLayerFunction)
{
QWidgetAction* assignToLayerAction = new QWidgetAction(assignToLayerMenu);
QWidget* assignToLayerWidget = new QWidget(assignToLayerMenu);
// Special behavior and properties are defined in NewEditorStyleSheet.qss based on this name.
assignToLayerWidget->setObjectName("LayerHierarchyMenuItem");
// Add class to fix hover state styling for WidgetAction
AzQtComponents::Style::addClass(assignToLayerWidget, "WidgetAction");
QHBoxLayout* assignToLayerLayout = new QHBoxLayout(assignToLayerWidget);
assignToLayerAction->setDefaultWidget(assignToLayerWidget);
QLabel* layerLabel = new QLabel(QObject::tr("New"), assignToLayerMenu);
assignToLayerLayout->addWidget(layerLabel);
QObject::connect(assignToLayerAction, &QAction::triggered,
[entitySelectionWithFlatHierarchy, newLayerFunction]
{
AZ::EntityId newLayerId(newLayerFunction());
AssignParentToAllEntities(newLayerId, entitySelectionWithFlatHierarchy);
});
assignToLayerMenu->addAction(assignToLayerAction);
}
void SetupAddToLayerMenu(
QMenu* parentMenu,
const AzToolsFramework::EntityIdSet& entitySelectionWithFlatHierarchy,
NewLayerFunction newLayerFunction)
{
// If nothing is selected, there is no reason to make the menu.
if (entitySelectionWithFlatHierarchy.size() == 0)
{
return;
}
QMenu* assignToLayerMenu = new QMenu(parentMenu);
assignToLayerMenu->setTitle(QObject::tr("Assign to layer"));
parentMenu->addMenu(assignToLayerMenu);
BuildNewLayerRow(assignToLayerMenu, entitySelectionWithFlatHierarchy, newLayerFunction);
assignToLayerMenu->addSeparator();
AZStd::vector<AZ::Entity*> editorEntities;
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
&AzToolsFramework::EditorEntityContextRequestBus::Events::GetLooseEditorEntities,
editorEntities);
// Construct the hierarchy of layer rows, so child layers can appear under their parents correctly.
EntityIdToLayerMenuRow layerRootRows;
for (AZ::Entity* entity : editorEntities)
{
bool isLayer = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
isLayer,
entity->GetId(),
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
if (!isLayer)
{
continue;
}
AZStd::stack<AZ::EntityId> layerAncestry;
BuildLayerAcenstry(layerAncestry, entity->GetId());
EntityIdToLayerMenuRow* currentRow = &layerRootRows;
while (!layerAncestry.empty())
{
AZ::EntityId ancestorId(layerAncestry.top());
layerAncestry.pop();
EntityIdToLayerMenuRow::iterator currentAncestorRow = currentRow->find(ancestorId);
if (currentAncestorRow == currentRow->end())
{
currentRow->insert(AZStd::pair<AZ::EntityId, AZStd::shared_ptr<LayerMenuRow>>(ancestorId, AZStd::make_shared<LayerMenuRow>()));
currentAncestorRow = currentRow->find(ancestorId);
}
currentRow = &currentAncestorRow->second->m_rowChildren;
if (currentAncestorRow->first == entity->GetId())
{
currentAncestorRow->second->m_rowLabel = entity->GetName().c_str();
}
}
}
BuildAddToLayerRows(
assignToLayerMenu,
entitySelectionWithFlatHierarchy,
true,
layerRootRows,
0);
}
}
@@ -0,0 +1,27 @@
/*
* 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.
*
*/
#pragma once
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
namespace AzToolsFramework
{
using NewLayerFunction = AZStd::function<AZ::EntityId()>;
// Creates a pull out menu that allows the selected entities to be assigned to a new layer.
// This expects a flattened selection hierarchy to be given,
// no entities in the set should be children or grandchildren of other entities.
void SetupAddToLayerMenu(
QMenu* parentMenu,
const AzToolsFramework::EntityIdSet& entitySelectionWithFlatHierarchy,
NewLayerFunction newLayerFunction);
}
@@ -0,0 +1,47 @@
/*
* 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.
*
*/
#include "NameConflictWarning.hxx"
#include <AzCore/std/string/conversions.h>
namespace AzToolsFramework
{
namespace Layers
{
NameConflictWarning::NameConflictWarning(QWidget* parent, const AZStd::unordered_map<AZStd::string, int>& nameConflictMapping)
: QMessageBox(parent)
{
setWindowTitle(tr("Unable to save layers with duplicate names"));
setIcon(QMessageBox::Warning);
QString conflicts;
for (const AZStd::pair<AZStd::string, int>& nameConflict : nameConflictMapping)
{
QString layerHierachy = QString(nameConflict.first.c_str());
int lastOccurrence = layerHierachy.lastIndexOf(".");
QString entityName = layerHierachy.right(layerHierachy.length() - lastOccurrence - 1);
QString entityDirectory = lastOccurrence == -1 ? "" : layerHierachy.left(lastOccurrence);
entityDirectory = entityDirectory.replace(".", replacementStr);
entityDirectory = lastOccurrence == -1 ? QObject::tr("at the root level") : QObject::tr("in %1").arg(entityDirectory);
QString currentConflict = QObject::tr("%1 %2s %3\n").arg(nameConflict.second).arg(entityName).arg(entityDirectory);
conflicts = QObject::tr("%1%2").arg(conflicts).arg(currentConflict);
}
setText(QObject::tr("Save failed. Layer names at the same hierarchy level must be unique.\n\n"
"%1\n"
"Rename these layers and try again.").arg(conflicts));
}
}
}
@@ -0,0 +1,32 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/string/string.h>
#include <QMessageBox>
namespace AzToolsFramework
{
namespace Layers
{
class NameConflictWarning
: public QMessageBox
{
public:
NameConflictWarning(QWidget* parent, const AZStd::unordered_map<AZStd::string, int>& nameConflictMapping);
private:
const QString replacementStr = " > ";
};
}
}
@@ -0,0 +1,54 @@
/*
* 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.
*
*/
#ifndef EDITORCONTEXTBUS_H
#define EDITORCONTEXTBUS_H
#include <AzCore/base.h>
#pragma once
#include <AzCore/EBus/EBus.h>
namespace LegacyFramework
{
// editor contexts derive from this to do all the boilerplate messaging between their GUI and their contexts
// messages from the GUI parts head to this interface:
class EditorContextMessages
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// Bus configuration
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; // there's going to be many editor contexts for various kinds of assets, each with an 'address'.
typedef AZ::Uuid BusIdType;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ:: EBusHandlerPolicy::Single; // but there's only one such context at each address.
//////////////////////////////////////////////////////////////////////////
virtual ~EditorContextMessages() {}
};
// messages from the editor context to the gui parts head to this interface:
class EditorContextClientMessages
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// Bus configuration
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; // there's going to be more than one client in existence (for example every lua panel)
typedef AZ::Uuid BusIdType;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ:: EBusHandlerPolicy::Multiple; // and there might be more than one listener at that address (many panels for the same document for example)
//////////////////////////////////////////////////////////////////////////
virtual ~EditorContextClientMessages() {}
};
}
#endif
@@ -0,0 +1,100 @@
/*
* 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.
*
*/
#include "AzToolsFramework_precompiled.h"
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Component/ComponentApplication.h>
#include "EditorFrameworkAPI.h"
namespace LegacyFramework
{
const char* appName()
{
const char* result = NULL;
EBUS_EVENT_RESULT(result, FrameworkApplicationMessages::Bus, GetApplicationName);
return result;
}
const char* appModule()
{
const char* result = NULL;
EBUS_EVENT_RESULT(result, FrameworkApplicationMessages::Bus, GetApplicationModule);
return result;
}
const char* appDir()
{
const char* result = NULL;
EBUS_EVENT_RESULT(result, FrameworkApplicationMessages::Bus, GetApplicationDirectory);
return result;
}
bool RequiresGameProject()
{
bool result = false;
EBUS_EVENT_RESULT(result, FrameworkApplicationMessages::Bus, RequiresGameProject);
return result;
}
bool IsGUIMode()
{
bool result = false;
EBUS_EVENT_RESULT(result, FrameworkApplicationMessages::Bus, IsRunningInGUIMode);
return result;
}
bool appAbortRequested()
{
bool result = false;
EBUS_EVENT_RESULT(result, FrameworkApplicationMessages::Bus, GetAbortRequested);
return result;
}
bool isPrimary()
{
bool result = false;
EBUS_EVENT_RESULT(result, FrameworkApplicationMessages::Bus, IsPrimary);
return result;
}
bool IsAppConfigWritable()
{
bool result = false;
EBUS_EVENT_RESULT(result, FrameworkApplicationMessages::Bus, IsAppConfigWritable);
return result;
}
// helper function which retrieves the serialize context and asserts if its not found.
AZ::SerializeContext* GetSerializeContext()
{
AZ::SerializeContext* serializeContext = NULL;
EBUS_EVENT_RESULT(serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(serializeContext, "No serialize context");
return serializeContext;
}
void AddToPATH(const char* folder)
{
QString currentPath = QString::fromUtf8(qgetenv("PATH"));
currentPath.append(";");
currentPath.append(folder);
qputenv("PATH", currentPath.toUtf8());
}
bool ShouldRunAssetProcessor()
{
bool result = false;
EBUS_EVENT_RESULT(result, FrameworkApplicationMessages::Bus, ShouldRunAssetProcessor);
return result;
}
}
@@ -0,0 +1,337 @@
/*
* 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.
*
*/
#ifndef EditorFrameworkAPI_H
#define EditorFrameworkAPI_H
#include <AzCore/base.h>
#include <AzCore/Math/Uuid.h>
#include <AzCore/EBus/EBus.h>
#include <AzFramework/CommandLine/CommandLine.h>
#pragma once
// this file contains the API for the buses that the framework communicates on to NON-GUI-CLIENTS
// note that this does not include UI messaging, this is for non-ui parts of it!
namespace AZ
{
class SerializeContext;
}
#ifdef AZ_PLATFORM_WINDOWS
typedef HINSTANCE HMODULE;
#endif
namespace LegacyFramework
{
// we agree that an entity list is a list of entity IDs
typedef AZStd::vector<AZ::EntityId> EntityList;
typedef AZ::u32 IPCHandleType;
/** Core messages core messages go to whoever wants them. If you're interested in these messages, listen here.
* they are always broadcast (To all listeners, not by address)
*/
class CoreMessages
: public AZ::EBusTraits
{
public:
virtual ~CoreMessages() {}
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ:: EBusHandlerPolicy::MultipleAndOrdered; // there are many listeners
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; // this is a broadcast bus
bool Compare(const CoreMessages* rhs) const
{
return this < rhs;
}
virtual void OnRestoreState() {} /// sent when everything is registered up and ready to go, this is what bootstraps stuff to get going.
virtual void OnReady() {} /// sent after onrestorestate - the entire app should be up and running now.
/** in a GUI application, this is already implemented in UIFramework in order to initialize QT and then execute the main application event loop
* in that case, implment OnRestoreState() in your gui-related components to start them functioning.
* on the other hand, in a Non-GUI application (such as a console app, if you initialized it without QT), you will not have a QApplication and
* you need to implment RUN() yourself to do whatever it is you want to do (process assets, etc).
* you can use virtual _TCHAR** FrameworkApplicationMessages::GetArguments(int& argc) = 0; to get the command line arguments.
* if you use your own console app, its up to you to send OnRestoreState, OnGetPermissionToShutdown, OnSaveState, etc.
*/
virtual void Run() {}
/** Executed as part of inter-process communication during startup of a second instance.
* this happens INSTEAD of run, if you are a secondary instance of an application instead of the primary.
* by default, the UI Framework, when it gets this, sends the "open" message to the primary instance for every misc value on its command line
* causing the primary to attempt to open it instead of ourselves.
* you can also implement this yourself in order to send messages to the primary (if you wish).
* note: The project is not set ever as another instance...
*/
virtual void RunAsAnotherInstance() {}
/** this happens when the project manager has set its project.
* the next thing that will happen is you get an OnRestoreState and OnReady() from the UI manager - if you're a UI application
* otherwise, you'll have to do whatever you want at this point in your non-gui application.
*/
virtual void OnProjectSet(const char* /*pathToProject*/) {}
/** this is sent when the user hits the [x] button to close the entire application and the final important window is closed.
* Components should override this if its possible for the user to abort the shutdown and deny it. This is also your opportunity to
* kick off any saves and caches that need to be cached.
* If EVERYONE answers true, then everyone is polled constantly with /ref CheckOkeyToShutdown, and then when
* all listeners return true for that, the actual shutdown occurs.
*/
virtual bool OnGetPermissionToShutDown() { return true; }
/** sent to everything when the app is about to shut down - do what you need to do to ensure that state is stored somewhere.
* this happens before anyone destroys state ( /ref OnDestroyState will be called after all OnSaveState)
*/
virtual void OnSaveState() {}
/** this message gets sent for you to destroy state objects that depend on outside components
* an example is components that have GUIs (such as the Lua Editor) need to destroy their Qt Objects, because we will tear down the QApplication after sending this.
* this happens after /ref OnSaveState
*/
virtual void OnDestroyState() {}
/** During shutdown, this is Sent repeatedly until everyone responds TRUE.
* Once everyone returns true from /ref OnGetPermissionToShutDown, we then start to send /ref CheckOkayToShutDown periodically until
* everyone returns TRUE, after which /ref OnSaveState will occur, then /ref OnDestroyState , and then finally the component
* will be stopped and destroyed.
* After the user says its okay to shut down, this is called in a loop to give everyone time to flush their buffers / wait and cancel
* pending operations. So for example, if you return true to /ref OnGetPermissionToShutdown you should start saving your files
* and cleaning up in that function. Then check to see if your pending async calls are done in /ref CheckOkayToShutdown
*/
virtual bool CheckOkayToShutDown() { return true; } // until everyone returns true, we can't shut down.
/** happens when the framework goes in focus in the global OS sense.
* i.e if the ANY window belonging to the app comes to front, this is sent to all contexts.
*/
virtual void ApplicationDeactivated() {}
/** happens when the framework goes out of focus in the global OS sense.
* i.e if a different application gets focus, this is sent to all contexts.
*/
virtual void ApplicationActivated() {}
/** This is broadcast to ALL applications registered with AddComponentInfo
* Each listener must check to see if the Uuid given is the UUid they registered with, in AddComponentInfo.
* This allows listeners to react to more than one Uuid menu item. Perhaps one context is responsible for more than one main window.
*/
virtual void ApplicationShow(AZ::Uuid) {}
/** This is broadcast to ALL applications registered with AddComponentInfo
* Each listener must check to see if the Uuid given is the UUid they registered with, in AddComponentInfo.
* This allows listeners to react to more than one Uuid menu item. Perhaps one context is responsible for more than one main window.
*/
virtual void ApplicationHide(AZ::Uuid) {}
/** A request from the application framework to ask the system what root level windows may be open.
* If you don't respond to this by issuing ApplicationCensusReply on the framwork message bus, the application will be allowed to quit
* entirely, even if your window is open.
* You should respond to recieving this message by calling ApplicationCensusReply if your context has a window open that counts as
* one of the 'root' windows of an app that should stop the app from closing if the window is open. When the last window that responded true
* is closed, the entire editor quits. windows like floating browsers which are NOT part of the permanent context should not respond.
*/
virtual void ApplicationCensus() {}
};
typedef AZ::EBus<CoreMessages> CoreMessageBus;
/** Retrieves the name of the app you set in your application descriptor before calling Run
*/
const char* appName();
/** Returns the name of the binary that this application belongs to (the full path)
*/
const char* appModule();
/** Returns the full path to the folder that contains this application binary.
*/
const char* appDir();
/** Returns true if this is the first instance of this application run. False if another instance is already running.
*/
bool isPrimary();
/** Returns true if someone issued a SetAbortRequested(true) on the application bus.
* Can be used for console applications when someone presses CTRL+C or such.
*/
bool appAbortRequested();
/** returns true if the application descriptor was created using GUI Mode set to true.
*/
bool IsGUIMode();
/** Adds a given folder to the executable (and shared dynamic library) search path for the current process.
* Does not alter the actual system environment, only for this run of the application.
*/
void AddToPATH(const char* folder);
/** Returns true if the application global configuration file (appname.xml) is writeable.
*/
bool IsAppConfigWritable();
/** helper function which retrieves the serialize context and asserts if its not found.
*/
AZ::SerializeContext* GetSerializeContext();
/** Returns true if the application descriptor passed in during creation of the application had it set to true.
* If false, it means that you have not requested that there be a "game project".
* the project manager, which makes sure the project directory is set and can make new projects will in that case not activate
* use this only if you're making a standalone app that needs no project (ie, no assets or anything from a project folder).
*/
bool RequiresGameProject();
/** Returns true if this is an app which USES assets and needs them to be ready.
* As opposed to pipeline tools such as the Project Creator or the asset processor itself.
*/
bool ShouldRunAssetProcessor();
/** FrameworkApplicationMessages is how you communicate to the framework itself (instead of the above /ref CoreMessages which goes the other way).
*/
class FrameworkApplicationMessages
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ:: EBusHandlerPolicy::Single; // its a singleton
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; // its a singleton
typedef AZ::EBus<FrameworkApplicationMessages> Bus;
typedef Bus::Handler Handler;
virtual ~FrameworkApplicationMessages() {}
/** Equivalent to and used by /ref IsGUIMode
*/
virtual bool IsRunningInGUIMode() = 0;
/** Retrieve a Command Line Parser object that you can then use to check for values on the command line
*/
virtual const AzFramework::CommandLine* GetCommandLineParser() = 0;
/** (Windows) retrieves the main module of the executable.
* This is always going to be the main executable except in the situation where the framework may be running as a DLL belonging to another process or program.
*/
#ifdef AZ_PLATFORM_WINDOWS
virtual HMODULE GetMainModule() = 0;
#endif
virtual const char* GetApplicationName() = 0;
virtual const char* GetApplicationModule() = 0;
virtual const char* GetApplicationDirectory() = 0;
/** Internal use only.
*/
virtual void TeardownApplicationComponent() = 0;
/** Reads the return code set by /ref SetDesiredExitCode.
* Since the application itself is in control, your void main() can use this function to know what to return in case you're making tools that are used in batches.
*/
virtual int GetDesiredExitCode() = 0;
/**
* If you're making a command-line program built on the Application Framework, you can use /ref SetDesiredExitCode to manipulate the return code.
*/
virtual void SetDesiredExitCode(int code) = 0;
virtual bool GetAbortRequested() = 0; /// returns true if someone called /ref SetAbortRequested. See /ref appAbortRequested, the helper function that calls this
virtual void SetAbortRequested() = 0; /// Call this to indicate that something has gone wrong and we need to bail out (of a batch process)
/** returns a path that points at the place where we can store application data that is specific to this user.
* note that this is not the application name, its just the root of a folder that is guaranteed to not be temporary and guaranteed
* to be writable.
* it is USER-SPECIFIC, but not application specific
* on windows, for example, this would be the Users/Name/AppData/Roaming/
*/
virtual AZStd::string GetApplicationGlobalStoragePath() = 0;
/** this is true if you are the first, 'primary' instance running.
* if this is false it means another app of the same kind ran before you and is still running
*/
virtual bool IsPrimary() = 0;
virtual bool RequiresGameProject() = 0; /// see /ref RequiresGameProject above.
virtual bool IsAppConfigWritable() = 0; /// see /ref IsAppConfigWriteable above.
virtual bool ShouldRunAssetProcessor() = 0; /// see /ref ShouldRunAssetProcessor above.
/** Run the asset processor on this project.
* only valid on projects!
*/
virtual void RunAssetProcessor() {} // not required to be implemented
};
/** This bus communicates TO the log component from the outside
* Its used to send queries to the log component itself.
*/
class LogComponentAPI
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// Bus configuration
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; // we have one bus that we always broadcast to
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ:: EBusHandlerPolicy::Single; // every listener registers unordered on this bus
typedef AZ::EBus<LogComponentAPI> Bus;
//////////////////////////////////////////////////////////////////////////
virtual ~LogComponentAPI() {}
virtual void EnumWindowTypes(AZStd::vector<AZStd::string>& target) = 0; /// get me a list of all the types of windows we've seen
virtual void RegisterWindowType(const AZStd::string& source) = 0; /// add this type to the known list of window types:
};
// -------------------------- IPC "COMMANDS" ------------------------------
/** IPC command system allows commands to be sent to other instances of the same application
*/
class IPCCommandAPI
: public AZ::EBusTraits
{
public:
typedef AZStd::function<bool(const AZStd::string& parameters)> IPCCommandHandler;
//////////////////////////////////////////////////////////////////////////
// Bus configuration
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; // we have one bus that we always broadcast to
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ:: EBusHandlerPolicy::Single; // every listener registers unordered on this bus
//////////////////////////////////////////////////////////////////////////
virtual ~IPCCommandAPI() {}
/** Register a handler to handle system calls of a certain kind. The operating system will be invoking this from the "outside" and thus will be operating system 'verbs'
* example: handle = RegisterIPCHandler("open", SomeOpenFunction)
* example: handle = RegisterIPCHandler("reprocessfile" , AZStd::bind(&someclass::somefunc, this,...)))
* example: handle = RegisterIPCHandler("print" , PrintSomethingForSomeReason);
*/
virtual IPCHandleType RegisterIPCHandler(const char* commandName, IPCCommandHandler handlerFn) = 0;
virtual void UnregisterIPCHandler(IPCHandleType handle) = 0;
/** Called internally by the system.
* This exists so that IPC handlers, even if they get messages in other threads, will drain their queue only in the proper thread.
*/
virtual void ExecuteIPCHandlers() = 0;
/** call this to send an IPC command to the primary
* you can only do this if you're not a primary. It can be any command you'd like the primary to perform as long as there is a handler
* registered with /ref RegisterIPCHandler
*/
virtual void SendIPCCommand(const char* commandName, const char* parameters) = 0;
};
typedef AZ::EBus<IPCCommandAPI> IPCCommandBus;
};
#endif
@@ -0,0 +1,532 @@
/*
* 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.
*
*/
#include "AzToolsFramework_precompiled.h"
#include <AzCore/PlatformIncl.h>
#include "EditorFrameworkApplication.h"
#include <time.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/Math/Sfmt.h>
#include <AzCore/std/time.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Memory/MemoryComponent.h>
#include <AzCore/Jobs/JobManagerComponent.h>
#include <AzCore/Asset/AssetManagerComponent.h>
#include <AzCore/Script/ScriptSystemComponent.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/Streamer/StreamerComponent.h>
#include <AzFramework/CommandLine/CommandLine.h>
#include <AzToolsFramework/UI/LegacyFramework/UIFramework.hxx>
#include <AzToolsFramework/UI/LegacyFramework/CustomMenus/CustomMenusAPI.h>
#include <AzFramework/Asset/AssetCatalogComponent.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/TargetManagement/TargetManagementComponent.h>
#include <AzFramework/Driller/RemoteDrillerInterface.h>
#include <AzCore/Driller/Driller.h>
#include <AzCore/Debug/ProfilerDriller.h>
#ifdef AZ_PLATFORM_WINDOWS
#include "shlobj.h"
#endif
#if AZ_TRAIT_OS_PLATFORM_APPLE
# include <mach-o/dyld.h>
#endif
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 'QFileInfo::d_ptr': class 'QSharedDataPointer<QFileInfoPrivate>' needs to have dll-interface to be used by clients of class 'QFileInfo'
#include <QFileInfo>
AZ_POP_DISABLE_OVERRIDE_WARNING
#include <QSharedMemory>
#include <QStandardPaths>
namespace LegacyFramework
{
ApplicationDesc::ApplicationDesc(const char* name, int argc, char** argv)
: m_applicationModule(NULL)
, m_enableGridmate(true)
, m_enablePerforce(true)
, m_enableGUI(true)
, m_enableProjectManager(true)
, m_shouldRunAssetProcessor(true)
, m_saveUserSettings(true)
, m_argc(argc)
, m_argv(argv)
{
m_applicationName[0] = 0;
if (name)
{
azstrcpy(m_applicationName, _MAX_PATH, name);
}
}
ApplicationDesc::ApplicationDesc(const ApplicationDesc& other)
{
this->operator=(other);
}
ApplicationDesc& ApplicationDesc::operator=(const ApplicationDesc& other)
{
if (this == &other)
{
return *this;
}
m_applicationModule = other.m_applicationModule;
m_enableGUI = other.m_enableGUI;
m_enableGridmate = other.m_enableGridmate;
m_enablePerforce = other.m_enablePerforce;
azstrcpy(m_applicationName, _MAX_PATH, other.m_applicationName);
m_enableProjectManager = other.m_enableProjectManager;
m_shouldRunAssetProcessor = other.m_shouldRunAssetProcessor;
m_saveUserSettings = other.m_saveUserSettings;
m_argc = other.m_argc;
m_argv = other.m_argv;
return *this;
}
Application::Application()
{
m_isPrimary = true;
m_desiredExitCode = 0;
m_abortRequested = false;
m_applicationEntity = NULL;
m_ptrSystemEntity = NULL;
m_applicationModule[0] = 0;
m_appRoot[0] = 0;
}
HMODULE Application::GetMainModule()
{
return m_desc.m_applicationModule;
}
const char* Application::GetApplicationName()
{
return m_desc.m_applicationName;
}
const char* Application::GetApplicationModule()
{
return m_applicationModule;
}
const char* Application::GetApplicationDirectory()
{
return GetExecutableFolder();
}
#ifdef AZ_PLATFORM_WINDOWS
BOOL CTRL_BREAK_HandlerRoutine(DWORD /*dwCtrlType*/)
{
EBUS_EVENT(FrameworkApplicationMessages::Bus, SetAbortRequested);
return TRUE;
}
#endif
AZStd::string Application::GetApplicationGlobalStoragePath()
{
return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation).toUtf8().data();
}
void Application::SetSettingsRegistrySpecializations(AZ::SettingsRegistryInterface::Specializations& specializations)
{
ComponentApplication::SetSettingsRegistrySpecializations(specializations);
specializations.Append("legacy");
specializations.Append("tools");
specializations.Append("editor");
}
int Application::Run(const ApplicationDesc& desc)
{
if (!AZ::AllocatorInstance<AZ::OSAllocator>::IsReady())
{
AZ::AllocatorInstance<AZ::OSAllocator>::Create();
}
QString appNameConcat = QStringLiteral("%1_GLOBALMUTEX").arg(desc.m_applicationName);
{
// If the application crashed before, it may have left behind shared memory
// We can go ahead and do a quick attach/detach here to clean it up
QSharedMemory fix(appNameConcat);
fix.attach();
}
QSharedMemory* shared = new QSharedMemory(appNameConcat);
if (qApp)
{
QObject::connect(qApp, &QCoreApplication::aboutToQuit, qApp, [shared]() { delete shared; });
}
m_isPrimary = shared->create(1);
m_desc = desc;
// Enable next line to load from the last state
// if left commented, we will start from scratch
// THE FOLLOWING LINE CREATES THE MEMORY MANAGER SUBSYSTEM, do not allocate memory before this call!
m_ptrSystemEntity = Create({});
AZ::Debug::Trace::HandleExceptions(false);
FrameworkApplicationMessages::Handler::BusConnect();
CoreMessageBus::Handler::BusConnect();
#ifdef AZ_PLATFORM_WINDOWS
// if we're in console mode, listen for CTRL+C
::SetConsoleCtrlHandler(CTRL_BREAK_HandlerRoutine, true);
#endif
m_ptrCommandLineParser = aznew AzFramework::CommandLine();
m_ptrCommandLineParser->Parse(m_desc.m_argc, m_desc.m_argv);
if (m_ptrCommandLineParser->HasSwitch("app-root"))
{
auto appRootOverride = m_ptrCommandLineParser->GetSwitchValue("app-root", 0);
if (!appRootOverride.empty())
{
m_appRoot = appRootOverride;
}
}
// If we don't have one create a serialize context
if (GetSerializeContext() == nullptr)
{
CreateReflectionManager();
}
CreateSystemComponents();
m_ptrSystemEntity->Init();
m_ptrSystemEntity->Activate();
// If we aren't the primary, RunAsAnotherInstance unless we are being forcestarted
if (!m_isPrimary && !m_ptrCommandLineParser->HasSwitch("forcestart"))
{
// Required for the application component to handle RunAsAnotherInstance
CreateApplicationComponent();
// if we're not the primary instance, what exactly do we do? This is a generic framework - not a specific app
// and what we do might depend on implementation specifics for each app.
EBUS_EVENT(LegacyFramework::CoreMessageBus, RunAsAnotherInstance);
}
else
{
//if (!RequiresGameProject())
{
// we won't be getting the project set message
CreateApplicationComponent();
}
EBUS_EVENT(LegacyFramework::CoreMessageBus, Run);
// as a precaution here, we save our app and system entities BEFORE we destroy anything
// so that we have the highest chance of storing the user's precious application state and preferences
// even if someone has done something bad on their destructor or shutdown
SaveApplicationEntity();
}
if (m_applicationEntity)
{
m_applicationEntity->Deactivate();
delete m_applicationEntity;
m_applicationEntity = NULL;
}
AZ::SystemTickBus::ExecuteQueuedEvents();
AZ::TickBus::ExecuteQueuedEvents();
#ifdef AZ_PLATFORM_WINDOWS
// clean up!
::SetConsoleCtrlHandler(CTRL_BREAK_HandlerRoutine, false);
#endif
delete m_ptrCommandLineParser;
m_ptrCommandLineParser = NULL;
CoreMessageBus::Handler::BusDisconnect();
FrameworkApplicationMessages::Handler::BusDisconnect();
m_ptrSystemEntity->Deactivate();
Destroy();
return GetDesiredExitCode();
}
void Application::TeardownApplicationComponent()
{
SaveApplicationEntity();
if (m_applicationEntity)
{
m_applicationEntity->Deactivate();
delete m_applicationEntity;
m_applicationEntity = NULL;
}
}
const AzFramework::CommandLine* Application::GetCommandLineParser()
{
return m_ptrCommandLineParser;
}
// returns TRUE if the component already existed, FALSE if it had to create one.
bool Application::EnsureComponentCreated(AZ::Uuid componentCRC)
{
if (m_applicationEntity)
{
// if the component already exists on the system entity, this is an error.
if (auto comp = m_ptrSystemEntity->FindComponent(componentCRC))
{
AZ_Warning("EditorFramework", 0, "Attempt to add a component that already exists on the system entity: %s\n", comp->RTTI_TypeName());
return true;
}
if (!m_applicationEntity->FindComponent(componentCRC))
{
if (m_applicationEntity->GetState() != AZ::Entity::State::Constructed)
{
AZ_Warning("EditorFramework", 0, "Attempt to add a component 0x%08x to the application entity when the application entity has already been activated\n", componentCRC);
return false;
}
m_applicationEntity->CreateComponent(componentCRC);
}
return true;
}
if (m_ptrSystemEntity->GetState() != AZ::Entity::State::Constructed)
{
AZ_Warning("EditorFramework", 0, "Attempt to add a component 0x%08x to the system entity when the system entity has already been activated\n", componentCRC);
return false;
}
if (!m_ptrSystemEntity->FindComponent(componentCRC))
{
m_ptrSystemEntity->CreateComponent(componentCRC);
return false;
}
return true;
}
// returns TRUE if the component existed, FALSE if the component did not exist.
bool Application::EnsureComponentRemoved(AZ::Uuid componentCRC)
{
if (m_applicationEntity)
{
if (auto comp = m_applicationEntity->FindComponent(componentCRC))
{
if (m_applicationEntity->GetState() != AZ::Entity::State::Constructed)
{
AZ_Warning("EditorFramework", 0, "Attempt to remove a component %s (0x%08x) from the application entity when the application entity has already been activated\n", comp->RTTI_GetTypeName(), componentCRC);
return true;
}
m_applicationEntity->RemoveComponent(comp);
delete comp;
return true;
}
return false;
}
if (m_ptrSystemEntity)
{
if (auto comp = m_ptrSystemEntity->FindComponent(componentCRC))
{
if (m_ptrSystemEntity->GetState() != AZ::Entity::State::Constructed)
{
AZ_Warning("EditorFramework", 0, "Attempt to remove a component %s (0x%08x) from the system entity when the entity entity has already been activated\n", comp->RTTI_GetTypeName(), componentCRC);
return true;
}
m_ptrSystemEntity->RemoveComponent(comp);
delete comp;
return true;
}
}
return false;
}
void Application::CreateApplicationComponent()
{
// create the application entity:
if (m_applicationEntity)
{
return;
}
AZ_TracePrintf("EditorFramework", "Application::OnProjectSet -- Creating Application Entity.\n");
AZ_Assert(m_applicationEntity == nullptr, "Attempt to set a project while the project is still set.");
AZStd::string applicationFilePath;
AzFramework::StringFunc::Path::Join(GetExecutableFolder(), appName(), applicationFilePath);
applicationFilePath.append("_app.xml");
AZ_Assert(applicationFilePath.size() <= _MAX_PATH, "Application path longer than expected");
qstrcpy(m_applicationFilePath, applicationFilePath.c_str());
// load all application entities, if present:
AZ::IO::SystemFile cfg;
if (cfg.Open(m_applicationFilePath, AZ::IO::SystemFile::SF_OPEN_READ_ONLY))
{
AZ::IO::SystemFileStream stream(&cfg, false);
stream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
AZ::ObjectStream::LoadBlocking(&stream, *GetSerializeContext(),
[this](void* classPtr, const AZ::Uuid& classId, const AZ::SerializeContext* sc)
{
AZ::Entity* entity = sc->Cast<AZ::Entity*>(classPtr, classId);
if (entity)
{
m_applicationEntity = entity;
}
});
cfg.Close();
}
if (!m_applicationEntity)
{
m_applicationEntity = aznew AZ::Entity("WoodpeckerApplicationEntity");
}
CreateApplicationComponents();
m_applicationEntity->InvalidateDependencies();
m_applicationEntity->Init();
m_applicationEntity->Activate();
OnApplicationEntityActivated();
}
void Application::OnApplicationEntityActivated()
{
}
bool Application::IsAppConfigWritable()
{
return !AZ::IO::SystemFile::Exists(m_applicationFilePath) || AZ::IO::SystemFile::IsWritable(m_applicationFilePath);
}
void Application::OnProjectSet(const char* projectPath)
{
(void)projectPath;
CreateApplicationComponent();
}
void Application::RunAssetProcessor()
{
return;
}
void Application::SaveApplicationEntity()
{
if (!m_applicationEntity)
{
return;
}
// write the current applicaiton entity:
AZStd::string applicationFilePath;
AzFramework::StringFunc::Path::Join(GetExecutableFolder(), appName(), applicationFilePath);
applicationFilePath += "_app.xml";
QFileInfo fileAttribs(applicationFilePath.c_str());
bool writeIt = true;
if (fileAttribs.exists())
{
// file is found.
if (fileAttribs.isHidden() || !fileAttribs.isWritable())
{
writeIt = false;
}
}
if (writeIt)
{
using namespace AZ;
AZStd::string tmpFileName(applicationFilePath);
tmpFileName += ".tmp";
IO::FileIOStream stream(tmpFileName.c_str(), IO::OpenMode::ModeWrite);
if (!stream.IsOpen())
{
return;
}
AZ::SerializeContext* serializeContext = GetSerializeContext();
AZ_Assert(serializeContext, "ComponentApplication::m_serializeContext is NULL!");
ObjectStream* objStream = ObjectStream::Create(&stream, *serializeContext, ObjectStream::ST_XML);
bool entityWriteOk = objStream->WriteClass(m_applicationEntity);
AZ_Warning("ComponentApplication", entityWriteOk, "Failed to write application entity to application file %s!", applicationFilePath.c_str());
bool flushOk = objStream->Finalize();
AZ_Warning("ComponentApplication", flushOk, "Failed finalizing application file %s!", applicationFilePath.c_str());
if (entityWriteOk && flushOk)
{
if (IO::SystemFile::Rename(tmpFileName.c_str(), applicationFilePath.c_str(), true))
{
return;
}
AZ_Warning("ComponentApplication", false, "Failed to rename %s to %s.", tmpFileName.c_str(), applicationFilePath.c_str());
}
}
}
void Application::CreateApplicationComponents()
{
EnsureComponentCreated(AzFramework::TargetManagementComponent::RTTI_Type());
EnsureComponentCreated(AzFramework::DrillerNetworkConsoleComponent::RTTI_Type());
EnsureComponentCreated(AzFramework::DrillerNetworkAgentComponent::RTTI_Type());
}
void Application::CreateSystemComponents()
{
EnsureComponentCreated(AZ::MemoryComponent::RTTI_Type());
EnsureComponentCreated(AZ::JobManagerComponent::RTTI_Type());
EnsureComponentCreated(AZ::StreamerComponent::RTTI_Type());
AZ_Assert(!m_desc.m_enableProjectManager || m_desc.m_enableGUI, "Enabling the project manager in the application settings requires enabling the GUI as well.");
// if we're a GUI APP we need the UI Framework component:
if (m_desc.m_enableGUI)
{
EnsureComponentCreated(AzToolsFramework::Framework::RTTI_Type());
}
else
{
EnsureComponentCreated(AzToolsFramework::Framework::RTTI_Type());
}
}
//=========================================================================
// RegisterCoreComponents
// [6/18/2012]
//=========================================================================
void Application::RegisterCoreComponents()
{
ComponentApplication::RegisterCoreComponents();
RegisterComponentDescriptor(AzFramework::TargetManagementComponent::CreateDescriptor());
RegisterComponentDescriptor(AzFramework::DrillerNetworkConsoleComponent::CreateDescriptor());
RegisterComponentDescriptor(AzFramework::DrillerNetworkAgentComponent::CreateDescriptor());
RegisterComponentDescriptor(AzToolsFramework::Framework::CreateDescriptor());
}
}
@@ -0,0 +1,147 @@
/*
* 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.
*
*/
#ifndef EDITORFRAMEWORKAPPLICATION_H
#define EDITORFRAMEWORKAPPLICATION_H
#include <AzCore/base.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/UserSettings/UserSettingsProvider.h>
#include "EditorFrameworkAPI.h"
#pragma once
namespace AZ
{
class SerializeContext;
}
namespace LegacyFramework
{
struct ApplicationDesc
{
HMODULE m_applicationModule; // only necessary if you want to attach your application as a DLL plugin to another application, hosting it
bool m_enableGUI; // false if you want none of the QT or GUI functionality to exist. You cannot use project manager if you do this.
bool m_enableGridmate; // false if you want to not activate the network communications module.
bool m_enablePerforce; // false if you want to not activate perforce SCM integration. note that this will eventually become a plugin anyway
bool m_enableProjectManager; // false if you want to disable project management. No project path will be set and the project picker GUI will not appear.
bool m_shouldRunAssetProcessor; // false if you want to disable auto launching the asset processor.
bool m_saveUserSettings; // true by default - set it to false if you want not to store usersettings (ie, you have no per-user state because you're something like an asset builder!)
int m_argc;
char** m_argv;
char m_applicationName[_MAX_PATH];
ApplicationDesc(const char* name = "Application", int argc = 0, char** argv = nullptr);
ApplicationDesc(const ApplicationDesc& other);
ApplicationDesc& operator=(const ApplicationDesc& other);
private:
};
class Application
: public AZ::ComponentApplication
, protected FrameworkApplicationMessages::Handler
, protected CoreMessageBus::Handler
{
/// Create application, if systemEntityFileName is NULL, we will create with default settings.
public:
using CoreMessageBus::Handler::Run;
virtual int Run(const ApplicationDesc& desc);
Application();
protected:
// ------------------------------------------------------------------
// implementation of FrameworkApplicationMessages::Handler
virtual bool IsRunningInGUIMode() { return m_desc.m_enableGUI; }
virtual bool RequiresGameProject() { return m_desc.m_enableProjectManager; }
virtual bool ShouldRunAssetProcessor() { return m_desc.m_shouldRunAssetProcessor; }
virtual HMODULE GetMainModule();
virtual const char* GetApplicationName();
virtual const char* GetApplicationModule();
virtual const char* GetApplicationDirectory();
virtual const AzFramework::CommandLine* GetCommandLineParser();
virtual void TeardownApplicationComponent();
virtual void RunAssetProcessor() override;
// ------------------------------------------------------------------
void SetSettingsRegistrySpecializations(AZ::SettingsRegistryInterface::Specializations& specializations) override;
// ------------------------------------------------------------------
// implementation of CoreMessageBus::Handler
virtual void OnProjectSet(const char* /*pathToProject*/);
// ------------------------------------------------------------------
// This is called during the bootstrap and makes all the components we should have for SYSTEM minimal functionality.
// This happens BEFORE the project is ready. Think carefully before you add additional system components. Examples of system components
// are memory managers, crash reporters, log writers, and the project manager itself which lets you switch to a project.
virtual void CreateSystemComponents();
// once the project is ready, then the CreateApplicationComponents() function is called. Those components are guarinteed
// to run inside a the context of a "Project" and thus have access to project data such as assets.
virtual void CreateApplicationComponents();
// called after the application entity is completed to pass some specifics to generic components.
virtual void OnApplicationEntityActivated();
// you must call EnsureComponentCreated and EnsureComponentRemoved functions inside the above function create functions:
// returns TRUE if the component already existed, FALSE if it had to create one.
// adds it as a SYSTEM component if its called inside CreateSystemComponents
// adds it as an APPLICATION component if its called inside CreateApplicationComponents
// will ERROR if you remove an application component after the application
bool EnsureComponentCreated(AZ::Uuid componentCRC);
// returns TRUE if the component existed, FALSE if the component did not exist.
// will ERROR if you remove a system component after the system is booted
bool EnsureComponentRemoved(AZ::Uuid componentCRC);
/**
* This is the function that will be called instantly after the memory
* manager is created. This is where we should register all core component
* factories that will participate in the loading of the bootstrap file
* or all factories in general.
* When you create your own application this is where you should FIRST call
* ComponentApplication::RegisterCoreComponents and then register the application
* specific core components.
*/
virtual void RegisterCoreComponents();
AZ::Entity* m_ptrSystemEntity;
virtual int GetDesiredExitCode() override { return m_desiredExitCode; }
virtual void SetDesiredExitCode(int code) override { m_desiredExitCode = code; }
virtual bool GetAbortRequested() override { return m_abortRequested; }
virtual void SetAbortRequested() override { m_abortRequested = true; }
virtual AZStd::string GetApplicationGlobalStoragePath() override;
virtual bool IsPrimary() override { return m_isPrimary; }
virtual bool IsAppConfigWritable() override;
AZ::Entity* m_applicationEntity;
private:
void CreateApplicationComponent();
void SaveApplicationEntity();
char m_applicationModule[_MAX_PATH];
int m_desiredExitCode;
bool m_isPrimary;
volatile bool m_abortRequested; // if you CTRL+C in a console app, this becomes true. its up to you to check...
char m_applicationFilePath[_MAX_PATH];
ApplicationDesc m_desc;
AzFramework::CommandLine* m_ptrCommandLineParser;
};
}
#endif // EDITORFRAMEWORKAPPLICATION_H
@@ -0,0 +1,267 @@
/*
* 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.
*
*/
#include "AzToolsFramework_precompiled.h"
#include "IPCComponent.h"
#include <AzCore/std/parallel/thread.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Serialization/EditContext.h>
namespace LegacyFramework
{
void IPCComponent::Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<IPCComponent, AZ::Component>()
->Version(1)
;
}
}
IPCComponent::IPCComponent()
{
m_LastAssignedHandle = 1;
m_isPrimary = false;
}
IPCComponent::~IPCComponent()
{
}
void IPCComponent::Init()
{
IPCCommandBus::Handler::BusConnect();
}
void IPCComponent::Activate()
{
m_isPrimary = LegacyFramework::isPrimary();
AZStd::string ipcName = AZStd::string::format("%s-IPCComponent", LegacyFramework::appName());
m_success = true;
// create / flush buffer if primary:
if (!m_IPCBuffer.Create(ipcName.c_str(), 8 * 1024, true))
{
AZ_Warning("App", false, "PRIMARY: Could not Open IPC buffer, open files will not work.");
m_success = false;
}
if ((m_success) && (!m_IPCBuffer.Map()))
{
AZ_Warning("App", false, "PRIMARY: Could not Map IPC buffer, open files will not work.");
m_success = false;
m_IPCBuffer.Close();
}
if ((m_isPrimary) && (m_success))
{
// start the listen thread:
m_ProcessThread = AZStd::thread(AZStd::bind(&IPCComponent::ProcessThread, this));
}
}
void IPCComponent::Deactivate()
{
m_ShutdownThread = true;
if (m_success)
{
if (m_isPrimary)
{
m_ProcessThread.join();
}
m_IPCBuffer.UnMap();
m_IPCBuffer.Close();
}
}
IPCHandleType IPCComponent::RegisterIPCHandler(const char* commandName, IPCCommandHandler handlerFn)
{
m_RegisteredIPCHandlers[++m_LastAssignedHandle] = RegisteredIPCHandler(commandName, handlerFn);
return m_LastAssignedHandle;
}
void IPCComponent::UnregisterIPCHandler(IPCHandleType handle)
{
m_RegisteredIPCHandlers.erase(handle);
}
// call this to send an IPC command to the primary. you can only do this if you're not a primary
void IPCComponent::SendIPCCommand(const char* commandName, const char* params)
{
AZ_Assert(!m_isPrimary, "Only clients may send commands");
if (m_isPrimary)
{
return;
}
if (!m_success)
{
AZ_Warning("IPC", m_success, "Unable to send IPC command - we never were able to attach to the Shared Memory");
return;
}
unsigned int commandSize = (unsigned int)strlen(commandName);
unsigned int dataSize = (unsigned int)strlen(params);
if (commandSize + dataSize > 4096)
{
AZ_Warning("IPC", m_success, "Unable to send IPC command - The data is too big");
return;
}
{
AZ::SharedMemory::MemoryGuard g(m_IPCBuffer);
if (!m_IPCBuffer.Write(LegacyFramework::appName(), (unsigned int)strlen(LegacyFramework::appName())))
{
return;
}
if (!m_IPCBuffer.Write(&commandSize, sizeof(unsigned int)))
{
return;
}
if (!m_IPCBuffer.Write(&dataSize, sizeof(unsigned int)))
{
return;
}
if (!m_IPCBuffer.Write(commandName, commandSize))
{
return;
}
if (!m_IPCBuffer.Write(params, dataSize))
{
return;
}
}
}
void IPCComponent::ProcessThread()
{
m_ShutdownThread = false;
bool flush = true;
char databuffer[4096] = {0};
while (!m_ShutdownThread)
{
if (flush)
{
AZ::SharedMemory::MemoryGuard g(m_IPCBuffer);
m_IPCBuffer.Clear();
flush = false;
continue;
}
unsigned int dataSize = 0;
{
dataSize = 0;
AZ::SharedMemory::MemoryGuard g(m_IPCBuffer);
if (m_IPCBuffer.IsLockAbandoned())
{
flush = true;
continue;
}
else
{
dataSize = m_IPCBuffer.Read(databuffer, (sizeof(unsigned int) * 2) + (unsigned int)strlen(LegacyFramework::appName()));
}
}
if (dataSize == 0)
{
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(10)); // how do we deal with this?
continue; // no data
}
if (dataSize < (sizeof(unsigned int) * 2) + strlen(LegacyFramework::appName()))
{
AZ_Warning("Application", false, "Invalid size of IPC receieved on the IPC SHM socket - flushing.");
flush = true;
continue;
}
if (strncmp(databuffer, LegacyFramework::appName(), strlen(LegacyFramework::appName())) != 0)
{
AZ_Warning("Application", false, "Invalid header of IPC receieved on the IPC SHM socket.");
flush = true;
continue;
}
unsigned int* currentReadPos = reinterpret_cast<unsigned int*>(databuffer + strlen(LegacyFramework::appName()));
unsigned int commandSize = *currentReadPos;
++currentReadPos;
unsigned int contentsSize = *currentReadPos;
++currentReadPos;
if (commandSize + contentsSize > 4096)
{
AZ_Warning("Application", false, "Invalid size of read-in IPC receieved on the IPC SHM.");
flush = true;
continue;
}
// now read that data:
dataSize = m_IPCBuffer.Read(databuffer, commandSize + contentsSize);
if (dataSize < commandSize + contentsSize)
{
AZ_Warning("Application", false, "Truncated read in IPC SHM.");
flush = true;
continue;
}
char* currentStringReadPos = reinterpret_cast<char*>(databuffer);
AZStd::string command;
AZStd::string contents;
command.assign(currentStringReadPos, currentStringReadPos + commandSize);
currentStringReadPos += commandSize;
contents.assign(currentStringReadPos, currentStringReadPos + contentsSize);
{
AZStd::lock_guard<AZStd::recursive_mutex> guard(m_CommandListMutex);
m_CommandsWaitingToExecute.push_back(WaitingIPCCommand(command.c_str(), contents.c_str()));
}
EBUS_QUEUE_FUNCTION(AZ::SystemTickBus, &IPCComponent::ExecuteIPCHandlers, this);
}
}
// call this to execute handlers. They will be executed in the caller's thread.
void IPCComponent::ExecuteIPCHandlers()
{
CommandContainer batch;
{
AZStd::lock_guard<AZStd::recursive_mutex> guard(m_CommandListMutex);
batch = AZStd::move(m_CommandsWaitingToExecute);
}
for (auto command = batch.begin(); command != batch.end(); ++command)
{
bool foundHandler = false;
for (auto handler = m_RegisteredIPCHandlers.begin(); handler != m_RegisteredIPCHandlers.end(); ++handler)
{
if (handler->second.m_commandName.compare(command->m_commandName) == 0)
{
handler->second.m_handler(command->m_Parameters);
foundHandler = true;
}
}
if (!foundHandler)
{
AZ_Warning("Application", false, "No handler for IPC: '%s' '%s", command->m_commandName.c_str(), command->m_Parameters.c_str());
}
}
}
}
@@ -0,0 +1,104 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/base.h>
#include <AzCore/IPC/SharedMemory.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Component/Component.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/parallel/thread.h>
#include <AzCore/std/parallel/atomic.h>
#include "EditorFrameworkAPI.h"
namespace LegacyFramework
{
class IPCComponent
: public AZ::Component
, private IPCCommandBus::Handler
{
public:
AZ_COMPONENT(LegacyFramework::IPCComponent, "{798D5FE3-034B-406A-9830-A6ED2AF05E26}")
IPCComponent();
virtual ~IPCComponent();
//////////////////////////////////////////////////////////////////////////
// AZ::Component
virtual void Init();
virtual void Activate();
virtual void Deactivate();
//////////////////////////////////////////////////////////////////////////
private:
//////////////////////////////////////////////////////////////////////////
// IPCCommandBus::Listener
virtual IPCHandleType RegisterIPCHandler(const char* commandName, IPCCommandHandler handlerFn);
virtual void UnregisterIPCHandler(IPCHandleType handle);
// call this to execute handlers. They will be executed in the caller's thread.
virtual void ExecuteIPCHandlers();
// call this to send an IPC command to the primary. you can only do this if you're not a primary
virtual void SendIPCCommand(const char* commandName, const char* params);
//////////////////////////////////////////////////////////////////////////
class RegisteredIPCHandler
{
public:
AZStd::string m_commandName; // "open"
IPCCommandHandler m_handler;
RegisteredIPCHandler(const char* command, IPCCommandHandler handler)
: m_commandName(command)
, m_handler(handler)
{
}
RegisteredIPCHandler()
: m_handler(NULL) {}
};
struct WaitingIPCCommand
{
AZStd::string m_commandName;
AZStd::string m_Parameters;
WaitingIPCCommand(const char* commandName, const char* params)
: m_commandName(commandName)
, m_Parameters(params) {}
};
void ProcessThread();
typedef AZStd::vector<WaitingIPCCommand> CommandContainer;
typedef AZStd::unordered_map<IPCHandleType, RegisteredIPCHandler> IPCHandlerContainer;
IPCHandlerContainer m_RegisteredIPCHandlers;
CommandContainer m_CommandsWaitingToExecute;
AZStd::recursive_mutex m_CommandListMutex;
AZStd::thread m_ProcessThread;
AZStd::atomic_bool m_ShutdownThread;
IPCHandleType m_LastAssignedHandle;
AZ::SharedMemoryRingBuffer m_IPCBuffer;
bool m_isPrimary;
bool m_success; // are we able to send?
static void Reflect(AZ::ReflectContext* reflection);
};
}
@@ -0,0 +1,101 @@
/*
* 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.
*
*/
#ifndef CUSTOMMENUSAPI_H
#define CUSTOMMENUSAPI_H
#include <AzCore/base.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/Math/Crc.h>
#include <QtCore/QString>
#pragma once
class QMenu;
namespace AZ
{
class ComponentApplication;
}
namespace LegacyFramework
{
namespace CustomMenusCommon
{
struct WorldEditor
{
static const AZ::Crc32 Woodpecker;
static const AZ::Crc32 File;
static const AZ::Crc32 Debug;
static const AZ::Crc32 Edit;
static const AZ::Crc32 Build;
};
struct Driller
{
static const AZ::Crc32 Woodpecker;
static const AZ::Crc32 DrillerMenu;
static const AZ::Crc32 Channels;
};
struct LUAEditor
{
static const AZ::Crc32 Woodpecker;
static const AZ::Crc32 File;
static const AZ::Crc32 Edit;
static const AZ::Crc32 View;
static const AZ::Crc32 Debug;
static const AZ::Crc32 SourceControl;
static const AZ::Crc32 Options;
};
struct Viewport
{
static const AZ::Crc32 Layout;
static const AZ::Crc32 Grid;
static const AZ::Crc32 View;
};
}
class CustomMenusMessages
: public AZ::EBusTraits
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ:: EBusHandlerPolicy::Single;
typedef AZ::EBus<CustomMenusMessages> Bus;
virtual ~CustomMenusMessages() {}
//when a menu item is clicked, the orignal entryId used to register this callback will be passed back to you.
using MenuSelectedType = AZStd::function<void(AZ::Crc32)>;
virtual void RegisterMenu(AZ::Crc32 menuId, QMenu* menu) = 0;
//can pass AZ::Crc32() (i.e. 0) for hotkeyId if you don't want to assign a hotkey
virtual void AddMenuEntry(AZ::Crc32 menuId, AZ::Crc32 entryId, const QString& menuText, AZ::Crc32 hotkeyId, MenuSelectedType callback) = 0;
};
//Typically want a component factory to listen for this, and register its menus using CustomMenusMessages bus when it gets the
//RegisterMenuEntries call. RegisterMenuEntries only gets called once with the CustomMenusComponent is activated, so only
//things that are brought up early can listen in on this, like a component factory.
class CustomMenusRegistration
: public AZ::EBusTraits
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ:: EBusHandlerPolicy::Multiple;
typedef AZ::EBus<CustomMenusRegistration> Bus;
virtual ~CustomMenusRegistration() {}
virtual void RegisterMenuEntries() = 0;
};
}
#endif
@@ -0,0 +1,152 @@
/*
* 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.
*
*/
#include "AzToolsFramework_precompiled.h"
#include <AzCore/Component/Component.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzToolsFramework/UI/LegacyFramework/UIFrameworkAPI.h>
#include <QtWidgets/qaction.h>
#include "CustomMenusAPI.h"
#include <QMenu>
namespace LegacyFramework
{
namespace CustomMenusCommon
{
const AZ::Crc32 WorldEditor::Woodpecker = AZ_CRC("World Editor - Woodpecker", 0x16f7b092);
const AZ::Crc32 WorldEditor::File = AZ_CRC("World Editor - File", 0x4b085508);
const AZ::Crc32 WorldEditor::Debug = AZ_CRC("World Editor - Debug", 0x7f0e4892);
const AZ::Crc32 WorldEditor::Edit = AZ_CRC("World Editor - Edit", 0x46a2bd02);
const AZ::Crc32 WorldEditor::Build = AZ_CRC("World Editor - Build", 0xae0bfdee);
const AZ::Crc32 Driller::Woodpecker = AZ_CRC("Driller - Woodpecker", 0xbcc90840);
const AZ::Crc32 Driller::DrillerMenu = AZ_CRC("Driller - File", 0x5a98bdd8);
const AZ::Crc32 Driller::Channels = AZ_CRC("Driller - Debug", 0xf9cc0aae);
const AZ::Crc32 LUAEditor::Woodpecker = AZ_CRC("LUAEditor - Woodpecker", 0x9e204437);
const AZ::Crc32 LUAEditor::File = AZ_CRC("LUAEditor - File", 0xcf589de3);
const AZ::Crc32 LUAEditor::Edit = AZ_CRC("LUAEditor - Edit", 0xc2f275e9);
const AZ::Crc32 LUAEditor::View = AZ_CRC("LUAEditor - View", 0xbd3a007d);
const AZ::Crc32 LUAEditor::Debug = AZ_CRC("LUAEditor - Debug", 0x485223aa);
const AZ::Crc32 LUAEditor::SourceControl = AZ_CRC("LUAEditor - SourceControl", 0x7a3d336c);
const AZ::Crc32 LUAEditor::Options = AZ_CRC("LUAEditor - Options", 0x2f44057c);
const AZ::Crc32 Viewport::Layout = AZ_CRC("Viewport - Layout", 0x0d57aea6);
const AZ::Crc32 Viewport::Grid = AZ_CRC("Viewport - Grid", 0x005b038a);
const AZ::Crc32 Viewport::View = AZ_CRC("Viewport - View", 0xd0867133);
}
class CustomMenusComponent
: public AZ::Component
, private LegacyFramework::CustomMenusMessages::Bus::Handler
{
public:
AZ_COMPONENT(CustomMenusComponent, "{34A1245B-AA6B-41BE-8CFD-50141877081A}")
void Init() override {}
void Activate() override;
void Deactivate() override;
static void Reflect(AZ::ReflectContext* reflection);
private:
void RegisterMenu(AZ::Crc32 menuId, QMenu* menu) override;
void AddMenuEntry(AZ::Crc32 menuId, AZ::Crc32 entryId, const QString& menuText, AZ::Crc32 hotkeyId, MenuSelectedType callback) override;
void AddMenuItem(QMenu* menuItem, AZ::Crc32 entryId, const QString& menuText, AZ::Crc32 hotkeyId, MenuSelectedType callback);
struct MenuEntry
{
QString m_menuText;
MenuSelectedType m_callback;
AZ::Crc32 m_hotkeyId;
};
AZStd::unordered_map<AZ::Crc32, QMenu*> m_registeredMenus;
AZStd::unordered_map<AZ::Crc32, AZStd::unordered_map<AZ::Crc32, MenuEntry> > m_customMenusEntries;
};
}
namespace LegacyFramework
{
void CustomMenusComponent::Activate()
{
CustomMenusMessages::Bus::Handler::BusConnect();
EBUS_EVENT(LegacyFramework::CustomMenusRegistration::Bus, RegisterMenuEntries);
}
void CustomMenusComponent::Deactivate()
{
CustomMenusMessages::Bus::Handler::BusDisconnect();
}
void CustomMenusComponent::AddMenuItem(QMenu* menuItem, AZ::Crc32 entryId, const QString& menuText, AZ::Crc32 hotkeyId, MenuSelectedType callback)
{
auto action = new QAction(menuText, menuItem);
menuItem->addAction(action);
QObject::connect(action, &QAction::triggered, action, [entryId, callback](bool)
{
callback(entryId);
});
if (hotkeyId != AZ::Crc32())
{
EBUS_EVENT(AzToolsFramework::FrameworkMessages::Bus, RegisterActionToHotkey, hotkeyId, action);
}
}
void CustomMenusComponent::RegisterMenu(AZ::Crc32 menuId, QMenu* menu)
{
AZ_Assert(menu, "tried to register a nullptr as a menu");
if (!menu)
{
return;
}
m_registeredMenus[menuId] = menu;
auto menuEntries = m_customMenusEntries.find(menuId);
if (menuEntries != m_customMenusEntries.end())
{
for (const auto& menuEntry : menuEntries->second)
{
AddMenuItem(menu, menuEntry.first, menuEntry.second.m_menuText, menuEntry.second.m_hotkeyId, menuEntry.second.m_callback);
}
}
}
void CustomMenusComponent::AddMenuEntry(AZ::Crc32 menuId, AZ::Crc32 entryId, const QString& menuText, AZ::Crc32 hotkeyId, MenuSelectedType callback)
{
auto registeredMenu = m_registeredMenus.find(menuId);
if (registeredMenu != m_registeredMenus.end())
{
AddMenuItem(registeredMenu->second, entryId, menuText, hotkeyId, callback);
}
auto& menuEntries = m_customMenusEntries[menuId];
menuEntries[entryId] = MenuEntry {
menuText, callback, hotkeyId
};
}
void CustomMenusComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<CustomMenusComponent, AZ::Component>()
->Version(1)
;
}
}
}
@@ -0,0 +1,65 @@
/*
* 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.
*
*/
#include "AzToolsFramework_precompiled.h"
#include "MainWindowSavedState.h"
#include <AzCore/Serialization/SerializeContext.h>
#include <QtCore/QByteArray>
namespace AzToolsFramework
{
void MainWindowSavedState::Init(const QByteArray& windowState, const QByteArray& windowGeom)
{
m_serializableWindowState.clear();
m_windowState.assign((AZ::u8*)windowState.begin(), (AZ::u8*)windowState.end());
m_windowGeometry.assign((AZ::u8*)windowGeom.begin(), (AZ::u8*)windowGeom.end());
// save 2k at a time :( need a better way to do this.
AZStd::size_t pos = 0;
AZStd::size_t remaining = m_windowState.size();
AZ::u8* charData = (AZ::u8*)windowState.begin();
while (remaining > 0)
{
AZStd::size_t bytes_this_gulp = AZStd::min((AZStd::size_t)2000, remaining);
m_serializableWindowState.push_back();
m_serializableWindowState.back().assign((AZ::u8*)windowState.begin() + pos, (AZ::u8*)windowState.begin() + pos + bytes_this_gulp);
pos += bytes_this_gulp;
charData += bytes_this_gulp;
remaining -= bytes_this_gulp;
}
}
const AZStd::vector<AZ::u8>& MainWindowSavedState::GetWindowState()
{
m_windowState.clear();
for (auto it = m_serializableWindowState.begin(); it != m_serializableWindowState.end(); ++it)
{
m_windowState.insert(m_windowState.end(), it->begin(), it->end());
}
return m_windowState;
}
void MainWindowSavedState::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<MainWindowSavedState>()
->Version(2)
->Field("m_windowGeometry", &MainWindowSavedState::m_windowGeometry)
->Field("m_serializableWindowState", &MainWindowSavedState::m_serializableWindowState);
}
}
}
@@ -0,0 +1,56 @@
/*
* 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.
*
*/
#ifndef MAINWINDOWSAVEDSTATE_H
#define MAINWINDOWSAVEDSTATE_H
#include <AzCore/base.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/UserSettings/UserSettings.h>
#pragma once
class QByteArray;
namespace AZ { class ReflectContext; }
namespace AzToolsFramework
{
// the Main Window Saved State is a nice base class for you to derive your own Main Window state from
// (or just use your own in addition).
// it saves both the geometry and state of QMainWindows (so all splitter information, positioning, dock visibility, dock placement, etc)
class MainWindowSavedState
: public AZ::UserSettings
{
public:
AZ_RTTI(MainWindowSavedState, "{0892EAAA-8440-4409-9E8B-35BEF203E8E1}", AZ::UserSettings);
AZ_CLASS_ALLOCATOR(MainWindowSavedState, AZ::SystemAllocator, 0);
// the dh object stream cannot store more than 4k and sometimes the window state is about 5k.
AZStd::vector< AZStd::vector<AZ::u8> > m_serializableWindowState;
const AZStd::vector<AZ::u8>& GetWindowState();
AZStd::vector<AZ::u8> m_windowGeometry;
MainWindowSavedState() {}
virtual void Init(const QByteArray& windowState, const QByteArray& windowGeom);
static void Reflect(AZ::ReflectContext* context);
private:
AZStd::vector<AZ::u8> m_windowState;
};
}
#endif
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f3ebce4455dfb86c521249223fb63ea95483730879adae1864168a982db4d368
size 778
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2ce8b5ea5d2bfc0ca19ccd862cbabcd7890a89a8a94173df2f265a4334e13d32
size 217
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:446d15868f72979cb2ff0a1543a479a2d6fe24da19459d87f50955b4954fcdd0
size 547
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:aeab1a63733f9800f06b871b3986231aeb5bf6bb91ee95e4220d583edb8f69fd
size 399
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:84a972fd6d840b7d2ca4923eaaf9789f02a9c898f396a66baf63df7a468a6213
size 402
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0e8dafc24416b369b156f648f92154755d66ee7fd95e8cd351f6732617f1349c
size 459
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5894a3649b213cf5b2d673b6e7a871815fd1d120fa68a463592f27db14eae323
size 224592
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3ca680f2444cc9e50447d057c006464566e92f2e77b0b6e26491e5bd757ed4e7
size 213292
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0fcbdb5cbeea00ae532352c7c94a7d288ebc911ba85f4d595012032dcab64ba8
size 222584
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0eeca981116621a96a484ecc58fbfcdc78fda0065fd21fd13707b63bf8a9912c
size 213420
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a54dc8488f8193bf30c3820cf6f261f911f9d328d699e1a1b8042641554cec70
size 212896
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cf5f5184c1441a1660aa52526328e9d5c2793e77b6d8d3a3ad654bdb07ab8424
size 222412
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4482d18b30c4534b5481d594b7c0bc7a9913a7c4c261985e452010a89ab755fc
size 213128
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e64e508b2aa2880f907e470c4550980ec4c0694d103a43f36150ac3f93189bee
size 217360
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:aa3b0ef53db12e3d45094030cac0e69d384e44cc5978643dd4390041cad546e2
size 221328
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:30536248e59274548d51245662f9deec7fb52946faba33aade28c41473bdd39b
size 212820
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d15d4b2849984581ebe02d535ec1030e7da62ff189fe965f56eb4077ad75476d
size 283
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:febc3883ac09ea579877deb49a41441f618a1558f63a838ca3e216c65c638b1c
size 448
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7e89ed1a88e4b7488a34f9d5ecc3cf231d1c3b2b225fe956e009431ae0cd02d3
size 438
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b7226b603a86d0f49f1fcc0159389555c02814af5382ba7b52374cac7e38f3b8
size 446
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:63ed841be1103f4892ee5a6e9d7a8b0cdb0985a8267d445299aafc62b645c2fd
size 546
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:638cf180bfacf7438c9ac457d61a55df341ce5b8edd818be9c71c99b747684ef
size 503
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:47e1305d8ad0fa10377b3001fa213a7f16463339e9a15478605217a3412c9eee
size 491
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6c62d00483d3a82337900af814e1ac1c5d460681deddc98c3a120da5c8a8b72f
size 203
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3295d0d4cf26b6962f29c7fe7c2cee9abbbda06e7b0fe32cbe702ba1f8953a59
size 202
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3d623ad9cecbb6c3fee912eb39689fe5442f805cd41d32fd6d5a49f5c7b049f7
size 372
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6686a111a1d0f0e02d544d24135b9b94599fb332cd43912675c6840eb318bc00
size 3293
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d49c822d8f43f0f05dc8e2fa1ccee86ef3731cca074a586eb5b943caddaa6e52
size 3306
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7c8bf7b3f70d5f2683a8e495ddf80ecea1a437d63d259730becfd546286a8be2
size 3191
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1b4c23e60d84cfa16a310638ffc30d14c7821dbb852e96c5cbfb814aa0af3b1d
size 3244
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:beb00c64d91c8e9035414fba270f2b9477a1d15debb2d4acad0481256948b58d
size 298
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9b15e390624dc61ad6ae48faedb2f901f016615548747b6ed66296a8dd6ce5d7
size 296
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b85878ce2422dba963334dfc04648d893b7f17f9f4e4a395d6fea92c3dc5998e
size 198
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:47df1e68051bd26a470e5d9d8af45c26e33c56d06cc379f861d8300963b606de
size 188
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:13b2e25ce3d4e2e296d28aa3c16f508a2ddfe7ddf0059dba61e8240f392aa357
size 3253
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:da80a624e07ee68173bbfc703ccd4749b97e46e17daa90f6cb3f5b7714ebfa64
size 256
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4981a24c68b7e556cf04dcb166ceef20403df1553d93b443441efd5a4d4733d5
size 326
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9bf638af991ed405191882e22a4eccefa6082a9d27c0a3d289566b608684f76f
size 252
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:03f4deaeb2bc3d333e79e9b965027c567b619d57a534cb65e9d688be7ec71fa0
size 295
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:999e53df46c18d8330aa49b2f4f53817d7779c96350b0e9442a4393e4bfd2212
size 406
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7b9ad9b6bc7d5f0298308782a748165d4e2acd72f2c4647e3f7dd1e4fb51c405
size 201
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:af0ecf9a4422fcb85e56416b534379f249c2ce7672c4e7bba0ddd4285fc5e7c6
size 155
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dda98cbfd15ddf9da3bf15d5bfa1b72d2cb99449098ff5892e7a6a2dc462ac4e
size 553
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b707d50f813c0bf6f790c7f5d516d07e10104aad041f2edb636daae8967c9b9a
size 547
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7c12a74be91ba439fac5fb45e468a89c581422e70764b851c9d3e3fe02db9cf7
size 1018
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:32cfe5086e6f3d8ab620b5e73f50412fbac2497eb6a81d343f783eff30dc937d
size 296
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:87c1ae925b739758b82351ff24c9a8491f25e1264e41e94712f7d662ce9de740
size 175
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3f1ae476d3a328e926fe627edc8807c01b3b288efa6c537e783eab76e2a1d1bb
size 172
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cae142997599bfcd6f8779902725369f656ce587e647e302d928e4ac1ea27598
size 230
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c6fbdf95bf6e0947f44fa198d2e77227d9fbae63324bcac5c21df919e1e30bd7
size 1377
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9d0ba981139e8a413301e780c49793af32ba2335c9c3fbc4d4fd5f008dc70564
size 201
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:45c9409451358922ae7af32412ab75bda8ece25ad3be514805c988b557cf9ba0
size 215
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f0a3f1619f2f5fbd452aed701348c55ab1aeca81e5e45cac357637e7d93045bf
size 232
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2f7fa50d86b0efd9c157265d31efa69e007eb218f1e1e9578be8570314271cb8
size 282
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:73d7cfcfdf299bfb682a2bb4492493ad4a1a4e1c2a0b9bd2a305218fe446feae
size 781
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:51a714e628af4c7a1ef9cc59493074eb1717f48cfee22433558504d35ce4b650
size 836
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bd8d215d7a3ccd97a34c04694e26473d34794acdeccb08d32fafdafefd267e50
size 221
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c0bd2b6880f134fb6bbe3f458a073060380c07d295a6720dd8229bc9e17bbe9b
size 422
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7c27dc45e69bc24a9c40b174921cec00b19956a963b5a461f2d18c20ea8a5051
size 396
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9a2ef9475f76cfc5ced04fa784d2a6ca1a13822ef7cf4154977a9568d1a189a3
size 175
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:323e20d7679a22dcdfefe13bd470ec17154ff294ef45c14a0adf56ad87cc837b
size 1645
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c9c7f374e2b5b990797b6ebe1d511917b4c5cf0974320d6a71566ad87dd5e74e
size 228
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a090c136a51e5fdd4f18260039d0b25834c24fda87d5b4094954095f6c64ae55
size 1648
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:29f98d4b55674a38a87734be2028bb313bc85ae6360c3cb594cff466bbbf663b
size 723
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:26539410861d7d40fd1efce832ccb30c65c65993dc715b30df6a2b1132590df2
size 786
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b28018012b81654072166bcda16e691576329fd6e49c3d2a5cc4a69b1ed910bf
size 309
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:45ef61672397a14668f115e3154fb8a48138c98b6df73f5791c3f71b51bbbdd0
size 249
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fc40e6afefd6ed57de081d7f322a3896ebbb96204b93894b46ae0be28e4be01d
size 312
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c9753cf3c6b30647e98946d0f79ff778a5bfa1be61b3aceeebb9973b7b73cb1f
size 341
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1ef30e9abdcb8ea1ac4acc1cf43b66a34bc3d895b863c98d64bac5b2e65acd12
size 3099
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ceea1a8a0d9efc93636bc8531d91b955c05907119539448ead8c0c22ea5889a9
size 3253
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:74044e8843298d084be104d1c1c17fdfb52c3409c855e420d03b704df86b3064
size 375
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0d7f721fc68ff816eeceafe07ebf570d42950fac8e236a037a50c58009cc14e0
size 105
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:601450f502d08c01a24ad9767d96c2abf3d1ea93d617a1f20f18cf9f11b58065
size 239

Some files were not shown because too many files have changed in this diff Show More