Files
gajim-plugins/clients_icons/clients_icons.py
2019-06-22 13:24:33 +02:00

462 lines
17 KiB
Python

import logging
from pathlib import Path
from functools import partial
from gi.repository import Gtk
from gajim.roster_window import Column
from gajim.common import ged
from gajim.common import app
from gajim.common import caps_cache
from gajim.gtk.util import load_icon
from gajim.plugins import GajimPlugin
from gajim.plugins.plugins_i18n import _
from clients_icons import clients
from clients_icons.config_dialog import ClientsIconsConfigDialog
from nbxmpp import JID
log = logging.getLogger('gajim.p.client_icons')
class ClientsIconsPlugin(GajimPlugin):
def init(self):
self.description = _('Shows client icons in roster'
' and in groupchats.')
self.config_dialog = partial(ClientsIconsConfigDialog, self)
self.events_handlers = {
'caps-update': (
ged.POSTGUI, self._on_caps_update),
}
self.gui_extension_points = {
'groupchat_control': (
self.connect_with_groupchat_control,
self.disconnect_from_groupchat_control),
'roster_draw_contact': (
self.connect_with_roster_draw_contact,
None),
'roster_tooltip_populate': (
self.connect_with_roster_tooltip_populate,
None),
'gc_tooltip_populate': (
self.connect_with_gc_tooltip_populate,
None),
}
self.config_default_values = {
'show_in_roster': (True, ''),
'show_in_groupchats': (True, ''),
'show_in_tooltip': (True, ''),
'show_unknown_icon': (True, ''),
'pos_in_list': ('0', ''),
'show_facebook': (True, ''),
}
_icon_theme = Gtk.IconTheme.get_default()
if _icon_theme is not None:
_icon_theme.append_search_path(str(Path(__file__).parent))
@staticmethod
def get_client_identity_name(contact):
identities = contact.client_caps.get_cache_lookup_strategy()(
caps_cache.capscache).identities
if identities:
for entry in identities:
if entry['category'] == 'client':
return entry.get('name')
@staticmethod
def is_groupchat(contact):
if hasattr(contact, 'is_groupchat'):
return contact.is_groupchat
return False
def add_tooltip_row(self, tooltip, contact, tooltip_grid):
caps = contact.client_caps._node
caps_image, client_name = self.get_icon(caps, contact, tooltip_grid)
caps_image.set_halign(Gtk.Align.END)
# Fill clients grid
self.grid = Gtk.Grid()
self.grid.set_name('client_icons_grid')
self.grid.set_property('column-spacing', 5)
self.grid.attach(caps_image, 1, 1, 1, 1)
label_name = Gtk.Label()
label_name.set_halign(Gtk.Align.END)
label_name.set_valign(Gtk.Align.CENTER)
label_name.set_markup(client_name)
self.grid.attach(label_name, 2, 1, 1, 1)
self.grid.show_all()
# Set label
label = Gtk.Label()
label.set_name('client_icons_label')
label.set_halign(Gtk.Align.END)
label.set_valign(Gtk.Align.CENTER)
label.set_markup(_('Client:'))
label.show()
# Set client grid to tooltip
tooltip_grid.insert_next_to(tooltip.resource_label,
Gtk.PositionType.BOTTOM)
tooltip_grid.attach_next_to(label, tooltip.resource_label,
Gtk.PositionType.BOTTOM, 1, 1)
tooltip_grid.attach_next_to(self.grid, label,
Gtk.PositionType.RIGHT, 1, 1)
def connect_with_gc_tooltip_populate(self, tooltip, contact, tooltip_grid):
if not self.config['show_in_tooltip']:
return
# Check if clients info already attached to tooltip
node = contact.client_caps._node
image, client_name = self.get_icon(node, contact, tooltip_grid)
label = Gtk.Label(label=client_name)
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
box.add(image)
box.add(label)
box.show_all()
tooltip_grid.insert_next_to(tooltip._ui.affiliation,
Gtk.PositionType.BOTTOM)
tooltip_grid.attach_next_to(box, tooltip._ui.affiliation,
Gtk.PositionType.BOTTOM, 1, 1)
def connect_with_roster_tooltip_populate(self, tooltip, contacts,
tooltip_grid):
if not self.config['show_in_tooltip']:
return
if len(contacts) == 1 and contacts[0].jid in app.get_our_jids():
return
if self.is_groupchat(contacts[0]):
return
# Put contacts in dict, where key is priority
num_resources = 0
contacts_dict = {}
for contact in contacts:
if contact.show == 'offline':
return
if contact.resource:
num_resources += 1
if int(contact.priority) in contacts_dict:
contacts_dict[int(contact.priority)].append(contact)
else:
contacts_dict[int(contact.priority)] = [contact]
contact_keys = sorted(contacts_dict.keys())
contact_keys.reverse()
# Fill clients grid
grid = Gtk.Grid()
grid.insert_row(0)
grid.insert_row(0)
grid.insert_column(0)
grid.set_property('column-spacing', 2)
vcard_current_row = 0
for priority in contact_keys:
for acontact in contacts_dict[priority]:
caps = acontact.client_caps._node
caps_image, client_name = self.get_icon(
caps, acontact, tooltip_grid)
caps_image.set_valign(Gtk.Align.START)
grid.attach(caps_image, 1, vcard_current_row, 1, 1)
label = Gtk.Label(label=client_name)
label.set_valign(Gtk.Align.START)
label.set_halign(Gtk.Align.START)
label.set_xalign(0)
grid.attach(label, 2, vcard_current_row, 1, 1)
vcard_current_row += 1
grid.show_all()
grid.set_valign(Gtk.Align.START)
# Set label
label = Gtk.Label()
label.set_halign(Gtk.Align.END)
label.set_valign(Gtk.Align.START)
if num_resources > 1:
label.set_text(_('Clients:'))
else:
label.set_text(_('Client:'))
label.show()
# Set clients grid to tooltip
tooltip_grid.insert_next_to(tooltip._ui.resource_label,
Gtk.PositionType.BOTTOM)
tooltip_grid.attach_next_to(label, tooltip._ui.resource_label,
Gtk.PositionType.BOTTOM, 1, 1)
tooltip_grid.attach_next_to(grid, label,
Gtk.PositionType.RIGHT, 1, 1)
def get_icon(self, node, contact, widget):
identity_name = self.get_client_identity_name(contact)
client_name, icon_name = clients.get_data(identity_name, node)
surface = load_icon(icon_name, widget=widget)
return Gtk.Image.new_from_surface(surface), client_name
def connect_with_roster_draw_contact(self, roster, jid, account, contact):
if not self.active:
return
if not self.config['show_in_roster']:
return
if self.is_groupchat(contact):
return
child_iters = roster._get_contact_iter(
jid, account, contact, roster.model)
if not child_iters:
return
for iter_ in child_iters:
if roster.model[iter_][self.renderer_num] is None:
node = contact.client_caps._node
self.set_icon(
roster.model, iter_, self.renderer_num, node, contact)
def connect_with_groupchat_control(self, chat_control):
chat_control.nb_ext_renderers += 1
chat_control.columns += [str]
self.groupchats_tree_is_transformed = True
self.chat_control = chat_control
col = Gtk.TreeViewColumn()
self.muc_renderer_num = 4 + chat_control.nb_ext_renderers
client_icon_rend = (
'client_icon', Gtk.CellRendererPixbuf(), False,
'icon_name', self.muc_renderer_num,
self.tree_cell_data_func, chat_control)
# Remove old column
chat_control.list_treeview.remove_column(
chat_control.list_treeview.get_column(0))
# Add new renderer in renderers list
position_list = ['name', 'avatar']
position = position_list[int(self.config['pos_in_list'])]
for renderer in chat_control.renderers_list:
if renderer[0] == position:
break
num = chat_control.renderers_list.index(renderer)
chat_control.renderers_list.insert(num, client_icon_rend)
# Fill and append column
chat_control.fill_column(col)
chat_control.list_treeview.insert_column(col, 0)
chat_control.model = Gtk.TreeStore(*chat_control.columns)
chat_control.model.set_sort_func(1, chat_control.tree_compare_iters)
chat_control.model.set_sort_column_id(1, Gtk.SortType.ASCENDING)
chat_control.list_treeview.set_model(chat_control.model)
# Draw roster
for nick in app.contacts.get_nick_list(
chat_control.account, chat_control.room_jid):
gc_contact = app.contacts.get_gc_contact(
chat_control.account, chat_control.room_jid, nick)
iter_ = chat_control.add_contact_to_roster(nick)
if not self.config['show_in_groupchats']:
continue
caps = gc_contact.client_caps._node
self.set_icon(
chat_control.model, iter_,
self.muc_renderer_num, caps, gc_contact)
chat_control.draw_all_roles()
# Recalculate column width for ellipsizing
chat_control.list_treeview.columns_autosize()
def disconnect_from_groupchat_control(self, gc_control):
gc_control.nb_ext_renderers -= 1
col = gc_control.list_treeview.get_column(0)
gc_control.list_treeview.remove_column(col)
col = Gtk.TreeViewColumn()
for renderer in gc_control.renderers_list:
if renderer[0] == 'client_icon':
gc_control.renderers_list.remove(renderer)
break
gc_control.fill_column(col)
gc_control.list_treeview.insert_column(col, 0)
gc_control.columns = gc_control.columns[:self.muc_renderer_num] + \
gc_control.columns[self.muc_renderer_num + 1:]
gc_control.model = Gtk.TreeStore(*gc_control.columns)
gc_control.model.set_sort_func(1, gc_control.tree_compare_iters)
gc_control.model.set_sort_column_id(1, Gtk.SortType.ASCENDING)
gc_control.list_treeview.set_model(gc_control.model)
gc_control.draw_roster()
def activate(self):
self.active = None
roster = app.interface.roster
col = Gtk.TreeViewColumn()
roster.nb_ext_renderers += 1
self.renderer_num = 11 + roster.nb_ext_renderers
self.renderer = Gtk.CellRendererPixbuf()
client_icon_rend = (
'client_icon', self.renderer, False,
'icon_name', self.renderer_num,
self._roster_icon_renderer, self.renderer_num)
# Remove old column
roster.tree.remove_column(roster.tree.get_column(0))
# Add new renderer in renderers list
position_list = ['name', 'avatar']
position = position_list[int(self.config['pos_in_list'])]
for renderer in roster.renderers_list:
if renderer[0] == position:
break
num = roster.renderers_list.index(renderer)
roster.renderers_list.insert(num, client_icon_rend)
# Fill and append column
roster.fill_column(col)
roster.tree.insert_column(col, 0)
# Redraw roster
roster.columns += [str]
self.active = True
roster.setup_and_draw_roster()
def _roster_icon_renderer(self, column, renderer, model, titer, data=None):
try:
type_ = model[titer][Column.TYPE]
except TypeError:
return
# Allocate space for the icon only if needed
if model[titer][data] is None:
renderer.set_property('visible', False)
else:
renderer.set_property('visible', True)
if type_ == 'account':
app.interface.roster._set_account_row_background_color(renderer)
renderer.set_property('xalign', 1)
elif type_:
if (not model[titer][Column.JID] or
not model[titer][Column.ACCOUNT]):
# This can append at the moment we add the row
return
jid = model[titer][Column.JID]
account = model[titer][Column.ACCOUNT]
app.interface.roster._set_contact_row_background_color(
renderer, jid, account)
def deactivate(self):
self.active = None
roster = app.interface.roster
roster.nb_ext_renderers -= 1
col = roster.tree.get_column(0)
roster.tree.remove_column(col)
col = Gtk.TreeViewColumn()
for renderer in roster.renderers_list:
if renderer[0] == 'client_icon':
roster.renderers_list.remove(renderer)
break
roster.fill_column(col)
roster.tree.insert_column(col, 0)
roster.columns = roster.columns[:self.renderer_num] + roster.columns[
self.renderer_num + 1:]
roster.setup_and_draw_roster()
def _on_caps_update(self, event):
# Zeroconf
if event.conn.name == 'Local':
return
contact = self._get_contact_or_gc_contact_for_jid(
event.conn.name, event.fjid)
if contact is None:
return
if contact.is_gc_contact:
self._draw_gc_contact(event, contact)
else:
self._draw_roster_contact(event, contact)
def _draw_roster_contact(self, event, contact):
if not self.config['show_in_roster']:
return
if contact.is_groupchat:
return
roster = app.interface.roster
iters = roster._get_contact_iter(
event.jid, event.conn.name, contact, roster.model)
iter_ = iters[0]
# Highest contact changed
caps = contact.client_caps._node
if not caps:
return
if roster.model[iter_][self.renderer_num] is not None:
self.set_icon(
roster.model, iter_, self.renderer_num, caps, contact)
return
for iter_ in iters:
self.set_icon(
roster.model, iter_, self.renderer_num, caps, contact)
def _draw_gc_contact(self, event, contact):
if not self.config['show_in_groupchats']:
return
control = app.interface.msg_win_mgr.get_gc_control(
contact.room_jid, event.conn.name)
if control is None:
return
iter_ = control.get_contact_iter(contact.name)
if control.model[iter_][self.muc_renderer_num] is not None:
return
caps = contact.client_caps._node
if not caps:
return
self.set_icon(
control.model, iter_, self.muc_renderer_num, caps, contact)
def _get_contact_or_gc_contact_for_jid(self, account, fjid):
contact = app.contacts.get_contact_from_full_jid(account, fjid)
if contact is None:
fjid = JID(fjid)
room_jid, resource = fjid.getStripped(), fjid.getResource()
contact = app.contacts.get_gc_contact(account, room_jid, resource)
return contact
def set_icon(self, model, iter_, pos, node, contact):
identity_name = self.get_client_identity_name(contact)
_client_name, icon_name = clients.get_data(identity_name, node)
if 'unknown' in icon_name:
if node is not None:
log.warning('Unknown client: %s %s', identity_name, node)
if not self.config['show_unknown_icon']:
model[iter_][pos] = None
return
model[iter_][pos] = icon_name
def tree_cell_data_func(self, column, renderer, model, iter_, control):
if not model.iter_parent(iter_):
renderer.set_property('visible', False)
return
if model[iter_][self.muc_renderer_num]:
renderer.set_property('visible', True)
contact = app.contacts.get_gc_contact(
control.account, control.room_jid, model[iter_][1])
if not contact:
return
bgcolor = app.config.get_per(
'themes', app.config.get('roster_theme'), 'contactbgcolor')
if bgcolor:
renderer.set_property('cell-background', bgcolor)
else:
renderer.set_property('cell-background', None)
renderer.set_property('width', 16)