From 232dc1dda019f0a7ea35a4e583e8c80b7d785394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Thu, 19 Oct 2017 21:12:27 +0200 Subject: [PATCH] Scale Avatars for HiDPI Screens --- gajim/chat_control.py | 21 ++++++++------ gajim/common/const.py | 1 + gajim/common/contacts.py | 13 +++++---- gajim/groupchat_control.py | 59 ++++++++++++++++++++++++++++---------- gajim/gui_interface.py | 19 +++++++++--- gajim/message_window.py | 11 +++---- gajim/profile_window.py | 5 ++-- gajim/roster_window.py | 35 +++++++++++++--------- gajim/tooltips.py | 17 ++++++----- 9 files changed, 120 insertions(+), 61 deletions(-) diff --git a/gajim/chat_control.py b/gajim/chat_control.py index 3e60fbf2d..9e001f7ad 100644 --- a/gajim/chat_control.py +++ b/gajim/chat_control.py @@ -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: diff --git a/gajim/common/const.py b/gajim/common/const.py index 5f48810d6..fab57d935 100644 --- a/gajim/common/const.py +++ b/gajim/common/const.py @@ -29,6 +29,7 @@ class OptionType(IntEnum): DIALOG = 4 class AvatarSize(IntEnum): + TAB = 16 ROSTER = 32 CHAT = 48 NOTIFICATION = 48 diff --git a/gajim/common/contacts.py b/gajim/common/contacts.py index fc8dcdd7f..8452f9c2a 100644 --- a/gajim/common/contacts.py +++ b/gajim/common/contacts.py @@ -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 diff --git a/gajim/groupchat_control.py b/gajim/groupchat_control.py index c1aeb87d8..45b25c9d9 100644 --- a/gajim/groupchat_control.py +++ b/gajim/groupchat_control.py @@ -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) diff --git a/gajim/gui_interface.py b/gajim/gui_interface.py index 8bb11fdd5..3736fb4c4 100644 --- a/gajim/gui_interface.py +++ b/gajim/gui_interface.py @@ -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): """ diff --git a/gajim/message_window.py b/gajim/message_window.py index 53db62741..545612cd6 100644 --- a/gajim/message_window.py +++ b/gajim/message_window.py @@ -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() diff --git a/gajim/profile_window.py b/gajim/profile_window.py index 2bf9bbfa9..0babe7efa 100644 --- a/gajim/profile_window.py +++ b/gajim/profile_window.py @@ -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() diff --git a/gajim/roster_window.py b/gajim/roster_window.py index 20ec90497..483cf600b 100644 --- a/gajim/roster_window.py +++ b/gajim/roster_window.py @@ -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'): diff --git a/gajim/tooltips.py b/gajim/tooltips.py index ed0823c8c..71d41d81c 100644 --- a/gajim/tooltips.py +++ b/gajim/tooltips.py @@ -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.