diff --git a/kivy/_event.pxd b/kivy/_event.pxd index ab88d2986..9cc169f91 100644 --- a/kivy/_event.pxd +++ b/kivy/_event.pxd @@ -14,6 +14,7 @@ cdef class EventDispatcher(ObjectWithUid): cdef dict __properties cdef dict __storage cdef object __weakref__ + cdef public set _kwargs_applied_init cpdef dict properties(self) diff --git a/kivy/_event.pyx b/kivy/_event.pyx index 66f739cf2..78188ad2f 100644 --- a/kivy/_event.pyx +++ b/kivy/_event.pyx @@ -244,12 +244,13 @@ cdef class EventDispatcher(ObjectWithUid): def __init__(self, **kwargs): cdef basestring func, name, key cdef dict properties - cdef list prop_args + cdef dict prop_args # Auto bind on own handler if exist properties = self.properties() - prop_args = [ - (k, kwargs.pop(k)) for k in list(kwargs.keys()) if k in properties] + prop_args = { + k: kwargs.pop(k) for k in list(kwargs.keys()) if k in properties} + self._kwargs_applied_init = set(prop_args.keys()) if prop_args else set() super(EventDispatcher, self).__init__(**kwargs) __cls__ = self.__class__ @@ -268,7 +269,7 @@ cdef class EventDispatcher(ObjectWithUid): self.fbind(func[3:], getattr(self, func)) # Apply the existing arguments to our widget - for key, value in prop_args: + for key, value in prop_args.items(): setattr(self, key, value) def register_event_type(self, basestring event_type): diff --git a/kivy/lang.py b/kivy/lang.py index 8ae70702e..b25d7d1c6 100755 --- a/kivy/lang.py +++ b/kivy/lang.py @@ -685,6 +685,46 @@ and not by any previous styles that may have set `state_image`. is reached. This means that initially, previous rules could be used to set the property. +Order of kwargs and KV rule application +--------------------------------------- + +Properties can be initialized in KV as well as in python. For example, in KV: + +.. code-block:: kv + +: + text: 'Hello' + ramp: 45. + order: self.x + 10 + +Then `MyRule()` would initialize all three kivy properties to +the given KV values. Separately in python, if the properties already exist as +kivy properties one can do for example `MyRule(line='Bye', side=55)`. + +However, what will be the final values of the properties when +`MyRule(text='Bye', order=55)` is executed? The quick rule is that python +initialization is stronger than KV initialization only for constant rules. + +Specifically, the `kwargs` provided to the python initializer are always +applied first. So in the above example, `text` is set to +`'Bye'` and `order` is set to `55`. Then, all the KV rules are applied, except +those constant rules that overwrite a python initializer provided value. + +That is, the KV rules that do not creates bindings such as `text: 'Hello'` +and `ramp: 45.`, if a value for that property has been provided in python, then +that rule will not be applied. + +So in the `MyRule(text='Bye', order=55)` example, `text` will be `'Bye'`, +`ramp` will be `45.`, and `order`, which creates a binding, will first be set +to `55`, but then when KV rules are applied will end up being whatever +`self.x + 10` is. + +.. versionchanged:: 1.9.1 + + Before, KV rules always overwrote the python values, now, python values + are not overwritten by constant rules. + + Lang Directives --------------- @@ -1638,7 +1678,8 @@ def create_handler(iself, element, key, value, rule, idmap, delayed=False): idmap = copy(idmap) idmap.update(global_idmap) idmap['self'] = iself.proxy_ref - handler_append = _handlers[iself.uid][key].append + bound_list = _handlers[iself.uid][key] + handler_append = bound_list.append # we need a hash for when delayed, so we don't execute duplicate canvas # callbacks from the same handler during a sync op @@ -1698,7 +1739,7 @@ def create_handler(iself, element, key, value, rule, idmap, delayed=False): handler_append(bound) try: - return eval(value, idmap) + return eval(value, idmap), bound_list except Exception as e: tb = sys.exc_info()[2] raise BuilderException(rule.ctx, rule.line, @@ -1912,8 +1953,13 @@ class BuilderBase(object): self._apply_rule(widget, rule, rule, template_ctx=proxy_ctx) return widget - def apply(self, widget): + def apply(self, widget, ignored_consts=set()): '''Search all the rules that match the widget and apply them. + + `ignored_consts` is a set or list type whose elements are property + names for which constant KV rules (i.e. those that don't create + bindings) of that widget will not be applied. This allows e.g. skipping + constant rules that overwrite a value initialized in python. ''' rules = self.match(widget) if __debug__: @@ -1921,12 +1967,13 @@ class BuilderBase(object): if not rules: return for rule in rules: - self._apply_rule(widget, rule, rule) + self._apply_rule(widget, rule, rule, ignored_consts=ignored_consts) def _clear_matchcache(self): BuilderBase._match_cache = {} - def _apply_rule(self, widget, rule, rootrule, template_ctx=None): + def _apply_rule(self, widget, rule, rootrule, template_ctx=None, + ignored_consts=set()): # widget: the current instantiated widget # rule: the current rule # rootrule: the current root rule (for children of a rule) @@ -2063,9 +2110,16 @@ class BuilderBase(object): key = rule.name value = rule.co_value if type(value) is CodeType: - value = create_handler(widget_set, widget_set, key, - value, rule, rctx['ids']) - setattr(widget_set, key, value) + value, bound = create_handler( + widget_set, widget_set, key, value, rule, + rctx['ids']) + # if there's a rule + if bound or key not in ignored_consts: + setattr(widget_set, key, value) + else: + if key not in ignored_consts: + setattr(widget_set, key, value) + except Exception as e: if rule is not None: tb = sys.exc_info()[2] @@ -2261,7 +2315,7 @@ class BuilderBase(object): key = prule.name value = prule.co_value if type(value) is CodeType: - value = create_handler( + value, _ = create_handler( widget, instr.proxy_ref, key, value, prule, idmap, True) setattr(instr, key, value) diff --git a/kivy/tests/test_lang.py b/kivy/tests/test_lang.py index 89e0ad5fd..05caf618a 100644 --- a/kivy/tests/test_lang.py +++ b/kivy/tests/test_lang.py @@ -226,3 +226,27 @@ class LangTestCase(unittest.TestCase): self.assertTrue('on_press' in wid.binded_func) wid.binded_func['on_press']() self.assertEquals(wid.a, 1) + + def test_kv_python_init(self): + from kivy.lang import Builder + from kivy.uix.widget import Widget + + class MyObject(object): + value = 55 + + class MyWidget(Widget): + cheese = MyObject() + + Builder.load_string(''' +: + x: 55 + y: self.width + 10 + height: self.cheese.value + width: 44 +''') + + w = MyWidget(x=22, height=12, y=999) + self.assertEqual(w.x, 22) + self.assertEqual(w.width, 44) + self.assertEqual(w.y, 44 + 10) + self.assertEqual(w.height, 12) diff --git a/kivy/uix/widget.py b/kivy/uix/widget.py index c7bb83a12..2f4ee6fe0 100644 --- a/kivy/uix/widget.py +++ b/kivy/uix/widget.py @@ -317,13 +317,7 @@ class Widget(WidgetBase): # Apply all the styles. if not no_builder: - #current_root = Builder.idmap.get('root') - #Builder.idmap['root'] = self - Builder.apply(self) - #if current_root is not None: - # Builder.idmap['root'] = current_root - #else: - # Builder.idmap.pop('root') + Builder.apply(self, ignored_consts=self._kwargs_applied_init) # Bind all the events. if on_args: