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 +