Files
o3de/Gems/QtForPython/Code/Source/QtForPythonSystemComponent.cpp
T
lumberyard-employee-dm f648cb1fd8 Update the FileIO Aliases (#4186)
* 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>
2021-10-05 15:12:53 -05:00

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");
}
}
}