/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz

« back to all changes in this revision

Viewing changes to avatarsbox.py

  • Committer: Jelmer Vernooij
  • Date: 2011-04-06 14:50:40 UTC
  • Revision ID: jelmer@samba.org-20110406145040-7u37k9hxkhleiqx2
Cleanups, fix compatibility with older versions of pygtk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2011 by Guillaume Hain (zedtux) <zedtux@zedroot.org>
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
import gtk
 
18
 
 
19
from bzrlib.config import parse_username
 
20
 
 
21
from bzrlib.plugins.gtk import _i18n
 
22
from bzrlib.plugins.gtk.avatarproviders import (
 
23
    AvatarProviderGravatar,
 
24
    AvatarDownloaderWorker,
 
25
    )
 
26
 
 
27
 
 
28
class Avatar(gtk.HBox):
 
29
    """ Author or committer avatar """
 
30
 
 
31
    def __init__(self, apparent_username):
 
32
        """ Constructor """
 
33
        gtk.HBox.__init__(self)
 
34
 
 
35
        self.apparent_username = apparent_username
 
36
        self.username, self.email = parse_username(apparent_username)
 
37
        self.image = None
 
38
 
 
39
    def __eq__(self, other):
 
40
        return (self.apparent_username == other.apparent_username and
 
41
                self.name == other.name and
 
42
                self.email == other.email)
 
43
 
 
44
    def show_spinner(self):
 
45
        """
 
46
        Replace the current content of the Avatar with a gtk.Spinner
 
47
        if an email address has been parsed. If not, show an gtk.Label with
 
48
        the translatable 'No email' text.
 
49
        """
 
50
        if self.email:
 
51
            spinner = gtk.Spinner()
 
52
            spinner.start()
 
53
            self.pack_start(spinner, False)
 
54
            spinner.set_tooltip_text(_i18n("Retrieving avatar for %s...") % self.email)
 
55
            spinner.set_size_request(20, 20)
 
56
            spinner.show()
 
57
        else:
 
58
            no_email = gtk.Label(_i18n("No email"))
 
59
            self.pack_start(no_email)
 
60
            self.set_tooltip_text(self.apparent_username)
 
61
            no_email.show()
 
62
 
 
63
    def show_image(self):
 
64
        """Replace the current content of the Avatar with the gtk.Image """
 
65
        if self.email and self.image:
 
66
            self.remove(self.get_children()[0])
 
67
            self.pack_start(self.image)
 
68
            self.image.set_tooltip_text(self.apparent_username)
 
69
            self.image.show()
 
70
 
 
71
 
 
72
class AvatarBox(gtk.HBox):
 
73
    """HBox showing an avatar."""
 
74
 
 
75
    def __init__(self, homogeneous=False, spacing=0):
 
76
        gtk.HBox.__init__(self, homogeneous, spacing)
 
77
        self.__avatars = {}
 
78
        self.avatar = None
 
79
        self.__displaying = None
 
80
 
 
81
    def reset_view(self):
 
82
        """Remove current avatars from the gtk box."""
 
83
        for child in self.get_children():
 
84
            self.remove(child)
 
85
        self.__displaying = None
 
86
 
 
87
    def have_avatar(self, avatar):
 
88
        """Return True if this box has the specified avatar.
 
89
        """
 
90
        return avatar.email in self.__avatars
 
91
 
 
92
    def showing(self, avatar):
 
93
        """Return True if the displaying avatar matches the specified one.
 
94
        """
 
95
        return self.__displaying and self.__displaying == avatar
 
96
 
 
97
    def append_avatars_with(self, avatar):
 
98
        """
 
99
        Append avatars collection with the given one if not already registed
 
100
        otherwise render it back.
 
101
        When an avatar is added this method True, otherwise, if the avatar
 
102
        was in the collection, return False.
 
103
        """
 
104
        if avatar.email and not avatar.email in self.__avatars:
 
105
            self.__avatars[avatar.email] = avatar
 
106
            self._new_cell_for_avatar(avatar)
 
107
            return True
 
108
        else:
 
109
            self.and_avatar_email(avatar.email).come_back_to_gui()
 
110
        return False
 
111
 
 
112
    def and_avatar_email(self, email):
 
113
        """
 
114
        Select the avatar from avatars collection
 
115
        in order to apply future actions
 
116
        """
 
117
        self.avatar = None
 
118
        if email and email in self.__avatars:
 
119
            self.avatar = self.__avatars[email]
 
120
        else:
 
121
            self.avatar = None
 
122
        return self
 
123
 
 
124
    def update_avatar_image_from_pixbuf_loader(self, loader):
 
125
        """Replace the gtk.Spinner with the given loader."""
 
126
        if self.avatar:
 
127
            self.avatar.image = gtk.Image()
 
128
            self.avatar.image.set_from_pixbuf(loader.get_pixbuf())
 
129
            self.avatar.show_image()
 
130
            self.__displaying = self.avatar
 
131
 
 
132
    def come_back_to_gui(self):
 
133
        """Render back avatar in the GUI."""
 
134
        if self.avatar:
 
135
            self.pack_start(self.avatar)
 
136
            self.__displaying = self.avatar
 
137
        else:
 
138
            self._show_no_email()
 
139
 
 
140
    def _new_cell_for_avatar(self, avatar):
 
141
        """Create a new cell in this box with a gtk.Spinner."""
 
142
        avatar.show_spinner()
 
143
        self.pack_start(avatar)
 
144
        avatar.show()
 
145
        self.__displaying = avatar
 
146
 
 
147
    def _show_no_email(self):
 
148
        """Show a gtk.Label with test 'No email'."""
 
149
        no_email = gtk.Label(_i18n("No email"))
 
150
        self.pack_start(no_email)
 
151
        no_email.show()
 
152
 
 
153
 
 
154
class AvatarsBox(gtk.HBox):
 
155
    """GTK container for author and committer avatars."""
 
156
 
 
157
    def __init__(self):
 
158
        gtk.HBox.__init__(self, False, 10)
 
159
 
 
160
        self.__committer_box = None
 
161
        self.__authors_box = None
 
162
        self._init_gui()
 
163
 
 
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.
 
168
 
 
169
        # For example, with Gravatar, the method return the complete url
 
170
        # with MD5 hash of the email address and put the value in a
 
171
        # gravatar_id field.
 
172
        # Then create a new worker (manage them in a python dictionary).
 
173
        provider = AvatarProviderGravatar()
 
174
        self.__worker = AvatarDownloaderWorker(
 
175
            provider.gravatar_id_for_email
 
176
        )
 
177
        # This callback method should be fired by all workers when a request
 
178
        # is done.
 
179
        self.__worker.set_callback_method(self._update_avatar_from_response)
 
180
        self.__worker.start()
 
181
 
 
182
    def add(self, username, role):
 
183
        """Add the given username in the role box and add in the worker queue.
 
184
        """
 
185
        avatar = Avatar(username)
 
186
        if (role == "author" and not self._role_box_for("committer").showing(avatar)) or role == "committer":
 
187
            if self._role_box_for(role).append_avatars_with(avatar):
 
188
                self.__worker.queue(avatar.email)
 
189
 
 
190
    def merge(self, usernames, role):
 
191
        """Add avatars from a list"""
 
192
        for username in usernames:
 
193
            self.add(username, role)
 
194
 
 
195
    def reset(self):
 
196
        """
 
197
        Request a reset view for all boxes in order to show only avatars
 
198
        of the selected line in the revision view screen.
 
199
        """
 
200
        for role in ("committer", "author"):
 
201
            self._role_box_for(role).reset_view()
 
202
 
 
203
    def _init_gui(self):
 
204
        """Create boxes where avatars will be displayed."""
 
205
        # 2 gtk.HBox: One for the committer and one for authors
 
206
        # Committer
 
207
        self.__committer_box = AvatarBox()
 
208
        self.__committer_box.set_size_request(80, 80)
 
209
        self.pack_end(self.__committer_box, False)
 
210
        self.__committer_box.show()
 
211
        # Authors
 
212
        self.__authors_box = AvatarBox()
 
213
        self.pack_end(self.__authors_box, False)
 
214
        self.__authors_box.set_spacing(10)
 
215
        self.__authors_box.show()
 
216
 
 
217
    def _update_avatar_from_response(self, response, email):
 
218
        """Callback method fired by avatar worker when finished.
 
219
 
 
220
        :param response: a urllib2.urlopen() return value.
 
221
        :param email: used to identify item from self.__avatars.
 
222
        """
 
223
        if email:
 
224
            # Convert downloaded image from provider to gtk.Image
 
225
            loader = gtk.gdk.PixbufLoader()
 
226
            loader.write(response.read())
 
227
            loader.close()
 
228
 
 
229
            for role in ["committer", "author"]:
 
230
                self._role_box_for(role).and_avatar_email(email).update_avatar_image_from_pixbuf_loader(loader)
 
231
 
 
232
    def _role_box_for(self, role):
 
233
        """ Return the gtk.HBox for the given role """
 
234
        if role == "committer":
 
235
            return self.__committer_box
 
236
        else:
 
237
            return self.__authors_box