mirror of
https://github.com/ModOrganizer2/modorganizer-plugin_python.git
synced 2026-07-27 14:03:33 -07:00
Add some tests for widgets.
This commit is contained in:
@@ -114,53 +114,6 @@ PYBIND11_MODULE(mobase, m)
|
||||
}
|
||||
});
|
||||
|
||||
py::detail::type_caster<QString> t1;
|
||||
py::detail::type_caster<QVariant> t2;
|
||||
|
||||
m.def("testQStringList", [](QStringList const& list) {
|
||||
QStringList res = list;
|
||||
for (QString& value : res) {
|
||||
value = value + "_CPP";
|
||||
}
|
||||
return res;
|
||||
});
|
||||
|
||||
m.def("testGuessedString", [](GuessedValue<QString> const& value) {
|
||||
return std::make_tuple(value.operator const QString&(), value.variants());
|
||||
});
|
||||
|
||||
m.def("testQStringList", [](QStringList const& list) {
|
||||
QStringList res = list;
|
||||
for (QString& value : res) {
|
||||
value = value + "_CPP";
|
||||
}
|
||||
return res;
|
||||
});
|
||||
|
||||
m.def("testQMap1", [](QMap<QString, QString> const& map) {
|
||||
QMap<QString, int> res;
|
||||
for (auto it = map.begin(); it != map.end(); ++it) {
|
||||
res[it.key()] = it.value().size();
|
||||
}
|
||||
return res;
|
||||
});
|
||||
|
||||
m.def("testQMap2", [](QMap<QString, int> const& map) {
|
||||
QMap<QString, QString> res;
|
||||
for (auto it = map.begin(); it != map.end(); ++it) {
|
||||
res[it.key()] = QString::number(it.value());
|
||||
}
|
||||
return res;
|
||||
});
|
||||
|
||||
m.def("testDateTime1", []() {
|
||||
return QDateTime::fromString("2022-02-15T12:33:45", Qt::ISODate);
|
||||
});
|
||||
|
||||
m.def("testDateTime2", [](QDateTime const& datetime) {
|
||||
return datetime.toString();
|
||||
});
|
||||
|
||||
m.def("testEnum0", []() {
|
||||
return Qt::GlobalColor::darkRed;
|
||||
});
|
||||
|
||||
@@ -174,8 +174,7 @@ namespace pybind11::detail::qt {
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
if (policy == return_value_policy::take_ownership &&
|
||||
std::is_copy_constructible_v<QClass>) {
|
||||
if (policy == return_value_policy::take_ownership) {
|
||||
// ensure Python deletes the C++ component
|
||||
qt::sipAPI()->api_transfer_back(sipObj);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ else()
|
||||
set(UIBASE_PATH "${MO2_INSTALL_PATH}/bin/uibase.dll")
|
||||
endif()
|
||||
|
||||
add_custom_target(pytest EXCLUDE_FROM_ALL
|
||||
add_custom_target(pytest
|
||||
${CMAKE_COMMAND} -E env
|
||||
PYTHONPATH="${PYLIB_DIR}\\;$<TARGET_FILE_DIR:mobase>"
|
||||
"UIBASE_PATH=${UIBASE_PATH}" "QT_ROOT=${QT_ROOT}"
|
||||
@@ -22,7 +22,7 @@ mo2_python_pip_install(pytest
|
||||
add_dependencies(pytest mobase)
|
||||
|
||||
|
||||
file(GLOB test_files "test_*.cpp")
|
||||
file(GLOB test_files CONFIGURE_DEPENDS "test_*.cpp")
|
||||
foreach (test_file ${test_files})
|
||||
get_filename_component(target ${test_file} NAME_WLE)
|
||||
string(REPLACE "test_" "" pymodule ${target})
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def pytest_configure():
|
||||
global app
|
||||
|
||||
os.add_dll_directory(Path(os.getenv("QT_ROOT")).joinpath("bin"))
|
||||
os.add_dll_directory(Path(os.getenv("UIBASE_PATH")).parent)
|
||||
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
using namespace pybind11::literals;
|
||||
|
||||
PYBIND11_MODULE(qt, m)
|
||||
{
|
||||
// QString
|
||||
@@ -29,4 +31,29 @@ PYBIND11_MODULE(qt, m)
|
||||
m.def("qstringlist_at", [](QStringList const& values, int index) {
|
||||
return values.at(index);
|
||||
});
|
||||
|
||||
// QMap
|
||||
m.def("qmap_to_length", [](QMap<QString, QString> const& map) {
|
||||
QMap<QString, int> res;
|
||||
for (auto it = map.begin(); it != map.end(); ++it) {
|
||||
res[it.key()] = it.value().size();
|
||||
}
|
||||
return res;
|
||||
});
|
||||
|
||||
// QDateTime
|
||||
|
||||
m.def(
|
||||
"datetime_from_string",
|
||||
[](QString const& date, Qt::DateFormat format) {
|
||||
return QDateTime::fromString(date, format);
|
||||
},
|
||||
"string"_a, "format"_a = Qt::DateFormat::ISODate);
|
||||
|
||||
m.def(
|
||||
"datetime_to_string",
|
||||
[](QDateTime const& datetime, Qt::DateFormat format) {
|
||||
return datetime.toString(format);
|
||||
},
|
||||
"datetime"_a, "format"_a = Qt::DateFormat::ISODate);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import pytest
|
||||
from PyQt6.QtCore import QDateTime, Qt
|
||||
from PyQt6.QtWidgets import QWidget
|
||||
|
||||
m = pytest.importorskip("mobase_tests.qt")
|
||||
|
||||
@@ -21,3 +23,28 @@ def test_qstringlist():
|
||||
|
||||
assert m.qstringlist_at(["x", "y"], 0) == "x"
|
||||
assert m.qstringlist_at(["x", "y"], 1) == "y"
|
||||
|
||||
|
||||
def test_qmap():
|
||||
assert m.qmap_to_length({"t1": "abc", "t2": "o", "t3": ""}) == {
|
||||
"t1": 3,
|
||||
"t2": 1,
|
||||
"t3": 0,
|
||||
}
|
||||
|
||||
|
||||
def test_qdatetime():
|
||||
assert m.datetime_from_string("2022-03-01") == QDateTime(2022, 3, 1, 0, 0)
|
||||
|
||||
date = QDateTime(1995, 5, 20, 0, 0)
|
||||
assert (
|
||||
m.datetime_from_string(
|
||||
date.toString(Qt.DateFormat.TextDate), Qt.DateFormat.TextDate
|
||||
)
|
||||
== date
|
||||
)
|
||||
|
||||
assert m.datetime_to_string(date) == "1995-05-20T00:00:00"
|
||||
assert m.datetime_to_string(date, Qt.DateFormat.TextDate) == date.toString(
|
||||
Qt.DateFormat.TextDate
|
||||
)
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
#include "pybind11_qt/pybind11_qt.h"
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include <QMap>
|
||||
#include <QWidget>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace py = pybind11;
|
||||
using namespace pybind11::literals;
|
||||
|
||||
QMap<QString, QWidget*> s_Widgets;
|
||||
QWidget* s_Parent;
|
||||
|
||||
class CustomWidget : public QWidget {
|
||||
public:
|
||||
CustomWidget(QString const& name, QWidget* parent = nullptr) : QWidget(parent)
|
||||
{
|
||||
s_Widgets[name] = this;
|
||||
setProperty("name", name);
|
||||
}
|
||||
|
||||
~CustomWidget() { s_Widgets.remove(property("name").toString()); }
|
||||
};
|
||||
|
||||
class PyCustomWidget : public CustomWidget {
|
||||
public:
|
||||
using CustomWidget::CustomWidget;
|
||||
int heightForWidth(int value) const
|
||||
{
|
||||
PYBIND11_OVERRIDE(int, CustomWidget, heightForWidth, value);
|
||||
}
|
||||
};
|
||||
|
||||
PYBIND11_MODULE(qt_widgets, m)
|
||||
{
|
||||
s_Parent = new QWidget();
|
||||
|
||||
py::class_<CustomWidget, PyCustomWidget, py::qt::qholder<CustomWidget>>
|
||||
pyCustomWidget(m, "CustomWidget");
|
||||
pyCustomWidget
|
||||
.def(py::init<QString, QWidget*>(), "name"_a, "parent"_a = (QWidget*)nullptr)
|
||||
.def("set_parent_cpp", [](CustomWidget* w) {
|
||||
w->setParent(s_Parent);
|
||||
});
|
||||
py::qt::add_qt_delegate<QWidget>(pyCustomWidget, "_widget");
|
||||
|
||||
m.def("is_alive", [](QString const& name) {
|
||||
return s_Widgets.contains(name);
|
||||
});
|
||||
|
||||
m.def("get", [](QString const& name) {
|
||||
return s_Widgets.contains(name) ? s_Widgets[name] : nullptr;
|
||||
});
|
||||
|
||||
m.def("set_parent", [](QWidget* widget) {
|
||||
widget->setParent(s_Parent);
|
||||
});
|
||||
|
||||
m.def("is_owned_cpp", [](QString const& name) {
|
||||
return s_Widgets.contains(name) && s_Widgets[name]->parent() == s_Parent;
|
||||
});
|
||||
|
||||
m.def("make_widget_own_cpp", [](QString const& name) -> QWidget* {
|
||||
return new CustomWidget(name, s_Parent);
|
||||
});
|
||||
|
||||
m.def(
|
||||
"make_widget_own_py",
|
||||
[](QString const& name) -> QWidget* {
|
||||
return new CustomWidget(name);
|
||||
},
|
||||
py::return_value_policy::take_ownership);
|
||||
|
||||
// simply passing the widget gives the ownership of the Python object to C++
|
||||
m.def("send_to_cpp", [](QString const& name, QWidget* widget) {
|
||||
widget->setProperty("name", name);
|
||||
widget->setParent(s_Parent);
|
||||
s_Widgets[name] = widget;
|
||||
});
|
||||
|
||||
m.def("heightForWidth", [](QString const& name, int value) {
|
||||
return s_Widgets.contains(name) ? s_Widgets[name]->heightForWidth(value)
|
||||
: -1024;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import pytest
|
||||
from PyQt6.QtWidgets import QWidget
|
||||
|
||||
m = pytest.importorskip("mobase_tests.qt_widgets")
|
||||
|
||||
|
||||
class PyWidget(QWidget):
|
||||
def heightForWidth(self, value: int) -> int:
|
||||
return value * 3 + 4
|
||||
|
||||
|
||||
class PyCustomWidget(m.CustomWidget):
|
||||
def __init__(self, name: str):
|
||||
super().__init__(name)
|
||||
|
||||
def heightForWidth(self, value: int) -> int:
|
||||
return value * 6 - 5
|
||||
|
||||
|
||||
def test_qt_widget():
|
||||
# own cpp
|
||||
w = m.make_widget_own_cpp("w1")
|
||||
assert m.is_alive("w1")
|
||||
assert m.is_owned_cpp("w1")
|
||||
|
||||
del w
|
||||
assert m.is_alive("w1")
|
||||
|
||||
# own py
|
||||
w = m.make_widget_own_py("w2")
|
||||
assert m.is_alive("w2")
|
||||
assert not m.is_owned_cpp("w2")
|
||||
|
||||
del w
|
||||
assert not m.is_alive("w2")
|
||||
|
||||
# transfer to C++
|
||||
w = PyWidget()
|
||||
m.send_to_cpp("w3", w)
|
||||
|
||||
# delete the reference w - this should NOT delete the underlying object since it
|
||||
# was transferred to C++
|
||||
del w
|
||||
assert m.is_alive("w3")
|
||||
assert m.is_owned_cpp("w3")
|
||||
|
||||
# if the Python object is dead (BAD!), this will crash horrible
|
||||
assert m.heightForWidth("w3", 4) == 4 * 3 + 4
|
||||
|
||||
# CustomWidget as a qholder, so the construction itself transfers the ownership
|
||||
# to C++
|
||||
w = PyCustomWidget("w4")
|
||||
w.set_parent_cpp()
|
||||
assert m.is_alive("w4")
|
||||
assert m.heightForWidth("w4", 7) == 6 * 7 - 5
|
||||
assert w.heightForWidth(7) == 6 * 7 - 5
|
||||
|
||||
# can call function not defined and not bound through the delegate
|
||||
assert not w.hasHeightForWidth()
|
||||
|
||||
del w
|
||||
assert m.is_alive("w4")
|
||||
assert m.heightForWidth("w4", 7) == 6 * 7 - 5
|
||||
Reference in New Issue
Block a user