Blender  V3.3
bpy_rna_context.c
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
9 #include <Python.h>
10 
11 #include "BLI_listbase.h"
12 #include "BLI_utildefines.h"
13 
14 #include "BKE_context.h"
15 
16 #include "WM_api.h"
17 #include "WM_types.h"
18 
19 #include "bpy_rna_context.h"
20 
21 #include "RNA_access.h"
22 #include "RNA_prototypes.h"
23 
24 #include "bpy_rna.h"
25 
26 /* -------------------------------------------------------------------- */
30 typedef struct ContextStore {
32  bool win_is_set;
38 
39 typedef struct BPyContextTempOverride {
40  PyObject_HEAD /* Required Python macro. */
42 
57 
59 {
60  PyObject_DEL(self);
61 }
62 
64 {
65  bContext *C = self->context;
66 
67  CTX_py_state_push(C, &self->py_state, self->py_state_context_dict);
68 
69  self->ctx_init.win = CTX_wm_window(C);
70  self->ctx_init.win_is_set = (self->ctx_init.win != self->ctx_temp.win);
71  self->ctx_init.area = CTX_wm_area(C);
72  self->ctx_init.area_is_set = (self->ctx_init.area != self->ctx_temp.area);
73  self->ctx_init.region = CTX_wm_region(C);
74  self->ctx_init.region_is_set = (self->ctx_init.region != self->ctx_temp.region);
75 
76  wmWindow *win = self->ctx_temp.win_is_set ? self->ctx_temp.win : self->ctx_init.win;
77  bScreen *screen = win ? WM_window_get_active_screen(win) : NULL;
78  ScrArea *area = self->ctx_temp.area_is_set ? self->ctx_temp.area : self->ctx_init.area;
79  ARegion *region = self->ctx_temp.region_is_set ? self->ctx_temp.region : self->ctx_init.region;
80 
81  /* Sanity check, the region is in the screen/area. */
82  if (self->ctx_temp.region_is_set && (region != NULL)) {
83  if (area == NULL) {
84  PyErr_SetString(PyExc_TypeError, "Region set with NULL area");
85  return NULL;
86  }
87  if ((screen && BLI_findindex(&screen->regionbase, region) == -1) &&
88  (BLI_findindex(&area->regionbase, region) == -1)) {
89  PyErr_SetString(PyExc_TypeError, "Region not found in area");
90  return NULL;
91  }
92  }
93 
94  if (self->ctx_temp.area_is_set && (area != NULL)) {
95  if (screen == NULL) {
96  PyErr_SetString(PyExc_TypeError, "Area set with NULL screen");
97  return NULL;
98  }
99  if (BLI_findindex(&screen->areabase, area) == -1) {
100  PyErr_SetString(PyExc_TypeError, "Area not found in screen");
101  return NULL;
102  }
103  }
104 
105  if (self->ctx_temp.win_is_set) {
106  CTX_wm_window_set(C, self->ctx_temp.win);
107  }
108  if (self->ctx_temp.area_is_set) {
109  CTX_wm_area_set(C, self->ctx_temp.area);
110  }
111  if (self->ctx_temp.region_is_set) {
112  CTX_wm_region_set(C, self->ctx_temp.region);
113  }
114 
115  Py_RETURN_NONE;
116 }
117 
119  PyObject *UNUSED(args))
120 {
121  bContext *C = self->context;
122 
123  /* Special case where the window is expected to be freed on file-read,
124  * in this case the window should not be restored, see: T92818. */
125  bool do_restore = true;
126  if (self->ctx_init.win) {
128  if (BLI_findindex(&wm->windows, self->ctx_init.win) == -1) {
130  do_restore = false;
131  }
132  }
133 
134  if (do_restore) {
135  if (self->ctx_init.win_is_set) {
136  CTX_wm_window_set(C, self->ctx_init.win);
137  }
138  if (self->ctx_init.area_is_set) {
139  CTX_wm_area_set(C, self->ctx_init.area);
140  }
141  if (self->ctx_init.region_is_set) {
142  CTX_wm_region_set(C, self->ctx_init.region);
143  }
144  }
145 
146  /* A copy may have been made when writing context members, see #BPY_context_dict_clear_members */
147  PyObject *context_dict_test = CTX_py_dict_get(C);
148  if (context_dict_test && (context_dict_test != self->py_state_context_dict)) {
149  Py_DECREF(context_dict_test);
150  }
151  CTX_py_state_pop(C, &self->py_state);
152  Py_CLEAR(self->py_state_context_dict);
153 
154  Py_RETURN_NONE;
155 }
156 
158  {"__enter__", (PyCFunction)bpy_rna_context_temp_override_enter, METH_NOARGS},
159  {"__exit__", (PyCFunction)bpy_rna_context_temp_override_exit, METH_VARARGS},
160  {NULL},
161 };
162 
163 static PyTypeObject BPyContextTempOverride_Type = {
164  PyVarObject_HEAD_INIT(NULL, 0).tp_name = "ContextTempOverride",
165  .tp_basicsize = sizeof(BPyContextTempOverride),
166  .tp_dealloc = (destructor)bpy_rna_context_temp_override__tp_dealloc,
167  .tp_flags = Py_TPFLAGS_DEFAULT,
169 };
170 
173 /* -------------------------------------------------------------------- */
177 static PyObject *bpy_context_temp_override_extract_known_args(const char *const *kwds_static,
178  PyObject *kwds)
179 {
180  PyObject *sentinel = Py_Ellipsis;
181  PyObject *kwds_parse = PyDict_New();
182  for (int i = 0; kwds_static[i]; i++) {
183  PyObject *key = PyUnicode_FromString(kwds_static[i]);
184  PyObject *val = _PyDict_Pop(kwds, key, sentinel);
185  if (val != sentinel) {
186  if (PyDict_SetItem(kwds_parse, key, val) == -1) {
188  }
189  }
190  Py_DECREF(key);
191  Py_DECREF(val);
192  }
193  return kwds_parse;
194 }
195 
196 PyDoc_STRVAR(bpy_context_temp_override_doc,
197  ".. method:: temp_override(window, area, region, **keywords)\n"
198  "\n"
199  " Context manager to temporarily override members in the context.\n"
200  "\n"
201  " :arg window: Window override or None.\n"
202  " :type window: :class:`bpy.types.Window`\n"
203  " :arg area: Area override or None.\n"
204  " :type area: :class:`bpy.types.Area`\n"
205  " :arg region: Region override or None.\n"
206  " :type region: :class:`bpy.types.Region`\n"
207  " :arg keywords: Additional keywords override context members.\n"
208  " :return: The context manager .\n"
209  " :rtype: context manager\n");
210 static PyObject *bpy_context_temp_override(PyObject *self, PyObject *args, PyObject *kwds)
211 {
212  const PointerRNA *context_ptr = pyrna_struct_as_ptr(self, &RNA_Context);
213  if (context_ptr == NULL) {
214  return NULL;
215  }
216 
217  if (kwds == NULL) {
218  /* While this is effectively NOP, support having no keywords as it's more involved
219  * to return an alternative (dummy) context manager. */
220  }
221  else {
222  /* Needed because the keywords copied into `kwds_parse` could contain anything.
223  * As the types of keys aren't checked. */
224  if (!PyArg_ValidateKeywordArguments(kwds)) {
225  return NULL;
226  }
227  }
228 
229  struct {
230  struct BPy_StructRNA_Parse window;
231  struct BPy_StructRNA_Parse area;
232  struct BPy_StructRNA_Parse region;
233  } params = {
234  .window = {.type = &RNA_Window},
235  .area = {.type = &RNA_Area},
236  .region = {.type = &RNA_Region},
237  };
238 
239  static const char *const _keywords[] = {"window", "area", "region", NULL};
240  static _PyArg_Parser _parser = {
241  "|$" /* Optional, keyword only arguments. */
242  "O&" /* `window` */
243  "O&" /* `area` */
244  "O&" /* `region` */
245  ":temp_override",
246  _keywords,
247  0,
248  };
249  /* Parse known keywords, the remaining keywords are set using #CTX_py_state_push. */
250  kwds = kwds ? PyDict_Copy(kwds) : PyDict_New();
251  {
252  PyObject *kwds_parse = bpy_context_temp_override_extract_known_args(_keywords, kwds);
253  const int parse_result = _PyArg_ParseTupleAndKeywordsFast(args,
254  kwds_parse,
255  &_parser,
257  &params.window,
259  &params.area,
261  &params.region);
262  Py_DECREF(kwds_parse);
263  if (parse_result == -1) {
264  Py_DECREF(kwds);
265  return NULL;
266  }
267  }
268 
269  bContext *C = context_ptr->data;
270  {
271  /* Merge existing keys that don't exist in the keywords passed in.
272  * This makes it possible to nest context overrides. */
273  PyObject *context_dict_current = CTX_py_dict_get(C);
274  if (context_dict_current != NULL) {
275  PyDict_Merge(kwds, context_dict_current, 0);
276  }
277  }
278 
279  ContextStore ctx_temp = {NULL};
280  if (params.window.ptr != NULL) {
281  ctx_temp.win = params.window.ptr->data;
282  ctx_temp.win_is_set = true;
283  }
284  if (params.area.ptr != NULL) {
285  ctx_temp.area = params.area.ptr->data;
286  ctx_temp.area_is_set = true;
287  }
288 
289  if (params.region.ptr != NULL) {
290  ctx_temp.region = params.region.ptr->data;
291  ctx_temp.region_is_set = true;
292  }
293 
295  ret->context = C;
296  ret->ctx_temp = ctx_temp;
297  memset(&ret->ctx_init, 0, sizeof(ret->ctx_init));
298 
299  ret->py_state_context_dict = kwds;
300 
301  return (PyObject *)ret;
302 }
303 
307  "temp_override",
308  (PyCFunction)bpy_context_temp_override,
309  METH_VARARGS | METH_KEYWORDS,
310  bpy_context_temp_override_doc,
311 };
312 
314 {
315  if (PyType_Ready(&BPyContextTempOverride_Type) < 0) {
317  return;
318  }
319 }
struct ScrArea * CTX_wm_area(const bContext *C)
Definition: context.c:738
void CTX_wm_region_set(bContext *C, struct ARegion *region)
Definition: context.c:1009
void CTX_py_state_push(bContext *C, struct bContext_PyState *pystate, void *value)
Definition: context.c:251
void * CTX_py_dict_get(const bContext *C)
Definition: context.c:242
struct wmWindowManager * CTX_wm_manager(const bContext *C)
Definition: context.c:713
void CTX_py_state_pop(bContext *C, struct bContext_PyState *pystate)
Definition: context.c:259
void CTX_wm_window_set(bContext *C, struct wmWindow *win)
Definition: context.c:966
struct ARegion * CTX_wm_region(const bContext *C)
Definition: context.c:749
void CTX_wm_area_set(bContext *C, struct ScrArea *area)
Definition: context.c:997
struct wmWindow * CTX_wm_window(const bContext *C)
Definition: context.c:723
#define BLI_assert_unreachable()
Definition: BLI_assert.h:93
int BLI_findindex(const struct ListBase *listbase, const void *vlink) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
#define UNUSED(x)
#define C
Definition: RandGen.cpp:25
PyObject * self
Definition: bpy_driver.c:165
int pyrna_struct_as_ptr_or_null_parse(PyObject *o, void *p)
Definition: bpy_rna.c:7993
const PointerRNA * pyrna_struct_as_ptr(PyObject *py_obj, const StructRNA *srna)
Definition: bpy_rna.c:7960
static PyTypeObject BPyContextTempOverride_Type
static PyObject * bpy_rna_context_temp_override_enter(BPyContextTempOverride *self)
static PyObject * bpy_context_temp_override_extract_known_args(const char *const *kwds_static, PyObject *kwds)
struct BPyContextTempOverride BPyContextTempOverride
struct ContextStore ContextStore
static PyMethodDef bpy_rna_context_temp_override__tp_methods[]
static PyObject * bpy_context_temp_override(PyObject *self, PyObject *args, PyObject *kwds)
static PyObject * bpy_rna_context_temp_override_exit(BPyContextTempOverride *self, PyObject *UNUSED(args))
void bpy_rna_context_types_init(void)
PyMethodDef BPY_rna_context_temp_override_method_def
PyDoc_STRVAR(bpy_context_temp_override_doc, ".. method:: temp_override(window, area, region, **keywords)\n" "\n" " Context manager to temporarily override members in the context.\n" "\n" " :arg window: Window override or None.\n" " :type window: :class:`bpy.types.Window`\n" " :arg area: Area override or None.\n" " :type area: :class:`bpy.types.Area`\n" " :arg region: Region override or None.\n" " :type region: :class:`bpy.types.Region`\n" " :arg keywords: Additional keywords override context members.\n" " :return: The context manager .\n" " :rtype: context manager\n")
static void bpy_rna_context_temp_override__tp_dealloc(BPyContextTempOverride *self)
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
static void area(int d1, int d2, int e1, int e2, float weights[2])
return ret
PyObject_HEAD bContext * context
struct bContext_PyState py_state
PyObject * py_state_context_dict
wmWindow * win
ARegion * region
ScrArea * area
void * data
Definition: RNA_types.h:38
ListBase regionbase
ListBase areabase
bScreen * WM_window_get_active_screen(const wmWindow *win)
Definition: wm_window.c:2300