changeset 1206:1d476b35dc79

Merge and move code of tracking max frame number to MBScene_dom_monitor
author Thinker K.F. Li <thinker@codemud.net>
date Tue, 04 Jan 2011 10:08:02 +0800
parents 1af64bcdfd7d (diff) 9d715956823a (current diff)
children 489e6e474fdf
files pyink/MBScene.py pyink/frameline.py
diffstat 3 files changed, 741 insertions(+), 548 deletions(-) [+]
line wrap: on
line diff
--- a/pyink/MBScene.py	Tue Jan 04 01:13:23 2011 +0800
+++ b/pyink/MBScene.py	Tue Jan 04 10:08:02 2011 +0800
@@ -45,8 +45,7 @@
 class Layer:
     def __init__(self,node):
 	self.scenes = []
-	self.node = node
-	self.nodes=[]
+	self.group = node
 	pass
     pass
 
@@ -73,21 +72,20 @@
 	self.func = func
 	self.arg = arg
 
-    def notifyChildAdded(self,node,child,prev):
+    def notifyChildAdded(self, node, child, prev):
         if self.type == 'DOMNodeInserted':
-	    self.func(node)
-    def notifyChildRemoved(self,node,child,prev):
+	    self.func(node, child)
+    def notifyChildRemoved(self, node, child, prev):
         if self.type == 'DOMNodeRemoved':
-	    self.func(node)
+	    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):
-        # print 'attr',node,name,old_value,new_value
         if self.type == 'DOMAttrModified':
-	    self.func(node,name)
+	    self.func(node, name, old_value, new_value)
 
 def addEventListener(obj, type, func,arg):
     obs = ObjectWatcher(obj,type,func,arg)
@@ -153,23 +151,274 @@
 	pass
     pass
 
-## \brief Layer of MBScene to manipulate DOM tree.
+
+## \brief Monitor changes of DOM-tree.
 #
-class MBScene_dom(object):
-    def newID(self):
-	while True:
-	    n = 's%d' % int(random.random()*10000)
-			#print "try %s" % n
-	    if self.ID.has_key(n) == False:
-		return n
+class MBScene_dom_monitor(object):
+    def __init__(self, *args, **kws):
+	super(MBScene_dom_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):
+	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):
+	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
     
-    def dumpID(self):
-	for a,v in self.ID.items():
+    ## \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] = (start, end, scene_type)
+	    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 Layer of MBScene to manipulate DOM tree.
+#
+class MBScene_dom(MBScene_dom_monitor):
+    # Declare variables, here, for keeping tracking
+    _doc = None
+    _root = None
+    
+    def __init__(self, *args, **kws):
+	super(MBScene_dom, self).__init__()
+	pass
+
+    def handle_doc_root(self, doc, root):
+	self._doc = doc
+	self._root = root
+	self._layers = []
+	
+	self._start_monitor()	# start MBScene_dom_monitor
+	self._init_metadata()
+	self._parse_all_layers()
+	pass
+   
     def dumpattr(self, n):
 	s = ""
 	for a,v in n.attrib.items():
@@ -185,63 +434,92 @@
 	print " " * l * 2,"/>"
 	pass
 
-    def _parse_one_scenes(self, scenes):
-	self.scenemap = {}
-	try:
-	    cur = int(n.getAttribute("current"))
-	except:
-	    cur = 1
+    ## \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
-	self.current = cur
+	else:
+	    raise RuntimeError, \
+		'can not find <svg:metadata> node in the document'
 	
-	for scene in scenes.childList():
-	    if scene.name() != 'ns0:scene':
-		continue
-	    
-	    try:
-		start = int(scene.getAttribute("start"))
-	    except:
-		traceback.print_exc()
-		continue
-	    try:
-		end = int(scene.getAttribute("end"))
-	    except:
-		end = start
-		pass
-	    
-	    if end > self.maxframe:
-		self.maxframe = end
-		pass
-	    try:
-		scene_type = scene.getAttribute('type')
-		if scene_type == None:
-		    scene_type = 'normal'
-		    pass
-	    except:
-		traceback.print_exc()
-		scene_type = 'normal'
-		pass
-	    link = scene.getAttribute("ref")
-	    self.scenemap[link] = (int(start), int(end), scene_type)
-	    if cur >= start and cur <= end:
-		self.currentscene = link
-		pass
-	    pass
-	pass
-    
-    def parseMetadata(self, node):
 	for n in node.childList():
 	    if n.name() == 'ns0:scenes':
-		self._parse_one_scenes(n)
+		self._scenes_node = n
 		break
 	    pass
 	else:
 	    ns = "http://madbutterfly.sourceforge.net/DTD/madbutterfly.dtd"
-	    self.root.setAttribute("xmlns:ns0", ns)
-	    scenes = self.document.createElement("ns0:scenes")
-	    node.appendChild(scenes)
+	    self._root.setAttribute("xmlns:ns0", ns)
+	    scenes_node = self._doc.createElement("ns0:scenes")
+	    node.appendChild(scenes_node)
+	    self._scenes_node = scenes_node
 	    pass
 	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:
+	    scene_node.setAttribute('start', str(start))
+	    pass
+	if end:
+	    scene_node.setAttribute('end', str(end))
+	    pass
+	if tween_type:
+	    scene_node.setAttribute('type', tween_type)
+	    pass
+	if ref:
+	    scene_node.setAttribute('ref', ref)
+	    pass
+	pass
+
+    def _rm_scene_node(self, scene_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 insertKeyScene(self, line, frame):
 	"""
@@ -252,85 +530,85 @@
 	new scene.
 
 	"""
-	rdoc = self.document
-	ns = rdoc.createElement("svg:g")
-	found = False
-	for node in line.node.childList():
+	doc = self._doc
+	layer_idx = self._framelines.index(line)
+	
+	scene_group = self._add_scene_group(layer_idx)
+	scene_group_id = scene_group.getAttribute('id')
+	scene_node = self._add_scene_node(frame, frame, ref=scene_group_id)
+	line.add_keyframe(frame, scene_node)
+
+	for node in self._layers[layer_idx].group.childList():
 	    try:
-		label = node.getAttribute("inkscape:label")
+		label = node.getAttribute('inkscape:label')
 	    except:
 		continue
-	    if label == "dup":
-		#FIXME: The duplication here is not perfect. We should
-		#       get the element inside the group and apply the
-		#       transformation matrix to it directly.
-		for n in node.childList():
-		    ns.appendChild(n.duplicate(self.document))
-		found = True
-		node.setAttribute("style","display:none")
-		break
+	    if label == 'dup':
+		node.setAttribute('style', 'display: none')
+		pass
+	    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
 
-	if found == False:
-	    txt = rdoc.createElement("svg:rect")
-	    txt.setAttribute("x","0")
-	    txt.setAttribute("y","0")
-	    txt.setAttribute("width","100")
-	    txt.setAttribute("height","100")
-	    txt.setAttribute("style","fill:#ff00")
-	    ns.appendChild(txt)
+    def _parse_all_layers(self):
+	root = self._root
+	layers = self._layers
+	
+	for child in root.childList():
+	    if child.name() != 'svg:g':
+		continue
 
-	gid = line.node.getAttribute('inkscape:label')+self.newID()
-	self.ID[gid]=1
-	ns.setAttribute("id",gid)
-	ns.setAttribute("inkscape:groupmode","layer")
-	line.node.appendChild(ns)
-	line.add_keyframe(frame, ns)
-	self.update_scenes_of_dom()
-	pass
-    
-    def add_scene_on_dom(self, frameline, scenes_node):
-	doc = self.document
-	for start_idx, stop_idx, tween_type in frameline.get_frame_blocks():
-	    ref = frameline.get_frame_data(start_idx)
-	    tween_type_idx = self._frameline_tween_types.index(tween_type)
-	    tween_type_name = self._tween_type_names[tween_type_idx]
-	    
-	    scene_node = doc.createElement("ns0:scene")
-	    scenes_node.appendChild(scene_node)
-	    scene_node.setAttribute("start", str(start_idx + 1))
-	    if start_idx != stop_idx:
-		scene_node.setAttribute("end", str(stop_idx + 1))
-		pass
-	    scene_node.setAttribute("ref", ref.attribute("id"))
-	    scene_node.setAttribute("type", tween_type_name)
+	    layer_group = child
+	    layer = Layer(layer_group)
+	    layer.idx = len(layers)
+	    layers.append(layer)
+	    self.parse_layer(layer.idx)
 	    pass
 	pass
 
-    def update_scenes_of_dom(self):
-        doc = self.root
-	rdoc = self.document
-	for node in doc.childList():
-	    if node.name() == 'svg:metadata':
-	        for t in node.childList():
-		    if t.name() == "ns0:scenes":
-		        node.removeChild(t)
-			scenes = rdoc.createElement("ns0:scenes")
-			node.appendChild(scenes)
-			self.maxframe = 0
-			for layer in range(0, len(self._framelines)):
-			    lobj = self._framelines[layer]
-			    self.add_scene_on_dom(lobj, scenes)
-			    maxframe = lobj.max_frame()+1
-			    if self.maxframe < maxframe:
-				self.maxframe = maxframe
-			    pass
-			pass
-		    pass
+    ## \brief Add/insert a layer at given position.
+    #
+    # \param layer_idx is the position in the layer list.
+    #
+    def add_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
+
+    def rm_layer(self, layer_idx):
+	layers = self._layers
+
+	del layers[layer_idx]
+
+	for idx in range(layer_idx, len(layers)):
+	    layers[idx].idx = idx
+	    pass
+	pass
     pass
 
 class MBScene(MBScene_dom):
@@ -340,22 +618,22 @@
 			      TweenObject.TWEEN_TYPE_SCALE)
     _tween_type_names = ('normal', 'scale')
     
+    _num_frames_of_line = 100
+    
     def __init__(self, desktop, win, root=None):
+	super(MBScene, self).__init__()
+
 	self.desktop = desktop
 	self.window = win
-	self.layers = []
-	self.layers.append(Layer(None))
-	self.scenemap = None
 	self.top = None
 	self.last_update = None
 	pybInkscape.inkscape.connect('change_selection', self.show_selection)
 	self.last_select = None
-	self.lockui=False
-	self.tween=None
+	self._lockui = False
+	self.tween = None
 	self.document = None
 	self.root = root
-	self.framerate=12
-	self.maxframe=0
+	self.framerate = 12
 	self._disable_tween_type_selector = False
 	pass
 
@@ -378,220 +656,45 @@
 	    pass
 	pass
 
-    def confirm(self,msg):
-	vbox = gtk.VBox()
-	vbox.pack_start(gtk.Label(msg))
-	self.button = gtk.Button('OK')
-	vbox.pack_start(self.button)
-	self.button.connect("clicked", self.onQuit)
-	self.window.add(vbox)
-	pass
-    
-    def parseScene(self):
-	"""
-	In this function, we will collect all items for the current
-	scene and then relocate them back to the appropriate scene
-	object.
-	"""
-	self.layers = []
-	self.scenemap = None
-	doc = self.root
-
-	# TODO: Remove following code sicne this function is for parsing.
-	#       Why do this here?
-	addEventListener(doc,'DOMNodeInserted',self.updateUI,None)
-	addEventListener(doc,'DOMNodeRemoved',self.updateUI,None)
+    def removeKeyScene(self, frameline, frame_idx):
+	start, end, scene_type = frameline.get_frame_block(frame_idx)
+	scene_node = frameline.get_frame_data(start)
 	
-	doc.childList()
-	try:
-	    self.width = float(doc.getAttribute("width"))
-	    self.height= float(doc.getAttribute("height"))
-	except:
-	    self.width = 640
-	    self.height=480
+	frameline.rm_keyframe(start)
+	if start != end:
+	    frameline.rm_keyframe(end)
 	    pass
-	    
-	for node in doc.childList():
-	    print node.name()
-	    if node.name() == 'svg:metadata':
-		self.parseMetadata(node)
-		pass
-	    elif node.name() == 'svg:g':
-		oldscene = None
-		lyobj = Layer(node)
-		self.layers.append(lyobj)
-		lyobj.current_scene = []
-		for scene in node.childList():
-		    print scene.getCenter()
-		    if scene.name() == 'svg:g':
-		        try:
-			    label = scene.getAttribute('inkscape:label')
-			    if label == 'dup':
-				# TODO: remove this since this functio is for
-				#       parsing.  Why do this here?
-			        node.removeChild(scene)
-			except:
-			    pass
-
-			try:
-			    scene_id = scene.getAttribute('id')
-			    start, stop, tween_type = self.scenemap[scene_id]
-			except:
-			    lyobj.current_scene.append(scene)
-			    continue
+	
+	scene_group_id = scene_node.getAttribute('ref')
+	scene_group = self.get_node(scene_group_id)
+	scene_group.parent().removeChild(scene_group)
+	scene_node.parent().removeChild(scene_node)
 
-			lyobj.scenes.append(Scene(scene, start, stop,
-						  tween_type))
-			pass
-		    else:
-			lyobj.current_scene.append(scene)
-			pass
-		    pass
-		pass
-	    pass
-
-	# TODO: Remove following code, too.  It is unreasonable.
-	self.collectID()
-	self.dumpID()
-	pass
-
-    def collectID(self):
-	self.ID = {}
-	root = self.root
-	for n in root.childList():
-	    self.collectID_recursive(n)
-	    pass
-	pass
-    
-    def collectID_recursive(self,node):
 	try:
-	    self.ID[node.getAttribute('id')] = 1
-	except:
-	    pass
-	for n in node.childList():
-	    self.collectID_recursive(n)
-	    pass
-	pass
-    
-    def getLayer(self, layer):
-	for l in self.layers:
-	    if l.node.getAttribute('id') == layer:
-		return l
-	    pass
-	return None
-    
-    
-    def removeKeyScene(self):
-	nth = self.last_frame
-	y = self.last_line
-	rdoc = self.document
-	i = 0
-	layer = self.last_line
-	while i < len(layer._keys):
-	    s = layer._keys[i]
-	    print "nth:%d idx %d" % (nth,s.idx)
-	    if nth > s.idx:
-	        if i == len(layer._keys)-1:
-	            return
-	    if nth == s.idx:
-	        if s.left_tween:
-		    # This is left tween, we move the keyframe one frame ahead
-		    if s.idx == layer._keys[i-1].idx:
-			layer._keys[i].ref.parent().removeChild(layer._keys[i].ref)
-	                self.last_line.rm_keyframe(nth)
-	                self.last_line.rm_keyframe(nth-1)
-		    else:
-		        s.idx = s.idx-1
-		else:
-		    layer._keys[i].ref.parent().removeChild(layer._keys[i].ref)
-		    if s.right_tween:
-		        self.last_line.rm_keyframe(layer._keys[i+1].idx)
-	                self.last_line.rm_keyframe(nth)
-		    else:
-	                self.last_line.rm_keyframe(nth)
-
-		self.update_scenes_of_dom()
-		self.last_line._draw_all_frames()
-	        self.last_line.update()
-		return
-	    i = i + 1
+	    frameline.duplicateGroup.setAttribute('style', 'display: none')
+	except AttributeError:
 	    pass
 	pass
     
     def extendScene(self):
-	nth = self.last_frame
-	layer = self.last_line
-	i = 0
-	while i < len(layer._keys):
-	    s = layer._keys[i]
-	    if s.right_tween:
-	        if nth > s.idx:
-		    if nth <= layer._keys[i+1].idx:
-		        return
-		    try:
-		        if nth <= layer._keys[i+2].idx:
-			    layer._keys[i+1].idx = nth
-			    layer.draw_all_frames()
-			    self.update_scenes_of_dom()
-			    self.setCurrentScene(nth)
-			    self.last_line.update()
-			    return
-			else:
-			    # We may in the next scene
-			    i = i + 2
-			    pass
-		    except:
-		        # This is the last keyframe, extend the keyframe by 
-			# relocate the location of the keyframe
-			layer._keys[i+1].idx = nth
-			layer._draw_all_frames()
-			self.update_scenes_of_dom()
-			self.last_line.update()
-			self.setCurrentScene(nth)
-			return
-		else:
-		    # We are in the front of all keyframes
-		    return
-	    else:
-		# This is a single keyframe
-		if nth < s.idx:
-		    return
-		if nth == s.idx:
-		    return
-		try:
-		    if nth < layer._keys[i+1].idx:
-			# We are after a single keyframe and no scene is 
-			# available here. Create a new tween here
-			idx = layer._keys[i].idx
-			layer.add_keyframe(nth,layer._keys[i].ref)
-			layer.tween(idx)
-		        layer._draw_all_frames()
-			self.update_scenes_of_dom()
-			self.setCurrentScene(nth)
-			self.last_line.update()
-			return
-		    else:
-			# We may in the next scene
-			i = i + 1
-			pass
-		    pass
-		except:
-		    # This is the last scene, create a new one
-		    idx = layer._keys[i].idx
-		    layer.add_keyframe(nth,layer._keys[i].ref)
-		    layer.tween(idx)
-		    layer._draw_all_frames()
-		    self.update_scenes_of_dom()
-		    self.setCurrentScene(nth)
-		    self.last_line.update()
-		    return
-		pass
+	frame_idx = self.last_frame
+	frameline = self.last_line
+
+	start, end, scene_type = frameline.get_frame_block_floor(frame_idx)
+	if frame_idx <= end:
+	   return
+
+	if start < end:
+	    frameline.rm_keyframe(end)
 	    pass
+	
+	scene_node = frameline.get_frame_data(start)
+	self._chg_scene_node(scene_node, end=frame_idx)
+	frameline.add_keyframe(frame_idx)
+	frameline.tween(start, scene_type)
 	pass
-
-
     
-    def setCurrentScene(self,nth):
+    def setCurrentScene(self, idx):
 	"""
 	    Update the scene group according to the curretn scene
 	    data. There are a couple of cases.
@@ -610,40 +713,58 @@
 	    create this group if it is not
 	    available.
 	"""
-	self.current = nth
+	self.current = idx
 	self.tween.updateMapping()
-	idx = nth - 1
-	for layer in self._framelines:
+	for frameline in self._framelines:
 	    i=0
 
 	    # Check the duplicated scene group and create it if it is not available
 	    try:
-		layer.duplicateGroup.setAttribute("style","display:none")
+		frameline.duplicateGroup.setAttribute("style","display:none")
 	    except:
-	        print "*"*40
-	        layer.duplicateGroup = self.document.createElement("svg:g")
-	        layer.duplicateGroup.setAttribute("inkscape:label","dup")
-	        layer.duplicateGroup.setAttribute("sodipodi:insensitive","1")
-	        layer.duplicateGroup.setAttribute("style","")
-	        layer.layer.node.appendChild(layer.duplicateGroup)
+	        print "*" * 40
+		layer_idx = frameline.layer_idx
+		layer = self._layers[layer_idx]
+		for child in layer.group.childList():
+		    label = child.getAttribute('inkscape:label')
+		    if label == 'dup':
+			frameline.duplicateGroup = child
+			break
+		    pass
+		else:
+		    duplicateGroup = self.document.createElement("svg:g")
+		    duplicateGroup.setAttribute("inkscape:label","dup")
+		    duplicateGroup.setAttribute("sodipodi:insensitive","1")
+		    duplicateGroup.setAttribute("style","")
+		    
+		    layer.group.appendChild(duplicateGroup)
+		    frameline.duplicateGroup = duplicateGroup
+		    pass
 	        pass
+	    
 	    # Create a new group
-	    for start_idx, stop_idx, tween_type in layer.get_frame_blocks():
+	    for start_idx, stop_idx, tween_type in frameline.get_frame_blocks():
 		if start_idx == stop_idx:
-		    scene_group = layer.get_frame_data(start_idx)
+		    scene_node = frameline.get_frame_data(start_idx)
+		    scene_group_id = scene_node.getAttribute('ref')
+		    scene_group = self.get_node(scene_group_id)
 		    if idx == start_idx:
 			scene_group.setAttribute('style', '')
 		    else:
 			scene_group.setAttribute('style', 'display: none')
 			pass
 		elif idx == start_idx:
-		    layer.duplicateGroup.setAttribute("style","display:none")
-		    scene_group = layer.get_frame_data(start_idx)
+		    frameline.duplicateGroup.setAttribute("style","display:none")
+		    scene_node = frameline.get_frame_data(start_idx)
+		    scene_group_id = scene_node.getAttribute('ref')
+		    scene_group = self.get_node(scene_group_id)
 		    scene_group.setAttribute("style","")
-		elif start_idx <= idx and stop_idx >= idx:
-		    scene_group = layer.get_frame_data(start_idx)
+		elif start_idx < idx and stop_idx >= idx:
+		    scene_node = frameline.get_frame_data(start_idx)
+		    scene_group_id = scene_node.getAttribute('ref')
+		    scene_group = self.get_node(scene_group_id)
 		    scene_group.setAttribute("style","display:none")
-		    layer.duplicateGroup.setAttribute("style","")
+		    frameline.duplicateGroup.setAttribute("style","")
 		    tween_type_idx = \
 			self._frameline_tween_types.index(tween_type)
 		    tween_obj_tween_type = \
@@ -651,31 +772,37 @@
 		    
 		    try:
 			next_idx, next_stop_idx, next_tween_type = \
-			    layer.get_frame_block(stop_idx + 1)
+			    frameline.get_frame_block(stop_idx + 1)
 		    except:
-			next_scene_group = scene_group
+			next_scene_node = scene_node
 		    else:
-			next_scene_group = layer.get_frame_data(next_idx)
+			next_scene_node = frameline.get_frame_data(next_idx)
 			pass
+
+		    next_scene_group_id = next_scene_node.getAttribute('ref')
+		    next_scene_group = self.get_node(next_scene_group_id)
 		    
 		    nframes = stop_idx - start_idx + 1
 		    percent = float(idx - start_idx) / nframes
-		    self.tween.updateTweenContent(layer.duplicateGroup,
+		    print tween_obj_tween_type
+		    self.tween.updateTweenContent(frameline.duplicateGroup,
 						  tween_obj_tween_type,
 						  scene_group,
 						  next_scene_group,
 						  percent)
 		else:
-		    scene_group = layer.get_frame_data(start_idx)
+		    scene_node = frameline.get_frame_data(start_idx)
+		    scene_group_id = scene_node.getAttribute('ref')
+		    scene_group = self.get_node(scene_group_id)
 		    scene_group.setAttribute("style","display:none")
 		    pass
 		pass
 	    pass
 	pass
 
-    def enterGroup(self,obj):
-        for l in self.layers:
-	    for s in l.node.childList():
+    def enterGroup(self, obj):
+        for l in self._layers:
+	    for s in l.group.childList():
 	        if s.getAttribute('id') == obj.getAttribute("id"):
 		    self.desktop.setCurrentLayer(s.spitem)
 		    pass
@@ -689,7 +816,9 @@
 	except:
 	    return
 
-	scene_group = frameline.get_frame_data(start)
+	scene_node = frameline.get_frame_data(start)
+	scene_group_id = scene_node.getAttribute('ref')
+	scene_group = self.get_node(scene_group_id)
 	self.enterGroup(scene_group)
 	self.setTweenType(tween_type)
 	pass
@@ -701,22 +830,13 @@
 	self._disable_tween_type_selector = False
 	pass
 	
-    def newCell(self,file):
-	img = gtk.Image()
-	img.set_from_file(file)
-	btn = gtk.EventBox()
-	btn.add(img)
-	btn.connect("button_press_event", self.cellSelect)
-	btn.modify_bg(gtk.STATE_NORMAL, btn.get_colormap().alloc_color("gray"))
-	return btn
-    
     def onCellClick(self, line, frame, but):
 	self.last_line = line
 	self.last_frame = frame
 	self.last_line.active_frame(frame)
-	self.lockui = True
+	self._lockui = True
         self.doEditScene(None)
-	self.lockui = False
+	self._lockui = False
         pass
         
     def _remove_active_frame(self,widget,event):
@@ -730,25 +850,128 @@
 		pass
 	    pass
 	pass
-	    
-    def _create_framelines(self):
-	if not hasattr(self, 'scrollwin'):
-	    self.scrollwin = gtk.ScrolledWindow()
-	    self.scrollwin.set_policy(gtk.POLICY_AUTOMATIC,
-				      gtk.POLICY_AUTOMATIC)
-	    self.scrollwin.set_size_request(-1,150)
-	    vbox = gtk.VBox()
-	    vbox.show()
-	    self.scrollwin.add_with_viewport(vbox)
-	    self.scrollwin_vbox = vbox
-	else:
-	    for c in self.scrollwin_vbox.get_children():
-	    	self.scrollwin_vbox.remove(c)
-	    vbox = self.scrollwin_vbox
+
+    ## \brief Add a frameline into the frameline box for the given layer.
+    #
+    def _add_frameline(self, layer_idx):
+	if layer_idx > len(self._framelines):
+	    raise ValueError, 'layer number should be a consequence'
+
+	vbox = self._frameline_vbox
+	
+	line = frameline(self._num_frames_of_line)
+	line.set_size_request(self._num_frames_of_line * 10, 20)
+	
+	hbox = gtk.HBox()
+	label = gtk.Label('')
+	label.set_size_request(100,0)
+	hbox.pack_start(label,expand=False, fill=True)
+	hbox.pack_start(line)
+	vbox.pack_start(hbox, False)
+	
+	if layer_idx != len(self._framelines):
+	    vbox.reorder_child(hbox, layer_idx + 1) # there is ruler at pos 0
 	    pass
 	
-	nframes = 100
+	self._framelines[layer_idx: layer_idx] = [line]
+	
+	line.label = label
+	line.layer_idx = layer_idx
+	line.connect(line.FRAME_BUT_PRESS, self.onCellClick)
+	line.connect('motion-notify-event', self._remove_active_frame)
+	pass
+    
+    ## \brief Remove the given frameline from the frameline box.
+    #
+    def _remove_frameline(self, layer_idx):
+	vbox = self._frameline_vbox
+	line = self._framelines[layer_idx]
+	
+	hbox = line.parent
+	vbox.remove(hbox)
+	del self._framelines[layer_idx]
+	pass
+
+    ## \brief Remove the layer that lost the layer group.
+    #
+    # This function is called when a layer group being removed from the
+    # DOM-tree.
+    def _remove_lost_group_layer(self, layer_idx):
+	layer = self._layers[layer_idx]
+	frameline = self._framelines[layer_idx]
+	for start, end, tween_type in frameline.get_frame_blocks():
+	    scene_node = frameline.get_frame_data(start)
+	    self._rm_scene_node(scene_node)
+	    pass
+
+	self._remove_frameline(layer_idx) # TODO
+	del self._layers[layer_idx]
+	pass
+
+    ## \brief Make status of layers is updated when DOM is changed.
+    #
+    # When DOM-tree is changed, this function make sure layer information is up
+    # to date.
+    def _make_layers_integral(self):
+	root = self._root
+	root_id = root.getAttribute('id')
 	
+	# Remove group of removed layers
+	layer_idx = 0
+	while layer_idx < len(self._layers):
+	    layer = self._layers[layer_idx]
+	    
+	    if layer.group.name() != 'svg:g':
+		self._remove_lost_group_layer(layer_idx) # TODO
+		continue
+
+	    parent = layer.group.parent()
+	    if parent.name() != 'svg:svg':
+		self._remove_lost_group_layer(layer_idx)
+		continue
+
+	    if parent.getAttribute('id') != root_id:
+		self._remove_lost_group_layer(layer_idx)
+		continue
+
+	    layer_idx = layer_idx + 1
+	    pass
+
+	# Add new layers
+	layer_idx = 0
+	for child in root.childList():
+	    if child.name() != 'svg:g':
+		continue
+	    
+	    layer = self._layers[layer_idx]
+	    layer_group_id = layer.group.getAttribute('id')
+	    child_id = child.getAttribute('id')
+	    if layer_group_id != child_id:
+		self.add_layer(layer_idx, child)
+		self.parse_layer(layer_idx)
+		self._add_frameline(layer_idx)
+		pass
+	    
+	    layer_idx = layer_idx + 1
+	    pass
+	pass
+
+    def _init_framelines(self):
+	self._framelines = []
+	
+	box = gtk.ScrolledWindow()
+	self._frameline_box = box
+	box.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+	box.set_size_request(-1, 150)
+	vbox = gtk.VBox()
+	self._frameline_vbox = vbox
+	box.add_with_viewport(vbox)
+	
+	nframes = self._num_frames_of_line
+	
+	#
+	# Set up a ruler
+	#
 	ruler = frameruler(nframes)
 	ruler.set_size_request(nframes * 10, 20)
 	ruler.show()
@@ -758,56 +981,16 @@
 	hbox.pack_start(label,expand=False,fill=True)
 	hbox.pack_start(ruler)
 	vbox.pack_start(hbox, False)
-
-	#
-	# Add a frameline for each layer
-	#
-	self._framelines = []
-	for i in range(len(self.layers)-1,-1,-1):
-	    line = frameline(nframes)
-	    hbox = gtk.HBox()
-	    label = gtk.Label(self.layers[i].node.getAttribute("inkscape:label"))
-	    label.set_size_request(100,0)
-	    hbox.pack_start(label,expand=False,fill=True)
-	    hbox.pack_start(line)
-	    line.set_size_request(nframes * 10, 20)
-	    vbox.pack_start(hbox, False)
-	    line.label = label
-	    self._framelines.append(line)
-	    line.connect(line.FRAME_BUT_PRESS, self.onCellClick)
-	    line.nLayer = i
-	    line.node = self.layers[i].node
-	    line.layer = self.layers[i]
-	    line.connect('motion-notify-event', self._remove_active_frame)
-	    pass
+	
 	vbox.show_all()
 	pass
 
-    ## \brief Update conetent of frameliens according layers.
-    #
-    def _update_framelines(self):
-	for frameline in self._framelines:
-	    layer = frameline.layer
-	    if frameline.node.getAttribute("inkscape:label")==None:
-	        frameline.label.set_text('???')
-	    else:
-	        frameline.label.set_text(frameline.node.getAttribute("inkscape:label"))
-	    last_scene = None
-	    for scene in layer.scenes:
-		if last_scene and last_scene.end == scene.start:
-		    frameline.setRightTween(last_scene.end)
-		else:
-		    frameline.add_keyframe(scene.start-1,scene.node)
-		last_scene = scene
-		if scene.start != scene.end:
-		    frameline.add_keyframe(scene.end-1,scene.node)
-		    tween_type_idx = self._tween_type_names.index(scene.type)
-		    tween_type = self._frameline_tween_types[tween_type_idx]
-		    frameline.tween(scene.start-1, tween_type)
-		pass
+    def _add_frameline_for_layers(self):
+	for layer_idx in range(len(self._layers)):
+	    self._add_frameline(layer_idx)
 	    pass
 	pass
-
+    
     def cellSelect(self, cell, data):
 	if self.last_cell:
 	    color = self.last_cell.get_colormap().alloc_color("gray")
@@ -820,25 +1003,28 @@
 	pass
 
     def duplicateKeyScene(self):
-        self.last_line.add_keyframe(self.last_frame)
         # Search for the current scene
-	i = 0
-	while i < len(self.last_line._keys):
-	    key = self.last_line._keys[i]
-	    if key.idx == self.last_frame:
-	        if i == 0:
-		    # This is the first frame, we can not duplicate it
-		    self.last_line.rm_keyframe(self.last_frame)
-		    return
-		node = self.duplicateSceneGroup(last_key.ref.getAttribute("id"))
-	        key.ref = node
-		self.update_scenes_of_dom()
-		self.updateUI()
-	        self.doEditScene(None)
-		return
-	    last_key = key
-	    i = i + 1
-	    pass
+	frameline = self.last_line
+	frame_idx = self.last_frame
+	
+	try:
+	    start, end, scene_type = frameline.get_frame_block_floor(frame_idx)
+	except:
+	    return
+	if end >= frame_idx:
+	    return
+
+	prev_scene_node = frameline.get_frame_data(start)
+	prev_scene_group_id = prev_scene_node.getAttribute('ref')
+	
+	scene_group = self.duplicateSceneGroup(prev_scene_group_id)
+	scene_group_id = scene_group.getAttribute('id')
+	scene_node = self._add_scene_node(frame_idx, frame_idx,
+					  ref=scene_group_id)
+
+	frameline.add_keyframe(frame_idx, scene_node)
+
+	self.setCurrentScene(frame_idx)
 	pass
 
     def duplicateSceneGroup(self,gid):
@@ -859,10 +1045,10 @@
 	    pass
 	if orig == None:
 	    return None
-	ns = orig.duplicate(rdoc)
+	scene_group = orig.duplicate(rdoc)
 
 	old_nodes = _travel_DOM(orig)
-	new_nodes = _travel_DOM(ns)
+	new_nodes = _travel_DOM(scene_group)
 	for old_node in old_nodes:
 	    print old_node
 	    old_node_id = old_node.getAttribute('id')
@@ -870,43 +1056,42 @@
 	    new_node.setAttribute('ns0:duplicate-src', old_node_id)
 	    pass
 
-	gid = self.last_line.node.getAttribute("inkscape:label")+self.newID()
-	self.ID[gid]=1
-	ns.setAttribute("id",gid)
-	ns.setAttribute("inkscape:groupmode","layer")
-	self.last_line.node.appendChild(ns)
-	return ns
+	layer = self._layers[self.last_line.layer_idx]
+	gid = layer.group.getAttribute("inkscape:label")+self.new_id()
+	scene_group.setAttribute("id",gid)
+	scene_group.setAttribute("inkscape:groupmode","layer")
+	layer.group.appendChild(scene_group)
+	return scene_group
     
     def doEditScene(self, w):
-	self.setCurrentScene(self.last_frame+1)
+	self.setCurrentScene(self.last_frame)
 	self.selectSceneObject(self.last_line, self.last_frame)
 	pass
     
     def doInsertKeyScene(self,w):
-	self.lockui=True
+	self._lockui=True
 	self.insertKeyScene(self.last_line, self.last_frame)
 	self.selectSceneObject(self.last_line, self.last_frame)
-	self.lockui=False
+	self._lockui=False
 	# self.grid.show_all()
 	return
     
     def doDuplicateKeyScene(self,w):
-	self.lockui = True
+	self._lockui = True
         self.duplicateKeyScene()
-	self.lockui = False
+	self._lockui = False
 
     def doRemoveScene(self,w):
-	self.lockui = True
-	self.removeKeyScene()
-	self.lockui = False
+	self._lockui = True
+	self.removeKeyScene(self.last_line, self.last_frame)
+	self._lockui = False
 	return
 
     
     def doExtendScene(self,w):
-	self.lockui = True
+	self._lockui = True
 	self.extendScene()
-	self.lockui = False
-	#self.grid.show_all()
+	self._lockui = False
 	pass
 
     def changeObjectLabel(self,w):
@@ -928,36 +1113,30 @@
 	"""
 	if self.btnRun.get_label() == "Run":
 	    self.btnRun.set_label("Stop")
-	    self.lockui = True
-            self.last_update = glib.timeout_add(1000/self.framerate,self.doRunNext)
+	    self._lockui = True
+	    tmout = 1000 / self.framerate
+            self.last_update = glib.timeout_add(tmout, self.doRunNext)
 	else:
 	    self.btnRun.set_label("Run")
 	    glib.source_remove(self.last_update)
-	    self.lockui = False
+	    self._lockui = False
 	    pass
+	pass
 
     def doRunNext(self):
-	if self.current >= self.maxframe:
+	if self.current > self._maxframe:
 	    self.current = 0
+	    pass
 	try:
-	    self.setCurrentScene(self.current+1)
+	    self.setCurrentScene(self.current)
 	except:
 	    traceback.print_exc()
 	    raise
-        self.last_update = glib.timeout_add(1000/self.framerate,self.doRunNext)
+	self.current = self.current + 1
+	tmout = 1000 / self.framerate
+        self.last_update = glib.timeout_add(tmout, self.doRunNext)
+	pass
 
-    def doInsertScene(self,w):
-	self.lockui=True
-	self.last_line.insert_frame(self.last_frame)
-	self.update_scenes_of_dom()
-	self.lockui=False
-
-    def doRemoveScene(self,w):
-	self.lockui=True
-	self.last_line.remove_frame(self.last_frame)
-	self.update_scenes_of_dom()
-	self.lockui=False
-    
     def addButtons(self,hbox):
 	btn = gtk.Button('Insert Key')
 	btn.connect('clicked',self.doInsertKeyScene)
@@ -975,18 +1154,6 @@
 	btn.connect('clicked', self.doDuplicateKeyScene)
 	hbox.pack_start(btn,expand=False,fill=False)
 
-	btn=gtk.Button('Duplicate Key')
-	btn.connect('clicked', self.doDuplicateKeyScene)
-	hbox.pack_start(btn,expand=False,fill=False)
-
-	btn=gtk.Button('Insert')
-	btn.connect('clicked', self.doInsertScene)
-	hbox.pack_start(btn,expand=False,fill=False)
-
-	btn=gtk.Button('Remove')
-	btn.connect('clicked', self.doRemoveScene)
-	hbox.pack_start(btn,expand=False,fill=False)
-
 	btn=gtk.Button('Run')
 	btn.connect('clicked', self.doRun)
 	self.btnRun = btn
@@ -1003,17 +1170,16 @@
 	if self.last_line == None:
 	    return
 	frameline = self.last_line
-	idx = self.last_frame
-        i = 0
-	found = -1
-	for start_idx, stop_idx, tween_type in frameline.get_frame_blocks():
-	    if start_idx <= idx and stop_idx >= idx:
-		n = self.tweenTypeSelector.get_active()
-		new_tween_type = MBScene._frameline_tween_types[n]
-		self.last_line.set_tween_type(start_idx, new_tween_type)
-		self.update_scenes_of_dom()
-		break
-	    pass
+	start, end, old_tween_type = frameline.get_frame_block(self.last_frame)
+
+	type_idx = self.tweenTypeSelector.get_active()
+	tween_type = MBScene._frameline_tween_types[type_idx]
+	type_name = self._tween_type_names[type_idx]
+	
+	frameline.tween(start, tween_type)
+	
+	scene_node = frameline.get_frame_data(start)
+	self._chg_scene_node(scene_node, tween_type=type_name)
 	pass
 
     def addTweenTypeSelector(self,hbox):
@@ -1041,8 +1207,8 @@
 	gtk.main_quit()
 	pass
 
-    def updateUI(self,node=None,arg=None):
-        if self.lockui: return
+    def updateUI(self, node=None, child=None, arg=None):
+        if self._lockui: return
 	
         if self.last_update!= None:
             glib.source_remove(self.last_update)
@@ -1051,9 +1217,9 @@
 	pass
     
     def _updateUI(self,node=None,arg=None):
-	self.parseScene()
-	self._create_framelines()
-	self._update_framelines()
+	self._lockui = True
+	self._make_layers_integral()
+	self._lockui = False
 	pass
     
     def show(self):
@@ -1063,11 +1229,15 @@
 	    pass
 	
 	self.document = self.desktop.doc().rdoc
+	self.handle_doc_root(self.document, self.root)
 	self.tween = TweenObject(self.document, self.root)
-	self._updateUI()
+	self._init_framelines()
+	self._add_frameline_for_layers()
+	
 	if self.top == None:
 	    self.top = gtk.VBox(False,0)
-	    self.desktop.getToplevel().child.child.pack_end(self.top,expand=False)
+	    toplevel = self.desktop.getToplevel()
+	    toplevel.child.child.pack_end(self.top,expand=False)
 	else:
 	    self.top.remove(self.startWindow)
 	    pass
@@ -1075,7 +1245,7 @@
 	vbox = gtk.VBox(False,0)
 	self.startWindow = vbox
 	self.top.pack_start(vbox,expand=False)
-	vbox.pack_start(self.scrollwin,expand=False)
+	vbox.pack_start(self._frameline_box,expand=False)
 	hbox=gtk.HBox(False,0)
 	self.addButtons(hbox)
 	vbox.pack_start(hbox,expand=False)
--- a/pyink/frameline.py	Tue Jan 04 01:13:23 2011 +0800
+++ b/pyink/frameline.py	Tue Jan 04 10:08:02 2011 +0800
@@ -655,11 +655,6 @@
 	    pass
         pass
 
-    ## Set the frame @idx as the right of a tween
-    def set_right_tween(self,idx):
-	pos = self._find_keyframe(idx)
-	self._keys[pos].right_tween = TRue
-
     def remove_frame(self, idx):
 	pos = self._find_keyframe_floor(idx)
 	if pos != -1:
@@ -721,14 +716,16 @@
             if key.right_tween:
 		right_key = self._keys[key_pos]
                 right_key.left_tween = False
-                redraw_range = (right_key.idx, idx + 1)
+                redraw_range = (key.idx, right_key.idx + 1)
             else:
                 left_key = self._keys[key_pos - 1]
                 left_key.right_tween = False
-                redraw_range = (idx, left_key.idx + 1)
+                redraw_range = (left_key.idx, key.idx + 1)
                 pass
-                self._draw_frame(i)
-                pass
+	    for i in range(*redraw_range):
+		self._draw_frame(i)
+		pass
+	    pass
         else:
             self._draw_frame(idx)
             pass
@@ -764,11 +761,14 @@
 		return key.right_tween_type
 	    pass
 	pass
+    
     ## Get the maximum frame number in a layer(frameline)
     # Return the frame number
     def max_frame(self):
-	if len(self._keys) == 0: return 0
-        return self._keys[len(self._keys)-1].idx
+	keys = self._keys
+	if not keys:
+	    return 0
+        return keys[-1].idx
 
     ## \bref Return range of blocks of conesequence frames (tweens).
     #
@@ -795,23 +795,46 @@
     # - If the indexed frame is a key frame with no right_tween, returns the
     #   range that start and stop are both equivalent to idx.
     #
+    # - If both earlier two are not meat, the previesou keyframe or tween is
+    #   returned.
+    #
+    # - Otherwise, raise an exception.
+    #
+    # \param idx is the index number of a frame.
+    # \return A tuple of (start, stop, tween_type)
+    #
+    def get_frame_block_floor(self, idx):
+	pos = self._find_keyframe_floor(idx)
+	if pos != -1:
+	    key = self._keys[pos]
+	    if key.right_tween:
+		next_key = self._keys[pos + 1]
+		return key.idx, next_key.idx, key.right_tween_type
+	    if key.left_tween:
+		prev_key = self._keys[pos - 1]
+		return prev_key.idx, key.idx, prev_key.right_tween_type
+	    return key.idx, key.idx, 0
+	raise ValueError, \
+	    'the frame specified by idx is not in any tween or a key frame'
+
+    ## \brief Return the range of a block of consequence frames (tween).
+    # 
+    # - If the index frame is in a tween, it returns the range of the tween.
+    #
+    # - If the indexed frame is a key frame with no right_tween, returns the
+    #   range that start and stop are both equivalent to idx.
+    #
     # - Otherwise, raise an exception.
     #
     # \param idx is the index number of a frame.
     # \return A tuple of (start, stop, tween_type)
     #
     def get_frame_block(self, idx):
-	pos = self._find_keyframe_floor(idx)
-	if pos != -1:
-	    key = self._keys[pos]
-	    if key.right_tween:
-		next_key = self._keys[pos + 1]
-		return key.idx, next_key.idx, key.right_tween_type
-	    elif key.idx == idx:
-		return key.idx, key.idx, 0
-	    pass
-	raise ValueError, \
-	    'the frame specified by idx is not in any tween or a key frame'
+	start, stop, tween_type = self.get_frame_block_floor(idx)
+	if stop < idx:
+	    raise ValueError, \
+		'the frame specified by idx is not in any tween or a key frame'
+	return start, stop, tween_type
     
     def get_frame_data(self, idx):
 	pos = self._find_keyframe(idx)
--- a/pyink/tween.py	Tue Jan 04 01:13:23 2011 +0800
+++ b/pyink/tween.py	Tue Jan 04 10:08:02 2011 +0800
@@ -61,6 +61,7 @@
 		pass
 	    node = node.next()
 	    pass
+	
 	# Collect all nodes in start scene
 	start_nodes = {}
 	node = start_scene_group.firstChild()
@@ -73,7 +74,6 @@
 	    node = node.next()
 	    pass
 
-
 	# Remove duplicate nodes that is not in the set of start nodes
 	for node_ref in dup_nodes:
 	    if node_ref not in start_nodes: