deluge/deluge/ui/gtkui/piecesbar.py
Calum Lind 807fa609f9 [Lint] Cleanup code to pass PyLint Warning category
Selected Warning messages disabled in pylintrc:
  * unused-argument: Quite a large and disruptive change if enabled.
  * broad-except: Most required in-depth investigation to determine type.
  * fixme: Not important
  * protected-access: Complicated to fix
  * import-error: Too many false-positives
  * unidiomatic-typecheck: Should be fixed in the next round of checks.
  * unused-variable: Again large and disruptive changes.
  * global-statement: Most usage is required.
  * attribute-defined-outside-init: Should be fixed in next round of checks.
  * arguments-differ: Possible false-positives, needs revisited.
  * no-init, non-parent-init-called, super-init-not-called: False-positives?
  * signature-differs: False-positives?
2015-10-30 18:39:47 +00:00

286 lines
10 KiB
Python

# -*- coding: utf-8 -*-
#
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
import logging
from math import pi
import cairo
import gtk
import pango
import pangocairo
from deluge.configmanager import ConfigManager
log = logging.getLogger(__name__)
COLOR_STATES = {
0: "missing",
1: "waiting",
2: "downloading",
3: "completed"
}
class PiecesBar(gtk.DrawingArea):
# Draw in response to an expose-event
__gsignals__ = {"expose-event": "override"}
def __init__(self):
gtk.DrawingArea.__init__(self)
# 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)
# 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 = 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(gtk.gdk.colormap_get_system())
self.show()
def do_size_allocate_event(self, widget, size):
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):
# Create cairo context
self.__cr = self.window.cairo_create()
self.__cr.set_line_width(max(self.__cr.device_to_user_distance(0.5, 0.5)))
# Restrict Cairo to the exposed area; avoid extra work
self.__roundcorners_clipping()
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()
self.__draw_progress_overlay()
self.__write_text()
self.__roundcorners_border()
# Drawn once, update width, eight
if self.__resized():
self.__old_width = self.__width
self.__old_height = self.__height
def __roundcorners_clipping(self):
self.__create_roundcorners_subpath(
self.__cr, 0, 0, self.__width, self.__height
)
self.__cr.clip()
def __roundcorners_border(self):
self.__create_roundcorners_subpath(
self.__cr, 0.5, 0.5, self.__width - 1, self.__height - 1
)
self.__cr.set_source_rgba(0.0, 0.0, 0.0, 0.9)
self.__cr.stroke()
def __create_roundcorners_subpath(self, ctx, x, y, width, height):
aspect = 1.0
corner_radius = height / 10.0
radius = corner_radius / aspect
degrees = pi / 180.0
ctx.new_sub_path()
ctx.arc(x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees)
ctx.arc(x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees)
ctx.arc(x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees)
ctx.arc(x + radius, y + radius, radius, 180 * degrees, 270 * degrees)
ctx.close_path()
return ctx
def __draw_pieces(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)
start_pos = 0
num_pieces = self.__num_pieces and self.__num_pieces or len(self.__pieces)
piece_width = self.__width * 1.0 / num_pieces
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
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
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()
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.__state:
text += _(self.__state) + " "
if self.__fraction == 1.0:
fraction_format = "%d%%"
else:
fraction_format = "%.2f%%"
text += fraction_format % (self.__fraction * 100)
log.trace("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.__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):
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):
log.trace("Updating PiecesBar from status")
self.set_fraction(status["progress"] / 100)
torrent_state = status["state"]
self.set_state(torrent_state)
if torrent_state == "Checking":
self.update()
# Skip the pieces assignment
return
self.set_pieces(status['pieces'], status['num_pieces'])
self.update()
def clear(self):
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.update()
def update(self):
self.queue_draw()