Refactor UserActivity

- Use IconTheme for mood icons
- Simplify modules because nbxmpp handles more stuff
This commit is contained in:
Philipp Hörist 2019-01-31 00:02:25 +01:00
parent be95b04007
commit b9b9dae6a0
91 changed files with 128 additions and 173 deletions

View File

@ -60,6 +60,8 @@ from gajim.gtk.util import get_icon_name
from gajim.gtk.util import get_cursor from gajim.gtk.util import get_cursor
from gajim.gtk.util import ensure_proper_control from gajim.gtk.util import ensure_proper_control
from gajim.gtk.util import format_mood from gajim.gtk.util import format_mood
from gajim.gtk.util import format_activity
from gajim.gtk.util import get_activity_icon_name
from gajim.command_system.implementation.hosts import ChatCommands from gajim.command_system.implementation.hosts import ChatCommands
from gajim.command_system.framework import CommandHost # pylint: disable=unused-import from gajim.command_system.framework import CommandHost # pylint: disable=unused-import
@ -131,7 +133,6 @@ class ChatControl(ChatControlBase):
self.update_toolbar() self.update_toolbar()
self._pep_images = {} self._pep_images = {}
self._pep_images['activity'] = self.xml.get_object('activity_image')
self._pep_images['tune'] = self.xml.get_object('tune_image') self._pep_images['tune'] = self.xml.get_object('tune_image')
self._pep_images['geoloc'] = self.xml.get_object('location_image') self._pep_images['geoloc'] = self.xml.get_object('location_image')
self.update_all_pep_types() self.update_all_pep_types()
@ -233,6 +234,8 @@ class ChatControl(ChatControlBase):
self._on_nickname_received) self._on_nickname_received)
app.ged.register_event_handler('mood-received', ged.GUI1, app.ged.register_event_handler('mood-received', ged.GUI1,
self._on_mood_received) self._on_mood_received)
app.ged.register_event_handler('activity-received', ged.GUI1,
self._on_activity_received)
if self.TYPE_ID == message_control.TYPE_CHAT: if self.TYPE_ID == message_control.TYPE_CHAT:
# Dont connect this when PrivateChatControl is used # Dont connect this when PrivateChatControl is used
app.ged.register_event_handler('update-roster-avatar', ged.GUI1, app.ged.register_event_handler('update-roster-avatar', ged.GUI1,
@ -416,6 +419,7 @@ class ChatControl(ChatControlBase):
for pep_type in self._pep_images: for pep_type in self._pep_images:
self.update_pep(pep_type) self.update_pep(pep_type)
self._update_pep(PEPEventType.MOOD) self._update_pep(PEPEventType.MOOD)
self._update_pep(PEPEventType.ACTIVITY)
def update_pep(self, pep_type): def update_pep(self, pep_type):
if isinstance(self.contact, GC_Contact): if isinstance(self.contact, GC_Contact):
@ -453,6 +457,9 @@ class ChatControl(ChatControlBase):
if type_ == PEPEventType.MOOD: if type_ == PEPEventType.MOOD:
icon = 'mood-%s' % data.mood icon = 'mood-%s' % data.mood
formated_text = format_mood(*data) formated_text = format_mood(*data)
elif type_ == PEPEventType.ACTIVITY:
icon = get_activity_icon_name(data.activity, data.subactivity)
formated_text = format_activity(*data)
image.set_from_icon_name(icon, Gtk.IconSize.MENU) image.set_from_icon_name(icon, Gtk.IconSize.MENU)
image.set_tooltip_markup(formated_text) image.set_tooltip_markup(formated_text)
@ -461,11 +468,17 @@ class ChatControl(ChatControlBase):
def _get_pep_widget(self, type_): def _get_pep_widget(self, type_):
if type_ == PEPEventType.MOOD: if type_ == PEPEventType.MOOD:
return self.xml.get_object('mood_image') return self.xml.get_object('mood_image')
if type_ == PEPEventType.ACTIVITY:
return self.xml.get_object('activity_image')
@ensure_proper_control @ensure_proper_control
def _on_mood_received(self, _event): def _on_mood_received(self, _event):
self._update_pep(PEPEventType.MOOD) self._update_pep(PEPEventType.MOOD)
@ensure_proper_control
def _on_activity_received(self, _event):
self._update_pep(PEPEventType.ACTIVITY)
@ensure_proper_control @ensure_proper_control
def _on_nickname_received(self, _event): def _on_nickname_received(self, _event):
self.update_ui() self.update_ui()
@ -1086,6 +1099,8 @@ class ChatControl(ChatControlBase):
self._on_nickname_received) self._on_nickname_received)
app.ged.remove_event_handler('mood-received', ged.GUI1, app.ged.remove_event_handler('mood-received', ged.GUI1,
self._on_mood_received) self._on_mood_received)
app.ged.remove_event_handler('activity-received', ged.GUI1,
self._on_activity_received)
if self.TYPE_ID == message_control.TYPE_CHAT: if self.TYPE_ID == message_control.TYPE_CHAT:
app.ged.remove_event_handler('update-roster-avatar', ged.GUI1, app.ged.remove_event_handler('update-roster-avatar', ged.GUI1,
self._nec_update_avatar) self._nec_update_avatar)

View File

@ -93,8 +93,6 @@ class Config:
'trayicon': [opt_str, 'always', _("When to show notification area icon. Can be 'never', 'on_event', 'always'."), False], 'trayicon': [opt_str, 'always', _("When to show notification area icon. Can be 'never', 'on_event', 'always'."), False],
'allow_hide_roster': [opt_bool, False, _("Allow to hide the roster window even if the tray icon is not shown."), False], 'allow_hide_roster': [opt_bool, False, _("Allow to hide the roster window even if the tray icon is not shown."), False],
'iconset': [opt_str, DEFAULT_ICONSET, '', True], 'iconset': [opt_str, DEFAULT_ICONSET, '', True],
'mood_iconset': [opt_str, DEFAULT_MOOD_ICONSET, '', True],
'activity_iconset': [opt_str, DEFAULT_ACTIVITY_ICONSET, '', True],
'use_transports_iconsets': [opt_bool, True, '', True], 'use_transports_iconsets': [opt_bool, True, '', True],
'notif_signin_color': [opt_color, '#32CD32', _('Contact signed in notification color.')], # limegreen 'notif_signin_color': [opt_color, '#32CD32', _('Contact signed in notification color.')], # limegreen
'notif_signout_color': [opt_color, '#FF0000', _('Contact signout notification color')], # red 'notif_signout_color': [opt_color, '#FF0000', _('Contact signout notification color')], # red

View File

@ -221,8 +221,6 @@ class ConfigPaths:
'emoticons', PathLocation.DATA, PathType.FOLDER_OPTIONAL), 'emoticons', PathLocation.DATA, PathType.FOLDER_OPTIONAL),
('MY_ICONSETS', ('MY_ICONSETS',
'iconsets', PathLocation.DATA, PathType.FOLDER_OPTIONAL), 'iconsets', PathLocation.DATA, PathType.FOLDER_OPTIONAL),
('MY_ACTIVITY_ICONSETS',
'activities', PathLocation.DATA, PathType.FOLDER_OPTIONAL),
# Cache paths # Cache paths
('CACHE_DB', 'cache.db', PathLocation.CACHE, PathType.FILE), ('CACHE_DB', 'cache.db', PathLocation.CACHE, PathType.FILE),

View File

@ -1098,13 +1098,6 @@ def get_current_show(account):
status = app.connections[account].connected status = app.connections[account].connected
return app.SHOW_LIST[status] return app.SHOW_LIST[status]
def get_activity_iconset_path(iconset):
if os.path.isdir(os.path.join(configpaths.get('DATA'), 'activities', iconset)):
return os.path.join(configpaths.get('DATA'), 'activities', iconset)
if os.path.isdir(os.path.join(configpaths.get('MY_ACTIVITY_ICONSETS'),
iconset)):
return os.path.join(configpaths.get('MY_ACTIVITY_ICONSETS'), iconset)
def prepare_and_validate_gpg_keyID(account, jid, keyID): def prepare_and_validate_gpg_keyID(account, jid, keyID):
""" """
Return an eight char long keyID that can be used with for GPG encryption Return an eight char long keyID that can be used with for GPG encryption

View File

@ -35,7 +35,9 @@ ZEROCONF_MODULES = ['iq',
_imported_modules = [] # type: List[tuple] _imported_modules = [] # type: List[tuple]
_modules = {} # type: Dict[str, Dict[str, Any]] _modules = {} # type: Dict[str, Dict[str, Any]]
_store_publish_modules = [ _store_publish_modules = [
'UserMood'] # type: List[str] 'UserMood',
'UserActivity',
] # type: List[str]
for file in Path(__file__).parent.iterdir(): for file in Path(__file__).parent.iterdir():
if file.stem == '__init__': if file.stem == '__init__':

View File

@ -59,7 +59,7 @@ class Message:
nbxmpp.NS_IBB]) nbxmpp.NS_IBB])
def _message_received(self, _con, stanza, properties): def _message_received(self, _con, stanza, properties):
if properties.is_mam_message or properties.is_pubsub_event: if properties.is_mam_message or properties.is_pubsub:
return return
# Check if a child of the message contains any # Check if a child of the message contains any
# namespaces that we handle in other modules. # namespaces that we handle in other modules.

View File

@ -14,92 +14,62 @@
# XEP-0108: User Activity # XEP-0108: User Activity
from typing import Any
from typing import Tuple
import logging import logging
import nbxmpp import nbxmpp
from gi.repository import GLib
from gajim.common.const import PEPEventType, ACTIVITIES from gajim.common import app
from gajim.common.exceptions import StanzaMalformed from gajim.common.nec import NetworkEvent
from gajim.common.modules.pep import AbstractPEPModule, AbstractPEPData from gajim.common.modules.base import BaseModule
from gajim.common.modules.util import event_node
from gajim.common.modules.util import store_publish
from gajim.common.const import PEPEventType
log = logging.getLogger('gajim.c.m.user_activity') log = logging.getLogger('gajim.c.m.user_activity')
class UserActivityData(AbstractPEPData): class UserActivity(BaseModule):
type_ = PEPEventType.ACTIVITY _nbxmpp_extends = 'Activity'
_nbxmpp_methods = [
'set_activity',
]
def as_markup_text(self): def __init__(self, con):
pep = self.data BaseModule.__init__(self, con)
activity = pep['activity'] self._register_pubsub_handler(self._activity_received)
subactivity = pep['subactivity'] if 'subactivity' in pep else None
text = pep['text'] if 'text' in pep else None
if activity in ACTIVITIES: @event_node(nbxmpp.NS_ACTIVITY)
# Translate standard activities def _activity_received(self, _con, _stanza, properties):
if subactivity in ACTIVITIES[activity]: data = properties.pubsub_event.data
subactivity = ACTIVITIES[activity][subactivity] for contact in app.contacts.get_contacts(self._account,
activity = ACTIVITIES[activity]['category'] str(properties.jid)):
if data.activity is not None:
markuptext = '<b>' + GLib.markup_escape_text(activity) contact.pep[PEPEventType.ACTIVITY] = data
if subactivity:
markuptext += ': ' + GLib.markup_escape_text(subactivity)
markuptext += '</b>'
if text:
markuptext += ' (%s)' % GLib.markup_escape_text(text)
return markuptext
class UserActivity(AbstractPEPModule):
name = 'activity'
namespace = nbxmpp.NS_ACTIVITY
pep_class = UserActivityData
store_publish = True
_log = log
def _extract_info(self, item):
activity_dict = {}
activity_tag = item.getTag('activity', namespace=self.namespace)
if activity_tag is None:
raise StanzaMalformed('No activity node')
if not activity_tag.getChildren():
return None
for child in activity_tag.getChildren():
name = child.getName().strip()
data = child.getData().strip()
if name == 'text':
activity_dict['text'] = data
else: else:
activity_dict['activity'] = name contact.pep.pop(PEPEventType.ACTIVITY, None)
for subactivity in child.getChildren():
subactivity_name = subactivity.getName().strip()
activity_dict['subactivity'] = subactivity_name
if 'activity' not in activity_dict: if properties.is_self_message:
raise StanzaMalformed('No activity value found') if data.activity is not None:
return activity_dict self._con.pep[PEPEventType.ACTIVITY] = data
else:
self._con.pep.pop(PEPEventType.ACTIVITY, None)
def _build_node(self, data): app.nec.push_incoming_event(
item = nbxmpp.Node('activity', {'xmlns': self.namespace}) NetworkEvent('activity-received',
if data is None: account=self._account,
return item jid=properties.jid.getBare(),
activity=data,
is_self_message=properties.is_self_message))
activity, subactivity, message = data @store_publish
if not activity: def set_activity(self, activity):
return item log.info('Send %s', activity)
self._nbxmpp('Activity').set_activity(activity)
i = item.addChild(activity)
if subactivity:
i.addChild(subactivity)
if message:
i = item.addChild('text')
i.addData(message)
return item
def get_instance(*args, **kwargs): def get_instance(*args: Any, **kwargs: Any) -> Tuple[UserActivity, str]:
return UserActivity(*args, **kwargs), 'UserActivity' return UserActivity(*args, **kwargs), 'UserActivity'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 B

View File

Before

Width:  |  Height:  |  Size: 842 B

After

Width:  |  Height:  |  Size: 842 B

View File

Before

Width:  |  Height:  |  Size: 1005 B

After

Width:  |  Height:  |  Size: 1005 B

View File

Before

Width:  |  Height:  |  Size: 749 B

After

Width:  |  Height:  |  Size: 749 B

View File

Before

Width:  |  Height:  |  Size: 854 B

After

Width:  |  Height:  |  Size: 854 B

View File

Before

Width:  |  Height:  |  Size: 906 B

After

Width:  |  Height:  |  Size: 906 B

View File

Before

Width:  |  Height:  |  Size: 918 B

After

Width:  |  Height:  |  Size: 918 B

View File

Before

Width:  |  Height:  |  Size: 712 B

After

Width:  |  Height:  |  Size: 712 B

View File

Before

Width:  |  Height:  |  Size: 706 B

After

Width:  |  Height:  |  Size: 706 B

View File

Before

Width:  |  Height:  |  Size: 906 B

After

Width:  |  Height:  |  Size: 906 B

View File

Before

Width:  |  Height:  |  Size: 844 B

After

Width:  |  Height:  |  Size: 844 B

View File

Before

Width:  |  Height:  |  Size: 969 B

After

Width:  |  Height:  |  Size: 969 B

View File

Before

Width:  |  Height:  |  Size: 1016 B

After

Width:  |  Height:  |  Size: 1016 B

View File

Before

Width:  |  Height:  |  Size: 868 B

After

Width:  |  Height:  |  Size: 868 B

View File

Before

Width:  |  Height:  |  Size: 901 B

After

Width:  |  Height:  |  Size: 901 B

View File

Before

Width:  |  Height:  |  Size: 957 B

After

Width:  |  Height:  |  Size: 957 B

View File

Before

Width:  |  Height:  |  Size: 723 B

After

Width:  |  Height:  |  Size: 723 B

View File

Before

Width:  |  Height:  |  Size: 625 B

After

Width:  |  Height:  |  Size: 625 B

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 854 B

After

Width:  |  Height:  |  Size: 854 B

View File

Before

Width:  |  Height:  |  Size: 900 B

After

Width:  |  Height:  |  Size: 900 B

View File

Before

Width:  |  Height:  |  Size: 846 B

After

Width:  |  Height:  |  Size: 846 B

View File

Before

Width:  |  Height:  |  Size: 912 B

After

Width:  |  Height:  |  Size: 912 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 665 B

After

Width:  |  Height:  |  Size: 665 B

View File

Before

Width:  |  Height:  |  Size: 689 B

After

Width:  |  Height:  |  Size: 689 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 793 B

After

Width:  |  Height:  |  Size: 793 B

View File

Before

Width:  |  Height:  |  Size: 1013 B

After

Width:  |  Height:  |  Size: 1013 B

View File

Before

Width:  |  Height:  |  Size: 1012 B

After

Width:  |  Height:  |  Size: 1012 B

View File

Before

Width:  |  Height:  |  Size: 589 B

After

Width:  |  Height:  |  Size: 589 B

View File

Before

Width:  |  Height:  |  Size: 907 B

After

Width:  |  Height:  |  Size: 907 B

View File

Before

Width:  |  Height:  |  Size: 752 B

After

Width:  |  Height:  |  Size: 752 B

View File

Before

Width:  |  Height:  |  Size: 917 B

After

Width:  |  Height:  |  Size: 917 B

View File

Before

Width:  |  Height:  |  Size: 882 B

After

Width:  |  Height:  |  Size: 882 B

View File

Before

Width:  |  Height:  |  Size: 997 B

After

Width:  |  Height:  |  Size: 997 B

View File

Before

Width:  |  Height:  |  Size: 967 B

After

Width:  |  Height:  |  Size: 967 B

View File

Before

Width:  |  Height:  |  Size: 981 B

After

Width:  |  Height:  |  Size: 981 B

View File

Before

Width:  |  Height:  |  Size: 719 B

After

Width:  |  Height:  |  Size: 719 B

View File

Before

Width:  |  Height:  |  Size: 866 B

After

Width:  |  Height:  |  Size: 866 B

View File

Before

Width:  |  Height:  |  Size: 793 B

After

Width:  |  Height:  |  Size: 793 B

View File

Before

Width:  |  Height:  |  Size: 990 B

After

Width:  |  Height:  |  Size: 990 B

View File

Before

Width:  |  Height:  |  Size: 913 B

After

Width:  |  Height:  |  Size: 913 B

View File

Before

Width:  |  Height:  |  Size: 921 B

After

Width:  |  Height:  |  Size: 921 B

View File

Before

Width:  |  Height:  |  Size: 964 B

After

Width:  |  Height:  |  Size: 964 B

View File

Before

Width:  |  Height:  |  Size: 621 B

After

Width:  |  Height:  |  Size: 621 B

View File

Before

Width:  |  Height:  |  Size: 956 B

After

Width:  |  Height:  |  Size: 956 B

View File

Before

Width:  |  Height:  |  Size: 958 B

After

Width:  |  Height:  |  Size: 958 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 955 B

After

Width:  |  Height:  |  Size: 955 B

View File

Before

Width:  |  Height:  |  Size: 839 B

After

Width:  |  Height:  |  Size: 839 B

View File

Before

Width:  |  Height:  |  Size: 792 B

After

Width:  |  Height:  |  Size: 792 B

View File

Before

Width:  |  Height:  |  Size: 651 B

After

Width:  |  Height:  |  Size: 651 B

View File

Before

Width:  |  Height:  |  Size: 865 B

After

Width:  |  Height:  |  Size: 865 B

View File

Before

Width:  |  Height:  |  Size: 836 B

After

Width:  |  Height:  |  Size: 836 B

View File

Before

Width:  |  Height:  |  Size: 868 B

After

Width:  |  Height:  |  Size: 868 B

View File

Before

Width:  |  Height:  |  Size: 982 B

After

Width:  |  Height:  |  Size: 982 B

View File

Before

Width:  |  Height:  |  Size: 960 B

After

Width:  |  Height:  |  Size: 960 B

View File

Before

Width:  |  Height:  |  Size: 696 B

After

Width:  |  Height:  |  Size: 696 B

View File

Before

Width:  |  Height:  |  Size: 897 B

After

Width:  |  Height:  |  Size: 897 B

View File

Before

Width:  |  Height:  |  Size: 773 B

After

Width:  |  Height:  |  Size: 773 B

View File

Before

Width:  |  Height:  |  Size: 825 B

After

Width:  |  Height:  |  Size: 825 B

View File

Before

Width:  |  Height:  |  Size: 962 B

After

Width:  |  Height:  |  Size: 962 B

View File

Before

Width:  |  Height:  |  Size: 797 B

After

Width:  |  Height:  |  Size: 797 B

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 757 B

After

Width:  |  Height:  |  Size: 757 B

View File

Before

Width:  |  Height:  |  Size: 1005 B

After

Width:  |  Height:  |  Size: 1005 B

View File

Before

Width:  |  Height:  |  Size: 726 B

After

Width:  |  Height:  |  Size: 726 B

View File

Before

Width:  |  Height:  |  Size: 926 B

After

Width:  |  Height:  |  Size: 926 B

View File

Before

Width:  |  Height:  |  Size: 815 B

After

Width:  |  Height:  |  Size: 815 B

View File

@ -37,7 +37,6 @@ from gi.repository import Gtk
from gi.repository import Gdk from gi.repository import Gdk
from gi.repository import GLib from gi.repository import GLib
from gajim import gtkgui_helpers
from gajim import vcard from gajim import vcard
from gajim import dataforms_widget from gajim import dataforms_widget
@ -58,6 +57,7 @@ from gajim.gtk.add_contact import AddNewContactWindow
from gajim.gtk.util import get_icon_name from gajim.gtk.util import get_icon_name
from gajim.gtk.util import resize_window from gajim.gtk.util import resize_window
from gajim.gtk.util import get_builder from gajim.gtk.util import get_builder
from gajim.gtk.util import get_activity_icon_name
log = logging.getLogger('gajim.dialogs') log = logging.getLogger('gajim.dialogs')
@ -369,9 +369,9 @@ class ChangeActivityDialog:
group = None group = None
for category in ACTIVITIES: for category in ACTIVITIES:
icon_name = get_activity_icon_name(category)
item = self.xml.get_object(category + '_image') item = self.xml.get_object(category + '_image')
item.set_from_pixbuf( item.set_from_icon_name(icon_name, Gtk.IconSize.MENU)
gtkgui_helpers.load_activity_icon(category).get_pixbuf())
item.set_tooltip_text(ACTIVITIES[category]['category']) item.set_tooltip_text(ACTIVITIES[category]['category'])
vbox = self.xml.get_object(category + '_vbox') vbox = self.xml.get_object(category + '_vbox')
@ -386,7 +386,7 @@ class ChangeActivityDialog:
else: else:
rbtns[act] = group = Gtk.RadioButton() rbtns[act] = group = Gtk.RadioButton()
icon = gtkgui_helpers.load_activity_icon(category, self.activity) icon = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.MENU)
hbox = Gtk.HBox(homogeneous=False, spacing=5) hbox = Gtk.HBox(homogeneous=False, spacing=5)
hbox.pack_start(icon, False, False, 0) hbox.pack_start(icon, False, False, 0)
lbl = Gtk.Label( lbl = Gtk.Label(
@ -412,7 +412,8 @@ class ChangeActivityDialog:
else: else:
rbtns[act] = group = Gtk.RadioButton() rbtns[act] = group = Gtk.RadioButton()
icon = gtkgui_helpers.load_activity_icon(category, activity) icon_name = get_activity_icon_name(category, activity)
icon = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.MENU)
label = Gtk.Label(label=ACTIVITIES[category][activity]) label = Gtk.Label(label=ACTIVITIES[category][activity])
hbox = Gtk.HBox(homogeneous=False, spacing=5) hbox = Gtk.HBox(homogeneous=False, spacing=5)
hbox.pack_start(icon, False, False, 0) hbox.pack_start(icon, False, False, 0)
@ -681,12 +682,12 @@ class ChangeStatusMessageDialog(TimeoutDialog):
ACTIVITIES: ACTIVITIES:
if 'subactivity' in self.pep_dict and self.pep_dict['subactivity'] \ if 'subactivity' in self.pep_dict and self.pep_dict['subactivity'] \
in ACTIVITIES[self.pep_dict['activity']]: in ACTIVITIES[self.pep_dict['activity']]:
img.set_from_pixbuf(gtkgui_helpers.load_activity_icon( icon_name = get_activity_icon_name(self.pep_dict['activity'],
self.pep_dict['activity'], self.pep_dict['subactivity']).\ self.pep_dict['subactivity'])
get_pixbuf()) img.set_from_icon_name(icon_name, Gtk.IconSize.MENU)
else: else:
img.set_from_pixbuf(gtkgui_helpers.load_activity_icon( icon_name = get_activity_icon_name(self.pep_dict['activity'])
self.pep_dict['activity']).get_pixbuf()) img.set_from_icon_name(icon_name, Gtk.IconSize.MENU)
if self.pep_dict['activity_text']: if self.pep_dict['activity_text']:
label.set_text(self.pep_dict['activity_text']) label.set_text(self.pep_dict['activity_text'])
else: else:

View File

@ -43,6 +43,8 @@ from gajim.common.i18n import _
from gajim.gtk.util import get_builder from gajim.gtk.util import get_builder
from gajim.gtk.util import get_icon_name from gajim.gtk.util import get_icon_name
from gajim.gtk.util import format_mood from gajim.gtk.util import format_mood
from gajim.gtk.util import format_activity
log = logging.getLogger('gajim.gtk.tooltips') log = logging.getLogger('gajim.gtk.tooltips')
@ -479,8 +481,8 @@ class RosterTooltip(StatusTable):
self._ui.mood.show() self._ui.mood.show()
self._ui.mood_label.show() self._ui.mood_label.show()
if 'activity' in contact.pep: if PEPEventType.ACTIVITY in contact.pep:
activity = contact.pep['activity'].as_markup_text() activity = format_activity(*contact.pep[PEPEventType.ACTIVITY])
self._ui.activity.set_markup(activity) self._ui.activity.set_markup(activity)
self._ui.activity.show() self._ui.activity.show()
self._ui.activity_label.show() self._ui.activity_label.show()

View File

@ -37,6 +37,7 @@ from gajim.common import configpaths
from gajim.common import i18n from gajim.common import i18n
from gajim.common.i18n import _ from gajim.common.i18n import _
from gajim.common.const import MOODS from gajim.common.const import MOODS
from gajim.common.const import ACTIVITIES
from gajim.gtk.const import GajimIconSet from gajim.gtk.const import GajimIconSet
@ -521,3 +522,27 @@ def format_mood(mood, text):
if text is not None: if text is not None:
markuptext += ' (%s)' % GLib.markup_escape_text(text) markuptext += ' (%s)' % GLib.markup_escape_text(text)
return markuptext return markuptext
def format_activity(activity, subactivity, text):
if activity is None:
return
if subactivity in ACTIVITIES[activity]:
subactivity = ACTIVITIES[activity][subactivity]
activity = ACTIVITIES[activity]['category']
markuptext = '<b>' + GLib.markup_escape_text(activity)
if subactivity:
markuptext += ': ' + GLib.markup_escape_text(subactivity)
markuptext += '</b>'
if text:
markuptext += ' (%s)' % GLib.markup_escape_text(text)
return markuptext
def get_activity_icon_name(activity, subactivity=None):
icon_name = 'activity-%s' % activity.replace('_', '-')
if subactivity is not None:
icon_name += '-%s' % subactivity.replace('_', '-')
return icon_name

View File

@ -41,10 +41,7 @@ except Exception:
from gajim.common.i18n import _ from gajim.common.i18n import _
from gajim.common import app from gajim.common import app
from gajim.common import helpers
from gajim.common.const import PEPEventType from gajim.common.const import PEPEventType
from gajim.common.const import ACTIVITIES
from gajim.common.const import MOODS
HAS_PYWIN32 = True HAS_PYWIN32 = True
if os.name == 'nt': if os.name == 'nt':
@ -267,72 +264,15 @@ def create_list_multi(value_list, selected_values=None):
treeview.show_all() treeview.show_all()
return treeview return treeview
def load_activity_icon(category, activity=None):
"""
Load an icon from the activity iconset in 16x16
"""
iconset = app.config.get('activity_iconset')
path = os.path.join(helpers.get_activity_iconset_path(iconset),
category, '')
if activity is None:
activity = 'category'
icon_list = _load_icon_list([activity], path)
return icon_list[activity]
def get_pep_icon(pep_class): def get_pep_icon(pep_class):
if pep_class == PEPEventType.TUNE: if pep_class == PEPEventType.TUNE:
return 'audio-x-generic' return 'audio-x-generic'
if pep_class == PEPEventType.ACTIVITY:
pep_ = pep_class.data
activity = pep_['activity']
has_known_activity = activity in ACTIVITIES
has_known_subactivity = (has_known_activity and
'subactivity' in pep_ and
pep_['subactivity'] in ACTIVITIES[activity])
if has_known_activity:
if has_known_subactivity:
subactivity = pep_['subactivity']
return load_activity_icon(activity, subactivity).get_pixbuf()
return load_activity_icon(activity).get_pixbuf()
return load_activity_icon('unknown').get_pixbuf()
if pep_class == PEPEventType.LOCATION: if pep_class == PEPEventType.LOCATION:
return 'applications-internet' return 'applications-internet'
return None return None
def _load_icon_list(icons_list, path, pixbuf2=None):
"""
Load icons in icons_list from the given path, and add pixbuf2 on top left of
each static images
"""
imgs = {}
for icon in icons_list:
# try to open a pixfile with the correct method
icon_file = icon.replace(' ', '_')
files = []
files.append(path + icon_file + '.gif')
files.append(path + icon_file + '.png')
image = Gtk.Image()
image.show()
imgs[icon] = image
for file_ in files: # loop seeking for either gif or png
if os.path.exists(file_):
image.set_from_file(file_)
if pixbuf2 and image.get_storage_type() == Gtk.ImageType.PIXBUF:
# add pixbuf2 on top-left corner of image
pixbuf1 = image.get_pixbuf()
pixbuf2.composite(pixbuf1, 0, 0,
pixbuf2.get_property('width'),
pixbuf2.get_property('height'), 0, 0, 1.0, 1.0,
GdkPixbuf.InterpType.NEAREST, 255)
image.set_from_pixbuf(pixbuf1)
break
return imgs
def label_set_autowrap(widget): def label_set_autowrap(widget):
""" """
Make labels automatically re-wrap if their containers are resized. Make labels automatically re-wrap if their containers are resized.

View File

@ -37,13 +37,13 @@ from enum import IntEnum, unique
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import Gdk from gi.repository import Gdk
from gi.repository import GdkPixbuf
from gi.repository import Pango from gi.repository import Pango
from gi.repository import GObject from gi.repository import GObject
from gi.repository import GLib from gi.repository import GLib
from gi.repository import Gio from gi.repository import Gio
from nbxmpp.protocol import NS_FILE, NS_ROSTERX, NS_CONFERENCE from nbxmpp.protocol import NS_FILE, NS_ROSTERX, NS_CONFERENCE
from nbxmpp.structs import MoodData from nbxmpp.structs import MoodData
from nbxmpp.structs import ActivityData
from gajim import dialogs from gajim import dialogs
from gajim import vcard from gajim import vcard
@ -87,6 +87,7 @@ from gajim.gtk.util import move_window
from gajim.gtk.util import get_metacontact_surface from gajim.gtk.util import get_metacontact_surface
from gajim.gtk.util import get_builder from gajim.gtk.util import get_builder
from gajim.gtk.util import set_urgency_hint from gajim.gtk.util import set_urgency_hint
from gajim.gtk.util import get_activity_icon_name
log = logging.getLogger('gajim.roster') log = logging.getLogger('gajim.roster')
@ -1049,10 +1050,11 @@ class RosterWindow:
else: else:
self.model[child_iter][Column.MOOD_PIXBUF] = None self.model[child_iter][Column.MOOD_PIXBUF] = None
if app.config.get('show_activity_in_roster') and 'activity' in \ if app.config.get('show_activity_in_roster') and PEPEventType.ACTIVITY in pep_dict:
pep_dict: activity = pep_dict[PEPEventType.ACTIVITY].activity
self.model[child_iter][Column.ACTIVITY_PIXBUF] = \ subactivity = pep_dict[PEPEventType.ACTIVITY].subactivity
gtkgui_helpers.get_pep_icon(pep_dict['activity']) icon_name = get_activity_icon_name(activity, subactivity)
self.model[child_iter][Column.ACTIVITY_PIXBUF] = icon_name
else: else:
self.model[child_iter][Column.ACTIVITY_PIXBUF] = None self.model[child_iter][Column.ACTIVITY_PIXBUF] = None
@ -1314,7 +1316,7 @@ class RosterWindow:
if pep_type == PEPEventType.MOOD: if pep_type == PEPEventType.MOOD:
return app.config.get('show_mood_in_roster') return app.config.get('show_mood_in_roster')
if pep_type == 'activity': if pep_type == PEPEventType.ACTIVITY:
return app.config.get('show_activity_in_roster') return app.config.get('show_activity_in_roster')
if pep_type == 'tune': if pep_type == 'tune':
@ -1329,6 +1331,7 @@ class RosterWindow:
for pep_type in self._pep_type_to_model_column: for pep_type in self._pep_type_to_model_column:
self.draw_pep(jid, account, pep_type, contact=contact) self.draw_pep(jid, account, pep_type, contact=contact)
self._draw_pep(account, jid, PEPEventType.MOOD) self._draw_pep(account, jid, PEPEventType.MOOD)
self._draw_pep(account, jid, PEPEventType.ACTIVITY)
def draw_pep(self, jid, account, pep_type, contact=None): def draw_pep(self, jid, account, pep_type, contact=None):
if pep_type not in self._pep_type_to_model_column: if pep_type not in self._pep_type_to_model_column:
@ -1366,6 +1369,10 @@ class RosterWindow:
column = Column.MOOD_PIXBUF column = Column.MOOD_PIXBUF
if data is not None: if data is not None:
icon = 'mood-%s' % data.mood icon = 'mood-%s' % data.mood
elif type_ == PEPEventType.ACTIVITY:
column = Column.ACTIVITY_PIXBUF
if data is not None:
icon = get_activity_icon_name(data.activity, data.subactivity)
for child_iter in iters: for child_iter in iters:
self.model[child_iter][column] = icon self.model[child_iter][column] = icon
@ -2094,15 +2101,14 @@ class RosterWindow:
def send_pep(self, account, pep_dict): def send_pep(self, account, pep_dict):
connection = app.connections[account] connection = app.connections[account]
if 'activity' in pep_dict: if 'activity' in pep_dict:
activity = pep_dict['activity'] activity = pep_dict['activity']
subactivity = pep_dict.get('subactivity', None) subactivity = pep_dict.get('subactivity', None)
activity_text = pep_dict.get('activity_text', None) activity_text = pep_dict.get('activity_text', None)
connection.get_module('UserActivity').send( connection.get_module('UserActivity').set_activity(ActivityData(
(activity, subactivity, activity_text)) activity, subactivity, activity_text))
else: else:
connection.get_module('UserActivity').send(None) connection.get_module('UserActivity').set_activity(None)
if 'mood' in pep_dict: if 'mood' in pep_dict:
mood = pep_dict['mood'] mood = pep_dict['mood']
@ -2630,8 +2636,7 @@ class RosterWindow:
self.remove_contact(jid, obj.conn.name, backend=True) self.remove_contact(jid, obj.conn.name, backend=True)
def _nec_pep_received(self, obj): def _nec_pep_received(self, obj):
if obj.user_pep.type_ not in (PEPEventType.ACTIVITY, if obj.user_pep.type_ not in (PEPEventType.TUNE,
PEPEventType.TUNE,
PEPEventType.LOCATION): PEPEventType.LOCATION):
return return
@ -2645,6 +2650,11 @@ class RosterWindow:
self.draw_account(event.account) self.draw_account(event.account)
self._draw_pep(event.account, event.jid, PEPEventType.MOOD) self._draw_pep(event.account, event.jid, PEPEventType.MOOD)
def _on_activity_received(self, event):
if event.is_self_message:
self.draw_account(event.account)
self._draw_pep(event.account, event.jid, PEPEventType.ACTIVITY)
def _on_nickname_received(self, event): def _on_nickname_received(self, event):
self.draw_contact(event.jid, event.account) self.draw_contact(event.jid, event.account)
@ -5582,7 +5592,7 @@ class RosterWindow:
# activity_pixbuf, TUNE_ICON, LOCATION_ICON, avatar_img, # activity_pixbuf, TUNE_ICON, LOCATION_ICON, avatar_img,
# padlock_pixbuf, visible] # padlock_pixbuf, visible]
self.columns = [str, str, str, str, str, self.columns = [str, str, str, str, str,
str, GdkPixbuf.Pixbuf, str, str, str, str, str, str,
Gtk.Image, str, bool] Gtk.Image, str, bool]
self.xml = get_builder('roster_window.ui') self.xml = get_builder('roster_window.ui')
@ -5682,8 +5692,7 @@ class RosterWindow:
# cell_data_func, func_arg) # cell_data_func, func_arg)
self.renderers_list = [] self.renderers_list = []
self.renderers_propertys = {} self.renderers_propertys = {}
self._pep_type_to_model_column = { self._pep_type_to_model_column = {'tune': Column.TUNE_ICON,
'activity': Column.ACTIVITY_PIXBUF, 'tune': Column.TUNE_ICON,
'geoloc': Column.LOCATION_ICON} 'geoloc': Column.LOCATION_ICON}
renderer_text = Gtk.CellRendererText() renderer_text = Gtk.CellRendererText()
@ -5710,7 +5719,7 @@ class RosterWindow:
self._fill_pep_pixbuf_renderer, Column.MOOD_PIXBUF), self._fill_pep_pixbuf_renderer, Column.MOOD_PIXBUF),
('activity', Gtk.CellRendererPixbuf(), False, ('activity', Gtk.CellRendererPixbuf(), False,
'pixbuf', Column.ACTIVITY_PIXBUF, 'icon_name', Column.ACTIVITY_PIXBUF,
self._fill_pep_pixbuf_renderer, Column.ACTIVITY_PIXBUF), self._fill_pep_pixbuf_renderer, Column.ACTIVITY_PIXBUF),
('tune', Gtk.CellRendererPixbuf(), False, ('tune', Gtk.CellRendererPixbuf(), False,
@ -5836,6 +5845,8 @@ class RosterWindow:
self._on_nickname_received) self._on_nickname_received)
app.ged.register_event_handler('mood-received', ged.GUI1, app.ged.register_event_handler('mood-received', ged.GUI1,
self._on_mood_received) self._on_mood_received)
app.ged.register_event_handler('activity-received', ged.GUI1,
self._on_activity_received)
app.ged.register_event_handler('update-roster-avatar', ged.GUI1, app.ged.register_event_handler('update-roster-avatar', ged.GUI1,
self._nec_update_avatar) self._nec_update_avatar)
app.ged.register_event_handler('update-room-avatar', ged.GUI1, app.ged.register_event_handler('update-room-avatar', ged.GUI1,