view pyink/frameline.py @ 1517:31a21656c0a8

Describe designs of Scribboo
author Thinker K.F. Li <thinker@codemud.net>
date Sat, 14 May 2011 01:07:23 +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