Commit be95b040 authored by Philipp Hörist's avatar Philipp Hörist

Refactor UserNickname and UserMood

- Use IconTheme for mood icons
- Simplify modules because nbxmpp handles more stuff
parent 8e336311
......@@ -47,6 +47,7 @@ from gajim.common.contacts import GC_Contact
from gajim.common.const import AvatarSize
from gajim.common.const import KindConstant
from gajim.common.const import Chatstate
from gajim.common.const import PEPEventType
from gajim import gtkgui_helpers
from gajim import gui_menu_builder
......@@ -57,6 +58,8 @@ from gajim.gtk.dialogs import ConfirmationDialog
from gajim.gtk.add_contact import AddNewContactWindow
from gajim.gtk.util import get_icon_name
from gajim.gtk.util import get_cursor
from gajim.gtk.util import ensure_proper_control
from gajim.gtk.util import format_mood
from gajim.command_system.implementation.hosts import ChatCommands
from gajim.command_system.framework import CommandHost # pylint: disable=unused-import
......@@ -128,7 +131,6 @@ class ChatControl(ChatControlBase):
self.update_toolbar()
self._pep_images = {}
self._pep_images['mood'] = self.xml.get_object('mood_image')
self._pep_images['activity'] = self.xml.get_object('activity_image')
self._pep_images['tune'] = self.xml.get_object('tune_image')
self._pep_images['geoloc'] = self.xml.get_object('location_image')
......@@ -227,6 +229,10 @@ class ChatControl(ChatControlBase):
app.ged.register_event_handler('pep-received', ged.GUI1,
self._nec_pep_received)
app.ged.register_event_handler('nickname-received', ged.GUI1,
self._on_nickname_received)
app.ged.register_event_handler('mood-received', ged.GUI1,
self._on_mood_received)
if self.TYPE_ID == message_control.TYPE_CHAT:
# Dont connect this when PrivateChatControl is used
app.ged.register_event_handler('update-roster-avatar', ged.GUI1,
......@@ -409,6 +415,7 @@ class ChatControl(ChatControlBase):
def update_all_pep_types(self):
for pep_type in self._pep_images:
self.update_pep(pep_type)
self._update_pep(PEPEventType.MOOD)
def update_pep(self, pep_type):
if isinstance(self.contact, GC_Contact):
......@@ -434,12 +441,36 @@ class ChatControl(ChatControlBase):
if obj.jid != self.contact.jid:
return
if obj.pep_type == 'nick':
self.update_ui()
self.parent_win.redraw_tab(self)
self.parent_win.show_title()
else:
self.update_pep(obj.pep_type)
self.update_pep(obj.pep_type)
def _update_pep(self, type_):
image = self._get_pep_widget(type_)
data = self.contact.pep.get(type_)
if data is None:
image.hide()
return
if type_ == PEPEventType.MOOD:
icon = 'mood-%s' % data.mood
formated_text = format_mood(*data)
image.set_from_icon_name(icon, Gtk.IconSize.MENU)
image.set_tooltip_markup(formated_text)
image.show()
def _get_pep_widget(self, type_):
if type_ == PEPEventType.MOOD:
return self.xml.get_object('mood_image')
@ensure_proper_control
def _on_mood_received(self, _event):
self._update_pep(PEPEventType.MOOD)
@ensure_proper_control
def _on_nickname_received(self, _event):
self.update_ui()
self.parent_win.redraw_tab(self)
self.parent_win.show_title()
def _update_jingle(self, jingle_type):
if jingle_type not in ('audio', 'video'):
......@@ -1051,6 +1082,10 @@ class ChatControl(ChatControlBase):
app.ged.remove_event_handler('pep-received', ged.GUI1,
self._nec_pep_received)
app.ged.remove_event_handler('nickname-received', ged.GUI1,
self._on_nickname_received)
app.ged.remove_event_handler('mood-received', ged.GUI1,
self._on_mood_received)
if self.TYPE_ID == message_control.TYPE_CHAT:
app.ged.remove_event_handler('update-roster-avatar', ged.GUI1,
self._nec_update_avatar)
......
......@@ -221,8 +221,6 @@ class ConfigPaths:
'emoticons', PathLocation.DATA, PathType.FOLDER_OPTIONAL),
('MY_ICONSETS',
'iconsets', PathLocation.DATA, PathType.FOLDER_OPTIONAL),
('MY_MOOD_ICONSETS',
'moods', PathLocation.DATA, PathType.FOLDER_OPTIONAL),
('MY_ACTIVITY_ICONSETS',
'activities', PathLocation.DATA, PathType.FOLDER_OPTIONAL),
......
......@@ -1631,6 +1631,7 @@ class Connection(CommonConnection, ConnectionHandlers):
# Inform GUI we just signed in
app.nec.push_incoming_event(NetworkEvent('signed-in', conn=self))
modules.send_stored_publish(self.name)
self.get_module('PEP').send_stored_publish()
self.continue_connect_info = None
......
......@@ -1098,13 +1098,6 @@ def get_current_show(account):
status = app.connections[account].connected
return app.SHOW_LIST[status]
def get_mood_iconset_path(iconset):
if os.path.isdir(os.path.join(configpaths.get('DATA'), 'moods', iconset)):
return os.path.join(configpaths.get('DATA'), 'moods', iconset)
if os.path.isdir(
os.path.join(configpaths.get('MY_MOOD_ICONSETS'), iconset)):
return os.path.join(configpaths.get('MY_MOOD_ICONSETS'), iconset)
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)
......
......@@ -34,6 +34,8 @@ ZEROCONF_MODULES = ['iq',
_imported_modules = [] # type: List[tuple]
_modules = {} # type: Dict[str, Dict[str, Any]]
_store_publish_modules = [
'UserMood'] # type: List[str]
for file in Path(__file__).parent.iterdir():
if file.stem == '__init__':
......@@ -112,6 +114,11 @@ def unregister_single(con: ConnectionT, name: str) -> None:
del _modules[con.name][name]
def send_stored_publish(account: str) -> None:
for name in _store_publish_modules:
_modules[account][name].send_stored_publish()
def get(account: str, name: str) -> Any:
try:
return _modules[account][name]
......
......@@ -20,6 +20,9 @@ import logging
from functools import partial
from unittest.mock import Mock
import nbxmpp
from nbxmpp.structs import StanzaHandler
from gajim.common import app
log = logging.getLogger('gajim.c.m.base')
......@@ -34,6 +37,7 @@ class BaseModule:
self._con = con
self._account = con.name
self._nbxmpp_callbacks = {} # type: Dict[str, Any]
self._stored_publish = None # type: Callable
self.handlers = [] # type: List[str]
def __getattr__(self, key):
......@@ -46,8 +50,10 @@ class BaseModule:
module = self._con.connection.get_module(self._nbxmpp_extends)
return partial(getattr(module, key),
callback=self._nbxmpp_callbacks.get(key))
callback = self._nbxmpp_callbacks.get(key)
if callback is None:
return getattr(module, key)
return partial(getattr(module, key), callback=callback)
def _nbxmpp(self, module_name=None):
if not app.account_is_connected(self._account):
......@@ -61,3 +67,16 @@ class BaseModule:
def _register_callback(self, method, callback):
self._nbxmpp_callbacks[method] = callback
def _register_pubsub_handler(self, callback):
handler = StanzaHandler(name='message',
callback=callback,
ns=nbxmpp.NS_PUBSUB_EVENT,
priority=49)
self.handlers.append(handler)
def send_stored_publish(self):
if self._stored_publish is None:
return
log.info('Send stored publish')
self._stored_publish()
......@@ -55,12 +55,11 @@ class Message:
]
# XEPs for which this message module should not be executed
self._message_namespaces = set([nbxmpp.NS_PUBSUB_EVENT,
nbxmpp.NS_ROSTERX,
self._message_namespaces = set([nbxmpp.NS_ROSTERX,
nbxmpp.NS_IBB])
def _message_received(self, _con, stanza, properties):
if properties.is_mam_message:
if properties.is_mam_message or properties.is_pubsub_event:
return
# Check if a child of the message contains any
# namespaces that we handle in other modules.
......
......@@ -6,94 +6,69 @@
#
# 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
# 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/>.
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
# XEP-0107: User Mood
from typing import Any
from typing import Dict
from typing import List # pylint: disable=unused-import
from typing import Optional
from typing import Tuple
import logging
import nbxmpp
from gi.repository import GLib
from gajim.common.const import PEPEventType, MOODS
from gajim.common.exceptions import StanzaMalformed
from gajim.common.modules.pep import AbstractPEPModule, AbstractPEPData
from gajim.common import app
from gajim.common.nec import NetworkEvent
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_mood')
class UserMoodData(AbstractPEPData):
class UserMood(BaseModule):
type_ = PEPEventType.MOOD
_nbxmpp_extends = 'Mood'
_nbxmpp_methods = [
'set_mood',
]
def as_markup_text(self) -> str:
if self.data is None:
return ''
mood = self._translate_mood(self.data['mood'])
markuptext = '<b>%s</b>' % GLib.markup_escape_text(mood)
if 'text' in self.data:
text = self.data['text']
markuptext += ' (%s)' % GLib.markup_escape_text(text)
return markuptext
def __init__(self, con):
BaseModule.__init__(self, con)
self._register_pubsub_handler(self._mood_received)
@staticmethod
def _translate_mood(mood: str) -> str:
if mood in MOODS:
return MOODS[mood]
return mood
class UserMood(AbstractPEPModule):
name = 'mood'
namespace = nbxmpp.NS_MOOD
pep_class = UserMoodData
store_publish = True
_log = log
def _extract_info(self, item: nbxmpp.Node) -> Optional[Dict[str, str]]:
mood_dict = {}
mood_tag = item.getTag('mood', namespace=nbxmpp.NS_MOOD)
if mood_tag is None:
raise StanzaMalformed('No mood node')
if not mood_tag.getChildren():
return None
for child in mood_tag.getChildren():
name = child.getName().strip()
if name == 'text':
mood_dict['text'] = child.getData()
@event_node(nbxmpp.NS_MOOD)
def _mood_received(self, _con, _stanza, properties):
data = properties.pubsub_event.data
for contact in app.contacts.get_contacts(self._account,
str(properties.jid)):
if data.mood is not None:
contact.pep[PEPEventType.MOOD] = data
else:
mood_dict['mood'] = name
if 'mood' not in mood_dict:
raise StanzaMalformed('No mood value found')
return mood_dict
def _build_node(self, data: Optional[Tuple[str, str]]) -> nbxmpp.Node:
item = nbxmpp.Node('mood', {'xmlns': nbxmpp.NS_MOOD})
if data is None:
return item
contact.pep.pop(PEPEventType.MOOD, None)
mood, text = data
if not mood:
return item
item.addChild(mood)
if text:
item.addChild('text', payload=text)
return item
if properties.is_self_message:
if data.mood is not None:
self._con.pep[PEPEventType.MOOD] = data
else:
self._con.pep.pop(PEPEventType.MOOD, None)
app.nec.push_incoming_event(
NetworkEvent('mood-received',
account=self._account,
jid=properties.jid.getBare(),
mood=data,
is_self_message=properties.is_self_message))
@store_publish
def set_mood(self, mood):
log.info('Send %s', mood)
self._nbxmpp('Mood').set_mood(mood)
def get_instance(*args: Any, **kwargs: Any) -> Tuple[UserMood, str]:
......
......@@ -6,17 +6,15 @@
#
# 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
# 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/>.
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
# XEP-0172: User Nickname
from typing import Any
from typing import List # pylint: disable=unused-import
from typing import Optional
from typing import Tuple
import logging
......@@ -24,56 +22,41 @@ import logging
import nbxmpp
from gajim.common import app
from gajim.common.const import PEPEventType
from gajim.common.exceptions import StanzaMalformed
from gajim.common.modules.pep import AbstractPEPModule, AbstractPEPData
from gajim.common.nec import NetworkEvent
from gajim.common.modules.base import BaseModule
from gajim.common.modules.util import event_node
log = logging.getLogger('gajim.c.m.user_nickname')
class UserNicknameData(AbstractPEPData):
class UserNickname(BaseModule):
type_ = PEPEventType.NICKNAME
_nbxmpp_extends = 'Nickname'
_nbxmpp_methods = [
'set_nickname',
]
def get_nick(self) -> str:
return self.data or ''
def __init__(self, con):
BaseModule.__init__(self, con)
self._register_pubsub_handler(self._nickname_received)
@event_node(nbxmpp.NS_NICK)
def _nickname_received(self, _con, _stanza, properties):
nick = properties.pubsub_event.data
if properties.self_message:
if nick is None:
nick = app.config.get_per('accounts', self._account, 'name')
app.nicks[self._account] = nick
class UserNickname(AbstractPEPModule):
for contact in app.contacts.get_contacts(self._account,
str(properties.jid)):
contact.contact_name = nick
name = 'nick'
namespace = nbxmpp.NS_NICK
pep_class = UserNicknameData
store_publish = True
_log = log
def _extract_info(self, item: nbxmpp.Node) -> Optional[str]:
nick = ''
child = item.getTag('nick', namespace=nbxmpp.NS_NICK)
if child is None:
raise StanzaMalformed('No nick node')
nick = child.getData()
return nick or None
def _build_node(self, data: Optional[str]) -> Optional[nbxmpp.Node]:
item = nbxmpp.Node('nick', {'xmlns': nbxmpp.NS_NICK})
if data is not None:
item.addData(data)
return item
def _notification_received(self,
jid: nbxmpp.JID,
user_pep: UserNicknameData) -> None:
for contact in app.contacts.get_contacts(self._account, str(jid)):
contact.contact_name = user_pep.get_nick()
if jid == self._con.get_own_jid().getStripped():
if user_pep:
app.nicks[self._account] = user_pep.get_nick()
else:
app.nicks[self._account] = app.config.get_per(
'accounts', self._account, 'name')
app.nec.push_incoming_event(
NetworkEvent('nickname-received',
account=self._account,
jid=properties.jid.getBare(),
nickname=nick))
def parse_nickname(stanza: nbxmpp.Node) -> str:
......
......@@ -16,6 +16,11 @@
from typing import Union
from functools import wraps
from functools import partial
from gajim.common import app
def from_xs_boolean(value: Union[str, bool]) -> bool:
if isinstance(value, bool):
......@@ -44,3 +49,25 @@ def to_xs_boolean(value: Union[bool, None]) -> str:
raise ValueError(
'Cant convert %s to xs:boolean' % value)
def event_node(node):
def event_node_decorator(func):
@wraps(func)
def func_wrapper(self, _con, _stanza, properties):
if properties.pubsub_event.node != node:
return
func(self, _con, _stanza, properties)
return func_wrapper
return event_node_decorator
def store_publish(func):
@wraps(func)
def func_wrapper(self, *args, **kwargs):
if not app.account_is_connected(self._account):
self._stored_publish = partial(func, self, *args, **kwargs)
return
return func(self, *args, **kwargs)
return func_wrapper
......@@ -504,10 +504,12 @@ class ChangeMoodDialog:
self.MOODS.sort()
for mood in self.MOODS:
image = Gtk.Image.new_from_icon_name(
'mood-%s' % mood, Gtk.IconSize.MENU)
self.mood_buttons[mood] = Gtk.RadioButton()
self.mood_buttons[mood].join_group(no_mood_button)
self.mood_buttons[mood].set_mode(False)
self.mood_buttons[mood].add(gtkgui_helpers.load_mood_icon(mood))
self.mood_buttons[mood].add(image)
self.mood_buttons[mood].set_relief(Gtk.ReliefStyle.NONE)
self.mood_buttons[mood].set_tooltip_text(MOODS[mood])
self.mood_buttons[mood].connect('clicked',
......@@ -700,8 +702,8 @@ class ChangeStatusMessageDialog(TimeoutDialog):
img = self.xml.get_object('mood_image')
label = self.xml.get_object('mood_button_label')
if 'mood' in self.pep_dict and self.pep_dict['mood'] in MOODS:
img.set_from_pixbuf(gtkgui_helpers.load_mood_icon(
self.pep_dict['mood']).get_pixbuf())
img.set_from_icon_name('mood-%s' % self.pep_dict['mood'],
Gtk.IconSize.MENU)
if self.pep_dict['mood_text']:
label.set_text(self.pep_dict['mood_text'])
else:
......
......@@ -325,7 +325,7 @@ class ProfileWindow(Gtk.ApplicationWindow):
return
vcard_, sha = self.make_vcard()
nick = vcard_.get('NICKNAME') or None
app.connections[self.account].get_module('UserNickname').send(nick)
app.connections[self.account].get_module('UserNickname').set_nickname(nick)
if not nick:
nick = app.config.get_per('accounts', self.account, 'name')
app.nicks[self.account] = nick
......
......@@ -36,11 +36,13 @@ from gi.repository import Pango
from gajim.common import app
from gajim.common import helpers
from gajim.common.const import AvatarSize
from gajim.common.const import PEPEventType
from gajim.common.i18n import Q_
from gajim.common.i18n import _
from gajim.gtk.util import get_builder
from gajim.gtk.util import get_icon_name
from gajim.gtk.util import format_mood
log = logging.getLogger('gajim.gtk.tooltips')
......@@ -471,8 +473,8 @@ class RosterTooltip(StatusTable):
Append Tune, Mood, Activity, Location information of the specified contact
to the given property list.
"""
if 'mood' in contact.pep:
mood = contact.pep['mood'].as_markup_text