diff --git a/deluge/ui/gtkui/piecesbar.py b/deluge/ui/gtkui/piecesbar.py index 180744c85..4d45829b6 100644 --- a/deluge/ui/gtkui/piecesbar.py +++ b/deluge/ui/gtkui/piecesbar.py @@ -60,187 +60,237 @@ class PiecesBar(gtk.DrawingArea): # Get progress bar styles, in order to keep font consistency pb = gtk.ProgressBar() pb_style = pb.get_style() - self.text_font = pb_style.font_desc - self.text_font.set_weight(pango.WEIGHT_BOLD) + self.__text_font = pb_style.font_desc + self.__text_font.set_weight(pango.WEIGHT_BOLD) # Done with the ProgressBar styles, don't keep refs of it del pb, pb_style self.set_size_request(-1, 25) self.gtkui_config = ConfigManager("gtkui.conf") - self.width = 0 - self.height = 0 - self.pieces = () - self.num_pieces = None - self.text = "" - self.fraction = 0.0 - self.torrent_state = None + self.__width = self.__old_width = 0 + self.__height = self.__old_height = 0 + self.__pieces = self.__old_pieces = () + self.__num_pieces = self.__old_num_pieces = None + self.__text = self.__old_text = "" + self.__fraction = self.__old_fraction = 0.0 + self.__state = self.__old_state = None + self.__progress_overlay = self.__text_overlay = self.__pieces_overlay = None + self.__cr = None + self.connect('size-allocate', self.do_size_allocate_event) self.set_colormap(self.get_screen().get_rgba_colormap()) self.show() def do_size_allocate_event(self, widget, size): - self.width = size.width - self.height = size.height + self.__old_width = self.__width + self.__width = size.width + self.__old_height = self.__height + self.__height = size.height # Handle the expose-event by drawing def do_expose_event(self, event): - self.draw_pieces(event) - - def draw_pieces(self, event): - # Create the cairo context - cr = self.window.cairo_create() + # Create cairo context + self.__cr = self.window.cairo_create() # Restrict Cairo to the exposed area; avoid extra work - cr.rectangle(event.area.x, event.area.y, - event.area.width, event.area.height) - cr.clip() - self.__roundcorners_clipping(cr, event) + self.__roundcorners_clipping() - if not self.pieces and self.num_pieces is not None: - # Complete Torrent - piece_width = self.width*1.0/self.num_pieces - start = 0 - for _ in range(self.num_pieces): - # Like this to keep same aspect ratio - color = self.gtkui_config["pieces_color_%s" % COLOR_STATES[3]] - cr.set_source_rgb( - color[0]/65535.0, - color[1]/65535.0, - color[2]/65535.0, - ) - cr.rectangle(start, 0, piece_width, self.height) - cr.fill() - start += piece_width - self.__write_text(cr, event) - return + if not self.__pieces and self.__num_pieces is not None: + # Special case. Completed torrents do not send any pieces in their + # status. + self.__draw_pieces_completed() + elif self.__pieces: + self.__draw_pieces() - if not self.pieces: - self.__write_text(cr, event) - return + self.__draw_progress_overlay() + self.__write_text() - start_pos = 0 - num_pieces = self.num_pieces and self.num_pieces or len(self.pieces) - piece_width = self.width*1.0/num_pieces + # Drawn once, update width, eight + if self.__resized(): + self.__old_width = self.__width + self.__old_height = self.__height - for state in self.pieces: - color = self.gtkui_config["pieces_color_%s" % COLOR_STATES[state]] - cr.set_source_rgb( - color[0]/65535.0, - color[1]/65535.0, - color[2]/65535.0, - ) - cr.rectangle(start_pos, 0, piece_width, self.height) - cr.fill() - start_pos += piece_width - self.__write_text(cr, event) - - def __roundcorners_clipping(self, cr, event): + def __roundcorners_clipping(self): from math import pi x = 0 y = 0 - width = event.area.width - height = event.area.height + width = self.__width + height = self.__height aspect = 1.0 corner_radius = height/10.0 radius = corner_radius/aspect degrees = pi/180.0 + self.__cr.new_sub_path() + self.__cr.arc(x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees) + self.__cr.arc(x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees) + self.__cr.arc(x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees) + self.__cr.arc(x + radius, y + radius, radius, 180 * degrees, 270 * degrees) + self.__cr.close_path() + self.__cr.clip() - cr.new_sub_path() - cr.arc(x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees) - cr.arc(x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees) - cr.arc(x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees) - cr.arc(x + radius, y + radius, radius, 180 * degrees, 270 * degrees) - cr.close_path() - cr.clip() + def __draw_pieces(self): + if (self.__resized() or self.__pieces != self.__old_pieces or + self.__pieces_overlay == None): + # Need to recreate the cache drawing + self.__pieces_overlay = cairo.ImageSurface( + cairo.FORMAT_ARGB32, self.__width, self.__height + ) + ctx = cairo.Context(self.__pieces_overlay) + start_pos = 0 + num_pieces = self.__num_pieces and self.__num_pieces or len(self.__pieces) + piece_width = self.__width*1.0/num_pieces - def __draw_progress_overlay(self, cr): - cr.set_source_rgba(0.1, 0.1, 0.1, 0.3) # Transparent - cr.rectangle(0.0, 0.0, self.width*self.fraction, self.height) - cr.fill() + for state in self.__pieces: + color = self.gtkui_config["pieces_color_%s" % COLOR_STATES[state]] + ctx.set_source_rgb( + color[0]/65535.0, + color[1]/65535.0, + color[2]/65535.0, + ) + ctx.rectangle(start_pos, 0, piece_width, self.__height) + ctx.fill() + start_pos += piece_width - def __write_text(self, cr, event): - if not self.torrent_state: + self.__cr.set_source_surface(self.__pieces_overlay) + self.__cr.paint() + + def __draw_pieces_completed(self): + if (self.__resized() or self.__pieces != self.__old_pieces or + self.__pieces_overlay is None): + # Need to recreate the cache drawing + self.__pieces_overlay = cairo.ImageSurface( + cairo.FORMAT_ARGB32, self.__width, self.__height + ) + ctx = cairo.Context(self.__pieces_overlay) + piece_width = self.__width*1.0/self.__num_pieces + start = 0 + for _ in range(self.__num_pieces): + # Like this to keep same aspect ratio + color = self.gtkui_config["pieces_color_%s" % COLOR_STATES[3]] + ctx.set_source_rgb( + color[0]/65535.0, + color[1]/65535.0, + color[2]/65535.0, + ) + ctx.rectangle(start, 0, piece_width, self.__height) + ctx.fill() + start += piece_width + + self.__cr.set_source_surface(self.__pieces_overlay) + self.__cr.paint() + + def __draw_progress_overlay(self): + if not self.__state: # Nothing useful to draw, return now! return - self.__draw_progress_overlay(cr) + if (self.__resized() or self.__fraction != self.__old_fraction) or \ + self.__progress_overlay is None: + # Need to recreate the cache drawing + self.__progress_overlay = cairo.ImageSurface( + cairo.FORMAT_ARGB32, self.__width, self.__height + ) + ctx = cairo.Context(self.__progress_overlay) + ctx.set_source_rgba(0.1, 0.1, 0.1, 0.3) # Transparent + ctx.rectangle(0.0, 0.0, self.__width*self.__fraction, self.__height) + ctx.fill() + self.__cr.set_source_surface(self.__progress_overlay) + self.__cr.paint() - pg = pangocairo.CairoContext(cr) - pl = pg.create_layout() - pl.set_font_description(self.text_font) - pl.set_width(-1) # No text wrapping + def __write_text(self): + if not self.__state: + # Nothing useful to draw, return now! + return + if (self.__resized() or self.__text != self.__old_text or + self.__fraction != self.__old_fraction or + self.__state != self.__old_state or + self.__text_overlay is None): + # Need to recreate the cache drawing + self.__text_overlay = cairo.ImageSurface( + cairo.FORMAT_ARGB32, self.__width, self.__height + ) + ctx = cairo.Context(self.__text_overlay) + pg = pangocairo.CairoContext(ctx) + pl = pg.create_layout() + pl.set_font_description(self.__text_font) + pl.set_width(-1) # No text wrapping - text = "" - if self.text: - text += self.text - else: - if self.torrent_state: - text += self.torrent_state + " " - if self.fraction == 1.0: - format = "%d%%" + text = "" + if self.__text: + text += self.__text else: - format = "%.2f%%" - text += format % (self.fraction*100) - log.debug("PiecesBar text %r", text) - pl.set_text(text) - plsize = pl.get_size() - text_width = plsize[0]/pango.SCALE - text_height = plsize[1]/pango.SCALE - area_width_without_text = event.area.width - text_width - area_height_without_text = event.area.height - text_height - cr.move_to(area_width_without_text/2, area_height_without_text/2) - cr.set_source_rgb(1.0, 1.0, 1.0) - pg.update_layout(pl) - pg.show_layout(pl) + if self.__state: + text += self.__state + " " + if self.__fraction == 1.0: + format = "%d%%" + else: + format = "%.2f%%" + text += format % (self.__fraction*100) + log.debug("PiecesBar text %r", text) + pl.set_text(text) + plsize = pl.get_size() + text_width = plsize[0]/pango.SCALE + text_height = plsize[1]/pango.SCALE + area_width_without_text = self.__width - text_width + area_height_without_text = self.__height - text_height + ctx.move_to(area_width_without_text/2, area_height_without_text/2) + ctx.set_source_rgb(1.0, 1.0, 1.0) + pg.update_layout(pl) + pg.show_layout(pl) + self.__cr.set_source_surface(self.__text_overlay) + self.__cr.paint() + def __resized(self): + return (self.__old_width != self.__width or + self.__old_height != self.__height) def set_fraction(self, fraction): - self.fraction = fraction - self.update() + self.__old_fraction = self.__fraction + self.__fraction = fraction + + def get_fraction(self): + return self.__fraction + + def get_text(self): + return self.__text + + def set_text(self, text): + self.__old_text = self.__text + self.__text = text def set_pieces(self, pieces, num_pieces): - if pieces != self.pieces: - self.pieces = pieces - self.num_pieces = num_pieces - self.update() + self.__old_pieces = self.__pieces + self.__pieces = pieces + self.__num_pieces = num_pieces + + def get_pieces(self): + return self.__pieces + + def set_state(self, state): + self.__old_state = self.__state + self.__state = state + + def get_state(self): + return self.__state def update_from_status(self, status): - update = False - - fraction = status["progress"]/100 - if fraction != self.fraction: - self.fraction = fraction - update = True - + log.debug("Updating PiecesBar from status") + self.set_fraction(status["progress"]/100) torrent_state = status["state"] - if torrent_state != self.torrent_state: - self.torrent_state = torrent_state - update = True - + self.set_state(torrent_state) if torrent_state == "Checking": self.update() # Skip the pieces assignment return - if status['pieces'] != self.pieces: - self.pieces = status['pieces'] - self.num_pieces = status['num_pieces'] - update = True - - if update: - self.update() + self.set_pieces(status['pieces'], status['num_pieces']) + self.update() def clear(self): - self.pieces = [] - self.num_pieces = None - self.fraction = 0.0 + self.__pieces = self.__old_pieces = () + self.__num_pieces = self.__old_num_pieces = None + self.__text = self.__oldtext = "" + self.__fraction = self.__old_fraction = 0.0 self.update() def update(self): self.queue_draw() - - def get_text(self): - return self.text - - def set_text(self, text): - self.text = text - self.update()