19
19
from bzrlib.config import parse_username
21
21
from bzrlib.plugins.gtk import _i18n
22
from bzrlib.plugins.gtk.avatarproviders import AvatarProviderGravatar, AvatarDownloaderWorker
25
class Avatar(gtk.Box):
22
from bzrlib.plugins.gtk.avatarproviders import (
23
AvatarProviderGravatar,
24
AvatarDownloaderWorker,
28
class Avatar(gtk.HBox):
26
29
""" Author or committer avatar """
28
31
def __init__(self, apparent_username):
29
32
""" Constructor """
30
gtk.Box.__init__(self)
33
gtk.HBox.__init__(self)
32
35
self.apparent_username = apparent_username
33
36
self.username, self.email = parse_username(apparent_username)
36
39
def __eq__(self, other):
38
Return True if attributes of the given avatar
39
match to current object attributes otherwise return False
41
return self.apparent_username == other.apparent_username and \
42
self.name == other.name and \
43
self.email == other.email
45
# ~~~~~ Public methods ~~~~~
40
return (self.apparent_username == other.apparent_username and
41
self.name == other.name and
42
self.email == other.email)
46
44
def show_spinner(self):
48
46
Replace the current content of the Avatar with a gtk.Spinner
49
47
if an email address has been parsed. If not, show an gtk.Label with
50
48
the translatable 'No email' text.
52
if not self.email is "":
53
51
spinner = gtk.Spinner()
55
53
self.pack_start(spinner, False)
74
72
class AvatarBox(gtk.HBox):
75
""" Improved gtk.HBox """
73
"""HBox showing an avatar."""
77
75
def __init__(self, homogeneous=False, spacing=0):
79
76
gtk.HBox.__init__(self, homogeneous, spacing)
80
77
self.__avatars = {}
82
79
self.__displaying = None
85
# ~~~~~ Public methods ~~~~~
86
81
def reset_view(self):
87
""" Remove current avatars from the gtk box """
82
"""Remove current avatars from the gtk box."""
88
83
for child in self.get_children():
90
85
self.__displaying = None
92
87
def have_avatar(self, avatar):
94
Return True if this box has registered given avatar,
95
otherwise return False
88
"""Return True if this box has the specified avatar.
97
90
return avatar.email in self.__avatars
99
92
def showing(self, avatar):
101
Return True if the displaying avatar is the same
102
as the given one otherwise return False
93
"""Return True if the displaying avatar matches the specified one.
104
95
return self.__displaying and self.__displaying == avatar
106
97
def append_avatars_with(self, avatar):
108
99
Append avatars collection with the given one if not already registed
110
101
When an avatar is added this method True, otherwise, if the avatar
111
102
was in the collection, return False.
113
if not avatar.email is "" and not avatar.email in self.__avatars:
104
if avatar.email and not avatar.email in self.__avatars:
114
105
self.__avatars[avatar.email] = avatar
115
106
self._new_cell_for_avatar(avatar)
118
109
self.and_avatar_email(avatar.email).come_back_to_gui()
121
112
def and_avatar_email(self, email):
123
114
Select the avatar from avatars collection
124
115
in order to apply future actions
126
117
self.avatar = None
127
if not email is "" and email in self.__avatars:
118
if email and email in self.__avatars:
128
119
self.avatar = self.__avatars[email]
130
121
self.avatar = None
133
124
def update_avatar_image_from_pixbuf_loader(self, loader):
134
""" Replace the gtk.Spinner with the given loader """
125
"""Replace the gtk.Spinner with the given loader."""
136
127
self.avatar.image = gtk.Image()
137
128
self.avatar.image.set_from_pixbuf(loader.get_pixbuf())
138
129
self.avatar.show_image()
139
130
self.__displaying = self.avatar
141
132
def come_back_to_gui(self):
142
""" Render back avatar in the GUI """
133
"""Render back avatar in the GUI."""
144
135
self.pack_start(self.avatar)
145
136
self.__displaying = self.avatar
147
138
self._show_no_email()
150
# ~~~~~ Private methods ~~~~~~
151
140
def _new_cell_for_avatar(self, avatar):
152
""" Create a new cell in this box with a gtk.Spinner """
141
"""Create a new cell in this box with a gtk.Spinner."""
153
142
avatar.show_spinner()
154
143
self.pack_start(avatar)
156
145
self.__displaying = avatar
158
147
def _show_no_email(self):
159
""" Show a gtk.Label with test 'No email' """
148
"""Show a gtk.Label with test 'No email'."""
160
149
no_email = gtk.Label(_i18n("No email"))
161
150
self.pack_start(no_email)
165
154
class AvatarsBox(gtk.HBox):
166
""" GTK container for authors and committers avatars """
155
"""GTK container for author and committer avatars."""
168
157
def __init__(self):
170
158
gtk.HBox.__init__(self, False, 10)
172
160
self.__committer_box = None
173
161
self.__authors_box = None
176
# If more later you want to implement more avatar providers, to it like this:
177
# Create a new class named AvatarProvider + provider_name that inherit from
178
# the AvatarProvider class.
179
# Implement a method that return url to use in the request.
164
# If more later you want to implement more avatar providers:
165
# * Create a new class named AvatarProvider + provider_name that
166
# inherit from the AvatarProvider class.
167
# * Implement a method that return url to use in the request.
180
169
# For example, with Gravatar, the method return the complete url
181
# with MD5 hash of the email address and put the value in a gravatar_id field.
182
# Then create a new worker (manage them in a python dictionnary).
170
# with MD5 hash of the email address and put the value in a
172
# Then create a new worker (manage them in a python dictionary).
183
173
provider = AvatarProviderGravatar()
184
174
self.__worker = AvatarDownloaderWorker(
185
175
provider.gravatar_id_for_email
187
# This callback method should be fired bt all workers when a request is done.
177
# This callback method should be fired by all workers when a request
188
179
self.__worker.set_callback_method(self._update_avatar_from_response)
189
180
self.__worker.start()
192
# ~~~~~ Public methods ~~~~~
193
182
def add(self, username, role):
195
Add the given username in the right role box and add in the worker queue.
196
Here again: If you want to implement more providers, you should add the
197
avatar request in all workers queue.
183
"""Add the given username in the role box and add in the worker queue.
199
185
avatar = Avatar(username)
200
if role is "author" and not self._role_box_for("committer").showing(avatar) or role is "committer":
186
if (role == "author" and not self._role_box_for("committer").showing(avatar)) or role == "committer":
201
187
if self._role_box_for(role).append_avatars_with(avatar):
202
188
self.__worker.queue(avatar.email)
204
190
def merge(self, usernames, role):
205
""" Add avatars from a list """
191
"""Add avatars from a list"""
206
192
for username in usernames:
207
193
self.add(username, role)
211
197
Request a reset view for all boxes in order to show only avatars
212
198
of the selected line in the revision view screen.
214
[self._role_box_for(role).reset_view() for role in ["committer", "author"]]
217
# ~~~~~ Private methods ~~~~~
200
for role in ("committer", "author"):
201
self._role_box_for(role).reset_view()
218
203
def _init_gui(self):
219
""" Create boxes where avatars will be displayed """
204
"""Create boxes where avatars will be displayed."""
220
205
# 2 gtk.HBox: One for the committer and one for authors
222
207
self.__committer_box = AvatarBox()
228
213
self.pack_end(self.__authors_box, False)
229
214
self.__authors_box.set_spacing(10)
230
215
self.__authors_box.show()
232
217
def _update_avatar_from_response(self, response, email):
234
Callback method fired by avatar worker when finished.
236
response is a urllib2.urlopen() return value.
237
email is used to identify item from self.__avatars.
218
"""Callback method fired by avatar worker when finished.
220
:param response: a urllib2.urlopen() return value.
221
:param email: used to identify item from self.__avatars.
240
224
# Convert downloaded image from provider to gtk.Image
241
225
loader = gtk.gdk.PixbufLoader()
242
226
loader.write(response.read())
245
229
for role in ["committer", "author"]:
246
230
self._role_box_for(role).and_avatar_email(email).update_avatar_image_from_pixbuf_loader(loader)
248
232
def _role_box_for(self, role):
249
233
""" Return the gtk.HBox for the given role """
250
return self.__committer_box if role is "committer" else self.__authors_box
234
if role == "committer":
235
return self.__committer_box
237
return self.__authors_box