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
+15
View File
@@ -0,0 +1,15 @@
#
# 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.
#
ly_add_external_target(
NAME pyside2
3RDPARTY_ROOT_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/pyside2
VERSION
)
@@ -0,0 +1,17 @@
#
# 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.
#
set(pyside2_SHARED_LIB_PATH ${BASE_PATH}/windows/$<IF:$<CONFIG:Debug>,debug,release>)
# Adding Shared libs
set(pyside2_RUNTIME_DEPENDENCIES
${pyside2_SHARED_LIB_PATH}/PySide2/$<IF:$<CONFIG:Debug>,pyside2_d.cp37-win_amd64.dll,pyside2.abi3.dll>
${pyside2_SHARED_LIB_PATH}/shiboken2/$<IF:$<CONFIG:Debug>,shiboken2_d.cp37-win_amd64.dll,shiboken2.abi3.dll>
${pyside2_SHARED_LIB_PATH}/shiboken2/$<IF:$<CONFIG:Debug>,shiboken2_d.cp37-win_amd64.pyd,shiboken2.pyd>)
+13
View File
@@ -0,0 +1,13 @@
#
# 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.
#
ly_add_external_target_path(${CMAKE_CURRENT_LIST_DIR}/3rdParty)
add_subdirectory(Code)
+54
View File
@@ -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.
#
if(NOT PAL_TRAIT_BUILD_HOST_TOOLS)
return()
endif()
ly_get_list_relative_pal_filename(common_source_dir ${CMAKE_CURRENT_LIST_DIR}/Source/Platform/Common)
include(${CMAKE_CURRENT_SOURCE_DIR}/Source/Platform/${PAL_PLATFORM_NAME}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}.cmake)
if(NOT PAL_TRAIT_BUILD_QTFORPYTHON_SUPPORTED)
return()
endif()
ly_add_target(
NAME QtForPython.Editor.Static STATIC
NAMESPACE Gem
FILES_CMAKE
qtforpython_editor_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
PLATFORM_INCLUDE_FILES
${common_source_dir}/${PAL_TRAIT_COMPILER_ID}/qtforpython_${PAL_TRAIT_COMPILER_ID_LOWERCASE}.cmake
INCLUDE_DIRECTORIES
PUBLIC
Include
Source
BUILD_DEPENDENCIES
PUBLIC
AZ::AzCore
AZ::AzFramework
AZ::AzToolsFramework
3rdParty::Qt::Widgets
Gem::EditorPythonBindings.Static
RUNTIME_DEPENDENCIES
3rdParty::pyside2
)
ly_add_target(
NAME QtForPython.Editor MODULE
NAMESPACE Gem
OUTPUT_NAME Gem.QtForPython.Editor.cd50c7a1e31f4c9495dcffdacc3bde92.v0.1.0
FILES_CMAKE
qtforpython_shared_files.cmake
BUILD_DEPENDENCIES
PRIVATE
Gem::QtForPython.Editor.Static
)
@@ -0,0 +1,59 @@
/*
* 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/EBus/EBus.h>
#include <AzCore/RTTI/RTTI.h>
namespace QtForPython
{
//////////////////////////////////////////////////////////////////////////
// Used to fetch the data points a bootstrap script requires to hook in
// QtForPython (aka PySide2)
struct QtBootstrapParameters
{
AZ_TYPE_INFO(QtBootstrapParameters, "{4103CF43-6CF7-413D-B2C8-D511E23BAB50}");
//! The path of the Qt binary files such as qt5core.dll
AZStd::string m_qtBinaryFolder;
//! The path of the Qt plugins such as /qtlibs/plugins
AZStd::string m_qtPluginsFolder;
//! The 'winId' of the main Qt window in the Lumberyard editor
AZ::u64 m_mainWindowId;
//! PySide package folder to attach to the Python system path
AZStd::string m_pySidePackageFolder;
};
//! Used to fetch tools framework data
class QtForPythonRequests
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
//! Check to make sure Python is ready and active
virtual bool IsActive() const = 0;
//! Fetches the data a bootstrap script requires to hook in QtForPython
virtual QtBootstrapParameters GetQtBootstrapParameters() const = 0;
};
using QtForPythonRequestBus = AZ::EBus<QtForPythonRequests>;
}
@@ -0,0 +1,15 @@
#
# 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.
#
set(LY_COMPILE_OPTIONS
PRIVATE
-fexceptions # QtForPythonSystemComponent uses a try catch block
)
@@ -0,0 +1,15 @@
#
# 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.
#
set(LY_COMPILE_OPTIONS
PRIVATE
/EHsc # QtForPythonSystemComponent uses a try catch block
)
@@ -0,0 +1,12 @@
#
# 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.
#
set(PAL_TRAIT_BUILD_QTFORPYTHON_SUPPORTED FALSE)
@@ -0,0 +1,12 @@
#
# 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.
#
set(PAL_TRAIT_BUILD_QTFORPYTHON_SUPPORTED FALSE)
@@ -0,0 +1,12 @@
#
# 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.
#
set(PAL_TRAIT_BUILD_QTFORPYTHON_SUPPORTED TRUE)
@@ -0,0 +1,51 @@
/*
* 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 <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Module/Module.h>
#include <QtForPythonSystemComponent.h>
namespace QtForPython
{
class QtForPythonModule
: public AZ::Module
{
public:
AZ_RTTI(QtForPythonModule, "{81545CD5-79FA-47CE-96F2-1A9C5D59B4B9}", AZ::Module);
AZ_CLASS_ALLOCATOR(QtForPythonModule, AZ::SystemAllocator, 0);
QtForPythonModule()
: AZ::Module()
{
m_descriptors.insert(m_descriptors.end(), {
QtForPythonSystemComponent::CreateDescriptor(),
});
}
/**
* Add required SystemComponents to the SystemEntity.
*/
AZ::ComponentTypeList GetRequiredSystemComponents() const override
{
return AZ::ComponentTypeList
{
azrtti_typeid<QtForPythonSystemComponent>(),
};
}
};
}
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
// The first parameter should be GemName_GemIdLower
// The second should be the fully qualified name of the class above
AZ_DECLARE_MODULE_CLASS(Gem_QtForPython, QtForPython::QtForPythonModule)
@@ -0,0 +1,296 @@
/*
* 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 <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))
->Property("pySidePackageFolder", BehaviorValueProperty(&QtBootstrapParameters::m_pySidePackageFolder))
;
}
}
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;
char devroot[AZ_MAX_PATH_LEN];
AZ::IO::FileIOBase::GetInstance()->ResolvePath("@devroot@", devroot, AZ_MAX_PATH_LEN);
#if defined(Q_OS_WIN)
const char* platform = "windows";
#else
#error Unsupported OS platform for this QtForPython gem
#endif
#if defined(AZ_DEBUG_BUILD)
const char* build = "debug";
#else
const char* build = "release";
#endif
// prepare the platform and build specific PySide2 package folder
AZ::StringFunc::Path::Join(devroot, "Gems/QtForPython/3rdParty/pyside2", params.m_pySidePackageFolder);
AZ::StringFunc::Path::Join(params.m_pySidePackageFolder.c_str(), platform, params.m_pySidePackageFolder);
AZ::StringFunc::Path::Join(params.m_pySidePackageFolder.c_str(), build, params.m_pySidePackageFolder);
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");
}
}
}
@@ -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.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <QtForPython/QtForPythonBus.h>
#include <EditorPythonBindings/EditorPythonBindingsBus.h>
namespace QtForPython
{
class QtForPythonEventHandler;
class QtForPythonSystemComponent
: public AZ::Component
, protected QtForPythonRequestBus::Handler
, protected EditorPythonBindings::EditorPythonBindingsNotificationBus::Handler
{
public:
AZ_COMPONENT(QtForPythonSystemComponent, "{0C939FBF-8BC9-4CB0-93B8-04140155AA8C}");
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
protected:
////////////////////////////////////////////////////////////////////////
// QtForPythonRequestBus interface implementation
bool IsActive() const override;
QtBootstrapParameters GetQtBootstrapParameters() const override;
////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
void Activate() override;
void Deactivate() override;
////////////////////////////////////////////////////////////////////////
// EditorPythonBindings::EditorPythonBindingsNotificationBus interface implementation
void OnImportModule(PyObject* module) override;
private:
QtForPythonEventHandler* m_eventHandler = nullptr;
};
}
@@ -0,0 +1,63 @@
"""
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.
"""
#
# This is a pytest module to test the basic integration of PySide2 (aka Qt for Python)
#
import pytest
import time
import logging
import os
import shutil
from test_tools import WINDOWS_LAUNCHER
import test_tools.shared.log_monitor
import test_tools.launchers.phase
import test_tools.builtin.fixtures as fixtures
# Use the built-in workspace and editor fixtures.
# These will configure the requested project and run the editor.
workspace = fixtures.use_fixture(fixtures.builtin_workspace_fixture, scope='function')
editor = fixtures.use_fixture(fixtures.editor, scope='function')
logger = logging.getLogger(__name__)
@pytest.mark.parametrize("platform,configuration,project,spec", [
pytest.param("win_x64_vs2017", "profile", "AutomatedTesting", "all", marks=pytest.mark.skipif(not WINDOWS_LAUNCHER, reason="Only supported on Windows hosts")),
])
class TestEditorMenuBarAutomation(object):
def test_MenuBar(self, request, editor, project):
logger.debug("Running automated test")
request.addfinalizer(editor.ensure_stopped)
editor.deploy()
editor.launch(["--runpython", "@engroot@/Gems/QtForPython/Code/Tests/pyside_auto_menubar_test_case.py"])
editorlog_file = os.path.join(editor.workspace.release.paths.project_log(), 'Editor.log')
expected_lines = [
"QtForPython Is Ready",
"Value allWindows greater than zero",
"GetMainWindowId",
"Get QtWidgets.QMainWindow",
"Value menuBar is valid",
"Found File action",
"Found Edit action",
"Found Game action",
"Found Tools action"
]
test_tools.shared.log_monitor.monitor_for_expected_lines(editor, editorlog_file, expected_lines)
# Rely on the test script to quit after running
editor.run(test_tools.launchers.phase.WaitForLauncherToQuit(editor, 10))
@@ -0,0 +1,61 @@
"""
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.
"""
# Import the Editor API to test the PySide2 & Qt 5.12.x integration
import azlmbr.bus
import azlmbr.editor as editor
import azlmbr.legacy.general as general
def printOrExcept(expression, message):
if(expression):
print (message)
return
failed = 'FAILED - '.format(message)
print (failed)
general.exit_no_prompt()
raise Exception(failed)
printOrExcept(azlmbr.qt.QtForPythonRequestBus(azlmbr.bus.Broadcast, 'IsActive'), 'QtForPython Is Ready')
# the PySide2 and shiboken2 libraries should import cleanly
try:
from shiboken2 import wrapInstance, getCppPointer
from PySide2 import QtWidgets
from PySide2 import QtGui
except:
printOrExcept(False, 'Importing PySide2 and Shiboken2')
allWindows = QtGui.QGuiApplication.allWindows()
printOrExcept(len(allWindows) > 0, 'Value allWindows greater than zero')
azMainWidgetId = azlmbr.qt.QtForPythonRequestBus(azlmbr.bus.Broadcast, 'GetMainWindowId')
printOrExcept(azMainWidgetId is not 0, 'GetMainWindowId')
mainWidgetWindow = QtWidgets.QWidget.find(azMainWidgetId)
mainWindow = wrapInstance(int(getCppPointer(mainWidgetWindow)[0]), QtWidgets.QMainWindow)
printOrExcept(mainWindow is not None, 'Get QtWidgets.QMainWindow')
menuBar = mainWindow.menuBar()
printOrExcept(menuBar is not None, 'Value menuBar is valid')
for action in menuBar.actions():
if("File" in action.text()):
print('Found File action')
elif("Edit" in action.text()):
print('Found Edit action')
elif("Game" in action.text()):
print('Found Game action')
elif("Tools" in action.text()):
print('Found Tools action')
general.exit_no_prompt()
@@ -0,0 +1,16 @@
#
# 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.
#
set(FILES
Include/QtForPython/QtForPythonBus.h
Source/QtForPythonSystemComponent.cpp
Source/QtForPythonSystemComponent.h
)
@@ -0,0 +1,16 @@
#
# 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.
#
set(FILES
Include/QtForPython/QtForPythonBus.h
Source/QtForPythonSystemComponent.cpp
Source/QtForPythonSystemComponent.h
)
@@ -0,0 +1,14 @@
#
# 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.
#
set(FILES
Source/QtForPythonModule.cpp
)
@@ -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.
"""
import azlmbr
import azlmbr.bus
import azlmbr.editor as editor
from PySide2 import QtWidgets
from shiboken2 import wrapInstance, getCppPointer
# The `view_pane_handlers` holds onto the callback handlers that get created
# for responding to requests for when the Editor needs to construct the view pane
view_pane_handlers = {}
# Helper method for registering a Python widget as a tool/view pane with the Editor
def register_view_pane(name, widget_type, options=editor.ViewPaneOptions()):
global view_pane_handlers
# The view pane names are unique in the Editor, so make sure one with the same name doesn't exist already
if name in view_pane_handlers:
return
# This method will be invoked by the ViewPaneCallbackBus::CreateViewPaneWidget
# when our view pane needs to be created
def on_create_view_pane_widget(parameters):
params = azlmbr.qt.QtForPythonRequestBus(azlmbr.bus.Broadcast, "GetQtBootstrapParameters")
editor_id = QtWidgets.QWidget.find(params.mainWindowId)
editor_main_window = wrapInstance(int(getCppPointer(editor_id)[0]), QtWidgets.QMainWindow)
dock_main_window = editor_main_window.findChild(QtWidgets.QMainWindow)
# Create the view pane widget parented to the Editor QMainWindow, so it can be found
new_widget = widget_type(dock_main_window)
return new_widget.winId()
# Register our widget as an Editor view pane
editor.EditorRequestBus(azlmbr.bus.Broadcast, 'RegisterCustomViewPane', name, 'Tools', options)
# Connect to the ViewPaneCallbackBus in order to respond to requests to create our widget
# We also need to store our handler so it will exist for the life of the Editor
handler = azlmbr.bus.NotificationHandler("ViewPaneCallbackBus")
handler.connect(name)
handler.add_callback("CreateViewPaneWidget", on_create_view_pane_widget)
view_pane_handlers[name] = handler
# Helper method for unregistering a Python widget as a tool/view pane with the Editor
def unregister_view_pane(name):
global view_pane_handlers
# No need to proceed if we don't have this view pane registered
if name not in view_pane_handlers:
return
# Unregister our view pane from the Editor and remove our stored handler for it
editor.EditorRequestBus(azlmbr.bus.Broadcast, 'UnregisterViewPane', name)
del view_pane_handlers[name]
@@ -0,0 +1,50 @@
"""
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.
"""
import sys
import os
import pathlib
import azlmbr
import azlmbr.bus
# establishes python paths to find PySide2 libraries
if (azlmbr.qt.QtForPythonRequestBus(azlmbr.bus.Broadcast, 'IsActive')):
params = azlmbr.qt.QtForPythonRequestBus(azlmbr.bus.Broadcast, 'GetQtBootstrapParameters')
# add PySide2 base folder to sys.path
# Ideally, the current directory or the directory of the script is the first
# always the first element: https://tinyurl.com/yysfw8pb
sys.path.insert(1, pathlib.PureWindowsPath(params.pySidePackageFolder).as_posix())
sys.path.insert(1, pathlib.PureWindowsPath(params.qtBinaryFolder).as_posix())
# add the Qt plugins to the environment
os.environ['QT_PLUGIN_PATH'] = params.qtPluginsFolder
# add Qt binaries to the Windows path to handle findings DLL file dependencies
if sys.platform.startswith('win'):
path = os.environ['PATH']
newPath = ''
newPath += params.qtBinaryFolder + os.pathsep
newPath += os.path.join(params.pySidePackageFolder, 'shiboken2') + os.pathsep
newPath += os.path.join(params.pySidePackageFolder, 'PySide2') + os.pathsep
newPath += path
os.environ['PATH'] = newPath
print('PySide2 bootstrapped PATH for Windows.')
# Once PySide2 has been bootstrapped, register our Object Tree visualizer with the Editor
try:
import az_qt_helpers
from show_object_tree import ObjectTreeDialog
az_qt_helpers.register_view_pane('Object Tree', ObjectTreeDialog)
except:
print ('Skipping register our Object Tree visualizer with the Editor.')
@@ -0,0 +1,328 @@
"""
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.
"""
import azlmbr
from shiboken2 import wrapInstance, getCppPointer
from PySide2 import QtCore, QtWidgets, QtGui
from PySide2.QtCore import QEvent, Qt
from PySide2.QtWidgets import QAction, QDialog, QHeaderView, QLabel, QLineEdit, QPushButton, QSplitter, QTreeWidget, QTreeWidgetItem, QWidget, QAbstractButton
class InspectPopup(QWidget):
def __init__(self, parent=None):
super(InspectPopup, self).__init__(parent)
self.setWindowFlags(Qt.Popup)
object_name = "InspectPopup"
self.setObjectName(object_name)
self.setStyleSheet("#{name} {{ background-color: #6441A4; }}".format(name=object_name))
self.setContentsMargins(10, 10, 10, 10)
layout = QtWidgets.QGridLayout()
self.name_label = QLabel("Name:")
self.name_value = QLabel("")
layout.addWidget(self.name_label, 0, 0)
layout.addWidget(self.name_value, 0, 1)
self.type_label = QLabel("Type:")
self.type_value = QLabel("")
layout.addWidget(self.type_label, 1, 0)
layout.addWidget(self.type_value, 1, 1)
self.geometry_label = QLabel("Geometry:")
self.geometry_value = QLabel("")
layout.addWidget(self.geometry_label, 2, 0)
layout.addWidget(self.geometry_value, 2, 1)
self.setLayout(layout)
def update_widget(self, new_widget):
name = "(None)"
type_str = "(Unknown)"
geometry_str = "(Unknown)"
if new_widget:
type_str = str(type(new_widget))
geometry_rect = new_widget.geometry()
geometry_str = "x: {x}, y: {y}, width: {width}, height: {height}".format(x=geometry_rect.x(), y=geometry_rect.y(), width=geometry_rect.width(), height=geometry_rect.height())
# Not all of our widgets have their objectName set
if new_widget.objectName():
name = new_widget.objectName()
self.name_value.setText(name)
self.type_value.setText(type_str)
self.geometry_value.setText(geometry_str)
class ObjectTreeDialog(QDialog):
def __init__(self, parent=None, root_object=None):
super(ObjectTreeDialog, self).__init__(parent)
self.setWindowTitle("Object Tree")
layout = QtWidgets.QVBoxLayout()
# Tree widget for displaying our object hierarchy
self.tree_widget = QTreeWidget()
self.tree_widget_columns = [
"TYPE",
"OBJECT NAME",
"TEXT",
"ICONTEXT",
"TITLE",
"WINDOW_TITLE",
"CLASSES",
"POINTER_ADDRESS",
"GEOMETRY"
]
self.tree_widget.setColumnCount(len(self.tree_widget_columns))
self.tree_widget.setHeaderLabels(self.tree_widget_columns)
# Only show our type and object name columns. The others we only use to store data so that
# we can use the built-in QTreeWidget.findItems to query.
for column_name in self.tree_widget_columns:
if column_name == "TYPE" or column_name == "OBJECT NAME":
continue
column_index = self.tree_widget_columns.index(column_name)
self.tree_widget.setColumnHidden(column_index, True)
header = self.tree_widget.header()
header.setSectionResizeMode(0, QHeaderView.ResizeToContents)
header.setSectionResizeMode(1, QHeaderView.ResizeToContents)
# Populate our object tree widget
# If a root object wasn't specified, then use the Editor main window
if not root_object:
params = azlmbr.qt.QtForPythonRequestBus(azlmbr.bus.Broadcast, "GetQtBootstrapParameters")
editor_id = QtWidgets.QWidget.find(params.mainWindowId)
editor_main_window = wrapInstance(int(getCppPointer(editor_id)[0]), QtWidgets.QMainWindow)
root_object = editor_main_window
self.build_tree(root_object, self.tree_widget)
# Listen for when the tree widget selection changes so we can update
# selected item properties
self.tree_widget.itemSelectionChanged.connect(self.on_tree_widget_selection_changed)
# Split our tree widget with a properties view for showing more information about
# a selected item. We also use a stacked layout for the properties view so that
# when nothing has been selected yet, we can show a message informing the user
# that something needs to be selected.
splitter = QSplitter()
splitter.addWidget(self.tree_widget)
self.widget_properties = QWidget(self)
self.stacked_layout = QtWidgets.QStackedLayout()
self.widget_info = QWidget()
form_layout = QtWidgets.QFormLayout()
self.name_value = QLineEdit("")
self.name_value.setReadOnly(True)
self.type_value = QLabel("")
self.geometry_value = QLabel("")
self.text_value = QLabel("")
self.icon_text_value = QLabel("")
self.title_value = QLabel("")
self.window_title_value = QLabel("")
self.classes_value = QLabel("")
form_layout.addRow("Name:", self.name_value)
form_layout.addRow("Type:", self.type_value)
form_layout.addRow("Geometry:", self.geometry_value)
form_layout.addRow("Text:", self.text_value)
form_layout.addRow("Icon Text:", self.icon_text_value)
form_layout.addRow("Title:", self.title_value)
form_layout.addRow("Window Title:", self.window_title_value)
form_layout.addRow("Classes:", self.classes_value)
self.widget_info.setLayout(form_layout)
self.widget_properties.setLayout(self.stacked_layout)
self.stacked_layout.addWidget(QLabel("Select an object to view its properties"))
self.stacked_layout.addWidget(self.widget_info)
splitter.addWidget(self.widget_properties)
# Give our splitter stretch factor of 1 so it will expand to take more room over
# the footer
layout.addWidget(splitter, 1)
# Create our popup widget for showing information when hovering over widgets
self.hovered_widget = None
self.inspect_mode = False
self.inspect_popup = InspectPopup()
self.inspect_popup.resize(100, 50)
self.inspect_popup.hide()
# Add a footer with a button to switch to widget inspect mode
self.footer = QWidget()
footer_layout = QtWidgets.QHBoxLayout()
self.inspect_button = QPushButton("Pick widget to inspect")
self.inspect_button.clicked.connect(self.on_inspect_clicked)
footer_layout.addStretch(1)
footer_layout.addWidget(self.inspect_button)
self.footer.setLayout(footer_layout)
layout.addWidget(self.footer)
self.setLayout(layout)
# Delete ourselves when the dialog is closed, so that we don't stay living in the background
# since we install an event filter on the application
self.setAttribute(Qt.WA_DeleteOnClose, True)
# Listen to events at the application level so we can know when the mouse is moving
app = QtWidgets.QApplication.instance()
app.installEventFilter(self)
def eventFilter(self, obj, event):
# Look for mouse movement events so we can see what widget the mouse is hovered over
event_type = event.type()
if event_type == QEvent.MouseMove:
global_pos = event.globalPos()
# Make our popup follow the mouse, but we need to offset it by 1, 1 otherwise
# the QApplication.widgetAt will always return our popup instead of the Editor
# widget since it is on top
self.inspect_popup.move(global_pos + QtCore.QPoint(1, 1))
# Find out which widget is under our current mouse position
hovered_widget = QtWidgets.QApplication.widgetAt(global_pos)
if self.hovered_widget:
# Bail out, this is the same widget we are already hovered on
if self.hovered_widget is hovered_widget:
return False
# Update our hovered widget and label
self.hovered_widget = hovered_widget
self.update_hovered_widget_popup()
elif event_type == QEvent.KeyRelease:
if event.key() == Qt.Key_Escape:
# Cancel the inspect mode if the Escape key is pressed
# We don't need to actually hide the inspect popup here because
# it will be hidden already by the Escape action
self.inspect_mode = False
elif event_type == QEvent.MouseButtonPress or event_type == QEvent.MouseButtonRelease:
# Trigger inspecting the currently hovered widget when the left mouse button is clicked
# Don't continue processing this event
if self.inspect_mode and event.button() == Qt.LeftButton:
# Only trigger the inspect on the click release, but we want to also eat the press
# event so that the widget we clicked on isn't stuck in a weird state (e.g. thinks its being dragged)
# Also hide the inspect popup since it won't be hidden automatically by the mouse click since we are
# consuming the event
if event_type == event_type == QEvent.MouseButtonRelease:
self.inspect_popup.hide()
self.inspect_widget()
return True
# Pass every event through
return False
def build_tree(self, obj, parent_tree):
if len(obj.children()) == 0:
return
for child in obj.children():
object_type = type(child).__name__
object_name = child.objectName()
text = icon_text = title = window_title = geometry_str = classes = "(N/A)"
if isinstance(child, QtGui.QWindow):
title = child.title()
if isinstance(child, QAction):
text = child.text()
icon_text = child.iconText()
if isinstance(child, QWidget):
window_title = child.windowTitle()
if not (child.property("class") == ""):
classes = child.property("class")
if isinstance(child, QAbstractButton):
text = child.text()
# Keep track of the pointer address for this object so we can search for it later
pointer_address = str(int(getCppPointer(child)[0]))
# Some objects might not have a geometry (e.g. actions, generic qobjects)
if hasattr(child, 'geometry'):
geometry_rect = child.geometry()
geometry_str = "x: {x}, y: {y}, width: {width}, height: {height}".format(x=geometry_rect.x(), y=geometry_rect.y(), width=geometry_rect.width(), height=geometry_rect.height())
child_tree = QTreeWidgetItem([object_type, object_name, text, icon_text, title, window_title, classes, pointer_address, geometry_str])
if isinstance(parent_tree, QTreeWidget):
parent_tree.addTopLevelItem(child_tree)
else:
parent_tree.addChild(child_tree)
self.build_tree(child, child_tree)
def update_hovered_widget_popup(self):
if self.inspect_mode and self.hovered_widget:
if not self.inspect_popup.isVisible():
self.inspect_popup.show()
self.inspect_popup.update_widget(self.hovered_widget)
else:
self.inspect_popup.hide()
def on_inspect_clicked(self):
self.inspect_mode = True
self.update_hovered_widget_popup()
def on_tree_widget_selection_changed(self):
selected_items = self.tree_widget.selectedItems()
# If nothing is selected, then switch the stacked layout back to 0
# to show the message
if not selected_items:
self.stacked_layout.setCurrentIndex(0)
return
# Update the selected widget properties and switch to the 1 index in
# the stacked layout so that all the rows will be visible
item = selected_items[0]
self.name_value.setText(item.text(self.tree_widget_columns.index("OBJECT NAME")))
self.type_value.setText(item.text(self.tree_widget_columns.index("TYPE")))
self.geometry_value.setText(item.text(self.tree_widget_columns.index("GEOMETRY")))
self.text_value.setText(item.text(self.tree_widget_columns.index("TEXT")))
self.icon_text_value.setText(item.text(self.tree_widget_columns.index("ICONTEXT")))
self.title_value.setText(item.text(self.tree_widget_columns.index("TITLE")))
self.window_title_value.setText(item.text(self.tree_widget_columns.index("WINDOW_TITLE")))
self.classes_value.setText(item.text(self.tree_widget_columns.index("CLASSES")))
self.stacked_layout.setCurrentIndex(1)
def inspect_widget(self):
self.inspect_mode = False
# Find the tree widget item that matches our hovered widget, and then set it as the current item
# so that the tree widget will scroll to it, expand it, and select it
widget_pointer_address = str(int(getCppPointer(self.hovered_widget)[0]))
pointer_address_column = self.tree_widget_columns.index("POINTER_ADDRESS")
items = self.tree_widget.findItems(widget_pointer_address, Qt.MatchFixedString | Qt.MatchRecursive, pointer_address_column)
if items:
item = items[0]
self.tree_widget.clearSelection()
self.tree_widget.setCurrentItem(item)
else:
print("Unable to find widget")
def get_object_tree(parent, obj=None):
"""
Returns the parent/child hierarchy for the given obj (QObject)
parent: Parent for the dialog that is created
obj: Root object for the tree to be built.
returns: QTreeWidget object starting with the root element obj.
"""
w = ObjectTreeDialog(parent, obj)
w.resize(1000, 500)
return w
if __name__ == "__main__":
# Get our Editor main window
params = azlmbr.qt.QtForPythonRequestBus(azlmbr.bus.Broadcast, "GetQtBootstrapParameters")
editor_id = QtWidgets.QWidget.find(params.mainWindowId)
editor_main_window = wrapInstance(int(getCppPointer(editor_id)[0]), QtWidgets.QMainWindow)
dock_main_window = editor_main_window.findChild(QtWidgets.QMainWindow)
# Show our object tree visualizer
object_tree = get_object_tree(dock_main_window)
object_tree.show()
@@ -0,0 +1,16 @@
"""
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.
"""
# a simple test to make sure PySide2 widgets can be used
from PySide2 import QtWidgets
hello = QtWidgets.QPushButton("Hello world!")
hello.resize(200, 60)
hello.show()
@@ -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.
"""
# bit more complex example set to demo connecting Lumberyard tech to PySide2 widgets
import azlmbr.bus
from PySide2 import QtWidgets
from PySide2 import QtGui
from shiboken2 import wrapInstance, getCppPointer
params = azlmbr.qt.QtForPythonRequestBus(azlmbr.bus.Broadcast, 'GetQtBootstrapParameters')
if(params is not None and params.mainWindowId is not 0):
mainWidgetWindow = QtWidgets.QWidget.find(params.mainWindowId)
mainWindow = wrapInstance(int(getCppPointer(mainWidgetWindow)[0]), QtWidgets.QMainWindow)
mainWindow.menuBar().addMenu("&Hello")
allWindows = QtGui.QGuiApplication.allWindows()
print ('allWindows = {}'.format(allWindows))
focusWin = QtGui.QGuiApplication.focusWindow()
print ('focusWin = {}'.format(focusWin))
buttonFlags = QtWidgets.QMessageBox.information(QtWidgets.QApplication.activeWindow(), 'title', 'ok')
print ('buttonFlags = {}'.format(buttonFlags))
+25
View File
@@ -0,0 +1,25 @@
{
"GemFormatVersion": 4,
"Uuid": "cd50c7a1e31f4c9495dcffdacc3bde92",
"Name": "QtForPython",
"DisplayName": "QtForPython",
"Version": "0.1.0",
"Summary": "Adds the ability to use the PySide2 Python libraries to manage Qt widgets.",
"Tags": ["Editor"],
"IconPath": "preview.png",
"Modules": [
{
"Name": "Editor",
"Type": "EditorModule"
}
],
"Dependencies": [
{
"Uuid": "b658359393884c4381c2fe2952b1472a",
"VersionConstraints": [
"~>0.1"
],
"_comment": "EditorPythonBindings"
}
]
}
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa
size 41127