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 jid = self.contact.jid
if app.config.get('show_avatar_in_tabs'): if app.config.get('show_avatar_in_tabs'):
avatar_pixbuf = app.contacts.get_avatar(self.account, jid, size=16) scale = self.parent_win.window.get_scale_factor()
if avatar_pixbuf is not None: surface = app.contacts.get_avatar(
return avatar_pixbuf self.account, jid, AvatarSize.TAB, scale)
if surface is not None:
return surface
if count_unread: if count_unread:
num_unread = len(app.events.get_events(self.account, jid, 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'): if not app.config.get('show_avatar_in_chat'):
return return
scale = self.parent_win.window.get_scale_factor()
if self.TYPE_ID == message_control.TYPE_CHAT: if self.TYPE_ID == message_control.TYPE_CHAT:
pixbuf = app.contacts.get_avatar( surface = app.contacts.get_avatar(
self.account, self.contact.jid, AvatarSize.CHAT) self.account, self.contact.jid, AvatarSize.CHAT, scale)
else: else:
pixbuf = app.interface.get_avatar( surface = app.interface.get_avatar(
self.gc_contact.avatar_sha, AvatarSize.CHAT) self.gc_contact.avatar_sha, AvatarSize.CHAT, scale)
image = self.xml.get_object('avatar_image') 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) image.set_from_icon_name('avatar-default', Gtk.IconSize.DIALOG)
else: else:
image.set_from_pixbuf(pixbuf) image.set_from_surface(surface)
def _nec_update_avatar(self, obj): def _nec_update_avatar(self, obj):
if obj.account != self.account: if obj.account != self.account:

View File

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

View File

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

View File

@ -77,25 +77,38 @@ class Column(IntEnum):
NICK = 1 # contact nickame or ROLE name NICK = 1 # contact nickame or ROLE name
TYPE = 2 # type of the row ('contact' or 'role') TYPE = 2 # type of the row ('contact' or 'role')
TEXT = 3 # text shown in the cellrenderer 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 = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 1, 1)
empty_pixbuf.fill(0xffffff00) 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 # cell data func is global, because we don't want it to keep
# reference to GroupchatControl instance (self) # reference to GroupchatControl instance (self)
theme = app.config.get('roster_theme') theme = app.config.get('roster_theme')
# allocate space for avatar only if needed # allocate space for avatar only if needed
parent_iter = model.iter_parent(iter_) parent_iter = model.iter_parent(iter_)
if isinstance(renderer, Gtk.CellRendererPixbuf): 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') avatar_position = app.config.get('avatar_position_in_roster')
if avatar_position == 'right': if avatar_position == 'right':
renderer.set_property('xalign', 1) # align pixbuf to the right renderer.set_property('xalign', 1) # align pixbuf to the right
else: else:
renderer.set_property('xalign', 0.5) renderer.set_property('xalign', 0.5)
if parent_iter and (model[iter_][Column.AVATAR] or avatar_position == \ if parent_iter:
'left'):
renderer.set_property('visible', True) renderer.set_property('visible', True)
renderer.set_property('width', AvatarSize.ROSTER) renderer.set_property('width', AvatarSize.ROSTER)
else: else:
@ -254,6 +267,16 @@ class PrivateChatControl(ChatControl):
return return
self.show_avatar() 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): def update_contact(self):
self.contact = self.gc_contact.as_contact() self.contact = self.gc_contact.as_contact()
@ -308,6 +331,9 @@ class GroupchatControl(ChatControlBase):
# Tooltip Window and Actions have to be created with parent # Tooltip Window and Actions have to be created with parent
self.set_tooltip() self.set_tooltip()
self.add_actions() 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') widget = self.xml.get_object('list_treeview')
id_ = widget.connect('row_expanded', self.on_list_treeview_row_expanded) id_ = widget.connect('row_expanded', self.on_list_treeview_row_expanded)
@ -392,7 +418,7 @@ class GroupchatControl(ChatControlBase):
self.hpaned.set_position(hpaned_position) self.hpaned.set_position(hpaned_position)
#status_image, shown_nick, type, nickname, avatar #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 = Gtk.TreeStore(*self.columns)
self.model.set_sort_func(Column.NICK, self.tree_compare_iters) self.model.set_sort_func(Column.NICK, self.tree_compare_iters)
self.model.set_sort_column_id(Column.NICK, Gtk.SortType.ASCENDING) self.model.set_sort_column_id(Column.NICK, Gtk.SortType.ASCENDING)
@ -415,20 +441,20 @@ class GroupchatControl(ChatControlBase):
self.renderers_list += ( self.renderers_list += (
# status img # status img
('icon', renderer_image, False, ('icon', renderer_image, False,
'image', Column.IMG, tree_cell_data_func, self.list_treeview), 'image', Column.IMG, tree_cell_data_func, None),
# contact name # contact name
('name', renderer_text, True, ('name', renderer_text, True,
'markup', Column.TEXT, tree_cell_data_func, self.list_treeview)) 'markup', Column.TEXT, tree_cell_data_func, None))
# avatar img # avatar img
avater_renderer = ('avatar', Gtk.CellRendererPixbuf(), avatar_renderer = ('avatar', Gtk.CellRendererPixbuf(),
False, 'pixbuf', Column.AVATAR, False, None, Column.AVATAR_IMG,
tree_cell_data_func, self.list_treeview) tree_cell_data_func, None)
if app.config.get('avatar_position_in_roster') == 'right': if app.config.get('avatar_position_in_roster') == 'right':
self.renderers_list.append(avater_renderer) self.renderers_list.append(avatar_renderer)
else: else:
self.renderers_list.insert(0, avater_renderer) self.renderers_list.insert(0, avatar_renderer)
self.fill_column(column) self.fill_column(column)
self.list_treeview.append_column(column) self.list_treeview.append_column(column)
@ -782,7 +808,8 @@ class GroupchatControl(ChatControlBase):
def fill_column(self, col): def fill_column(self, col):
for rend in self.renderers_list: for rend in self.renderers_list:
col.pack_start(rend[1], rend[2]) 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]) col.set_cell_data_func(rend[1], rend[5], rend[6])
# set renderers propertys # set renderers propertys
for renderer in self.renderers_propertys.keys(): for renderer in self.renderers_propertys.keys():
@ -1652,8 +1679,10 @@ class GroupchatControl(ChatControlBase):
if not iter_: if not iter_:
return return
pixbuf = app.interface.get_avatar(gc_contact.avatar_sha, AvatarSize.ROSTER) surface = app.interface.get_avatar(
self.model[iter_][Column.AVATAR] = pixbuf or empty_pixbuf 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): def draw_role(self, role):
role_iter = self.get_role_iter(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 GdkPixbuf
from gi.repository import GLib from gi.repository import GLib
from gi.repository import Gio from gi.repository import Gio
from gi.repository import Gdk
try: try:
from PIL import Image from PIL import Image
@ -2449,10 +2450,16 @@ class Interface:
return sha return sha
@staticmethod @staticmethod
def get_avatar(filename, size=None, publish=False): def get_avatar(filename, size=None, scale=None, publish=False):
if filename is None or '': if filename is None or '':
return return
if size is None and scale is not None:
raise ValueError
if scale is not None:
size = size * scale
if publish: if publish:
path = os.path.join(app.AVATAR_PATH, filename) path = os.path.join(app.AVATAR_PATH, filename)
with open(path, 'rb') as file: with open(path, 'rb') as file:
@ -2460,8 +2467,10 @@ class Interface:
return data return data
try: try:
sha = app.avatar_cache[filename][size] pixbuf = app.avatar_cache[filename][size]
return sha if scale is None:
return pixbuf
return Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale)
except KeyError: except KeyError:
pass pass
@ -2501,7 +2510,9 @@ class Interface:
app.avatar_cache[filename] = {} app.avatar_cache[filename] = {}
app.avatar_cache[filename][size] = pixbuf 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): def auto_join_bookmarks(self, account):
""" """

View File

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

View File

@ -131,11 +131,12 @@ class ProfileWindow:
self.dialog.destroy() self.dialog.destroy()
self.dialog = None 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') button = self.xml.get_object('PHOTO_button')
image = button.get_image() image = button.get_image()
image.set_from_pixbuf(pixbuf) image.set_from_surface(surface)
button.show() button.show()
text_button = self.xml.get_object('NOPHOTO_button') text_button = self.xml.get_object('NOPHOTO_button')
text_button.hide() text_button.hide()

View File

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

View File

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