view pyink/MBScene.py @ 1529:af8dd27bf450

Remove layer from button
author Thinker K.F. Li <thinker@codemud.net>
date Wed, 31 Aug 2011 22:30:02 +0800
parents b7d70341ee57
children 5a3597eba722
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

## \page design_scribboo Designs of Scribboo
#
# \image html scribboo_arch.png
#
# The idea of Scribboo is that domview_ui is responsible for synchronizing
# domview and framelines.  domview is responsible for managing data model
# provided by Inkscape for SVG documents.  All access to data model use APIs
# provided by domview.  domview_ui is a decorator of domview.  It does not only
# delegate calls to domview, but also make sure that framelines are always up
# to date and keep consistent with data model provided by domview.
#
# MBScene implements most feature about editing scenes.  It uses domview_ui to
# manage and control scenes.  With MBScene, you can create, delete, and change
# scenes.  It also provide the capability of tweening animation.
#
# comp_dock is responsible for managing components and actions.  You can see a
# list of components and a list of actions.  These two list boxes are
# implemented by comp_dock.
#
# FSM_window is responsible for implementation of FSM editor.  All features
# provided by FSM editor is a part of FSM_window.  It also use domview_ui to
# access data model.
#

# 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:
	    dup_group = self._domviewui.get_layer_dup_group(layer_idx)
	    self._enterGroup(dup_group)
	    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)
	self._domviewui.remember_current_frame(layer_idx, frame_idx)
        pass

    def doAddLayer(self, w):
	domview = self._domviewui
	layer_num = domview.get_layer_num()
	domview.insert_layer(layer_num)
	pass

    def doRemoveLayer(self, w):
	domview = self._domviewui
	layer_idx, frame_idx = domview.get_current_frame()
	domview.rm_layer(layer_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('Add a Layer')
	btn.connect('clicked', self.doAddLayer)
	hbox.pack_start(btn, expand=False, fill=False)

	btn = gtk.Button('Remove the Layer')
	btn.connect('clicked', self.doRemoveLayer)
	hbox.pack_start(btn, expand=False, fill=False)

	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()

	self.desktop.connectCurrentLayerChanged(self.handle_change_layer)
	
	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 Handle event that user change layers.
    #
    # This method is always being called for chaning layer event.
    # So, we should do some check at beginning for re-entry condition.
    #
    def handle_change_layer(self, node):
	layer = self.desktop.currentLayer()
	node = layer.repr

	#
	# Only scene group and dup group are allowed.
	#
	try:
	    scene_group_attr = node.getAttribute('scene_group')
	except KeyError:
	    pass
	else:
	    if scene_group_attr == 'true':
		return
	    pass

	try:
	    label = node.getAttribute('inkscape:label')
	except KeyError:
	    pass
	else:
	    if label == 'dup':
		return
	    pass
	# It is not a scene or dup group.

	layer_idx, frame_idx = self._domviewui.get_current_frame()
	self.selectSceneObject(layer_idx, frame_idx)
	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