Mercurial > MadButterfly
changeset 1345:e0400a2b7c35
Use trait instead of mixin for component_manager
author | Thinker K.F. Li <thinker@codemud.net> |
---|---|
date | Fri, 11 Feb 2011 15:10:37 +0800 |
parents | 8f1f8ef5c9ea |
children | 4fdc998d3dc3 |
files | pyink/domview.py pyink/trait.py |
diffstat | 2 files changed, 351 insertions(+), 50 deletions(-) [+] |
line wrap: on
line diff
--- a/pyink/domview.py Wed Feb 09 07:07:08 2011 +0800 +++ b/pyink/domview.py Fri Feb 11 15:10:37 2011 +0800 @@ -1,6 +1,7 @@ import random import dom_event from tween import TweenObject +from trait import trait, require, composite ## \brief Compare two nodes with ID. @@ -48,8 +49,8 @@ # # \param comp_node is None for main component. # - def __init__(self, domview, comp_node): - self._domview = domview + def __init__(self, comp_mgr, comp_node): + self._comp_mgr = comp_mgr self.node = comp_node self.layers = [] self.timelines = [] @@ -66,11 +67,11 @@ break pass else: # no any ns0:scenes - doc = self._domview._doc + doc = self._comp_mgr._doc scenes_node = doc.createElement('ns0:scenes') scenes_node.setAttribute('name', 'default') - node_id = self._domview.new_id() + node_id = self._comp_mgr.new_id() scenes_node.setAttribute('id', node_id) comp_node.appendChild(scenes_node) @@ -126,12 +127,12 @@ raise ValueError, \ 'try add a timeline with duplicated name - %s' % (name) - doc = self._domview._doc + doc = self._comp_mgr._doc comp_node = self.node scenes_node = doc.createElement('ns0:scenes') scenes_node.setAttribute('name', name) - node_id = self._domview.new_id() + node_id = self._comp_mgr.new_id() scenes_node.setAttribute('id', node_id) comp_node.appendChild(scenes_node) @@ -219,7 +220,7 @@ pass -## \brief A mix-in for class domview for management of components. +## \brief A trait for class domview for managing components. # # This class is responsible for manage components and timelines. It # is also responsible for switching component and timeline. Switching @@ -235,8 +236,20 @@ # special case with slightly different in structure. It should be # removed and normalized to normal components. # +@trait class component_manager(component_manager_ui_update): - def __init__(self, domview): + _layers = require + _scenes_node = require + _metadata_node = require + _doc = require + _root = require + _layers = require + _layers_parent = require + new_id = require + get_node = require + reset = require + + def __init__(self): super(component_manager, self).__init__() self._components_node = None self._components = [] @@ -245,14 +258,12 @@ self._cur_comp = None self._cur_timeline = None self._components_group = None - - self._domview = domview pass def _set_main_component(self): - comp = Component(self._domview, None) - comp.layers = self._domview._layers - scenes_node = self._domview._scenes_node + comp = Component(self, None) + comp.layers = self._layers + scenes_node = self._scenes_node timeline = Timeline(scenes_node) comp.timelines = [timeline] @@ -272,7 +283,7 @@ if child_name in comp_names: raise ValueError, 'duplicate component name %s' % (child_name) - comp = Component(self._domview, child) + comp = Component(self, child) comp.parse_timelines() self._components.append(comp) @@ -285,7 +296,7 @@ # This method is called by domview._init_metadata(). # def _component_manager_init_metadata(self): - metadata_node = self._domview._metadata_node + metadata_node = self._metadata_node # Make sure ns0:components in metadata for n in metadata_node.childList(): @@ -295,13 +306,13 @@ pass else: components_node = \ - self._domview._doc.createElement("ns0:components") + self._doc.createElement("ns0:components") metadata_node.appendChild(components_node) self._components_node = components_node pass # Make sure the group for containing components. - for n in self._domview._root.childList(): + for n in self._root.childList(): if n.name() != 'svg:g': continue try: @@ -313,12 +324,12 @@ break pass else: # no components group - components_group = self._domview._doc.createElement('svg:g') + components_group = self._doc.createElement('svg:g') components_group.setAttribute('inkscape:label', 'components') - gid = self._domview.new_id() + gid = self.new_id() components_group.setAttribute('id', gid) - self._domview._root.appendChild(components_group) + self._root.appendChild(components_group) self._components_group = components_group pass pass @@ -331,7 +342,7 @@ self._cur_comp = self._main_comp tl = self._main_comp.get_timeline('default') self._cur_timeline = tl - self._domview._scenes_node = tl.scenes_node + self._scenes_node = tl.scenes_node pass ## \brief Create component group @@ -340,16 +351,16 @@ # The layers group is where layer groups is in. # def _create_component_group(self): - doc = self._domview._doc + doc = self._doc group = doc.createElement('svg:g') - gid = self._domview.new_id() + gid = self.new_id() group.setAttribute('id', gid) self._components_group.appendChild(group) # Create layers group layers_group = doc.createElement('svg:g') - gid = self._domview.new_id() + gid = self.new_id() layers_group.setAttribute('id', gid) layers_group.setAttribute('inkscape:label', 'layers') group.appendChild(layers_group) @@ -363,8 +374,8 @@ # \return a ns0:component node. # def _create_component_node(self, comp_name, comp_group_id): - comp_node = self._domview._doc.createElement('ns0:component') - comp_id = self._domview.new_id() + comp_node = self._doc.createElement('ns0:component') + comp_id = self.new_id() comp_node.setAttribute('id', comp_id) comp_node.setAttribute('name', comp_name) comp_node.setAttribute('ref', comp_group_id) @@ -385,8 +396,8 @@ ## \brief Create a layer group for give layer of a component. # def _create_comp_layer_group(self, layers_group, layer_name): - doc = self._domview._doc - gid = self._domview.new_id() + doc = self._doc + gid = self.new_id() layer_group = doc.createElement('svg:g') layer_group.setAttribute('id', gid) @@ -408,7 +419,7 @@ def _get_layers_group_of_component(self, comp_name): if comp_name == 'main': - return self._domview._root + return self._root comp_group = self.get_component_group(comp_name) layers_group = comp_group.firstChild() @@ -453,10 +464,10 @@ comp = self._get_component(comp_name) self._cur_comp = comp - self._domview._layers = comp.layers + self._layers = comp.layers comp_name = self._cur_comp.name() # for domview - self._domview._layers_parent = \ + self._layers_parent = \ self._get_layers_group_of_component(comp_name) first_name = comp.all_timeline_names()[0] @@ -475,7 +486,7 @@ comp_group_id = comp_group.getAttribute('id') comp_node = self._create_component_node(comp_name, comp_group_id) - comp = Component(self._domview, comp_node) + comp = Component(self, comp_node) comp.parse_timelines() self._components.append(comp) @@ -491,7 +502,7 @@ pass def add_component_node(self, comp_node): - comp = Component(self._domview, comp_node) + comp = Component(self, comp_node) comp_name = comp.name() if self.has_component(comp_name): raise ValueError, \ @@ -523,11 +534,11 @@ comp_name = comp.name() if comp_name == 'main': - return self._domview._root + return self._root comp_node = comp.node gid = comp_node.getAttribute('ref') - comp_group = self._domview.get_node(gid) + comp_group = self.get_node(gid) return comp_group def get_current_component(self): @@ -544,7 +555,7 @@ if child.name() != 'ns0:scene': continue gid = child.getAttribute('ref') - group = self._domview.get_node(gid) + group = self.get_node(gid) group.setAttribute('style', 'display: none') pass pass @@ -556,10 +567,10 @@ tl = self._cur_comp.get_timeline(timeline_name) self._cur_timeline = tl - self._domview._scenes_node = tl.scenes_node # of class domview + self._scenes_node = tl.scenes_node # of class domview # Make domview to rescan layers and scenes. - self._domview.reset() # from domview + self.reset() # from domview pass def add_timeline(self, timeline_name): @@ -614,10 +625,10 @@ def link_to_component(self, comp_name, parent_group): layers_group = self._get_layers_group_of_component(comp_name) - use_node = self._domview._doc.createElement('svg:use') + use_node = self._doc.createElement('svg:use') layers_group_id = layers_group.getAttribute('id') use_node.setAttribute('xlink:href', '#' + layers_group_id) - use_node_id = self._domview.new_id() + use_node_id = self.new_id() use_node.setAttribute('id', use_node_id) use_node.setAttribute('use_component', 'true') @@ -962,7 +973,15 @@ # change and destroy scene node and scene group. A scene node is a 'ns0:scene' # in 'ns0:scenes' tag. A scene group is respective 'svg:g' for a scene. # +@composite class domview(domview_monitor): + use_traits = (component_manager,) + + method_map_component_manager = \ + {component_manager._start_component_manager: + '_start_component_manager'} + method_map_traits = {component_manager: method_map_component_manager} + # Declare variables, here, for keeping tracking _doc = None _root = None @@ -977,15 +996,6 @@ self._scenes_node = None self._layers_parent = None self._layers = [] - - self._comp_mgr = component_manager(self) - # Mixing-in component_manager to domview. - for attr in dir(self._comp_mgr): - if not attr.startswith('_'): - v = getattr(self._comp_mgr, attr) - setattr(self, attr, v) - pass - pass pass ## \brief Create a scenes node if not existed. @@ -1052,7 +1062,7 @@ self._init_metadata() self._start_monitor() # from domview_monitor - self._comp_mgr._start_component_manager() + self._start_component_manager() self._parse_all_layers() pass
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pyink/trait.py Fri Feb 11 15:10:37 2011 +0800 @@ -0,0 +1,291 @@ +## \brief Implement descriptors for mapping attributes for traits. +# +# The instances of require map attributes of traits to corresponding +# attributes of the instance of the composition class. +# +class require(object): + def __get__(self, instance, owner): + if not instance: # from a class object + return self + + attrname = instance._trait_attrname_map[self] + composite_obj = instance._trait_composite_obj + val = getattr(composite_obj, attrname) + return val + + def __set__(self, instance, value): + attrname = instance._trait_attrname_map[self] + composite_obj = instance._trait_composite_obj + setattr(composite_obj, attrname, value) + pass + pass + + +## \brief Decorator for making a class being a trait. +# +def trait(trait_clazz): + attrname_map = {} + trait_clazz._trait_attrname_map = attrname_map + + for attr in dir(trait_clazz): + value = getattr(trait_clazz, attr) + if value != require: + continue + + require_o = require() + setattr(trait_clazz, attr, require_o) + attrname_map[require_o] = attr + pass + + trait_clazz._is_trait = True + + return trait_clazz + + +## \brief The function to return a proxy for a method of a trait. +# +def trait_method_proxy(trait_clazz, method): + def trait_method_proxy_real(self, *args, **kws): + if not hasattr(self, '_all_trait_objs'): + # self is an instance of the class composed from traits. + self._all_trait_objs = {} + pass + + try: + trait_obj = self._all_trait_objs[trait_clazz] + except KeyError: + trait_obj = trait_clazz() + trait_obj._trait_composite_obj = self + self._all_trait_objs[trait_clazz] = trait_obj + pass + + r = method(trait_obj, *args, **kws) + + return r + + return trait_method_proxy_real + + +## \brief Derive and modify an existing trait. +# +def derive_trait(a_trait, composite_clazz): + attrname_map = None + if hasattr(composite_clazz, 'provide_traits'): + provide_traits = composite_clazz.provide_traits + if a_trait in provide_traits: + provide_trait = provide_traits[a_trait] + attrname_map = dict(a_trait._trait_attrname_map) + attrname_map.update(provide_trait) + pass + pass + + dic = {} + if attrname_map: + dic['_trait_attrname_map'] = attrname_map + pass + + derived = type('derived_trait', (a_trait,), dic) + + return derived + +## \brief A decorator to make class composited from traits. +# +# The class decorated by composite must own a use_traits attribute. +# +# \verbatim +# @trait +# class trait_a(object): +# var_a = require +# def xxx(self): return self.var_a +# +# @trait +# class trait_b(object): +# def ooo(self): pass +# +# @composite +# class foo(object): +# use_traits = (trait_a, trait_b) +# +# var_a = 'value of var_a' +# pass +# +# obj = foo() +# \endverbatim +# +# To make a class from a set of traits. You must decorate the class +# with the decorator 'composite'. The class must has an attribute, +# named use_traits, to provide a list or tuple of traits. +# +# Class that defines a trait must decorated with the decorator +# 'trait'. If the trait need to access state (varaibles) of the +# intances of composition class, it must define attributes with value +# 'require', likes what 'var_a' of trait_a does. Then, the attributes +# would be mapped to corresponding attributes of instances of +# composition class. For example, when you call obj.xxx(), it returns +# value of 'var_a', and attribute 'var_a' is a property that returns +# the value of 'var_a' of 'obj', an instance of class foo. +# +# By default, traits map attribute 'var_a' to 'var_a' of instances of +# composition classes. But, you can change it by specifying the map +# in an attribute, named 'provide_traits', defined in composition +# class. The attribute provide_traits is a dictionary mapping from +# trait class to a dictionary, named 'attrname_map' for the trait. +# The attrname_map maps require attributes of the trait to names of +# attributes of instances of the composition class. +# +def composite(clazz): + if not hasattr(clazz, 'use_traits'): + raise KeyError, \ + '%s has no use_trait: it must be a list of traits' % (repr(clazz)) + traits = clazz.use_traits + + for a_trait in traits: + if not hasattr(a_trait, '_is_trait'): + raise TypeError, '%s is not a trait' % (repr(a_trait)) + pass + + # + # Check content of clazz.provide_traits + # + if hasattr(clazz, 'provide_traits'): + if not isinstance(clazz.provide_traits, dict): + raise TypeError, \ + 'provide_traits of a composite must be a dictionary' + + provide_set = set(clazz.provide_traits.keys()) + trait_set = set(traits) + unused_set = provide_set - trait_set + if unused_set: + raise ValueError, \ + 'can not find %s in provide_traits' % (repr(unused_set.pop())) + + for trait, attrname_map in clazz.provide_traits.items(): + for req in attrname_map: + if not isinstance(req, require): + raise TypeError, \ + '%s is not a require: key of an ' \ + 'attribute name map must be a require' % (repr(req)) + pass + pass + pass + + # + # Count number of appearing in all traits for every attribute name. + # + attrname_cnts = {} + for a_trait in traits: + for attr in dir(a_trait): + if attr.startswith('_'): + continue + + value = getattr(a_trait, attr) + if value == require: + continue + + attrname_cnts[attr] = attrname_cnts.setdefault(attr, 0) + 1 + pass + pass + + if hasattr(clazz, 'method_map_traits'): + method_map_traits = clazz.method_map_traits + else: + method_map_traits = {} + pass + + # + # Set a proxy for every exported methods. + # + derived_traits = clazz._derived_traits = {} + for a_trait in traits: + derived = derive_trait(a_trait, clazz) + derived_traits[a_trait] = derived + + if a_trait in method_map_traits: + method_map_trait = method_map_traits[a_trait] + else: + method_map_trait = {} + + for attr in dir(derived): + if attr not in attrname_cnts: # hidden + continue + if attrname_cnts[attr] > 1: # conflict + continue + + if hasattr(clazz, attr): # override + continue + + value = getattr(a_trait, attr) + if value in method_map_trait: # do it later + continue + + value = getattr(derived, attr) + + if not callable(value): + raise TypeError, \ + '%s.%s is not a callable' % (repr(a_trait), attr) + + func = value.im_func + proxy = trait_method_proxy(derived, func) + setattr(clazz, attr, proxy) + pass + pass + + # + # Map methods specified in method_map_traits. + # + for a_trait, method_map_trait in method_map_traits.items(): + if a_trait not in derived_traits: + raise TypeError, \ + '%s is not a trait used by the composition class' % \ + (repr(a_trait)) + + derived = derived_traits[a_trait] + for method, attrname in method_map_trait.items(): + if not callable(method): + raise TypeError, \ + '%s.%s is not a callable' % (repr(a_trait), repr(method)) + func = method.im_func + proxy = trait_method_proxy(derived, func) + setattr(clazz, attrname, proxy) + pass + pass + + return clazz + + +if __name__ == '__main__': + @trait + class hello(object): + msg = require + + def hello(self, name): + return self.msg + ' hello ' + name + pass + + @trait + class bye(object): + msg = require + + def bye(self, name): + return self.msg + ' bye ' + name + pass + + @composite + class hello_bye(object): + use_traits = (hello, bye) + + provide_hello = {hello.msg: 'msg1'} + provide_traits = {hello: provide_hello} + + method_map_hello = {hello.hello: 'hello1'} + method_map_traits = {hello: method_map_hello} + + msg = 'hello_bye' + msg1 = 'hello_bye_msg1' + pass + + o = hello_bye() + assert o.hello1('Miky') == 'hello_bye_msg1 hello Miky' + assert o.bye('Miky') == 'hello_bye bye Miky' + print 'OK' + pass