view pyink/frameline.py @ 1532:4a92b639a1cd

Clear selection set when switching current scene. To clear selection set after switching away from current to another scene. It avoids Inkscape select on nodes they are not saw after switching.
author Thinker K.F. Li <thinker@codemud.net>
date Fri, 30 Sep 2011 12:31:33 +0800
parents 45e9566ea5c0
children 9aff42a7e2b9
line wrap: on
line source

# -*- indent-tabs-mode: t; tab-width: 8; python-indent: 4; fill-column: 79 -*-
# vim: sw=4:ts=8:sts=4:textwidth=79
import pygtk
pygtk.require("2.0")
import gtk
import gtk.gdk
import pango
import gobject
import traceback

def color_to_rgb(v):
    return (((v >> 16) & 0xff) * 65535 / 0xff,
            ((v >> 8) & 0xff) * 65535 / 0xff,
            (v & 0xff) * 65535 / 0xff)

class keyframe(object):
    def __init__(self, frame_idx):
        self.idx = frame_idx
        self.left_tween = False
        self.right_tween = False
        self.right_tween_type = 0
	self.ref = ''
        pass
    pass

class frameruler(gtk.DrawingArea):
    _type = 0
    _frame_width = 10           # Width for each frame is 10 pixels
    _mark_color = 0x808080      # color of mark lines
    _number_color = 0x000000    # color of frame number
    _number_sz = 8             # font size of frame number
    
    def __new__(clz, *args):
        if not frameruler._type:
            frameruler._type = gobject.type_register(frameruler)
            pass
        fr = gobject.new(frameruler._type)
        return fr

    def __init__(self, num_frames=20):
        self.connect('expose_event', self._fr_expose)
        self._num_frames = num_frames
        pass

    def _fr_expose(self, widget, event):
        self.update()
        pass

    def queue_draw(self):
        print 'queue_draw'
        self.update()
        pass

    def queue_draw_area(self, x, y, w, h):
        print 'queue_draw_area'
        pass

    def update(self):
        win = self.window
        w_x, w_y, w_w, w_h, depth = win.get_geometry()

        gc = gtk.gdk.GC(win)

        #
        # Set color of mark lines
        #
        color_rgb = color_to_rgb(self._mark_color)
        color = gtk.gdk.Color(*color_rgb)
        gc.set_rgb_fg_color(color)
        
        #
        # Mark mark lines
        #
        mark_h = w_h / 10
        for i in range(self._num_frames):
            mark_x = (i + 1) * self._frame_width
            win.draw_line(gc, mark_x, 0, mark_x, mark_h)
            win.draw_line(gc, mark_x, w_h - mark_h - 1, mark_x, w_h - 1)
            pass

        win.draw_line(gc, 0, w_h - 1, w_w, w_h -1)

        #
        # Set color of frame number
        #
        color_rgb = color_to_rgb(self._number_color)
        color = gtk.gdk.Color(*color_rgb)
        gc.set_rgb_fg_color(color)
        
        font_desc = pango.FontDescription()
        font_desc.set_size(self._number_sz * pango.SCALE)

        number_y = (w_h - self._number_sz) / 2
        
        #
        # Draw frame number
        #
        layout = self.create_pango_layout('1')
        layout.set_font_description(font_desc)
        win.draw_layout(gc, 0, number_y, layout)
        for i in range(4, self._num_frames, 5):
            mark_x = i * self._frame_width
            layout.set_text(str(i + 1))
            win.draw_layout(gc, mark_x, number_y, layout)
            pass
        pass
    pass

## \brief Drawing on the screen for a frameline.
#
# This class contain all functions that drawing thing on the screen for a
# frameline.  It is used by descendants to drawing a frameline.  This class is
# only responsible to draw screen, the logical part is the responsible of
# deriving classes.
#
# This class should only include functions about drawing, no any control, logic
# function, and state should be implemented here.  This class should change/or
# read states of instances.  It is stateless.
#
class frameline_draw(gtk.DrawingArea):
    _type = 0
    _frame_width = 10           # Width for each frame is 10 pixels
    _select_color = 0xee2222    # color of border of selected frame
    _key_mark_color = 0x000000  # color of marks for key frames.
    _key_mark_sz = 4            # width and height of a key frame mark
    _tween_color = 0x808080     # color of tween line
    # bg colors of tween frames
    _tween_bgcolors = [0x80ff80, 0xff8080, 0xffff80]
    # Colors for normal frames
    _normal_bgcolors = [0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xcccccc]
    _normal_border = 0xaaaaaa   # border color of normal frames.
    _active_border = 0xff3030   # border color of an active frame
    _hover_border_color = 0xa0a0a0 # border when the pointer over a frame
    
    def __new__(clz, *args):
        if not clz._type:
            clz._type = gobject.type_register(clz)
            pass
        fl_obj = gobject.new(clz._type)
        return fl_obj
    
    def _draw_tween(self, first_idx, last_idx, tween_type):
        win = self.window
        w_x, w_y, w_w, w_h, depth = win.get_geometry()
        
        #
        # Get background color of a tween
        #
        bg_idx = tween_type
        bg_color_v = self._tween_bgcolors[bg_idx]
        bg_color_rgb = color_to_rgb(bg_color_v)
        bg_color = gtk.gdk.Color(*bg_color_rgb)
        
        gc = self._gc
	gc.set_rgb_fg_color(bg_color)
	
        draw_x = first_idx * self._frame_width + 1
        draw_w = (last_idx -  first_idx + 1) * self._frame_width - 1

        win.draw_rectangle(gc, True, draw_x, 0, draw_w, w_h)

        #
        # Set color of tween line
        #
        line_v = self._tween_color
        line_rgb = color_to_rgb(line_v)
        line_color = gtk.gdk.Color(*line_rgb)
        gc.set_rgb_fg_color(line_color)
        
        #
        # Draw tween line
        #
        line_x1 = int((first_idx + 0.5) * self._frame_width)
        line_x2 = line_x1 + (last_idx - first_idx) * self._frame_width
        line_y = int(w_h * 2 / 3)
        win.draw_line(gc, line_x1, line_y, line_x2, line_y)
        pass

    def _draw_normal_frame(self, idx):
        win = self.window
        w_x, w_y, w_w, w_h, depth = win.get_geometry()
        
        gc = self._gc
        bg_idx = idx % len(self._normal_bgcolors)
        rgb = color_to_rgb(self._normal_bgcolors[bg_idx])
        color = gtk.gdk.Color(*rgb)
        gc.set_rgb_fg_color(color)
        
        f_x = self._frame_width * idx
        win.draw_rectangle(gc, True, f_x + 1, 0, self._frame_width - 1, w_h)
        next_f_x = f_x + self._frame_width
        
        border_rgb = color_to_rgb(self._normal_border)
        border_color = gtk.gdk.Color(*border_rgb)
        gc.set_rgb_fg_color(border_color)
        gc.set_line_attributes(1, gtk.gdk.LINE_SOLID,
                               gtk.gdk.CAP_BUTT, gtk.gdk.JOIN_MITER)
        win.draw_line(gc, next_f_x, 0, next_f_x, w_h)
        pass

    ## \brief Draw a bottom line from start to the point before stop frame.
    #
    def _draw_bottom_line(self, start_idx, stop_idx):
        win = self.window
        w_x, w_y, w_w, w_h, depth = win.get_geometry()
        gc = self._gc
        
        border_rgb = color_to_rgb(self._normal_border)
        border_color = gtk.gdk.Color(*border_rgb)
        gc.set_rgb_fg_color(border_color)
        start_x = start_idx * self._frame_width
        stop_x = stop_idx * self._frame_width
        win.draw_line(gc, start_x, w_h - 1, stop_x, w_h - 1)
        pass
    
    def _draw_active(self, idx):
        win = self.window
        w_x, w_y, w_w, w_h, depth = win.get_geometry()

        color_v = self._active_border
        color_rgb = color_to_rgb(color_v)
        color = gtk.gdk.Color(*color_rgb)

        gc = self._gc
        gc.set_rgb_fg_color(color)
        
        line_x1 = idx * self._frame_width + 1
        line_x2 = line_x1 + self._frame_width - 2

        win.draw_line(gc, line_x1, 0, line_x1, w_h - 2)
        win.draw_line(gc, line_x2, 0, line_x2, w_h - 2)
        win.draw_line(gc, line_x1, w_h - 2, line_x2, w_h - 2)
        win.draw_line(gc, line_x1, 0, line_x2, 0)
	pass

    def _draw_keyframe_(self, frame_idx):
        win = self.window
        w_x, w_y, w_w, w_h, depth = win.get_geometry()
        
        color_v = self._key_mark_color
        color_rgb = color_to_rgb(color_v)
        color = gtk.gdk.Color(*color_rgb)
        
        gc = self._gc
        gc.set_rgb_fg_color(color)
        
        mark_sz = self._key_mark_sz
        mark_x = int((frame_idx + 0.5) * self._frame_width - mark_sz / 2)
        mark_y = w_h * 2 / 3 - mark_sz / 2
        
        win.draw_rectangle(gc, True, mark_x, mark_y, mark_sz, mark_sz)
        pass

    ## \brief Show a mark for the pointer for a frame.
    #
    def _draw_hover(self, frame_idx):
        win = self.window
        w_x, w_y, w_w, w_h, depth = win.get_geometry()
        gc = self._gc
        
        color_rgb = color_to_rgb(self._hover_border_color)
        color = gtk.gdk.Color(*color_rgb)
        gc.set_rgb_fg_color(color)

        line_x1 = frame_idx * self._frame_width + 2
        line_x2 = line_x1 + self._frame_width - 4
        
        win.draw_line(gc, line_x1, 1, line_x1, w_h - 3)
        win.draw_line(gc, line_x2, 1, line_x2, w_h - 3)
        win.draw_line(gc, line_x1, 1, line_x2, 1)
        win.draw_line(gc, line_x1, w_h - 3, line_x2, w_h - 3)
        pass
    pass


## \brief Drawing frameline according state of a frameline.
#
# Thsi class calls methods of class frameline_draw to drawing frameline.  But,
# this class is state awared.  It according states of an instance to determines
# calling methods and arguments.  This class decorates methods of
# frameline_draw class according states of an instance.
#
# This classs reading state of a frameline, but it does not change and control
# states.  The deriving classes are responsible to change and control states.
#
class frameline_draw_state(frameline_draw):
    # tween types
    TWEEN_TYPE_NONE = 0
    TWEEN_TYPE_MOVE = 1
    TWEEN_TYPE_SHAPE = 2    

    def __init__(self, num_frames):
	frameline_draw.__init__(self)
	
        self._num_frames = num_frames
        self._keys = []
        self._active_frame = -1
        self._drawing = False
        self._last_hover = -1   # frame index of last hover
	pass

    def _draw_keyframe(self, frame_idx):
	# Only keyframes that is not right-side of NONE type tween should be
	# draw.
	pos = self._find_keyframe(frame_idx)
	key = self._keys[pos]
	if key.left_tween and not key.right_tween:
	    return
	
	self._draw_keyframe_(frame_idx)
	pass

    def _draw_keyframes(self):
        for key in self._keys:
	    self._draw_keyframe(key.idx)
            pass
        pass

    ## \brief Redraw a frame specified by an index.
    #
    def _draw_frame(self, frame_idx):
        if not self._drawing:
            return

	pos = self._find_keyframe_floor(frame_idx)
	try:
	    key = self._keys[pos]
	except IndexError:
	    key = None
	    pass
	
	if key and (key.right_tween or
		    (key.left_tween and key.idx == frame_idx)):
	    #
            # in tween
            #
            first_pos, last_pos = self._find_tween_range(pos)
            first_key = self._keys[first_pos]
            last_key = self._keys[last_pos]
            
            self._draw_tween_of_key(first_pos)
        else:                   # not in tween
            self._draw_normal_frame(frame_idx)
            self._draw_bottom_line(frame_idx, frame_idx + 1)
            if key and (key.idx == frame_idx):
                self._draw_keyframe(frame_idx)
                pass
            pass
        pass
    
    def _draw_all_frames(self):
        if not self._drawing:
            return
        
        i = 0
        key_pos = 0
        try:
            key = self._keys[key_pos]
        except IndexError:
            key = keyframe(self._num_frames)
            pass
        num_frames = self._num_frames
        while i < num_frames:
            if key.idx == i and key.right_tween:
                #
                # Skip tween keys
                #
		first_tween_pos, last_tween_pos = \
		    self._find_tween_range(key_pos)
		first_tween_key = self._keys[first_tween_pos]
		last_tween_key = self._keys[last_tween_pos]
		self._draw_tween(first_tween_key.idx, last_tween_key.idx,
				 first_tween_key.right_tween_type)
		last_tween_key = self._keys[last_tween_pos]
		key_pos = last_tween_pos + 1
		try:
		    key = self._keys[key_pos]
		except:
		    key = keyframe(self._num_frames)
		    pass
                i = last_tween_key.idx + 1
	    else:
                self._draw_normal_frame(i)
	        if key.idx == i:
		    key_pos = key_pos+1
		    try:
		        key = self._keys[key_pos]
		    except:
		        key = keyframe(self._num_frames)
			pass
		    pass
                i = i + 1
                pass
            pass

        self._draw_bottom_line(0, num_frames)

	self._draw_keyframes()
        pass

    def _draw_tween_of_key(self, key_pos):
        if not self._drawing:
            return

	first_pos, last_pos = self._find_tween_range(key_pos)
	first_key = self._keys[first_pos]
	last_key = self._keys[last_pos]
	
	self._draw_tween(first_key.idx, last_key.idx,
			 first_key.right_tween_type)
	self._draw_bottom_line(first_key.idx, last_key.idx + 1)

	for i in range(first_pos, last_pos + 1):
	    key = self._keys[i]
	    self._draw_keyframe(key.idx)
            pass
	pass
    
    def _draw_active_frame(self):
        if not self._drawing:
            return
        
        if self._active_frame == -1:
            return

	self._draw_active(self._active_frame)
        pass

    def _draw_hover_frame(self, frame_idx):
	if not self._drawing:
	    return

	last_hover = self._last_hover
	
        if last_hover != -1:
            self._draw_frame(last_hover)
            pass
	
        if frame_idx < self._num_frames and frame_idx >= 0:
            self._draw_hover(frame_idx)
	    self._last_hover = frame_idx
	else:
	    self._last_hover = -1
	    pass

	#
	# Redraw active frame if active frame in a tween that is same as the
	# one that the give frame or last hover frame is in.
	#
	if self._active_frame in (frame_idx, last_hover):
	    self._draw_active_frame()
	    return

	for idx in (frame_idx, last_hover):
	    key_pos = self._find_keyframe_floor(idx)
	    if key_pos != -1:
		key = self._keys[key_pos]
		if key.right_tween or \
			(key.left_tween and key.idx == idx):
		    # The given frame is in a tween
		    first_pos, last_pos = self._find_tween_range(key_pos)
		    start = self._keys[first_pos].idx
		    end = self._keys[last_pos].idx
		    if self._active_frame >= start and \
			    self._active_frame <= end:
			# The active frame is in the tween
			self._draw_active_frame()
			break
		    pass
		pass
	    pass
        pass

    ## \brief Start future drawing actions
    #
    def start_drawing(self):
        if not hasattr(self, '_gc'):
	    win = self.window
            self._gc = gtk.gdk.GC(win)
            #
            # register for button press event
            #
            emask = win.get_events()
            emask = emask | gtk.gdk.BUTTON_PRESS_MASK | \
                gtk.gdk.POINTER_MOTION_MASK
            win.set_events(emask)
            pass
        self._drawing = True
        pass
    
    ## \brief Stop any future drawing actions
    #
    # When doing massive udpate, to stop drawing the screen make
    # application more effecient.  The screen is updated by calling
    # update() method after massive update and calliing start_drawing().
    #
    def stop_drawing(self):
        self._drawing = False
        pass
    pass


## Show frame status of a layer
#
# \section frameline_sigs Signals
# - 'frame-button-pree' for user press on a frame.
#   - callback(widget, frame_idx, button)
#
# All methos that change state of the frameline, must call methods to update
# the screen.
#
class frameline(frameline_draw_state):
    _sig_frame_but_press = None
    
    FRAME_BUT_PRESS = 'frame-button-press'
    
    def __new__(clz, *args):
	fl_obj = frameline_draw_state.__new__(clz, *args)
	
        if not clz._sig_frame_but_press:
            but_press = gobject.signal_new(frameline.FRAME_BUT_PRESS,
                                           frameline._type,
                                           gobject.SIGNAL_RUN_FIRST,
                                           gobject.TYPE_NONE,
                                           (gobject.TYPE_INT,
                                            gobject.TYPE_INT))
            clz._sig_frame_but_press = but_press
            pass
        return fl_obj
    
    def __init__(self, num_frames=20):
	frameline_draw_state.__init__(self, num_frames)
	
        self.connect('button-press-event', self._press_hdl)
        self.connect('expose-event', self._fl_expose)
        self.connect('motion-notify-event', self._motion_n_leave_hdl)
        self.connect('leave-notify-event', self._motion_n_leave_hdl)
        pass

    def __len__(self):
        return self._num_frames
    
    def _find_keyframe(self, idx):
	key_indic = [key.idx for key in self._keys]
	key_pos = key_indic.index(idx)
	return key_pos

    def _find_keyframe_floor(self, frame_idx):
	pos = 0
        keys = [key.idx for key in self._keys]
	keys.append(frame_idx)
	keys.sort()
	keys.reverse()
	pos = (len(keys) - 1) - keys.index(frame_idx) - 1
	return pos

    ## \brief Find the range a continous tween.
    #
    def _find_tween_range(self, key_pos):
	key = self._keys[key_pos]
	if not (key.left_tween or key.right_tween):
	    raise ValueError, 'the keyframe is not in a tween'
	
	#
	# Initialize tween type and first_pos
	#
	if key.right_tween:
	    tween_type = key.right_tween_type
	    first_pos = key_pos
	else:
	    # key.left_tween is True since the key is in a tween.
	    first_pos = key_pos -1
	    key = self._keys[first_pos]
	    tween_type = key.right_tween_type
	    pass
	
	#
	# Find first_pos
	#
        while first_pos and key.left_tween:
	    right_pos = first_pos - 1
	    right_key = self._keys[right_pos]
	    if right_key.right_tween_type != tween_type:
		break
            first_pos = right_pos
	    key = right_key
            pass
        
	#
	# Find last_pos
	#
        max_pos = len(self._keys) - 1
        last_pos = key_pos
	key = self._keys[last_pos]
        while last_pos < max_pos and self._keys[last_pos].right_tween:
	    if key.right_tween_type != tween_type:
		break
            last_pos = last_pos + 1
	    key = self._keys[last_pos]
            pass
        
        return first_pos, last_pos

    def _press_hdl(self, widget, event):
        frame = event.x / self._frame_width
        but = event.button
        self.emit(frameline.FRAME_BUT_PRESS, frame, but)
        pass
    
    def _motion_n_leave_hdl(self, widget, event):
	if event.type == gtk.gdk.LEAVE_NOTIFY:
	    frame_idx = -1
	else:
	    frame_idx = int(event.x / self._frame_width)
	    pass

	self._draw_hover_frame(frame_idx)
	pass
    
    def _fl_expose(self, widget, event):
        win = self.window
	self.start_drawing()
        self.update()
        pass

    def set_tween_type(self, frame_idx, tween_type):
	pos = self._find_keyframe(frame_idx)
	key = self._keys[pos]
	assert key.right_tween

	key.right_tween_type = tween_type
	self._draw_tween_of_key(pos)

	self._draw_active_frame()
	pass

    def update(self):
        self._draw_all_frames()
	self._draw_active_frame()
        pass

    ## Add a key frame
    #
    # A key frame is the frame that user specify actions.  For
    # example, move a object or add new objects at the frame.
    def mark_keyframe(self, idx, ref=None):
	try:
	    pos = self._find_keyframe(idx) # it is not already a keyframe.
	except ValueError:
	    pass
	else:
	    raise ValueError, 'the frame is already a key frame'
	
	key_indic = [key.idx for key in self._keys]
        key_indic.append(idx)
        key_indic.sort()
        insert_pos = key_indic.index(idx)
        
        key = keyframe(idx)
	key.ref = ref
        if insert_pos > 0 and self._keys[insert_pos - 1].right_tween:
	    if self._keys[insert_pos-1].idx == idx-1:
		self._keys[insert_pos-1].right_tween = False
                self._keys[insert_pos:insert_pos] = [key]
		return
	    else:
		key2 = keyframe(idx-1)
		key2.ref = self._keys[insert_pos-1].ref
		key2.left_tween = True
		self._keys[insert_pos:insert_pos] = [key2,key]
		key.left_tween = False
		key.right_tween = True
		key.right_tween_type = self._keys[insert_pos - 1].right_tween_type
            pass
	else:
            self._keys[insert_pos:insert_pos] = [key]

	if self._drawing:
	    self._draw_keyframe(idx)
	    pass
        pass

    ## \brief Remove a frame from the frameline.
    #
    # The frames after specified one would move to head in one position.  It
    # means sub one from positions of all frame after given frame.
    #
    def rm_frame(self, idx):
	pos = self._find_keyframe_floor(idx)
	if pos != -1:
	    key = self._keys[pos]
	    if key.idx == idx:
		self.unmark_keyframe(idx)
	    else:
		pos = pos + 1
		pass

	    while pos < len(self._keys):
		key = self._keys[pos]
		key.idx = key.idx - 1
		pos = pos + 1
		pass
	    pass

	self._draw_all_frames()
	self._draw_active_frame()
	pass

    ## \brief Inser a frame before given frame.
    #
    # All frame at and after given frame position move to tail in one position.
    # It means to add one to positions of all key frames at/after given frame.
    #
    def add_frame(self,idx):
	pos = self._find_keyframe_floor(idx)
	if pos != -1:
	    key = self._keys[pos]
	    if key.idx != idx:
		pos = pos + 1
		pass
	    
	    while pos < len(self._keys):
		key = self._keys[pos]
		key.idx = key.idx + 1
		pos = pos + 1
		pass
	    pass

	self._draw_all_frames()
	self._draw_active_frame()
	pass
    
    def unmark_keyframe(self, idx):
	key_pos = self._find_keyframe(idx)
        key = self._keys[key_pos]
	del self._keys[key_pos]
        
        if key.right_tween ^ key.left_tween:
            #
            # tween in one side
            #
            if key.right_tween:
		right_key = self._keys[key_pos]
                right_key.left_tween = False
                redraw_range = (key.idx, right_key.idx + 1)
            else:
                left_key = self._keys[key_pos - 1]
                left_key.right_tween = False
                redraw_range = (left_key.idx, key.idx + 1)
                pass
	    for i in range(*redraw_range):
		self._draw_frame(i)
		pass
	    pass
        else:
            self._draw_frame(idx)
            pass

        self._draw_active_frame()
        pass

    ## Tween the key frame specified by an index and the key frame at right.
    #
    # \see http://www.entheosweb.com/Flash/shape_tween.asp
    def tween(self, idx, tween_type=frameline_draw_state.TWEEN_TYPE_NONE):
	pos = self._find_keyframe(idx)
        key = self._keys[pos]
        
        try:
            right_key = self._keys[pos + 1]
        except IndexError:
            raise ValueError, 'no right key frame'

        key.right_tween = True
        right_key.left_tween = True
	key.right_tween_type = tween_type
	
	self._draw_tween_of_key(pos)
	self._draw_active_frame()
        pass

    ## \brief Remove right tween for given key frame.
    #
    def untween(self, idx):
	pos = self._find_keyframe(idx)
	key = self._keys[pos]
	right_key = self._keys[pos + 1]
	
	if not key.right_tween:
	    return

	key.right_tween = False
	key.right_tween_type = frameline_draw_state.TWEEN_TYPE_NONE
	right_key.left_tween = False
	pass
    
    def get_tween_type(self, idx):
        for i in range(0,len(self._keys)):
	    key = self._keys[i]
	    if key.left_tween is True: continue
	    if key.idx == idx:
		return key.right_tween_type
	    pass
	pass
    
    ## Get the maximum frame number in a layer(frameline)
    # Return the frame number
    def max_frame(self):
	keys = self._keys
	if not keys:
	    return 0
        return keys[-1].idx

    ## \bref Return range of blocks of conesequence frames (tweens).
    #
    # Return range and type of tweens and key frames that is not start or stop
    # of a tween.
    def get_frame_blocks(self):
	blocks = []
	for pos, key in enumerate(self._keys):
	    if key.right_tween:
		next_key = self._keys[pos + 1]
		block = (key.idx, next_key.idx, key.right_tween_type)
	    elif not key.left_tween:
		block = (key.idx, key.idx, 0)
	    else:
		continue
	    blocks.append(block)
	    pass
	return blocks

    ## \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.
    #
    # - 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, \
	    'no any key frame at left side (%d)' % (idx)

    ## \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):
	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)
	key = self._keys[pos]
	return key.ref

    def set_frame_data(self, idx, value):
	pos = self._find_keyframe(idx)
	key = self._keys[pos]
	key.ref = value
	pass
    
    ## Set active frame
    #
    # The active frame is the frame that is working on.
    #
    def active_frame(self, idx):
        if idx < 0 or idx >= self._num_frames:
            raise IndexError, 'value of index (%d) is out of range' % (idx)

        if self._active_frame != -1:
            self._draw_frame(self._active_frame)
            pass
        self._active_frame = idx
        self._draw_active_frame()
        pass

    def deactive(self):
        self._draw_frame(self._active_frame)
        self._active_frame = -1
        pass

    def set_num_frames(self, num):
        self._num_frames = num
        pass

    def reset(self):
        self._keys = []
	self._active_frame = -1
	self._draw_all_frames()
        pass

    def get_active_frame(self):
	return self._active_frame

    ## \brief Called when mouse leave the widget.
    #
    # This is here for buggy pygtk.  It does not send leave-notify-event.  We
    # need this to workaround.
    #
    def mouse_leave(self):
	self._draw_hover_frame(-1)
	pass
    pass

if __name__ == '__main__':
    window = gtk.Window(gtk.WINDOW_TOPLEVEL)
    fr = frameruler(40)
    fr.set_size_request(300, 20)
    
    fl = frameline(40)
    fl.set_size_request(300, 20)
    fl.mark_keyframe(15)
    fl.mark_keyframe(3)
    fl.tween(3)
    fl.mark_keyframe(9)
    fl.mark_keyframe(20)
    fl.tween(9)
    fl.active_frame(1)
    fl.unmark_keyframe(15)
    print 'num of frames: %d' % (len(fl))

    def press_sig(fl, frame, but):
        print 'press_sig button %d for frame %d' % (but, frame)
        pass
    fl.connect(frameline.FRAME_BUT_PRESS, press_sig)

    box = gtk.VBox()

    box.pack_start(fr, False)
    box.pack_start(fl, False)
    window.add(box)
    
    fr.show()
    fl.show()
    box.show()
    window.show()
    gtk.main()
    pass