# 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 . import time from gi.repository import Gdk from gi.repository import GLib from gi.repository import Gtk from nbxmpp.namespaces import Namespace from gajim.common import app from gajim.common.i18n import _ from gajim.common.i18n import Q_ from gajim.common.helpers import open_uri from gajim.common.helpers import get_groupchat_name from gajim.common.const import RFC5646_LANGUAGE_TAGS from gajim.common.const import AvatarSize from .util import get_builder from .util import make_href_markup MUC_FEATURES = { 'muc_open': ( 'feather-globe-symbolic', Q_('?Group chat feature:Open'), _('Anyone can join this group chat')), 'muc_membersonly': ( 'feather-user-check-symbolic', Q_('?Group chat feature:Members Only'), _('This group chat is restricted ' 'to members only')), 'muc_nonanonymous': ( 'feather-shield-off-symbolic', Q_('?Group chat feature:Not Anonymous'), _('All other group chat participants ' 'can see your XMPP address')), 'muc_semianonymous': ( 'feather-shield-symbolic', Q_('?Group chat feature:Semi-Anonymous'), _('Only moderators can see your XMPP address')), 'muc_moderated': ( 'feather-mic-off-symbolic', Q_('?Group chat feature:Moderated'), _('Participants entering this group chat need ' 'to request permission to send messages')), 'muc_unmoderated': ( 'feather-mic-symbolic', Q_('?Group chat feature:Not Moderated'), _('Participants entering this group chat are ' 'allowed to send messages')), 'muc_public': ( 'feather-eye-symbolic', Q_('?Group chat feature:Public'), _('Group chat can be found via search')), 'muc_hidden': ( 'feather-eye-off-symbolic', Q_('?Group chat feature:Hidden'), _('This group chat can not be found via search')), 'muc_passwordprotected': ( 'feather-lock-symbolic', Q_('?Group chat feature:Password Required'), _('This group chat ' 'does require a password upon entry')), 'muc_unsecured': ( 'feather-unlock-symbolic', Q_('?Group chat feature:No Password Required'), _('This group chat does not require ' 'a password upon entry')), 'muc_persistent': ( 'feather-hard-drive-symbolic', Q_('?Group chat feature:Persistent'), _('This group chat persists ' 'even if there are no participants')), 'muc_temporary': ( 'feather-clock-symbolic', Q_('?Group chat feature:Temporary'), _('This group chat will be destroyed ' 'once the last participant left')), 'mam': ( 'feather-server-symbolic', Q_('?Group chat feature:Archiving'), _('Messages are archived on the server')), } class GroupChatInfoScrolled(Gtk.ScrolledWindow): def __init__(self, account=None, options=None): Gtk.ScrolledWindow.__init__(self) if options is None: options = {} self._minimal = options.get('minimal', False) self.set_size_request(options.get('width', 400), -1) self.set_halign(Gtk.Align.CENTER) if self._minimal: self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER) else: self.set_vexpand(True) self.set_min_content_height(400) self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self._account = account self._info = None self._ui = get_builder('groupchat_info_scrolled.ui') self.add(self._ui.info_grid) self._ui.connect_signals(self) self.show_all() def get_account(self): return self._account def set_account(self, account): self._account = account def get_jid(self): return self._info.jid def set_author(self, author, epoch_timestamp=None): has_author = bool(author) if has_author and epoch_timestamp is not None: time_ = time.strftime('%c', time.localtime(epoch_timestamp)) author = f'{author} - {time_}' self._ui.author.set_text(author or '') self._ui.author.set_visible(has_author) self._ui.author_label.set_visible(has_author) def set_subject(self, subject): has_subject = bool(subject) subject = GLib.markup_escape_text(subject or '') self._ui.subject.set_markup(make_href_markup(subject)) self._ui.subject.set_visible(has_subject) self._ui.subject_label.set_visible(has_subject) def set_from_disco_info(self, info): self._info = info # Set name if self._account is None: name = info.muc_name else: con = app.connections[self._account] name = get_groupchat_name(con, info.jid) self._ui.name.set_text(name) self._ui.name.set_visible(True) # Set avatar surface = app.interface.avatar_storage.get_muc_surface( self._account, str(info.jid), AvatarSize.GROUP_INFO, self.get_scale_factor()) self._ui.avatar_image.set_from_surface(surface) # Set description has_desc = bool(info.muc_description) self._ui.description.set_text(info.muc_description or '') self._ui.description.set_visible(has_desc) self._ui.description_label.set_visible(has_desc) # Set address self._ui.address.set_text(str(info.jid)) if self._minimal: return # Set subject self.set_subject(info.muc_subject) # Set user has_users = info.muc_users is not None self._ui.users.set_text(info.muc_users or '') self._ui.users.set_visible(has_users) self._ui.users_image.set_visible(has_users) # Set contacts self._ui.contact_box.foreach(self._ui.contact_box.remove) has_contacts = bool(info.muc_contacts) if has_contacts: for contact in info.muc_contacts: self._ui.contact_box.add(self._get_contact_button(contact)) self._ui.contact_box.set_visible(has_contacts) self._ui.contact_label.set_visible(has_contacts) # Set discussion logs has_log_uri = bool(info.muc_log_uri) self._ui.logs.set_uri(info.muc_log_uri or '') self._ui.logs.set_label(_('Website')) self._ui.logs.set_visible(has_log_uri) self._ui.logs_label.set_visible(has_log_uri) # Set room language has_lang = bool(info.muc_lang) lang = '' if has_lang: lang = RFC5646_LANGUAGE_TAGS.get(info.muc_lang, info.muc_lang) self._ui.lang.set_text(lang) self._ui.lang.set_visible(has_lang) self._ui.lang_image.set_visible(has_lang) self._add_features(info.features) def _add_features(self, features): grid = self._ui.info_grid for row in range(30, 9, -1): # Remove everything from row 30 to 10 # We probably will never have 30 rows and # there is no method to count grid rows grid.remove_row(row) features = list(features) if Namespace.MAM_2 in features: features.append('mam') row = 10 for feature in MUC_FEATURES: if feature in features: icon, name, tooltip = MUC_FEATURES.get(feature, (None, None, None)) if icon is None: continue grid.attach(self._get_feature_icon(icon, tooltip), 0, row, 1, 1) grid.attach(self._get_feature_label(name), 1, row, 1, 1) row += 1 grid.show_all() def _on_copy_address(self, _button): clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) clipboard.set_text(f'xmpp:{self._info.jid}?join', -1) @staticmethod def _on_activate_log_link(button): open_uri(button.get_uri()) return Gdk.EVENT_STOP def _on_activate_contact_link(self, button): open_uri(f'xmpp:{button.get_uri()}?message', account=self._account) return Gdk.EVENT_STOP @staticmethod def _on_activate_subject_link(_label, uri): # We have to use this, because the default GTK handler # is not cross-platform compatible open_uri(uri) return Gdk.EVENT_STOP @staticmethod def _get_feature_icon(icon, tooltip): image = Gtk.Image.new_from_icon_name(icon, Gtk.IconSize.MENU) image.set_valign(Gtk.Align.CENTER) image.set_halign(Gtk.Align.END) image.set_tooltip_text(tooltip) return image @staticmethod def _get_feature_label(text): label = Gtk.Label(label=text, use_markup=True) label.set_halign(Gtk.Align.START) label.set_valign(Gtk.Align.START) return label def _get_contact_button(self, contact): button = Gtk.LinkButton.new(contact) button.set_halign(Gtk.Align.START) button.get_style_context().add_class('link-button') button.connect('activate-link', self._on_activate_contact_link) button.show() return button