Browse Source

More american spelling; added an avatar cache to be shared between all

the grids. Messages are displayed properly now (although the interface
doesn't respond properly when messages are selected.)
master
Julio Biason 15 years ago
parent
commit
e2c2379191
  1. 125
      mitterlib/ui/helpers/gdk_avatarcache.py
  2. 37
      mitterlib/ui/helpers/gtk_messagegrid.py
  3. 143
      mitterlib/ui/ui_pygtk.py

125
mitterlib/ui/helpers/gdk_avatarcache.py

@ -0,0 +1,125 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Mitter, a micro-blogging client with multiple interfaces.
# Copyright (C) 2007-2010 Mitter Contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import urllib2
import gtk
# ----------------------------------------------------------------------
# Constants
# ----------------------------------------------------------------------
_log = logging.getLogger('ui.helpers.avatarcache')
# ----------------------------------------------------------------------
# The cache class
# ----------------------------------------------------------------------
class AvatarCache(object):
"""An avatar cache, storing GDK pixbufs references."""
def __init__(self, connection, default, timeout=None):
"""Initialization of the cache.
connection:
A :class:`ThreadManager`, which will be used as thread pool to
request the avatars.
default:
A gdk_pixbuf to be used as placeholder while the real avatar is
being downloaded.
timeout:
Request timeout when downloading the avatars."""
self._connection = connection
self._default = default
self._cache = {}
self._timeout = timeout
def get(self, owner, avatar, url=None):
"""Return the avatar from the cache or, if the avatar isn't available
yet, returns the default avatar and queue the download of it.
owner:
The object requesting the avatar. Must have a "queue_draw()"
method, which will be called once the avatar is available. Can be
None.
avatar:
The identification of the avatar. Can be any string.
url:
URL to download the avatar. If not passed, *avatar* **must** be a
valid URL."""
if avatar in self._cache:
return self._cache[avatar]
if not url:
url = avatar
self._connection.add_work(self._post_avatar_download,
self._exception_avatar_download,
self._download_pic,
avatar,
url,
owner)
# add the default avatar in the cache just to avoid a new request for
# it. It will be overwritten anyway later.
self._cache[avatar] = self._default
return self._default
# ------------------------------------------------------------
# Network functions
# ------------------------------------------------------------
def _download_pic(self, avatar, url, owner):
"""Download a picture from the web. Can be used in a thread."""
request = urllib2.Request(url=url)
timeout = self._timeout
_log.debug('Starting request of %s (timeout %ds)' % (
url, timeout))
response = urllib2.urlopen(request, timeout=timeout)
data = response.read()
_log.debug('Request completed')
return (avatar, owner, data)
def _post_avatar_download(self, widget, data):
"""Called after the data from the picture is available."""
(avatar, owner, data) = data
loader = gtk.gdk.PixbufLoader()
loader.write(data)
loader.close()
user_pic = loader.get_pixbuf()
user_pic = user_pic.scale_simple(48, 48, gtk.gdk.INTERP_BILINEAR)
self._cache[avatar] = user_pic
if owner:
owner.queue_draw()
return
def _exception_avatar_download(self, widget, exception):
"""Called in case we have a problem downloading an user avatar."""
_log.debug('Exception trying to get an avatar.')
_log.debug(str(exception))
return

37
mitterlib/ui/helpers/gtk_messagegrid.py

@ -22,6 +22,7 @@ import gtk
import logging
import pango
import re
import gettext
from cgi import escape as html_escape
@ -30,7 +31,7 @@ import timesince
# ----------------------------------------------------------------------
# Constants for the message
# ----------------------------------------------------------------------
MESSAGE_FORMAT = ('%(favourite_star)s'
MESSAGE_FORMAT = ('%(favorite_star)s'
'<b>%(full_name)s</b> '
'<small>(%(username)s'
'%(message_type)s'
@ -47,6 +48,13 @@ URL_RE = re.compile(r'(https?://[^\s\n\r]+)', re.I)
# ----------------------------------------------------------------------
_log = logging.getLogger('ui.pygtk.messagegrid')
# ----------------------------------------------------------------------
# I18n bits
# ----------------------------------------------------------------------
t = gettext.translation('ui_pygtk', fallback=True)
_ = t.gettext
N_ = t.ngettext
# ----------------------------------------------------------------------
# MessageGrid class
# ----------------------------------------------------------------------
@ -71,10 +79,10 @@ class MessageGrid(gtk.ScrolledWindow, gobject.GObject):
self.emit('count-changed', messages)
return
def __init__(self):
def __init__(self, avatar_cache):
super(MessageGrid, self).__init__()
self.avatars = None
self.avatars = avatar_cache
self.link_color = '#0000ff'
self.unread_char = '(o)'
self.favorite_char = '(F)'
@ -128,7 +136,7 @@ class MessageGrid(gtk.ScrolledWindow, gobject.GObject):
data = store.get_value(position, 0)
pic = data.author.avatar
cell.set_property('pixbuf', self.avatars[pic])
cell.set_property('pixbuf', self.avatars.get(self, pic))
return
@ -164,10 +172,10 @@ class MessageGrid(gtk.ScrolledWindow, gobject.GObject):
else:
message_values['read_status'] = ''
if data.favourite:
message_values['favourite_star'] = (self.unfavourite_char)
if data.favorite:
message_values['favorite_star'] = (self.unfavorite_char)
else:
message_values['favourite_star'] = (self.favourite_char)
message_values['favorite_star'] = (self.favorite_char)
info = []
if data.reposted_by:
@ -186,6 +194,10 @@ class MessageGrid(gtk.ScrolledWindow, gobject.GObject):
message_values['message_type'] = ''.join(info)
markup = MESSAGE_FORMAT % (message_values)
# This log is massive, but it can help when the grid doesn't show any
# messages (usually when there is something broken with the markup.)
#_log.debug('Markup:\n%s' % (markup))
cell.set_property('markup', markup)
return
@ -205,10 +217,10 @@ class MessageGrid(gtk.ScrolledWindow, gobject.GObject):
data = model.get_value(iter, 0)
data.read = True
self._delete_action.set_property('sensitive', data.deletable)
self._reply_action.set_property('sensitive', data.replyable)
self._repost_action.set_property('sensitive', data.repostable)
self._favourite_action.set_property('sensitive', data.favoritable)
#self._delete_action.set_property('sensitive', data.deletable)
#self._reply_action.set_property('sensitive', data.replyable)
#self._repost_action.set_property('sensitive', data.repostable)
#self._favorite_action.set_property('sensitive', data.favoritable)
return 0
@ -333,6 +345,9 @@ class MessageGrid(gtk.ScrolledWindow, gobject.GObject):
iter = model.iter_next(iter)
return
#-----------------------------------------------------------------------
# Public functions
#-----------------------------------------------------------------------
def update(self, messages):
"""Update the grid with new messages."""
self._grid.freeze_notify()

143
mitterlib/ui/ui_pygtk.py

@ -17,13 +17,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import gtk
import gobject
import gtk
gobject.threads_init()
import logging
import re
import urllib2
import webbrowser
import gettext
@ -32,6 +31,7 @@ from mitterlib.ui.helpers.image_helpers import find_image
from mitterlib.ui.helpers.gtk_threading import ThreadManager
from mitterlib.ui.helpers.gtk_updatebox import UpdateBox
from mitterlib.ui.helpers.gtk_messagegrid import MessageGrid
from mitterlib.ui.helpers.gdk_avatarcache import AvatarCache
from mitterlib.constants import gpl_3, version
# ----------------------------------------------------------------------
@ -88,9 +88,31 @@ class Interface(object):
)
self._statusbar = self._create_statusbar()
# the messages grid
messages = MessageGrid(self._avatars)
messages.link_color = self._options[self.NAMESPACE]['link_color']
messages.unread_char = self._options[self.NAMESPACE]['unread_char']
messages.favorite_char = (
self._options[self.NAMESPACE]['favorite_char'])
messages.unfavorite_char = (
self._options[self.NAMESPACE]['unfavorite_char'])
messages.protected_char = (
self._options[self.NAMESPACE]['protected_char'])
# replies grid
replies = MessageGrid(self._avatars)
replies.link_color = self._options[self.NAMESPACE]['link_color']
replies.unread_char = self._options[self.NAMESPACE]['unread_char']
replies.favorite_char = (
self._options[self.NAMESPACE]['favorite_char'])
replies.unfavorite_char = (
self._options[self.NAMESPACE]['unfavorite_char'])
replies.protected_char = (
self._options[self.NAMESPACE]['protected_char'])
self._main_tabs = gtk.Notebook()
self._main_tabs.insert_page(MessageGrid(), gtk.Label('Messages'))
self._main_tabs.insert_page(MessageGrid(), gtk.Label('Replies'))
self._main_tabs.insert_page(messages, gtk.Label('Messages'))
self._main_tabs.insert_page(replies, gtk.Label('Replies'))
update_box = gtk.VBox()
update_box.set_property('border_width', 2)
@ -116,8 +138,6 @@ class Interface(object):
self._statusbar.show_all()
box.show()
self._message_count_updated()
# now that all elements are created, connect the signals
main_window.connect('destroy', self._quit_app)
main_window.connect('delete-event', self._quit_app)
@ -266,12 +286,12 @@ class Interface(object):
self._repost_action.connect('activate', self._repost_message)
action_group.add_action_with_accel(self._repost_action, '<Ctrl>p')
self._favourite_action = gtk.Action('Favourite', _('_Favorite'),
self._favorite_action = gtk.Action('Favourite', _('_Favorite'),
_('Toggle the favorite status of a message'),
gtk.STOCK_ABOUT)
self._favourite_action.set_property('sensitive', False)
self._favourite_action.connect('activate', self._favourite_message)
action_group.add_action_with_accel(self._favourite_action, '<Ctrl>f')
self._favorite_action.set_property('sensitive', False)
self._favorite_action.connect('activate', self._favorite_message)
action_group.add_action_with_accel(self._favorite_action, '<Ctrl>f')
# view actions
view_messages_action = gtk.Action('Messages', _('_Messages'),
@ -367,25 +387,6 @@ class Interface(object):
self._reply_message_id = None
return
def _message_count_updated(self):
"""Update the elements in the interface that should change in case of
changes in the message count."""
child = self._main_tabs.get_nth_page(0)
self._main_tabs.set_tab_label_text(child, _('Messages (%d)') %
(self._new_message_count))
child = self._main_tabs.get_nth_page(1)
self._main_tabs.set_tab_label_text(child, _('Replies (%d)') %
(self._new_replies_count))
if self._statusicon:
if (self._new_message_count + self._new_replies_count) == 0:
self._statusicon.set_from_pixbuf(self._images['icon'])
else:
self._statusicon.set_from_pixbuf(
self._images['new-messages'])
return
# ------------------------------------------------------------
# Widget callback functions
@ -528,22 +529,22 @@ class Interface(object):
message)
return
def _favourite_message(self, widget, user_data=None):
"""Toggle the favourite status of a message."""
def _favorite_message(self, widget, user_data=None):
"""Toggle the favorite status of a message."""
(grid, counter) = self._grids[self._main_tabs.get_current_page()]
(model, iter) = grid.get_selection().get_selected()
message = model.get_value(iter, 0)
self._favourite_info = (grid, iter)
self._favorite_info = (grid, iter)
if message.favourite:
if message.favorite:
display = _('Removing message from %s from favorites...')
else:
display = _('Marking message from %s as favorite...')
self._update_statusbar(display % (message.author.username))
self._threads.add_work(self._post_favourite_message,
self._exception_favourite_message,
self._connection.favourite,
self._threads.add_work(self._post_favorite_message,
self._exception_favorite_message,
self._connection.favorite,
message)
return
@ -665,7 +666,7 @@ class Interface(object):
self._delete_action.set_property('sensitive', False)
self._reply_action.set_property('sensitive', False)
self._repost_action.set_property('sensitive', False)
self._favourite_action.set_property('sensitive', False)
self._favorite_action.set_property('sensitive', False)
return
def _new_message(self, widget, user_data=None):
@ -746,24 +747,6 @@ class Interface(object):
_log.debug('Request completed')
return (url, data)
### Results from the picture request
def _post_download_pic(self, widget, data):
"""Called after the data from the picture is available."""
(url, data) = data
loader = gtk.gdk.PixbufLoader()
loader.write(data)
loader.close()
user_pic = loader.get_pixbuf()
user_pic = user_pic.scale_simple(48, 48, gtk.gdk.INTERP_BILINEAR)
self._avatars[url] = user_pic
page = self._main_tabs.get_current_page()
grid = self._main_tabs.get_nth_page(page)
grid.queue_draw()
return
### Results from the avatar download request
def _post_avatar_pic(self, widget, data):
"""Called after the data from the picture of the user's avatar is
@ -843,23 +826,23 @@ class Interface(object):
error_win.hide()
return
### Results from the favourite call
def _post_favourite_message(self, widget, data):
"""Called when the message was favourited successfully."""
### Results from the favorite call
def _post_favorite_message(self, widget, data):
"""Called when the message was favorited successfully."""
_log.debug('Favourite status changed.')
(grid, iter) = self._favourite_info
(grid, iter) = self._favorite_info
message = grid.get_model().get_value(iter, 0)
if message.favourite:
if message.favorite:
display = _('Message unfavorited.')
else:
display = _('Message favorited.')
self._update_statusbar(display)
message.favourite = not message.favourite
self._favourite_info = None
message.favorite = not message.favorite
self._favorite_info = None
return
def _exception_favourite_message(self, widget, exception):
"""Called when the message couldn't be favourited."""
def _exception_favorite_message(self, widget, exception):
"""Called when the message couldn't be favorited."""
_log.debug('Favourite error.')
_log.debug(str(exception))
@ -879,13 +862,6 @@ class Interface(object):
"""Start the interface. `connection` is the :class:`Networks` object
with all the available networks. `options` is the :class:`ConfigOpt`
object with the configuration to run Mitter."""
self._connection = connection
self._options = options
self._avatars = {}
self._pic_queue = set()
# Load images
unknown_pixbuf = find_image('unknown.png')
if unknown_pixbuf:
@ -900,6 +876,9 @@ class Interface(object):
self._images['logo'] = gtk.gdk.pixbuf_new_from_file(
find_image('mitter-big.png'))
self._connection = connection
self._options = options
# icons (app and statusicon)
self._images['icon'] = gtk.gdk.pixbuf_new_from_file(
find_image('mitter.png'))
@ -912,9 +891,7 @@ class Interface(object):
# interthread communication.
self._delete_info = None
self._reply_message_id = None
self._favourite_info = None
self._new_message_count = 0 # TODO: Turn this into a @property
self._new_replies_count = 0 # TODO: Turn this into a @property
self._favorite_info = None
return
@ -929,11 +906,15 @@ class Interface(object):
else:
self._statusicon = None
self._threads = ThreadManager()
self._avatars = AvatarCache(
self._threads,
self._images['avatar'],
self._options['NetworkManager']['timeout'])
self._main_window = self._create_main_window()
self._main_window.show()
self._threads = ThreadManager()
# get a user avatar. We need that for the updatebox.
user = self._connection.user()
self._threads.add_work(self._post_avatar_pic,
@ -1007,7 +988,7 @@ class Interface(object):
is_cmd_option=False)
options.add_option(
group=self.NAMESPACE,
option='link_colour',
option='link_color',
help='Color of links in the interface',
type='str',
metavar='COLOR',
@ -1032,17 +1013,17 @@ class Interface(object):
is_cmd_option=False)
options.add_option(
group=self.NAMESPACE,
option='unfavourite_char',
option='unfavorite_char',
help='String to be used to indicate a message is not ' \
'marked as favourite.',
'marked as favorite.',
metavar='CHAR',
default='&#9733;',
is_cmd_option=False)
options.add_option(
group=self.NAMESPACE,
option='favourite_char',
option='favorite_char',
help='String to be used to indicate a message is marked ' \
'as favourite.',
'as favorite.',
metavar='CHAR',
default='&#9734;',
is_cmd_option=False)

Loading…
Cancel
Save