コード例 #1
0
 def __init__(self, meters_count):
     w = SLOT_W * meters_count
     h = METER_SLOT_H
     
     self.widget = CairoDrawableArea(w,
                                     h, 
                                     self._draw)
     
     self.audio_meters = [] # displays both l_Value and r_value
     for i in range(0, meters_count):
         meter = AudioMeter(METER_HEIGHT)
         if i != meters_count - 1:
             meter.right_channel.draw_dB = True
         self.audio_meters.append(meter)
コード例 #2
0
    def __init__(self):
        self.meter = AudioMeter(METER_HEIGHT + 40)
        self.meter.x_pad_l = 6
        self.meter.x_pad_r = 14
        self.meter.right_channel.draw_dB = True
        self.meter.right_channel.dB_x_pad = -14
        self.meter.meter_width = 5
        self.top_pad = 14
        self.meter.right_channel.y_top_pad = self.top_pad 
        self.meter.left_channel.y_top_pad = self.top_pad 

        w = SLOT_W - 40
        h = METER_SLOT_H + 2 + 40
        self.canvas = CairoDrawableArea(w,
                                        h, 
                                        self._draw)

        self.widget = gtk.VBox(False, 0)
        self.widget.pack_start(self.canvas, False, False, 0)
コード例 #3
0
ファイル: extraeditors.py プロジェクト: Mermouy/flowblade
 def __init__(self, edit_listener, width=260, height=260):
     self.W = width
     self.H = height
     self.widget = CairoDrawableArea(self.W, 
                                     self.H, 
                                     self._draw)
     self.widget.press_func = self._press_event
     self.widget.motion_notify_func = self._motion_notify_event
     self.widget.release_func = self._release_event
     self.X_PAD = 12
     self.Y_PAD = 12
     self.CIRCLE_HALF = 8
     self.cursor_x = self.X_PAD
     self.cursor_y = self.H - self.Y_PAD
     self.edit_listener = edit_listener
     self.hue = 0.0
     self.saturation = 0.0
     self.draw_saturation_gradient = True
     self.selection_cursor = SELECT_CIRCLE
コード例 #4
0
ファイル: extraeditors.py プロジェクト: Mermouy/flowblade
 def __init__(self, edit_listener):
     self.widget = CairoDrawableArea(260, 
                                     260, 
                                     self._draw)
     self.widget.press_func = self._press_event
     self.widget.motion_notify_func = self._motion_notify_event
     self.widget.release_func = self._release_event
     self.X_PAD = 3
     self.Y_PAD = 3
     self.CENTER_X = 130
     self.CENTER_Y = 130
     self.MAX_DIST = 123
     self.twelwe_p = (self.CENTER_X , self.CENTER_Y - self.MAX_DIST)
     self.CIRCLE_HALF = 6
     self.cursor_x = self.CENTER_X
     self.cursor_y = self.CENTER_Y
     self.WHEEL_IMG = gtk.gdk.pixbuf_new_from_file(respaths.IMAGE_PATH + "color_wheel.png")
     self.edit_listener = edit_listener
     self.angle = 0.0
     self.distance = 0.0
コード例 #5
0
ファイル: positionbar.py プロジェクト: Mermouy/flowblade
    def __init__(self):
        self.widget = CairoDrawableArea(BAR_WIDTH, 
                                        BAR_HEIGHT, 
                                        self._draw)
        self.widget.press_func = self._press_event
        self.widget.motion_notify_func = self._motion_notify_event
        self.widget.release_func = self._release_event
        self._pos = END_PAD # in display pixels
        self.mark_in_norm = -1.0 # program length normalized
        self.mark_out_norm = -1.0
        self.disabled = False
        self.mouse_release_listener = None # when used in tools (Tiler ate.) this used to update bg image

        if editorpersistance.prefs.dark_theme == True:
            global LINE_COLOR, BG_COLOR, DISABLED_BG_COLOR, SELECTED_RANGE_COLOR, MARK_COLOR
            LINE_COLOR = DARK_LINE_COLOR
            BG_COLOR = DARK_BG_COLOR
            DISABLED_BG_COLOR = DARK_DISABLED_BG_COLOR
            SELECTED_RANGE_COLOR = DARK_SELECTED_RANGE_COLOR
            MARK_COLOR = DARK_MARK_COLOR
コード例 #6
0
ファイル: extraeditors.py プロジェクト: Mermouy/flowblade
    def __init__(self, pix_size, curve, edit_listener):
        BoxEditor.__init__(self, pix_size)
        self.curve = curve # lutfilter.CRCurve
        global BOX_LINE_COLOR, CURVE_COLOR
        self.curve_color = CURVE_COLOR
        self.edit_listener = edit_listener # Needs to implement "curve_edit_done()"

        self.widget = CairoDrawableArea(self.pix_size + 2, 
                                        self.pix_size + 2, 
                                        self._draw)
        self.widget.press_func = self._press_event
        self.widget.motion_notify_func = self._motion_notify_event
        self.widget.release_func = self._release_event

        self.last_point = None
        self.edit_on = False

        if editorpersistance.prefs.dark_theme == True:
            BOX_LINE_COLOR = (0.8, 0.8, 0.8)
            CURVE_COLOR = (0.8, 0.8, 0.8)
            self.curve_color = CURVE_COLOR
コード例 #7
0
    def __init__(self, button_width, button_height, button_y, widget_width, widget_height):
        # Create widget and connect listeners
        self.widget = CairoDrawableArea(widget_width, 
                                        widget_height, 
                                        self._draw)
        self.widget.press_func = self._press_event
        self.widget.motion_notify_func = self._motion_notify_event
        self.widget.release_func = self._release_event

        self.pressed_callback_funcs = None # set later
        self.released_callback_funcs = None # set later

        self.pressed_button = -1

        self.degrees = M_PI / 180.0

        self.button_width = button_width
        self.button_height = button_height
        self.button_y = button_y
        self.button_x = 0 # set when first allocation known by extending class

        self.icons = []
        self.image_x = []
        self.image_y = []
        self.sensitive = []
        
        if editorpersistance.prefs.buttons_style == editorpersistance.GLASS_STYLE:
            self.glass_style = True
        else:
            self.glass_style = False
        
        # Dark theme comes with flat buttons
        self.dark_theme = False
        if editorpersistance.prefs.dark_theme == True:
            self.glass_style = False
            self.dark_theme = True

        self.draw_button_gradients = True # set False at object creation site to kill all gradients
コード例 #8
0
ファイル: extraeditors.py プロジェクト: Mermouy/flowblade
class CurvesBoxEditor(BoxEditor):

    def __init__(self, pix_size, curve, edit_listener):
        BoxEditor.__init__(self, pix_size)
        self.curve = curve # lutfilter.CRCurve
        global BOX_LINE_COLOR, CURVE_COLOR
        self.curve_color = CURVE_COLOR
        self.edit_listener = edit_listener # Needs to implement "curve_edit_done()"

        self.widget = CairoDrawableArea(self.pix_size + 2, 
                                        self.pix_size + 2, 
                                        self._draw)
        self.widget.press_func = self._press_event
        self.widget.motion_notify_func = self._motion_notify_event
        self.widget.release_func = self._release_event

        self.last_point = None
        self.edit_on = False

        if editorpersistance.prefs.dark_theme == True:
            BOX_LINE_COLOR = (0.8, 0.8, 0.8)
            CURVE_COLOR = (0.8, 0.8, 0.8)
            self.curve_color = CURVE_COLOR

    def set_curve(self, curve, curve_color):
        self.curve = curve
        self.curve_color = curve_color

        self.widget.queue_draw()

    def _press_event(self, event):
        vx, vy = BoxEditor.get_box_val_point(self, event.x, event.y)
        p = lutfilter.CurvePoint(int(round(vx * 255)), int(round(vy * 255)))
        self.last_point = p
        self.edit_on = True
        self.curve.remove_range(self.last_point.x - 3, self.last_point.x + 3 )
        self.curve.set_curve_point(p)

        self.widget.queue_draw()

    def _motion_notify_event(self, x, y, state):
        if self.edit_on == False:
            return
        vx, vy = BoxEditor.get_box_val_point(self, x, y)
        p = lutfilter.CurvePoint(int(round(vx * 255)), int(round(vy * 255)))
        self.curve.remove_range(self.last_point.x, p.x)
        self.curve.set_curve_point(p)
        self.last_point = p

        self.widget.queue_draw()

    def _release_event(self, event):
        if self.edit_on == False:
            return
        vx, vy = BoxEditor.get_box_val_point(self, event.x, event.y)
        p = lutfilter.CurvePoint(int(round(vx * 255)),int(round(vy * 255)))
        self.curve.remove_range(self.last_point.x, p.x)
        self.curve.set_curve_point(p)

        self.edit_on = False
        self.edit_listener.curve_edit_done()
        self.widget.queue_draw()

    def _draw(self, event, cr, allocation):
        # bg box
        BoxEditor.draw_box(self, cr, allocation)

        x, y, w, h = allocation

        # curve
        cr.set_source_rgb(*self.curve_color)#  seg.setColor( CURVE_COLOR );
        cr.set_line_width(1.5)
        cp = self.curve.get_curve(True) #we get 256 values
        px, py = BoxEditor.get_box_panel_point(self, 0, cp[0], 255)
        cr.move_to(px, py)

        for i in range(1, len(cp)): #int i = 0; i < cp.length - 1; i++ )
            px, py = BoxEditor.get_box_panel_point(self, i, cp[i], 255.0)
            cr.line_to(px, py)
        cr.stroke()

        cr.rectangle(1, 1, w - 3, h - 3)
        cr.clip()

        # edit points
        for p in self.curve.points:
            px, py = BoxEditor.get_box_panel_point(self, p.x, p.y, 255.0)
            _draw_select_circle(cr, px, py, (1,1,1), 4, 2, 0, -4, -4)
コード例 #9
0
ファイル: extraeditors.py プロジェクト: Mermouy/flowblade
class ColorBox:
    
    def __init__(self, edit_listener, width=260, height=260):
        self.W = width
        self.H = height
        self.widget = CairoDrawableArea(self.W, 
                                        self.H, 
                                        self._draw)
        self.widget.press_func = self._press_event
        self.widget.motion_notify_func = self._motion_notify_event
        self.widget.release_func = self._release_event
        self.X_PAD = 12
        self.Y_PAD = 12
        self.CIRCLE_HALF = 8
        self.cursor_x = self.X_PAD
        self.cursor_y = self.H - self.Y_PAD
        self.edit_listener = edit_listener
        self.hue = 0.0
        self.saturation = 0.0
        self.draw_saturation_gradient = True
        self.selection_cursor = SELECT_CIRCLE

    def get_hue_saturation(self):
        return (self.hue, self.saturation)

    def _save_values(self):
        self.hue = float((self.cursor_x - self.X_PAD)) / float((self.W - 2 * self.X_PAD))
        self.saturation = float(abs(self.cursor_y - self.H + self.Y_PAD)) / float((self.H - 2 * self.Y_PAD))

    def set_cursor(self, hue, saturation):
        self.cursor_x = self._x_for_hue(hue)
        self.cursor_y = self._y_for_saturation(saturation)
        self._save_values()

    def _x_for_hue(self, hue):
        return self.X_PAD + hue * (self.W - self.X_PAD * 2)

    def _y_for_saturation(self, saturation):
        return self.Y_PAD + (1.0 - saturation) * (self.H - self.Y_PAD *2)
        
    def _press_event(self, event):
        self.cursor_x, self.cursor_y = self._get_legal_point(event.x, event.y)
        self._save_values()
        self.edit_listener()
        self.widget.queue_draw()

    def _motion_notify_event(self, x, y, state):
        self.cursor_x, self.cursor_y = self._get_legal_point(x, y)
        self._save_values()
        self.edit_listener()
        self.widget.queue_draw()
        
    def _release_event(self, event):
        self.cursor_x, self.cursor_y = self._get_legal_point(event.x, event.y)
        self._save_values()
        self.edit_listener()
        self.widget.queue_draw()
        
    def _get_legal_point(self, x, y):
        if x < self.X_PAD:
            x = self.X_PAD
        elif x > self.W - self.X_PAD:
            x = self.W - self.X_PAD

        if y < self.Y_PAD:
            y = self.Y_PAD
        elif y > self.H - self.Y_PAD:
            y = self.H - self.Y_PAD
        
        return (x, y)
        
    def _draw(self, event, cr, allocation):
        """
        Callback for repaint from CairoDrawableArea.
        We get cairo context and allocation.
        """
        x, y, w, h = allocation

        # Draw bg
        cr.set_source_rgb(*(gui.bg_color_tuple))
        cr.rectangle(0, 0, w, h)
        cr.fill()

        x_in = self.X_PAD
        x_out = self.W - self.X_PAD
        y_in = self.Y_PAD
        y_out = self.H - self.Y_PAD

        grad = cairo.LinearGradient (x_in, 0, x_out, 0)
        grad.add_color_stop_rgba(*RED_STOP)
        grad.add_color_stop_rgba(*YELLOW_STOP)
        grad.add_color_stop_rgba(*GREEN_STOP)
        grad.add_color_stop_rgba(*CYAN_STOP)
        grad.add_color_stop_rgba(*MAGENTA_STOP)
        grad.add_color_stop_rgba(*RED_STOP_END)

        cr.set_source(grad)
        cr.rectangle(self.X_PAD, self.Y_PAD, x_out - x_in, y_out - y_in)
        cr.fill()

        if self.draw_saturation_gradient == True:
            grey_grad = cairo.LinearGradient (0, y_in, 0, y_out)
            grey_grad.add_color_stop_rgba(*GREY_GRAD_1)
            grey_grad.add_color_stop_rgba(*GREY_GRAD_2)

            cr.set_source(grey_grad)
            cr.rectangle(self.X_PAD, self.Y_PAD, x_out - x_in, y_out - y_in)
            cr.fill()

        if self.selection_cursor == SELECT_CIRCLE:
            _draw_select_circle(cr, self.cursor_x - self.CIRCLE_HALF, self.cursor_y - self.CIRCLE_HALF, (1, 1, 1), 8, 6, 8)
        else:
            _draw_select_line(cr, self.cursor_x, y_out)
コード例 #10
0
ファイル: extraeditors.py プロジェクト: Mermouy/flowblade
class AbstractColorWheel:

    def __init__(self, edit_listener):
        self.widget = CairoDrawableArea(260, 
                                        260, 
                                        self._draw)
        self.widget.press_func = self._press_event
        self.widget.motion_notify_func = self._motion_notify_event
        self.widget.release_func = self._release_event
        self.X_PAD = 3
        self.Y_PAD = 3
        self.CENTER_X = 130
        self.CENTER_Y = 130
        self.MAX_DIST = 123
        self.twelwe_p = (self.CENTER_X , self.CENTER_Y - self.MAX_DIST)
        self.CIRCLE_HALF = 6
        self.cursor_x = self.CENTER_X
        self.cursor_y = self.CENTER_Y
        self.WHEEL_IMG = gtk.gdk.pixbuf_new_from_file(respaths.IMAGE_PATH + "color_wheel.png")
        self.edit_listener = edit_listener
        self.angle = 0.0
        self.distance = 0.0

    def _press_event(self, event):
        """
        Mouse button callback
        """
        self.cursor_x, self.cursor_y = self._get_legal_point(event.x, event.y)
        self._save_point()
        self.widget.queue_draw()

    def _motion_notify_event(self, x, y, state):
        """
        Mouse move callback
        """
        self.cursor_x, self.cursor_y = self._get_legal_point(x, y)
        self._save_point()
        self.widget.queue_draw()
        
    def _release_event(self, event):
        self.cursor_x, self.cursor_y = self._get_legal_point(event.x, event.y)
        self._save_point()
        self.edit_listener()
        self.widget.queue_draw()
        
    def _get_legal_point(self, x, y):
        vec = viewgeom.get_vec_for_points((self.CENTER_X, self.CENTER_Y), (x, y))
        dist = vec.get_length()
        if dist < self.MAX_DIST:
            return (x, y)

        new_vec = vec.get_multiplied_vec(self.MAX_DIST / dist )
        return new_vec.end_point

    def get_angle(self, p):
        angle = viewgeom.get_angle_in_deg(self.twelwe_p, (self.CENTER_X, self.CENTER_Y), p)
        clockwise = viewgeom.points_clockwise(self.twelwe_p, (self.CENTER_X, self.CENTER_Y), p)

        if clockwise:
             angle = 360.0 - angle;
        
        # Color circle starts from 11 o'clock
        angle = angle - 30.0
        if angle  < 0.0:
            angle = angle + 360.0

        return angle

    def get_distance(self, p):
        vec = viewgeom.get_vec_for_points((self.CENTER_X, self.CENTER_Y), p)
        dist = vec.get_length()
        return dist/self.MAX_DIST

    def _save_point(self):
        print "_save_point not implemented"
        pass

    def get_angle_and_distance(self):
        if self.band == SHADOW:
            x = self.shadow_x
            y = self.shadow_y
        elif self.band == MID:
            x = self.mid_x
            y = self.mid_y
        else:
            x = self.hi_x
            y = self.hi_y
        p = (x, y)
        angle = self._get_angle(p)
        distance = self._get_distance(p)
        return (angle, distance)

    def _draw(self, event, cr, allocation):
        """
        Callback for repaint from CairoDrawableArea.
        We get cairo context and allocation.
        """
        x, y, w, h = allocation

        # Draw bg
        cr.set_source_rgb(*(gui.bg_color_tuple))
        cr.rectangle(0, 0, w, h)
        cr.fill()

        cr.set_source_pixbuf(self.WHEEL_IMG, self.X_PAD, self.Y_PAD)
        cr.paint()
コード例 #11
0
ファイル: positionbar.py プロジェクト: Mermouy/flowblade
class PositionBar:
    """
    GUI component used to set/display position in clip/timeline
    """

    def __init__(self):
        self.widget = CairoDrawableArea(BAR_WIDTH, 
                                        BAR_HEIGHT, 
                                        self._draw)
        self.widget.press_func = self._press_event
        self.widget.motion_notify_func = self._motion_notify_event
        self.widget.release_func = self._release_event
        self._pos = END_PAD # in display pixels
        self.mark_in_norm = -1.0 # program length normalized
        self.mark_out_norm = -1.0
        self.disabled = False
        self.mouse_release_listener = None # when used in tools (Tiler ate.) this used to update bg image

        if editorpersistance.prefs.dark_theme == True:
            global LINE_COLOR, BG_COLOR, DISABLED_BG_COLOR, SELECTED_RANGE_COLOR, MARK_COLOR
            LINE_COLOR = DARK_LINE_COLOR
            BG_COLOR = DARK_BG_COLOR
            DISABLED_BG_COLOR = DARK_DISABLED_BG_COLOR
            SELECTED_RANGE_COLOR = DARK_SELECTED_RANGE_COLOR
            MARK_COLOR = DARK_MARK_COLOR

    def set_listener(self, listener):
        self.position_listener = listener

    def set_normalized_pos(self, norm_pos):
        """
        Sets position in range 0 - 1
        """
        self._pos = self._get_panel_pos(norm_pos)
        self.widget.queue_draw()

    def update_display_from_producer(self, producer):
        self.producer = producer
        length = producer.get_length() # Get from MLT
        try:
            self.mark_in_norm = float(producer.mark_in) / length
            self.mark_out_norm = float(producer.mark_out) / length
            frame_pos = producer.frame()
            norm_pos = float(frame_pos) / length
            self._pos = self._get_panel_pos(norm_pos)
        except ZeroDivisionError:
            self.mark_in_norm = 0
            self.mark_out_norm = 0
            self._pos = self._get_panel_pos(0)

        self.widget.queue_draw()

    def _get_panel_pos(self, norm_pos):
        return END_PAD + int(norm_pos * 
               (self.widget.allocation.width - 2 * END_PAD))

    def _draw(self, event, cr, allocation):
        """
        Callback for repaint from CairoDrawableArea.
        We get cairo contect and allocation.
        """
        x, y, w, h = allocation
        
        # Draw bb
        draw_color = BG_COLOR
        if self.disabled:
            draw_color = DISABLED_BG_COLOR
        cr.set_source_rgb(*draw_color)
        cr.rectangle(0,0,w,h)
        cr.fill()
        
        # Draw selected area if marks set
        if self.mark_in_norm >= 0 and self.mark_out_norm >= 0:
            cr.set_source_rgb(*SELECTED_RANGE_COLOR)
            m_in = self._get_panel_pos(self.mark_in_norm)
            m_out = self._get_panel_pos(self.mark_out_norm)
            cr.rectangle(m_in, 0, m_out - m_in, h)
            cr.fill()
                
        # Get area between end pads
        active_width = w - 2 * END_PAD

        # Draw lines
        cr.set_line_width(1.0)
        x_step = float(active_width) / (LINE_COUNT)        
        for i in range(LINE_COUNT + 1):
            cr.move_to(int((i) * x_step) + END_PAD + 0.5, -0.5)
            cr.line_to(int((i) * x_step) + END_PAD + 0.5, LINE_HEIGHT + 0.5)
        for i in range(LINE_COUNT + 1):
            cr.move_to(int((i) * x_step) + END_PAD + 0.5, BAR_HEIGHT)
            cr.line_to(int((i) * x_step) + END_PAD + 0.5, 
                       BAR_HEIGHT - LINE_HEIGHT  + 0.5)
            
        cr.set_source_rgb(*LINE_COLOR)
        cr.stroke()

        # Draw mark in and mark out
        self.draw_mark_in(cr, h)
        self.draw_mark_out(cr, h)

        # Draw position pointer
        if self.disabled:
            return
        cr.set_line_width(2.0)
        cr.set_source_rgb(*POINTER_COLOR)
        cr.move_to(self._pos + 0.5, 0)
        cr.line_to(self._pos + 0.5, BAR_HEIGHT)
        cr.stroke()

        speed = editorstate.PLAYER().producer.get_speed()
        if speed != 1.0 and speed != 0.0:
            cr.set_source_rgb(*SPEED_TEST_COLOR)
            cr.select_font_face ("sans-serif",
                                 cairo.FONT_SLANT_NORMAL,
                                 cairo.FONT_WEIGHT_BOLD)
            cr.set_font_size(11)
            disp_str = str(speed) + "x"
            tx, ty, twidth, theight, dx, dy = cr.text_extents(disp_str)
            cr.move_to( w/2 - twidth/2, 13)
            cr.show_text(disp_str)

    def draw_mark_in(self, cr, h):
        """
        Draws mark in graphic if set.
        """
        if self.mark_in_norm < 0:
            return
             
        x = self._get_panel_pos(self.mark_in_norm)

        cr.move_to (x, MARK_PAD)
        cr.line_to (x, h - MARK_PAD)
        cr.line_to (x - 2 * MARK_LINE_WIDTH, h - MARK_PAD)
        cr.line_to (x - 2 * MARK_LINE_WIDTH, 
                    h - MARK_LINE_WIDTH - MARK_PAD) 
        cr.line_to (x - MARK_LINE_WIDTH, h - MARK_LINE_WIDTH - MARK_PAD )
        cr.line_to (x - MARK_LINE_WIDTH, MARK_LINE_WIDTH + MARK_PAD)
        cr.line_to (x - 2 * MARK_LINE_WIDTH, MARK_LINE_WIDTH + MARK_PAD )
        cr.line_to (x - 2 * MARK_LINE_WIDTH, MARK_PAD)
        cr.close_path();

        cr.set_source_rgb(*MARK_COLOR)
        cr.fill()

    def draw_mark_out(self, cr, h):
        """
        Draws mark out graphic if set.
        """
        if self.mark_out_norm < 0:
            return
             
        x = self._get_panel_pos(self.mark_out_norm)

        cr.move_to (x, MARK_PAD)
        cr.line_to (x, h - MARK_PAD)
        cr.line_to (x + 2 * MARK_LINE_WIDTH, h - MARK_PAD)
        cr.line_to (x + 2 * MARK_LINE_WIDTH, 
                    h - MARK_LINE_WIDTH - MARK_PAD) 
        cr.line_to (x + MARK_LINE_WIDTH, h - MARK_LINE_WIDTH - MARK_PAD )
        cr.line_to (x + MARK_LINE_WIDTH, MARK_LINE_WIDTH + MARK_PAD)
        cr.line_to (x + 2 * MARK_LINE_WIDTH, MARK_LINE_WIDTH + MARK_PAD )
        cr.line_to (x + 2 * MARK_LINE_WIDTH, MARK_PAD)
        cr.close_path();

        cr.set_source_rgb(*MARK_COLOR)
        cr.fill()

    def _press_event(self, event):
        """
        Mouse button callback
        """
        if self.disabled:
            return
        if editorstate.timeline_visible():
            trimmodes.set_no_edit_trim_mode()

        if((event.button == 1)
            or(event.button == 3)):
            # Set pos to in active range to get normalized pos
            self._pos = self._legalize_x(event.x)
            # Listener calls self.set_normalized_pos()
            # _pos gets actually set twice
            # Listener also updates other frame displayers
            self.position_listener(self.normalized_pos(), self.producer.get_length())

    def _motion_notify_event(self, x, y, state):
        """
        Mouse move callback
        """
        if self.disabled:
            return

        if((state & gtk.gdk.BUTTON1_MASK)
            or (state & gtk.gdk.BUTTON3_MASK)):
            self._pos = self._legalize_x(x)
            # Listener calls self.set_normalized_pos()
            self.position_listener(self.normalized_pos(), self.producer.get_length())

    def _release_event(self, event):
        """
        Mouse release callback.
        """
        if self.disabled:
            return

        self._pos = self._legalize_x(event.x)
        # Listener calls self.set_normalized_pos()
        self.position_listener(self.normalized_pos(), self.producer.get_length())

        if self.mouse_release_listener != None:
            self.mouse_release_listener(self.normalized_pos(), self.producer.get_length())
 
    def _legalize_x(self, x):
        """
        Get x in pixel range corresponding normalized position 0.0 - 1.0.
        This is needed because of end pads.
        """
        w = self.widget.allocation.width
        if x < END_PAD:
            return END_PAD
        elif x > w - END_PAD:
            return w - END_PAD
        else:
            return x
    
    def normalized_pos(self):
        return float(self._pos - END_PAD) / \
                (self.widget.allocation.width - END_PAD * 2)