fix to allow [#24009] to be fixed.

WM_operator_poll() could fail in cases WM_operator_name_call() would succeed because calling the operator would setup the context before calling poll.
this would result in python raising an invalid error or menu items being greyed out.

now python can also check with an operator context:
  bpy.ops.object.editmode_toggle.poll('INVOKE_SCREEN')
This commit is contained in:
Campbell Barton 2010-11-04 12:59:03 +00:00
parent 0e81723683
commit 64ff9d6de4
6 changed files with 64 additions and 39 deletions

@ -115,14 +115,34 @@ class bpy_ops_submodule_op(object):
def _get_doc(self):
return op_as_string(self.idname())
@staticmethod
def _parse_args(args):
C_dict = None
C_exec = 'EXEC_DEFAULT'
if len(args) == 0:
pass
elif len(args) == 1:
if type(args[0]) != str:
C_dict = args[0]
else:
C_exec = args[0]
elif len(args) == 2:
C_exec, C_dict = args
else:
raise ValueError("1 or 2 args execution context is supported")
return C_dict, C_exec
__doc__ = property(_get_doc)
def __init__(self, module, func):
self.module = module
self.func = func
def poll(self, context=None):
return op_poll(self.idname_py(), context)
def poll(self, *args):
C_dict, C_exec = __class__._parse_args(args)
return op_poll(self.idname_py(), C_dict, C_exec)
def idname(self):
# submod.foo -> SUBMOD_OT_foo
@ -135,31 +155,11 @@ class bpy_ops_submodule_op(object):
def __call__(self, *args, **kw):
# Get the operator from blender
if len(args) > 2:
raise ValueError("1 or 2 args execution context is supported")
C_dict = None
if args:
C_exec = 'EXEC_DEFAULT'
if len(args) == 2:
C_exec = args[0]
C_dict = args[1]
else:
if type(args[0]) != str:
C_dict = args[0]
else:
C_exec = args[0]
if len(args) == 2:
C_dict = args[1]
C_dict, C_exec = __class__._parse_args(args)
ret = op_call(self.idname_py(), C_dict, kw, C_exec)
else:
ret = op_call(self.idname_py(), C_dict, kw)
ret = op_call(self.idname_py(), None, kw)
if 'FINISHED' in ret:
import bpy

@ -629,7 +629,7 @@ void uiEndBlock(const bContext *C, uiBlock *block)
if(but->context)
CTX_store_set((bContext*)C, but->context);
if(ot == NULL || WM_operator_poll((bContext*)C, ot)==0) {
if(ot == NULL || WM_operator_poll_context((bContext*)C, ot, but->opcontext)==0) {
but->flag |= UI_BUT_DISABLED;
but->lock = 1;
}

@ -434,7 +434,7 @@ ARegion *ui_tooltip_create(bContext *C, ARegion *butregion, uiBut *but)
if(but->flag & UI_BUT_DISABLED) {
const char *poll_msg;
CTX_wm_operator_poll_msg_set(C, NULL);
WM_operator_poll(C, but->optype);
WM_operator_poll_context(C, but->optype, but->opcontext);
poll_msg= CTX_wm_operator_poll_msg_get(C);
if(poll_msg) {
BLI_snprintf(data->lines[data->totline], sizeof(data->lines[0]), "Disabled: %s", poll_msg);

@ -46,12 +46,15 @@ static PyObject *pyop_poll(PyObject *UNUSED(self), PyObject *args)
char *opname;
PyObject *context_dict= NULL; /* optional args */
PyObject *context_dict_back;
char *context_str= NULL;
PyObject *ret;
int context= WM_OP_EXEC_DEFAULT;
// XXX Todo, work out a better solution for passing on context, could make a tuple from self and pack the name and Context into it...
bContext *C = BPy_GetContext();
if (!PyArg_ParseTuple(args, "s|O:_bpy.ops.poll", &opname, &context_dict))
if (!PyArg_ParseTuple(args, "s|Os:_bpy.ops.poll", &opname, &context_dict, &context_str))
return NULL;
ot= WM_operatortype_find(opname, TRUE);
@ -61,6 +64,15 @@ static PyObject *pyop_poll(PyObject *UNUSED(self), PyObject *args)
return NULL;
}
if(context_str) {
if(RNA_enum_value_from_id(operator_context_items, context_str, &context)==0) {
char *enum_str= BPy_enum_as_string(operator_context_items);
PyErr_Format(PyExc_TypeError, "Calling operator \"bpy.ops.%s.poll\" error, expected a string enum in (%.200s)", opname, enum_str);
MEM_freeN(enum_str);
return NULL;
}
}
if(!PyDict_Check(context_dict))
context_dict= NULL;
@ -70,7 +82,7 @@ static PyObject *pyop_poll(PyObject *UNUSED(self), PyObject *args)
Py_XINCREF(context_dict); /* so we done loose it */
/* main purpose of thsi function */
ret= WM_operator_poll((bContext*)C, ot) ? Py_True : Py_False;
ret= WM_operator_poll_context((bContext*)C, ot, context) ? Py_True : Py_False;
/* restore with original context dict, probably NULL but need this for nested operator calls */
Py_XDECREF(context_dict);
@ -126,7 +138,7 @@ static PyObject *pyop_call(PyObject *UNUSED(self), PyObject *args)
CTX_py_dict_set(C, (void *)context_dict);
Py_XINCREF(context_dict); /* so we done loose it */
if(WM_operator_poll((bContext*)C, ot) == FALSE) {
if(WM_operator_poll_context((bContext*)C, ot, context) == FALSE) {
const char *msg= CTX_wm_operator_poll_msg_get(C);
PyErr_Format( PyExc_SystemError, "Operator bpy.ops.%.200s.poll() %s", opname, msg ? msg : "failed, context is incorrect");
CTX_wm_operator_poll_msg_set(C, NULL); /* better set to NULL else it could be used again */

@ -218,6 +218,7 @@ struct wmOperatorTypeMacro *WM_operatortype_macro_define(struct wmOperatorType *
int WM_operator_poll (struct bContext *C, struct wmOperatorType *ot);
int WM_operator_poll_context(struct bContext *C, struct wmOperatorType *ot, int context);
int WM_operator_call (struct bContext *C, struct wmOperator *op);
int WM_operator_repeat (struct bContext *C, struct wmOperator *op);
int WM_operator_name_call (struct bContext *C, const char *opstring, int context, struct PointerRNA *properties);

@ -72,6 +72,8 @@
#include "wm_event_types.h"
#include "wm_draw.h"
static int wm_operator_call_internal(bContext *C, wmOperatorType *ot, PointerRNA *properties, ReportList *reports, short context, short poll_only);
/* ************ event management ************** */
void wm_event_add(wmWindow *win, wmEvent *event_to_add)
@ -379,6 +381,12 @@ int WM_operator_poll(bContext *C, wmOperatorType *ot)
return 1;
}
/* sets up the new context and calls 'wm_operator_invoke()' with poll_only */
int WM_operator_poll_context(bContext *C, wmOperatorType *ot, int context)
{
return wm_operator_call_internal(C, ot, NULL, NULL, context, TRUE);
}
static void wm_operator_print(wmOperator *op)
{
char *buf = WM_operator_pystring(NULL, op->type, op->ptr, 1);
@ -603,11 +611,15 @@ static void wm_region_mouse_co(bContext *C, wmEvent *event)
}
}
int wm_operator_invoke(bContext *C, wmOperatorType *ot, wmEvent *event, PointerRNA *properties, ReportList *reports)
int wm_operator_invoke(bContext *C, wmOperatorType *ot, wmEvent *event, PointerRNA *properties, ReportList *reports, short poll_only)
{
wmWindowManager *wm= CTX_wm_manager(C);
int retval= OPERATOR_PASS_THROUGH;
/* this is done because complicated setup is done to call this function that is better not duplicated */
if(poll_only)
return WM_operator_poll(C, ot);
if(WM_operator_poll(C, ot)) {
wmOperator *op= wm_operator_create(wm, ot, properties, reports); /* if reports==NULL, theyll be initialized */
@ -693,7 +705,7 @@ int wm_operator_invoke(bContext *C, wmOperatorType *ot, wmEvent *event, PointerR
* this is for python to access since its done the operator lookup
*
* invokes operator in context */
static int wm_operator_call_internal(bContext *C, wmOperatorType *ot, int context, PointerRNA *properties, ReportList *reports)
static int wm_operator_call_internal(bContext *C, wmOperatorType *ot, PointerRNA *properties, ReportList *reports, short context, short poll_only)
{
wmWindow *window= CTX_wm_window(C);
wmEvent *event;
@ -758,7 +770,7 @@ static int wm_operator_call_internal(bContext *C, wmOperatorType *ot, int contex
CTX_wm_region_set(C, ar1);
}
retval= wm_operator_invoke(C, ot, event, properties, reports);
retval= wm_operator_invoke(C, ot, event, properties, reports, poll_only);
/* set region back */
CTX_wm_region_set(C, ar);
@ -772,7 +784,7 @@ static int wm_operator_call_internal(bContext *C, wmOperatorType *ot, int contex
ARegion *ar= CTX_wm_region(C);
CTX_wm_region_set(C, NULL);
retval= wm_operator_invoke(C, ot, event, properties, reports);
retval= wm_operator_invoke(C, ot, event, properties, reports, poll_only);
CTX_wm_region_set(C, ar);
return retval;
@ -786,7 +798,7 @@ static int wm_operator_call_internal(bContext *C, wmOperatorType *ot, int contex
CTX_wm_region_set(C, NULL);
CTX_wm_area_set(C, NULL);
retval= wm_operator_invoke(C, ot, event, properties, reports);
retval= wm_operator_invoke(C, ot, event, properties, reports, poll_only);
CTX_wm_region_set(C, ar);
CTX_wm_area_set(C, area);
@ -794,7 +806,7 @@ static int wm_operator_call_internal(bContext *C, wmOperatorType *ot, int contex
}
case WM_OP_EXEC_DEFAULT:
case WM_OP_INVOKE_DEFAULT:
return wm_operator_invoke(C, ot, event, properties, reports);
return wm_operator_invoke(C, ot, event, properties, reports, poll_only);
}
}
@ -807,7 +819,7 @@ int WM_operator_name_call(bContext *C, const char *opstring, int context, Pointe
{
wmOperatorType *ot= WM_operatortype_find(opstring, 0);
if(ot)
return wm_operator_call_internal(C, ot, context, properties, NULL);
return wm_operator_call_internal(C, ot, properties, NULL, context, FALSE);
return 0;
}
@ -839,7 +851,7 @@ int WM_operator_call_py(bContext *C, wmOperatorType *ot, int context, PointerRNA
printf("error \"%s\" operator has no exec function, python cannot call it\n", op->type->name);
#endif
retval= wm_operator_call_internal(C, ot, context, properties, reports);
retval= wm_operator_call_internal(C, ot, properties, reports, context, FALSE);
/* keep the reports around if needed later */
if ( (retval & OPERATOR_RUNNING_MODAL) ||
@ -1168,7 +1180,7 @@ static int wm_handler_operator_call(bContext *C, ListBase *handlers, wmEventHand
wmOperatorType *ot= WM_operatortype_find(event->keymap_idname, 0);
if(ot)
retval= wm_operator_invoke(C, ot, event, properties, NULL);
retval= wm_operator_invoke(C, ot, event, properties, NULL, FALSE);
}
/* Finished and pass through flag as handled */
@ -1421,7 +1433,7 @@ static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers)
if(drop->poll(C, drag, event)) {
drop->copy(drag, drop);
wm_operator_invoke(C, drop->ot, event, drop->ptr, NULL);
wm_operator_invoke(C, drop->ot, event, drop->ptr, NULL, FALSE);
action |= WM_HANDLER_BREAK;
}
}