Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

# -*- 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: 

                    format = "%d%%" 

                else: 

                    format = "%.2f%%" 

                text += 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()