Mercurial > MadButterfly
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