view pyink/MBScene.py @ 1395:a768d74e5f49

Fix the svg:use. For a svg:use, it is a group which include the content it reference. It means that we can not tween it to its origin object directly. Instead, we need to ungroup it and then use the result matrix to generate the tweened transformation matrix. Therefore, we need to concate its matrix to the referenced object. Ad center object when the bbox-x is not available.
author wycc
date Sat, 02 Apr 2011 05:36:36 +0800
parents d0e6f350b3fd
children 60f2c9a24cdb
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 scenes_director
from domview_ui import create_domview_ui
from data_monitor import data_monitor

# 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 MBScene connect GUI and DOM-tree
#
# This method accepts user actions and involves domview_ui to update
# data on the document.
#
# This class is protected by \ref data_monitor, meta-class.
#
class MBScene(object):
    __metaclass__ = data_monitor

    _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.do_selection)
	self.last_select = None
	self._director = None
	self.document = None
	self._root = root
	self.framerate = 12
	self._disable_tween_type_selector = False
	self.current = 0

	self._domviewui = create_domview_ui()
	self._locker = self._domviewui
	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._domviewui.find_key_from_group(node_id)
	    except:
		pass
	    else:
		self._domviewui.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._domviewui.mark_key(layer_idx, frame_idx)
	except ValueError:	# existed key frame
	    pass
	pass

    def removeKeyScene(self, layer_idx, frame_idx):
	self._domviewui.unmark_key(layer_idx, frame_idx)
	self._director.show_scene(frame_idx)
	pass
    
    def extendScene(self):
	# Create a tween
	layer_idx, frame_idx = self._domviewui.get_active_layer_frame()
	start, end, tween_type = \
	    self._domviewui.get_left_key(layer_idx, frame_idx)
	tween_len = frame_idx - start
	self._domviewui.tween(layer_idx, start, tween_len, tween_type)

	# Create a key frame which link to the previous key frame
	self._domviewui.mark_key(layer_idx, frame_idx)
	self._domviewui.clone_key_group(layer_idx, start, frame_idx)
	self._director.show_scene(frame_idx)
	self.selectSceneObject(layer_idx, frame_idx)
	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._domviewui.get_key(layer_idx, frame_idx)
	except:
	    return

	scene_group = self._domviewui.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._domviewui.get_active_layer_frame()
	try:
	    self.removeKeyScene(layer_idx, frame_idx)
	except:			# no key and tween
	    pass

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

	self._domviewui.mark_key(layer_idx, frame_idx)
	self._domviewui.copy_key_group(layer_idx, left_start, frame_idx)

	self._director.show_scene(frame_idx)
	pass

    def _drop_undo(self):
	self.document.commit()	# commit the transation and drop change log.
	self.document.beginTransaction()
    
    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.do_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.do_TweenTypeChange)
	pass
    
    def do_changeObjectLabel(self,w):
	o = self.desktop.selection.list()[0]
	o.setAttribute("inkscape:label", self.nameEditor.get_text())
	pass

    def do_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 do_CellClick(self, layer_idx, frame_idx):
	self._director.show_scene(frame_idx)
	self.selectSceneObject(layer_idx, frame_idx)
        pass

    def doInsertKeyScene(self,w):
	layer_idx, frame_idx = self._domviewui.get_active_layer_frame()
	self.insertKeyScene(layer_idx, frame_idx)
	self.selectSceneObject(layer_idx, frame_idx)
	self._drop_undo()
	return
    
    def doDuplicateKeyScene(self,w):
        self.duplicateKeyScene()
	self._drop_undo()
	pass

    def doRemoveScene(self,w):
	layer_idx, frame_idx = self._domviewui.get_active_layer_frame()
	self.removeKeyScene(layer_idx, frame_idx)
	self._drop_undo()
	return

    def doExtendScene(self,w):
	self.extendScene()
	self._drop_undo()
	pass

    def doRun(self,arg):
        """
	    Execute the current animation till the last frame.
	"""
	if self.btnRun.get_label() == "Run":
	    #
	    # Make dup groups empty.
	    # It forces TweenObject to re-generate content from scratch.
	    #
	    nlayers = self._domviewui.get_layer_num()
	    for layer_idx in range(nlayers):
		layer_dup = self._domviewui.get_layer_dup_group(layer_idx)
		for child in layer_dup.childList():
		    layer_dup.removeChild(child)
		    pass
		pass
	    
	    self.btnRun.set_label("Stop")
	    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)
	    pass
	pass

    def doRunNext(self):
	if self.current > self._domviewui.get_max_frame():
	    self.current = 0
	    pass
	try:
	    self._director.show_scene(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):
	layer_idx, frame_idx = self._domviewui.get_active_layer_frame()
	self._domviewui.insert_frames(layer_idx, frame_idx, 1)
	self._drop_undo()

    def doRemoveFrame(self, w):
	layer_idx, frame_idx = self._domviewui.get_active_layer_frame()
	self._domviewui.rm_frames(layer_idx, frame_idx, 1)
	self._drop_undo()

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

	layer_idx, frame_idx = self._domviewui.get_active_layer_frame()
	tween_type = self.tweenTypeSelector.get_active()
	
	start, end, old_tween_type = \
	    self._domviewui.get_left_key(layer_idx, frame_idx)
	if end >= frame_idx and start != end:
	    # Length of tween > 1 and cover this frame
	    self._domviewui.chg_tween(layer_idx, start, tween_type=tween_type)
	    pass
	self._drop_undo()
	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 do_show(self):
	self.OK = True
	if not self._root:
	    self._root = self.desktop.doc().root().repr
	    pass
	
	self.document = self.desktop.doc().rdoc
	
	self._domviewui.set_desktop(self.desktop)
	self._domviewui.handle_doc_root(self.document, self._root)
	self._domviewui.register_active_frame_callback(self.do_CellClick)
	self._director = scenes_director(self._domviewui)

	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._domviewui.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
	
	self._drop_undo()
	
	return False

    ## \brief To handle context menu event.
    #
    def do_make_component_from_group(self, node):
	node_parent_group = node.parent()
	
	comp_name = 'Component ' + node.getAttribute('id')
	i = 0
	while comp_name in self._domviewui.all_comp_names():
	    comp_name = 'Component %s - %d' % (comp_name, i)
	    i = i + 1
	    pass
	self._domviewui.add_component_from_group(comp_name, node)
	
	self._domviewui.link_to_component(comp_name, node_parent_group)
	pass

    ## \brief Add menu item to context menu.
    #
    # This method is called by pyink.pyink_context_menu() to notify the
    # creation of context menu for a node.
    #
    def context_menu(self, spitem, menu_factory):
	node = spitem.repr
	if node.name() != 'svg:g':
	    return		# not a group

	if self._domviewui.is_graph_node(node):
	    menu_item_handler = \
		lambda *args: self.do_make_component_from_group(node)
	    menu_factory.add_item_label('Make a component',
					menu_item_handler)
	    pass
	pass

    def show(self):
	self.do_show()
	pass
    pass