Committing patch "[#27676] Change window size/resolution in realtime" by me.
[blender-staging.git] / source / blender / python / intern / bpy_app_handlers.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * Contributor(s): Campbell Barton
19  *
20  * ***** END GPL LICENSE BLOCK *****
21  */
22
23 /** \file blender/python/intern/bpy_app_handlers.c
24  *  \ingroup pythonintern
25  *
26  * This file defines a 'PyStructSequence' accessed via 'bpy.app.handlers',
27  * which exposes various lists that the script author can add callback
28  * functions into (called via blenders generic BLI_cb api)
29  */
30
31 #include <Python.h>
32 #include "BLI_utildefines.h"
33 #include "BLI_callbacks.h"
34
35 #include "RNA_types.h"
36 #include "RNA_access.h"
37 #include "bpy_rna.h"
38 #include "bpy_app_handlers.h"
39
40 void bpy_app_generic_callback(struct Main *main, struct ID *id, void *arg);
41
42 static PyTypeObject BlenderAppCbType;
43
44 static PyStructSequence_Field app_cb_info_fields[] = {
45         {(char *)"frame_change_pre",  (char *)"Callback list - on frame change for playback and rendering (before)"},
46         {(char *)"frame_change_post", (char *)"Callback list - on frame change for playback and rendering (after)"},
47         {(char *)"render_pre",        (char *)"Callback list - on render (before)"},
48         {(char *)"render_post",       (char *)"Callback list - on render (after)"},
49         {(char *)"render_stats",      (char *)"Callback list - on printing render statistics"},
50         {(char *)"load_pre",          (char *)"Callback list - on loading a new blend file (before)"},
51         {(char *)"load_post",         (char *)"Callback list - on loading a new blend file (after)"},
52         {(char *)"save_pre",          (char *)"Callback list - on saving a blend file (before)"},
53         {(char *)"save_post",         (char *)"Callback list - on saving a blend file (after)"},
54         {(char *)"scene_update_pre",  (char *)"Callback list - on updating the scenes data (before)"},
55         {(char *)"scene_update_post", (char *)"Callback list - on updating the scenes data (after)"},
56
57         /* sets the permanent tag */
58 #   define APP_CB_OTHER_FIELDS 1
59         {(char *)"persistent",        (char *)"Function decorator for callback functions not to be removed when loading new files"},
60
61         {NULL}
62 };
63
64 static PyStructSequence_Desc app_cb_info_desc = {
65         (char *)"bpy.app.handlers",     /* name */
66         (char *)"This module contains callbacks",    /* doc */
67         app_cb_info_fields,    /* fields */
68         (sizeof(app_cb_info_fields) / sizeof(PyStructSequence_Field)) - 1
69 };
70
71 /*
72 #if (BLI_CB_EVT_TOT != ((sizeof(app_cb_info_fields)/sizeof(PyStructSequence_Field))))
73 #  error "Callbacks are out of sync"
74 #endif
75 */
76
77 /* --------------------------------------------------------------------------*/
78 /* permanent tagging code */
79 #define PERMINENT_CB_ID "_bpy_persistent"
80
81 static PyObject *bpy_app_handlers_persistent_new(PyTypeObject *UNUSED(type), PyObject *args, PyObject *UNUSED(kwds))
82 {
83         PyObject *value;
84
85         if (!PyArg_ParseTuple(args, "O:bpy.app.handlers.persistent", &value))
86                 return NULL;
87
88         if (PyFunction_Check(value)) {
89                 PyObject **dict_ptr = _PyObject_GetDictPtr(value);
90                 if (dict_ptr == NULL) {
91                         PyErr_SetString(PyExc_ValueError,
92                                         "bpy.app.handlers.persistent wasn't able to "
93                                         "get the dictionary from the function passed");
94                         return NULL;
95                 }
96                 else {
97                         /* set id */
98                         if (*dict_ptr == NULL) {
99                                 *dict_ptr = PyDict_New();
100                         }
101
102                         PyDict_SetItemString(*dict_ptr, PERMINENT_CB_ID, Py_None);
103                 }
104
105                 Py_INCREF(value);
106                 return value;
107         }
108         else {
109                 PyErr_SetString(PyExc_ValueError,
110                                 "bpy.app.handlers.persistent expected a function");
111                 return NULL;
112         }
113 }
114
115 /* dummy type because decorators can't be PyCFunctions */
116 static PyTypeObject BPyPersistent_Type = {
117
118 #if defined(_MSC_VER) || defined(FREE_WINDOWS)
119     PyVarObject_HEAD_INIT(NULL, 0)
120 #else
121     PyVarObject_HEAD_INIT(&PyType_Type, 0)
122 #endif
123
124     "persistent",                               /* tp_name */
125     0,                                          /* tp_basicsize */
126     0,                                          /* tp_itemsize */
127     /* methods */
128     0,                                          /* tp_dealloc */
129     0,                                          /* tp_print */
130     0,                                          /* tp_getattr */
131     0,                                          /* tp_setattr */
132     0,                                          /* tp_reserved */
133     0,                                          /* tp_repr */
134     0,                                          /* tp_as_number */
135     0,                                          /* tp_as_sequence */
136     0,                                          /* tp_as_mapping */
137     0,                                          /* tp_hash */
138     0,                                          /* tp_call */
139     0,                                          /* tp_str */
140     0,                                          /* tp_getattro */
141     0,                                          /* tp_setattro */
142     0,                                          /* tp_as_buffer */
143     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
144         Py_TPFLAGS_BASETYPE,                    /* tp_flags */
145     0,                                          /* tp_doc */
146     0,                                          /* tp_traverse */
147     0,                                          /* tp_clear */
148     0,                                          /* tp_richcompare */
149     0,                                          /* tp_weaklistoffset */
150     0,                                          /* tp_iter */
151     0,                                          /* tp_iternext */
152     0,                                          /* tp_methods */
153     0,                                          /* tp_members */
154     0,                                          /* tp_getset */
155     0,                                          /* tp_base */
156     0,                                          /* tp_dict */
157     0,                                          /* tp_descr_get */
158     0,                                          /* tp_descr_set */
159     0,                                          /* tp_dictoffset */
160     0,                                          /* tp_init */
161     0,                                          /* tp_alloc */
162     bpy_app_handlers_persistent_new,            /* tp_new */
163     0,                                          /* tp_free */
164 };
165
166 static PyObject *py_cb_array[BLI_CB_EVT_TOT] = {NULL};
167
168 static PyObject *make_app_cb_info(void)
169 {
170         PyObject *app_cb_info;
171         int pos = 0;
172
173         app_cb_info = PyStructSequence_New(&BlenderAppCbType);
174         if (app_cb_info == NULL) {
175                 return NULL;
176         }
177
178         for (pos = 0; pos < BLI_CB_EVT_TOT; pos++) {
179                 if (app_cb_info_fields[pos].name == NULL) {
180                         Py_FatalError("invalid callback slots 1");
181                 }
182                 PyStructSequence_SET_ITEM(app_cb_info, pos, (py_cb_array[pos] = PyList_New(0)));
183         }
184         if (app_cb_info_fields[pos + APP_CB_OTHER_FIELDS].name != NULL) {
185                 Py_FatalError("invalid callback slots 2");
186         }
187
188         /* custom function */
189         PyStructSequence_SET_ITEM(app_cb_info, pos++, (PyObject *)&BPyPersistent_Type);
190
191         return app_cb_info;
192 }
193
194 PyObject *BPY_app_handlers_struct(void)
195 {
196         PyObject *ret;
197
198 #if defined(_MSC_VER) || defined(FREE_WINDOWS)
199         BPyPersistent_Type.ob_base.ob_base.ob_type = &PyType_Type;
200 #endif
201
202         if (PyType_Ready(&BPyPersistent_Type) < 0) {
203                 BLI_assert(!"error initializing 'bpy.app.handlers.persistent'");
204         }
205
206         PyStructSequence_InitType(&BlenderAppCbType, &app_cb_info_desc);
207
208         ret = make_app_cb_info();
209
210         /* prevent user from creating new instances */
211         BlenderAppCbType.tp_init = NULL;
212         BlenderAppCbType.tp_new = NULL;
213         BlenderAppCbType.tp_hash = (hashfunc)_Py_HashPointer; /* without this we can't do set(sys.modules) [#29635] */
214
215         /* assign the C callbacks */
216         if (ret) {
217                 static bCallbackFuncStore funcstore_array[BLI_CB_EVT_TOT] = {{NULL}};
218                 bCallbackFuncStore *funcstore;
219                 int pos = 0;
220
221                 for (pos = 0; pos < BLI_CB_EVT_TOT; pos++) {
222                         funcstore = &funcstore_array[pos];
223                         funcstore->func = bpy_app_generic_callback;
224                         funcstore->alloc = 0;
225                         funcstore->arg = SET_INT_IN_POINTER(pos);
226                         BLI_add_cb(funcstore, pos);
227                 }
228         }
229
230         return ret;
231 }
232
233 void BPY_app_handlers_reset(const short do_all)
234 {
235         int pos = 0;
236
237         if (do_all) {
238         for (pos = 0; pos < BLI_CB_EVT_TOT; pos++) {
239                         /* clear list */
240                         PyList_SetSlice(py_cb_array[pos], 0, PY_SSIZE_T_MAX, NULL);
241                 }
242         }
243         else {
244                 /* save string conversion thrashing */
245                 PyObject *perm_id_str = PyUnicode_FromString(PERMINENT_CB_ID);
246
247                 for (pos = 0; pos < BLI_CB_EVT_TOT; pos++) {
248                         /* clear only items without PERMINENT_CB_ID */
249                         PyObject *ls = py_cb_array[pos];
250                         Py_ssize_t i;
251
252                         PyObject *item;
253                         PyObject **dict_ptr;
254
255                         for (i = PyList_GET_SIZE(ls) - 1; i >= 0; i--) {
256
257                                 if ( (PyFunction_Check((item = PyList_GET_ITEM(ls, i)))) &&
258                                      (dict_ptr = _PyObject_GetDictPtr(item)) &&
259                                      (*dict_ptr) &&
260                                      (PyDict_GetItem(*dict_ptr, perm_id_str) != NULL))
261                                 {
262                                         /* keep */
263                                 }
264                                 else {
265                                         /* remove */
266                                         /* PySequence_DelItem(ls, i); */ /* more obvious buw slower */
267                                         PyList_SetSlice(ls, i, i + 1, NULL);
268                                 }
269                         }
270                 }
271
272                 Py_DECREF(perm_id_str);
273         }
274 }
275
276 /* the actual callback - not necessarily called from py */
277 void bpy_app_generic_callback(struct Main *UNUSED(main), struct ID *id, void *arg)
278 {
279         PyObject *cb_list = py_cb_array[GET_INT_FROM_POINTER(arg)];
280         Py_ssize_t cb_list_len;
281         if ((cb_list_len = PyList_GET_SIZE(cb_list)) > 0) {
282                 PyGILState_STATE gilstate = PyGILState_Ensure();
283
284                 PyObject* args = PyTuple_New(1); // save python creating each call
285                 PyObject* func;
286                 PyObject* ret;
287                 Py_ssize_t pos;
288
289                 /* setup arguments */
290                 if (id) {
291                         PointerRNA id_ptr;
292                         RNA_id_pointer_create(id, &id_ptr);
293                         PyTuple_SET_ITEM(args, 0, pyrna_struct_CreatePyObject(&id_ptr));
294                 }
295                 else {
296                         PyTuple_SET_ITEM(args, 0, Py_None);
297                         Py_INCREF(Py_None);
298                 }
299
300                 // Iterate the list and run the callbacks
301                 for (pos = 0; pos < cb_list_len; pos++) {
302                         func = PyList_GET_ITEM(cb_list, pos);
303                         ret = PyObject_Call(func, args, NULL);
304                         if (ret == NULL) {
305                                 PyErr_Print();
306                                 PyErr_Clear();
307                         }
308                         else {
309                                 Py_DECREF(ret);
310                         }
311                 }
312
313                 Py_DECREF(args);
314
315                 PyGILState_Release(gilstate);
316         }
317 }