Roster: Use icons from IconTheme for Roster

With this Gajim loses the ability to draw animated GIFs in the Roster

GIFs have numerous drawbacks:

- We cant add them to the IconTheme, which means we need a different interface to load them
- The IconTheme scales all icons we load for the current scale (HiDPI)
- The animation causes many updates to the Roster, and causes high cpu usage

Fixes #8814, #8655
This commit is contained in:
Philipp Hörist 2018-11-18 14:50:03 +01:00
parent 8be5562b92
commit acc89ad622
4 changed files with 59 additions and 191 deletions

View File

@ -1,155 +0,0 @@
# Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
# Copyright (C) 2005 Vincent Hanquez <tab AT snarc.org>
# Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
# Copyright (C) 2006 Travis Shirk <travis AT pobox.com>
#
# This file is part of Gajim.
#
# Gajim 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; version 3 only.
#
# Gajim 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 Gajim. If not, see <http://www.gnu.org/licenses/>.
from gi.repository import GLib
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GObject
class CellRendererImage(Gtk.CellRendererPixbuf):
__gproperties__ = {
'image': (GObject.TYPE_OBJECT, 'Image',
'Image', GObject.ParamFlags.READWRITE),
}
def __init__(self, col_index, tv_index):
super(CellRendererImage, self).__init__()
self.image = None
self.col_index = col_index
self.tv_index = tv_index
self.iters = {}
def do_set_property(self, pspec, value):
setattr(self, pspec.name, value)
def do_get_property(self, pspec):
return getattr(self, pspec.name)
def do_activate(self, *args, **kwargs):
"""Renderers cannot be activated; always return True."""
return True
def do_editing_started(self, *args, **kwargs):
"""Renderers cannot be edited; always return None."""
return None
def func(self, model, path, iter_, image_tree):
image, tree = image_tree
if model.get_value(iter_, self.tv_index) != image:
return
self.redraw = 1
col = tree.get_column(self.col_index)
cell_area = tree.get_cell_area(path, col)
tree.queue_draw_area(cell_area.x, cell_area.y, cell_area.width,
cell_area.height)
def animation_timeout(self, tree, image):
if image.get_storage_type() != Gtk.ImageType.ANIMATION:
return
self.redraw = 0
iter_ = self.iters[image]
timeval = GLib.TimeVal()
timeval.tv_sec = GLib.get_monotonic_time() / 1000000
iter_.advance(timeval)
model = tree.get_model()
if model:
model.foreach(self.func, (image, tree))
if self.redraw:
GLib.timeout_add(iter_.get_delay_time(),
self.animation_timeout, tree, image)
elif image in self.iters:
del self.iters[image]
def do_render(self, ctx, widget, background_area, cell_area, flags):
if not self.image:
return
if self.image.get_storage_type() == Gtk.ImageType.ANIMATION:
if self.image not in self.iters:
if not isinstance(widget, Gtk.TreeView):
return
animation = self.image.get_animation()
timeval = GLib.TimeVal()
timeval.tv_sec = GLib.get_monotonic_time() / 1000000
iter_ = animation.get_iter(timeval)
self.iters[self.image] = iter_
GLib.timeout_add(iter_.get_delay_time(), self.animation_timeout,
widget, self.image)
pix = self.iters[self.image].get_pixbuf()
elif self.image.get_storage_type() == Gtk.ImageType.PIXBUF:
pix = self.image.get_pixbuf()
else:
return
calc_width = self.get_property('xpad') * 2 + pix.get_width()
calc_height = self.get_property('ypad') * 2 + pix.get_height()
x_pos = cell_area.x + self.get_property('xalign') * \
(cell_area.width - calc_width - self.get_property('xpad'))
y_pos = cell_area.y + self.get_property('yalign') * \
(cell_area.height - calc_height - self.get_property('ypad'))
Gdk.cairo_set_source_pixbuf(ctx, pix, x_pos, y_pos)
ctx.paint()
def do_get_preferred_height(self, widget):
"""
Return the height we need for this cell.
Each cell is drawn individually and is only as wide as it needs
to be, we let the TreeViewColumn take care of making them all
line up.
"""
if not self.image:
return 0, 0
if self.image.get_storage_type() == Gtk.ImageType.ANIMATION:
animation = self.image.get_animation()
timeval = GLib.TimeVal()
timeval.tv_sec = GLib.get_monotonic_time() / 1000000
pix = animation.get_iter(timeval).get_pixbuf()
elif self.image.get_storage_type() == Gtk.ImageType.PIXBUF:
pix = self.image.get_pixbuf()
else:
return 0, 0, 0, 0
calc_height = self.get_property('ypad') * 2 + pix.get_height()
return calc_height, calc_height
def do_get_preferred_width(self, widget):
"""
Return the width we need for this cell.
Each cell is drawn individually and is only as wide as it needs
to be, we let the TreeViewColumn take care of making them all
line up.
"""
if not self.image:
return 0, 0
if self.image.get_storage_type() == Gtk.ImageType.ANIMATION:
animation = self.image.get_animation()
timeval = GLib.TimeVal()
timeval.tv_sec = GLib.get_monotonic_time() / 1000000
pix = animation.get_iter(timeval).get_pixbuf()
elif self.image.get_storage_type() == Gtk.ImageType.PIXBUF:
pix = self.image.get_pixbuf()
else:
return 0, 0, 0, 0
calc_width = self.get_property('xpad') * 2 + pix.get_width()
return calc_width, calc_width

View File

@ -824,8 +824,8 @@ def get_icon_name_to_show(contact, account=None):
return 'event'
if account and contact.jid in app.gc_connected[account]:
if app.gc_connected[account][contact.jid]:
return 'muc_active'
return 'muc_inactive'
return 'muc-active'
return 'muc-inactive'
if contact.jid.find('@') <= 0: # if not '@' or '@' starts the jid ==> agent
return contact.show
if contact.sub in ('both', 'to'):

View File

@ -25,6 +25,7 @@ import xml.etree.ElementTree as ET
from gi.repository import Gdk
from gi.repository import Gtk
from gi.repository import GLib
import cairo
from gajim.common import app
from gajim.common import configpaths
@ -284,3 +285,25 @@ def get_monitor_scale_factor() -> int:
log.warning('Could not determine scale factor')
return 1
return monitor.get_scale_factor()
def get_metacontact_surface(icon_name, expanded, scale):
icon_size = 16
state_surface = _icon_theme.load_surface(
icon_name, icon_size, scale, None, 0)
if 'event' in icon_name:
return state_surface
if expanded:
icon = get_icon_name('opened')
expanded_surface = _icon_theme.load_surface(
icon, icon_size, scale, None, 0)
else:
icon = get_icon_name('closed')
expanded_surface = _icon_theme.load_surface(
icon, icon_size, scale, None, 0)
ctx = cairo.Context(state_surface)
ctx.rectangle(0, 0, icon_size, icon_size)
ctx.set_source_surface(expanded_surface)
ctx.fill()
return state_surface

View File

@ -48,7 +48,6 @@ from gajim import dialogs
from gajim import vcard
from gajim import gtkgui_helpers
from gajim import gui_menu_builder
from gajim import cell_renderer_image
from gajim import message_control
from gajim.common import app
@ -84,6 +83,7 @@ from gajim.gtk.adhoc_commands import CommandWindow
from gajim.gtk.util import get_icon_name
from gajim.gtk.util import resize_window
from gajim.gtk.util import move_window
from gajim.gtk.util import get_metacontact_surface
log = logging.getLogger('gajim.roster')
@ -282,8 +282,7 @@ class RosterWindow:
if self.regroup:
# Merged accounts view
show = helpers.get_global_show()
it = self.model.append(None, [
app.interface.jabber_state_images['16'][show],
it = self.model.append(None, [get_icon_name(show),
_('Merged accounts'), 'account', '', 'all', None, None, None,
None, None, None, True] + [None] * self.nb_ext_renderers)
self._iters['MERGED']['account'] = it
@ -295,8 +294,7 @@ class RosterWindow:
if app.account_is_securely_connected(account):
tls_pixbuf = 'changes-prevent'
it = self.model.append(None, [
app.interface.jabber_state_images['16'][show],
it = self.model.append(None, [get_icon_name(show),
GLib.markup_escape_text(account), 'account', our_jid,
account, None, None, None, None, None, tls_pixbuf, True] +
[None] * self.nb_ext_renderers)
@ -356,7 +354,7 @@ class RosterWindow:
else:
iter_parent = self._get_account_iter(account, self.model)
iter_group = self.model.append(iter_parent,
[app.interface.jabber_state_images['16']['closed'],
[get_icon_name('closed'),
GLib.markup_escape_text(group), 'group', group, account, None,
None, None, None, None, None, False] + [None] * self.nb_ext_renderers)
self.draw_group(group, account)
@ -1266,7 +1264,7 @@ class RosterWindow:
# look if another resource has awaiting events
for c in contact_instances:
c_icon_name = helpers.get_icon_name_to_show(c, account)
if c_icon_name in ('event', 'muc_active', 'muc_inactive'):
if c_icon_name in ('event', 'muc-active', 'muc-inactive'):
icon_name = c_icon_name
break
@ -1303,32 +1301,27 @@ class RosterWindow:
iterC = self.model.iter_next(iterC)
if self.tree.row_expanded(path):
state_images = self.get_appropriate_state_images(
jid, size='opened',
icon_name=icon_name)
icon_name += ':opened'
else:
state_images = self.get_appropriate_state_images(
jid, size='closed',
icon_name=icon_name)
icon_name += ':closed'
# Expand/collapse icon might differ per iter
# (group)
img = state_images[icon_name]
self.model[child_iter][Column.IMG] = img
theme_icon = get_icon_name(icon_name)
self.model[child_iter][Column.IMG] = theme_icon
self.model[child_iter][Column.NAME] = name
#TODO: compute visible
visible = True
self.model[child_iter][Column.VISIBLE] = visible
else:
# A normal contact or little brother
state_images = self.get_appropriate_state_images(jid,
icon_name=icon_name)
transport = app.get_transport_name_from_jid(jid)
if transport == 'jabber':
transport = None
theme_icon = get_icon_name(icon_name, transport=transport)
visible = self.contact_is_visible(contact, account)
# All iters have the same icon (no expand/collapse)
img = state_images[icon_name]
for child_iter in child_iters:
self.model[child_iter][Column.IMG] = img
self.model[child_iter][Column.IMG] = theme_icon
self.model[child_iter][Column.NAME] = name
self.model[child_iter][Column.VISIBLE] = visible
if visible:
@ -2095,8 +2088,7 @@ class RosterWindow:
def set_state(self, account, state):
child_iterA = self._get_account_iter(account, self.model)
if child_iterA:
self.model[child_iterA][0] = \
app.interface.jabber_state_images['16'][state]
self.model[child_iterA][0] = get_icon_name(state)
if app.interface.systray_enabled:
app.interface.systray.change_status(state)
@ -3861,8 +3853,7 @@ class RosterWindow:
type_ = model[titer][Column.TYPE]
if type_ == 'group':
group = model[titer][Column.JID]
child_model[child_iter][Column.IMG] = \
app.interface.jabber_state_images['16']['opened']
child_model[child_iter][Column.IMG] = get_icon_name('opened')
if self.rfilter_enabled:
return
for account in accounts:
@ -3924,8 +3915,7 @@ class RosterWindow:
type_ = model[titer][Column.TYPE]
if type_ == 'group':
child_model[child_iter][Column.IMG] = app.interface.\
jabber_state_images['16']['closed']
child_model[child_iter][Column.IMG] = get_icon_name('closed')
if self.rfilter_enabled:
return
group = model[titer][Column.JID]
@ -4647,8 +4637,7 @@ class RosterWindow:
show = app.SHOW_LIST[status]
else: # accounts merged
show = helpers.get_global_show()
self.model[child_iterA][Column.IMG] = app.interface.jabber_state_images[
'16'][show]
self.model[child_iterA][Column.IMG] = get_icon_name(show)
################################################################################
### Style and theme related methods
@ -4714,6 +4703,17 @@ class RosterWindow:
"""
When a row is added, set properties for icon renderer
"""
icon_name = model[titer][Column.IMG]
if ':' in icon_name:
icon_name, expanded = icon_name.split(':')
surface = get_metacontact_surface(
icon_name, expanded == 'opened', self.scale_factor)
renderer.set_property('icon_name', None)
renderer.set_property('surface', surface)
else:
renderer.set_property('surface', None)
renderer.set_property('icon_name', icon_name)
try:
type_ = model[titer][Column.TYPE]
except TypeError:
@ -4727,7 +4727,7 @@ class RosterWindow:
if model[parent_iter][Column.TYPE] == 'group':
renderer.set_property('xalign', 0.4)
else:
renderer.set_property('xalign', 0.2)
renderer.set_property('xalign', 0.6)
elif type_:
# prevent type_ = None, see http://trac.gajim.org/ticket/2534
if not model[titer][Column.JID] or not model[titer][Column.ACCOUNT]:
@ -5652,7 +5652,7 @@ class RosterWindow:
# [icon, name, type, jid, account, editable, mood_pixbuf,
# activity_pixbuf, TUNE_ICON, LOCATION_ICON, avatar_img,
# padlock_pixbuf, visible]
self.columns = [Gtk.Image, str, str, str, str,
self.columns = [str, str, str, str, str,
GdkPixbuf.Pixbuf, GdkPixbuf.Pixbuf, str, str,
Gtk.Image, str, bool]
@ -5773,8 +5773,8 @@ class RosterWindow:
add_avatar_renderer()
self.renderers_list += (
('icon', cell_renderer_image.CellRendererImage(0, 0), False,
'image', Column.IMG, self._iconCellDataFunc, None),
('icon', Gtk.CellRendererPixbuf(), False,
'icon_name', Column.IMG, self._iconCellDataFunc, None),
('name', renderer_text, True,
'markup', Column.NAME, self._nameCellDataFunc, None),