source: deluge/ui/gtkui/piecesbar.py@ 649638

2.0.x develop
Last change on this file since 649638 was 649638, checked in by Calum Lind <calumlind+deluge@gmail.com>, 11 years ago

[GTKUI] Reorganise layout of tab items and add Tracker tab

  • Changed layout of Status, Details and Options tabs.
  • Moved the Tracker translations to ui.common.
  • Created a new Trackers tab.
  • Added State to progressbar.
  • Translate State in piecesbar.
  • Property mode set to 100644
File size: 11.1 KB
Line 
1# -*- coding: utf-8 -*-
2#
3# piecesbar.py
4#
5# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
6#
7# Deluge is free software.
8#
9# You may redistribute it and/or modify it under the terms of the
10# GNU General Public License, as published by the Free Software
11# Foundation; either version 3 of the License, or (at your option)
12# any later version.
13#
14# deluge is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17# See the GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with deluge. If not, write to:
21# The Free Software Foundation, Inc.,
22# 51 Franklin Street, Fifth Floor
23# Boston, MA 02110-1301, USA.
24#
25# In addition, as a special exception, the copyright holders give
26# permission to link the code of portions of this program with the OpenSSL
27# library.
28# You must obey the GNU General Public License in all respects for all of
29# the code used other than OpenSSL. If you modify file(s) with this
30# exception, you may extend this exception to your version of the file(s),
31# but you are not obligated to do so. If you do not wish to do so, delete
32# this exception statement from your version. If you delete this exception
33# statement from all source files in the program, then also delete it here.
34#
35#
36
37import gtk
38import cairo
39import pango
40import pangocairo
41import logging
42from math import pi
43from deluge.configmanager import ConfigManager
44
45log = logging.getLogger(__name__)
46
47
48COLOR_STATES = {
49 0: "missing",
50 1: "waiting",
51 2: "downloading",
52 3: "completed"
53}
54
55
56class PiecesBar(gtk.DrawingArea):
57 # Draw in response to an expose-event
58 __gsignals__ = {"expose-event": "override"}
59
60 def __init__(self):
61 gtk.DrawingArea.__init__(self)
62 # Get progress bar styles, in order to keep font consistency
63 pb = gtk.ProgressBar()
64 pb_style = pb.get_style()
65 self.__text_font = pb_style.font_desc
66 self.__text_font.set_weight(pango.WEIGHT_BOLD)
67 # Done with the ProgressBar styles, don't keep refs of it
68 del pb, pb_style
69
70 self.set_size_request(-1, 25)
71 self.gtkui_config = ConfigManager("gtkui.conf")
72 self.__width = self.__old_width = 0
73 self.__height = self.__old_height = 0
74 self.__pieces = self.__old_pieces = ()
75 self.__num_pieces = self.__old_num_pieces = None
76 self.__text = self.__old_text = ""
77 self.__fraction = self.__old_fraction = 0.0
78 self.__state = self.__old_state = None
79 self.__progress_overlay = self.__text_overlay = self.__pieces_overlay = None
80 self.__cr = None
81
82 self.connect('size-allocate', self.do_size_allocate_event)
83 self.set_colormap(gtk.gdk.colormap_get_system())
84 self.show()
85
86 def do_size_allocate_event(self, widget, size):
87 self.__old_width = self.__width
88 self.__width = size.width
89 self.__old_height = self.__height
90 self.__height = size.height
91
92 # Handle the expose-event by drawing
93 def do_expose_event(self, event):
94 # Create cairo context
95 self.__cr = self.window.cairo_create()
96 self.__cr.set_line_width(max(self.__cr.device_to_user_distance(0.5, 0.5)))
97
98 # Restrict Cairo to the exposed area; avoid extra work
99 self.__roundcorners_clipping()
100
101 if not self.__pieces and self.__num_pieces is not None:
102 # Special case. Completed torrents do not send any pieces in their
103 # status.
104 self.__draw_pieces_completed()
105 elif self.__pieces:
106 self.__draw_pieces()
107
108 self.__draw_progress_overlay()
109 self.__write_text()
110 self.__roundcorners_border()
111
112 # Drawn once, update width, eight
113 if self.__resized():
114 self.__old_width = self.__width
115 self.__old_height = self.__height
116
117 def __roundcorners_clipping(self):
118 self.__create_roundcorners_subpath(
119 self.__cr, 0, 0, self.__width, self.__height
120 )
121 self.__cr.clip()
122
123 def __roundcorners_border(self):
124 self.__create_roundcorners_subpath(
125 self.__cr, 0.5, 0.5, self.__width-1, self.__height-1
126 )
127 self.__cr.set_source_rgba(0.0, 0.0, 0.0, 0.9)
128 self.__cr.stroke()
129
130 def __create_roundcorners_subpath(self, ctx, x, y, width, height):
131 aspect = 1.0
132 corner_radius = height/10.0
133 radius = corner_radius/aspect
134 degrees = pi/180.0
135 ctx.new_sub_path()
136 ctx.arc(x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees)
137 ctx.arc(x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees)
138 ctx.arc(x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees)
139 ctx.arc(x + radius, y + radius, radius, 180 * degrees, 270 * degrees)
140 ctx.close_path()
141 return ctx
142
143 def __draw_pieces(self):
144 if (self.__resized() or self.__pieces != self.__old_pieces or
145 self.__pieces_overlay is None):
146 # Need to recreate the cache drawing
147 self.__pieces_overlay = cairo.ImageSurface(
148 cairo.FORMAT_ARGB32, self.__width, self.__height
149 )
150 ctx = cairo.Context(self.__pieces_overlay)
151 start_pos = 0
152 num_pieces = self.__num_pieces and self.__num_pieces or len(self.__pieces)
153 piece_width = self.__width*1.0/num_pieces
154
155 for state in self.__pieces:
156 color = self.gtkui_config["pieces_color_%s" % COLOR_STATES[state]]
157 ctx.set_source_rgb(
158 color[0]/65535.0,
159 color[1]/65535.0,
160 color[2]/65535.0,
161 )
162 ctx.rectangle(start_pos, 0, piece_width, self.__height)
163 ctx.fill()
164 start_pos += piece_width
165
166 self.__cr.set_source_surface(self.__pieces_overlay)
167 self.__cr.paint()
168
169 def __draw_pieces_completed(self):
170 if (self.__resized() or self.__pieces != self.__old_pieces or
171 self.__pieces_overlay is None):
172 # Need to recreate the cache drawing
173 self.__pieces_overlay = cairo.ImageSurface(
174 cairo.FORMAT_ARGB32, self.__width, self.__height
175 )
176 ctx = cairo.Context(self.__pieces_overlay)
177 piece_width = self.__width*1.0/self.__num_pieces
178 start = 0
179 for _ in range(self.__num_pieces):
180 # Like this to keep same aspect ratio
181 color = self.gtkui_config["pieces_color_%s" % COLOR_STATES[3]]
182 ctx.set_source_rgb(
183 color[0]/65535.0,
184 color[1]/65535.0,
185 color[2]/65535.0,
186 )
187 ctx.rectangle(start, 0, piece_width, self.__height)
188 ctx.fill()
189 start += piece_width
190
191 self.__cr.set_source_surface(self.__pieces_overlay)
192 self.__cr.paint()
193
194 def __draw_progress_overlay(self):
195 if not self.__state:
196 # Nothing useful to draw, return now!
197 return
198 if (self.__resized() or self.__fraction != self.__old_fraction) or self.__progress_overlay is None:
199 # Need to recreate the cache drawing
200 self.__progress_overlay = cairo.ImageSurface(
201 cairo.FORMAT_ARGB32, self.__width, self.__height
202 )
203 ctx = cairo.Context(self.__progress_overlay)
204 ctx.set_source_rgba(0.1, 0.1, 0.1, 0.3) # Transparent
205 ctx.rectangle(0.0, 0.0, self.__width*self.__fraction, self.__height)
206 ctx.fill()
207 self.__cr.set_source_surface(self.__progress_overlay)
208 self.__cr.paint()
209
210 def __write_text(self):
211 if not self.__state:
212 # Nothing useful to draw, return now!
213 return
214 if (self.__resized() or self.__text != self.__old_text or
215 self.__fraction != self.__old_fraction or
216 self.__state != self.__old_state or
217 self.__text_overlay is None):
218 # Need to recreate the cache drawing
219 self.__text_overlay = cairo.ImageSurface(
220 cairo.FORMAT_ARGB32, self.__width, self.__height
221 )
222 ctx = cairo.Context(self.__text_overlay)
223 pg = pangocairo.CairoContext(ctx)
224 pl = pg.create_layout()
225 pl.set_font_description(self.__text_font)
226 pl.set_width(-1) # No text wrapping
227
228 text = ""
229 if self.__text:
230 text += self.__text
231 else:
232 if self.__state:
233 text += _(self.__state) + " "
234 if self.__fraction == 1.0:
235 format = "%d%%"
236 else:
237 format = "%.2f%%"
238 text += format % (self.__fraction*100)
239 log.trace("PiecesBar text %r", text)
240 pl.set_text(text)
241 plsize = pl.get_size()
242 text_width = plsize[0]/pango.SCALE
243 text_height = plsize[1]/pango.SCALE
244 area_width_without_text = self.__width - text_width
245 area_height_without_text = self.__height - text_height
246 ctx.move_to(area_width_without_text/2, area_height_without_text/2)
247 ctx.set_source_rgb(1.0, 1.0, 1.0)
248 pg.update_layout(pl)
249 pg.show_layout(pl)
250 self.__cr.set_source_surface(self.__text_overlay)
251 self.__cr.paint()
252
253 def __resized(self):
254 return (self.__old_width != self.__width or
255 self.__old_height != self.__height)
256
257 def set_fraction(self, fraction):
258 self.__old_fraction = self.__fraction
259 self.__fraction = fraction
260
261 def get_fraction(self):
262 return self.__fraction
263
264 def get_text(self):
265 return self.__text
266
267 def set_text(self, text):
268 self.__old_text = self.__text
269 self.__text = text
270
271 def set_pieces(self, pieces, num_pieces):
272 self.__old_pieces = self.__pieces
273 self.__pieces = pieces
274 self.__num_pieces = num_pieces
275
276 def get_pieces(self):
277 return self.__pieces
278
279 def set_state(self, state):
280 self.__old_state = self.__state
281 self.__state = state
282
283 def get_state(self):
284 return self.__state
285
286 def update_from_status(self, status):
287 log.trace("Updating PiecesBar from status")
288 self.set_fraction(status["progress"]/100)
289 torrent_state = status["state"]
290 self.set_state(torrent_state)
291 if torrent_state == "Checking":
292 self.update()
293 # Skip the pieces assignment
294 return
295
296 self.set_pieces(status['pieces'], status['num_pieces'])
297 self.update()
298
299 def clear(self):
300 self.__pieces = self.__old_pieces = ()
301 self.__num_pieces = self.__old_num_pieces = None
302 self.__text = self.__old_text = ""
303 self.__fraction = self.__old_fraction = 0.0
304 self.__state = self.__old_state = None
305 self.__progress_overlay = self.__text_overlay = self.__pieces_overlay = None
306 self.__cr = None
307 self.update()
308
309 def update(self):
310 self.queue_draw()
Note: See TracBrowser for help on using the repository browser.