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
+