From 0aefcced95cc4fb97081c1b90f8ac7dfa0bfb5cc Mon Sep 17 00:00:00 2001 From: baldurk Date: Wed, 18 Nov 2020 18:21:20 +0000 Subject: [PATCH] Add a small interface into Qt widgets to our own python wrapping * This by no means replaces PySide2, but it allows python extensions to write simple UIs without needing to rely on PySide2, which might not be available (generally all windows builds have it as well as recent binary linux builds, but local windows builds may not and most linux builds probably won't). --- qrenderdoc/Code/CaptureContext.cpp | 9 + qrenderdoc/Code/CaptureContext.h | 5 + qrenderdoc/Code/Interface/Extensions.h | 431 ++++++++++++++ qrenderdoc/Code/Interface/QRDInterface.h | 7 +- qrenderdoc/Code/MiniQtHelper.cpp | 556 ++++++++++++++++++ qrenderdoc/Code/MiniQtHelper.h | 111 ++++ qrenderdoc/Code/pyrenderdoc/PythonContext.cpp | 11 + qrenderdoc/Code/pyrenderdoc/PythonContext.h | 6 + qrenderdoc/Windows/PythonShell.cpp | 512 ++++++++++++---- qrenderdoc/Windows/PythonShell.h | 3 + qrenderdoc/qrenderdoc.pro | 2 + qrenderdoc/qrenderdoc_local.vcxproj | 2 + qrenderdoc/qrenderdoc_local.vcxproj.filters | 6 + 13 files changed, 1545 insertions(+), 116 deletions(-) create mode 100644 qrenderdoc/Code/MiniQtHelper.cpp create mode 100644 qrenderdoc/Code/MiniQtHelper.h diff --git a/qrenderdoc/Code/CaptureContext.cpp b/qrenderdoc/Code/CaptureContext.cpp index 633cf5816..0c0c9e4b8 100644 --- a/qrenderdoc/Code/CaptureContext.cpp +++ b/qrenderdoc/Code/CaptureContext.cpp @@ -57,6 +57,7 @@ #include "Windows/StatisticsViewer.h" #include "Windows/TextureViewer.h" #include "Windows/TimelineBar.h" +#include "MiniQtHelper.h" #include "QRDUtils.h" #include "RGPInterop.h" #include "version.h" @@ -82,6 +83,8 @@ CaptureContext::CaptureContext(PersistantConfig &cfg) : m_Config(cfg) m_StructuredFile = &m_DummySDFile; + m_QtHelper = new MiniQtHelper(*this); + qApp->setApplicationVersion(QString::fromLatin1(RENDERDOC_GetVersionString())); m_Icon = new QIcon(); @@ -112,6 +115,7 @@ CaptureContext::CaptureContext(PersistantConfig &cfg) : m_Config(cfg) CaptureContext::~CaptureContext() { + delete m_QtHelper; RENDERDOC_UnregisterMemoryRegion(this); delete m_Icon; m_Replay.CloseThread(); @@ -504,6 +508,11 @@ void CaptureContext::MenuDisplaying(PanelMenu panelMenu, QMenu *menu, QWidget *e } } +IMiniQtHelper &CaptureContext::GetMiniQtHelper() +{ + return *m_QtHelper; +} + void CaptureContext::MessageDialog(const rdcstr &text, const rdcstr &title) { RDDialog::information(m_MainWindow, title, text); diff --git a/qrenderdoc/Code/CaptureContext.h b/qrenderdoc/Code/CaptureContext.h index ce60646b3..a13461508 100644 --- a/qrenderdoc/Code/CaptureContext.h +++ b/qrenderdoc/Code/CaptureContext.h @@ -53,6 +53,7 @@ class TimelineBar; class PythonShell; class ResourceInspector; class ShaderViewer; +class MiniQtHelper; class CaptureContext : public ICaptureContext, IExtensionManager { @@ -86,6 +87,8 @@ public: void MenuDisplaying(PanelMenu panelMenu, QMenu *menu, QWidget *extensionButton, const ExtensionCallbackData &data) override; + IMiniQtHelper &GetMiniQtHelper() override; + void MessageDialog(const rdcstr &text, const rdcstr &title = "Python Extension Message") override; void ErrorDialog(const rdcstr &text, const rdcstr &title = "Python Extension Error") override; DialogButton QuestionDialog(const rdcstr &text, const rdcarray &options, @@ -400,6 +403,8 @@ private: QList m_ShaderEditors; + MiniQtHelper *m_QtHelper = NULL; + // Windows MainWindow *m_MainWindow = NULL; EventBrowser *m_EventBrowser = NULL; diff --git a/qrenderdoc/Code/Interface/Extensions.h b/qrenderdoc/Code/Interface/Extensions.h index 28510234d..dd289522a 100644 --- a/qrenderdoc/Code/Interface/Extensions.h +++ b/qrenderdoc/Code/Interface/Extensions.h @@ -312,6 +312,430 @@ DECLARE_REFLECTION_STRUCT(ExtensionMetadata); typedef struct _object PyObject; +DOCUMENT(R"(Python can have direct access to Qt via PySide2, but this is not always available in +all RenderDoc builds. To aid extensions to manipulate widgets in a simple but portable fashion this +helper exposes a small subset of Qt via RenderDoc's python bindings. + +The intention is not to allow fully flexible building of Qt panels, but to allow access to some +basic UI building tools for simple data input and display which can be used on any RenderDoc build. + +.. note:: + The widget handles returned are PySide2 widgets where that is available, so this can be used to + make a basic UI and optionally customise it further with PySide2 when possible. + +.. function:: WidgetCallback(context, widget, text) + + Not a member function - the signature for any ``WidgetCallback`` callbacks. + + Callback for widgets can be registered at creation time, the text field is optional and may be + blank depending on the event, but the context and widget are always valid. + + :param CaptureContext context: The current capture context. + :param QWidget widget: The widget sending the callback. + :param str text: Additional data for the call, such as the current or selected text. +)"); +struct IMiniQtHelper +{ + typedef std::function WidgetCallback; + + // top level widgets + + DOCUMENT(R"(Creates and returns a top-level widget for creating layouts. + +The widget is not immediately visible. It should be shown either with :meth:`ShowWidgetAsDialog` or +with :meth:`CaptureContext.AddDockWindow` once it's ready. + +This widget can have children added, but it is recommended to immediately add only one child which +is a layout type widget, to allow customising how children are added. By default the children are +added in a vertical layout. + +:param str windowTitle: The title of any window with this widget as its root. +:return: The handle to the newly created widget. +:rtype: ``QWidget`` +)"); + virtual QWidget *CreateToplevelWidget(const rdcstr &windowTitle) = 0; + + // widget hierarchy + + DOCUMENT(R"(Set the internal name of a widget. This is not displayed anywhere but can be used by +:meth:`FindChildByName` to locate a widget within a hierarchy. + +.. note:: + Names are optional and only for your use. Nothing prevents from you from setting duplicate names, + but this makes searches by name ambiguous. + +:param QWidget widget: The widget to set an internal name for. +:param str name: The internal name to set for the widget. +)"); + virtual void SetWidgetName(QWidget *widget, const rdcstr &name) = 0; + + DOCUMENT(R"(Return the internal name of a widget, as set my :meth:`SetWidgetName`. + +:param QWidget widget: The widget to query. +:return: The widget's internal name, which may be an empty string if no name has been set. +:rtype: str +)"); + virtual rdcstr GetWidgetName(QWidget *widget) = 0; + + DOCUMENT(R"(Return the type of the widget as a string. This type is the Qt type name so this +should only be used for debugging as the name may change even if for the same type of widget. + +:param QWidget widget: The widget to query. +:return: The widget's type name. +:rtype: str +)"); + virtual rdcstr GetWidgetType(QWidget *widget) = 0; + + DOCUMENT(R"(Find a child widget of a parent by internal name. + +:param QWidget widget: The widget to start the search from. +:param str name: The internal name to search for. +:return: The handle to the first widget with a matching name, or ``None`` if no widget is found. +:rtype: ``QWidget`` +)"); + virtual QWidget *FindChildByName(QWidget *parent, const rdcstr &name) = 0; + + DOCUMENT(R"(Return the parent of a widget in the widget hierarchy. + +.. note:: + The widget returned may not be a widget created through this helper interface if the specified + widget has been docked somewhere. Beware making changes to any widgets returned as you may modify + the RenderDoc UI itself. + +:param QWidget widget: The widget to query. +:return: The handle to the parent widget with a matching name, or ``None`` if this widget is either + not yet parented or is a top-level window. +:rtype: ``QWidget`` +)"); + virtual QWidget *GetParent(QWidget *widget) = 0; + + DOCUMENT(R"(Return the number of children this widget has. This is generally only useful for +layout type widgets. + +:param QWidget widget: The widget to query. +:return: The number of child widgets this widget has. +:rtype: int +)"); + virtual int GetNumChildren(QWidget *widget) = 0; + + DOCUMENT(R"(Return a child widget for a parent. + +:param QWidget parent: The parent widget to look up. +:param int index: The child index to return. +:return: The specified child of the parent, or ``None`` if the index is out of bounds. +:rtype: ``QWidget`` +)"); + virtual QWidget *GetChild(QWidget *parent, int index) = 0; + + // dialogs + + DOCUMENT(R"(Show a top-level widget as a blocking modal dialog. This is most useful to prompt the +user for some specific information. + +The dialog is only closed when the user closes the window explicitly or if you call +:meth:`CloseCurrentDialog` in a widget callback, e.g. upon a button press. + +:param QWidget widget: The top-level widget to show as a dialog. +:return: Whether the dialog was closed successfully, via :meth:`CloseCurrentDialog`. +:rtype: bool +)"); + virtual bool ShowWidgetAsDialog(QWidget *widget) = 0; + + DOCUMENT(R"(Close the active modal dialog. This does nothing if no dialog is being shown. + +.. note:: + Closing a dialog 'sucessfully' does nothing except modify the return value of + :meth:`CloseCurrentDialog`. It allows quick distinguishing between OK and Cancel actions without + having to carry that information separately in a global or other state. + +:param bool success: ``True`` if the dialog was successful (the user clicked an OK/Accept type + button). +)"); + virtual void CloseCurrentDialog(bool success) = 0; + + // layout functions + + DOCUMENT(R"(Creates and returns a horizontal layout widget. + +The widget needs to be added to a parent to become part of a panel or window. + +Children added to this layout widget are listed horizontally. Widget sizing follows default logic, +which typically has some widgets be only large enough for their content and others which are +'greedy' evenly divide any remaining free space. + +:return: The handle to the newly created widget. +:rtype: ``QWidget`` +)"); + virtual QWidget *CreateHorizontalContainer() = 0; + + DOCUMENT(R"(Creates and returns a vertical layout widget. + +The widget needs to be added to a parent to become part of a panel or window. + +Children added to this layout widget are listed vertically. Widget sizing follows default logic, +which typically has some widgets be only large enough for their content and others which are +'greedy' evenly divide any remaining free space. + +:return: The handle to the newly created widget. +:rtype: ``QWidget`` +)"); + virtual QWidget *CreateVerticalContainer() = 0; + + DOCUMENT(R"(Creates and returns a grid layout widget. + +The widget needs to be added to a parent to become part of a panel or window. + +Children added to this layout widget are arranged in a grid. Widget sizing follows default logic, +which typically has some widgets be only large enough for their content and others which are +'greedy' evenly divide any remaining free space. This will not violate the grid constraint though. + +:return: The handle to the newly created widget. +:rtype: ``QWidget`` +)"); + virtual QWidget *CreateGridContainer() = 0; + + DOCUMENT(R"(Removes all child widgets from a parent and makes them invisible. + +These widgets remain valid and can be re-added to another parent or the same parent. + +:param QWidget parent: The parent widget to clear of children. +)"); + virtual void ClearContainedWidgets(QWidget *parent) = 0; + + DOCUMENT(R"(Adds a child widget to a grid layout. If the parent is not a grid layout nothing will +happen and the widget will not be added anywhere. + +:param QWidget parent: The parent grid layout widget. +:param int row: The row at which to add the child widget. +:param int column: The column at which to add the child widget. +:param QWidget child: The child widget to add. +:param int rowSpan: How many rows should this child span over. +:param int columnSpan: How many columns should this child span over. +)"); + virtual void AddGridWidget(QWidget *parent, int row, int column, QWidget *child, int rowSpan, + int columnSpan) = 0; + + DOCUMENT(R"(Adds a child widget to the end of an ordered layout (either horizontal or vertical). +If the parent is not an ordered layout nothing will happen and the widget will not be added anywhere. + +:param QWidget parent: The parent grid layout widget. +:param QWidget child: The child widget to add. +)"); + virtual void AddWidget(QWidget *parent, QWidget *child) = 0; + + DOCUMENT(R"(Insert a child widget at the specified index in an ordered layout (either horizontal +or vertical). If the parent is not an ordered layout nothing will happen and the widget will not be +added anywhere. + +:param QWidget parent: The parent grid layout widget. +:param int index: The index to insert the widget at. If this index is out of bounds it will be + clamped, so that negative indices will be equivalent to index 0 and all indices above the number + of children will append the widget +:param QWidget child: The child widget to add. +)"); + virtual void InsertWidget(QWidget *parent, int index, QWidget *child) = 0; + + // widget manipulation + + DOCUMENT(R"(Set the 'text' of a widget. How this manifests depends on the type of the widget, for +example a text-box or label will set the text directly. For a checkbox or radio button this will +add text next to it. + +:param QWidget widget: The widget to set text for. +:param str text: The text to set for the widget. +)"); + virtual void SetWidgetText(QWidget *widget, const rdcstr &text) = 0; + + DOCUMENT(R"(Return the current text of a widget. See :meth:`SetWidgetText`. + +:param QWidget widget: The widget to query. +:return: The widget's current text, which may be an empty string if no valid text is available. +:rtype: str +)"); + virtual rdcstr GetWidgetText(QWidget *widget) = 0; + + DOCUMENT(R"(Change the font properties of a widget. + +:param QWidget widget: The widget to change font of. +:param str font: The new font family to use, or an empty string to leave the font family the same. +:param int fontSize: The new font point size to use, or 0 to leave the size the same. +:param bool bold: ``True`` if the font should be bold. +:param bool italic: ``True`` if the font should be italic. +)"); + virtual void SetWidgetFont(QWidget *widget, const rdcstr &font, int fontSize, bool bold, + bool italic) = 0; + + DOCUMENT(R"(Set whether the widget is enabled or not. This generally only affects interactive +widgets and not fixed widgets, interactive widgets become read-only while still displaying the same +data. + +.. note:: + Disabled widgets can still be modified programmatically, they are only disabled for the user. + +:param QWidget widget: The widget to enable or disable. +:param bool enabled: ``True`` if the widget should be enabled. +)"); + virtual void SetWidgetEnabled(QWidget *widget, bool enabled) = 0; + + DOCUMENT(R"(Return the current enabled-state of a widget. See :meth:`SetWidgetEnabled`. + +:param QWidget widget: The widget to query. +:return: ``True`` if the widget is currently enabled. +:rtype: bool +)"); + virtual bool IsWidgetEnabled(QWidget *widget) = 0; + + // specific widgets + + DOCUMENT(R"(Create a groupbox widget which can optionally allow collapsing. + +This widget can have children added, but it is recommended to immediately add only one child which +is a layout type widget, to allow customising how children are added. By default the children are +added in a vertical layout. + +The widget needs to be added to a parent to become part of a panel or window. + +:param bool collapsible: ``True`` if the groupbox should have a toggle in its header to allow + collapsing its contents down vertically. +:return: The handle to the newly created widget. +:rtype: ``QWidget`` +)"); + virtual QWidget *CreateGroupBox(bool collapsible) = 0; + + DOCUMENT(R"(Create a normal button widget. + +:param WidgetCallback pressed: Callback to be called when the button is pressed. +:return: The handle to the newly created widget. +:rtype: ``QWidget`` +)"); + virtual QWidget *CreateButton(WidgetCallback pressed) = 0; + + DOCUMENT(R"(Create a read-only label widget. + +.. note:: + This widget will be blank by default, you can set the text with :meth:`SetWidgetText`. + +:return: The handle to the newly created widget. +:rtype: ``QWidget`` +)"); + virtual QWidget *CreateLabel() = 0; + + DOCUMENT(R"(Create a checkbox widget which can be toggled between unchecked and checked. When +created the checkbox is unchecked. + +:param WidgetCallback changed: Callback to be called when the widget is toggled. +:return: The handle to the newly created widget. +:rtype: ``QWidget`` +)"); + virtual QWidget *CreateCheckbox(WidgetCallback changed) = 0; + + DOCUMENT(R"(Create a radio box widget which can be toggled between unchecked and checked but with +at most one radio box in any group of sibling radio boxes being checked. + +Upon creation the radio box is unchecked, even in a group of other radio boxes that are unchecked. +If you want a default radio box to be checked, you should use :meth:`SetWidgetChecked`. + +:param WidgetCallback changed: Callback to be called when the widget is toggled. +:return: The handle to the newly created widget. +:rtype: ``QWidget`` +)"); + virtual QWidget *CreateRadiobox(WidgetCallback changed) = 0; + + DOCUMENT(R"(Set whether the widget is checked or not. This only affects checkboxes and radio +boxes. If another type of widget is passed nothing will happen. + +:param QWidget checkableWidget: The widget to check or uncheck. +:param bool checked: ``True`` if the widget should be checked. +)"); + virtual void SetWidgetChecked(QWidget *checkableWidget, bool checked) = 0; + + DOCUMENT(R"(Return the current checked-state of a widget. See :meth:`SetWidgetChecked`. If another +type of widget is passed other than a checkbox or radio box ``False`` will be returned. + +:param QWidget checkableWidget: The widget to query. +:return: ``True`` if the widget is currently checked. +:rtype: bool +)"); + virtual bool IsWidgetChecked(QWidget *checkableWidget) = 0; + + DOCUMENT(R"(Create a spinbox widget with a numerical value and up/down buttons to change it. + +The number of decimal places can be set to 0 for an integer spinbox, and in that case the step +should be set to 1.0. + +By default the spinbox has minimum and maximum values of 0.0 and 100.0, these can be changed with +:meth:`SetSpinboxBounds`. + +:param int decimalPlaces: The number of decimal places to display when showing the number. +:param float step: The step value to apply in each direction when clicking up or down. +:return: The handle to the newly created widget. +:rtype: ``QWidget`` +)"); + virtual QWidget *CreateSpinbox(int decimalPlaces, double step) = 0; + + DOCUMENT(R"(Set the minimum and maximum values allowed in the spinbox. If another type of widget +is passed nothing will happen. + +:param QWidget spinbox: The spinbox. +:param float minVal: The minimum value allowed for the spinbox to reach. Lower values entered will + be clamped to this. +:param float maxVal: The maximum value allowed for the spinbox to reach. Higher values entered will + be clamped to this. +)"); + virtual void SetSpinboxBounds(QWidget *spinbox, double minVal, double maxVal) = 0; + + DOCUMENT(R"(Set the value contained in a spinbox. If another type of widget is passed nothing will +happen. + +:param QWidget spinbox: The spinbox. +:param float value: The value for the spinbox, which will be clamped by the current bounds. +)"); + virtual void SetSpinboxValue(QWidget *spinbox, double value) = 0; + + DOCUMENT(R"(Return the current value of a spinbox widget. If another type of widget is passed +``0.0`` will be returned. + +:param QWidget spinbox: The widget to query. +:return: The current value of the spinbox. +:rtype: float +)"); + virtual double GetSpinboxValue(QWidget *spinbox) = 0; + + DOCUMENT(R"(Create a text box widget for the user to enter text into. + +:param bool singleLine: ``True`` if the widget should be a single-line entry, otherwise it is a + multi-line text box. +:param WidgetCallback changed: Callback to be called when the text in the textbox is changed. +:return: The handle to the newly created widget. +:rtype: ``QWidget`` +)"); + virtual QWidget *CreateTextBox(bool singleLine, WidgetCallback changed) = 0; + + DOCUMENT(R"(Create a drop-down combo box widget. + +When created there are no pre-defined entries in the drop-down section. This can be changed with +:meth:`SetComboOptions`. + +:param bool editable: ``True`` if the widget should allow the user to enter any text they wish as + well as being able to select a pre-defined entry. +:param WidgetCallback changed: Callback to be called when the text in the combobox is changed. This + will be called both when a new option is selected or when the user edits the text. +:return: The handle to the newly created widget. +:rtype: ``QWidget`` +)"); + virtual QWidget *CreateComboBox(bool editable, WidgetCallback changed) = 0; + + DOCUMENT(R"(Set the pre-defined options in a drop-down combo box. If another type of widget is +passed nothing will happen. + +:param QWidget combo: The combo box. +:param ``list`` of ``str`` options: The new options for the combo box. +)"); + virtual void SetComboOptions(QWidget *combo, const rdcarray &options) = 0; +}; + +DECLARE_REFLECTION_STRUCT(IMiniQtHelper); + DOCUMENT(R"(A manager for listing available and active extensions, as well as the interface for extensions to register hooks and additional functionality. @@ -412,6 +836,13 @@ struct IExtensionManager ////////////////////////////////////////////////////////////////////////// // Utility UI functions + DOCUMENT(R"(Returns a handle to the mini Qt helper. See :class:`MiniQtHelper`. + +:return: The helper interface. +:rtype: MiniQtHelper +)"); + virtual IMiniQtHelper &GetMiniQtHelper() = 0; + DOCUMENT(R"(Display a simple informational message dialog. :param str text: The text of the dialog itself, required. diff --git a/qrenderdoc/Code/Interface/QRDInterface.h b/qrenderdoc/Code/Interface/QRDInterface.h index 1b07c3dfd..9f43ba47c 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.h +++ b/qrenderdoc/Code/Interface/QRDInterface.h @@ -545,8 +545,8 @@ struct IPerformanceCounterViewer { DOCUMENT( "Retrieves the QWidget for this :class:`PerformanceCounterViewer` if PySide2 is available, " - "or " - "``None``."); + "or otherwise unique opaque pointer that can be passed to RenderDoc functions expecting a " + "QWidget."); virtual QWidget *Widget() = 0; protected: @@ -1980,8 +1980,7 @@ This function is intended for internal use for restoring layouts, and generally by user code. :param str objectName: The built-in name of a singleton window. -:return: The handle to the existing or newly created window of this type, or ``None`` if PySide2 is - not available. +:return: The handle to the existing or newly created window of this type. :rtype: ``QWidget`` )"); virtual QWidget *CreateBuiltinWindow(const rdcstr &objectName) = 0; diff --git a/qrenderdoc/Code/MiniQtHelper.cpp b/qrenderdoc/Code/MiniQtHelper.cpp new file mode 100644 index 000000000..28ce51f9a --- /dev/null +++ b/qrenderdoc/Code/MiniQtHelper.cpp @@ -0,0 +1,556 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2020 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "MiniQtHelper.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Code/QRDUtils.h" +#include "Code/pyrenderdoc/PythonContext.h" +#include "Widgets/CollapseGroupBox.h" +#include "Widgets/Extended/RDDoubleSpinBox.h" +#include "Widgets/Extended/RDLabel.h" +#include "Widgets/Extended/RDLineEdit.h" +#include "Widgets/Extended/RDTextEdit.h" +#include "Widgets/Extended/RDToolButton.h" + +MiniQtHelper::MiniQtHelper(ICaptureContext &ctx) : m_Ctx(ctx) +{ +} + +MiniQtHelper::~MiniQtHelper() +{ + PythonContext::ProcessExtensionWork([this]() { + for(rdcpair &conn : m_Connections) + { + if(conn.second) + QObject::disconnect(conn.second); + } + }); +} + +void MiniQtHelper::AddWidgetCallback(QWidget *widget, QMetaObject::Connection connection) +{ + // remember the connection and delete it python-safely at shutdown if it's still there + m_Connections.push_back({widget, connection}); + + // when this widget is destroyed otherwise, delete it python-safely then. + QObject::connect(widget, &QWidget::destroyed, [this, widget]() { + PythonContext::ProcessExtensionWork([this, widget]() { + for(int i = 0; i < m_Connections.count();) + { + if(m_Connections[i].first == widget) + { + QObject::disconnect(m_Connections[i].second); + m_Connections.takeAt(i); + continue; + } + + i++; + } + }); + }); +} + +QWidget *MiniQtHelper::CreateToplevelWidget(const rdcstr &windowTitle) +{ + QWidget *ret = new QWidget(); + ret->setWindowTitle(windowTitle); + ret->setLayout(new QVBoxLayout()); + return ret; +} + +void MiniQtHelper::SetWidgetName(QWidget *widget, const rdcstr &name) +{ + if(widget) + widget->setObjectName(name); +} + +rdcstr MiniQtHelper::GetWidgetName(QWidget *widget) +{ + if(widget) + return widget->objectName(); + + return rdcstr(); +} + +rdcstr MiniQtHelper::GetWidgetType(QWidget *widget) +{ + if(widget) + return widget->metaObject()->className(); + + return rdcstr(); +} + +QWidget *MiniQtHelper::FindChildByName(QWidget *parent, const rdcstr &name) +{ + if(!parent) + return NULL; + + return parent->findChild(name); +} + +QWidget *MiniQtHelper::GetParent(QWidget *widget) +{ + if(!widget) + return NULL; + + return widget->parentWidget(); +} + +int MiniQtHelper::GetNumChildren(QWidget *widget) +{ + if(!widget) + return 0; + + QLayout *layout = widget->layout(); + if(!layout) + return 0; + + return layout->count(); +} + +QWidget *MiniQtHelper::GetChild(QWidget *parent, int index) +{ + if(!parent) + return NULL; + + QLayout *layout = parent->layout(); + if(!layout) + return NULL; + + QLayoutItem *item = layout->itemAt(index); + if(!item) + return NULL; + + return item->widget(); +} + +bool MiniQtHelper::ShowWidgetAsDialog(QWidget *widget) +{ + QWidget *mainWindow = m_Ctx.GetMainWindow()->Widget(); + + m_CurrentDialog = new QDialog(mainWindow); + m_CurrentDialog->setWindowFlags(m_CurrentDialog->windowFlags() & ~Qt::WindowContextHelpButtonHint); + m_CurrentDialog->setWindowIcon(mainWindow->windowIcon()); + m_CurrentDialog->setWindowTitle(widget->windowTitle()); + m_CurrentDialog->setModal(true); + + QVBoxLayout l; + l.addWidget(widget); + l.setMargin(3); + + m_CurrentDialog->setLayout(&l); + + bool success = (RDDialog::show(m_CurrentDialog) == QDialog::Accepted); + + m_CurrentDialog->deleteLater(); + m_CurrentDialog = NULL; + + return success; +} + +void MiniQtHelper::CloseCurrentDialog(bool success) +{ + if(m_CurrentDialog) + { + if(success) + m_CurrentDialog->accept(); + else + m_CurrentDialog->reject(); + } +} + +QWidget *MiniQtHelper::CreateHorizontalContainer() +{ + QWidget *ret = new QWidget(); + ret->setLayout(new QHBoxLayout(ret)); + return ret; +} + +QWidget *MiniQtHelper::CreateVerticalContainer() +{ + QWidget *ret = new QWidget(); + ret->setLayout(new QVBoxLayout(ret)); + return ret; +} + +QWidget *MiniQtHelper::CreateGridContainer() +{ + QWidget *ret = new QWidget(); + ret->setLayout(new QGridLayout(ret)); + return ret; +} + +void MiniQtHelper::ClearContainedWidgets(QWidget *parent) +{ + if(!parent) + return; + + QLayout *layout = parent->layout(); + if(!layout) + return; + + while(layout->count()) + { + layout->takeAt(0)->widget()->hide(); + } +} + +void MiniQtHelper::AddGridWidget(QWidget *parent, int row, int column, QWidget *child, int rowSpan, + int columnSpan) +{ + if(!parent || !child) + return; + + QLayout *layout = parent->layout(); + if(!layout) + return; + + QGridLayout *grid = qobject_cast(layout); + if(!grid) + return; + + grid->addWidget(child, row, column, rowSpan, columnSpan); +} + +void MiniQtHelper::AddWidget(QWidget *parent, QWidget *child) +{ + if(!parent) + return; + + QLayout *layout = parent->layout(); + if(!layout) + return; + + QBoxLayout *box = qobject_cast(layout); + if(!box) + return; + + box->addWidget(child); +} + +void MiniQtHelper::InsertWidget(QWidget *parent, int index, QWidget *child) +{ + if(!parent) + return; + + QLayout *layout = parent->layout(); + if(!layout) + return; + + QBoxLayout *box = qobject_cast(layout); + if(!box) + return; + + box->insertWidget(qMin(qMax(0, index), box->count()), child); +} + +void MiniQtHelper::SetWidgetText(QWidget *widget, const rdcstr &text) +{ + if(!widget) + return; + + widget->setWindowTitle(text); + +#define SET_TEXT(TextWidget) \ + { \ + TextWidget *w = qobject_cast(widget); \ + if(w) \ + return w->setText(text); \ + } + + SET_TEXT(RDLabel); + SET_TEXT(QLabel); + SET_TEXT(RDLineEdit); + SET_TEXT(RDTextEdit); + SET_TEXT(QLineEdit); + SET_TEXT(QTextEdit); + SET_TEXT(QPushButton); + SET_TEXT(RDToolButton); + SET_TEXT(QToolButton); + SET_TEXT(QCheckBox); + SET_TEXT(QRadioButton); + + { + QGroupBox *w = qobject_cast(widget); + if(w) + return w->setTitle(text); + } + { + CollapseGroupBox *w = qobject_cast(widget); + if(w) + return w->setTitle(text); + } +} + +rdcstr MiniQtHelper::GetWidgetText(QWidget *widget) +{ + if(!widget) + return rdcstr(); + +#define GET_TEXT(TextWidget) \ + { \ + TextWidget *w = qobject_cast(widget); \ + if(w) \ + return w->text(); \ + } + + GET_TEXT(RDLabel); + GET_TEXT(QLabel); + GET_TEXT(RDLineEdit); + GET_TEXT(QLineEdit); + GET_TEXT(QPushButton); + GET_TEXT(RDToolButton); + GET_TEXT(QToolButton); + GET_TEXT(QCheckBox); + GET_TEXT(QRadioButton); + + { + QTextEdit *w = qobject_cast(widget); + if(w) + return w->toPlainText(); + } + { + RDTextEdit *w = qobject_cast(widget); + if(w) + return w->toPlainText(); + } + + { + QGroupBox *w = qobject_cast(widget); + if(w) + return w->title(); + } + { + CollapseGroupBox *w = qobject_cast(widget); + if(w) + return w->title(); + } + + // if all else failed, return the window title of the widget + return widget->windowTitle(); +} + +void MiniQtHelper::SetWidgetFont(QWidget *widget, const rdcstr &font, int fontSize, bool bold, + bool italic) +{ + if(!widget) + return; + + QFont f = widget->font(); + + if(font.empty()) + f.setFamily(font); + if(fontSize != 0) + f.setPointSize(fontSize); + f.setBold(bold); + f.setItalic(italic); + + widget->setFont(f); +} + +void MiniQtHelper::SetWidgetEnabled(QWidget *widget, bool enabled) +{ + if(!widget) + return; + + widget->setEnabled(enabled); +} + +bool MiniQtHelper::IsWidgetEnabled(QWidget *widget) +{ + if(!widget) + return false; + + return widget->isEnabled(); +} + +QWidget *MiniQtHelper::CreateGroupBox(bool collapsible) +{ + QWidget *ret; + if(collapsible) + ret = new CollapseGroupBox(); + else + ret = new QGroupBox(); + ret->setLayout(new QVBoxLayout()); + return ret; +} + +QWidget *MiniQtHelper::CreateButton(WidgetCallback pressed) +{ + QPushButton *w = new QPushButton(); + if(pressed) + AddWidgetCallback(w, QObject::connect(w, &QPushButton::pressed, + [this, w, pressed]() { pressed(&m_Ctx, w, rdcstr()); })); + return w; +} + +QWidget *MiniQtHelper::CreateLabel() +{ + return new RDLabel(); +} + +QWidget *MiniQtHelper::CreateCheckbox(WidgetCallback changed) +{ + QCheckBox *w = new QCheckBox(); + if(changed) + AddWidgetCallback(w, QObject::connect(w, &QCheckBox::stateChanged, + [this, w, changed]() { changed(&m_Ctx, w, rdcstr()); })); + return w; +} + +QWidget *MiniQtHelper::CreateRadiobox(WidgetCallback changed) +{ + QRadioButton *w = new QRadioButton(); + if(changed) + AddWidgetCallback(w, QObject::connect(w, &QRadioButton::toggled, + [this, w, changed]() { changed(&m_Ctx, w, rdcstr()); })); + return w; +} + +void MiniQtHelper::SetWidgetChecked(QWidget *checkableWidget, bool checked) +{ + if(!checkableWidget) + return; + + QCheckBox *check = qobject_cast(checkableWidget); + QRadioButton *radio = qobject_cast(checkableWidget); + + if(check) + check->setChecked(checked); + else if(radio) + radio->setChecked(checked); +} + +bool MiniQtHelper::IsWidgetChecked(QWidget *checkableWidget) +{ + if(!checkableWidget) + return false; + + QCheckBox *check = qobject_cast(checkableWidget); + QRadioButton *radio = qobject_cast(checkableWidget); + + if(check) + return check->isChecked(); + else if(radio) + return radio->isChecked(); + + return false; +} + +QWidget *MiniQtHelper::CreateSpinbox(int decimalPlaces, double step) +{ + RDDoubleSpinBox *ret = new RDDoubleSpinBox(); + ret->setSingleStep(step); + ret->setDecimals(decimalPlaces); + return ret; +} + +void MiniQtHelper::SetSpinboxBounds(QWidget *spinbox, double minVal, double maxVal) +{ + if(!spinbox) + return; + + RDDoubleSpinBox *spin = qobject_cast(spinbox); + if(spin) + spin->setRange(minVal, maxVal); +} + +void MiniQtHelper::SetSpinboxValue(QWidget *spinbox, double value) +{ + if(!spinbox) + return; + + RDDoubleSpinBox *spin = qobject_cast(spinbox); + if(spin) + spin->setValue(value); +} + +double MiniQtHelper::GetSpinboxValue(QWidget *spinbox) +{ + if(!spinbox) + return 0.0; + + RDDoubleSpinBox *spin = qobject_cast(spinbox); + if(spin) + return spin->value(); + + return 0.0; +} + +QWidget *MiniQtHelper::CreateTextBox(bool singleLine, WidgetCallback changed) +{ + if(singleLine) + { + RDLineEdit *w = new RDLineEdit(); + if(changed) + AddWidgetCallback(w, QObject::connect(w, &RDLineEdit::textEdited, [this, w, changed]() { + changed(&m_Ctx, w, w->text()); + })); + return w; + } + else + { + RDTextEdit *w = new RDTextEdit(); + if(changed) + AddWidgetCallback(w, QObject::connect(w, &RDTextEdit::textChanged, [this, w, changed]() { + changed(&m_Ctx, w, w->toPlainText()); + })); + return w; + } +} + +QWidget *MiniQtHelper::CreateComboBox(bool editable, WidgetCallback changed) +{ + QComboBox *w = new QComboBox(); + if(changed) + AddWidgetCallback( + w, QObject::connect(w, &QComboBox::currentTextChanged, + [this, w, changed](QString str) { changed(&m_Ctx, w, str); })); + w->setEditable(editable); + return w; +} + +void MiniQtHelper::SetComboOptions(QWidget *combo, const rdcarray &options) +{ + if(!combo) + return; + QComboBox *comb = qobject_cast(combo); + + QStringList texts; + + for(const rdcstr &o : options) + texts << o; + + comb->clear(); + comb->addItems(texts); +} diff --git a/qrenderdoc/Code/MiniQtHelper.h b/qrenderdoc/Code/MiniQtHelper.h new file mode 100644 index 000000000..1877be88b --- /dev/null +++ b/qrenderdoc/Code/MiniQtHelper.h @@ -0,0 +1,111 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2020 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#pragma once + +#include +#include "Interface/QRDInterface.h" + +class QDialog; + +class MiniQtHelper : public IMiniQtHelper +{ +public: + MiniQtHelper(ICaptureContext &ctx); + ~MiniQtHelper(); + + QWidget *CreateToplevelWidget(const rdcstr &windowTitle) override; + + // widget hierarchy + + void SetWidgetName(QWidget *widget, const rdcstr &name) override; + rdcstr GetWidgetName(QWidget *widget) override; + rdcstr GetWidgetType(QWidget *widget) override; + QWidget *FindChildByName(QWidget *parent, const rdcstr &name) override; + QWidget *GetParent(QWidget *widget) override; + int GetNumChildren(QWidget *widget) override; + QWidget *GetChild(QWidget *parent, int index) override; + + // dialogs + + bool ShowWidgetAsDialog(QWidget *widget) override; + void CloseCurrentDialog(bool success) override; + + // layout functions + + QWidget *CreateHorizontalContainer() override; + QWidget *CreateVerticalContainer() override; + QWidget *CreateGridContainer() override; + + void ClearContainedWidgets(QWidget *parent) override; + void AddGridWidget(QWidget *parent, int row, int column, QWidget *child, int rowSpan, + int columnSpan) override; + void AddWidget(QWidget *parent, QWidget *child) override; + void InsertWidget(QWidget *parent, int index, QWidget *child) override; + + // widget manipulation + + void SetWidgetText(QWidget *widget, const rdcstr &text) override; + rdcstr GetWidgetText(QWidget *widget) override; + + void SetWidgetFont(QWidget *widget, const rdcstr &font, int fontSize, bool bold, + bool italic) override; + + void SetWidgetEnabled(QWidget *widget, bool enabled) override; + bool IsWidgetEnabled(QWidget *widget) override; + + // specific widgets + + QWidget *CreateGroupBox(bool collapsible) override; + + QWidget *CreateButton(WidgetCallback pressed) override; + + QWidget *CreateLabel() override; + + QWidget *CreateCheckbox(WidgetCallback changed) override; + QWidget *CreateRadiobox(WidgetCallback changed) override; + + void SetWidgetChecked(QWidget *checkableWidget, bool checked) override; + bool IsWidgetChecked(QWidget *checkableWidget) override; + + QWidget *CreateSpinbox(int decimalPlaces, double step) override; + + void SetSpinboxBounds(QWidget *spinbox, double minVal, double maxVal) override; + void SetSpinboxValue(QWidget *spinbox, double value) override; + double GetSpinboxValue(QWidget *spinbox) override; + + QWidget *CreateTextBox(bool singleLine, WidgetCallback changed) override; + + QWidget *CreateComboBox(bool editable, WidgetCallback changed) override; + + void SetComboOptions(QWidget *combo, const rdcarray &options) override; + +private: + ICaptureContext &m_Ctx; + + QDialog *m_CurrentDialog = NULL; + rdcarray> m_Connections; + + void AddWidgetCallback(QWidget *widget, QMetaObject::Connection connection); +}; diff --git a/qrenderdoc/Code/pyrenderdoc/PythonContext.cpp b/qrenderdoc/Code/pyrenderdoc/PythonContext.cpp index ba4684375..91bea7d1f 100644 --- a/qrenderdoc/Code/pyrenderdoc/PythonContext.cpp +++ b/qrenderdoc/Code/pyrenderdoc/PythonContext.cpp @@ -491,6 +491,17 @@ void PythonContext::Finish() PyGILState_Release(gil); } +void PythonContext::PausePythonThreading() +{ + m_SavedThread = PyEval_SaveThread(); +} + +void PythonContext::ResumePythonThreading() +{ + PyEval_RestoreThread((PyThreadState *)m_SavedThread); + m_SavedThread = NULL; +} + void PythonContext::GlobalShutdown() { if(!initialised()) diff --git a/qrenderdoc/Code/pyrenderdoc/PythonContext.h b/qrenderdoc/Code/pyrenderdoc/PythonContext.h index 0f7db6188..25de0f50b 100644 --- a/qrenderdoc/Code/pyrenderdoc/PythonContext.h +++ b/qrenderdoc/Code/pyrenderdoc/PythonContext.h @@ -52,6 +52,9 @@ public: void Finish(); PyThreadState *GetExecutingThreadState() { return m_State; } + void PausePythonThreading(); + void ResumePythonThreading(); + static void GlobalInit(); static void GlobalShutdown(); @@ -131,6 +134,9 @@ private: // not PyThreadState *m_State = NULL; + // this is stored so we can push/pop the GIL state properly + void *m_SavedThread = NULL; + struct { QString file; diff --git a/qrenderdoc/Windows/PythonShell.cpp b/qrenderdoc/Windows/PythonShell.cpp index 2e2e505c9..a75416044 100644 --- a/qrenderdoc/Windows/PythonShell.cpp +++ b/qrenderdoc/Windows/PythonShell.cpp @@ -37,125 +37,28 @@ // Note this does NOT make CaptureContext thread safe. We just invoke for any potentially UI // operations. All invokes are blocking, so there can't be any times when the UI thread waits // on the python thread. -struct CaptureContextInvoker : ICaptureContext +template +struct ObjectForwarder : Obj { + ObjectForwarder(PythonShell *sh, Obj &o) : m_Shell(sh), m_Obj(o) {} PythonShell *m_Shell; - ICaptureContext &m_Ctx; - CaptureContextInvoker(PythonShell *shell, ICaptureContext &ctx) : m_Shell(shell), m_Ctx(ctx) {} - virtual ~CaptureContextInvoker() {} - // - /////////////////////////////////////////////////////////////////////// - // pass-through functions that don't need the UI thread - /////////////////////////////////////////////////////////////////////// - // - virtual rdcstr TempCaptureFilename(const rdcstr &appname) override - { - return m_Ctx.TempCaptureFilename(appname); - } - virtual IExtensionManager &Extensions() override { return m_Ctx.Extensions(); } - virtual IReplayManager &Replay() override { return m_Ctx.Replay(); } - virtual bool IsCaptureLoaded() override { return m_Ctx.IsCaptureLoaded(); } - virtual bool IsCaptureLocal() override { return m_Ctx.IsCaptureLocal(); } - virtual bool IsCaptureTemporary() override { return m_Ctx.IsCaptureTemporary(); } - virtual bool IsCaptureLoading() override { return m_Ctx.IsCaptureLoading(); } - virtual rdcstr GetCaptureFilename() override { return m_Ctx.GetCaptureFilename(); } - virtual CaptureModifications GetCaptureModifications() override - { - return m_Ctx.GetCaptureModifications(); - } - virtual const FrameDescription &FrameInfo() override { return m_Ctx.FrameInfo(); } - virtual const APIProperties &APIProps() override { return m_Ctx.APIProps(); } - virtual rdcarray TargetShaderEncodings() override - { - return m_Ctx.TargetShaderEncodings(); - } - virtual rdcarray CustomShaderEncodings() override - { - return m_Ctx.CustomShaderEncodings(); - } - virtual uint32_t CurSelectedEvent() override { return m_Ctx.CurSelectedEvent(); } - virtual uint32_t CurEvent() override { return m_Ctx.CurEvent(); } - virtual const DrawcallDescription *CurSelectedDrawcall() override - { - return m_Ctx.CurSelectedDrawcall(); - } - virtual const DrawcallDescription *CurDrawcall() override { return m_Ctx.CurDrawcall(); } - virtual const DrawcallDescription *GetFirstDrawcall() override - { - return m_Ctx.GetFirstDrawcall(); - } - virtual const DrawcallDescription *GetLastDrawcall() override { return m_Ctx.GetLastDrawcall(); } - virtual const rdcarray &CurDrawcalls() override - { - return m_Ctx.CurDrawcalls(); - } - virtual ResourceDescription *GetResource(ResourceId id) override { return m_Ctx.GetResource(id); } - virtual const rdcarray &GetResources() override - { - return m_Ctx.GetResources(); - } - virtual rdcstr GetResourceName(ResourceId id) override { return m_Ctx.GetResourceName(id); } - virtual rdcstr GetResourceNameUnsuffixed(ResourceId id) override - { - return m_Ctx.GetResourceNameUnsuffixed(id); - } - virtual bool IsAutogeneratedName(ResourceId id) override { return m_Ctx.IsAutogeneratedName(id); } - virtual bool HasResourceCustomName(ResourceId id) override - { - return m_Ctx.HasResourceCustomName(id); - } - virtual int ResourceNameCacheID() override { return m_Ctx.ResourceNameCacheID(); } - virtual TextureDescription *GetTexture(ResourceId id) override { return m_Ctx.GetTexture(id); } - virtual const rdcarray &GetTextures() override { return m_Ctx.GetTextures(); } - virtual BufferDescription *GetBuffer(ResourceId id) override { return m_Ctx.GetBuffer(id); } - virtual const rdcarray &GetBuffers() override { return m_Ctx.GetBuffers(); } - virtual const DrawcallDescription *GetDrawcall(uint32_t eventId) override - { - return m_Ctx.GetDrawcall(eventId); - } - virtual bool OpenRGPProfile(const rdcstr &filename) override - { - return m_Ctx.OpenRGPProfile(filename); - } - virtual IRGPInterop *GetRGPInterop() override { return m_Ctx.GetRGPInterop(); } - virtual const SDFile &GetStructuredFile() override { return m_Ctx.GetStructuredFile(); } - virtual WindowingSystem CurWindowingSystem() override { return m_Ctx.CurWindowingSystem(); } - virtual const rdcarray &DebugMessages() override { return m_Ctx.DebugMessages(); } - virtual int UnreadMessageCount() override { return m_Ctx.UnreadMessageCount(); } - virtual void MarkMessagesRead() override { return m_Ctx.MarkMessagesRead(); } - virtual rdcstr GetNotes(const rdcstr &key) override { return m_Ctx.GetNotes(key); } - virtual rdcarray GetBookmarks() override { return m_Ctx.GetBookmarks(); } - virtual const D3D11Pipe::State *CurD3D11PipelineState() override - { - return m_Ctx.CurD3D11PipelineState(); - } - virtual const D3D12Pipe::State *CurD3D12PipelineState() override - { - return m_Ctx.CurD3D12PipelineState(); - } - virtual const GLPipe::State *CurGLPipelineState() override { return m_Ctx.CurGLPipelineState(); } - virtual const VKPipe::State *CurVulkanPipelineState() override - { - return m_Ctx.CurVulkanPipelineState(); - } - virtual const PipeState &CurPipelineState() override { return m_Ctx.CurPipelineState(); } - virtual PersistantConfig &Config() override { return m_Ctx.Config(); } - // - /////////////////////////////////////////////////////////////////////// - // functions that invoke onto the UI thread - /////////////////////////////////////////////////////////////////////// - // + Obj &m_Obj; + template void InvokeVoidFunction(F ptr, paramTypes... params) { if(!GUIInvoke::onUIThread()) { - GUIInvoke::blockcall(m_Shell, [this, ptr, params...]() { (m_Ctx.*ptr)(params...); }); - + PythonContext *scriptContext = m_Shell->GetScriptContext(); + if(scriptContext) + scriptContext->PausePythonThreading(); + GUIInvoke::blockcall(m_Shell, [this, ptr, params...]() { (m_Obj.*ptr)(params...); }); + if(scriptContext) + scriptContext->ResumePythonThreading(); return; } - (m_Ctx.*ptr)(params...); + (m_Obj.*ptr)(params...); } template @@ -164,15 +67,395 @@ struct CaptureContextInvoker : ICaptureContext if(!GUIInvoke::onUIThread()) { R ret; + PythonContext *scriptContext = m_Shell->GetScriptContext(); + if(scriptContext) + scriptContext->PausePythonThreading(); GUIInvoke::blockcall(m_Shell, - [this, &ret, ptr, params...]() { ret = (m_Ctx.*ptr)(params...); }); - + [this, &ret, ptr, params...]() { ret = (m_Obj.*ptr)(params...); }); + if(scriptContext) + scriptContext->ResumePythonThreading(); return ret; } - return (m_Ctx.*ptr)(params...); + return (m_Obj.*ptr)(params...); + } +}; + +struct MiniQtInvoker : ObjectForwarder +{ + MiniQtInvoker(PythonShell *shell, IMiniQtHelper &obj) : ObjectForwarder(shell, obj) {} + virtual ~MiniQtInvoker() {} + /////////////////////////////////////////////////////////////////////// + // all functions invoke onto the UI thread since they deal with widgets! + /////////////////////////////////////////////////////////////////////// + + QWidget *CreateToplevelWidget(const rdcstr &windowTitle) + { + return InvokeRetFunction(&IMiniQtHelper::CreateToplevelWidget, windowTitle); } + // widget hierarchy + + void SetWidgetName(QWidget *widget, const rdcstr &name) + { + InvokeVoidFunction(&IMiniQtHelper::SetWidgetName, widget, name); + } + rdcstr GetWidgetName(QWidget *widget) + { + return InvokeRetFunction(&IMiniQtHelper::GetWidgetName, widget); + } + rdcstr GetWidgetType(QWidget *widget) + { + return InvokeRetFunction(&IMiniQtHelper::GetWidgetType, widget); + } + QWidget *FindChildByName(QWidget *parent, const rdcstr &name) + { + return InvokeRetFunction(&IMiniQtHelper::FindChildByName, parent, name); + } + QWidget *GetParent(QWidget *widget) + { + return InvokeRetFunction(&IMiniQtHelper::GetParent, widget); + } + int GetNumChildren(QWidget *widget) + { + return InvokeRetFunction(&IMiniQtHelper::GetNumChildren, widget); + } + QWidget *GetChild(QWidget *parent, int index) + { + return InvokeRetFunction(&IMiniQtHelper::GetChild, parent, index); + } + + // dialogs + + bool ShowWidgetAsDialog(QWidget *widget) + { + return InvokeRetFunction(&IMiniQtHelper::ShowWidgetAsDialog, widget); + } + void CloseCurrentDialog(bool success) + { + InvokeVoidFunction(&IMiniQtHelper::CloseCurrentDialog, success); + } + + // layout functions + + QWidget *CreateHorizontalContainer() + { + return InvokeRetFunction(&IMiniQtHelper::CreateHorizontalContainer); + } + QWidget *CreateVerticalContainer() + { + return InvokeRetFunction(&IMiniQtHelper::CreateVerticalContainer); + } + QWidget *CreateGridContainer() + { + return InvokeRetFunction(&IMiniQtHelper::CreateGridContainer); + } + + void ClearContainedWidgets(QWidget *parent) + { + InvokeVoidFunction(&IMiniQtHelper::ClearContainedWidgets, parent); + } + void AddGridWidget(QWidget *parent, int row, int column, QWidget *child, int rowSpan, int columnSpan) + { + InvokeVoidFunction(&IMiniQtHelper::AddGridWidget, parent, row, column, child, rowSpan, + columnSpan); + } + void AddWidget(QWidget *parent, QWidget *child) + { + InvokeVoidFunction(&IMiniQtHelper::AddWidget, parent, child); + } + void InsertWidget(QWidget *parent, int index, QWidget *child) + { + InvokeVoidFunction(&IMiniQtHelper::InsertWidget, parent, index, child); + } + + // widget manipulation + + void SetWidgetText(QWidget *widget, const rdcstr &text) + { + InvokeVoidFunction(&IMiniQtHelper::SetWidgetText, widget, text); + } + rdcstr GetWidgetText(QWidget *widget) + { + return InvokeRetFunction(&IMiniQtHelper::GetWidgetText, widget); + } + + void SetWidgetFont(QWidget *widget, const rdcstr &font, int fontSize, bool bold, bool italic) + { + InvokeVoidFunction(&IMiniQtHelper::SetWidgetFont, widget, font, fontSize, bold, italic); + } + + void SetWidgetEnabled(QWidget *widget, bool enabled) + { + InvokeVoidFunction(&IMiniQtHelper::SetWidgetEnabled, widget, enabled); + } + bool IsWidgetEnabled(QWidget *widget) + { + return InvokeRetFunction(&IMiniQtHelper::IsWidgetEnabled, widget); + } + + // specific widgets + + QWidget *CreateGroupBox(bool collapsible) + { + return InvokeRetFunction(&IMiniQtHelper::CreateGroupBox, collapsible); + } + + QWidget *CreateButton(WidgetCallback pressed) + { + return InvokeRetFunction(&IMiniQtHelper::CreateButton, pressed); + } + + QWidget *CreateLabel() { return InvokeRetFunction(&IMiniQtHelper::CreateLabel); } + QWidget *CreateCheckbox(WidgetCallback changed) + { + return InvokeRetFunction(&IMiniQtHelper::CreateCheckbox, changed); + } + QWidget *CreateRadiobox(WidgetCallback changed) + { + return InvokeRetFunction(&IMiniQtHelper::CreateRadiobox, changed); + } + + void SetWidgetChecked(QWidget *checkableWidget, bool checked) + { + InvokeVoidFunction(&IMiniQtHelper::SetWidgetChecked, checkableWidget, checked); + } + bool IsWidgetChecked(QWidget *checkableWidget) + { + return InvokeRetFunction(&IMiniQtHelper::IsWidgetChecked, checkableWidget); + } + + QWidget *CreateSpinbox(int decimalPlaces, double step) + { + return InvokeRetFunction(&IMiniQtHelper::CreateSpinbox, decimalPlaces, step); + } + + void SetSpinboxBounds(QWidget *spinbox, double minVal, double maxVal) + { + InvokeVoidFunction(&IMiniQtHelper::SetSpinboxBounds, spinbox, minVal, maxVal); + } + void SetSpinboxValue(QWidget *spinbox, double value) + { + InvokeVoidFunction(&IMiniQtHelper::SetSpinboxValue, spinbox, value); + } + double GetSpinboxValue(QWidget *spinbox) + { + return InvokeRetFunction(&IMiniQtHelper::GetSpinboxValue, spinbox); + } + + QWidget *CreateTextBox(bool singleLine, WidgetCallback changed) + { + return InvokeRetFunction(&IMiniQtHelper::CreateTextBox, singleLine, changed); + } + + QWidget *CreateComboBox(bool editable, WidgetCallback changed) + { + return InvokeRetFunction(&IMiniQtHelper::CreateComboBox, editable, changed); + } + + void SetComboOptions(QWidget *combo, const rdcarray &options) + { + InvokeVoidFunction(&IMiniQtHelper::SetComboOptions, combo, options); + } +}; + +struct ExtensionInvoker : ObjectForwarder +{ + MiniQtInvoker *m_MiniQt; + ExtensionInvoker(PythonShell *shell, IExtensionManager &obj) : ObjectForwarder(shell, obj) + { + m_MiniQt = new MiniQtInvoker(shell, obj.GetMiniQtHelper()); + } + virtual ~ExtensionInvoker() { delete m_MiniQt; } + // + /////////////////////////////////////////////////////////////////////// + // pass-through functions that don't need the UI thread + /////////////////////////////////////////////////////////////////////// + // + rdcarray GetInstalledExtensions() { return m_Obj.GetInstalledExtensions(); } + bool IsExtensionLoaded(rdcstr name) { return m_Obj.IsExtensionLoaded(name); } + bool LoadExtension(rdcstr name) { return m_Obj.LoadExtension(name); } + IMiniQtHelper &GetMiniQtHelper() { return *m_MiniQt; } + // + /////////////////////////////////////////////////////////////////////// + // functions that invoke onto the UI thread + /////////////////////////////////////////////////////////////////////// + // + void RegisterWindowMenu(WindowMenu base, const rdcarray &submenus, + ExtensionCallback callback) + { + InvokeVoidFunction(&IExtensionManager::RegisterWindowMenu, base, submenus, callback); + } + + void RegisterPanelMenu(PanelMenu base, const rdcarray &submenus, ExtensionCallback callback) + { + InvokeVoidFunction(&IExtensionManager::RegisterPanelMenu, base, submenus, callback); + } + + void RegisterContextMenu(ContextMenu base, const rdcarray &submenus, + ExtensionCallback callback) + { + InvokeVoidFunction(&IExtensionManager::RegisterContextMenu, base, submenus, callback); + } + + void MessageDialog(const rdcstr &text, const rdcstr &title) + { + InvokeVoidFunction(&IExtensionManager::MessageDialog, text, title); + } + + void ErrorDialog(const rdcstr &text, const rdcstr &title) + { + InvokeVoidFunction(&IExtensionManager::ErrorDialog, text, title); + } + + DialogButton QuestionDialog(const rdcstr &text, const rdcarray &options, + const rdcstr &title) + { + return InvokeRetFunction(&IExtensionManager::QuestionDialog, text, options, title); + } + + rdcstr OpenFileName(const rdcstr &caption, const rdcstr &dir, const rdcstr &filter) + { + return InvokeRetFunction(&IExtensionManager::OpenFileName, caption, dir, filter); + } + + rdcstr OpenDirectoryName(const rdcstr &caption, const rdcstr &dir) + { + return InvokeRetFunction(&IExtensionManager::OpenDirectoryName, caption, dir); + } + + rdcstr SaveFileName(const rdcstr &caption, const rdcstr &dir, const rdcstr &filter) + { + return InvokeRetFunction(&IExtensionManager::SaveFileName, caption, dir, filter); + } + + void MenuDisplaying(ContextMenu contextMenu, QMenu *menu, const ExtensionCallbackData &data) + { + InvokeVoidFunction( + (void (IExtensionManager::*)(ContextMenu, QMenu *, const ExtensionCallbackData &)) & + IExtensionManager::MenuDisplaying, + contextMenu, menu, data); + } + void MenuDisplaying(PanelMenu panelMenu, QMenu *menu, QWidget *extensionButton, + const ExtensionCallbackData &data) + { + InvokeVoidFunction( + (void (IExtensionManager::*)(PanelMenu, QMenu *, QWidget *, const ExtensionCallbackData &)) & + IExtensionManager::MenuDisplaying, + panelMenu, menu, extensionButton, data); + } +}; + +struct CaptureContextInvoker : ObjectForwarder +{ + ExtensionInvoker *m_Ext; + CaptureContextInvoker(PythonShell *shell, ICaptureContext &obj) : ObjectForwarder(shell, obj) + { + m_Ext = new ExtensionInvoker(shell, obj.Extensions()); + } + virtual ~CaptureContextInvoker() { delete m_Ext; } + // + /////////////////////////////////////////////////////////////////////// + // pass-through functions that don't need the UI thread + /////////////////////////////////////////////////////////////////////// + // + virtual rdcstr TempCaptureFilename(const rdcstr &appname) override + { + return m_Obj.TempCaptureFilename(appname); + } + virtual IExtensionManager &Extensions() override { return *m_Ext; } + virtual IReplayManager &Replay() override { return m_Obj.Replay(); } + virtual bool IsCaptureLoaded() override { return m_Obj.IsCaptureLoaded(); } + virtual bool IsCaptureLocal() override { return m_Obj.IsCaptureLocal(); } + virtual bool IsCaptureTemporary() override { return m_Obj.IsCaptureTemporary(); } + virtual bool IsCaptureLoading() override { return m_Obj.IsCaptureLoading(); } + virtual rdcstr GetCaptureFilename() override { return m_Obj.GetCaptureFilename(); } + virtual CaptureModifications GetCaptureModifications() override + { + return m_Obj.GetCaptureModifications(); + } + virtual const FrameDescription &FrameInfo() override { return m_Obj.FrameInfo(); } + virtual const APIProperties &APIProps() override { return m_Obj.APIProps(); } + virtual rdcarray TargetShaderEncodings() override + { + return m_Obj.TargetShaderEncodings(); + } + virtual rdcarray CustomShaderEncodings() override + { + return m_Obj.CustomShaderEncodings(); + } + virtual uint32_t CurSelectedEvent() override { return m_Obj.CurSelectedEvent(); } + virtual uint32_t CurEvent() override { return m_Obj.CurEvent(); } + virtual const DrawcallDescription *CurSelectedDrawcall() override + { + return m_Obj.CurSelectedDrawcall(); + } + virtual const DrawcallDescription *CurDrawcall() override { return m_Obj.CurDrawcall(); } + virtual const DrawcallDescription *GetFirstDrawcall() override + { + return m_Obj.GetFirstDrawcall(); + } + virtual const DrawcallDescription *GetLastDrawcall() override { return m_Obj.GetLastDrawcall(); } + virtual const rdcarray &CurDrawcalls() override + { + return m_Obj.CurDrawcalls(); + } + virtual ResourceDescription *GetResource(ResourceId id) override { return m_Obj.GetResource(id); } + virtual const rdcarray &GetResources() override + { + return m_Obj.GetResources(); + } + virtual rdcstr GetResourceName(ResourceId id) override { return m_Obj.GetResourceName(id); } + virtual rdcstr GetResourceNameUnsuffixed(ResourceId id) override + { + return m_Obj.GetResourceNameUnsuffixed(id); + } + virtual bool IsAutogeneratedName(ResourceId id) override { return m_Obj.IsAutogeneratedName(id); } + virtual bool HasResourceCustomName(ResourceId id) override + { + return m_Obj.HasResourceCustomName(id); + } + virtual int ResourceNameCacheID() override { return m_Obj.ResourceNameCacheID(); } + virtual TextureDescription *GetTexture(ResourceId id) override { return m_Obj.GetTexture(id); } + virtual const rdcarray &GetTextures() override { return m_Obj.GetTextures(); } + virtual BufferDescription *GetBuffer(ResourceId id) override { return m_Obj.GetBuffer(id); } + virtual const rdcarray &GetBuffers() override { return m_Obj.GetBuffers(); } + virtual const DrawcallDescription *GetDrawcall(uint32_t eventId) override + { + return m_Obj.GetDrawcall(eventId); + } + virtual bool OpenRGPProfile(const rdcstr &filename) override + { + return m_Obj.OpenRGPProfile(filename); + } + virtual IRGPInterop *GetRGPInterop() override { return m_Obj.GetRGPInterop(); } + virtual const SDFile &GetStructuredFile() override { return m_Obj.GetStructuredFile(); } + virtual WindowingSystem CurWindowingSystem() override { return m_Obj.CurWindowingSystem(); } + virtual const rdcarray &DebugMessages() override { return m_Obj.DebugMessages(); } + virtual int UnreadMessageCount() override { return m_Obj.UnreadMessageCount(); } + virtual void MarkMessagesRead() override { return m_Obj.MarkMessagesRead(); } + virtual rdcstr GetNotes(const rdcstr &key) override { return m_Obj.GetNotes(key); } + virtual rdcarray GetBookmarks() override { return m_Obj.GetBookmarks(); } + virtual const D3D11Pipe::State *CurD3D11PipelineState() override + { + return m_Obj.CurD3D11PipelineState(); + } + virtual const D3D12Pipe::State *CurD3D12PipelineState() override + { + return m_Obj.CurD3D12PipelineState(); + } + virtual const GLPipe::State *CurGLPipelineState() override { return m_Obj.CurGLPipelineState(); } + virtual const VKPipe::State *CurVulkanPipelineState() override + { + return m_Obj.CurVulkanPipelineState(); + } + virtual const PipeState &CurPipelineState() override { return m_Obj.CurPipelineState(); } + virtual PersistantConfig &Config() override { return m_Obj.Config(); } + // + /////////////////////////////////////////////////////////////////////// + // functions that invoke onto the UI thread + /////////////////////////////////////////////////////////////////////// + // virtual void ConnectToRemoteServer(RemoteHost host) override { InvokeVoidFunction(&ICaptureContext::ConnectToRemoteServer, host); @@ -596,6 +879,11 @@ PythonShell::~PythonShell() delete ui; } +PythonContext *PythonShell::GetScriptContext() +{ + return scriptContext; +} + void PythonShell::on_execute_clicked() { QString command = ui->lineInput->text(); diff --git a/qrenderdoc/Windows/PythonShell.h b/qrenderdoc/Windows/PythonShell.h index dee7ac11c..0cbf8e41c 100644 --- a/qrenderdoc/Windows/PythonShell.h +++ b/qrenderdoc/Windows/PythonShell.h @@ -47,6 +47,9 @@ public: ~PythonShell(); + // for UI-forwarding helper classes + PythonContext *GetScriptContext(); + // IPythonShell QWidget *Widget() override { return this; } private slots: diff --git a/qrenderdoc/qrenderdoc.pro b/qrenderdoc/qrenderdoc.pro index b29df3053..b9d108fab 100644 --- a/qrenderdoc/qrenderdoc.pro +++ b/qrenderdoc/qrenderdoc.pro @@ -169,6 +169,7 @@ SOURCES += Code/qrenderdoc.cpp \ Code/CaptureContext.cpp \ Code/ScintillaSyntax.cpp \ Code/QRDUtils.cpp \ + Code/MiniQtHelper.cpp \ Code/BufferFormatter.cpp \ Code/Resources.cpp \ Code/RGPInterop.cpp \ @@ -248,6 +249,7 @@ HEADERS += Code/CaptureContext.h \ Code/ReplayManager.h \ Code/ScintillaSyntax.h \ Code/QRDUtils.h \ + Code/MiniQtHelper.h \ Code/Resources.h \ Code/RGPInterop.h \ Code/pyrenderdoc/PythonContext.h \ diff --git a/qrenderdoc/qrenderdoc_local.vcxproj b/qrenderdoc/qrenderdoc_local.vcxproj index e75f13fd5..560cf1048 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj +++ b/qrenderdoc/qrenderdoc_local.vcxproj @@ -603,6 +603,7 @@ + @@ -987,6 +988,7 @@ + %(Fullpath);$(QtBinDir)\moc.exe;%(AdditionalInputs) "$(QtBinDir)\moc.exe" -DUNICODE -DWIN32 -DWIN64 -D_WIN32 -D_WIN64 -DRENDERDOC_PLATFORM_WIN32 -DSCINTILLA_QT=1 -DSCI_LEXER=1 -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -D_MSC_VER=1900 -I"$(ProjectDir)." -I"$(SolutionDir)\renderdoc\api\replay" -I"$(QtIncludeDir)" -I"$(QtIncludeDir)\QtWidgets" -I"$(QtIncludeDir)\QtGui" -I"$(QtIncludeDir)\QtCore" "%(Fullpath)" -o "$(IntDir)generated\moc_%(Filename).cpp" diff --git a/qrenderdoc/qrenderdoc_local.vcxproj.filters b/qrenderdoc/qrenderdoc_local.vcxproj.filters index 8bc5f211d..1a9856098 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj.filters +++ b/qrenderdoc/qrenderdoc_local.vcxproj.filters @@ -741,6 +741,9 @@ Generated Files + + Code + @@ -1103,6 +1106,9 @@ Generated Files + + Code +