From 7908e90d293bf5d2f15212edae20e78366c4396d Mon Sep 17 00:00:00 2001 From: Jaume Bellet Date: Tue, 6 Feb 2024 18:06:36 +0100 Subject: [PATCH 1/2] Adds operator handlers to operators. Allows executing python code pre and post execution. --- scripts/modules/bpy/ops.py | 31 +- source/blender/blenkernel/BKE_context.hh | 2 + source/blender/blenkernel/intern/context.cc | 5 + .../makesdna/DNA_windowmanager_types.h | 2 + source/blender/python/intern/bpy_operator.cc | 483 ++++++++++++++++++ source/blender/python/intern/bpy_rna.cc | 19 + source/blender/python/intern/bpy_rna.h | 2 + source/blender/windowmanager/CMakeLists.txt | 4 + source/blender/windowmanager/intern/wm.cc | 11 + .../windowmanager/intern/wm_event_system.cc | 31 +- 10 files changed, 586 insertions(+), 4 deletions(-) diff --git a/scripts/modules/bpy/ops.py b/scripts/modules/bpy/ops.py index a7a908c8b31..5246459e0bc 100644 --- a/scripts/modules/bpy/ops.py +++ b/scripts/modules/bpy/ops.py @@ -13,8 +13,27 @@ _op_as_string = _ops_module.as_string _op_get_rna_type = _ops_module.get_rna_type _op_get_bl_options = _ops_module.get_bl_options +_op_handlers = _ops_module.handlers + _ModuleType = type(_ops_module) +class handler_action: + def __init__(self, mod, append_func, remove_func): + self._mod = mod + self._append_func = append_func + self._remove_func = remove_func + + def append(self,cb, owner = None, args = None, poll = None): + #is there a way to remove self from console show? + self._append_func(owner=owner, op = self._mod.idname(), cb=cb, args=args, poll = poll) + + def remove(self, cb = None, owner = None): + self._remove_func(owner=owner, op = self._mod.idname(), cb=cb, args=None, poll = None) + + +def remove_handlers(owner = None, cb = None): + _op_handlers.remove(owner = owner, cb = cb, op = None, args = None, poll = None) + # ----------------------------------------------------------------------------- # Callable Operator Wrapper @@ -26,7 +45,14 @@ class _BPyOpsSubModOp: eg. bpy.ops.object.somefunc """ - __slots__ = ("_module", "_func") + __slots__ = ("_module", "_func", "handlers") + + class _handlers: + def __init__(self, mod): + self.invoke_pre = handler_action(mod, _op_handlers.pre_invoke, _op_handlers.pre_invoke_remove) + self.invoke_post = handler_action(mod, _op_handlers.post_invoke, _op_handlers.post_invoke_remove) + self.modal = handler_action(mod, _op_handlers.modal, _op_handlers.modal_remove) + self.modal_end = handler_action(mod, _op_handlers.modal_end, _op_handlers.modal_end_remove) def _get_doc(self): idname = self.idname() @@ -77,6 +103,7 @@ class _BPyOpsSubModOp: def __init__(self, module, func): self._module = module self._func = func + self.handlers = self._handlers(self) def poll(self, *args): C_exec, _C_undo = _BPyOpsSubModOp._parse_args(args) @@ -178,4 +205,6 @@ def __dir__(): else: submodules.add(id_split[0]) + submodules.add("remove_handlers") + return list(submodules) diff --git a/source/blender/blenkernel/BKE_context.hh b/source/blender/blenkernel/BKE_context.hh index b1daaeefa6c..88f127b8b71 100644 --- a/source/blender/blenkernel/BKE_context.hh +++ b/source/blender/blenkernel/BKE_context.hh @@ -69,6 +69,7 @@ struct View3D; struct ViewLayer; struct wmGizmoGroup; struct wmMsgBus; +struct wmOpHandlers; struct wmWindow; struct wmWindowManager; struct WorkSpace; @@ -191,6 +192,7 @@ void *CTX_wm_region_data(const bContext *C); ARegion *CTX_wm_menu(const bContext *C); wmGizmoGroup *CTX_wm_gizmo_group(const bContext *C); wmMsgBus *CTX_wm_message_bus(const bContext *C); +wmOpHandlers *CTX_wm_op_handlers(const bContext *C); ReportList *CTX_wm_reports(const bContext *C); View3D *CTX_wm_view3d(const bContext *C); diff --git a/source/blender/blenkernel/intern/context.cc b/source/blender/blenkernel/intern/context.cc index 075f20ce57b..7607fd9fcda 100644 --- a/source/blender/blenkernel/intern/context.cc +++ b/source/blender/blenkernel/intern/context.cc @@ -763,6 +763,11 @@ wmMsgBus *CTX_wm_message_bus(const bContext *C) return C->wm.manager ? C->wm.manager->message_bus : nullptr; } +struct wmOpHandlers *CTX_wm_op_handlers(const bContext *C) +{ + return C->wm.manager ? C->wm.manager->op_handlers : nullptr; +} + ReportList *CTX_wm_reports(const bContext *C) { if (C->wm.manager) { diff --git a/source/blender/makesdna/DNA_windowmanager_types.h b/source/blender/makesdna/DNA_windowmanager_types.h index e55911bd0bf..1badf4d36e7 100644 --- a/source/blender/makesdna/DNA_windowmanager_types.h +++ b/source/blender/makesdna/DNA_windowmanager_types.h @@ -213,6 +213,8 @@ typedef struct wmWindowManager { struct wmMsgBus *message_bus; + struct wmOpHandlers *op_handlers; + // #ifdef WITH_XR_OPENXR wmXrData xr; // #endif diff --git a/source/blender/python/intern/bpy_operator.cc b/source/blender/python/intern/bpy_operator.cc index 4c66122ea06..0895705f966 100644 --- a/source/blender/python/intern/bpy_operator.cc +++ b/source/blender/python/intern/bpy_operator.cc @@ -36,6 +36,7 @@ #include "RNA_prototypes.h" #include "WM_api.hh" +#include "WM_op_handlers.h" #include "WM_types.hh" #include "MEM_guardedalloc.h" @@ -460,11 +461,493 @@ static PyModuleDef bpy_ops_module = { /*m_free*/ nullptr, }; +static int bpy_op_handler_check(void *py_data, void* owner, void *callback) +{ + PyObject *py_owner = PyTuple_GET_ITEM(py_data, 0); + PyObject *py_callback = PyTuple_GET_ITEM(py_data, 2); + if (owner != NULL && callback != NULL) { + return (py_owner == owner && py_callback == callback); + } + else if (owner != NULL) + { + return (py_owner == owner); + } + else { //callback != NULL + return (py_callback == callback); + } +} + +/* + * @Properties Rna Properties that has been used to set the operator + */ +static PyObject *bpy_op_get_operator_params(PointerRNA *properties) +{ + const char *arg_name = NULL; + PyObject *py_dict = PyDict_New(); + PyObject *data; + RNA_STRUCT_BEGIN (properties, prop) { + arg_name = RNA_property_identifier(prop); + data = NULL; + if (STREQ(arg_name, "rna_type")) { + continue; + } + switch (RNA_property_type(prop)) { + case PROP_BOOLEAN: { + bool val = RNA_property_boolean_get(properties, prop); + // from Py Docs, Py_False and Py_truee needs to be treated just like any other object with + // respect to reference counts. + data = val ? Py_False : Py_True; + break; + } + case PROP_INT: { + const int prop_array_length = RNA_property_array_length(properties, prop); + if (prop_array_length == 0) { + int val = RNA_property_int_get(properties, prop); + data = PyLong_FromLong(val); + } + else { + int *values = (int*) MEM_callocN(sizeof(int) * prop_array_length, __func__); + RNA_property_int_get_array(properties, prop, values); + data = PyTuple_New(prop_array_length); + for (int i = 0; i < prop_array_length; i++) { + PyObject *py_val = PyLong_FromLong(*(values + i)); + PyTuple_SET_ITEM(data, i, py_val); + } + MEM_freeN(values); + } + break; + } + case PROP_FLOAT: { + const int prop_array_length = RNA_property_array_length(properties, prop); + if (prop_array_length == 0) { + float val; + val = RNA_property_float_get(properties, prop); + data = PyFloat_FromDouble(val); + } + else { + float *values = (float*) MEM_callocN(sizeof(float) * prop_array_length, __func__); + RNA_property_float_get_array(properties, prop, values); + data = PyTuple_New(prop_array_length); + for (int i = 0; i < prop_array_length; i++) { + PyObject *py_val = PyFloat_FromDouble(*(values + i)); + PyTuple_SET_ITEM(data, i, py_val); + } + MEM_freeN(values); + } + break; + } + case PROP_STRING: { + char buff[256]; + char *value = RNA_property_string_get_alloc(properties, prop, buff, sizeof(buff), NULL); + data = PyUnicode_FromString(value); + if (value != buff) { + MEM_freeN(value); + } + break; + } + case PROP_ENUM: { + int val = RNA_property_enum_get(properties, prop); + data = PyLong_FromLong(val); + break; + } + case PROP_POINTER: { + data = PyUnicode_FromString("POINTER"); + break; + } + case PROP_COLLECTION: { + data = PyUnicode_FromString("COLLECTION"); + break; + } + default: + BLI_assert(false); + } + if (data != NULL) { + PyDict_SetItemString(py_dict, arg_name, data); + } + } + RNA_STRUCT_END; + return py_dict; +} + + +static bool bpy_op_callback_get_return_value(PyObject *callback, PyObject *py_ret) +{ + bool ret = true; // Do not interrump on error + if (py_ret == NULL) { + PyC_Err_PrintWithFunc(callback); + } + else { + if (py_ret == Py_None) { + // pass + } + else if (py_ret == Py_True) { + // pass + } + else if (py_ret == Py_False){ + ret = false; + } else { + PyErr_SetString(PyExc_ValueError, "the return value must be None or boolean"); + PyC_Err_PrintWithFunc(callback); + } + Py_DECREF(py_ret); + } + return ret; +} + + +static PyObject *bpy_op_get_callback_call(PyObject *callback, + bContext *C, const wmEvent *event, int *operator_ret, PyObject *params, PyObject *callback_args) +{ + PointerRNA ctx_ptr; + PointerRNA event_ptr; + + PyObject *bpy_ctx; + PyObject *bpy_event; + PyObject *py_ret; + + ctx_ptr = RNA_pointer_create(nullptr, &RNA_Context, C); + bpy_ctx = pyrna_struct_CreatePyObject(&ctx_ptr); + + if (event != NULL) { + event_ptr = RNA_pointer_create(NULL, &RNA_Event, (void*) event); + bpy_event = pyrna_struct_CreatePyObject(&event_ptr); + } + else { + bpy_event = Py_None; + } + + int s = (operator_ret == NULL) ? 3 : 4; + int c = (callback_args == Py_None) ? s : PyTuple_GET_SIZE(callback_args) + s; + PyObject *func_args = PyTuple_New(c); + + PyTuple_SET_ITEM(func_args, 0, bpy_ctx); + PyTuple_SET_ITEM(func_args, 1, bpy_event); + PyTuple_SET_ITEM(func_args, 2, params); + + if (operator_ret != NULL) { + PyObject *op_ret = pyrna_enum_bitfield_to_py(rna_enum_operator_return_items, *operator_ret); + PyTuple_SET_ITEM(func_args, 3, op_ret); + } + + for (int i = s; i < c; i++) { + PyTuple_SET_ITEM(func_args, i, PyTuple_GET_ITEM(callback_args, i - s)); + } + + py_ret = PyObject_CallObject(callback, func_args); + Py_DECREF(func_args); + return py_ret; +} + + +static bool bpy_op_handler_poll(struct bContext *C, + const wmEvent *event, void *py_data, + PointerRNA *properties) +{ + bool ret = true; + PyGILState_STATE gilstate; // this is because is not thread safe + bpy_context_set(C, &gilstate); + { + PyObject *callback_args = PyTuple_GET_ITEM(py_data, 3); + PyObject *py_poll = PyTuple_GET_ITEM(py_data, 4); + + // Properties get null on modall poll, params are not bypassed to Py poll function + PyObject *params = (properties == NULL) ? Py_None : bpy_op_get_operator_params(properties); + if (py_poll != Py_None) { + PyObject *py_ret = bpy_op_get_callback_call(py_poll, C, event, NULL, params, callback_args); + + if (py_ret == NULL) { + // Error + PyErr_Print(); + return false; + } + else if (py_ret == Py_True) { + ret = true; + Py_DECREF(py_ret); + } + else if (py_ret == Py_False) { + ret = false; + Py_DECREF(py_ret); + } + else { + ret = false; + Py_DECREF(py_ret); + PyErr_SetString(PyExc_ValueError, "the return value must be boolean"); + PyC_Err_PrintWithFunc(py_poll); + } + } + // Py_DECREF(params); + } + bpy_context_clear(C, &gilstate); + return ret; +} + +static bool bpy_op_handler_modal (bContext *C, const wmEvent *event, void *py_data, PointerRNA *properties, int operator_ret) +{ + // this is because is not thread safe + bool ret = true; + PyGILState_STATE gilstate; + bpy_context_set(C, &gilstate); + { + PyObject *callback = PyTuple_GET_ITEM(py_data, 2); + PyObject *callback_args = PyTuple_GET_ITEM(py_data, 3); + PyObject *py_ret = bpy_op_get_callback_call(callback, C, event, &operator_ret, Py_None, callback_args); + ret = bpy_op_callback_get_return_value(callback, py_ret); + } + bpy_context_clear(C, &gilstate); + return ret; +} + + +static bool bpy_op_handler_invoke(bContext *C, + const wmEvent *event, + void *py_data, + PointerRNA *properties, + int operator_ret) +{ + bool ret = true; + // this is because is not thread safe + PyGILState_STATE gilstate; + bpy_context_set(C, &gilstate); + { + PyObject *callback = PyTuple_GET_ITEM(py_data, 2); + PyObject *callback_args = PyTuple_GET_ITEM(py_data, 3); + PyObject *params = bpy_op_get_operator_params(properties); + PyObject *py_ret = bpy_op_get_callback_call(callback, C, event, operator_ret ? &operator_ret : NULL , params, callback_args); + ret = bpy_op_callback_get_return_value(callback, py_ret); + } + bpy_context_clear(C, &gilstate); + return ret; +} + + +static PyObject *bpy_op_handler_proc( + PyObject *args, + PyObject *kw) +{ + const char *error_prefix = "op_handler_proc"; + + PyObject *py_op = NULL; + PyObject *py_owner = NULL; // Object who creates the handler + PyObject *callback = NULL, *py_poll = NULL; + PyObject *callback_args = NULL; + + + if (PyTuple_GET_SIZE(args) != 0) { + PyErr_Format(PyExc_TypeError, "%s: only keyword arguments are supported", error_prefix); + } + + // see https://docs.python.org/3/c-api/arg.html + static const char *_keywords[] = {"owner", "op", "cb", "args", "poll", NULL}; + static _PyArg_Parser _parser = {"OOOOO|:handler_proc", _keywords, 0}; + + if (!_PyArg_ParseTupleAndKeywordsFast(args, kw, &_parser, &py_owner, &py_op, &callback, &callback_args, &py_poll)) { + PyErr_SetString( + PyExc_TypeError, "Cannot set arguments, or types does not match"); + } + + if (callback != Py_None && !PyFunction_Check(callback)) { + // Callback may be none on remove + PyErr_Format( + PyExc_TypeError, "callback expects a function, found %.200s", Py_TYPE(callback)->tp_name); + } + + if (py_poll != Py_None && !PyFunction_Check(py_poll)) { + // Callback may be none on remove + PyErr_Format( + PyExc_TypeError, "poll expects a function, found %.200s", Py_TYPE(callback)->tp_name); + } + + if (py_op != Py_None && !PyUnicode_Check(py_op)) { + PyErr_Format( + PyExc_TypeError, "op expects an astring, found %.200s", Py_TYPE(py_op)->tp_name); + } + + if (PyErr_Occurred() != NULL) { + PyErr_Print(); + return NULL; + } + + + PyObject *py_data = PyTuple_New(5); + PyTuple_SET_ITEMS(py_data, + Py_INCREF_RET(py_owner), // 0 + Py_INCREF_RET(py_op), // 1 + Py_INCREF_RET(callback), // 2 + Py_INCREF_RET(callback_args), // 3 + Py_INCREF_RET(py_poll)); // 4 + + return Py_INCREF_RET(py_data); +} + +static PyObject *op_handler_append(int handler_id , PyObject *args, PyObject *kw) +{ + bContext *C = BPY_context_get(); + struct wmOpHandlers *op_handlers = CTX_wm_op_handlers(C); + + PyObject *py_data = bpy_op_handler_proc(args, kw); + + bool (*func)(bContext * C,const wmEvent * event, void *, PointerRNA *, int) = nullptr; + + switch (handler_id) { + case HANDLER_TYPE_PRE_INVOKE: + case HANDLER_TYPE_POST_INVOKE: + func = bpy_op_handler_invoke; + break; + case HANDLER_TYPE_MODAL: + case HANDLER_TYPE_MODAL_END: + func = bpy_op_handler_modal; + break; + } + + if (py_data != NULL) { + PyObject *py_owner = PyTuple_GET_ITEM(py_data, 0); + PyObject *py_op = PyTuple_GET_ITEM(py_data, 1); + PyObject *py_callback = PyTuple_GET_ITEM(py_data, 2); + PyObject *py_poll = PyTuple_GET_ITEM(py_data, 4); + if (py_op == Py_None) { + PyErr_Format(PyExc_TypeError, "missing operator"); + } + else if (py_callback == Py_None) { + PyErr_Format(PyExc_TypeError, "callback expects a function"); + } else { + WM_op_handlers_append(op_handlers, + handler_id, + py_owner, + PyUnicode_AsUTF8(py_op), + func, + bpy_op_handler_check, + py_poll == Py_None ? NULL : bpy_op_handler_poll, + py_data); + } + } + + if (PyErr_Occurred() != NULL) { + PyErr_Print(); + } + Py_RETURN_NONE; +} + +static PyObject *op_handler_remove(int handler_id, PyObject *args, PyObject *kw) +{ + bContext *C = BPY_context_get(); + struct wmOpHandlers *op_handlers = CTX_wm_op_handlers(C); + + PyObject *py_data = bpy_op_handler_proc(args, kw); + + if (py_data != NULL) { + PyObject *py_owner = PyTuple_GET_ITEM(py_data, 0); + PyObject *py_op = PyTuple_GET_ITEM(py_data, 1); + PyObject *py_cb = PyTuple_GET_ITEM(py_data, 2); + if (py_owner == Py_None && py_cb == Py_None) { + PyErr_Format(PyExc_TypeError, "missing owner or callback"); + } else { + if (WM_op_handlers_remove(op_handlers, + handler_id, + (py_op == Py_None ? NULL : PyUnicode_AsUTF8(py_op)), + (py_cb == Py_None ? NULL : py_cb), + (py_owner == Py_None ? NULL : py_owner)) == 0) { + PyErr_Format(PyExc_NameError, "data not found on %s", PyUnicode_AsUTF8(py_op)); + } + } + } + + if (PyErr_Occurred() != NULL) { + PyErr_Print(); + } + + Py_RETURN_NONE; +} + +static PyObject *op_handler_append_pre_invoke(PyObject *self, PyObject *args, PyObject *kw) +{ + return op_handler_append(HANDLER_TYPE_PRE_INVOKE, args, kw); +} + +static PyObject *op_handler_append_post_invoke(PyObject *self, PyObject *args, PyObject *kw) +{ + return op_handler_append(HANDLER_TYPE_POST_INVOKE, args, kw); +} + +static PyObject *op_handler_append_modal(PyObject *self, PyObject *args, PyObject *kw) +{ + return op_handler_append(HANDLER_TYPE_MODAL, args, kw); +} + +static PyObject *op_handler_append_modal_end(PyObject *self, PyObject *args, PyObject *kw) +{ + return op_handler_append(HANDLER_TYPE_MODAL_END, args, kw); +} + + + +static PyObject *op_handler_remove_pre_invoke(PyObject *self, PyObject *args, PyObject *kw) +{ + return op_handler_remove(HANDLER_TYPE_PRE_INVOKE, args, kw); +} + +static PyObject *op_handler_remove_post_invoke(PyObject *self, PyObject *args, PyObject *kw) +{ + return op_handler_remove(HANDLER_TYPE_POST_INVOKE, args, kw); +} + +static PyObject *op_handler_remove_modal(PyObject *self, PyObject *args, PyObject *kw) +{ + return op_handler_remove(HANDLER_TYPE_MODAL, args, kw); +} + +static PyObject *op_handler_remove_modal_end(PyObject *self, PyObject *args, PyObject *kw) +{ + return op_handler_remove(HANDLER_TYPE_MODAL_END, args, kw); +} + + +static PyObject *op_handlers_remove(PyObject *self, PyObject *args, PyObject *kw) +{ + return op_handler_remove(HANDLER_TYPE_ALL, args, kw); +} + + +static struct PyMethodDef bpy_ops_handlers_methods[] = { + {"pre_invoke", (PyCFunction)op_handler_append_pre_invoke, METH_VARARGS | METH_KEYWORDS, NULL}, + {"post_invoke", (PyCFunction)op_handler_append_post_invoke, METH_VARARGS | METH_KEYWORDS, NULL}, + {"modal", (PyCFunction)op_handler_append_modal, METH_VARARGS | METH_KEYWORDS, NULL}, + {"modal_end", (PyCFunction)op_handler_append_modal_end, METH_VARARGS | METH_KEYWORDS, NULL}, + {"pre_invoke_remove", (PyCFunction)op_handler_remove_pre_invoke, METH_VARARGS | METH_KEYWORDS, NULL}, + {"post_invoke_remove", (PyCFunction)op_handler_remove_post_invoke, METH_VARARGS | METH_KEYWORDS, NULL}, + {"modal_remove",(PyCFunction)op_handler_remove_post_invoke, METH_VARARGS | METH_KEYWORDS, NULL}, + {"modal_end_remove", (PyCFunction)op_handler_remove_modal_end, METH_VARARGS | METH_KEYWORDS, NULL}, + {"remove", (PyCFunction)op_handlers_remove, METH_VARARGS | METH_KEYWORDS, NULL}, + {NULL, NULL, 0, NULL}, +}; + +static struct PyModuleDef bpy_ops_handlers = { + PyModuleDef_HEAD_INIT, + "_bpy.ops.handlers", + NULL, + -1, /* multiple "initialization" just copies the module dict. */ + bpy_ops_handlers_methods, + NULL, + NULL, + NULL, + NULL, +}; + + + PyObject *BPY_operator_module() { PyObject *submodule; submodule = PyModule_Create(&bpy_ops_module); + PyObject *handlers = PyModule_Create(&bpy_ops_handlers); + + Py_INCREF(handlers); + if (PyModule_AddObject(submodule, "handlers", handlers) < 0) { + Py_DECREF(submodule); + Py_DECREF(handlers); + return NULL; + } + return submodule; } diff --git a/source/blender/python/intern/bpy_rna.cc b/source/blender/python/intern/bpy_rna.cc index 4af762aa089..fbd03477776 100644 --- a/source/blender/python/intern/bpy_rna.cc +++ b/source/blender/python/intern/bpy_rna.cc @@ -1299,6 +1299,25 @@ static int pyrna_prop_to_enum_bitfield( return ret; } +PyObject *pyrna_enum_bitfield_to_py(const EnumPropertyItem *items, int value) +{ + PyObject *ret = PySet_New(NULL); + const char *identifier[RNA_ENUM_BITFLAG_SIZE + 1]; + + if (RNA_enum_bitflag_identifiers(items, value, identifier)) { + PyObject *item; + int index; + for (index = 0; identifier[index]; index++) { + item = PyUnicode_FromString(identifier[index]); + PySet_Add(ret, item); + Py_DECREF(item); + } + } + + return ret; +} + + static PyObject *pyrna_enum_to_py(PointerRNA *ptr, PropertyRNA *prop, int val) { PyObject *item, *ret = nullptr; diff --git a/source/blender/python/intern/bpy_rna.h b/source/blender/python/intern/bpy_rna.h index 661c7062462..2e57405b47a 100644 --- a/source/blender/python/intern/bpy_rna.h +++ b/source/blender/python/intern/bpy_rna.h @@ -189,6 +189,8 @@ PyObject *pyrna_id_CreatePyObject(struct ID *id); bool pyrna_id_FromPyObject(PyObject *obj, struct ID **id); bool pyrna_id_CheckPyObject(PyObject *obj); +PyObject *pyrna_enum_bitfield_to_py(const struct EnumPropertyItem *items, int value); + /* operators also need this to set args */ int pyrna_pydict_to_props(PointerRNA *ptr, PyObject *kw, bool all_args, const char *error_prefix); PyObject *pyrna_prop_to_py(PointerRNA *ptr, PropertyRNA *prop); diff --git a/source/blender/windowmanager/CMakeLists.txt b/source/blender/windowmanager/CMakeLists.txt index bb247962f7d..bb590209e14 100644 --- a/source/blender/windowmanager/CMakeLists.txt +++ b/source/blender/windowmanager/CMakeLists.txt @@ -69,10 +69,12 @@ set(SRC message_bus/intern/wm_message_bus.cc message_bus/intern/wm_message_bus_rna.cc message_bus/intern/wm_message_bus_static.cc + op_handlers/intern/wm_op_handlers.cc WM_api.hh WM_keymap.hh WM_message.hh + WM_op_handlers.h WM_toolsystem.hh WM_types.hh wm.hh @@ -92,6 +94,8 @@ set(SRC gizmo/intern/wm_gizmo_intern.hh message_bus/intern/wm_message_bus_intern.hh message_bus/wm_message_bus.hh + op_handlers/wm_op_handlers.h + op_handlers/intern/wm_op_handlers_intern.h ) set(LIB diff --git a/source/blender/windowmanager/intern/wm.cc b/source/blender/windowmanager/intern/wm.cc index 6288be426b4..eb76be922af 100644 --- a/source/blender/windowmanager/intern/wm.cc +++ b/source/blender/windowmanager/intern/wm.cc @@ -41,6 +41,7 @@ #include "WM_api.hh" #include "WM_message.hh" +#include "WM_op_handlers.h" #include "WM_types.hh" #include "wm.hh" #include "wm_draw.hh" @@ -217,6 +218,7 @@ static void window_manager_blend_read_data(BlendDataReader *reader, ID *id) wm->undo_stack = nullptr; wm->message_bus = nullptr; + wm->op_handlers = nullptr; wm->xr.runtime = nullptr; @@ -474,6 +476,10 @@ void WM_check(bContext *C) wm->message_bus = WM_msgbus_create(); } + if (wm->op_handlers == NULL) { + wm->op_handlers = WM_op_handlers_create(); + } + if (!G.background) { /* Case: file-read. */ if ((wm->init_flag & WM_INIT_FLAG_WINDOW) == 0) { @@ -574,6 +580,11 @@ void wm_close_and_free(bContext *C, wmWindowManager *wm) WM_msgbus_destroy(wm->message_bus); } + if (wm->op_handlers != nullptr) { + WM_op_handlers_destroy(wm->op_handlers); + } + + #ifdef WITH_PYTHON BPY_callback_wm_free(wm); #endif diff --git a/source/blender/windowmanager/intern/wm_event_system.cc b/source/blender/windowmanager/intern/wm_event_system.cc index 41e4e5b62e9..867aa5f20d5 100644 --- a/source/blender/windowmanager/intern/wm_event_system.cc +++ b/source/blender/windowmanager/intern/wm_event_system.cc @@ -66,6 +66,7 @@ #include "WM_api.hh" #include "WM_message.hh" +#include "WM_op_handlers.h" #include "WM_toolsystem.hh" #include "WM_types.hh" @@ -1510,6 +1511,12 @@ static int wm_operator_invoke(bContext *C, return WM_operator_poll(C, ot); } + // Ensure any change is processed by poll + if (WM_op_handlers_operator_pre_invoke(C, event, CTX_wm_op_handlers(C), ot, properties) == + false) { + return OPERATOR_FINISHED; + } + if (WM_operator_poll(C, ot)) { wmWindowManager *wm = CTX_wm_manager(C); const intptr_t undo_id_prev = wm_operator_undo_active_id(wm); @@ -1550,6 +1557,8 @@ static int wm_operator_invoke(bContext *C, retval = op->type->invoke(C, op, &event_temp); OPERATOR_RETVAL_CHECK(retval); + WM_op_handlers_operator_post_invoke(C, event, CTX_wm_op_handlers(C), ot, op->ptr, retval); + if (op->type->flag & OPTYPE_UNDO && CTX_wm_manager(C) == wm) { wm->op_undo_depth--; } @@ -1562,6 +1571,8 @@ static int wm_operator_invoke(bContext *C, retval = op->type->exec(C, op); OPERATOR_RETVAL_CHECK(retval); + WM_op_handlers_operator_post_invoke(C, event, CTX_wm_op_handlers(C), ot, op->ptr, retval); + if (op->type->flag & OPTYPE_UNDO && CTX_wm_manager(C) == wm) { wm->op_undo_depth--; } @@ -2476,9 +2487,20 @@ static eHandlerActionFlag wm_handler_operator_call(bContext *C, wm->op_undo_depth++; } - /* Warning, after this call all context data and 'event' may be freed. see check below. */ - retval = ot->modal(C, op, event); - OPERATOR_RETVAL_CHECK(retval); + if ( (WM_get_op_handlers(CTX_wm_op_handlers(C), ot->idname) != NULL) && !ot->poll(C)) { + // Py Handler, changing poll conditions + retval = OPERATOR_CANCELLED; + } + else { + + /* Warning, after this call all context data and 'event' may be freed. see check below. */ + retval = ot->modal(C, op, event); + OPERATOR_RETVAL_CHECK(retval); + + if (WM_op_handlers_operator_modal(C, event, CTX_wm_op_handlers(C), ot, retval) == false) { + retval = OPERATOR_CANCELLED; + } + } if (ot->flag & OPTYPE_UNDO && CTX_wm_manager(C) == wm) { wm->op_undo_depth--; @@ -2492,6 +2514,9 @@ static eHandlerActionFlag wm_handler_operator_call(bContext *C, wm_event_modalkeymap_end(event, &event_backup); if (retval & (OPERATOR_CANCELLED | OPERATOR_FINISHED)) { + + WM_op_handlers_operator_modal_end(C, nullptr, CTX_wm_op_handlers(C), ot, retval); + wm_operator_reports(C, op, retval, false); wmOperator *op_test = handler->op->opm ? handler->op->opm : handler->op; -- 2.30.2 From 5cc4c2978451b4ec884d2e599f5d87aef035bb94 Mon Sep 17 00:00:00 2001 From: Jaume Bellet Date: Tue, 6 Feb 2024 20:41:11 +0100 Subject: [PATCH 2/2] missing files to be added on previous commit --- source/blender/windowmanager/WM_op_handlers.h | 25 ++ .../op_handlers/intern/wm_op_handlers.cc | 246 ++++++++++++++++++ .../intern/wm_op_handlers_intern.h | 36 +++ .../op_handlers/wm_op_handlers.h | 101 +++++++ 4 files changed, 408 insertions(+) create mode 100644 source/blender/windowmanager/WM_op_handlers.h create mode 100644 source/blender/windowmanager/op_handlers/intern/wm_op_handlers.cc create mode 100644 source/blender/windowmanager/op_handlers/intern/wm_op_handlers_intern.h create mode 100644 source/blender/windowmanager/op_handlers/wm_op_handlers.h diff --git a/source/blender/windowmanager/WM_op_handlers.h b/source/blender/windowmanager/WM_op_handlers.h new file mode 100644 index 00000000000..45c74f0fcaa --- /dev/null +++ b/source/blender/windowmanager/WM_op_handlers.h @@ -0,0 +1,25 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup wm + */ + +#pragma once + +struct wmOpHandlers; + +#include "op_handlers/wm_op_handlers.h" diff --git a/source/blender/windowmanager/op_handlers/intern/wm_op_handlers.cc b/source/blender/windowmanager/op_handlers/intern/wm_op_handlers.cc new file mode 100644 index 00000000000..e46acfaa821 --- /dev/null +++ b/source/blender/windowmanager/op_handlers/intern/wm_op_handlers.cc @@ -0,0 +1,246 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup wm + */ + +#include + + +#include "MEM_guardedalloc.h" + +#include "DNA_windowmanager_types.h" + +#include "BLI_listbase.h" +#include "BLI_utildefines.h" +#include "BLI_string.h" + +#include "WM_types.hh" + +#include "op_handlers/intern/wm_op_handlers_intern.h" +#include "op_handlers/wm_op_handlers.h" + +/* -------------------------------------------------------------------------- */ +/** \name Public API + * \{ */ + + + +struct wmOpHandlers *WM_op_handlers_create(void) +{ + + struct wmOpHandlers *op_handlers = (wmOpHandlers*) MEM_callocN(sizeof(*op_handlers), __func__); + return op_handlers; +} + +void WM_op_handlers_destroy(struct wmOpHandlers *op_handlers) +{ + LISTBASE_FOREACH (wmOpHandlerData *, opHandlers, &op_handlers->handlers ) { + BLI_freelistN(&opHandlers->pre_invoke); + BLI_freelistN(&opHandlers->post_invoke); + BLI_freelistN(&opHandlers->modal); + BLI_freelistN(&opHandlers->modal_end); + } + BLI_freelistN(&op_handlers->handlers); + MEM_freeN(op_handlers); + op_handlers = NULL; +} + +ListBase *WM_op_handlers_get_handler_list(wmOpHandlerData *opHandlers, int id) +{ + ListBase *list = NULL; + switch (id) { + case HANDLER_TYPE_PRE_INVOKE: + list = &opHandlers->pre_invoke; + break; + case HANDLER_TYPE_POST_INVOKE: + list = &opHandlers->post_invoke; + break; + case HANDLER_TYPE_MODAL: + list = &opHandlers->modal; + break; + case HANDLER_TYPE_MODAL_END: + list = &opHandlers->modal_end; + break; + default: + BLI_assert(false); + } + return list; +} + +wmOpHandlerData *WM_get_op_handlers(struct wmOpHandlers *op_handlers, const char *op_name) +{ + return (wmOpHandlerData*) BLI_findstring(&op_handlers->handlers, op_name, offsetof(wmOpHandlerData, id_name)); +} + + +void WM_op_handlers_append(struct wmOpHandlers *op_handlers, + int id, + void *py_handle, + const char *op_name, + bool (*cb)(struct bContext *, const struct wmEvent *event, void *, PointerRNA *properties, int), + int (*check)(void *, void*, void*), + bool (*poll)(struct bContext *, const struct wmEvent *event, void *, PointerRNA *properties), + void *py_data) +{ + wmOpHandlerData *opHandlers = WM_get_op_handlers(op_handlers, op_name); + wmHandlerData *data = (wmHandlerData *)MEM_mallocN(sizeof(wmHandlerData), "wmHandlerData"); + + if (opHandlers == NULL) { + // Create + opHandlers = (wmOpHandlerData *) MEM_callocN(sizeof(wmOpHandlerData), "wmOpHandlerData"); + BLI_strncpy(opHandlers->id_name, op_name, OP_MAX_TYPENAME); + BLI_addtail(&op_handlers->handlers, opHandlers); + } + + data->py_handle = py_handle; + data->id_name = opHandlers->id_name; + data->cb = cb; + data->check = check; + data->poll = poll; + data->py_data = py_data; + + ListBase *list = WM_op_handlers_get_handler_list(opHandlers, id); + BLI_addtail(list, data); +} + +int WM_op_handlers_remove_all(struct wmOpHandlers *op_handlers, void *cb, void *owner) +{ + int ret = 0; + LISTBASE_FOREACH (wmOpHandlerData *, opHandlers, &op_handlers->handlers) { + ret += WM_op_handlers_remove(op_handlers, HANDLER_TYPE_PRE_INVOKE, opHandlers->id_name, cb, owner); + ret += WM_op_handlers_remove(op_handlers, HANDLER_TYPE_POST_INVOKE, opHandlers->id_name, cb, owner); + ret += WM_op_handlers_remove(op_handlers, HANDLER_TYPE_MODAL, opHandlers->id_name, cb, owner); + ret += WM_op_handlers_remove(op_handlers, HANDLER_TYPE_MODAL_END, opHandlers->id_name, cb, owner); + } + return ret; +} + +int WM_op_handlers_remove( + struct wmOpHandlers *op_handlers, int id, const char *op_name, void *cb, void *owner) +{ + int ret = 0; + if (id == HANDLER_TYPE_ALL) { + ret = WM_op_handlers_remove_all(op_handlers, cb, owner); + } + else { + wmOpHandlerData *opHandlers = WM_get_op_handlers(op_handlers, op_name); + + if (opHandlers != NULL) { + ListBase *list = WM_op_handlers_get_handler_list(opHandlers, id); + wmHandlerData *next; + for (wmHandlerData *data = (wmHandlerData*) list->first; data; data = next) { + next = data->next; + if (data->check(data->py_data, owner, cb)) { + BLI_freelinkN(list, data); + ret++; + } + } + } + } + return ret; +} + + +bool WM_op_handlers_operator_exec(struct bContext *C, + const wmEvent *event, + ListBase *list, + struct wmOperatorType *ot, + struct PointerRNA *properties, + int retval) +{ + bool ret = true; + LISTBASE_FOREACH (wmHandlerData *, data, list) { + if (data->poll == nullptr || data->poll(C, event, data->py_data, properties)) { + ret = ret && data->cb(C, event, data->py_data, properties, retval); + } + } + return ret; +} + + +// Previous to invoke +bool WM_op_handlers_operator_pre_invoke(struct bContext *C, + const wmEvent *event, + struct wmOpHandlers *op_handlers, + struct wmOperatorType *ot, + PointerRNA *properties) +{ + bool ret = true; + if (op_handlers != NULL) { + wmOpHandlerData *opHandlers = WM_get_op_handlers(op_handlers, ot->idname); + if (opHandlers != NULL) { + ListBase *list = WM_op_handlers_get_handler_list(opHandlers, HANDLER_TYPE_PRE_INVOKE); + ret = ret && WM_op_handlers_operator_exec(C, event, list, ot, properties, 0); + } + } + return ret; +} + + +// Post To invoke +void WM_op_handlers_operator_post_invoke(struct bContext *C, + const wmEvent *event, + struct wmOpHandlers *op_handlers, + struct wmOperatorType *ot, + struct PointerRNA *properties, + int retval) +{ + if (op_handlers != NULL) { + wmOpHandlerData *opHandlers = WM_get_op_handlers(op_handlers, ot->idname); + if (opHandlers != NULL) { + ListBase *list = WM_op_handlers_get_handler_list(opHandlers, HANDLER_TYPE_POST_INVOKE); + WM_op_handlers_operator_exec(C, event, list, ot, properties, retval); + } + } +} + +bool WM_op_handlers_operator_modal(struct bContext *C, + const wmEvent *event, + struct wmOpHandlers *op_handlers, + struct wmOperatorType *ot, + int retval + ) +{ + bool ret = true; + if (op_handlers != NULL) { + wmOpHandlerData *opHandlers = WM_get_op_handlers(op_handlers, ot->idname); + if (opHandlers != NULL) { + ListBase *list = WM_op_handlers_get_handler_list(opHandlers, HANDLER_TYPE_MODAL); + ret = ret && WM_op_handlers_operator_exec(C, event, list, ot, NULL, retval); + } + } + return ret; +} + +void WM_op_handlers_operator_modal_end(struct bContext *C, + const wmEvent *event, + struct wmOpHandlers *op_handlers, + struct wmOperatorType *ot, + int retval) +{ + if (op_handlers != NULL) { + wmOpHandlerData *opHandlers = WM_get_op_handlers(op_handlers, ot->idname); + if (opHandlers != NULL) { + ListBase *list = WM_op_handlers_get_handler_list(opHandlers, HANDLER_TYPE_MODAL_END); + WM_op_handlers_operator_exec(C, event, list, ot, NULL, retval); + } + } +} + + +/** \} */ diff --git a/source/blender/windowmanager/op_handlers/intern/wm_op_handlers_intern.h b/source/blender/windowmanager/op_handlers/intern/wm_op_handlers_intern.h new file mode 100644 index 00000000000..8532fa482b5 --- /dev/null +++ b/source/blender/windowmanager/op_handlers/intern/wm_op_handlers_intern.h @@ -0,0 +1,36 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup wm + */ + +#pragma once + +#include "../wm_op_handlers.h" + + +typedef struct wmHandlerData { + struct wmHandlerData *next, *prev; + char *id_name; // Pointer to wmOpHandlerData.id_name + void *py_handle; + void *py_data; + bool (*cb)(struct bContext *, const wmEvent *event, void *, PointerRNA *properties, int); // callback + int (*check)(void *, void*, void*); + bool (*poll)(struct bContext *, const wmEvent *event, void *, PointerRNA *properties); +} wmHandlerData; + + diff --git a/source/blender/windowmanager/op_handlers/wm_op_handlers.h b/source/blender/windowmanager/op_handlers/wm_op_handlers.h new file mode 100644 index 00000000000..24bf24c4566 --- /dev/null +++ b/source/blender/windowmanager/op_handlers/wm_op_handlers.h @@ -0,0 +1,101 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup wm + */ + +#pragma once + + +#include +#include "RNA_types.hh" + +#define HANDLER_TYPE_ALL 0 +#define HANDLER_TYPE_PRE_INVOKE 1 +#define HANDLER_TYPE_POST_INVOKE 2 +#define HANDLER_TYPE_MODAL 3 +#define HANDLER_TYPE_MODAL_END 4 + +#ifdef __cplusplus +extern "C" { +#endif + +struct wmOpHandlers { + /** Handlers in order of being added. */ + ListBase handlers; +}; + + +typedef struct wmOpHandlerData { + struct wmOpHandlerData *next, *prev; + char id_name[OP_MAX_TYPENAME]; + /** Handlers in order of being added. */ + ListBase pre_invoke; + ListBase post_invoke; + ListBase modal; + ListBase modal_end; +} wmOpHandlerData; + + +struct wmOpHandlers *WM_op_handlers_create(void); +void WM_op_handlers_destroy(struct wmOpHandlers *opHandlers); + +wmOpHandlerData *WM_get_op_handlers(struct wmOpHandlers *op_handlers, const char *op_name); + +void WM_op_handlers_append(struct wmOpHandlers *op_handlers, int id, + void *handle, + const char *op_name, + bool (*cb)(struct bContext *, const struct wmEvent *event, void *, PointerRNA *properties, int), + int (*check)(void *, void*, void *), + bool (*poll)(struct bContext *, const struct wmEvent *event, void *, PointerRNA *properties), + void *py_data); + +int WM_op_handlers_remove(struct wmOpHandlers *op_handlers, + int id, + const char *op_name, + void *cb, + void *owner); + + +bool WM_op_handlers_operator_pre_invoke(struct bContext *C, + const struct wmEvent *event, + struct wmOpHandlers *op_handlers, + struct wmOperatorType *ot, + PointerRNA *properties); +void WM_op_handlers_operator_post_invoke(struct bContext *C, + const struct wmEvent *event, + struct wmOpHandlers *op_handlers, + struct wmOperatorType *ot, + PointerRNA *properties, int retval); + + +bool WM_op_handlers_operator_modal(struct bContext *C, + const struct wmEvent *event, + struct wmOpHandlers *op_handlers, + struct wmOperatorType *ot, int retval); + + +void WM_op_handlers_operator_modal_end(struct bContext *C, + const struct wmEvent *event, + struct wmOpHandlers *op_handlers, + struct wmOperatorType *ot, + int retval); + + +#ifdef __cplusplus +} +#endif -- 2.30.2