Daniel Brötzmann pushed to branch mainwindow at gajim / gajim
Commits: f495405d by wurstsalat at 2021-03-20T00:25:51+01:00 Add SubscriptionManager
- - - - -
10 changed files:
- gajim/data/gui/account_page.ui - − gajim/data/gui/subscription_request_window.ui - gajim/gtk/account_page.py - gajim/gtk/const.py - gajim/gtk/main.py - + gajim/gtk/subscription_manager.py - − gajim/gtk/subscription_request.py - gajim/gui_interface.py - gajim/gui_menu_builder.py - gajim/roster_window.py
Changes:
===================================== gajim/data/gui/account_page.ui ===================================== @@ -123,7 +123,7 @@ <property name="halign">center</property> <property name="hexpand">True</property> <property name="orientation">vertical</property> - <property name="spacing">6</property> + <property name="spacing">18</property> <child> <object class="GtkBox"> <property name="visible">True</property> @@ -231,6 +231,24 @@ <property name="position">1</property> </packing> </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Contact Requests</property> + <style> + <class name="bold16"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <placeholder/> + </child> <style> <class name="account-page-box"/> </style>
===================================== gajim/data/gui/subscription_request_window.ui deleted ===================================== @@ -1,251 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.36.0 --> -<interface> - <requires lib="gtk+" version="3.20"/> - <object class="GtkPopoverMenu" id="deny_popover"> - <property name="can_focus">False</property> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="border_width">6</property> - <property name="orientation">vertical</property> - <property name="spacing">6</property> - <child> - <object class="GtkModelButton" id="deny_button"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="text" translatable="yes">_Deny</property> - <signal name="clicked" handler="_on_deny_clicked" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkModelButton" id="block_button"> - <property name="visible">True</property> - <property name="sensitive">False</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="text" translatable="yes">_Block User</property> - <signal name="clicked" handler="_on_block_clicked" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkModelButton" id="report_button"> - <property name="visible">True</property> - <property name="sensitive">False</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="text" translatable="yes">_Report as Spam</property> - <signal name="clicked" handler="_on_report_clicked" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="submenu">main</property> - <property name="position">1</property> - </packing> - </child> - </object> - <object class="GtkImage" id="image1"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">user-available-symbolic</property> - </object> - <object class="GtkImage" id="image2"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">user-info-symbolic</property> - </object> - <object class="GtkBox" id="subscription_box"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">start</property> - <property name="border_width">18</property> - <property name="orientation">vertical</property> - <property name="spacing">6</property> - <child> - <object class="GtkGrid"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="row_spacing">6</property> - <property name="column_spacing">12</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label">Subscription Request</property> - <property name="wrap">True</property> - <property name="wrap_mode">word-char</property> - <style> - <class name="large-header"/> - </style> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - <property name="width">2</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="request_label"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">start</property> - <property name="label"><subscription request></property> - <property name="wrap">True</property> - <property name="max_width_chars">50</property> - <property name="xalign">0</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="subscription_text"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">start</property> - <property name="margin_top">6</property> - <property name="label"><subscription text> -</property> - <property name="wrap">True</property> - <property name="max_width_chars">50</property> - <property name="xalign">0</property> - <property name="yalign">0</property> - <attributes> - <attribute name="style" value="italic"/> - </attributes> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkButton" id="start_chat_button"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="tooltip_text" translatable="yes">Start Chat</property> - <property name="valign">center</property> - <property name="image">image1</property> - <signal name="clicked" handler="_on_start_chat_clicked" swapped="no"/> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkButton" id="contact_info_button"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="tooltip_text" translatable="yes">Contact Information</property> - <property name="valign">center</property> - <property name="image">image2</property> - <signal name="clicked" handler="_on_contact_info_clicked" swapped="no"/> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">12</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">baseline</property> - <property name="margin_top">12</property> - <property name="spacing">12</property> - <property name="homogeneous">True</property> - <child> - <object class="GtkMenuButton"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="focus_on_click">False</property> - <property name="receives_default">True</property> - <property name="popover">deny_popover</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">_Deny…</property> - <property name="use_underline">True</property> - </object> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkButton" id="authorize_button"> - <property name="label" translatable="yes">Ac_cept</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="can_default">True</property> - <property name="receives_default">True</property> - <property name="tooltip_text" translatable="yes">Allows the contact to see your online status</property> - <property name="use_underline">True</property> - <signal name="clicked" handler="_on_authorize_clicked" swapped="no"/> - <style> - <class name="suggested-action"/> - </style> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - </object> -</interface>
===================================== gajim/gtk/account_page.py ===================================== @@ -16,11 +16,14 @@ from gi.repository import Gtk
from gajim.common import app +from gajim.common import ged from gajim.common.const import AvatarSize from gajim.common.i18n import _ +from gajim.common.nec import EventHelper
from .roster import Roster from .status_selector import StatusSelector +from .subscription_manager import SubscriptionManager from .util import get_builder from .util import open_window
@@ -29,9 +32,11 @@ }
-class AccountPage(Gtk.Box): +class AccountPage(Gtk.Box, EventHelper): def __init__(self, account): Gtk.Box.__init__(self) + EventHelper.__init__(self) + self._account = account self._jid = app.get_jid_from_account(account) client = app.get_client(account) @@ -44,6 +49,9 @@ def __init__(self, account): self._status_selector.set_halign(Gtk.Align.CENTER) self._ui.account_action_box.add(self._status_selector)
+ self._subscription_manager = SubscriptionManager(account) + self._ui.account_box.add(self._subscription_manager) + self._roster = Roster(account) self._ui.roster_box.add(self._roster)
@@ -58,6 +66,10 @@ def __init__(self, account): self._ui.connect_signals(self) self.show_all()
+ self.register_events([ + ('subscribe-presence-received', ged.GUI1, self._on_subscribe_received), + ]) + self.update()
def _on_edit_profile(self, _button): @@ -93,5 +105,9 @@ def update(self):
self._status_selector.update()
+ def _on_subscribe_received(self, event): + self._subscription_manager.add_request( + event.jid, event.status, user_nick=event.user_nick) + def process_event(self, event): self._roster.process_event(event)
===================================== gajim/gtk/const.py ===================================== @@ -128,7 +128,6 @@ def __str__(self): 'Themes': 'gajim.gui.themes', 'AdvancedConfig': 'gajim.gui.advanced_config', 'CertificateDialog': 'gajim.gui.dialogs', - 'SubscriptionRequest': 'gajim.gui.subscription_request', 'RemoveAccount': 'gajim.gui.remove_account', 'ChangePassword': 'gajim.gui.change_password', 'PluginsWindow': 'gajim.plugins.gui',
===================================== gajim/gtk/main.py ===================================== @@ -273,6 +273,9 @@ def show_title(self, *args): def get_active_jid(self, *args): pass
+ def get_account_page(self, account): + return self._account_pages[account] + def show_account_page(self, account): self._account_side_bar.activate_account_page(account) self._ui.main_stack.set_visible_child_name(account)
===================================== gajim/gtk/subscription_manager.py ===================================== @@ -0,0 +1,196 @@ +# 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 Gio +from gi.repository import GLib +from gi.repository import Gtk +from gi.repository import Pango + +from gajim.common import app +from gajim.common.i18n import _ + +from gajim.gui_menu_builder import get_subscription_manager_menu + +from .add_contact import AddNewContactWindow + + +class SubscriptionManager(Gtk.ScrolledWindow): + def __init__(self, account): + Gtk.ScrolledWindow.__init__(self) + self._account = account + self._client = app.get_client(account) + + self.set_hexpand(True) + self.set_size_request(400, 200) + + self._listbox = Gtk.ListBox() + self._listbox.set_selection_mode(Gtk.SelectionMode.NONE) + self._listbox.get_style_context().add_class('subscription-listbox') + self._set_placeholder() + + self.add(self._listbox) + self.show_all() + + self._add_actions() + self.connect('destroy', self._on_destroy) + + def _on_destroy(self, *args): + self._remove_actions() + + def _add_actions(self): + actions = [ + ('subscription-accept', self._on_subscription_accept), + ('subscription-deny', self._on_subscription_deny), + ('subscription-block', self._on_subscription_block), + ('subscription-report', self._on_subscription_report), + ] + for action in actions: + action_name, func = action + act = Gio.SimpleAction.new( + f'{action_name}-{self._account}', GLib.VariantType.new('s')) + act.connect('activate', func) + app.window.add_action(act) + + def update_actions(self): + online = app.account_is_connected(self._account) + blocking_support = self._client.get_module('Blocking').supported + + app.window.lookup_action( + f'subscription-accept-{self._account}').set_enabled(online) + app.window.lookup_action( + f'subscription-deny-{self._account}').set_enabled(online) + app.window.lookup_action( + f'subscription-block-{self._account}').set_enabled( + online and blocking_support) + app.window.lookup_action( + f'subscription-report-{self._account}').set_enabled( + online and blocking_support) + + def _remove_actions(self): + actions = [ + 'subscription-accept', + 'subscription-deny', + 'subscription-block', + 'subscription-report', + ] + for action in actions: + app.window.remove_action(f'{action}-{self._account}') + + def _set_placeholder(self): + label = Gtk.Label(label=_('No pending requests.')) + label.set_valign(Gtk.Align.START) + label.get_style_context().add_class('dim-label') + label.show() + self._listbox.set_placeholder(label) + + def _on_subscription_accept(self, _action, param): + jid = param.get_string() + row = self._get_request_row(jid) + self._client.get_module('Presence').subscribed(jid) + contact = self._client.get_module('Contacts').get_contact(jid) + if not contact.is_in_roster: + AddNewContactWindow(self._account, jid, row.user_nick) + self._listbox.remove(row) + + def _on_subscription_block(self, _action, param): + jid = param.get_string() + app.events.remove_events(self._account, jid) + self._deny_request(jid) + self._client.get_module('Blocking').block([jid]) + row = self._get_request_row(jid) + self._listbox.remove(row) + + def _on_subscription_report(self, _action, param): + jid = param.get_string() + app.events.remove_events(self._account, jid) + self._deny_request(jid) + self._client.get_module('Blocking').block([jid], report='spam') + row = self._get_request_row(jid) + self._listbox.remove(row) + + def _on_subscription_deny(self, _action, param): + jid = param.get_string() + self._deny_request(jid) + row = self._get_request_row(jid) + self._listbox.remove(row) + + def _deny_request(self, jid): + self._client.get_module('Presence').unsubscribed(jid) + + def _get_request_row(self, jid): + for child in self._listbox.get_children(): + if child.jid == jid: + return child + return None + + def add_request(self, jid, text, user_nick=None): + if self._get_request_row(jid) is None: + self._listbox.add(RequestRow(self._account, jid, text, user_nick)) + + +class RequestRow(Gtk.ListBoxRow): + def __init__(self, account, jid, text, user_nick): + Gtk.ListBoxRow.__init__(self) + self._account = account + self.jid = jid + self.user_nick = user_nick + self.get_style_context().add_class('padding-6') + + grid = Gtk.Grid(column_spacing=12) + image = Gtk.Image.new_from_icon_name( + 'avatar-default-symbolic', Gtk.IconSize.DND) + image.set_valign(Gtk.Align.CENTER) + grid.attach(image, 1, 1, 1, 2) + + if user_nick is not None: + escaped_nick = GLib.markup_escape_text(user_nick) + nick_markup = f'<b>{escaped_nick}</b> ({jid})' + else: + nick_markup = f'<b>{jid}</b>' + nick_label = Gtk.Label() + nick_label.set_halign(Gtk.Align.START) + nick_label.set_ellipsize(Pango.EllipsizeMode.END) + nick_label.set_max_width_chars(30) + nick_label.set_tooltip_markup(nick_markup) + nick_label.set_markup(nick_markup) + grid.attach(nick_label, 2, 1, 1, 1) + + message_text = GLib.markup_escape_text(text) + text_label = Gtk.Label(label=message_text) + text_label.set_halign(Gtk.Align.START) + text_label.set_ellipsize(Pango.EllipsizeMode.END) + text_label.set_max_width_chars(30) + text_label.set_tooltip_text(message_text) + text_label.get_style_context().add_class('dim-label') + grid.attach(text_label, 2, 2, 1, 1) + + accept_button = Gtk.Button.new_with_label(label=_('Accept')) + accept_button.set_valign(Gtk.Align.CENTER) + accept_button.set_action_name( + f'win.subscription-accept-{self._account}') + accept_button.set_action_target_value(GLib.Variant('s', str(self.jid))) + grid.attach(accept_button, 3, 1, 1, 2) + + more_image = Gtk.Image.new_from_icon_name( + 'view-more-symbolic', Gtk.IconSize.MENU) + more_button = Gtk.MenuButton() + more_button.set_valign(Gtk.Align.CENTER) + more_button.add(more_image) + subscription_menu = get_subscription_manager_menu( + self._account, self.jid) + more_button.set_menu_model(subscription_menu) + grid.attach(more_button, 4, 1, 1, 2) + + self.add(grid) + self.show_all()
===================================== gajim/gtk/subscription_request.py deleted ===================================== @@ -1,120 +0,0 @@ -# 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 Gtk -from gi.repository import Gdk - -from gajim.common import app -from gajim.common.i18n import _ - -from .add_contact import AddNewContactWindow -from .util import get_builder -from .util import open_window - - -class SubscriptionRequest(Gtk.ApplicationWindow): - def __init__(self, account, jid, text, user_nick=None): - Gtk.ApplicationWindow.__init__(self) - self.set_name('SubscriptionRequest') - self.set_application(app.app) - self.set_show_menubar(False) - self.set_resizable(False) - self.set_position(Gtk.WindowPosition.CENTER) - self.set_title(_('Subscription Request')) - - self.jid = jid - self.account = account - self.user_nick = user_nick - - self._ui = get_builder('subscription_request_window.ui') - self.add(self._ui.subscription_box) - self._ui.authorize_button.grab_default() - - if len(app.connections) >= 2: - prompt_text = _( - 'Subscription request for account %(account)s from ' - '%(jid)s') % {'account': self.account, 'jid': self.jid} - else: - prompt_text = _('Subscription request from %s') % self.jid - - self._ui.request_label.set_text(prompt_text) - self._ui.subscription_text.set_text(text) - - con = app.connections[self.account] - if con.get_module('Blocking').supported: - self._ui.block_button.set_sensitive(True) - self._ui.report_button.set_sensitive(True) - - self.connect('key-press-event', self._on_key_press) - - self._ui.connect_signals(self) - self.show_all() - - def _on_key_press(self, _widget, event): - if event.keyval == Gdk.KEY_Escape: - self.destroy() - - def _on_authorize_clicked(self, _widget): - """ - Accept the request - """ - con = app.connections[self.account] - con.get_module('Presence').subscribed(self.jid) - self.destroy() - contact = app.contacts.get_contact(self.account, self.jid) - if not contact or _('Not in contact list') in contact.groups: - AddNewContactWindow(self.account, self.jid, self.user_nick) - - def _on_contact_info_clicked(self, _widget): - """ - Ask for vCard - """ - client = app.get_client(self.account) - contact = client.get_module('Contacts').get_contact(self.jid) - open_window('ContactInfo', account=self.account, contact=contact) - - def _on_start_chat_clicked(self, _widget): - """ - Open chat - """ - app.interface.new_chat_from_jid(self.account, self.jid) - - def _on_deny_clicked(self, _widget): - self._deny_request() - self._remove_contact() - - def _on_block_clicked(self, _widget): - app.events.remove_events(self.account, self.jid) - self._deny_request() - con = app.connections[self.account] - con.get_module('Blocking').block([self.jid]) - self._remove_contact() - - def _on_report_clicked(self, _widget): - app.events.remove_events(self.account, self.jid) - self._deny_request() - con = app.connections[self.account] - con.get_module('Blocking').block([self.jid], report='spam') - self._remove_contact() - - def _deny_request(self): - con = app.connections[self.account] - con.get_module('Presence').unsubscribed(self.jid) - - def _remove_contact(self): - contact = app.contacts.get_contact(self.account, self.jid) - if contact and _('Not in contact list') in contact.get_shown_groups(): - app.interface.roster.remove_contact( - self.jid, self.account, force=True, backend=True) - self.destroy()
===================================== gajim/gui_interface.py ===================================== @@ -301,25 +301,18 @@ def handle_event_msgnotsent(obj): obj.session.roster_message(obj.jid, msg, obj.time_, obj.conn.name, msg_type='error')
- def handle_event_subscribe_presence(self, obj): - #('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172 - account = obj.conn.name - if helpers.allow_popup_window(account) or not self.systray_enabled: - open_window('SubscriptionRequest', - account=account, - jid=obj.jid, - text=obj.status, - user_nick=obj.user_nick) - return + def handle_event_subscribe_presence(self, event): + # ('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is XEP-0172 + account = event.conn.name + event = events.SubscriptionRequestEvent(event.status, event.user_nick)
- event = events.SubscriptionRequestEvent(obj.status, obj.user_nick) - self.add_event(account, obj.jid, event) + self.add_event(account, str(event.jid), event)
if helpers.allow_showing_notification(account): event_type = _('Subscription request') app.notification.popup( - event_type, obj.jid, account, 'subscription_request', - 'gajim-subscription_request', event_type, obj.jid) + event_type, str(event.jid), account, 'subscription_request', + 'gajim-subscription_request', event_type, str(event.jid))
def handle_event_subscribed_presence(self, event): bare_jid = event.jid.bare @@ -1270,11 +1263,7 @@ def handle_event(self, account, fjid, type_): event = app.events.get_first_event(account, jid, type_) if event is None: return - open_window('SubscriptionRequest', - account=account, - jid=jid, - text=event.text, - user_nick=event.nick) + # TODO: Show account page app.events.remove_events(account, jid, event) self.roster.draw_contact(jid, account) elif type_ == 'unsubscribed':
===================================== gajim/gui_menu_builder.py ===================================== @@ -819,6 +819,38 @@ def get_roster_menu(account, jid): return menu
+def get_subscription_manager_menu(account, jid): + menu = Gio.Menu() + + action_name = 'win.add-chat' + add_chat = Gio.MenuItem.new(_('Start Chat'), action_name) + dict_ = { + 'account': GLib.Variant('s', account), + 'jid': GLib.Variant('s', str(jid)), + 'type_': GLib.Variant('s', 'contact'), + 'select': GLib.Variant.new_boolean(True), + } + variant_dict = GLib.Variant('a{sv}', dict_) + add_chat.set_action_and_target_value(action_name, variant_dict) + menu.append_item(add_chat) + + menu_items = [ + ('contact-info', _('Information')), + ('subscription-block', _('Block')), + ('subscription-report', _('Report')), + ('subscription-deny', _('Deny')), + ] + for item in menu_items: + action, label = item + action = f'win.{action}-{account}' + menuitem = Gio.MenuItem.new(label, action) + variant = GLib.Variant('s', str(jid)) + menuitem.set_action_and_target_value(action, variant) + menu.append_item(menuitem) + + return menu + + def get_chat_list_row_menu(workspace_id, account, jid, pinned): client = app.get_client(account) contact = client.get_module('Contacts').get_contact(jid)
===================================== gajim/roster_window.py ===================================== @@ -1923,11 +1923,11 @@ def open_event(self, account, jid, event): return True
if event.type_ == 'subscription_request': - open_window('SubscriptionRequest', - account=account, - jid=jid, - text=event.text, - user_nick=event.nick) + # open_window('SubscriptionRequest', + # account=account, + # jid=jid, + # text=event.text, + # user_nick=event.nick) app.events.remove_events(account, jid, event) return True
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/commit/f495405db3c0ce066153d12c27527220e...