view pyink/domview.py @ 1464:28769a82a72d

Raise a maningful exception for traits that is not ready
author Thinker K.F. Li <thinker@codemud.net>
date Sun, 17 Apr 2011 21:34:49 +0800
parents 5ff744b981fb
children a3ce8d22d163
line wrap: on
line source

import random
import dom_event
from tween import TweenObject
from trait import trait, require, composite


## \brief Compare two nodes with ID.
#
# \return True if node1 ID equivalent to ndoe2 ID.
#
def _id_eq(node1, node2):
    try:
        node1_id = node1.getAttribute('id')
    except:
        return False

    try:
        node2_id = node2.getAttribute('id')
    except:
        return False
    return node1_id == node2_id


class Layer:
    def __init__(self, node):
	self.scenes = []
	self.group = node
	pass
    pass


class Timeline(object):
    def __init__(self, scenes_node):
        self.scenes_node = scenes_node
        pass

    def name(self):
    	try:
            name = self.scenes_node.getAttribute('name')
	except:
	    name='default'
        return name

    def rename(self, new_name):
        scenes_node = self.scenes_node
        scenes_node.setAttribute('name', new_name)
        pass
    pass


class Transition(object):
    node = None
    condition = None
    target = None
    action = None
    
    def __init__(self, node=None):
        self.node = node
        pass

    def set_condition(self, cond):
        node = self.node

        node.setAttribute('condition', cond)
        self.condition = cond
        pass

    def set_action(self, action):
        self.action = action
        node = self.node
        node.setAttribute('action', action)
        pass

    def reparse(self):
        condition = node.getAttribute('condition')
        target = node.getAttribute('target')
        try:
            action = node.getAttribute('action')
        except:
            action = None
            pass
        
        self.condition = condition
        self.target = target
        self.action = action
        pass

    def update_node(self):
        node = self.node
        node.setAttribute('condition', self.condition)
        node.setAttribute('target', self.target)
        if self.action:
            node.setAttribute('action', self.action)
            pass
        pass

    ## \brief Create a node according status of the object.
    #
    # A new node is created for the object, and attributes of the node
    # is initialized with information from the object.
    #
    def create_node(self, doc):
        node = doc.createElement('ns0:transition')
        self.node = node
        self.update_node()
        
        return node
    
    @staticmethod
    def parse_transition(node):
        trn = Transition(node)
        trn.reparse()
        return trn
    pass


## \brief A state in a FSM
#
# Every component can have a FSM to describe its behavior.  Every
# state has an entry action and transitions to other states.  Every
# transition can have a transition action.
#
# The FSM of a component is defined as a child of ns0:component node.
# \verbatim
# <ns0:component>
#     <ns0:states start_state="UP">
#         <ns0:state name="UP" entry_action="entry action">
#             <ns0:transition target="DOWN"
#                             condition="button_down" action="..."/>
#         </ns0:state>
#     </ns0:states>
# </ns0:/component>
# \endverbatim
#
class State(object):
    name = None
    entry_action = None
    transitions = None
    
    def __init__(self, node):
        self.node = node
        pass

    def rename(self, name):
        self.name = name
        self.node.setAttribute('name', name)
        pass

    def set_entry_action(self, action):
        self.entry_action = action
        self.node.setAttribute('entry_action', action)
        pass

    def reparse(self):
        node = self.node
        
        name = node.getAttribute('name')
        try:
            entry_action = node.getAttribute('entry_action')
        except:
            entry_action = None
            pass
        
        all_transitions = [Transition.parse_transition(child)
                           for child in node.childList()
                           if child.name() == 'ns0:transition']
        transitions = dict([(trn.condition, trn)
                            for trn in all_transitions])
        
        self.name = name
        self.transitions = transitions
        self.entry_action = entry_action
        pass

    def update_node(self):
        node = self.node
        
        node.setAttribute('name', self.name)
        if self.entry_action:
            node.setAttribute('entry_action', self.entry_action)
            pass

        transitions = self.transitions or []
        for trn in transitions:
            trn.update_node()
            pass
        pass

    def create_node(self, doc):
        node = doc.createElement('ns0:state')
        self.node = node

        self.update_node()
        
        return node

    @staticmethod
    def parse_state(node):
        state = State(node)
        state.parse()
        
        return state

    def change_transition_cond(self, old_cond, new_cond):
        transitions = self.transitions
        trn = transitions[old_cond]
        del transitions[old_cond]
        transitions[new_cond] = trn

        trn.set_condition(new_cond)
        pass

    def add_transition(self, cond, target):
        transitions = self.transitions
        assert cond not in transitions
        
        node = self.node
        
        trn = Transition()
        trn.condition = cond
        trn.target = target
        trn_node = trn.create_node(node.document())

        node.addChild(trn_node)
        transitions[cond] = trn
        pass

    def rm_transition(self, cond):
        transitions = self.transitions
        if cond not in transitions:
            raise ValueError, \
                'There is no transition defined for this condition (%s)' % \
                (cond)
        
        trn = transitions[cond]
        trn_node = trn.node
        node = self.node
        
        node.removeChild(trn_node)
        del transitions[cond]
        pass

    def all_transitions(self):
        return self.transitions.keys()

    def get_transition(self, cond):
        transitions = self.transitions
        transition = transitions[cond]
        return transition
    pass


class Component(object):
    #
    # \param comp_node is None for main component.
    #
    def __init__(self, comp_mgr, comp_node):
        self._comp_mgr = comp_mgr
        self.node = comp_node
        self.layers = []
        self.timelines = []
        self.fsm_states = {}
        self.fsm_states_node = None
        self.fsm_start_state = None

        if comp_node:
            self._initialize_comp()
            pass
        pass

    def _initialize_comp(self):
        comp_node = self.node
        for child in comp_node.childList():
            if child.name() == 'ns0:scenes':
                break
            pass
        else:                   # no any ns0:scenes
            doc = self._comp_mgr._doc
            scenes_node = doc.createElement('ns0:scenes')
            scenes_node.setAttribute('name', 'default')
            
            node_id = self._comp_mgr.new_id()
            scenes_node.setAttribute('id', node_id)
            
            comp_node.appendChild(scenes_node)
            pass
        pass

    def name(self):
        if self.node:
            name = self.node.getAttribute('name')
        else:
            name = 'main'
            pass
        return name

    def all_timeline_names(self):
        names = [tl.name() for tl in self.timelines]
        return names

    def parse_timelines(self):
        self.timelines[:] = []
        
        if self.node:
            assert self.node.name() == 'ns0:component'
            pass
        
        comp_node = self.node
        for child in comp_node.childList():
            if child.name() == 'ns0:scenes':
                tl = Timeline(child)
                self.timelines.append(tl)
                print '    ' + tl.name()
                pass
            pass
        pass

    ## \brief Parse FSM from a ns0:states node.
    #
    def parse_states(self):
        comp_node = self.node
        assert (not comp_node) or comp_node.name() == 'ns0:component'
        
        states_nodes = [node
                        for node in comp_node.childList()
                        if node.name() == 'ns0:states']
        if not states_nodes:
            self.fsm_states = {}
            return
        states_node = states_nodes[0]
        
        self.fsm_start_state = states_node.getAttribute('start_state')
        
        state_nodes = [child
                       for child in states_node.childList
                       if child.name() == 'ns0:state']
        states = [State.parse_state(node) for state_node in state_nodes]
        self.fsm_states = dict([(state.name, state) for state in states])

        self.fsm_states_node = states_node
        pass

    def get_timeline(self, name):
        for tl in self.timelines:
            if tl.name() == name:
                return tl
            pass
        raise Value, 'invalid timeline name - %s' % (name)

    def has_timeline(self, name):
        for tl in self.timelines:
            if tl.name() == name:
                return True
            pass
        return False

    def add_timeline(self, name):
        if self.has_timeline(name):
            raise ValueError, \
                'try add a timeline with duplicated name - %s' % (name)
        
        doc = self._comp_mgr._doc
        comp_node = self.node
        
        scenes_node = doc.createElement('ns0:scenes')
        scenes_node.setAttribute('name', name)
        node_id = self._comp_mgr.new_id()
        scenes_node.setAttribute('id', node_id)

        comp_node.appendChild(scenes_node)

        tl = Timeline(scenes_node)
        self.timelines.append(tl)
        pass

    ## \brief Add a timeline for an existed scenes node.
    #
    def add_timeline_scenes(self, scenes_node):
        tl = Timeline(scenes_node)
        name = tl.name()
        if self.has_timeline(name):
            raise ValueError, \
                'name of scenes node of a timeline is duplicated'

        self.timeline.append(tl)
        pass

    def rm_timeline(self, name):
        for i, tl in enumerate(self.timelines):
            if tl.name() == name:
                comp_node = self.node
                comp_node.removeChild(tl.scenes_node)
                
                del self.timelines[i]
                return
            pass
        raise ValueError, 'try to remove a non-existed timeline - %s' % (name)

    def rename_timeline(self, timeline_name, new_name):
        for i, tl in enumerate(self.timelines):
            if tl.name() == timeline_name:
                tl.rename(new_name)
                return
            pass
        raise ValueError, 'try to remove a non-existed timeline - %s' % (name)

    def rename(self, new_name):
        self.node.setAttribute('name', new_name)
        pass

    def get_start_state_name(self):
        return self.fsm_start_state

    def set_start_state(self, state_name):
        assert state_name in self.fsm_states
        self.fsm_start_state = state_name
        pass

    def all_state_names(self):
        return self.fsm_states.keys()

    def get_state(self, name):
        return self.fsm_states[name]

    def add_state(self, name):
        doc = self._comp_mgr._doc
        
        state = State(name)
        state.transitions = []
        self.fsm_states[name] = state

        state_node = state.create_node(doc)
        node = self.node
        node.addChild(state_node)
        pass

    def rename_state(self, state_name, new_name):
        state = self.fsm_states[state_name]
        del self.fsm_states[state_name]
        self.fsm_states[new_name] = state
        
        state.rename(new_name)
        pass

    def rm_state(self, name):
        fsm_states = self.fsm_states
        state = fsm_states[name]
        del self.fsm_states[name]

        state_node = state.node
        node = self.node
        node.removeChild(state_node)
        pass
    pass


## \brief A mix-in for class component_manager for UI updating.
#
# This class collects all methods for supporting UI updating.
#
class component_manager_ui_update(object):
    ## \brief Update the list of components.
    #
    def reparse_components(self):
        saved_cur_comp = self._cur_comp
        self._components[:] = [self._main_comp]
        self._comp_names.clear()
        self._parse_components()

        for comp in self._components:
            if comp.name() == saved_cur_comp.name():
                self._cur_comp = comp
                break
            pass
        else:
            self._cur_comp = self._main_comp
            pass
        pass

    ## \brief Update the list of timelines of current component.
    #
    def reparse_timelines(self):
        comp = self._cur_comp
        saved_cur_timeline = self._cur_timeline
        comp.parse_timelines()
        
        for timeline in comp.timelines:
            if timeline.name() == saved_cur_timeline.name():
                self._cur_timeline = timeline
                break
            pass
        else:
            self._cur_timeline = comp.timelines[0]
            pass
        pass
    pass


## \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
# to a component is actually switching to a timeline in another
# component.  When switch to a timeline, it actuall change
# domview._scense_node, parent of all scene nodes of a component, and
# domview._layers_parent, parent of all layer group of a component.
# domview relys on these two variables to operate on right component
# and timeline.  (It should be changed to get more hint with
# meaningful names.)
#
# FIXME: The root node is always the 'main' component.  It is a
# special case with slightly different in structure.  It should be
# removed and normalized to normal components.
#
@trait
class component_manager(component_manager_ui_update):
    _scenes_node = require
    _metadata_node = require
    _doc = require
    _root = require
    _layers = require
    _layers_parent = require
    _cur_comp = require
    new_id = require
    get_node = require
    reset = require

    def __init__(self):
        super(component_manager, self).__init__()
        self._components_node = None
        self._components = []
        self._comp_names = set()
        self._main_comp = None
        self._cur_timeline = None
        self._components_group = None
        pass

    def _set_main_component(self):
        comp = Component(self, None)
        comp.layers = self._layers
        scenes_node = self._scenes_node
        timeline = Timeline(scenes_node)
        comp.timelines = [timeline]

        self._components.append(comp)
        self._comp_names.add('main')
        
        self._main_comp = comp
        pass

    def _parse_components(self):
        comp_names = self._comp_names
        components_node = self._components_node
        for child in components_node.childList():
            child_node_name = child.name()
            if child_node_name != 'ns0:component':
                continue
            
            child_name = child.getAttribute('name')
            if child_name in comp_names:
                raise ValueError, 'duplicate component name %s' % (child_name)

            comp = Component(self, child)
            comp.parse_timelines()
            
            self._components.append(comp)
            
            comp_names.add(child_name)
            pass
        pass

    ## \brief To initialize subtree of metadata.
    #
    # This method is called by domview._init_metadata().
    #
    def _component_manager_init_metadata(self):
        metadata_node = self._metadata_node

        # Make sure ns0:components in metadata
	for n in metadata_node.childList():
	    if n.name() == 'ns0:components':
		self._components_node = n
		break
	    pass
	else:
	    components_node = \
                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._root.childList():
            if n.name() != 'svg:g':
                continue
            try:
                nlabel = n.getAttribute('inkscape:label')
            except:
                continue
            if nlabel == 'components':
                self._components_group = n
                break
            pass
        else:                   # no components group
            components_group = self._doc.createElement('svg:g')
            components_group.setAttribute('inkscape:label', 'components')
            gid = self.new_id()
            components_group.setAttribute('id', gid)
            
            self._root.appendChild(components_group)
            self._components_group = components_group
            pass
        pass

    def _start_component_manager(self):
        self._component_manager_init_metadata()
        self._set_main_component()
        self._parse_components()

        self._cur_comp = self._main_comp
        tl = self._main_comp.get_timeline('default')
        self._cur_timeline = tl
        self._scenes_node = tl.scenes_node
        pass

    ## \brief Create component group
    #
    # A component group is a group with a layers group as child.
    # The layers group is where layer groups is in.
    #
    def _create_component_group(self):
        doc = self._doc
        group = doc.createElement('svg:g')
        gid = self.new_id()
        group.setAttribute('id', gid)
        
        self._components_group.appendChild(group)

        # Create layers group
        layers_group = doc.createElement('svg:g')
        gid = self.new_id()
        layers_group.setAttribute('id', gid)
        layers_group.setAttribute('inkscape:label', 'layers')
        group.appendChild(layers_group)
        
        return group

    ## \brief Create a ns0:component node for a given name.
    #
    # \param comp_name is the name of the created component.
    # \param comp_group_id is the component group.
    # \return a ns0:component node.
    #
    def _create_component_node(self, comp_name, comp_group_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)
        self._components_node.appendChild(comp_node)
        return comp_node

    ## \brief Get Component object associated with the given name.
    #
    def _get_component(self, comp_name):
        if comp_name in self._comp_names:
            for comp in self._components:
                if comp.name() == comp_name:
                    return comp
                pass
            pass
        raise ValueError, 'can not find component node - %s' % (comp_name)

    ## \brief Create a layer group for give layer of a component.
    #
    def _create_comp_layer_group(self, layers_group, layer_name):
        doc = self._doc
        gid = self.new_id()
        
        layer_group = doc.createElement('svg:g')
        layer_group.setAttribute('id', gid)
        layer_group.setAttribute('inkscape:label', layer_name)
        layer_group.setAttribute('inkscape:groupmode', 'layer')
        layers_group.appendChild(layer_group)
        
        return layer_group
    
    ## \brief Return group of specified layer in a component.
    #
    # This is here for getting layer group without switch current
    # component.
    #
    def _get_group_of_component_layer(self, comp_name, layer_idx):
        comp = self._get_component(comp_name)
        layer = comp.layers[layer_idx]
        return layer.group

    def _get_layers_group_of_component(self, comp_name):
        if comp_name == 'main':
            return self._root
        
        comp_group = self.get_component_group(comp_name)
        layers_group = comp_group.firstChild()
        assert layers_group.getAttribute('inkscape:label') == 'layers'
        
        return layers_group
    
    def all_comp_names(self):
        return [comp.name() for comp in self._components]

    def has_component(self, name):
        return name in self._comp_names

    def hide_component(self, comp_name):
        if comp_name == 'main':
            comp = self._get_component(comp_name)
            for layer in comp.layers:
                group = layer.group
                group.setAttribute('style', 'display: none')
                pass
            return

        comp_group = self.get_component_group(comp_name)
        comp_group.setAttribute('style', 'display: none')
        pass

    def show_component(self, comp_name):
        if comp_name == 'main':
            comp = self._get_component(comp_name)
            for layer in comp.layers:
                group = layer.group
                group.setAttribute('style', '')
                pass
            return

        comp_group = self.get_component_group(comp_name)
        comp_group.setAttribute('style', '')
        pass

    def switch_component(self, comp_name):
        old_comp = self._cur_comp
        
        comp = self._get_component(comp_name)
        self._cur_comp = comp
        self._layers = comp.layers
        comp_name = self._cur_comp.name()
        # for domview
        self._layers_parent = \
            self._get_layers_group_of_component(comp_name)
        
        self.make_sure_timeline()

        try:
            comp_grp = self.get_component_group(old_comp.name())
            old_comp_existed = True
        except ValueError:
            old_comp_existed = False
            pass
        
        if old_comp_existed:
            self.hide_component(old_comp.name())
            pass
        
        self.show_component(comp.name())
        pass

    def add_component(self, comp_name):
        if self.has_component(comp_name):
            raise ValueError, \
                'try add a component with existed name %s' % (comp_name)
        
        comp_group = self._create_component_group()
        comp_group_id = comp_group.getAttribute('id')
        comp_node = self._create_component_node(comp_name, comp_group_id)
        
        comp = Component(self, comp_node)
        comp.parse_timelines()

        self._components.append(comp)
        self._comp_names.add(comp_name)

        # Create Layer1 (at least one layer for a component)
        layers_group = self._get_layers_group_of_component(comp_name)
        layer_group = self._create_comp_layer_group(layers_group, 'Layer1')
        layer = Layer(layer_group)
        comp.layers.append(layer)
        
        self.hide_component(comp_name)
        pass

    def add_component_node(self, comp_node):
        comp = Component(self, comp_node)
        comp_name = comp.name()
        if self.has_component(comp_name):
            raise ValueError, \
                'the name of a ns0:component is duplicated'

        self._components.append(comp)
        self._comp_names.add(comp_name)
        pass

    def rm_component(self, comp_name):
        comp = self._get_component(comp_name)
        comp_name = comp.name()
        comp_node = comp.node
        comp_group = self.get_component_group(comp_name)
        
        self._components.remove(comp)
        self._comp_names.remove(comp_name)
        self._components_node.removeChild(comp_node)
        self._components_group.removeChild(comp_group)
        pass

    def rename_component(self, comp_name, new_name):
        comp = self._get_component(comp_name)
        comp.rename(new_name)
        self._comp_names.remove(comp_name)
        self._comp_names.add(new_name)
        pass
    
    def get_component_group(self, comp_name):
        comp = self._get_component(comp_name)
        
        comp_name = comp.name()
        if comp_name == 'main':
            return self._root
        
        comp_node = comp.node
        gid = comp_node.getAttribute('ref')
        comp_group = self.get_node(gid)
        return comp_group

    def get_current_component(self):
        return self._cur_comp.name()

    ## \brief Hide scene groups of current timeline.
    #
    # This method all scene groups of current timeline invisible.
    #
    def _hide_current_timeline(self):
        tl = self._cur_timeline
        scenes_node = tl.scenes_node
        for child in scenes_node.childList():
            if child.name() != 'ns0:scene':
                continue
            gid = child.getAttribute('ref')
            group = self.get_node(gid)
            group.setAttribute('style', 'display: none')
            pass
        pass
    
    def switch_timeline(self, timeline_name):
        if self._cur_timeline:
            self._hide_current_timeline()
            pass
        
        tl = self._cur_comp.get_timeline(timeline_name)
        self._cur_timeline = tl
        self._scenes_node = tl.scenes_node # of class domview

        # Make domview to rescan layers and scenes.
        self.reset()            # from domview

        cur_comp_name = self.get_current_component()
        cur_comp_node = self.get_component_group(cur_comp_name)
        cur_comp_node.setAttribute("cur_timeline", timeline_name)
        pass

    def make_sure_timeline(self):
        cur_comp_name = self.get_current_component()
        cur_comp_node = self.get_component_group(cur_comp_name)
        try:
            timeline_name = cur_comp_node.getAttribute("cur_timeline")
        except KeyError:
            timeline_name = self.all_timeline_names()[0]
            pass
        self._cur_timeline = None
        self.switch_timeline(timeline_name)
        pass

    def add_timeline(self, timeline_name):
        self._cur_comp.add_timeline(timeline_name)
        pass

    def rm_timeline(self, timeline_name):
        self._cur_comp.rm_timeline(timeline_name)
        pass

    def rename_timeline_of_component(self, timeline_name, new_name, comp_name):
        comp = self._get_component(comp_name)
        comp.rename_timeline(timeline_name, new_name)
        pass

    def rename_timeline(self, timeline_name, new_name):
        comp_name = self._cur_comp.name()
        self.rename_timeline_of_component(timeline_name, new_name, comp_name)
        pass

    def all_timeline_names(self):
        r = self._cur_comp.all_timeline_names()
        return r

    def has_timeline(self, name):
        r = self._cur_comp.has_timeline(name)
        return r

    def get_current_timeline(self):
        return self._cur_timeline.name()

    ## \brief Add a new component from a group node.
    #
    # The group node is reparented to the group of first layer of
    # specified component.
    #
    def mv_group_to_component(self, group, comp_name):
        group_parent = group.parent()
        if group_parent:
            group_parent.removeChild(group)
            pass
        
        layer_group = self._get_group_of_component_layer(comp_name, 0)
        layer_group.appendChild(group)
        pass

    ## \brief Create a link to a component.
    #
    # \param parent_group is where the link will be pliaced in.
    # \return link node.
    #
    def link_to_component(self, comp_name, parent_group):
        layers_group = self._get_layers_group_of_component(comp_name)
        
        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.new_id()
        use_node.setAttribute('id', use_node_id)
        use_node.setAttribute('use_component', 'true')

        parent_group.appendChild(use_node)
        
        return use_node

    ## \brief Remember current frame and layer on the scenes node.
    #
    def remember_current_frame(self, layer_idx, frame_idx):
        if not isinstance(layer_idx, int):
            raise TypeError, 'layer index should be a integer'
        if not isinstance(frame_idx, int):
            raise TypeError, 'frame index should be a integer'
        
        timeline_name = self.get_current_timeline()
        timeline = self._cur_comp.get_timeline(timeline_name)
        timeline_scenes = timeline.scenes_node
        timeline_scenes.setAttribute('cur_layer', str(layer_idx))
        timeline_scenes.setAttribute('cur_frame', str(frame_idx))
        pass

    ## \brief Get current frame and layer from the scenes node.
    #
    def get_current_frame(self):
        timeline_name = self.get_current_timeline()
        timeline = self._cur_comp.get_timeline(timeline_name)
        timeline_scenes = timeline.scenes_node
        try:
            cur_layer = timeline_scenes.getAttribute('cur_layer')
        except KeyError:
            cur_layer_idx = 0
        else:
            cur_layer_idx = int(cur_layer)
            pass
        try:
            cur_frame = timeline_scenes.getAttribute('cur_frame')
        except KeyError:
            cur_frame_idx = 0
        else:
            cur_frame_idx = int(cur_frame)
            pass

        return cur_layer_idx, cur_frame_idx
    pass


## \brief A trait for management FSM associated with current component.
#
@trait
class FSM_manager(object):
    _cur_comp = require

    def __init__(self):
        super(FSM_manager, self).__init__()
        pass
    
    def all_state_names(self):
        return self._cur_comp.all_state_names()

    def get_start_state_name(self):
        return self._cur_comp.get_start_state_name()

    ## \brief To return state object for the given name.
    #
    # This method should only be used by component_manager internally.
    #
    def _get_state(self, state_name):
        return self._cur_comp.get_state(state_name)

    def rm_state(self, state_name):
        self._cur_comp.rm_state(state_name)
        pass

    def add_state(self, state_name):
        self._cur_comp.add_state(state_name)
        pass

    def rename_state(self, state_name, new_name):
        self._cur_comp.rename_state(state_name, new_name)
        pass

    def set_start_state(self, state_name):
        self._cur_comp.set_start_state(state_name)
        pass

    def set_state_entry_action(self, state_name, entry_action):
        state = self._get_state(state_name)
        state.set_entry_action(entry_action)
        pass

    def all_transitions(self, state_name):
        state = self._get_state(state_name)
        trn_names = state.all_transitions()
        return trn_names

    def add_transition(self, state_name, cond, target):
        state = self._get_state(state_name)
        state.add_transition(cond, target)
        pass

    def rm_transition(self, state_name, cond):
        state = self._get_state(state_name)
        state.rm_transition(cond)
        pass

    def change_transition_cond(self, state_name, old_cond, new_cond):
        state = self._get_state(state_name)
        state.change_transition_cond(old_cond, new_cond)
        pass

    def get_transition(self, state_name, cond):
        state = self._get_state(state_name)
        trn = state.get_transition(cond)
        
        cond = trn.condition
        target = trn.target
        action = trn.action
        
        return cond, target, action

    def set_transition_action(self, state_name, cond, action):
        trn = state.get_transition(state_name, cond)
        trn.set_action(action)
        pass
    pass


## \brief Parser for scenes nodes.
#
# This class parses scenes nodes and collect ID of all nodes.
#
@trait
class scenes_parser(object):
    _root = require
    _scenes_node = require
    _id2node = require
    _group2scene = require
    current = require
    _maxframe = require
    
    def _find_maxframe(self, scenes_node):
	maxframe = 0
	for child in scenes_node.childList():
	    if child.name() != 'ns0:scene':
		continue
	    
	    try:
		start = child.getAttribute('start')
		maxframe = max(int(start), maxframe)
	    except:
		pass
	    try:
		end = child.getAttribute('end')
		maxframe = max(int(end), maxframe)
	    except:
		pass
	    pass
	return maxframe

    ## \brief Collect ID of nodes in the document.
    #
    # It is used to implement a fast mapping from an ID to the respective node.
    #
    def _collect_node_ids(self):
	self._id2node = {}
	root = self._root
	for n in root.childList():
	    self._collect_node_ids_recursive(n)
	    pass
	pass
    
    def _collect_node_ids_recursive(self, node):
	try:
	    node_id = node.getAttribute('id')
	except:
            pass
        else:
            self._id2node[node_id] = node
            pass
        
	for n in node.childList():
	    self._collect_node_ids_recursive(n)
	    pass
	pass
    
    def parse_one_scene(self, scene_node):
	assert scene_node.name() == 'ns0:scene'

	start = int(scene_node.getAttribute("start"))
	try:
	    end = int(scene_node.getAttribute("end"))
	except:
	    end = start
	    pass
	
	try:
	    scene_type = scene_node.getAttribute('type')
	    if scene_type == None:
		scene_type = 'normal'
		pass
	except:
	    scene_type = 'normal'
	    pass

	return start, end, scene_type

    def _parse_one_scenes(self, scenes_node):
	try:
	    cur = int(n.getAttribute("current"))
	except:
	    cur = 0
	    pass
	self.current = cur
	
	for scene_node in scenes_node.childList():
	    if scene_node.name() != 'ns0:scene':
		continue

	    try:
		start, end, scene_type = self.parse_one_scene(scene_node)
                group_id = scene_node.getAttribute("ref")
	    except:             # the scene node is incompleted.
		continue
	    
	    self._group2scene[group_id] = scene_node
	    pass
	pass

    ## \brief Parse all scenes node in svg:metadata subtree.
    #
    def _collect_all_scenes(self):
        scenes_node = self._scenes_node
        self._parse_one_scenes(scenes_node)
        self._maxframe = self._find_maxframe(scenes_node)
        pass
	pass
    
    ## \brief Return the node with given ID.
    #
    def get_node(self, node_id):
	value = self._id2node[node_id]
        if isinstance(value, list):
            return value[-1]
        return value

    ## \brief Return a scene node corresponding to a scene group of given ID.
    #
    def get_scene(self, group_id):
	return self._group2scene[group_id]

    def new_id(self):
	while True:
	    candidate = 's%d' % int(random.random()*100000)
	    if candidate not in self._id2node:
		return candidate
	    pass
	pass
    pass

## \brief Monitor changes of DOM-tree.
#
# This class monitors DOM-tree to maintain _maxframe and maps for node ID to
# node and scene group ID to scene node.
@composite
class domview_monitor(object):
    use_traits = (scenes_parser,)

    method_map_traits = {
        scenes_parser._find_maxframe: '_find_maxframe',
        scenes_parser._collect_all_scenes: '_collect_all_scenes',
        scenes_parser._collect_node_ids: '_collect_node_ids'}
    
    def __init__(self, *args, **kws):
	super(domview_monitor, self).__init__()

	self._maxframe = 0
	self._id2node = {}	# map ID to the node in the DOM tree.
	self._group2scene = {}	# map ID of a group to associated scene node.
	pass
    
    def _start_monitor(self):
	self._collect_node_ids()
	self._collect_all_scenes()
	
	doc = self._doc
	dom_event.addEventListener(doc, 'DOMNodeInserted',
                                   self._on_insert_node, None)
	dom_event.addEventListener(doc, 'DOMNodeRemoved',
                                   self._on_remove_node, None)
	dom_event.addEventListener(doc, 'DOMAttrModified',
                                   self._on_attr_modified, None)
	pass

    ## \brief Add a node to id2node mapping.
    #
    # domview_monitor._id2node is a multiple mapping to map a key to
    # multiple node.  The reason that it is not a single mapping is
    # Inkscape would insert a node with the ID from the node been
    # copied, and change its ID to a unique one later.  So, we must
    # provide the capability to handle two or more nodes with the same
    # ID.
    def _map_id2node(self, node, node_id):
        if node_id in self._id2node:
            old_value = self._id2node[node_id]
            if isinstance(old_value, list):
                old_value.append(node)
            else:
                self._id2node[node_id] = [old_value, node]
                pass
        else:
            self._id2node[node_id] = node
            pass
        pass

    def _unmap_id2node(self, node, node_id):
        if node_id not in self._id2node:
            raise ValueError, 'invalide node ID (%s)' % (node_id)

        value = self._id2node[node_id]
        if isinstance(value, list):
            value.remove(node)
            if not value:
                del self._id2node[node_id]
                pass
            pass
        else:
            del self._id2node[node_id]
            pass
        pass

    ## \brief Rescan the tree.
    #
    def _monitor_reparse(self):
        self._maxframe = 0
        self._id2node = {}
        self._group2scene = {}

        self._collect_node_ids()
        self._collect_all_scenes()
        pass

    def _on_insert_node(self, node, child):
	for cchild in child.childList():
	    self._on_insert_node(child, cchild)
	    pass
	
	try:
	    child_id = child.getAttribute('id')
	except:
	    pass
	else:
            self._map_id2node(child, child_id)
	    pass

	if child.name() == 'ns0:scene' and _id_eq(node, self._scenes_node):
	    try:
		ref = child.getAttribute('ref')
	    except:
		pass
	    else:
		if ref not in self._group2scene:
		    self._group2scene[ref] = child
		    pass
		pass

	    try:
		start = child.getAttribute('start')
		self._maxframe = max(int(start), self._maxframe)
	    except:
		pass
	    try:
		start = child.getAttribute('end')
		self._maxframe = max(int(start), self._maxframe)
	    except:
		pass
	    pass
	pass	    

    def _on_remove_node(self, node, child):
	for cchild in child.childList():
	    self._on_remove_node(child, cchild)
	    pass
	
	try:
	    child_id = child.getAttribute('id')
	except:
	    pass
	else:
            self._unmap_id2node(child, child_id)
	    pass
	
	if child.name() == 'ns0:scene' and _id_eq(node, self._scenes_node):
	    try:
		ref = child.getAttribute('ref')
	    except:
		pass
	    else:
		del self._group2scene[ref]
		pass

	    try:
		if int(child.getAttribute('start')) == self._maxframe or \
                        int(child.getAttribute('end')) == self._maxframe:
		    self._maxframe = self._find_maxframe(node)
		    pass
	    except:
		pass
	    pass
	pass

    def _on_attr_modified(self, node, name, old_value, new_value):
        if old_value == new_value:
            return
        
	if name == 'id':
	    if old_value and old_value in self._id2node:
                self._unmap_id2node(node, old_value)
		pass
            if new_value:
                self._map_id2node(node, new_value)
                pass
	    pass
	elif name == 'ref' and node.name() == 'ns0:scene':
            parent_node = node.parent()
            scenes_node = self._scenes_node
            if not _id_eq(parent_node, scenes_node):
                return          # not in current timeline
            
	    if old_value:
		node = self._group2scene[old_value] # use old node.  Binding
						    # may generate a new
						    # wrapper.
		del self._group2scene[old_value]
		pass
	    if new_value:
		self._group2scene[new_value] = node
		pass
	    pass
	elif (name in ('start', 'end')) and node.name() == 'ns0:scene':
            parent_node = node.parent()
            scenes_node = self._scenes_node
            if not _id_eq(parent_node, scenes_node):
                return          # not in current timeline
            
            try:
                new_value = int(new_value)
                old_value = int(old_value)
            except TypeError:
                self._maxframe = self._find_maxframe(scenes_node)
            else:
                if old_value == self._maxframe and old_value > new_value:
                    # _maxframe may be reduced.
                    self._maxframe = self._find_maxframe(scenes_node)
                else:
                    self._maxframe = max(int(new_value), self._maxframe)
                    pass
                pass
	    pass
	pass
    pass


## \brief Iterator to travel a sub-tree of DOM.
#
def _DOM_iterator(node):
    nodes = [node]
    while nodes:
	node = nodes.pop(0)
	child = node.firstChild()
	while child:
	    nodes.append(child)
	    child = child.next()
	    pass
	yield node
	pass
    pass


@trait
class layers_parser(object):
    _doc = require
    _layers = require
    _layers_parent = require
    get_scene = require
    get_node = require
    new_id = require
    
    def parse_all_layers(self):
	layers = self._layers
        layers_parent = self._layers_parent
	
	for child in layers_parent.childList():
	    if child.name() != 'svg:g':
		continue
            
            try:
                label = child.getAttribute('inkscape:label')
            except:
                pass
            else:               # has no label
                if label == 'components':
                    continue
                pass

	    layer_group = child
	    layer = Layer(layer_group)
	    layer.idx = len(layers)
	    layers.append(layer)
	    self.parse_layer(layer.idx)
	    pass
	pass
    
    def parse_layer(self, layer_idx):
	layer = self._layers[layer_idx]
	layer_group = layer.group
	
	for child in layer_group.childList():
	    if child.name() != 'svg:g':
		continue
	    try:
		child_id = child.getAttribute('id')
		scene_node = self.get_scene(child_id)
	    except:
		continue
	    
	    layer.scenes.append(scene_node)
	    pass
	pass
    
    def get_layer_num(self):
	return len(self._layers)

    ## \brief Add/insert a layer at given position.
    #
    # \param layer_idx is the position in the layer list.
    #
    def insert_layer(self, layer_idx, layer_group):
	layers = self._layers
	
	layer = Layer(layer_group)
	if layer_idx >= len(layers):
	    layers.append(layer)
	else:
	    layers.insert(layer_idx, layer)
	    for idx in range(layer_idx, len(layers)):
		layers[idx].idx = idx
		pass
	    pass
	pass

    ## \brief Manage a existed layer group
    #
    # This method scan layer groups of all managed layers, and find a
    # proper place to insert it.
    #
    # \return -1 for error, or layer index.
    #
    def manage_layer_group(self, layer_group_id):
        layer_group = self.get_node(layer_group_id)
        new_layer = Layer(layer_group)

        if not self._layers:
            new_layer.idx = 0
            self._layers.append(new_layer)
            return 0
        
        #
        # Scan who is after the given group
        #
        next_group = layer_group.next()
        while next_group:
            next_group_id = next_group.getAttribute('id')
            
            for vlayer in self._layers:
                vlayer_group_id = vlayer.group.getAttribute('id')
                if vlayer_group_id == next_group_id:
                    # This layer group is after given one.
                    self._layers.insert(vlayer.idx, new_layer)
                    
                    for idx in range(vlayer.idx, len(self._layers)):
                        self._layers[idx].idx = idx
                        pass
                    return new_layer.idx
                pass
            
            next_group = next_group.next()
            pass
        
        #
        # Is the given group after last layer group?
        #
        tail_group = self._layers[-1].group.next()
        while tail_group:
            tail_group_id = tail_group.getAttribute('id')
            
            if tail_group_id == layer_group_id:
                # it is after last layer group.
                new_layer.idx = len(self._layers)
                self._layers.append(new_layer)
                return new_layer.idx
            
            tail_group = tail_group.next()
            pass

        return -1             # error, can not determinze the position
    
    ## \brief Remove layer and associated scene nodes and scene groups.
    #
    def rm_layer(self, layer_idx):
	layers = self._layers

        layer = self._layers[layer_idx]
        for scene_node in layer.scenes:
            scene_group_id = scene_node.getAttribute('ref')
            try:
                scene_group_node = self.get_node(scene_group_id)
                if scene_group_node.parent(): # keep from crashing
                    scene_group_node.parent().removeChild(scene_group_node)
                    pass
            except:
                pass
            
            if scene_node.parent(): # keep from crashing
                scene_node.parent().removeChild(scene_node)
		pass
	    pass
	
	del layers[layer_idx]

	for idx in range(layer_idx, len(layers)):
	    layers[idx].idx = idx
	    pass
	pass

    def get_layer_group(self, layer_idx):
	layer = self._layers[layer_idx]
	return layer.group

    def get_all_scene_node_of_layer(self, layer_idx):
	layer = self._layers[layer_idx]
	return layer.scenes

    def get_layer_data(self, layer_idx):
	layer = self._layers[layer_idx]
	try:
	    data = layer.data
	except:
	    return None
	return data

    def set_layer_data(self, layer_idx, data):
	layer = self._layers[layer_idx]
	layer.data = data
	pass

    def create_layer_dup_group(self, layer_idx):
	layer = self._layers[layer_idx]
	
	dup_group = self._doc.createElement('svg:g')
	gid = self.new_id()
	dup_group.setAttribute('id', gid)
	dup_group.setAttribute('inkscape:label', 'dup')
	dup_group.setAttribute('sodipodi:insensitive', '1')
	dup_group.setAttribute('style', '')

	layer.group.appendChild(dup_group)
	
	return dup_group

    ## \brief Return associated layer index of given layer group.
    #
    # \return -1 for error.
    #
    def find_layer_of_group(self, group_id):
        for layer_idx, layer in enumerate(self._layers):
            if layer.group.getAttribute('id') == group_id:
                return layer_idx
            pass
        return -1

    def reset_layers(self):
        self._layers[:] = []
        self.parse_all_layers()
        pass
    pass

## \brief This layer provide a data view to the DOM-tree.
#
# This class maintains layers information, and provides functions to create,
# 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, layers_parser, FSM_manager)
    
    method_map_traits = {component_manager._start_component_manager:
                             '_start_component_manager'}

    # Declare variables, here, for keeping tracking
    _doc = None
    _root = None
    # Required by component_manager and FSM_manager
    _cur_comp = None
    
    def __init__(self, *args, **kws):
	super(domview, self).__init__()
        self._metadata_node = None
        #
        # Following two variables would be changed by class
        # component_manager to switch components and timelines.
        #
        self._scenes_node = None
        self._layers_parent = None
        self._layers = []
	pass

    ## \brief Create a scenes node if not existed.
    #
    def _init_metadata(self):
        self._layers_parent = self._root

	for node in self._root.childList():
	    if node.name() == 'svg:metadata':
		break
	    pass
	else:
	    raise RuntimeError, \
		'can not find <svg:metadata> node in the document'

        self._metadata_node = node
	
	for n in node.childList():
	    if n.name() == 'ns0:scenes':
		self._scenes_node = n
		break
	    pass
	else:
	    ns = "http://madbutterfly.sourceforge.net/DTD/madbutterfly.dtd"
	    self._root.setAttribute("xmlns:ns0", ns)
	    scenes_node = self._doc.createElement("ns0:scenes")
            scenes_node_id = 'main_default_scenes'
            scenes_node.setAttribute('id', scenes_node_id)
            scenes_node.setAttribute('name', 'default')
	    node.appendChild(scenes_node)
	    self._scenes_node = scenes_node
	    pass
	pass

    def handle_doc_root(self, doc, root):
	self._doc = doc
	self._root = root
	
	self._init_metadata()
	self._start_monitor()	# from domview_monitor
        self._start_component_manager()
        self.reset_layers()
	pass

    def reset(self):
        self._monitor_reparse() # from domview_monitor
        self.reset_layers()
	pass
   
    def dumpattr(self, n):
	s = ""
	for a,v in n.attrib.items():
	    s = s + ("%s=%s"  % (a,v))
	    pass
	return s
	
    def dump(self, node, l=0):
	print " " * l*2,"<", node.tag, self.dumpattr(node),">"
	for n in node:
	    self.dump(n, l+1)
	    pass
	print " " * l * 2,"/>"
	pass

    ## \brief Create and add a ns0:scene node under ns0:scenes subtree.
    #
    def add_scene_node(self, layer_idx, start, end,
		       frame_type=TweenObject.TWEEN_TYPE_NORMAL,
		       ref=None):
	type_names = ('normal', 'scale')
	scenes_node = self._scenes_node
	doc = self._doc
	
	scene_node = doc.createElement('ns0:scene')
	self.chg_scene_node(scene_node, start=start)
	if start != end:
	    self.chg_scene_node(scene_node, end=end)
	    pass
	type_name = type_names[frame_type]
	self.chg_scene_node(scene_node, tween_type=type_name)
	if ref:
	    self.chg_scene_node(scene_node, ref=ref)
	    pass
	
	scenes_node.appendChild(scene_node)
        
        self._layers[layer_idx].scenes.append(scene_node)
	
	return scene_node

    ## \brief Manage a existed scene node at given layer.
    #
    def manage_scene_node(self, layer_idx, scene_node):
        self._layers[layer_idx].scenes.append(scene_node)
        pass

    ## \brief Change attributes of a scene node.
    #
    # This is here to monitor changes of scene node.
    def chg_scene_node(self, scene_node, start=None, end=None,
			tween_type=None, ref=None):
	if start is not None:
	    scene_node.setAttribute('start', str(start))
	    pass
	if end is not None:
	    scene_node.setAttribute('end', str(end))
	    pass
	if tween_type is not None:
	    scene_node.setAttribute('type', tween_type)
	    pass
	if ref is not None:
	    scene_node.setAttribute('ref', ref)
	    pass
	pass

    ## \brief Remove scene node from DOM-tree.
    #
    def rm_scene_node(self, scene_node):
        if not scene_node.parent():
            return              # without this, may crash the Inkscape.
        
	self._scenes_node.removeChild(scene_node)
        for layer in self._layers:
            try:
                layer.scenes.remove(scene_node)
            except ValueError:  # not in the list
                pass
            else:
                break
            pass
	pass

    ## \brief Remove scene node and asssociated scene group from DOM.
    #
    # It will remove as many as possible.  Does not complain about
    # error in the procedure of removing.
    #
    def rm_scene_node_n_group(self, scene_node):
	scene_group_id = scene_node.getAttribute('ref')
        try:
            scene_group_node = self.get_node(scene_group_id)
            if scene_group_node.parent(): # Check it, or crash the
                                          # Inkscape.
                scene_group_node.parent().removeChild(scene_group_node)
                pass
        except:
            pass
	
        try:
            self.rm_scene_node(scene_node)
        except:
            pass
	pass

    ## \brief Create and add a svg:g for a scene under a group for a layer.
    #
    def add_scene_group(self, layer_idx):
	layer = self._layers[layer_idx]
	doc = self._doc
	
	scene_group = doc.createElement('svg:g')
	gid = self.new_id()
	scene_group.setAttribute("id", gid)
	scene_group.setAttribute("inkscape:groupmode", "layer")
        scene_group.setAttribute('scene_group', 'true')

	layer.group.appendChild(scene_group)
	
	return scene_group
    
    ## \brief Find layer index and scene info for a given scene node.
    #
    # \return (-1, None) for error.
    #
    def find_layer_n_scene_of_node(self, node_id):
	for layer_idx, layer in enumerate(self._layers):
	    for scene_node in layer.scenes:
		scene_group_id = scene_node.getAttribute('ref')
		if scene_group_id == node_id:
		    return layer_idx, scene_node
		pass
	    pass
	return -1, None

    def insert_frames(self, layer_idx, frame_idx, num):
	layer = self._layers[layer_idx]
	for scene_node in layer.scenes:
	    start, end, tween_type = self.parse_one_scene(scene_node)
	    if start >= frame_idx:
		self.chg_scene_node(scene_node, start=(start + num))
		pass
	    if end >= frame_idx:
		self.chg_scene_node(scene_node, end=(end + num))
		pass
	    pass
	pass

    ## \brief add the current position to the undo buffer
    #
    def mark_undo(self, msg):
    	self._doc.done("none", msg)
    	pass

    ## \brief Remove frames
    #
    # - Scenes covered by removing range were removed.
    # - Scenes after removing range were shifted left.
    #
    def rm_frames(self, layer_idx, frame_idx, num):
	layer = self._layers[layer_idx]
	
	last_rm = frame_idx + num - 1 # last removed frame
	for scene_node in layer.scenes:
	    start, end, tween_type = \
		self.parse_one_scene(scene_node)
	    
	    if end < frame_idx:
		continue
            
	    if start > last_rm:	# this scene is at right side
		self.chg_scene_node(scene_node,
				    start=(start - num),
				    end=(end - num))
	    else:	 # this scene is covered by removing range
                self.rm_scene_node_n_group(scene_node)
		pass
	    pass
	pass

    def get_max_frame(self):
	return self._maxframe
    
    ## \brief Copy children of a group.
    #
    # Duplicate children of a group, and append them to another group.
    #
    def copy_group_children(self, src_group, dst_group):
	# Search for the duplicated group
	doc = self._doc
        
        dup_group = src_group.duplicate(doc)
        
	old_nodes = _DOM_iterator(src_group)
        new_nodes = _DOM_iterator(dup_group)
        new_gids = set()
	for old_node in old_nodes:
	    old_node_id = old_node.getAttribute('id')
            new_node = new_nodes.next()
	    new_node.setAttribute('ns0:duplicate-src', old_node_id)
            
            #
            # Change ID here, or inkscape would insert the node with
            # the same ID, and change it later to avoid duplication.
            # But, our event handler would be called before changing
            # ID.  It would confuse our code.  We change ID of nodes
            # before inserting them into the DOM-tree.
            #
            gid = self.new_id()
            while gid in new_gids:
                gid = self.new_id()
                pass
            new_gids.add(gid)
            new_node.setAttribute('id', gid)
	    pass

        for child in dup_group.childList():
            dup_group.removeChild(child) # prevent from crash
            dst_group.appendChild(child)
            pass
	pass

    ## \brief Clone children of a source group to a destinate group.
    #
    # It create a 'svg:use' node for every child of the source group,
    # and append nodes to the desitnate group.
    #
    def clone_group_children(self, src_group, dst_group):
        doc = self._doc

        for src_child in src_group.childList():
            src_child_id = src_child.getAttribute('id')
            dst_child_id = self.new_id()
            
            dst_child = doc.createElement('svg:use')
            dst_child.setAttribute('id', dst_child_id)
            dst_child.setAttribute('xlink:href', '#' + src_child_id)
            dst_child.setAttribute('ns0:duplicate-src', src_child_id)
            dst_group.appendChild(dst_child)
            pass
        pass

    ## \brief To test a graphic node.
    #
    # A graphic node is a SVG node that is not layer group, scene
    # group, ... etc.  It is only a normal node in a layer group or a
    # scene group.
    def is_graph_node(self, node):
        try:
            mode = node.getAttribute('inkscape:groupmode')
        except:
            pass
        else:
            if mode == 'layer':
                return False
            pass

        try:
            label = node.geteAttribute('inkscape:label')
        except:
            pass
        else:
            return False
        
        try:
            scene_group = node.geteAttribute('scene_group')
        except:
            pass
        else:
            if scene_group == 'true':
                return False
            pass
        
        return True
    pass