PiecesBar caching.
The drawings made on the pieces bar are now cached in "sub-drawings" kept in memory. If no data has changed, those "sub-drawings" are used. If data changed, redraw whatever is necessary.
This commit is contained in:
parent
6d57a29f1d
commit
25f086fa85
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue