예제 #1
0
 def start_animation(self, x, y):    
     if not self.in_animation:
         self.in_animation = False
         try:
             self.timeline.stop()
         except:    
             pass
         
         self.timeline = Timeline(self.animation_time, CURVE_SINE)
         self.timeline.connect("stop", self.stop_animation)
         self.timeline.connect("update", self.update_animation, x, y)
         self.timeline.connect("completed", self.completed_animation, x, y)
         self.timeline.run()
예제 #2
0
def favorite_animation(window):

    # Add install animation.
    (screen, px, py, modifier_type) = window.get_display().get_pointer()
    ax, ay = px, py

    (wx, wy) = window.window.get_origin()
    offset_bx = 480
    offset_by = 5
    bx, by = wx + offset_bx, wy + offset_by

    offset_cx = 10
    offset_cy = 10
    if ax < bx:
        cx, cy = wx + offset_bx + offset_cx, wy + offset_by + offset_cy
    else:
        cx, cy = wx + offset_bx - offset_cx, wy + offset_by + offset_cy

    [[a], [b], [c]] = solve_parabola((ax, ay), (bx, by), (cx, cy))

    icon_window = IconWindow(utils.get_common_image('heart/heartx2.png'))
    icon_window.move(ax, ay)
    icon_window.show_all()

    timeline = Timeline(500, CURVE_SINE)
    timeline.connect(
        "update",
        lambda source, status: update(source, status, icon_window, (ax, ay),
                                      (bx, by), (cx, cy), (a, b, c)))
    timeline.connect("completed", lambda source: finish(source, icon_window))
    timeline.run()
예제 #3
0
 def start_animation(self, target_pixbuf, is_default=False):
     self.is_default = False
     self.target_pixbuf = target_pixbuf
     if not self.in_animation:
         self.in_animation = False
         try:
             self.timeline.stop()
         except:
             pass
         self.timeline = Timeline(self.animation_time, CURVE_SINE)
         self.timeline.connect("update", self.update_animation)
         self.timeline.connect(
             "completed", lambda source: self.completed_animation(
                 source, target_pixbuf, is_default))
         self.timeline.run()
예제 #4
0
def install_pkg(bus_interface, install_page, pkg_names, window):
    for install_item in install_page.treeview.visible_items:
        if install_item.pkg_name in pkg_names and install_item.status != install_item.STATUS_DOWNLOAD_FAILED:
            pkg_names.remove(install_item.pkg_name)
    if pkg_names == []:
        return

    # Add install animation.
    (screen, px, py, modifier_type) = window.get_display().get_pointer()
    ax, ay = px, py

    (wx, wy) = window.window.get_origin()
    offset_bx = 430
    offset_by = -20
    bx, by = wx + offset_bx, wy + offset_by

    offset_cx = 10
    offset_cy = 10
    if ax < bx:
        cx, cy = wx + offset_bx + offset_cx, wy + offset_by + offset_cy
    else:
        cx, cy = wx + offset_bx - offset_cx, wy + offset_by + offset_cy

    [[a], [b], [c]] = solve_parabola((ax, ay), (bx, by), (cx, cy))

    icon_window = IconWindow(pkg_names[0])
    icon_window.move(ax, ay)
    icon_window.show_all()

    timeline = Timeline(500, CURVE_SINE)
    timeline.connect("update", lambda source, status: update(source, status, icon_window, (ax, ay), (bx, by), (cx, cy), (a, b, c)))
    timeline.connect("completed", lambda source: finish(source, icon_window, bus_interface, pkg_names))
    timeline.run()
예제 #5
0
def favorite_animation(window):

    # Add install animation.
    (screen, px, py, modifier_type) = window.get_display().get_pointer()
    ax, ay = px, py
    
    (wx, wy) = window.window.get_origin()
    offset_bx = 480
    offset_by = 5
    bx, by = wx + offset_bx, wy + offset_by
    
    offset_cx = 10
    offset_cy = 10
    if ax < bx:
        cx, cy = wx + offset_bx + offset_cx, wy + offset_by + offset_cy
    else:
        cx, cy = wx + offset_bx - offset_cx, wy + offset_by + offset_cy
    
    [[a], [b], [c]] = solve_parabola((ax, ay), (bx, by), (cx, cy))
    
    icon_window = IconWindow(utils.get_common_image('heart/heartx2.png'))
    icon_window.move(ax, ay)
    icon_window.show_all()
    
    timeline = Timeline(500, CURVE_SINE)
    timeline.connect("update", lambda source, status: update(source, status, icon_window, (ax, ay), (bx, by), (cx, cy), (a, b, c)))
    timeline.connect("completed", lambda source: finish(source, icon_window))
    timeline.run()
예제 #6
0
 def move_down(self, send_obj):
     print "send_level", send_obj.level
     print "self_level", self.level
     if self.level == 2:
         (move_down_height) = send_obj.window_height
         self.move_up_timeline = Timeline(self.animation_time, CURVE_SINE)
         self.move_up_timeline.connect("update", self.update_move_down_animation, move_down_height)
         self.move_up_timeline.run()
         self.level = 1
예제 #7
0
 def start_animation(self, widget):    
     if not self.in_animation:
         self.in_animation = False
         try:
             self.timeline.stop()
         except:    
             pass
         self.timeline = Timeline(self.animation_time, CURVE_SINE)
         self.timeline.connect("update", self.update_animation)
         self.timeline.connect("completed", lambda source: self.completed_animation(source, widget))
         self.timeline.run()
예제 #8
0
 def start_animation(self, target_pixbuf, is_default=False):    
     self.is_default = False
     self.target_pixbuf = target_pixbuf
     if not self.in_animation:
         self.in_animation = False
         try:
             self.timeline.stop()
         except:    
             pass
         self.timeline = Timeline(self.animation_time, CURVE_SINE)
         self.timeline.connect("update", self.update_animation)
         self.timeline.connect("completed", 
                               lambda source: self.completed_animation(source, target_pixbuf, is_default))
         self.timeline.run()
 def start_animation(self, index, tab_start_x):
     if not self.in_animiation:
         self.in_animiation = True
         
         source_tab_x = tab_start_x + self.tab_index * self.tab_width
         target_tab_x = tab_start_x + index * self.tab_width
         
         timeline = Timeline(self.tab_animation_time, CURVE_SINE)
         timeline.connect('update', lambda source, status: self.update_animation(source, status, source_tab_x, (target_tab_x - source_tab_x)))
         timeline.connect("completed", lambda source: self.completed_animation(source, index))
         timeline.run()
         
         self.emit("tab_switch_start", index)
예제 #10
0
    def start_animation(self, animiation_time, target_index=None):
        if target_index == None:
            if self.active_index >= self.slide_number - 1:
                target_index = 0
            else:
                target_index = self.active_index + 1

        if not self.in_animiation:
            self.in_animiation = True
            self.target_index = target_index
            timeline = Timeline(animiation_time, CURVE_SINE)
            timeline.connect("update", self.update_animation)
            timeline.connect(
                "completed",
                lambda source: self.completed_animation(source, target_index))
            timeline.run()
        return True
예제 #11
0
    def start_animation(self, index, tab_start_x):
        if not self.in_animiation:
            self.in_animiation = True

            source_tab_x = tab_start_x + self.tab_index * self.tab_width
            target_tab_x = tab_start_x + index * self.tab_width

            timeline = Timeline(self.tab_animation_time, CURVE_SINE)
            timeline.connect(
                'update', lambda source, status: self.update_animation(
                    source, status, source_tab_x,
                    (target_tab_x - source_tab_x)))
            timeline.connect(
                "completed",
                lambda source: self.completed_animation(source, index))
            timeline.run()

            self.emit("tab_switch_start", self.items[index])
 def start_animation(self, animiation_time, target_index=None):        
     if target_index == None:
         if self.active_index >= self.slide_number - 1:
             target_index = 0
         else:    
             target_index = self.active_index + 1
             
     if not self.in_animiation:        
         self.in_animiation = True
         self.target_index = target_index
         timeline = Timeline(animiation_time, CURVE_SINE)
         timeline.connect("update", self.update_animation)
         timeline.connect("completed", lambda source: self.completed_animation(source, target_index))
         timeline.run()
     return True    
예제 #13
0
    def start_animation(self, animation_time, index=None):
        # Update ticker with active index if option index is None.
        if index == None:
            if self.active_index >= self.image_number - 1:
                index = 0
            else:
                index = self.active_index + 1

        if not self.in_animiation:
            self.in_animiation = True
            self.target_index = index

            timeline = Timeline(animation_time, CURVE_SINE)
            timeline.connect('update', self.update_animation)
            timeline.connect(
                "completed",
                lambda source: self.completed_animation(source, index))
            timeline.run()

        return True
예제 #14
0
    def start_animation(self, animation_time, index=None):
        # Update ticker with active index if option index is None.
        if index == None:
            if self.active_index >= self.image_number - 1:
                index = 0
            else:
                index = self.active_index + 1

        if not self.in_animiation:
            self.in_animiation = True
            self.target_index = index

            timeline = Timeline(animation_time, CURVE_SINE)
            timeline.connect('update', self.update_animation)
            timeline.connect("completed", lambda source: self.completed_animation(source, index))
            timeline.run()

        return True
 def start_move_up_animation(self, height):
     timeline = Timeline(ANIMATION_TIME, CURVE_SINE)
     timeline.connect("update", self.update_move_up_animation, height)
     timeline.connect("completed", self.on_move_up_completed)
     timeline.run()
예제 #16
0
 def start_destroy_animation(self):
     timeline = Timeline(self.animation_time, CURVE_SINE)
     timeline.connect("update", self.update_destory_animation)
     timeline.connect("completed", self.destroy_animation_complete)
     timeline.run()
     return False
예제 #17
0
class Bubble(gtk.Window):
    '''
    class docs
    '''

    def __init__(self, notification, height, create_time):
        '''
        init docs
        '''
        gtk.Window.__init__(self, gtk.WINDOW_POPUP)
        self.notification = notification
        self.create_time = create_time
        self.init_size(height)
        self.init_pixbuf()
        self.init_action_dict()

        self.set_colormap(gtk.gdk.Screen().get_rgba_colormap() or gtk.gdk.Screen().get_rgb_colormap())
        self.set_keep_above(True)
        self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK)

        self.connect("expose-event", self.on_expose_event)
        self.connect("button-press-event", self.on_button_press_event)
        self.connect("motion-notify-event", self.on_motion_notify_event)

        self.animation_time = 200
        self.level = 0
        self.win_x, self.win_y = self._get_position()
        self.pointer_hand_rectangles = []

        self.move(self.win_x, self.win_y)
        self.set_opacity(0)
        self.show_all()
        self.timeout_id = gobject.timeout_add(EXPIRE_TIMEOUT, self.start_destroy_animation)

        event_manager.connect("manual-cancelled", self.move_down)

    def init_size(self, height):
        '''
        docs
        '''
        self.window_width = 302
        self.window_height = height

        self.close_pixbuf = app_theme.get_pixbuf("close.png").get_pixbuf()
        self.close_rect = gtk.gdk.Rectangle(self.window_width - self.close_pixbuf.get_width() - CLOSE_OFFSET_WIDTH,
                                            CLOSE_OFFSET_HEIGHT,
                                            self.close_pixbuf.get_width(),
                                            self.close_pixbuf.get_height())


        self.set_size_request(self.window_width, self.window_height)

        self.action_button_areas = []

    @property
    def source_cmd(self):
        actions = self.notification.actions
        if "default" in actions:
            try:
                cmd = actions[actions.index("default") + 1]
                return cmd
            except Exception:
                pass

        return self.notification.app_name

    def get_icon_pixbuf(self):
        hints = self.notification.hints
        pixbuf = None
        image_data = hints.get("image-data", None) or hints.get("image_data", None)
        if image_data:
            pixbuf = icon_manager.pixbuf_from_dbus(image_data, ICON_SIZE)

        if pixbuf: return pixbuf

        image_path = hints.get("image-path", None) or hints.get("image_path", None)
        if image_path:
            try:
                pixbuf = icon_manager.pixbuf_from_path(image_path, ICON_SIZE[0], ICON_SIZE[1])
            except:
                pixbuf = None

        if pixbuf: return pixbuf

        app_icon = self.notification.app_icon
        if app_icon:
            pixbuf = icon_manager.pixbuf_from_icon_name(app_icon, ICON_SIZE[0])
            if pixbuf:
                return pixbuf
            else:
                pixbuf = icon_manager.pixbuf_from_path(app_icon, ICON_SIZE[0], ICON_SIZE[1])
                if pixbuf:
                    return pixbuf

        return self.default_icon


    def init_pixbuf(self):
        pixbuf_file_name = "mini.png" if self.window_height == 87 else "max.png"
        self.bg_pixbuf = app_theme.get_pixbuf(pixbuf_file_name).get_pixbuf()

        self.default_icon = app_theme.get_pixbuf("icon.png").get_pixbuf()
        self.notification_icon = self.get_icon_pixbuf()


    def on_expose_event(self, widget, event):
        cr = widget.window.cairo_create()
        rect = widget.allocation

        if self.is_composited():
            cr.rectangle(*rect)
            cr.set_source_rgba(0, 0, 0, 0)
            cr.set_operator(cairo.OPERATOR_SOURCE)
            cr.fill()
        else:
            cr.rectangle(rect.x, rect.y, rect.width, rect.height)
            cr.set_operator(cairo.OPERATOR_SOURCE)
            cr.set_source_rgb(0.9, 0.9, 0.9)
            cr.fill()


        cr.set_operator(cairo.OPERATOR_OVER)
        self.draw_notification(cr, rect)

        propagate_expose(widget, event)

        return True

    def _render_action(self, cr, rect, text):
        with cairo_disable_antialias(cr):
            cr.rectangle(*rect)
            cr.set_source_rgb(1, 1, 1)
            cr.set_line_width(2)
            cr.stroke_preserve()
            cr.set_source_rgb(*color_hex_to_cairo(ACTION_BUTTON_COLOR_BG))
            cr.fill()

            draw_text(cr, text, rect.x, rect.y, rect.width, rect.height, text_color="#ffffff", alignment=pango.ALIGN_CENTER)

    def init_action_dict(self):
        self.action_dict = {}

        actions = self.notification.actions
        if len(actions) % 2 == 0:
            for index, action in enumerate(actions):
                if index % 2 == 0:
                    self.action_dict[action] = actions[index + 1]
        if "default" in self.action_dict:
            self.action_dict.pop("default")

    def draw_notification(self, cr, rect):
        draw_pixbuf(cr, self.bg_pixbuf, rect.x, rect.y)

        icon_x = rect.x + self.close_rect.x
        icon_y = rect.y + self.close_rect.y
        draw_pixbuf(cr, self.close_pixbuf, icon_x, icon_y)

        # Draw app icon.
        icon_x = APP_ICON_X + (APP_ICON_WIDTH  - self.notification_icon.get_width()) / 2
        icon_y = rect.y + (rect.height - self.notification_icon.get_height()) / 2
        draw_pixbuf(cr, self.notification_icon, icon_x, icon_y)

        # Draw summary.
        width, _height =  get_content_size(self.notification.summary)
        draw_text(cr,
                  "<b>%s</b>" % self.notification.summary,
                  rect.x + TEXT_X, rect.y + SUMMARY_TEXT_Y,
                  TEXT_WIDTH - self.close_pixbuf.get_width(), _height,
                  text_color="#FFFFFF", text_size=10)

        #Draw body
        body_text_y = SUMMARY_TEXT_Y + _height + TEXT_PADDING_Y
        render_hyperlink_support_text(self, cr, self.notification.body,
                                      rect.x + TEXT_X, rect.y + body_text_y,
                                      TEXT_WIDTH - self.close_pixbuf.get_width(),
                                      BODY_TEXT_HEIGHT,
                                      wrap_width=TEXT_WIDTH - self.close_pixbuf.get_width() + 5,
                                      text_color="#FFFFFF", text_size=10,
                                      vertical_alignment=TEXT_ALIGN_TOP,
                                      clip_line_count=2)

        # Draw action
        for index, key in enumerate(self.action_dict):
            if key != "default" and index < 2:
                temp_rect = gtk.gdk.Rectangle(
                    rect.x + TEXT_X + index * (ACTION_BUTTON_SPACING + ACTION_BUTTON_WIDTH),
                    rect.y + body_text_y + BODY_TEXT_HEIGHT + ACTION_BUTTON_PADDING_Y,
                    ACTION_BUTTON_WIDTH,
                    ACTION_BUTTON_HEIGHT)
                self.action_button_areas.append(temp_rect)
                self._render_action(cr, temp_rect, self.action_dict[key])


    def on_motion_notify_event(self, widget, event):
        for rect in self.pointer_hand_rectangles:
            if is_in_rect((event.x, event.y), rect):
                widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND1))
                return
            else:
                widget.window.set_cursor(None)

        for rect in self.action_button_areas:
            if is_in_rect((event.x, event.y), rect):
                widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND1))
                return
            else:
                widget.window.set_cursor(None)



    def on_button_press_event(self, widget, event):
        if is_in_rect((event.x, event.y),
                      (self.close_rect.x, self.close_rect.y,
                       self.close_rect.width, self.close_rect.height)):
            gobject.source_remove(self.timeout_id)
            self.destroy()
            event_manager.emit("manual-cancelled", self)
            return

        for index, rect in enumerate(self.pointer_hand_rectangles):
            if is_in_rect((event.x, event.y), rect):
                action = self.notification["hints"]["x-deepin-hyperlinks"][index]
                if action.has_key("href"):
                    webbrowser.open_new_tab(action.get("href"))
                return

        for index, rect in enumerate(self.action_button_areas):
            if is_in_rect((event.x, event.y), rect):
                for i, action_key in enumerate(self.action_dict):
                    if i == index:
                        event_manager.emit("action-invoked", (self.notification.id, action_key))
                        gobject.source_remove(self.timeout_id)
                        self.destroy()
                        event_manager.emit("manual-cancelled", self)
                        return

        rect = widget.allocation
        if is_in_rect((event.x, event.y),
                      (rect.x, rect.y, APP_ICON_WIDTH, self.window_height)):
            self.open_source_software()

    def open_source_software(self):
        run_command(self.source_cmd)

    def _get_position(self):
        screen_w, screen_h = get_screen_size()
        win_x = screen_w - self.window_width - WINDOW_OFFSET_WIDTH
        win_y = screen_h - self.window_height - DOCK_HEIGHT - WINDOW_OFFSET_HEIGHT
        self.last_y = win_y + self.window_height

        return win_x, win_y

    def start_destroy_animation(self):
        timeline = Timeline(self.animation_time, CURVE_SINE)
        timeline.connect("update", self.update_destory_animation)
        timeline.connect("completed", self.destroy_animation_complete)
        timeline.run()
        return False

    def update_destory_animation(self, source, status):
        if status != 1:
            self.set_opacity(1.0-status)
        else:
            self.destroy()

    def destroy_animation_complete(self, source):
        event_manager.emit("bubble-destroy", self)

    def move_up_by(self, move_up_height, update_height=False):
        self.win_y = int(self.last_y - move_up_height)
        self.move(self.win_x, self.win_y)
        if update_height:
            self.last_y = self.win_y

    def move_down_by(self, move_down_height, update_height=False):
        self.win_y = int(self.last_y + move_down_height)
        self.move(self.win_x, self.win_y)
        if update_height:
            self.last_y = self.win_y

    def move_down(self, send_obj):
        print "send_level", send_obj.level
        print "self_level", self.level
        if self.level == 2:
            (move_down_height) = send_obj.window_height
            self.move_up_timeline = Timeline(self.animation_time, CURVE_SINE)
            self.move_up_timeline.connect("update", self.update_move_down_animation, move_down_height)
            self.move_up_timeline.run()
            self.level = 1

    def update_move_down_animation(self, source, status, move_down_height):
        self.move_down_by(move_down_height * status, not bool(status - 1))
예제 #18
0
class CoverButton(gtk.Button):
    def __init__(self):
        super(CoverButton, self).__init__()
        
        self.cover_side_pixbuf = app_theme.get_pixbuf("cover/side.png").get_pixbuf()
        self.set_size_request(self.cover_side_pixbuf.get_width(), self.cover_side_pixbuf.get_height())
        self.webcast_dpixbuf = app_theme.get_pixbuf("cover/webcast.png")
        self.local_dpixbuf = app_theme.get_pixbuf("cover/default_cover.png")

        self.connect("expose-event", self.expose_button_cb)
        Dispatcher.connect("album-changed", self.on_album_changed)
        
        self.current_song = None
        self.next_cover_to_download = None
        
        self.condition = threading.Condition()
        self.thread = threading.Thread(target=self.func_thread)
        self.thread.setDaemon(True)
        self.thread.start()
        
        # animation params.
        self.active_alpha = 1.0
        self.target_alpha = 0.0
        self.in_animation = False
        self.animation_time = 1500
        self.active_pixbuf = self.get_default_pixbuf()
        self.target_pixbuf = None
        self.is_default = True
        
    def start_animation(self, target_pixbuf, is_default=False):    
        self.is_default = False
        self.target_pixbuf = target_pixbuf
        if not self.in_animation:
            self.in_animation = False
            try:
                self.timeline.stop()
            except:    
                pass
            self.timeline = Timeline(self.animation_time, CURVE_SINE)
            self.timeline.connect("update", self.update_animation)
            self.timeline.connect("completed", 
                                  lambda source: self.completed_animation(source, target_pixbuf, is_default))
            self.timeline.run()
    
    def update_animation(self, source, status):
        self.active_alpha = 1.0 - status
        self.target_alpha = status
        self.queue_draw()
    
    def completed_animation(self, source, target_pixbuf, is_default):    
        self.active_pixbuf = target_pixbuf
        self.active_alpha = 1.0
        self.target_alpha = 0.0
        self.in_animation = False
        self.is_default = is_default
        self.queue_draw()
        
    def expose_button_cb(self, widget, event):    
        cr = widget.window.cairo_create()
        rect = widget.allocation
        
        # Draw cover side.
        if self.is_default:
            self.active_pixbuf = self.get_default_pixbuf(self.current_song)
            
        draw_pixbuf(cr, self.cover_side_pixbuf, rect.x, rect.y)
        draw_pixbuf(cr, self.active_pixbuf, rect.x + 3, rect.y + 3, self.active_alpha)
        
        if self.target_pixbuf:
            draw_pixbuf(cr, self.target_pixbuf, rect.x + 3, rect.y + 3, self.target_alpha)
            
        return True
        
        
    def func_thread(self):    
        while True:
            self.condition.acquire()
            while not self.next_cover_to_download:
                self.condition.wait()
            next_cover_to_download = self.next_cover_to_download    
            self.next_cover_to_download = None
            self.condition.release()
            self.set_current_cover(True, next_cover_to_download)

    def update_default_cover(self, song):            
        self.start_animation(self.get_default_pixbuf(song), is_default=True)
            
    def get_default_pixbuf(self, song=None):        
        if not song:
            song = Player.song
            
        if not song:    
            pixbuf = self.local_dpixbuf.get_pixbuf()
            return utils.get_optimum_pixbuf(pixbuf, COVER_SIZE["x"], COVER_SIZE["y"])
            
        if song.get_type() == "webcast":
            pixbuf = self.webcast_dpixbuf.get_pixbuf()
        else:    
            pixbuf = self.local_dpixbuf.get_pixbuf()
        return utils.get_optimum_pixbuf(pixbuf, COVER_SIZE["x"], COVER_SIZE["y"])            
            
    def init_default_cover(self):        
        pixbuf = self.get_default_pixbuf()
        self.start_animation(pixbuf, is_default=True)
        
    def on_album_changed(self, obj, song):    
        if song == self.current_song:
            self.update_cover(None, song)
            
    def update_cover(self, widget, song):        
        self.current_song = song
        
        if self.current_song is not None:        
            if not self.set_current_cover(False):
                self.condition.acquire()
                self.next_cover_to_download = self.current_song
                self.condition.notify()
                self.condition.release()
                
    def set_current_cover(self, try_web=True, force_song=None):            
        if not force_song:
            force_song = self.current_song
        filename = CoverManager.get_cover(force_song, try_web)    
        if Player.song != force_song:
            return False
        
        if not filename:
            if not try_web:
                if force_song.get_type() == "webcast":
                    pixbuf = utils.get_optimum_pixbuf(self.webcast_dpixbuf.get_pixbuf(), 
                                                      COVER_SIZE["x"], COVER_SIZE["y"])
                    self.start_animation(pixbuf, is_default=True)
                    return True
                else:    
                    pixbuf = utils.get_optimum_pixbuf(self.local_dpixbuf.get_pixbuf(), 
                                                      COVER_SIZE["x"], COVER_SIZE["y"])
                    self.start_animation(pixbuf, is_default=True)
                    return False
            return False    
        else:
            try:
                pixbuf = get_optimum_pixbuf_from_file(filename, 
                                                      COVER_SIZE["x"], COVER_SIZE["y"])
            except gobject.GError:    
                return False
            else:
                self.start_animation(pixbuf, is_default=False)
                return True
예제 #19
0
class MiniWindow(Window):
    
    def __init__(self):
        Window.__init__(self,
                        window_type=gtk.WINDOW_POPUP,
                        shape_frame_function=self.shape_mini_frame,
                        expose_frame_function=self.expose_mini_frame)
        
        self.set_property("skip-pager-hint", True)
        self.set_property("skip-taskbar-hint", True)
        
        self.body_box = gtk.VBox()
        
        self.control_box = gtk.HBox()
        self.action_box = gtk.HBox()
        self.event_box = gtk.HBox()  
        self.info_box = gtk.HBox()              

        # Build info box
        self.logo_padding_left = 10
        self.logo_padding_right = 10
        logo_box = self.create_logo_box()
        logo_box_align = set_widget_gravity(logo_box, (0.5, 0.5, 0, 0),
                                            (0, 0, self.logo_padding_left,
                                             self.logo_padding_right))
        self.playinfo = PlayInfo(239, None, 0, "#000000")
        self.info_box.pack_start(logo_box_align, False, False)
        self.info_box.pack_start(self.playinfo, False, False)
        
        # Build control box
        self.lyrics_button = self.create_lyrics_button()
        self.signal_auto = False        
        if config.getboolean("lyrics", "status"):
            self.lyrics_button.set_active(True)
        self.signal_auto = True    
        
        
        self.lyrics_padding_left = 10
        self.lyrics_padding_right = 8
        lyrics_button_align = set_widget_gravity(self.lyrics_button, (0.5, 0.5, 0, 0),
                                                 (0, 0, self.lyrics_padding_left,
                                                  self.lyrics_padding_right))
        
        self.previous_button = self.create_button("previous")
        self.next_button = self.create_button("next")
        self.playpause_button = self.create_playpause_button()
        
        # swap played status handler
        self.__id_signal_play = self.playpause_button.connect("toggled", self.on_player_playpause)        
        Player.connect("played", self.__swap_play_status, True)
        Player.connect("paused", self.__swap_play_status, False)
        Player.connect("stopped", self.__swap_play_status, False)
        Player.connect("play-end", self.__swap_play_status, False)
        
        self.volume_slider = VolumeSlider(auto_hide=False, mini_mode=True)
        self.volume_padding_left = 8
        volume_slider_align = set_widget_gravity(self.volume_slider, (0.5, 0.5, 0, 0),
                                                 (0, 0, self.volume_padding_left, 0))
        
        self.action_box.pack_start(lyrics_button_align, False, False)
        self.action_box.pack_start(set_widget_vcenter(self.previous_button), False, False)
        self.action_box.pack_start(set_widget_vcenter(self.playpause_button), False, True)
        self.action_box.pack_start(set_widget_vcenter(self.next_button), False, False)
        self.action_box.pack_start(volume_slider_align, False, False)
        
        # Build event box.
        quell_button = QuellButton()
        quell_button.connect("clicked", self.on_quell_button_clicked)
        
        min_button = MinButton()
        min_button.connect("clicked", lambda w: self.min_window())
        close_button = CloseButton()
        close_button.connect("clicked", self.try_to_quit)
        self.event_box.pack_start(quell_button, False, False)
        self.event_box.pack_start(min_button, False, False)
        self.event_box.pack_start(close_button, False, False)
        event_box_align = set_widget_gravity(self.event_box, paddings=(0, 0, 8, 0))
        
        self.control_box.pack_start(self.action_box, False, True)
        self.control_box.pack_end(event_box_align, False, True)
        
        self.connect("configure-event", self.on_configure_event)
        self.connect("enter-notify-event", self.on_enter_notify_event)
        self.connect("leave-notify-event", self.on_leave_notify_event)
        self.connect("button-press-event", self.on_button_press_event)
        self.connect("motion-notify-event", self.on_motion_notify_event)
        self.connect("button-release-event", self.on_button_release_event)
        
        Dispatcher.connect("close-lyrics", lambda w : self.lyrics_button.set_active(False))
        Dispatcher.connect("show-lyrics", lambda w: self.lyrics_button.set_active(True))
        
        if config.get("mini", "x") == "-1":
            self.set_position(gtk.WIN_POS_CENTER)
        else:    
            self.move(int(config.get("mini","x")),int(config.get("mini","y")))
            
        # pixbufs    
        self.info_pixbuf = None
        self.control_pixbuf = None

        self.body_box.add(self.info_box)    
        self.window_frame.add(self.body_box)
        self.set_size_request(305, 40)
        
        
        # animation params.
        self.active_alpha = 1.0
        self.target_alpha = 0.0
        self.in_animation = False
        self.animation_time = 1000
        self.animation_timeout_id = None
        self.draw_animation = False
        self.active_draw_func = None
        self.target_draw_func = None
        
        # drag params.
        self.drag_move = False
        self.old_x = self.old_y = self.mouse_x = self.mouse_y = 0
        
    def on_quell_button_clicked(self, widget):    
        main_window = get_main_window()
        main_window.change_app_mode("normal")
        switch_tab(self.body_box, self.info_box)
        
    def try_to_quit(self, widget):
        self.hide_all()
        main_window = get_main_window()
        main_window.quit()
        
    def create_lyrics_button(self):    
        toggle_button = ToggleButton(
            app_theme.get_pixbuf("mini/lyrics_inactive_normal.png"),
            app_theme.get_pixbuf("mini/lyrics_active_normal.png"),
            app_theme.get_pixbuf("mini/lyrics_inactive_hover.png"),
            app_theme.get_pixbuf("mini/lyrics_active_hover.png"),
            app_theme.get_pixbuf("mini/lyrics_inactive_press.png"),
            app_theme.get_pixbuf("mini/lyrics_active_press.png"),
            ) 
        
        toggle_button.connect("toggled", self.change_lyrics_status)
        return toggle_button
    
    def change_lyrics_status(self, widget):    
        if self.signal_auto:
            if widget.get_active():
                Dispatcher.show_lyrics()
            else:    
                Dispatcher.close_lyrics()
    
    def create_button(self, name, tip_msg=None):
        button = ImageButton(
            app_theme.get_pixbuf("mini/%s_normal.png" % name),
            app_theme.get_pixbuf("mini/%s_hover.png" % name),
            app_theme.get_pixbuf("mini/%s_press.png" % name),
            )
        
        button.connect("clicked", self.player_control, name)

        if tip_msg:
            Tooltip.text(button, tip_msg)
        return button
    
    def create_logo_box(self):
        return ImageBox(app_theme.get_pixbuf("mini/logo.png"))
    
    def player_control(self, button, name):
        if name == "next":
            getattr(Player, name)(True)
        else:    
            getattr(Player, name)()
            
    def is_in_window(self):        
        root_window = gtk.gdk.get_default_root_window()
        r_x, r_y = root_window.get_pointer()[:2]
        o_x, o_y = self.get_position()
        rect = self.allocation
        rect.x = o_x + 1
        rect.y = o_y + 1
        rect.width -= 2
        rect.height -= 2
        return is_in_rect((r_x, r_y), rect)
    
    def adjust_move_coordinate(self, widget, x, y):
        x = max(x, 0)
        y = max(y, 0)
        screen = widget.get_screen()
        w, h = widget.get_size()
        screen_w, screen_h = screen.get_width(), screen.get_height()
        
        if x + w > screen_w:
            x = screen_w - w
           
        if y + h > screen_h:    
            y = screen_h - h
        return (int(x), int(y))
    
    
    def create_playpause_button(self):
        play_normal_pixbuf = app_theme.get_pixbuf("mini/play_normal.png")
        pause_normal_pixbuf = app_theme.get_pixbuf("mini/pause_normal.png")
        play_hover_pixbuf = app_theme.get_pixbuf("mini/play_hover.png")
        pause_hover_pixbuf = app_theme.get_pixbuf("mini/pause_hover.png")
        play_press_pixbuf = app_theme.get_pixbuf("mini/play_press.png")
        pause_press_pixbuf = app_theme.get_pixbuf("mini/pause_press.png")
        playpause_button = ToggleButton(play_normal_pixbuf, pause_normal_pixbuf,
                     play_hover_pixbuf, pause_hover_pixbuf,
                     play_press_pixbuf, pause_press_pixbuf)
        return playpause_button    
    
    def on_player_playpause(self, widget):    
        if Player.song:
            Player.playpause()
            
    def __swap_play_status(self, obj, active):    
        self.playpause_button.handler_block(self.__id_signal_play)
        self.playpause_button.set_active(active)
        self.playpause_button.handler_unblock(self.__id_signal_play)
            
    def on_configure_event(self, widget, event):        
        if widget.get_property("visible"):
            config.set("mini","x","%d" % event.x)
            config.set("mini","y","%d" % event.y)
            
    def on_enter_notify_event(self, widget, event):        
        if self.drag_move: return
        
        childs = self.body_box.get_children()
        if len(childs) > 0:
            child = childs[0]
            if child != self.control_box:
                self.draw_animation = True
                container_remove_all(self.body_box)
                self.active_draw_func = self.draw_info
                self.target_draw_func = self.draw_control
                self.start_animation(self.control_box)
        else:
            self.draw_animation = True
            container_remove_all(self.body_box)
            self.active_draw_func = self.draw_info
            self.target_draw_func = self.draw_control
            self.start_animation(self.control_box)
        
    def on_leave_notify_event(self, widget, event):    
        if self.drag_move: return
        
        if not self.is_in_window():
            self.draw_animation = True
            container_remove_all(self.body_box)
            self.active_draw_func = self.draw_control
            self.target_draw_func = self.draw_info
            self.start_animation(self.info_box)
            
    def on_button_press_event(self, widget, event):        
        if event.button == 1:
            self.old_x, self.old_y = widget.get_position()
            self.mouse_x, self.mouse_y = event.x_root, event.y_root
            self.drag_move = True
            
    def on_motion_notify_event(self, widget, event):        
        if self.drag_move:
            x = int(self.old_x + (event.x_root - self.mouse_x))
            y = int(self.old_y + (event.y_root - self.mouse_y))
            widget.move(*self.adjust_move_coordinate(widget, x, y))
            
    def on_button_release_event(self, widget, event):        
        self.drag_move = False
            
    def toggle_visible(self, bring_to_front=False):        
        if self.get_property("visible"):
            if self.is_active():
                if not bring_to_front:
                    self.hide_all()
            else:    
                self.present()
        else:        
            self.show_from_tray()
            
    def toggle_window(self):        
        if self.get_property("visible"):
            self.hide_to_tray()
        else:    
            self.show_from_tray()

    def show_from_tray(self):        
        if config.get("mini", "x") != "-1":
            self.move(int(config.get("mini", "x")), int(config.get("mini", "y")))
        self.show_all()    
        
    def hide_to_tray(self):    
        self.hide_all()
        
    def get_widget_pixbuf(self, widget=None):    
        if widget is None:
            widget = self
        drawable = widget.window
        x, y, width, height = widget.allocation
        pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, width, height)
        pixbuf.get_from_drawable(drawable, drawable.get_colormap(), x, y, 0, 0, width, height)
        return pixbuf
    
    def shape_mini_frame(self, widget, event):    
        pass
    
    def draw_info(self, cr, allocation, alpha):
        rect = gtk.gdk.Rectangle(allocation.x, allocation.y, allocation.width, allocation.height)
        cr.push_group()
        
        # Draw logo.
        rect.x += self.logo_padding_left
        logo_pixbuf = app_theme.get_pixbuf("mini/logo.png").get_pixbuf()
        icon_y = rect.y + (rect.height - logo_pixbuf.get_height()) / 2
        draw_pixbuf(cr, logo_pixbuf, rect.x, icon_y)
        
        # Draw playinfo.
        left_width = logo_pixbuf.get_width() + self.logo_padding_right
        rect.x += left_width
        rect.width -= left_width * 2
        self.playinfo.draw_content(cr, rect)
        
        # set source to paint with alpha.
        cr.pop_group_to_source()
        cr.paint_with_alpha(alpha)
    
    def draw_control(self, cr, allocation, alpha):
        rect = gtk.gdk.Rectangle(allocation.x, allocation.y, allocation.width, allocation.height)
        cr.push_group()
        
        # Draw lyrics button
        rect.x += self.lyrics_padding_left
        enable_lyrics = config.getboolean('lyrics', 'status')
        if enable_lyrics:
            lyrics_pixbuf = app_theme.get_pixbuf('mini/lyrics_active_normal.png').get_pixbuf()
        else:
            lyrics_pixbuf = app_theme.get_pixbuf('mini/lyrics_inactive_normal.png').get_pixbuf()
        icon_y = rect.y + (rect.height - lyrics_pixbuf.get_height()) / 2
        draw_pixbuf(cr, lyrics_pixbuf, rect.x, icon_y)
        
        # Draw previous button.
        rect.x += lyrics_pixbuf.get_width() + self.lyrics_padding_right
        previous_pixbuf = app_theme.get_pixbuf('mini/previous_normal.png').get_pixbuf()
        icon_y = rect.y + (rect.height - previous_pixbuf.get_height()) / 2
        draw_pixbuf(cr, previous_pixbuf, rect.x, icon_y)
        
        # Draw playpause button.
        rect.x += previous_pixbuf.get_width()
        is_played = config.getboolean('player', 'play')
        if is_played:
            playpause_pixbuf = app_theme.get_pixbuf('mini/pause_normal.png').get_pixbuf()
        else:
            playpause_pixbuf = app_theme.get_pixbuf('mini/play_normal.png').get_pixbuf()
        icon_y = rect.y + (rect.height - playpause_pixbuf.get_height()) / 2
        draw_pixbuf(cr, playpause_pixbuf, rect.x, icon_y)
        
        # Draw next button.
        rect.x += playpause_pixbuf.get_width()
        next_pixbuf = app_theme.get_pixbuf('mini/next_normal.png').get_pixbuf()
        icon_y = rect.y + (rect.height - next_pixbuf.get_height()) / 2
        draw_pixbuf(cr, next_pixbuf, rect.x, icon_y)
        
        # Draw volume button.
        (v_w, v_h,) = self.volume_slider.volume_button.get_size()
        v_y = rect.y + (rect.height - v_h) / 2
        rect.x += self.volume_padding_left + next_pixbuf.get_width()
        volume_button_rect = gtk.gdk.Rectangle(rect.x, v_y, v_w, v_h)
        self.volume_slider.volume_button.draw_volume(cr, volume_button_rect)
        
        # Draw event box, draw close button.
        close_pixbuf = ui_theme.get_pixbuf('button/window_close_normal.png').get_pixbuf()
        event_box_x = rect.width - close_pixbuf.get_width() + 4
        draw_pixbuf(cr, close_pixbuf, event_box_x, rect.y)
        
        # Draw min button.
        min_pixbuf = ui_theme.get_pixbuf('button/window_min_normal.png').get_pixbuf()
        event_box_x -= min_pixbuf.get_width()
        draw_pixbuf(cr, min_pixbuf, event_box_x, rect.y)
        
        # draw quell button.
        quell_pixbuf = app_theme.get_pixbuf('mode/quell_normal.png').get_pixbuf()
        event_box_x -= quell_pixbuf.get_width()
        draw_pixbuf(cr, quell_pixbuf, event_box_x, rect.y)
        
        cr.pop_group_to_source()
        cr.paint_with_alpha(alpha)
        
    def expose_mini_frame(self, widget, event):
        cr  = widget.window.cairo_create()
        rect = widget.allocation
        cr.set_source_rgba(1,1,1, 0.8)
        cr.rectangle(rect.x, rect.y, rect.width, rect.height)
        cr.fill()
        
        if self.draw_animation:
            if self.active_draw_func:
                self.active_draw_func(cr, rect, self.active_alpha)
            if self.target_draw_func:    
                self.target_draw_func(cr, rect, self.target_alpha)
        
    def start_animation(self, widget):    
        if not self.in_animation:
            self.in_animation = False
            try:
                self.timeline.stop()
            except:    
                pass
            self.timeline = Timeline(self.animation_time, CURVE_SINE)
            self.timeline.connect("update", self.update_animation)
            self.timeline.connect("completed", lambda source: self.completed_animation(source, widget))
            self.timeline.run()
    
    def update_animation(self, source, status):
        self.active_alpha = 1.0 - status
        self.target_alpha = status
        self.queue_draw()
    
    def completed_animation(self, source, widget):    
        self.draw_animation = False        
        self.active_alpha = 1.0
        self.target_alpha = 0.0
        self.in_animation = False
        self.queue_draw()
        switch_tab(self.body_box, widget)
        
    def sync_volume(self):    
        self.volume_slider.load_volume_config()
예제 #20
0
class CoverButton(gtk.Button):
    def __init__(self):
        super(CoverButton, self).__init__()

        self.cover_side_pixbuf = app_theme.get_pixbuf(
            "cover/side.png").get_pixbuf()
        self.set_size_request(self.cover_side_pixbuf.get_width(),
                              self.cover_side_pixbuf.get_height())
        self.webcast_dpixbuf = app_theme.get_pixbuf("cover/webcast.png")
        self.local_dpixbuf = app_theme.get_pixbuf("cover/default_cover.png")

        self.connect("expose-event", self.expose_button_cb)
        Dispatcher.connect("album-changed", self.on_album_changed)

        self.current_song = None
        self.next_cover_to_download = None

        self.condition = threading.Condition()
        self.thread = threading.Thread(target=self.func_thread)
        self.thread.setDaemon(True)
        self.thread.start()

        # animation params.
        self.active_alpha = 1.0
        self.target_alpha = 0.0
        self.in_animation = False
        self.animation_time = 1500
        self.active_pixbuf = self.get_default_pixbuf()
        self.target_pixbuf = None
        self.is_default = True

    def start_animation(self, target_pixbuf, is_default=False):
        self.is_default = False
        self.target_pixbuf = target_pixbuf
        if not self.in_animation:
            self.in_animation = False
            try:
                self.timeline.stop()
            except:
                pass
            self.timeline = Timeline(self.animation_time, CURVE_SINE)
            self.timeline.connect("update", self.update_animation)
            self.timeline.connect(
                "completed", lambda source: self.completed_animation(
                    source, target_pixbuf, is_default))
            self.timeline.run()

    def update_animation(self, source, status):
        self.active_alpha = 1.0 - status
        self.target_alpha = status
        self.queue_draw()

    def completed_animation(self, source, target_pixbuf, is_default):
        self.active_pixbuf = target_pixbuf
        self.active_alpha = 1.0
        self.target_alpha = 0.0
        self.in_animation = False
        self.is_default = is_default
        self.queue_draw()

    def expose_button_cb(self, widget, event):
        cr = widget.window.cairo_create()
        rect = widget.allocation

        # Draw cover side.
        if self.is_default:
            self.active_pixbuf = self.get_default_pixbuf(self.current_song)

        draw_pixbuf(cr, self.cover_side_pixbuf, rect.x, rect.y)
        draw_pixbuf(cr, self.active_pixbuf, rect.x + 3, rect.y + 3,
                    self.active_alpha)

        if self.target_pixbuf:
            draw_pixbuf(cr, self.target_pixbuf, rect.x + 3, rect.y + 3,
                        self.target_alpha)

        return True

    def func_thread(self):
        while True:
            self.condition.acquire()
            while not self.next_cover_to_download:
                self.condition.wait()
            next_cover_to_download = self.next_cover_to_download
            self.next_cover_to_download = None
            self.condition.release()
            self.set_current_cover(True, next_cover_to_download)

    def update_default_cover(self, song):
        self.start_animation(self.get_default_pixbuf(song), is_default=True)

    def get_default_pixbuf(self, song=None):
        if not song:
            song = Player.song

        if not song:
            pixbuf = self.local_dpixbuf.get_pixbuf()
            return utils.get_optimum_pixbuf(pixbuf, COVER_SIZE["x"],
                                            COVER_SIZE["y"])

        if song.get_type() == "webcast":
            pixbuf = self.webcast_dpixbuf.get_pixbuf()
        else:
            pixbuf = self.local_dpixbuf.get_pixbuf()
        return utils.get_optimum_pixbuf(pixbuf, COVER_SIZE["x"],
                                        COVER_SIZE["y"])

    def init_default_cover(self):
        pixbuf = self.get_default_pixbuf()
        self.start_animation(pixbuf, is_default=True)

    def on_album_changed(self, obj, song):
        if song == self.current_song:
            self.update_cover(None, song)

    def update_cover(self, widget, song):
        self.current_song = song

        if self.current_song is not None:
            if not self.set_current_cover(False):
                self.condition.acquire()
                self.next_cover_to_download = self.current_song
                self.condition.notify()
                self.condition.release()

    def set_current_cover(self, try_web=True, force_song=None):
        if not force_song:
            force_song = self.current_song
        filename = CoverManager.get_cover(force_song, try_web)
        if Player.song != force_song:
            return False

        if not filename:
            if not try_web:
                if force_song.get_type() == "webcast":
                    pixbuf = utils.get_optimum_pixbuf(
                        self.webcast_dpixbuf.get_pixbuf(), COVER_SIZE["x"],
                        COVER_SIZE["y"])
                    self.start_animation(pixbuf, is_default=True)
                    return True
                else:
                    pixbuf = utils.get_optimum_pixbuf(
                        self.local_dpixbuf.get_pixbuf(), COVER_SIZE["x"],
                        COVER_SIZE["y"])
                    self.start_animation(pixbuf, is_default=True)
                    return False
            return False
        else:
            try:
                pixbuf = get_optimum_pixbuf_from_file(filename,
                                                      COVER_SIZE["x"],
                                                      COVER_SIZE["y"])
            except gobject.GError:
                return False
            else:
                self.start_animation(pixbuf, is_default=False)
                return True
예제 #21
0
class SongNotify(gtk.Window):    
    
    def __init__(self, song=None):
        gtk.Window.__init__(self, 
                        # shadow_visible=False,
                        # shadow_radius=0,
                        # shape_frame_function=self.shape_panel_frame,
                        # expose_frame_function=self.expose_panel_frame,
                        # window_type=gtk.WINDOW_POPUP,
                        )
        
        self.set_can_focus(False)
        self.set_accept_focus(False)
        self.set_keep_above(True)        
        self.set_skip_taskbar_hint(True)
        self.set_skip_pager_hint(True)        
        self.set_decorated(False)
        self.set_app_paintable(True)
        self.set_colormap(gtk.gdk.Screen().get_rgba_colormap() or gtk.gdk.Screen().get_rga_colormap())
        
        self.connect("expose-event", self.on_panel_expose_event)
        # self.connect("size-allocate", self.on_size_allocate)
        self.song = song
        self.title_text_size = 9
        self.other_text_size = 8
        self.text_padding_y = 5
        self.other_text_padding_y = 2
        self.text_padding_x = 5
        self.cover_padding_x = 7
        self.cover_up_offset = 12
        self.cover_bottom_offset = 10
        self.other_up_offset = 8
        self.line_height = 15
        self.cover_width = self.cover_height = BROWSER_COVER_SIZE["x"]
        self.default_width = 222
        self.default_height = 118
        self.set_size_request(self.default_width, self.default_height)
        self.other_text_color = app_theme.get_color("labelText").get_color()
        
        # animation params.
        self.width_percent = 1.0
        self.in_animation = False
        self.animation_time = 500
        self.animation_timeout_id = None
        self.last_x = None
        self.last_y = None
        self.move_y = None

        
    def shape_panel_frame(self, widget, event):    
        pass
        
    def expose_panel_frame(self, widget, event):
        pass
    
    def on_size_allocate(self, widget, rect):
        x, y, w, h = rect.x, rect.y, rect.width, rect.height
        bitmap = gtk.gdk.Pixmap(None, w, h, 1)
        cr = bitmap.cairo_create()

        cr.set_source_rgb(0.0, 0.0, 0.0)
        cr.set_operator(cairo.OPERATOR_CLEAR)
        cr.paint()
        
        cr.set_operator(cairo.OPERATOR_OVER)
        with cairo_disable_antialias(cr):
            draw_round_rectangle(cr, rect.x, rect.y, rect.width, rect.height, 4)
            cr.fill()
        widget.shape_combine_mask(bitmap, 0, 0)        
        
    def on_panel_expose_event(self, widget, event):    
        cr  = widget.window.cairo_create()
        rect = widget.allocation

        
        # Clear color to transparent window.
        if self.is_composited():
            cr.rectangle(*rect)
            cr.set_source_rgba(1, 1, 1, 0.95)
            cr.set_operator(cairo.OPERATOR_SOURCE)
            cr.paint()

        else:    
            cr.rectangle(rect.x, rect.y, rect.width, rect.height)            
            cr.set_operator(cairo.OPERATOR_SOURCE)
            cr.set_source_rgb(0.9, 0.9, 0.9)
            cr.fill()
         
        cr  = widget.window.cairo_create()
        rect = widget.allocation
        
        with cairo_disable_antialias(cr):    
            cr.rectangle(rect.x + 1, rect.y + 1, rect.width - 1, rect.height - 1)
            cr.set_line_width(1)
            cr.set_source_rgb(*color_hex_to_cairo("#D6D6D6"))
            cr.stroke()
            
        if not self.song:
            return True
        
        dash_y = rect.y + self.cover_height + self.cover_up_offset + self.cover_bottom_offset + 1
        dash_x = rect.x
        dash_width = rect.width
                
        cover_pixbuf = self.get_song_cover()
        
        rect.x += self.cover_padding_x
        rect.width -= self.cover_padding_x * 2
        
        # rect.y += (rect.height - self.cover_height) / 2
        rect.y += self.cover_up_offset
        draw_pixbuf(cr, cover_pixbuf, rect.x, rect.y)
        
        rect.x += self.cover_width + self.text_padding_x
        rect.width -= (self.cover_width + self.text_padding_x)
        
        title = utils.xmlescape(self.song.get_str("title"))
        artist = utils.xmlescape(self.song.get_str("artist"))        
        album = utils.xmlescape(self.song.get_str("album"))
        
        _width, _height = get_content_size(title, text_size=self.title_text_size)                
        if not artist and not album:
            _height = self.cover_height
        draw_text(cr, title, rect.x, rect.y, rect.width, _height, 
                  text_color=self.other_text_color, 
                  text_size=self.title_text_size)        
        
        if artist:
            rect.y += _height + self.text_padding_y
            _width, _height = get_content_size(artist, text_size=self.other_text_size)
            draw_text(cr, artist, rect.x, rect.y, rect.width, _height,
                      text_size=self.other_text_size,
                      text_color=self.other_text_color)
            
        if album:    
            rect.y += _height + self.text_padding_y
            _width, _height = get_content_size(artist, text_size=self.other_text_size)
            draw_text(cr, album, rect.x, rect.y, rect.width, _height,
                      text_size=self.other_text_size,
                      text_color=self.other_text_color)
            
        # draw dash    
        with cairo_disable_antialias(cr):    
            cr.set_source_rgb(*color_hex_to_cairo("#D6D6D6"))
            cr.set_line_width(1)            
            cr.move_to(dash_x, dash_y)
            cr.rel_line_to(dash_width, 0)
            # cr.set_dash([2.0, 2.0])
            cr.stroke()
            
            
        other_width = (dash_width - self.cover_padding_x * 2) / 2 - self.cover_padding_x / 2
        other_left_x = dash_x + self.cover_padding_x
        other_right_x = dash_x + dash_width / 2 + self.cover_padding_x / 2
        
        # draw size and format    
        other_y = dash_y + self.other_up_offset
        song_size = "%s: %s" % (_("Size"), utils.xmlescape(self.song.get_str("#size")))
        song_format = "%s: %s" % (_("Type"), utils.get_song_attr(self.song))
        _width, _height = get_content_size(song_size, text_size=self.other_text_size)
        draw_text(cr, song_size, other_left_x, other_y, other_width, _height, 
                  text_size=self.other_text_size,
                  text_color=self.other_text_color
                  )
        draw_text(cr, song_format, other_right_x, other_y, other_width, _height,
                  text_size=self.other_text_size,
                  text_color=self.other_text_color
                  )
        
        # draw playcount and duration.
        other_y += _height + self.other_text_padding_y
        playcount = self.song.get_str("#playcount")
        if playcount == "Never" or not playcount: playcount = "0"
        song_playcount = "%s: %s" % (_("Playcount"), playcount)
        duration = "%s: %s" % (_("Duration"), self.song.get_str("#duration"))
        
        draw_text(cr, song_playcount, other_left_x, other_y, other_width, _height, 
                  text_size=self.other_text_size,
                  text_color=self.other_text_color
                  )
        draw_text(cr, duration, other_right_x, other_y, other_width, _height,
                  text_size=self.other_text_size,
                  text_color=self.other_text_color
                  )
        return True
    
    def get_song_cover(self):
        return CoverManager.get_pixbuf_from_song(self.song, 
                                                BROWSER_COVER_SIZE["x"], 
                                                BROWSER_COVER_SIZE["y"],
                                                try_web=False,
                                                optimum=True)
    def show(self, x, y):
        self.show_all()
        if self.last_x is None or self.last_x != x:
            self.last_x = x
            self.last_y = y
            self.move(x, y)
        else:    
            self.start_animation(x, y)
        
    def start_animation(self, x, y):    
        if not self.in_animation:
            self.in_animation = False
            try:
                self.timeline.stop()
            except:    
                pass
            
            self.timeline = Timeline(self.animation_time, CURVE_SINE)
            self.timeline.connect("stop", self.stop_animation)
            self.timeline.connect("update", self.update_animation, x, y)
            self.timeline.connect("completed", self.completed_animation, x, y)
            self.timeline.run()
            
    def stop_animation(self, source):        
        if self.move_y is not None:
            self.last_y = self.move_y
            
    def update_animation(self, source, status, x, y):        
        height = y - self.last_y
        new_height = status * height
        self.move_y = int(self.last_y + new_height) 
        self.move(x, self.move_y)
    
    def completed_animation(self, source, x, y):    
        self.last_x = x
        self.last_y = y
        
    def hide_notify(self):    
        self.last_x = None
        self.hide_all()
        
    def update_song(self, song):    
        self.song = song
        self.queue_draw()