Scale Avatars for HiDPI Screens

This commit is contained in:
Philipp Hörist 2017-10-19 21:12:27 +02:00 committed by Philipp Hörist
parent 440b6e4829
commit 232dc1dda0
9 changed files with 120 additions and 61 deletions

View File

@ -1040,9 +1040,11 @@ class ChatControl(ChatControlBase):
jid = self.contact.jid
if app.config.get('show_avatar_in_tabs'):
avatar_pixbuf = app.contacts.get_avatar(self.account, jid, size=16)
if avatar_pixbuf is not None:
return avatar_pixbuf
scale = self.parent_win.window.get_scale_factor()
surface = app.contacts.get_avatar(
self.account, jid, AvatarSize.TAB, scale)
if surface is not None:
return surface
if count_unread:
num_unread = len(app.events.get_events(self.account, jid,
@ -1273,18 +1275,19 @@ class ChatControl(ChatControlBase):
if not app.config.get('show_avatar_in_chat'):
return
scale = self.parent_win.window.get_scale_factor()
if self.TYPE_ID == message_control.TYPE_CHAT:
pixbuf = app.contacts.get_avatar(
self.account, self.contact.jid, AvatarSize.CHAT)
surface = app.contacts.get_avatar(
self.account, self.contact.jid, AvatarSize.CHAT, scale)
else:
pixbuf = app.interface.get_avatar(
self.gc_contact.avatar_sha, AvatarSize.CHAT)
surface = app.interface.get_avatar(
self.gc_contact.avatar_sha, AvatarSize.CHAT, scale)
image = self.xml.get_object('avatar_image')
if pixbuf is None:
if surface is None:
image.set_from_icon_name('avatar-default', Gtk.IconSize.DIALOG)
else:
image.set_from_pixbuf(pixbuf)
image.set_from_surface(surface)
def _nec_update_avatar(self, obj):
if obj.account != self.account:

View File

@ -29,6 +29,7 @@ class OptionType(IntEnum):
DIALOG = 4
class AvatarSize(IntEnum):
TAB = 16
ROSTER = 32
CHAT = 48
NOTIFICATION = 48

View File

@ -197,8 +197,8 @@ class GC_Contact(CommonContact):
def get_shown_name(self):
return self.name
def get_avatar(self, size=None):
return common.app.interface.get_avatar(self.avatar_sha, size)
def get_avatar(self, *args, **kwargs):
return common.app.interface.get_avatar(self.avatar_sha, *args, **kwargs)
def as_contact(self):
"""
@ -310,8 +310,8 @@ class LegacyContactsAPI:
def get_contact(self, account, jid, resource=None):
return self._accounts[account].contacts.get_contact(jid, resource=resource)
def get_avatar(self, account, jid, size=None):
return self._accounts[account].contacts.get_avatar(jid, size)
def get_avatar(self, account, *args, **kwargs):
return self._accounts[account].contacts.get_avatar(*args, **kwargs)
def get_avatar_sha(self, account, jid):
return self._accounts[account].contacts.get_avatar_sha(jid)
@ -509,14 +509,15 @@ class Contacts():
return c
return self._contacts[jid][0]
def get_avatar(self, jid, size=None):
def get_avatar(self, jid, size=None, scale=None):
if jid not in self._contacts:
return None
for resource in self._contacts[jid]:
if resource.avatar_sha is None:
continue
avatar = common.app.interface.get_avatar(resource.avatar_sha, size)
avatar = common.app.interface.get_avatar(
resource.avatar_sha, size, scale)
if avatar is None:
self.set_avatar(jid, None)
return avatar

View File

@ -77,25 +77,38 @@ class Column(IntEnum):
NICK = 1 # contact nickame or ROLE name
TYPE = 2 # type of the row ('contact' or 'role')
TEXT = 3 # text shown in the cellrenderer
AVATAR = 4 # avatar of the contact
AVATAR_IMG = 4 # avatar of the contact
empty_pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 1, 1)
empty_pixbuf.fill(0xffffff00)
def tree_cell_data_func(column, renderer, model, iter_, tv=None):
def status_cell_data_func(column, renderer, model, iter_, user_data):
renderer.set_property('width', 26)
image = model[iter_][Column.IMG]
surface = image.get_property('surface')
renderer.set_property('surface', surface)
def tree_cell_data_func(column, renderer, model, iter_, user_data):
# cell data func is global, because we don't want it to keep
# reference to GroupchatControl instance (self)
theme = app.config.get('roster_theme')
# allocate space for avatar only if needed
parent_iter = model.iter_parent(iter_)
if isinstance(renderer, Gtk.CellRendererPixbuf):
image = model[iter_][Column.AVATAR_IMG]
if image is None:
return
surface = image.get_property('surface')
renderer.set_property('surface', surface)
avatar_position = app.config.get('avatar_position_in_roster')
if avatar_position == 'right':
renderer.set_property('xalign', 1) # align pixbuf to the right
else:
renderer.set_property('xalign', 0.5)
if parent_iter and (model[iter_][Column.AVATAR] or avatar_position == \
'left'):
if parent_iter:
renderer.set_property('visible', True)
renderer.set_property('width', AvatarSize.ROSTER)
else:
@ -254,6 +267,16 @@ class PrivateChatControl(ChatControl):
return
self.show_avatar()
def show_avatar(self):
if not app.config.get('show_avatar_in_chat'):
return
scale = self.parent_win.window.get_scale_factor()
surface = app.interface.get_avatar(
self.gc_contact.avatar_sha, AvatarSize.CHAT, scale)
image = self.xml.get_object('avatar_image')
image.set_from_surface(surface)
def update_contact(self):
self.contact = self.gc_contact.as_contact()
@ -308,6 +331,9 @@ class GroupchatControl(ChatControlBase):
# Tooltip Window and Actions have to be created with parent
self.set_tooltip()
self.add_actions()
self.scale_factor = parent_win.window.get_scale_factor()
else:
self.scale_factor = app.interface.roster.scale_factor
widget = self.xml.get_object('list_treeview')
id_ = widget.connect('row_expanded', self.on_list_treeview_row_expanded)
@ -392,7 +418,7 @@ class GroupchatControl(ChatControlBase):
self.hpaned.set_position(hpaned_position)
#status_image, shown_nick, type, nickname, avatar
self.columns = [Gtk.Image, str, str, str, GdkPixbuf.Pixbuf]
self.columns = [Gtk.Image, str, str, str, Gtk.Image]
self.model = Gtk.TreeStore(*self.columns)
self.model.set_sort_func(Column.NICK, self.tree_compare_iters)
self.model.set_sort_column_id(Column.NICK, Gtk.SortType.ASCENDING)
@ -415,20 +441,20 @@ class GroupchatControl(ChatControlBase):
self.renderers_list += (
# status img
('icon', renderer_image, False,
'image', Column.IMG, tree_cell_data_func, self.list_treeview),
'image', Column.IMG, tree_cell_data_func, None),
# contact name
('name', renderer_text, True,
'markup', Column.TEXT, tree_cell_data_func, self.list_treeview))
'markup', Column.TEXT, tree_cell_data_func, None))
# avatar img
avater_renderer = ('avatar', Gtk.CellRendererPixbuf(),
False, 'pixbuf', Column.AVATAR,
tree_cell_data_func, self.list_treeview)
avatar_renderer = ('avatar', Gtk.CellRendererPixbuf(),
False, None, Column.AVATAR_IMG,
tree_cell_data_func, None)
if app.config.get('avatar_position_in_roster') == 'right':
self.renderers_list.append(avater_renderer)
self.renderers_list.append(avatar_renderer)
else:
self.renderers_list.insert(0, avater_renderer)
self.renderers_list.insert(0, avatar_renderer)
self.fill_column(column)
self.list_treeview.append_column(column)
@ -782,7 +808,8 @@ class GroupchatControl(ChatControlBase):
def fill_column(self, col):
for rend in self.renderers_list:
col.pack_start(rend[1], rend[2])
col.add_attribute(rend[1], rend[3], rend[4])
if rend[0] != 'avatar':
col.add_attribute(rend[1], rend[3], rend[4])
col.set_cell_data_func(rend[1], rend[5], rend[6])
# set renderers propertys
for renderer in self.renderers_propertys.keys():
@ -1652,8 +1679,10 @@ class GroupchatControl(ChatControlBase):
if not iter_:
return
pixbuf = app.interface.get_avatar(gc_contact.avatar_sha, AvatarSize.ROSTER)
self.model[iter_][Column.AVATAR] = pixbuf or empty_pixbuf
surface = app.interface.get_avatar(
gc_contact.avatar_sha, AvatarSize.ROSTER, self.scale_factor)
image = Gtk.Image.new_from_surface(surface)
self.model[iter_][Column.AVATAR_IMG] = image
def draw_role(self, role):
role_iter = self.get_role_iter(role)

View File

@ -44,6 +44,7 @@ from gi.repository import Gtk
from gi.repository import GdkPixbuf
from gi.repository import GLib
from gi.repository import Gio
from gi.repository import Gdk
try:
from PIL import Image
@ -2449,10 +2450,16 @@ class Interface:
return sha
@staticmethod
def get_avatar(filename, size=None, publish=False):
def get_avatar(filename, size=None, scale=None, publish=False):
if filename is None or '':
return
if size is None and scale is not None:
raise ValueError
if scale is not None:
size = size * scale
if publish:
path = os.path.join(app.AVATAR_PATH, filename)
with open(path, 'rb') as file:
@ -2460,8 +2467,10 @@ class Interface:
return data
try:
sha = app.avatar_cache[filename][size]
return sha
pixbuf = app.avatar_cache[filename][size]
if scale is None:
return pixbuf
return Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale)
except KeyError:
pass
@ -2501,7 +2510,9 @@ class Interface:
app.avatar_cache[filename] = {}
app.avatar_cache[filename][size] = pixbuf
return pixbuf
if scale is None:
return pixbuf
return Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale)
def auto_join_bookmarks(self, account):
"""

View File

@ -680,12 +680,13 @@ class MessageWindow(object):
tab_img = ctrl.get_tab_image()
if tab_img:
if isinstance(tab_img, GdkPixbuf.Pixbuf):
status_img.set_from_pixbuf(tab_img)
elif tab_img.get_storage_type() == Gtk.ImageType.ANIMATION:
status_img.set_from_animation(tab_img.get_animation())
if isinstance(tab_img, Gtk.Image):
if tab_img.get_storage_type() == Gtk.ImageType.ANIMATION:
status_img.set_from_animation(tab_img.get_animation())
else:
status_img.set_from_pixbuf(tab_img.get_pixbuf())
else:
status_img.set_from_pixbuf(tab_img.get_pixbuf())
status_img.set_from_surface(tab_img)
self.show_icon()

View File

@ -131,11 +131,12 @@ class ProfileWindow:
self.dialog.destroy()
self.dialog = None
pixbuf = app.interface.get_avatar(sha, AvatarSize.VCARD)
scale = self.window.get_scale_factor()
surface = app.interface.get_avatar(sha, AvatarSize.VCARD, scale)
button = self.xml.get_object('PHOTO_button')
image = button.get_image()
image.set_from_pixbuf(pixbuf)
image.set_from_surface(surface)
button.show()
text_button = self.xml.get_object('NOPHOTO_button')
text_button.hide()

View File

@ -82,7 +82,7 @@ class Column(IntEnum):
ACTIVITY_PIXBUF = 6
TUNE_PIXBUF = 7
LOCATION_PIXBUF = 8
AVATAR_PIXBUF = 9 # avatar_pixbuf
AVATAR_IMG = 9 # avatar_sha
PADLOCK_PIXBUF = 10 # use for account row only
VISIBLE = 11
@ -1397,11 +1397,12 @@ class RosterWindow:
return
jid = self.model[iters[0]][Column.JID]
pixbuf = app.contacts.get_avatar(account, jid, size=AvatarSize.ROSTER)
if pixbuf is None:
pixbuf = empty_pixbuf
scale = self.window.get_scale_factor()
surface = app.contacts.get_avatar(
account, jid, AvatarSize.ROSTER, scale)
image = Gtk.Image.new_from_surface(surface)
for child_iter in iters:
self.model[child_iter][Column.AVATAR_PIXBUF] = pixbuf
self.model[child_iter][Column.AVATAR_IMG] = image
return False
def draw_completely(self, jid, account):
@ -2251,7 +2252,7 @@ class RosterWindow:
else:
# No need to redraw contacts if we're quitting
if child_iterA:
self.model[child_iterA][Column.AVATAR_PIXBUF] = empty_pixbuf
self.model[child_iterA][Column.AVATAR_IMG] = None
if account in app.con_types:
app.con_types[account] = None
for jid in list(app.contacts.get_jid_list(account)):
@ -4836,8 +4837,11 @@ class RosterWindow:
renderer.set_property('visible', False)
return
image = model[titer][Column.AVATAR_IMG]
surface = image.get_property('surface')
renderer.set_property('surface', surface)
# allocate space for the icon only if needed
if model[titer][Column.AVATAR_PIXBUF] or \
if model[titer][Column.AVATAR_IMG] or \
app.config.get('avatar_position_in_roster') == 'left':
renderer.set_property('visible', True)
if type_:
@ -4850,7 +4854,7 @@ class RosterWindow:
self._set_contact_row_background_color(renderer, jid, account)
else:
renderer.set_property('visible', False)
if model[titer][Column.AVATAR_PIXBUF] == empty_pixbuf and \
if model[titer][Column.AVATAR_IMG] is None and \
app.config.get('avatar_position_in_roster') != 'left':
renderer.set_property('visible', False)
@ -5561,7 +5565,8 @@ class RosterWindow:
def fill_column(self, col):
for rend in self.renderers_list:
col.pack_start(rend[1], rend[2])
col.add_attribute(rend[1], rend[3], rend[4])
if rend[0] != 'avatar':
col.add_attribute(rend[1], rend[3], rend[4])
col.set_cell_data_func(rend[1], rend[5], rend[6])
# set renderers propertys
for renderer in self.renderers_propertys.keys():
@ -5659,12 +5664,14 @@ class RosterWindow:
self.nb_ext_renderers = 0
# When we quit, rememver if we already saved config once
self.save_done = False
# [icon, name, type, jid, account, mood_pixbuf,
# activity_pixbuf, tune_pixbuf, location_pixbuf, avatar_pixbuf,
# [icon, name, type, jid, account, editable, mood_pixbuf,
# activity_pixbuf, tune_pixbuf, location_pixbuf, avatar_img,
# padlock_pixbuf, visible]
self.columns = [Gtk.Image, str, str, str, str,
GdkPixbuf.Pixbuf, GdkPixbuf.Pixbuf, GdkPixbuf.Pixbuf, GdkPixbuf.Pixbuf,
GdkPixbuf.Pixbuf, GdkPixbuf.Pixbuf, bool]
Gtk.Image, str, bool]
self.xml = gtkgui_helpers.get_gtk_builder('roster_window.ui')
self.window = self.xml.get_object('roster_window')
application.add_window(self.window)
@ -5819,7 +5826,7 @@ class RosterWindow:
def add_avatar_renderer():
self.renderers_list.append(('avatar', Gtk.CellRendererPixbuf(),
False, 'pixbuf', Column.AVATAR_PIXBUF,
False, None, Column.AVATAR_IMG,
self._fill_avatar_pixbuf_renderer, None))
if app.config.get('avatar_position_in_roster') == 'left':
@ -5908,6 +5915,8 @@ class RosterWindow:
app.config.get('trayicon') != 'always':
self.window.show_all()
self.scale_factor = self.window.get_scale_factor()
if not app.config.get_per('accounts') or \
app.config.get_per('accounts') == ['Local'] and not \
app.config.get_per('accounts', 'Local', 'active'):

View File

@ -244,9 +244,11 @@ class GCTooltip():
if contact.avatar_sha is not None:
app.log('avatar').debug(
'Load GCTooltip: %s %s', contact.name, contact.avatar_sha)
pixbuf = app.interface.get_avatar(contact.avatar_sha, AvatarSize.TOOLTIP)
if pixbuf is not None:
self.avatar.set_from_pixbuf(pixbuf)
scale = self.tooltip_grid.get_scale_factor()
surface = app.interface.get_avatar(
contact.avatar_sha, AvatarSize.TOOLTIP, scale)
if surface is not None:
self.avatar.set_from_surface(surface)
self.avatar.show()
self.fillelement.show()
@ -521,11 +523,12 @@ class RosterTooltip(Gtk.Window, StatusTable):
self._set_idle_time(contact)
# Avatar
pixbuf = app.contacts.get_avatar(
account, self.prim_contact.jid, AvatarSize.TOOLTIP)
if pixbuf is None:
scale = self.get_scale_factor()
surface = app.contacts.get_avatar(
account, self.prim_contact.jid, AvatarSize.TOOLTIP, scale)
if surface is None:
return
self.avatar.set_from_pixbuf(pixbuf)
self.avatar.set_from_surface(surface)
self.avatar.show()
# Sets the Widget that is at the bottom to expand.