f648cb1fd8
* Update the FileIOAlias naming to make the cache, project root and engine root paths more clear The alias of `@root@`, `@assets@`, and `@projectplatformcache@` has been collapsed to `@projectproductassets@` The alias of `@devroot@` and `@engroot@` has been collapsed to `@engroot@` The alias of `@devassets@` and `@projectroot@` has been collapsed to `@projectroot@` Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Updated use of devassets and devroot properties in python Those properties now use projectroot and engroot Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Updating the alias @engroot@ alias path comment in each platform specific LocalFileIO_*.cpp file Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Removed hardcoded size of 9 for the product asset alias. The ResolvePath function now just appends the @projectproductassets@ alias with the input path Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Remove duplicate @projectproductassets@ check in ProcessFileTreeRequest Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Fix for typos in Hydra python test Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Updated LocalFileIO::Copy call on Windows to use the Unicode aware CopyFileW API Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Updated the AWSMetreicsGemAllocatorFixture to properly suppress asset cache write errors for Test file creation. Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Removed unneeded call to set the @projectproductasstes@ alias at the bottom of the AssetSeedManagerTest SetUp Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Added a deprecated alias map to the FileIO System When a deprecated alias is accessed, the FileIO System logs an AZ_Error and indicates the alias that should be used Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Updated python test scripts to use the projectroot binding Retrieving the AutomatedTesting project path based on "<devroot>/AutomatedTesting" has been removed. Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Updated references to devroot and devgame within the codebase The GetAbsoluteDevGameFolderPath functions has been replaced with direct call to AZ::Utils::GetProjectPath The GetAbsoluteDevRootFolderPath functions has been replaced with direct calls to AZ::Utils::GetEnginePath Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Updated <engroot>/AutomatedTesting references to projectroot Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Replaced references that assumes the project path is <engroot>/AutomatedTesting with <projectroot> in the AutomatedTesting python test Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Correct casing in emfxworkspace file Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Removed newly added AppendPathParts function Removed the Path constructors which accepts a PathIterable instance The PathIterable isn't safe to return to a user of the Path class as it might be referencing temporary paths supplied via PathView arguments Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Fixed unused parameter warning Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Undid change to the LexicallyProximate function to set the path result to the base path. It needs to return the *this path if the pathResult is empty Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Moved the LocalFileIO ConvertToAbsolutePath implementations to AZ::Utils Fixed the ConvertToAbsolutePath implementation for Unix platforms to use a buffer that is size PATH_MAX(4096 on all our supported Unix platforms). Because the buffer before was AZ::IO::MaxPathLength which as a size of 1024, this was resulting in the Unix `realpath` function smashing the stack when it converts a path to an absolute path that is longer than 1024 characters Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Updated the EditorCore.Tests to attach the AZ Environment to the EditorCore shared library that is statically loaded on launch. Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Fixed for DeprecatedAlaisesKeyVisitor Visit function causing the non string_view overloads being hidden causing a hidden base class function warning Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Changed the AWSMetricsGemMock to use a temporary for writing test assets Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Updated the LocalFileIO::ResolvePath function to use HasRootPath to determine if a path starts with a root path such as "D:", "D:/" or "/" IsAbsolute was not the corect check as the path of "D:" is a relative path. To be absolute according to the Windows the path must have a root directory. i.e "D:/" or "D:\" Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Removed absolute path comment from LocalFile_UnixLike.cpp and LocalFile_Android.cpp FindFiles implementations Updated the ConvertToAlias to supply an AZ::IO::FixedMaxPath Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Replaced usage of the @projectproductassets@ alias with @engroot@ when referring to the LmbrCentral source folder in the CopyDependencyBuilderTest and the SeedBuilderTests Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Updated the ScriptCanvas Upgrade Tool to output backed up files to the Project User Path instead of the engine root Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Fixed whitespacing issues in Application.cpp Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Remove unnecessary creation of a FixedMaxPath in the UpgradeTool.cpp Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Modified testSeedFolder variable in the SeedBuilderTests to use the @engroot@ alias instead of @projectproductassets@/.. alias when referring to the LmbrCentral Gem source path Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Updated references to the Project Asset Cache in the PythonTests. Those tests no longer use the logic `azlmbr.paths.projectroot / "Cache" / "pc"` to retrieve a path to the cache root but instead the `azlmbr.paths.projectproductassets` constant Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Fixed the FileIO Deprecated Alias test on Windows Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Removing @projectsourceassets@ alias, as it is only used once. Updated the PhysX EditorSystemComponent.cpp to query the ProjectPath form the SettingsRegistry. Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Replaced @projectproductassets@ alias with @products@ Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Rollback changes to the PhysX EditorSystemComponent.cpp The changes to use the ProjectPath from the SettingsRegistry has been implemented in PR #4497 Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
277 lines
10 KiB
C++
277 lines
10 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
#include <QtForPythonSystemComponent.h>
|
|
|
|
#include <AzCore/Serialization/SerializeContext.h>
|
|
#include <AzCore/RTTI/BehaviorContext.h>
|
|
#include <AzFramework/StringFunc/StringFunc.h>
|
|
#include <AzToolsFramework/API/EditorWindowRequestBus.h>
|
|
#include <AzToolsFramework/API/EditorPythonRunnerRequestsBus.h>
|
|
|
|
#include <AzCore/IO/SystemFile.h>
|
|
#include <AzFramework/IO/LocalFileIO.h>
|
|
|
|
#include <EditorPythonBindings/EditorPythonBindingsSymbols.h>
|
|
|
|
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // (qwidget.h) 'uint': forcing value to bool 'true' or 'false' (performance warning)
|
|
#include <QPointer>
|
|
#include <QWidget>
|
|
#include <QApplication>
|
|
#include <QDateTime>
|
|
AZ_POP_DISABLE_WARNING
|
|
|
|
// Qt defines slots, which interferes with the use here.
|
|
#pragma push_macro("slots")
|
|
#undef slots
|
|
#include <Python.h>
|
|
#include <pybind11/pybind11.h>
|
|
#include <pybind11/functional.h>
|
|
#pragma pop_macro("slots")
|
|
|
|
namespace QtForPython
|
|
{
|
|
static const constexpr int s_loopTimerInterval = 5;
|
|
static const constexpr float s_maxTime = 25.f * 60.f * 60.f;
|
|
|
|
class QtForPythonEventHandler : public QObject
|
|
{
|
|
private:
|
|
std::function<void()> m_loopCallback;
|
|
float m_time = 0.f;
|
|
QPointer<QObject> m_lastTimerParent = nullptr;
|
|
int m_lastTimerId = 0;
|
|
|
|
public:
|
|
void SetupTimer(QObject* parent)
|
|
{
|
|
if (parent == m_lastTimerParent)
|
|
{
|
|
return;
|
|
}
|
|
if (m_lastTimerParent)
|
|
{
|
|
m_lastTimerParent->killTimer(m_lastTimerId);
|
|
}
|
|
m_lastTimerId = parent->startTimer(s_loopTimerInterval, Qt::CoarseTimer);
|
|
m_lastTimerParent = parent;
|
|
}
|
|
|
|
QtForPythonEventHandler(QObject* parent = nullptr)
|
|
: QObject(parent)
|
|
{
|
|
qApp->installEventFilter(this);
|
|
SetupTimer(this);
|
|
}
|
|
|
|
float GetTime() const
|
|
{
|
|
return m_time;
|
|
}
|
|
|
|
void RunEventLoop()
|
|
{
|
|
if (m_loopCallback)
|
|
{
|
|
try
|
|
{
|
|
m_loopCallback();
|
|
}
|
|
catch (pybind11::error_already_set& pythonError)
|
|
{
|
|
// Release the exception stack and let Python print it
|
|
pythonError.restore();
|
|
PyErr_Print();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool eventFilter(QObject* obj, QEvent* event)
|
|
{
|
|
// Determine which object should own our event loop timer
|
|
// By default it's this object
|
|
QObject* activeTimerParent = this;
|
|
// If it's a modal or popup widget, use that to ensure we get timer events
|
|
if (qApp->activePopupWidget())
|
|
{
|
|
activeTimerParent = qApp->activePopupWidget();
|
|
}
|
|
else if (qApp->activeModalWidget())
|
|
{
|
|
activeTimerParent = qApp->activeModalWidget();
|
|
}
|
|
SetupTimer(activeTimerParent);
|
|
|
|
if (obj == m_lastTimerParent && event->type() == QEvent::Timer && static_cast<QTimerEvent*>(event)->timerId() == m_lastTimerId)
|
|
{
|
|
m_time += s_loopTimerInterval / 1000.f;
|
|
if (m_time > s_maxTime)
|
|
{
|
|
m_time = 0.f;
|
|
}
|
|
RunEventLoop();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void SetLoopCallback(std::function<void()> callback)
|
|
{
|
|
m_loopCallback = callback;
|
|
}
|
|
|
|
void ClearLoopCallback()
|
|
{
|
|
m_loopCallback = {};
|
|
}
|
|
|
|
bool HasLoopCallback() const
|
|
{
|
|
return m_loopCallback.operator bool();
|
|
}
|
|
};
|
|
|
|
void QtForPythonSystemComponent::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
if (auto* serialize = azrtti_cast<AZ::SerializeContext*>(context))
|
|
{
|
|
serialize->Class<QtForPythonSystemComponent, AZ::Component>()
|
|
->Version(0)
|
|
;
|
|
|
|
serialize->RegisterGenericType<QWidget>();
|
|
}
|
|
|
|
if (AZ::BehaviorContext* behavior = azrtti_cast<AZ::BehaviorContext*>(context))
|
|
{
|
|
behavior->EBus<QtForPythonRequestBus>("QtForPythonRequestBus")
|
|
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
|
|
->Attribute(AZ::Script::Attributes::Module, "qt")
|
|
->Event("IsActive", &QtForPythonRequestBus::Events::IsActive)
|
|
->Event("GetQtBootstrapParameters", &QtForPythonRequestBus::Events::GetQtBootstrapParameters)
|
|
;
|
|
|
|
behavior->Class<QtBootstrapParameters>()
|
|
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
|
|
->Attribute(AZ::Script::Attributes::Module, "qt")
|
|
->Property("qtBinaryFolder", BehaviorValueProperty(&QtBootstrapParameters::m_qtBinaryFolder))
|
|
->Property("qtPluginsFolder", BehaviorValueProperty(&QtBootstrapParameters::m_qtPluginsFolder))
|
|
->Property("mainWindowId", BehaviorValueProperty(&QtBootstrapParameters::m_mainWindowId))
|
|
;
|
|
}
|
|
}
|
|
|
|
void QtForPythonSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
|
|
{
|
|
provided.push_back(AZ_CRC_CE("QtForPythonService"));
|
|
}
|
|
|
|
void QtForPythonSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
|
|
{
|
|
incompatible.push_back(AZ_CRC_CE("QtForPythonService"));
|
|
}
|
|
|
|
void QtForPythonSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
|
|
{
|
|
required.push_back(EditorPythonBindings::PythonEmbeddedService);
|
|
}
|
|
|
|
void QtForPythonSystemComponent::Activate()
|
|
{
|
|
m_eventHandler = new QtForPythonEventHandler;
|
|
QtForPythonRequestBus::Handler::BusConnect();
|
|
EditorPythonBindings::EditorPythonBindingsNotificationBus::Handler::BusConnect();
|
|
}
|
|
|
|
void QtForPythonSystemComponent::Deactivate()
|
|
{
|
|
QtForPythonRequestBus::Handler::BusDisconnect();
|
|
EditorPythonBindings::EditorPythonBindingsNotificationBus::Handler::BusDisconnect();
|
|
delete m_eventHandler;
|
|
}
|
|
|
|
bool QtForPythonSystemComponent::IsActive() const
|
|
{
|
|
return AzToolsFramework::EditorPythonRunnerRequestBus::HasHandlers();
|
|
}
|
|
|
|
QtBootstrapParameters QtForPythonSystemComponent::GetQtBootstrapParameters() const
|
|
{
|
|
QtBootstrapParameters params;
|
|
|
|
#if !defined(Q_OS_WIN)
|
|
#error Unsupported OS platform for this QtForPython gem
|
|
#endif
|
|
|
|
params.m_mainWindowId = 0;
|
|
using namespace AzToolsFramework;
|
|
QWidget* activeWindow = nullptr;
|
|
EditorWindowRequestBus::BroadcastResult(activeWindow, &EditorWindowRequests::GetAppMainWindow);
|
|
if (activeWindow)
|
|
{
|
|
// store the Qt main window so that scripts can hook into the main menu and/or docking framework
|
|
params.m_mainWindowId = aznumeric_cast<AZ::u64>(activeWindow->winId());
|
|
}
|
|
|
|
// prepare the folder where the build system placed the QT binary files
|
|
AZ::ComponentApplicationBus::BroadcastResult(params.m_qtBinaryFolder, &AZ::ComponentApplicationBus::Events::GetExecutableFolder);
|
|
|
|
// prepare the QT plugins folder
|
|
AZ::StringFunc::Path::Join(params.m_qtBinaryFolder.c_str(), "EditorPlugins", params.m_qtPluginsFolder);
|
|
return params;
|
|
}
|
|
|
|
void QtForPythonSystemComponent::OnImportModule(PyObject* module)
|
|
{
|
|
// Register azlmbr.qt_helpers for our event loop callback
|
|
pybind11::module parentModule = pybind11::cast<pybind11::module>(module);
|
|
std::string pythonModuleName = pybind11::cast<std::string>(parentModule.attr("__name__"));
|
|
if (AzFramework::StringFunc::Equal(pythonModuleName.c_str(), "azlmbr"))
|
|
{
|
|
pybind11::module helperModule = parentModule.def_submodule("qt_helpers");
|
|
helperModule.def("set_loop_callback", [this](std::function<void()> callback)
|
|
{
|
|
if (m_eventHandler)
|
|
{
|
|
m_eventHandler->SetLoopCallback(callback);
|
|
}
|
|
}, R"delim(
|
|
Sets a callback that will be invoked periodically during the course of Qt's event loop (even if a nested event loop is running).
|
|
This is intended for internal use in pyside_utils and should generally not be used directly.)delim");
|
|
helperModule.def("clear_loop_callback", [this]()
|
|
{
|
|
if (m_eventHandler)
|
|
{
|
|
m_eventHandler->ClearLoopCallback();
|
|
}
|
|
}, R"delim(
|
|
Clears callback that will be invoked periodically during the course of Qt's event loop.
|
|
This is intended for internal use in pyside_utils and should generally not be used directly.)delim");
|
|
helperModule.def("loop_is_running", [this]()
|
|
{
|
|
if (m_eventHandler)
|
|
{
|
|
return m_eventHandler->HasLoopCallback();
|
|
}
|
|
return false;
|
|
}, R"delim(
|
|
Returns True if the qt_helper event_loop callback is set and running.
|
|
This is intended for internal use in pyside_utils and should generally not be used directly.)delim");
|
|
helperModule.def("time", [this]()
|
|
{
|
|
if (m_eventHandler)
|
|
{
|
|
return m_eventHandler->GetTime();
|
|
}
|
|
return -1.f;
|
|
}, R"delim(
|
|
Returns a floating timestamp, measured in seconds, that updates with the Qt event loop.
|
|
This is intended for internal use in pyside_utils and should generally not be used directly.)delim");
|
|
}
|
|
}
|
|
}
|