Merge pull request #3736 from matham/kv-overwrite

Make widget kwargs passing higher priority than kv
This commit is contained in:
Akshay Arora 2015-11-21 02:28:13 +05:30
commit 1dc1a4a64d
5 changed files with 94 additions and 20 deletions

View File

@ -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)

View File

@ -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):

View File

@ -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
<MyRule@Widget>:
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)

View File

@ -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('''
<MyWidget>:
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)

View File

@ -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: