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,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))