Mercurial > MadButterfly
diff pyink/domview.py @ 1243:d5f70928e9f1
Move MBScene_domview_ui and MBScene_domview to separated modules.
- domview.py is where MBScene_domview and MBScene_domview_monitor are.
- domview_ui.py is where MBScene_domview_ui and MBScene_frameline_stack are.
- keep modules small to make better collaberation.
author | Thinker K.F. Li <thinker@codemud.net> |
---|---|
date | Mon, 10 Jan 2011 16:32:16 +0800 |
parents | |
children | b241f9768833 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pyink/domview.py Mon Jan 10 16:32:16 2011 +0800 @@ -0,0 +1,594 @@ +import random +import pybInkscape +from tween import TweenObject + +class Layer: + def __init__(self, node): + self.scenes = [] + self.group = node + pass + pass + +class ObjectWatcher(pybInkscape.PYNodeObserver): + def __init__(self, obj, type, func, arg): + self.obj = obj + self.type = type + self.func = func + self.arg = arg + + def notifyChildAdded(self, node, child, prev): + if self.type == 'DOMNodeInserted': + self.func(node, child) + def notifyChildRemoved(self, node, child, prev): + if self.type == 'DOMNodeRemoved': + self.func(node, child) + def notifyChildOrderChanged(self,node,child,prev): + pass + def notifyContentChanged(self,node,old_content,new_content): + if self.type == 'DOMSubtreeModified': + self.func(node) + def notifyAttributeChanged(self,node, name, old_value, new_value): + if self.type == 'DOMAttrModified': + self.func(node, name, old_value, new_value) + +def addEventListener(obj, type, func, arg): + obs = ObjectWatcher(obj, type, func, arg) + obj.addSubtreeObserver(obs) + 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. +class MBScene_domview_monitor(object): + def __init__(self, *args, **kws): + super(MBScene_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 + addEventListener(doc, 'DOMNodeInserted', self._on_insert_node, None) + addEventListener(doc, 'DOMNodeRemoved', self._on_remove_node, None) + addEventListener(doc, 'DOMAttrModified', self._on_attr_modified, None) + 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: + if child_id not in self._id2node: + self._id2node[child_id] = child + pass + pass + + if child.name() == 'ns0:scene': + 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 _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 + + 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: + if child_id not in self._id2node: + raise ValueError, \ + 'remove a node that is never known (%s)' % (child_id) + del self._id2node[child_id] + pass + + if child.name() == 'ns0:scene': + try: + ref = child.getAttribute('ref') + except: + pass + else: + del self._group2scene[ref] + pass + + try: + if node.name() == 'ns0:scenes' and \ + (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 name == 'id' and old_value != new_value: + if old_value and (old_value not in self._id2node): + raise ValueError, \ + 'old ID value of passed node is invalid one (%s)' % \ + (old_value) + if (new_value in self._id2node): + raise ValueError, \ + 'new ID value of passed node is invalid one (%s)' % \ + (new_value) + + if old_value: + del self._id2node[old_value] + pass + self._id2node[new_value] = node + pass + elif name == 'ref' and node.name() == 'ns0:scene': + if old_value == new_value: + return + 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': + self._maxframe = max(int(new_value), self._maxframe) + pass + pass + + ## \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: + return + + self._id2node[node_id] = node + 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) + except: + continue + + group_id = scene_node.getAttribute("ref") + self._group2scene[group_id] = scene_node + pass + pass + + ## \brief Parse all scenes node in svg:metadata subtree. + # + def _collect_all_scenes(self): + root = self._root + for child in root.childList(): + if child.name() != 'svg:metadata': + continue + + metadata_node = child + for metachild in metadata_node.childList(): + if metachild.name() == 'ns0:scenes': + self._parse_one_scenes(metachild) + self._maxframe = self._find_maxframe(metachild) + pass + pass + pass + pass + + ## \brief Return the node with given ID. + # + def get_node(self, node_id): + return self._id2node[node_id] + + ## \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 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. +# +class MBScene_domview(MBScene_domview_monitor): + # Declare variables, here, for keeping tracking + _doc = None + _root = None + + def __init__(self, *args, **kws): + super(MBScene_domview, self).__init__() + pass + + ## \brief Create a scenes node if not existed. + # + def _init_metadata(self): + 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' + + 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") + node.appendChild(scenes_node) + self._scenes_node = scenes_node + pass + pass + + def _parse_all_layers(self): + root = self._root + layers = self._layers + + for child in root.childList(): + if child.name() != 'svg:g': + continue + + layer_group = child + layer = Layer(layer_group) + layer.idx = len(layers) + layers.append(layer) + self.parse_layer(layer.idx) + pass + pass + + def handle_doc_root(self, doc, root): + self._doc = doc + self._root = root + self._layers = [] + + self._start_monitor() # start MBScene_domview_monitor + self._init_metadata() + self._parse_all_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, 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) + + return scene_node + + ## \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 + + def rm_scene_node(self, scene_node): + self._scenes_node.removeChild(scene_node) + pass + + def rm_scene_node_n_group(self, scene_node): + scene_group_id = scene_node.getAttribute('ref') + scene_group_node = self.get_node(scene_group_id) + scene_group_node.parent().removeChild(scene_group_node) + + self._scenes_node.removeChild(scene_node) + 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") + + layer.group.appendChild(scene_group) + + return scene_group + + 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 + + ## \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 Remove layer and associated scene nodes and scene groups. + # + def rm_layer(self, layer_idx): + layers = self._layers + + for layer in layers: + for scene_node in layer.scenes: + scene_group_id = scene_node.getAttribute('ref') + scene_group_node = self.get_node(scene_group_id) + scene_group_node.parent().removeChild(scene_group_node) + + 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_num(self): + return len(self._layers) + + 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 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 + + 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 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._dom._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 + pass +