view pyink/MBScene.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 1b1eb8f9a866
children b241f9768833
line wrap: on
line source

#!/usr/bin/python
# -*- indent-tabs-mode: t; tab-width: 8; python-indent: 4; fill-column: 79 -*-
# vim: sw=4:ts=8:sts=4:textwidth=79
import pygtk
import gtk
import glib
import traceback
import pybInkscape
from tween import TweenObject
from domview_ui import MBScene_domview_ui

# Please refer to
# http://www.assembla.com/wiki/show/MadButterfly/Inkscape_extention
# for the designed document.


# Algorithm:
# 
# We will parse the first two level of the SVG DOM. collect a table of
# layer and scene.
# - 1. Collect the layer table which will be displayed as the first
#      column of the grid.
# - 2. Get the maximum scene number. This will decide the size of the
#      grid.
# - 3. When F6 is pressed, we will check if this scene has been
#      defined. This can be done by scan all second level group and
#      check if the current scene number is within the range specified
#      by scene field. The function IsSceneDefined(scene) can be used
#      for this purpose.
# - 4. If this is a new scene, we will append a new group which
#      duplication the content of the last scene in the same
#      group. The scene field will contain the number from the last
#      scene number of the last scene to the current scenen
#      number. For example, if the last scene is from 4-7 and the new
#      scene is 10, we will set the scene field as "8-10".
# - 5. If this scene are filled screne, we will split the existing
#       scene into two scenes with the same content.
#

## \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


## \brief MBScene connect GUI and DOM-tree
#
# This method accepts user actions and involves MBScene_domview_ui to update
# data on the document.
#
class MBScene(object):
    _tween_obj_tween_types = (TweenObject.TWEEN_TYPE_NORMAL,
			      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.top = None
	self.last_update = None
	pybInkscape.inkscape.connect('change_selection', self.on_selection)
	self.last_select = None
	self._lockui = False
	self.tween = None
	self.document = None
	self._root = root
	self.framerate = 12
	self._disable_tween_type_selector = False
	self.current = 0

	self._domview = MBScene_domview_ui()
	pass

    def change_active_frame(self, node):
	"""
	    Change the active frame to the current selected node. This will
	    tell users where the current node is.
	"""
	while node:
	    try:
		node_id = node.getAttribute('id')
	    except:
		node = node.parent()
		continue
	    
	    try:
		layer_idx, (start, end, tween_type) = \
		    self._domview.find_key_from_group(node_id)
	    except:
		pass
	    else:
		self._domview.set_active_layer_frame(layer_idx, start)
		break
	    
	    node = node.parent()
	    pass
	pass

    def insertKeyScene(self, layer_idx, frame_idx):
	"""
	Insert a new key scene into the stage. If the nth is always a
	key scene, we will return without changing anything.  If the
	nth is a filled scene, we will break the original scene into
	two parts. If the nth is out of any scene, we will append a
	new scene.

	"""
	try:
	    self._domview.mark_key(layer_idx, frame_idx)
	except ValueError:	# existed key frame
	    pass
	pass

    def removeKeyScene(self, layer_idx, frame_idx):
	self._domview.unmark_key(layer_idx, frame_idx)
	self.setCurrentScene(frame_idx)
	pass
    
    def extendScene(self):
	layer_idx, frame_idx = self._domview.get_active_layer_frame()
	start, end, tween_type = \
	    self._domview.get_left_key(layer_idx, frame_idx)
	tween_len = frame_idx - start + 1
	self._domview.tween(layer_idx, start, tween_len, tween_type)
	
	scene_group = self._domview.get_key_group(layer_idx, start)
	self._enterGroup(scene_group)
	pass
    
    def setCurrentScene(self, idx):
	"""
	    Update the scene group according to the curretn scene
	    data. There are a couple of cases.
	    1. If the type of the scene is normal, we display it when 
	    it contains the current frame. Otherwise hide it.
	    2. If the type of the scene is relocate or scale, we need 
	       to duplicate the scene group and then modify its 
	       transform matrix according to the definition of the 
	       scene. Then, hide the original scenr group and display 
	       the duplciate scene group. In addition, we may need to 
	       delete the old duplicated scene group as well.

	    For each layer, we will always use the duplicated scene 
	    group whose name as dup.
	    We will put the duplicated scene group inside it. We will 
	    create this group if it is not
	    available.
	"""
	self.current = idx
	self.tween.updateMapping()
	for layer_idx in range(self._domview.get_layer_num()):
	    dup_group = self._domview.get_layer_dup_group(layer_idx)
	    dup_group.setAttribute('style', 'display: none')

	    all_key_tweens = self._domview.get_layer_keys(layer_idx)
	    for start, end, tween_type in all_key_tweens:
		if start == idx: # at key frame
		    scene_group = \
			self._domview.get_key_group(layer_idx, start)
		    scene_group.setAttribute('style', '')
		elif start < idx and end >= idx: # in Tween
		    dup_group.setAttribute('style', '')
		    scene_group = \
			self._domview.get_key_group(layer_idx, start)
		    scene_group.setAttribute('style', 'display: none')
		    
		    try:
			next_scene_group = \
			    self._domview.get_key_group(layer_idx, end + 1)
		    except:	# no next key frame
			next_scene_group = scene_group
			pass

		    tween_obj_type = self._tween_obj_tween_types[tween_type]
		    nframes = end - start + 1
		    percent = float(idx - start) / nframes
		    self.tween.updateTweenContent(dup_group,
						  tween_obj_type,
						  scene_group,
						  next_scene_group,
						  percent)
		    pass
		else:		# this scene should not be showed.
		    scene_group = \
			self._domview.get_key_group(layer_idx, start)
		    scene_group.setAttribute('style', 'display: none')
		    pass
		pass
	    pass
	pass
    
    def _enterGroup(self, scene_group):
	self.desktop.setCurrentLayer(scene_group.spitem)
	pass
    
    def setTweenType(self, tween_type):
	self._disable_tween_type_selector = True
	self.tweenTypeSelector.set_active(tween_type)
	self._disable_tween_type_selector = False
	pass
	
    def selectSceneObject(self, layer_idx, frame_idx):
	try:
	    start, stop, tween_type = \
		self._domview.get_key(layer_idx, frame_idx)
	except:
	    return

	scene_group = self._domview.get_key_group(layer_idx, start)
	self._enterGroup(scene_group)
	self.setTweenType(tween_type)
	pass

    def duplicateKeyScene(self):
        # Search for the current scene
	layer_idx, frame_idx = self._domview.get_active_layer_frame()

	try:
	    left_start, left_end, left_tween_type = \
		self._domview.get_left_key(layer_idx, frame_idx)
	except:
	    return
	if left_end >= frame_idx:
	    return

	self._domview.mark_key(layer_idx, frame_idx)
	
	scene_group = self._domview.get_key_group(layer_idx, frame_idx)
	left_scene_group = \
	    self._domview.get_key_group(layer_idx, left_start)
	
	dup_group = self._duplicate_group(left_scene_group, scene_group)

	self.setCurrentScene(frame_idx)
	pass

    ## \brief Duplicate children of a group.
    #
    # Duplicate children of a group, and append them to another group.
    #
    def _duplicate_group(self, src_group, dst_group):
	# Search for the duplicated group
        root = self._root
	doc = self.document
	
	dup_group = src_group.duplicate(doc)
	for child in dup_group.childList():
	    dup_group.removeChild(child) # prvent from crash
	    dst_group.appendChild(child)
	    pass

	old_nodes = _DOM_iterator(src_group)
	new_nodes = _DOM_iterator(dst_group)
	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)
	    pass
	pass
    
    def changeObjectLabel(self,w):
	o = self.desktop.selection.list()[0]
	o.setAttribute("inkscape:label", self.nameEditor.get_text())
	pass

    def addNameEditor(self,hbox):
	self.nameEditor = gtk.Entry(max=40)
	hbox.pack_start(self.nameEditor,expand=False,fill=False)
	self.editDone = gtk.Button('Set')
	hbox.pack_start(self.editDone,expand=False,fill=False)
	self.editDone.connect('clicked', self.changeObjectLabel)
	pass

    def addTweenTypeSelector(self, hbox):
	tweenbox = gtk.HBox()
	label = gtk.Label('Tween Type')
	tweenbox.pack_start(label)
	
        self.tweenTypeSelector = gtk.combo_box_new_text()
	self.tweenTypeSelector.append_text('normal')
	self.tweenTypeSelector.append_text('scale')
	self.tweenTypeSelector.set_active(0)
	tweenbox.pack_start(self.tweenTypeSelector, expand=False, fill=False)
	hbox.pack_start(tweenbox, expand=False, fill=False)
	self.tweenTypeSelector.connect('changed', self.onTweenTypeChange)
	pass
    
    def on_selection(self,w,obj):
	objs =  self.desktop.selection.list()
	try:
	    o = objs[0]
	    print o.getCenter()
	    if o == self.last_select: 
	        return
	except:
	    self.nameEditor.set_text('')
	    self.last_select = None
	    return
	self.last_select = o
	try:
	    self.nameEditor.set_text(o.getAttribute("inkscape:label"))
	except:
	    self.nameEditor.set_text('')
	    pass

	# The selection is a PYSPObject. Convert it to be PYNode
	self.change_active_frame(self.last_select.repr.parent())
	pass

    def onCellClick(self, layer_idx, frame_idx):
	self._lockui = True
	
	self.setCurrentScene(frame_idx)
	self.selectSceneObject(layer_idx, frame_idx)
	
	self._lockui = False
        pass
    
    def doInsertKeyScene(self,w):
	self._lockui=True
	layer_idx, frame_idx = self._domview.get_active_layer_frame()
	self.insertKeyScene(layer_idx, frame_idx)
	self.selectSceneObject(layer_idx, frame_idx)
	self._lockui=False
	return
    
    def doDuplicateKeyScene(self,w):
	self._lockui = True
        self.duplicateKeyScene()
	self._lockui = False

    def doRemoveScene(self,w):
	self._lockui = True
	layer_idx, frame_idx = self._domview.get_active_layer_frame()
	self.removeKeyScene(layer_idx, frame_idx)
	self._lockui = False
	return

    
    def doExtendScene(self,w):
	self._lockui = True
	self.extendScene()
	self._lockui = False
	pass

    def doRun(self,arg):
        """
	    Execute the current animation till the last frame.
	"""
	if self.btnRun.get_label() == "Run":
	    self.btnRun.set_label("Stop")
	    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
	    pass
	pass

    def doRunNext(self):
	if self.current > self._domview.get_max_frame():
	    self.current = 0
	    pass
	try:
	    self.setCurrentScene(self.current)
	except:
	    traceback.print_exc()
	    raise
	self.current = self.current + 1
	tmout = 1000 / self.framerate
        self.last_update = glib.timeout_add(tmout, self.doRunNext)
	pass

    def doInsertFrame(self, w):
	self.lockui=True
	layer_idx, frame_idx = self._domview.get_active_layer_frame()
	self._domview.insert_frames(layer_idx, frame_idx, 1)
	self.lockui=False

    def doRemoveFrame(self, w):
        self.lockui=True
	layer_idx, frame_idx = self._domview.get_active_layer_frame()
	self._domview.rm_frames(layer_idx, frame_idx, 1)
	self.lockui=False

    def onTweenTypeChange(self, w):
	if self._disable_tween_type_selector:
	    return

	layer_idx, frame_idx = self._domview.get_active_layer_frame()
	tween_type = self.tweenTypeSelector.get_active()
	
	start, end, old_tween_type = \
	    self._domview.get_left_key(layer_idx, frame_idx)
	if end >= frame_idx and start != end:
	    # Length of tween > 1 and cover this frame
	    self._domview.chg_tween(layer_idx, start, tween_type=tween_type)
	    pass
	pass
    
    def onQuit(self, event):
	self.OK = False
	gtk.main_quit()
	pass
    
    def onOK(self, event):
	self.OK = True
	gtk.main_quit()
	pass

    def _add_buttons(self, hbox):
	btn = gtk.Button('Insert Key')
	btn.connect('clicked', self.doInsertKeyScene)
	hbox.pack_start(btn, expand=False, fill=False)

	btn=gtk.Button('Remove Key')
	btn.connect('clicked', self.doRemoveScene)
	hbox.pack_start(btn, expand=False, fill=False)

	btn=gtk.Button('Extend scene')
	btn.connect('clicked', self.doExtendScene)
	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.doInsertFrame)
	hbox.pack_start(btn, expand=False, fill=False)
	
	btn=gtk.Button('remove')
	btn.connect('clicked', self.doRemoveFrame)
	hbox.pack_start(btn, expand=False, fill=False)
	
	btn=gtk.Button('Run')
	btn.connect('clicked', self.doRun)
	self.btnRun = btn
	hbox.pack_start(btn, expand=False, fill=False)

	self.addNameEditor(hbox)
	self.addTweenTypeSelector(hbox)
	pass

    def show(self):
	self.OK = True
	if not self._root:
	    self._root = self.desktop.doc().root().repr
	    pass
	
	self.document = self.desktop.doc().rdoc
	
	self.tween = TweenObject(self.document, self._root)
	self._domview.handle_doc_root(self.document, self._root)
	self._domview.register_active_frame_callback(self.onCellClick)

	if self.top == None:
	    self.top = gtk.VBox(False, 0)
	    toplevel = self.desktop.getToplevel()
	    toplevel.child.child.pack_end(self.top, expand=False)
	else:
	    self.top.remove(self.startWindow)
	    pass
	
	vbox = gtk.VBox(False, 0)
	self.startWindow = vbox
	self.top.pack_start(vbox, expand=False)
	frame_ui = self._domview.get_frame_ui_widget()
	vbox.pack_start(frame_ui, expand=False)
	hbox=gtk.HBox(False, 0)
	self._add_buttons(hbox)
	vbox.pack_start(hbox, expand=False)

	self.top.show_all()
	self.last_update = None
	return False
    pass