From cb35c950cf1108bece6229f21fdb30cb46f4b74a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Fri, 6 May 2022 22:06:55 +0200 Subject: [PATCH] Update documentation. Minor changes for clarity. --- README.md | 19 +- src/mobase/README.md | 110 ++++++++++ src/mobase/pybind11_all.h | 6 +- src/mobase/wrappers/wrappers.cpp | 2 +- src/proxy/proxypython.cpp | 2 +- src/proxy/proxypython.h | 2 +- src/pybind11-qt/README.md | 64 ++++++ .../pybind11_qt/details/pybind11_qt_sip.h | 11 +- .../include/pybind11_qt/pybind11_qt.h | 4 +- .../include/pybind11_qt/pybind11_qt_enums.h | 196 +++++++++--------- .../include/pybind11_qt/pybind11_qt_holder.h | 18 +- .../include/pybind11_qt/pybind11_qt_objects.h | 66 +++--- src/pybind11-utils/CMakeLists.txt | 13 +- src/pybind11-utils/README.md | 50 +++++ .../include/pybind11_utils/arg_wrapper.h | 82 +++++--- tests/python/test_qt_widgets.cpp | 2 +- 16 files changed, 450 insertions(+), 197 deletions(-) create mode 100644 src/mobase/README.md create mode 100644 src/pybind11-qt/README.md create mode 100644 src/pybind11-utils/README.md diff --git a/README.md b/README.md index 6536263..059fa5b 100644 --- a/README.md +++ b/README.md @@ -13,21 +13,22 @@ This repository is part of MO2 main repositories and should usually be build usi This repositories contains 5 sub-projects in `src`. The interface between Python and C++ is done using the [`pybind11`](https://github.com/pybind/pybind11) library. +See the `README` in the subfolder (when there is one) for more details. -- [`src/proxy`](src/proxy/) contains the actual proxy plugin. This project is a simple - interface between MO2 and the runner (see below). - - This project generates the `plugin_python.dll` library. - - TODO: this project generates the translation file (-> moved the file). - - TODO: this project install necessary files for the plugin (Python DLL, Python - libraries, etc), INCLUDING `mobase`. +- [`src/proxy`](src/proxy/) contains the actual proxy plugin. + This project is a simple interface between MO2 and the runner (see below). + The CMake code: + - generates the `plugin_python.dll` library, + - generates the translation file (under `src/`), + - installs necessary files for the plugin (Python DLL, Python libraries, etc), + including `mobase`. - [`src/runner`](src/runner/) contains the Python runner. This is the project that instantiates a Python interpreter and load/unload Python plugins. - - TODO: static library -- [`src/pybind11-qt](src/pybind11-qt/) contains many utility stuff to interface +- [`src/pybind11-qt`](src/pybind11-qt/) contains many utility stuff to interface pybind11 with Qt and PyQt. - [`src/pybind11-utils`](src/pybind11-utils/) contains some utility stuff pybind11. This project is header-only. -- [`src/mobase](src/mobase) contains the Python plugin interface. +- [`src/mobase`](src/mobase) contains the Python plugin interface. - This projects generates the `mobase` Python library. Some (woefully incomplete) tests are available under `tests`, split in three diff --git a/src/mobase/README.md b/src/mobase/README.md new file mode 100644 index 0000000..b4d184e --- /dev/null +++ b/src/mobase/README.md @@ -0,0 +1,110 @@ +# mobase + +`mobase` is a ModOrganizer2 Python API. +It provides access to the part of +[`uibase`](https://github.com/ModOrganizer2/modorganizer-uibase) C++ API from +Python through [`pybind11`](https://github.com/pybind/pybind11). + +## Organization + +**Important:** All (most) files should include `pybind11_all.h` (either directly +or through another header) to get proper `type_caster` available. + +- `mobase.cpp` contains the `PYBIND11_MODULE` definition of `mobase` but is otherwise + the entrypoint for other functions. +- `wrappers.h` contains the declaration of most functions implemented under + `wrappers/`. +- The other files under `wrappers/` contains bindings and trampoline classes (see + below) for `uibase` classes. + - `basic_classes.cpp` contains the bindings for most classes that cannot be extended + in Python (`IOrganizer`, `IModInterface`, etc.) + - `game_features.cpp` contains the bindings and trampoline classes for game features. + - `pyfiletree.h` and `pyfiletree.cpp` contains bindings for the `IFileTree`-related + classes. + - `pyplugins.h` contains the trampoline classes for the `IPluginXXX` classes and + `pyplugins.cpp` the bindings. + - `pyplugins.h` is required since the trampoline classes are tagged with `Q_OBJECT`, + and MOC does not work if the classes are declared in a C++ file. + - `pyplugins.cpp` also contains the `extract_plugins` function in `mobase.private` + that is used to extract plugins from Python object in the runner. + - `widgets.cpp` contains the bindings for the widget classes. + - `wrappers.cpp` contains the trampoline and bindings for non-plugin classes that can + be extended through Python. + +## Updating mobase + +### Classes that cannot be extended through Python + +Updating or adding classes that cannot be extended through Python is quite easily. +One simply needs to declare the appropriate `py::class_` or add new `.def()`. + +See below for things to remember when creating pybind11 bindings. + +### Free functions + +Similar to classes that cannot be extended through Python, see above. + +### Classes that can be extended through Python: Plugins + +To extend plugins, simply update the trampoline classes in `pyplugins.h` and the +bindings in `pyplugins.cpp`. + +**Note:** For new plugins, simply look at the existing one. + +### Classes that can be extended through Python: Game Features + +To extend or expose game features: + +- Create (if there is not already one) a trampoline class for the feature in + `game_features.cpp`. + - Add implementation of missing functions if required. +- Add the bindings in `add_game_feature_bindings` in `game_features.cpp`. +- For new feature, add the feature type to `GameFeaturesHelper::GameFeatures` in + `game_features.cpp`. + +### Classes that can be extended through Python: Others + +Non-plugin classes should be added to the `wrappers.cpp` file and should be exposed +with `std::shared_ptr<>` or `qobject_holder<>` holders. + +- If the classes extends `QObject`, use a `qobject_holder`. +- Otherwise use a `std::shared_ptr<>` holder and add a `MO2_PYBIND11_SHARED_CPP_HOLDER` + declaration in `pybind11_all.h`. + +Trampoline can be defined directly in `wrappers.cpp`, and bindings in the appropriate +function. +See the existing classes for example. + +**Important:** +You need to make sure that `uibase` manipulates such classes through +`std::shared_ptr<>` (unless those inherit `QObject`). +Using `std::unique_ptr<>` is not possible since `std::unique_ptr<>` cannot have custom +runtime-specified deleters. + +## Things to remember + +Here are a few things to remember when creating bindings: + +- If a function has multiple overloads that can conflict in Python, the more complex + one must be defined first as pybind11 will try calling them in order. +- If a C++ function expect a `QString`, `QFileInfo` or `QDir` that represents a file or + a directory, wrapping the function with `wrap_for_filepath` or `wrap_for_directory` is + a good idea. This allows Python to call the function with `pathlib.Path`. +- Most of the C++ function taking a reference to modify in C++ cannot be directly + exposed in Python since Python cannot modify reference to simple type (e.g. + `QString&` or `int&`). + The best way to expose such function is to bind a lambda that returns a variant from + Python, e.g. + +```cpp +// assume the C++ function is QString fn(QString& foo, QString const& bar, int& maz); + +m.def("function", [](QString& foo, QString const& bar, int& maz) { + // call the function + auto ret = function(foo, bar, maz); + + // make a tuple containing the return value (if there is one), and the modified + // values passed by reference + return std::make_tuple(ret, foo, maz); +}); +``` diff --git a/src/mobase/pybind11_all.h b/src/mobase/pybind11_all.h index 5813616..7dbba2b 100644 --- a/src/mobase/pybind11_all.h +++ b/src/mobase/pybind11_all.h @@ -80,12 +80,12 @@ namespace mo2::python { } // namespace mo2::python -MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::IPluginRequirement) -MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::ISaveGame) - MO2_PYBIND11_WRAP_ARGUMENT_CASTER(mo2::python::FileWrapper, "FileWrapper", QFileInfo, std::filesystem::path, QString); MO2_PYBIND11_WRAP_ARGUMENT_CASTER(mo2::python::DirectoryWrapper, "DirectoryWrapper", QDir, std::filesystem::path, QString); +MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::IPluginRequirement) +MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::ISaveGame) + #endif diff --git a/src/mobase/wrappers/wrappers.cpp b/src/mobase/wrappers/wrappers.cpp index a6f480d..d2cdf45 100644 --- a/src/mobase/wrappers/wrappers.cpp +++ b/src/mobase/wrappers/wrappers.cpp @@ -90,7 +90,7 @@ namespace mo2::python { // the widget py::class_> + py::qt::qobject_holder> iSaveGameInfoWidget(m, "ISaveGameInfoWidget"); iSaveGameInfoWidget.def(py::init(), "parent"_a = (QWidget*)nullptr) .def("setSave", &ISaveGameInfoWidget::setSave, "save"_a); diff --git a/src/proxy/proxypython.cpp b/src/proxy/proxypython.cpp index f050735..3fd5eac 100644 --- a/src/proxy/proxypython.cpp +++ b/src/proxy/proxypython.cpp @@ -1,5 +1,5 @@ /* -Copyright (C) 2013 Sebastian Herbord. All rights reserved. +Copyright (C) 2022 Sebastian Herbord & MO2 Team. All rights reserved. This file is part of python proxy plugin for MO diff --git a/src/proxy/proxypython.h b/src/proxy/proxypython.h index 34ac7f2..60cd5a3 100644 --- a/src/proxy/proxypython.h +++ b/src/proxy/proxypython.h @@ -1,5 +1,5 @@ /* -Copyright (C) 2013 Sebastian Herbord. All rights reserved. +Copyright (C) 2022 Sebastian Herbord & MO2 Team. All rights reserved. This file is part of python proxy plugin for MO diff --git a/src/pybind11-qt/README.md b/src/pybind11-qt/README.md new file mode 100644 index 0000000..784f9a8 --- /dev/null +++ b/src/pybind11-qt/README.md @@ -0,0 +1,64 @@ +# pybind11-qt + +This library contains code to interface pybind11 with Qt and PyQt. + +## Type casters + +The main part of this library is a set of (templated) type casters for Qt types that +can be used by simply importing `pybind11_qt/pybind11_qt.h`. +This provides type casters for: + +- Standard Qt types, such as `QString` or `QVariant`. + - `QString` is equivalent to Python `str` (unicode or not). + - `QVariant` is not exposed but the object is directly converted, similarly to + `std::variant` default type caster. +- Qt containers (`QList`, `QSet`, `QMap`, `QStringList`). + - The `QList` type-caster is more flexible than the standard container type-casters + from pybind11 as it accepts any iterable. +- `QFlags` - Delegates the cast to the underlying type, basically. +- Qt enumerations: a lot of enumerations are provided in + [`pybind11_qt_enums.h`](include/pybind11_qt/pybind11_qt_enums.h) and new ones can be + easily added using the `PYQT_ENUM` macro (inside the header file). +- Qt objects: very few are provided in + [`pybind11_qt_objects.h`](include/pybind11_qt/pybind11_qt_objects.h) and new ones can + be added using the `PYQT_OBJECT` macro (inside the header file). + - Copy-constructible Qt objects are copied when passing from C++ to Python or + vice-versa. + - Non copy-constructible Qt objects, e.g., `QObject` or `QWidget` should always be + exposed as pointer, and their ownership is transferred to C++ when coming from + Python. + +## Qt holder + +The library also provides a `pybind11::qt::qobject_holder` holder for pybind11 that +transfer ownerships of the Python object to the underlying `QObject`. + +This holder is useful when exposing classes inheriting `QObject` (or a child class of +`QObject`) that can be extended to Python. + +The library also provides two `set_qt_owner` functions that can be used to transfer +ownership manually. + +## Qt delegates + +The library provides a `add_qt_delegate` function that can be used to delegate Python +call to Qt functions to C++: + +```cpp +py::class_< + // the C++ class extending QObject to expose + ISaveGameInfoWidget, + + // the trampoline class + PySaveGameInfoWidget, + + // the Qt holder to keep the Python object alive alongside the C++ one + py::qt::qobject_holder + +> iSaveGameInfoWidget(m, "ISaveGameInfoWidget"); + +// allow to access most of the class attributes through Python via an overload of +// __getattr__ and add a _widget() method to access the widget itself if needed +// +py::qt::add_qt_delegate(iSaveGameInfoWidget, "_widget"); +``` diff --git a/src/pybind11-qt/include/pybind11_qt/details/pybind11_qt_sip.h b/src/pybind11-qt/include/pybind11_qt/details/pybind11_qt_sip.h index 4a7debb..a2712ca 100644 --- a/src/pybind11-qt/include/pybind11_qt/details/pybind11_qt_sip.h +++ b/src/pybind11-qt/include/pybind11_qt/details/pybind11_qt_sip.h @@ -112,7 +112,7 @@ namespace pybind11::detail::qt { sipAPI()->api_transfer_to(src.ptr(), Py_None); // tie the py::object to the C++ one - new pybind11::detail::qt::qobject_holder(value); + new pybind11::detail::qt::qobject_holder_impl(value); } else { value = *reinterpret_cast(data); @@ -183,10 +183,15 @@ namespace pybind11::detail::qt { return Py_None; } - if (policy == return_value_policy::take_ownership) { - // ensure Python deletes the C++ component + // ensure Python deletes the C++ component + if constexpr (!is_pointer) { qt::sipAPI()->api_transfer_back(sipObj); } + else { + if (policy == return_value_policy::take_ownership) { + qt::sipAPI()->api_transfer_back(sipObj); + } + } return sipObj; } diff --git a/src/pybind11-qt/include/pybind11_qt/pybind11_qt.h b/src/pybind11-qt/include/pybind11_qt/pybind11_qt.h index 9091d3a..3edba59 100644 --- a/src/pybind11-qt/include/pybind11_qt/pybind11_qt.h +++ b/src/pybind11-qt/include/pybind11_qt/pybind11_qt.h @@ -34,7 +34,7 @@ namespace pybind11::qt { */ inline void set_qt_owner(QObject* owner, object child) { - new detail::qt::qobject_holder{owner, child}; + new detail::qt::qobject_holder_impl{owner, child}; } /** @@ -49,7 +49,7 @@ namespace pybind11::qt { void set_qt_owner(Class* object) { static_assert(std::is_base_of_v); - new detail::qt::qobject_holder{object}; + new detail::qt::qobject_holder_impl{object}; } /** diff --git a/src/pybind11-qt/include/pybind11_qt/pybind11_qt_enums.h b/src/pybind11-qt/include/pybind11_qt/pybind11_qt_enums.h index e6cdfe0..ae98cd5 100644 --- a/src/pybind11-qt/include/pybind11_qt/pybind11_qt_enums.h +++ b/src/pybind11-qt/include/pybind11_qt/pybind11_qt_enums.h @@ -8,110 +8,108 @@ #include "details/pybind11_qt_utils.h" #define PYQT_ENUM(QPackage, QEnum) \ - namespace qt { \ + namespace pybind11::detail { \ + namespace qt { \ + template <> \ + struct EnumData { \ + constexpr static const auto package = \ + const_name("PyQt6.") + const_name(#QPackage); \ + constexpr static const auto name = qt_name_cpp2py(#QEnum); \ + }; \ + } \ template <> \ - struct EnumData { \ - constexpr static const auto package = \ - const_name("PyQt6.") + const_name(#QPackage); \ - constexpr static const auto name = qt_name_cpp2py(#QEnum); \ + struct type_caster : qt::qt_enum_caster { \ }; \ - } \ - template <> \ - struct type_caster : qt::qt_enum_caster { \ } -namespace pybind11::detail { +PYQT_ENUM(QtCore, Qt::AlignmentFlag); +PYQT_ENUM(QtCore, Qt::AnchorPoint); +PYQT_ENUM(QtCore, Qt::ApplicationAttribute); +PYQT_ENUM(QtCore, Qt::ApplicationState); +PYQT_ENUM(QtCore, Qt::ArrowType); +PYQT_ENUM(QtCore, Qt::AspectRatioMode); +PYQT_ENUM(QtCore, Qt::Axis); +PYQT_ENUM(QtCore, Qt::BGMode); +PYQT_ENUM(QtCore, Qt::BrushStyle); +PYQT_ENUM(QtCore, Qt::CaseSensitivity); +PYQT_ENUM(QtCore, Qt::CheckState); +PYQT_ENUM(QtCore, Qt::ChecksumType); +PYQT_ENUM(QtCore, Qt::ClipOperation); +PYQT_ENUM(QtCore, Qt::ConnectionType); +PYQT_ENUM(QtCore, Qt::ContextMenuPolicy); +PYQT_ENUM(QtCore, Qt::CoordinateSystem); +PYQT_ENUM(QtCore, Qt::Corner); +PYQT_ENUM(QtCore, Qt::CursorMoveStyle); +PYQT_ENUM(QtCore, Qt::CursorShape); +PYQT_ENUM(QtCore, Qt::DateFormat); +PYQT_ENUM(QtCore, Qt::DayOfWeek); +PYQT_ENUM(QtCore, Qt::DockWidgetArea); +PYQT_ENUM(QtCore, Qt::DropAction); +PYQT_ENUM(QtCore, Qt::Edge); +PYQT_ENUM(QtCore, Qt::EnterKeyType); +PYQT_ENUM(QtCore, Qt::EventPriority); +PYQT_ENUM(QtCore, Qt::FillRule); +PYQT_ENUM(QtCore, Qt::FindChildOption); +PYQT_ENUM(QtCore, Qt::FocusPolicy); +PYQT_ENUM(QtCore, Qt::FocusReason); +PYQT_ENUM(QtCore, Qt::GestureFlag); +PYQT_ENUM(QtCore, Qt::GestureState); +PYQT_ENUM(QtCore, Qt::GestureType); +PYQT_ENUM(QtCore, Qt::GlobalColor); +PYQT_ENUM(QtCore, Qt::HitTestAccuracy); +PYQT_ENUM(QtCore, Qt::ImageConversionFlag); +PYQT_ENUM(QtCore, Qt::InputMethodHint); +PYQT_ENUM(QtCore, Qt::InputMethodQuery); +PYQT_ENUM(QtCore, Qt::ItemDataRole); +PYQT_ENUM(QtCore, Qt::ItemFlag); +PYQT_ENUM(QtCore, Qt::ItemSelectionMode); +PYQT_ENUM(QtCore, Qt::ItemSelectionOperation); +PYQT_ENUM(QtCore, Qt::Key); +PYQT_ENUM(QtCore, Qt::KeyboardModifier); +PYQT_ENUM(QtCore, Qt::LayoutDirection); +PYQT_ENUM(QtCore, Qt::MaskMode); +PYQT_ENUM(QtCore, Qt::MatchFlag); +PYQT_ENUM(QtCore, Qt::Modifier); +PYQT_ENUM(QtCore, Qt::MouseButton); +PYQT_ENUM(QtCore, Qt::MouseEventFlag); +PYQT_ENUM(QtCore, Qt::MouseEventSource); +PYQT_ENUM(QtCore, Qt::NativeGestureType); +PYQT_ENUM(QtCore, Qt::NavigationMode); +PYQT_ENUM(QtCore, Qt::Orientation); +PYQT_ENUM(QtCore, Qt::PenCapStyle); +PYQT_ENUM(QtCore, Qt::PenJoinStyle); +PYQT_ENUM(QtCore, Qt::PenStyle); +PYQT_ENUM(QtCore, Qt::ScreenOrientation); +PYQT_ENUM(QtCore, Qt::ScrollBarPolicy); +PYQT_ENUM(QtCore, Qt::ScrollPhase); +PYQT_ENUM(QtCore, Qt::ShortcutContext); +PYQT_ENUM(QtCore, Qt::SizeHint); +PYQT_ENUM(QtCore, Qt::SizeMode); +PYQT_ENUM(QtCore, Qt::SortOrder); +PYQT_ENUM(QtCore, Qt::TabFocusBehavior); +PYQT_ENUM(QtCore, Qt::TextElideMode); +PYQT_ENUM(QtCore, Qt::TextFlag); +PYQT_ENUM(QtCore, Qt::TextFormat); +PYQT_ENUM(QtCore, Qt::TextInteractionFlag); +PYQT_ENUM(QtCore, Qt::TileRule); +PYQT_ENUM(QtCore, Qt::TimeSpec); +PYQT_ENUM(QtCore, Qt::TimerType); +PYQT_ENUM(QtCore, Qt::ToolBarArea); +PYQT_ENUM(QtCore, Qt::ToolButtonStyle); +PYQT_ENUM(QtCore, Qt::TransformationMode); +PYQT_ENUM(QtCore, Qt::WhiteSpaceMode); +PYQT_ENUM(QtCore, Qt::WidgetAttribute); +PYQT_ENUM(QtCore, Qt::WindowFrameSection); +PYQT_ENUM(QtCore, Qt::WindowModality); +PYQT_ENUM(QtCore, Qt::WindowState); +PYQT_ENUM(QtCore, Qt::WindowType); - PYQT_ENUM(QtCore, Qt::AlignmentFlag); - PYQT_ENUM(QtCore, Qt::AnchorPoint); - PYQT_ENUM(QtCore, Qt::ApplicationAttribute); - PYQT_ENUM(QtCore, Qt::ApplicationState); - PYQT_ENUM(QtCore, Qt::ArrowType); - PYQT_ENUM(QtCore, Qt::AspectRatioMode); - PYQT_ENUM(QtCore, Qt::Axis); - PYQT_ENUM(QtCore, Qt::BGMode); - PYQT_ENUM(QtCore, Qt::BrushStyle); - PYQT_ENUM(QtCore, Qt::CaseSensitivity); - PYQT_ENUM(QtCore, Qt::CheckState); - PYQT_ENUM(QtCore, Qt::ChecksumType); - PYQT_ENUM(QtCore, Qt::ClipOperation); - PYQT_ENUM(QtCore, Qt::ConnectionType); - PYQT_ENUM(QtCore, Qt::ContextMenuPolicy); - PYQT_ENUM(QtCore, Qt::CoordinateSystem); - PYQT_ENUM(QtCore, Qt::Corner); - PYQT_ENUM(QtCore, Qt::CursorMoveStyle); - PYQT_ENUM(QtCore, Qt::CursorShape); - PYQT_ENUM(QtCore, Qt::DateFormat); - PYQT_ENUM(QtCore, Qt::DayOfWeek); - PYQT_ENUM(QtCore, Qt::DockWidgetArea); - PYQT_ENUM(QtCore, Qt::DropAction); - PYQT_ENUM(QtCore, Qt::Edge); - PYQT_ENUM(QtCore, Qt::EnterKeyType); - PYQT_ENUM(QtCore, Qt::EventPriority); - PYQT_ENUM(QtCore, Qt::FillRule); - PYQT_ENUM(QtCore, Qt::FindChildOption); - PYQT_ENUM(QtCore, Qt::FocusPolicy); - PYQT_ENUM(QtCore, Qt::FocusReason); - PYQT_ENUM(QtCore, Qt::GestureFlag); - PYQT_ENUM(QtCore, Qt::GestureState); - PYQT_ENUM(QtCore, Qt::GestureType); - PYQT_ENUM(QtCore, Qt::GlobalColor); - PYQT_ENUM(QtCore, Qt::HitTestAccuracy); - PYQT_ENUM(QtCore, Qt::ImageConversionFlag); - PYQT_ENUM(QtCore, Qt::InputMethodHint); - PYQT_ENUM(QtCore, Qt::InputMethodQuery); - PYQT_ENUM(QtCore, Qt::ItemDataRole); - PYQT_ENUM(QtCore, Qt::ItemFlag); - PYQT_ENUM(QtCore, Qt::ItemSelectionMode); - PYQT_ENUM(QtCore, Qt::ItemSelectionOperation); - PYQT_ENUM(QtCore, Qt::Key); - PYQT_ENUM(QtCore, Qt::KeyboardModifier); - PYQT_ENUM(QtCore, Qt::LayoutDirection); - PYQT_ENUM(QtCore, Qt::MaskMode); - PYQT_ENUM(QtCore, Qt::MatchFlag); - PYQT_ENUM(QtCore, Qt::Modifier); - PYQT_ENUM(QtCore, Qt::MouseButton); - PYQT_ENUM(QtCore, Qt::MouseEventFlag); - PYQT_ENUM(QtCore, Qt::MouseEventSource); - PYQT_ENUM(QtCore, Qt::NativeGestureType); - PYQT_ENUM(QtCore, Qt::NavigationMode); - PYQT_ENUM(QtCore, Qt::Orientation); - PYQT_ENUM(QtCore, Qt::PenCapStyle); - PYQT_ENUM(QtCore, Qt::PenJoinStyle); - PYQT_ENUM(QtCore, Qt::PenStyle); - PYQT_ENUM(QtCore, Qt::ScreenOrientation); - PYQT_ENUM(QtCore, Qt::ScrollBarPolicy); - PYQT_ENUM(QtCore, Qt::ScrollPhase); - PYQT_ENUM(QtCore, Qt::ShortcutContext); - PYQT_ENUM(QtCore, Qt::SizeHint); - PYQT_ENUM(QtCore, Qt::SizeMode); - PYQT_ENUM(QtCore, Qt::SortOrder); - PYQT_ENUM(QtCore, Qt::TabFocusBehavior); - PYQT_ENUM(QtCore, Qt::TextElideMode); - PYQT_ENUM(QtCore, Qt::TextFlag); - PYQT_ENUM(QtCore, Qt::TextFormat); - PYQT_ENUM(QtCore, Qt::TextInteractionFlag); - PYQT_ENUM(QtCore, Qt::TileRule); - PYQT_ENUM(QtCore, Qt::TimeSpec); - PYQT_ENUM(QtCore, Qt::TimerType); - PYQT_ENUM(QtCore, Qt::ToolBarArea); - PYQT_ENUM(QtCore, Qt::ToolButtonStyle); - PYQT_ENUM(QtCore, Qt::TransformationMode); - PYQT_ENUM(QtCore, Qt::WhiteSpaceMode); - PYQT_ENUM(QtCore, Qt::WidgetAttribute); - PYQT_ENUM(QtCore, Qt::WindowFrameSection); - PYQT_ENUM(QtCore, Qt::WindowModality); - PYQT_ENUM(QtCore, Qt::WindowState); - PYQT_ENUM(QtCore, Qt::WindowType); - - PYQT_ENUM(QtWidgets, QMessageBox::ButtonRole); - PYQT_ENUM(QtWidgets, QMessageBox::DialogCode); - PYQT_ENUM(QtWidgets, QMessageBox::Icon); - PYQT_ENUM(QtWidgets, QMessageBox::PaintDeviceMetric); - PYQT_ENUM(QtWidgets, QMessageBox::RenderFlag); - PYQT_ENUM(QtWidgets, QMessageBox::StandardButton); - -} // namespace pybind11::detail +PYQT_ENUM(QtWidgets, QMessageBox::ButtonRole); +PYQT_ENUM(QtWidgets, QMessageBox::DialogCode); +PYQT_ENUM(QtWidgets, QMessageBox::Icon); +PYQT_ENUM(QtWidgets, QMessageBox::PaintDeviceMetric); +PYQT_ENUM(QtWidgets, QMessageBox::RenderFlag); +PYQT_ENUM(QtWidgets, QMessageBox::StandardButton); #undef PYQT_ENUM diff --git a/src/pybind11-qt/include/pybind11_qt/pybind11_qt_holder.h b/src/pybind11-qt/include/pybind11_qt/pybind11_qt_holder.h index 29d6d8f..08fdd78 100644 --- a/src/pybind11-qt/include/pybind11_qt/pybind11_qt_holder.h +++ b/src/pybind11-qt/include/pybind11_qt/pybind11_qt_holder.h @@ -7,7 +7,7 @@ namespace pybind11::detail::qt { - class qobject_holder : public QObject { + class qobject_holder_impl : public QObject { object p_; public: @@ -18,14 +18,15 @@ namespace pybind11::detail::qt { * @param p Parent of this holder. * @param o Python object to keep alive. */ - qobject_holder(QObject* p, object o) : p_{o} { setParent(p); } + qobject_holder_impl(QObject* p, object o) : p_{o} { setParent(p); } template - qobject_holder(U* p) : qobject_holder{p, reinterpret_borrow(cast(p))} + qobject_holder_impl(U* p) + : qobject_holder_impl{p, reinterpret_borrow(cast(p))} { } - ~qobject_holder() + ~qobject_holder_impl() { gil_scoped_acquire s; p_ = std::move(none()); @@ -37,19 +38,22 @@ namespace pybind11::detail::qt { namespace pybind11::qt { template - class qholder { + class qobject_holder { using type = Type; type* qobj_; public: - qholder(type* qobj) : qobj_{qobj} { new detail::qt::qobject_holder(qobj_); } + qobject_holder(type* qobj) : qobj_{qobj} + { + new detail::qt::qobject_holder_impl(qobj_); + } type* get() { return qobj_; } }; } // namespace pybind11::qt -PYBIND11_DECLARE_HOLDER_TYPE(T, ::pybind11::qt::qholder) +PYBIND11_DECLARE_HOLDER_TYPE(T, ::pybind11::qt::qobject_holder) #endif diff --git a/src/pybind11-qt/include/pybind11_qt/pybind11_qt_objects.h b/src/pybind11-qt/include/pybind11_qt/pybind11_qt_objects.h index 85d7408..1a9517d 100644 --- a/src/pybind11-qt/include/pybind11_qt/pybind11_qt_objects.h +++ b/src/pybind11-qt/include/pybind11_qt/pybind11_qt_objects.h @@ -17,47 +17,45 @@ #include "details/pybind11_qt_utils.h" #define PYQT_CLASS(QModule, QClass) \ - namespace qt { \ + namespace pybind11::detail { \ + namespace qt { \ + template <> \ + struct MetaData { \ + constexpr static const auto class_name = #QClass; \ + constexpr static const auto python_name = \ + const_name("PyQt6.") + const_name(#QModule) + const_name(".") + \ + const_name(#QClass); \ + }; \ + } \ template <> \ - struct MetaData { \ - constexpr static const auto class_name = #QClass; \ - constexpr static const auto python_name = \ - const_name("PyQt6.") + const_name(#QModule) + const_name(".") + \ - const_name(#QClass); \ + struct type_caster \ + : std::conditional_t, \ + type_caster_generic, qt::qt_type_caster> { \ + }; \ + template <> \ + struct type_caster \ + : std::conditional_t, \ + qt::qt_type_caster, type_caster> { \ }; \ - } \ - template <> \ - struct type_caster \ - : std::conditional_t, \ - type_caster_generic, qt::qt_type_caster> { \ - }; \ - template <> \ - struct type_caster \ - : std::conditional_t, \ - qt::qt_type_caster, type_caster> { \ } -namespace pybind11::detail { +// add declarations below to create bindings - the first argument is simply +// the name of the PyQt6 package containing the class, and is only used for +// the python signature - // add declarations below to create bindings - the first argument is simply - // the name of the PyQt6 package containing the class, and is only used for - // the python signature +PYQT_CLASS(QtCore, QDateTime); +PYQT_CLASS(QtCore, QDir); +PYQT_CLASS(QtCore, QFileInfo); +PYQT_CLASS(QtCore, QObject); +PYQT_CLASS(QtCore, QSize); +PYQT_CLASS(QtCore, QUrl); - PYQT_CLASS(QtCore, QDateTime); - PYQT_CLASS(QtCore, QDir); - PYQT_CLASS(QtCore, QFileInfo); - PYQT_CLASS(QtCore, QObject); - PYQT_CLASS(QtCore, QSize); - PYQT_CLASS(QtCore, QUrl); +PYQT_CLASS(QtGui, QColor); +PYQT_CLASS(QtGui, QIcon); +PYQT_CLASS(QtGui, QPixmap); - PYQT_CLASS(QtGui, QColor); - PYQT_CLASS(QtGui, QIcon); - PYQT_CLASS(QtGui, QPixmap); - - PYQT_CLASS(QtWidgets, QMainWindow); - PYQT_CLASS(QtWidgets, QWidget); - -} // namespace pybind11::detail +PYQT_CLASS(QtWidgets, QMainWindow); +PYQT_CLASS(QtWidgets, QWidget); #undef METADATA diff --git a/src/pybind11-utils/CMakeLists.txt b/src/pybind11-utils/CMakeLists.txt index 3d05b2e..4b2c170 100644 --- a/src/pybind11-utils/CMakeLists.txt +++ b/src/pybind11-utils/CMakeLists.txt @@ -1,7 +1,14 @@ cmake_minimum_required(VERSION 3.16) -add_library(pybind11-utils INTERFACE) -target_link_libraries(pybind11-utils INTERFACE pybind11::pybind11) -target_include_directories(pybind11-utils INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) +add_library(pybind11-utils STATIC) +mo2_configure_library(pybind11-utils + SOURCE_TREE + WARNINGS 4 + EXTERNAL_WARNINGS 4 + AUTOMOC OFF + TRANSLATIONS OFF +) +target_link_libraries(pybind11-utils PUBLIC pybind11::pybind11) +target_include_directories(pybind11-utils PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) add_library(pybind11::utils ALIAS pybind11-utils) diff --git a/src/pybind11-utils/README.md b/src/pybind11-utils/README.md new file mode 100644 index 0000000..d9c21af --- /dev/null +++ b/src/pybind11-utils/README.md @@ -0,0 +1,50 @@ +# pybind11-utils + +This library contains some utility stuff for `pybind11` + +## arg_wrapper.h + +Expose a function `mo2::python::wrap_arguments` and a macro +`MO2_PYBIND11_WRAP_ARGUMENT_CASTER`. +These can be used to convert C++ function when exposing them to Python to accept more +type than the C++ one. + +A toy example can be found in the test folder at +[`tests/python/test_argument_wrapper.cpp`](../../tests/python/test_argument_wrapper.cpp). + +More concrete examples can be found in +[`mobase/pybind11_all.h`](../mobase/pybind11_all.h) for `FileWrapper` and +`DirectoryWrapper`. + +## functional.h + +TODO: updated version of `` that should check the signature +a bit more when creating `std::function` (similar to previous implementation). + +## shared_cpp_owner.h + +Expose a macro `MO2_PYBIND11_SHARED_CPP_HOLDER` that can be used to declare that +`std::shared_ptr<...>` must hold-on their associate Python object. + +```cpp +// use the macro on the type to be exposed (with a trampoline class) +MO2_PYBIND11_SHARED_CPP_HOLDER(ISaveGame) + +// use std::shared_ptr<> as the holder for the class +py::class_>(...); +``` + +Using the `MO2_PYBIND11_SHARED_CPP_HOLDER` (must be present in all files manipulating +`ISaveGame` between C++ and Python) ensure that the Python instance remains alive +alongside the C++ one. + +The `MO2_PYBIND11_SHARED_CPP_HOLDER` declares a specialization of `type_caster<>` that +alters the `std::shared_ptr` by return a `std::shared_ptr<>` that owns the Python +object (via a `pybind11::object`) but does not release the C++ one - The C++ object is +owned by the Python one, so the relation is as follows: + +- The `std::shared_ptr` manipulated in C++ maintains the `pybind11::object` alive + through a custom deleter but DOES NOT release the C++ object when the reference count + reaches 0. +- The Python object holds a standard `std::shared_ptr` that will release the object + when the reference count reaches 0. diff --git a/src/pybind11-utils/include/pybind11_utils/arg_wrapper.h b/src/pybind11-utils/include/pybind11_utils/arg_wrapper.h index f0c688d..2debb2d 100644 --- a/src/pybind11-utils/include/pybind11_utils/arg_wrapper.h +++ b/src/pybind11-utils/include/pybind11_utils/arg_wrapper.h @@ -39,7 +39,7 @@ namespace mo2::python { template auto make_convertible_index_sequence(std::index_sequence) { - return std::index_sequence<(std::is_convertible_v ? Is : -1)...>{}; + return std::index_sequence<(std::is_convertible_v ? Is : -1)...>{}; } template @@ -58,6 +58,47 @@ namespace mo2::python { } } + // list of wrap_fn_impl for possible function types, the set of overload is from + // pybind11::cpp_function + + template + auto wrap_fn_impl(R (*fn)(Args...)) + { + return wrap_fn_impl(fn, fn); + } + + template + auto wrap_fn_impl(R (C::*fn)(Args...)) + { + return wrap_fn_impl(fn, (R(*)(C*, Args...)) nullptr); + } + + template + auto wrap_fn_impl(R (C::*fn)(Args...) &) + { + return wrap_fn_impl(fn, (R(*)(C*, Args...)) nullptr); + } + + template + auto wrap_fn_impl(R (C::*fn)(Args...) const) + { + return wrap_fn_impl(fn, (R(*)(const C*, Args...)) nullptr); + } + + template + auto wrap_fn_impl(R (C::*fn)(Args...) const&) + { + return wrap_fn_impl(fn, (R(*)(const C*, Args...)) nullptr); + } + + template + auto wrap_fn_impl(Fn&& fn) + { + return wrap_fn_impl( + std::forward(fn), + (pybind11::detail::function_signature_t*)nullptr); + } + template struct load_wrapped_argument_helper; @@ -84,41 +125,16 @@ namespace mo2::python { } // namespace detail + // wrap the given function-like object to accept T instead of the specified + // arguments at the specified positions + // + // if the list of positions is empty, replace all arguments that can be converted to + // T + // template auto wrap_arguments(Fn&& fn) { - return detail::wrap_fn_impl( - std::forward(fn), (pybind11::detail::function_signature_t*)nullptr); - } - - template - auto wrap_arguments(R (*fn)(Args...)) - { - return detail::wrap_fn_impl(fn, fn); - } - - template - auto wrap_arguments(R (C::*fn)(Args...)) - { - return detail::wrap_fn_impl(fn, (R(*)(C*, Args...)) nullptr); - } - - template - auto wrap_arguments(R (C::*fn)(Args...) &) - { - return detail::wrap_fn_impl(fn, (R(*)(C*, Args...)) nullptr); - } - - template - auto wrap_arguments(R (C::*fn)(Args...) const) - { - return detail::wrap_fn_impl(fn, (R(*)(const C*, Args...)) nullptr); - } - - template - auto wrap_arguments(R (C::*fn)(Args...) const&) - { - return detail::wrap_fn_impl(fn, (R(*)(const C*, Args...)) nullptr); + return detail::wrap_fn_impl(std::forward(fn)); } } // namespace mo2::python diff --git a/tests/python/test_qt_widgets.cpp b/tests/python/test_qt_widgets.cpp index 30c39a3..6d8a901 100644 --- a/tests/python/test_qt_widgets.cpp +++ b/tests/python/test_qt_widgets.cpp @@ -35,7 +35,7 @@ PYBIND11_MODULE(qt_widgets, m) { s_Parent = new QWidget(); - py::class_> + py::class_> pyCustomWidget(m, "CustomWidget"); pyCustomWidget .def(py::init(), "name"_a, "parent"_a = (QWidget*)nullptr)