From 61a791d67cd548f6c24e96516182d036dbf5bf09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Fri, 2 Nov 2018 22:24:28 +0100 Subject: [PATCH] Rewrite GroupchatConfig dialog - Complete rewrite of the dialog - Use new DataFormWidget --- gajim/common/const.py | 10 + gajim/common/modules/muc.py | 42 ++- gajim/data/gui/groupchat_config.ui | 493 ++++++++++++++++++++++++++ gajim/data/style/gajim.css | 9 + gajim/groupchat_control.py | 21 +- gajim/gtk/groupchat_config.py | 542 ++++++++++++++++++----------- gajim/gui_interface.py | 17 +- 7 files changed, 892 insertions(+), 242 deletions(-) create mode 100644 gajim/data/gui/groupchat_config.ui diff --git a/gajim/common/const.py b/gajim/common/const.py index ee4099444..a16fd28c2 100644 --- a/gajim/common/const.py +++ b/gajim/common/const.py @@ -193,6 +193,16 @@ class SyncThreshold(IntEnum): return str(self.value) +class MUCUser(IntEnum): + JID = 0 + NICK = 1 + REASON = 1 + NICK_OR_REASON = 1 + ROLE = 2 + AFFILIATION = 3 + AFFILIATION_TEXT = 4 + + ACTIVITIES = { 'doing_chores': { 'category': _('Doing Chores'), diff --git a/gajim/common/modules/muc.py b/gajim/common/modules/muc.py index 67a98a874..f7f8295b2 100644 --- a/gajim/common/modules/muc.py +++ b/gajim/common/modules/muc.py @@ -17,6 +17,7 @@ import time import logging +import weakref import nbxmpp @@ -187,28 +188,40 @@ class MUC: item = iq.setQuery() for jid in users_dict: affiliation = users_dict[jid].get('affiliation') - reason = users_dict[jid].get('reason') or None + reason = users_dict[jid].get('reason') + nick = users_dict[jid].get('nick') item_tag = item.addChild('item', {'jid': jid, 'affiliation': affiliation}) if reason is not None: item_tag.setTagData('reason', reason) + + if nick is not None: + item_tag.setAttr('nick', nick) log.info('Set affiliation for %s: %s', room_jid, users_dict) self._con.connection.SendAndCallForResponse( iq, self._default_response, {}) - def get_affiliation(self, room_jid, affiliation): + def get_affiliation(self, room_jid, affiliation, success_cb, error_cb): if not app.account_is_connected(self._account): return iq = nbxmpp.Iq(typ='get', to=room_jid, queryNS=nbxmpp.NS_MUC_ADMIN) item = iq.setQuery().setTag('item') item.setAttr('affiliation', affiliation) log.info('Get affiliation %s for %s', affiliation, room_jid) - self._con.connection.SendAndCallForResponse( - iq, self._affiliation_received) - def _affiliation_received(self, stanza): + weak_success_cb = weakref.WeakMethod(success_cb) + weak_error_cb = weakref.WeakMethod(error_cb) + + self._con.connection.SendAndCallForResponse( + iq, self._affiliation_received, {'affiliation': affiliation, + 'success_cb': weak_success_cb, + 'error_cb': weak_error_cb}) + + def _affiliation_received(self, _con, stanza, affiliation, + success_cb, error_cb): if not nbxmpp.isResultNode(stanza): - log.info('Error: %s', stanza.getError()) + if error_cb() is not None: + error_cb()(affiliation, stanza.getError()) return room_jid = stanza.getFrom().getStripped() @@ -222,8 +235,8 @@ class MUC: log.warning('Invalid JID: %s, ignoring it', item.getAttr('jid')) continue - affiliation = item.getAttr('affiliation') - users_dict[jid] = {'affiliation': affiliation} + + users_dict[jid] = {} if item.has_attr('nick'): users_dict[jid]['nick'] = item.getAttr('nick') if item.has_attr('role'): @@ -231,9 +244,12 @@ class MUC: reason = item.getTagData('reason') if reason: users_dict[jid]['reason'] = reason - log.info('Affiliations received from %s: %s', room_jid, users_dict) - app.nec.push_incoming_event(MucAdminReceivedEvent( - None, conn=self._con, room_jid=room_jid, users_dict=users_dict)) + + log.info('%s affiliations received from %s: %s', + affiliation, room_jid, users_dict) + + if success_cb() is not None: + success_cb()(self._account, room_jid, affiliation, users_dict) def set_role(self, room_jid, nick, role, reason=''): if not app.account_is_connected(self._account): @@ -423,10 +439,6 @@ class GcDeclineReceived(NetworkIncomingEvent): name = 'gc-decline-received' -class MucAdminReceivedEvent(NetworkIncomingEvent): - name = 'muc-admin-received' - - class MucOwnerReceivedEvent(NetworkIncomingEvent): name = 'muc-owner-received' diff --git a/gajim/data/gui/groupchat_config.ui b/gajim/data/gui/groupchat_config.ui new file mode 100644 index 000000000..e6ae6ff5d --- /dev/null +++ b/gajim/data/gui/groupchat_config.ui @@ -0,0 +1,493 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Owner + owner + + + Admin + admin + + + Member + member + + + + + False + + + True + False + 12 + 12 + 12 + 12 + <b>Jabber-ID</b> +&lt;user@domain/resource&gt; (only that resource matches) +&lt;user@domain&gt; (any resource matches) +&lt;domain/resource&gt; (only that resource matches) +&lt;domain&gt; (the domain itself matches, as does any user@domain or domain/resource) + + True + + + + + + + + + + + + + + + + + + + + + + + GroupchatConfig + 700 + 500 + True + False + 12 + + + True + False + False + + + True + False + start + 12 + False + 6 + + + True + True + True + info_popover + + + True + False + dialog-information-symbolic + + + + + + False + False + 0 + + + + + True + False + True + True + Only Admins and Owners can modify the affiliation + start + + + + True + False + list-add-symbolic + + + + + False + False + 1 + + + + + Remove + True + False + True + True + Only Admins and Owners can modify the affiliation + + + + + False + False + 2 + + + + + False + False + 0 + + + + + True + False + 6 + end + + + gtk-cancel + True + True + True + True + + + + False + False + end + 0 + + + + + gtk-ok + True + True + True + True + + + + False + False + end + 1 + + + + + False + False + end + 1 + + + + + 1 + 1 + + + + + True + False + True + True + 300 + crossfade + + + + True + False + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + config + Configuration + + + + + True + False + + + True + True + True + True + never + in + + + True + True + affiliation_store + 0 + + + multiple + + + + + + True + autosize + Jabber-ID + True + True + 0 + + + user@example.org + + + + 6 + 0 + + + + + + + True + autosize + Reserved Name + True + 1 + + + + + + 6 + 1 + + + + + + + False + True + autosize + 100 + Role + True + 2 + + + + 2 + + + + + + + True + autosize + 120 + Affiliation + True + 3 + + + False + combo_store + 0 + + + + 5 + 4 + + + + + + + + + + 0 + 0 + + + + + affiliation + Affiliations + 1 + + + + + False + True + + + True + True + True + True + in + + + True + True + outcast_store + 0 + + + multiple + + + + + + True + autosize + Jabber-ID + True + True + 0 + + + user@example.org + + + + 6 + 0 + + + + + + + True + autosize + 250 + Reason + True + 1 + + + spam + + + + 6 + 1 + + + + + + + + + + 0 + 0 + + + + + outcast + Ban List + 2 + + + + + 1 + 0 + + + + + 140 + True + False + stack + + + 0 + 0 + 2 + + + + diff --git a/gajim/data/style/gajim.css b/gajim/data/style/gajim.css index 013961ce6..3d8185fb9 100644 --- a/gajim/data/style/gajim.css +++ b/gajim/data/style/gajim.css @@ -146,6 +146,14 @@ list.settings > row > box { #StartChatListBox > row { padding: 10px 20px 10px 10px; } #StartChatListBox > row:not(.activatable) label { color: @insensitive_fg_color } +/* GroupchatConfig */ +#GroupchatConfig buttonbox { margin: 0px 12px 12px 12px; } +#GroupchatConfig stack { border-bottom: 1px solid; border-color: @borders;} +#GroupchatConfig stacksidebar > scrolledwindow { + background-color:@theme_base_color; + border-bottom: 1px solid; + border-color: @borders;} + /* Popover Treeview */ .popover_treeview { border-radius: 3px; background-color: @theme_bg_color; } .popover_treeview { padding: 6px; } @@ -211,6 +219,7 @@ list.settings > row > box { /* Padding/Margins */ .margin-top6 { margin-top: 6px; } +.margin-12 { margin: 12px; } /* Treeview */ treeview.space { padding: 6px; } diff --git a/gajim/groupchat_control.py b/gajim/groupchat_control.py index daaa48daa..584c6d29b 100644 --- a/gajim/groupchat_control.py +++ b/gajim/groupchat_control.py @@ -728,18 +728,21 @@ class GroupchatControl(ChatControlBase): _('You may also enter an alternate venue:'), ok_handler=on_ok, transient_for=self.parent_win.window) - def _on_configure_room(self, action, param): - c = app.contacts.get_gc_contact( + def _on_configure_room(self, _action, _param): + contact = app.contacts.get_gc_contact( self.account, self.room_jid, self.nick) - if c.affiliation == 'owner': + if contact.affiliation == 'owner': con = app.connections[self.account] con.get_module('MUC').request_config(self.room_jid) - elif c.affiliation == 'admin': - if self.room_jid not in app.interface.instances[self.account][ - 'gc_config']: - app.interface.instances[self.account]['gc_config'][ - self.room_jid] = GroupchatConfig(self.account, - self.room_jid) + elif contact.affiliation == 'admin': + win = app.get_app_window( + 'GroupchatConfig', self.account, self.room_jid) + if win is not None: + win.present() + else: + GroupchatConfig(self.account, + self.room_jid, + contact.affiliation) def _on_bookmark_room(self, action, param): """ diff --git a/gajim/gtk/groupchat_config.py b/gajim/gtk/groupchat_config.py index 84438714e..2d7a2f5ba 100644 --- a/gajim/gtk/groupchat_config.py +++ b/gajim/gtk/groupchat_config.py @@ -12,237 +12,365 @@ # You should have received a copy of the GNU General Public License # along with Gajim. If not, see . +import logging + +import nbxmpp from gi.repository import Gtk from gajim.common import app from gajim.common.i18n import _ +from gajim.common.const import MUCUser +from gajim.common.caps_cache import muc_caps_cache -from gajim import dataforms_widget - +from gajim.gtk.dialogs import ErrorDialog +from gajim.gtk.dataform import DataFormWidget from gajim.gtk.util import get_builder -from gajim.gtk.dialogs import InputDialog + +log = logging.getLogger('gajim.gtk.groupchat_config') -class GroupchatConfig: - def __init__(self, account, room_jid, form=None): +class GroupchatConfig(Gtk.ApplicationWindow): + def __init__(self, account, jid, own_affiliation, form=None): + Gtk.ApplicationWindow.__init__(self) + self.set_application(app.app) + self.set_position(Gtk.WindowPosition.CENTER) + self.set_show_menubar(False) + self.set_title(_('Group Chat Configuration')) + self.account = account - self.room_jid = room_jid - self.form = form - self.remove_button = {} - self.affiliation_treeview = {} - self.start_users_dict = {} # list at the beginning - self.affiliation_labels = { - 'outcast': _('Ban List'), - 'member': _('Member List'), - 'owner': _('Owner List'), - 'admin':_('Administrator List') - } + self.jid = jid + self._own_affiliation = own_affiliation - self._ui = get_builder('data_form_window.ui', ['data_form_window']) - self.window = self._ui.data_form_window - self.window.set_transient_for(app.interface.roster.window) + self._ui = get_builder('groupchat_config.ui') + self.add(self._ui.grid) - if self.form: - self.data_form_widget = dataforms_widget.DataFormWidget(self.form) - # hide scrollbar of this data_form_widget, we already have in this - # widget - sw = self.data_form_widget.xml.get_object( - 'single_form_scrolledwindow') - sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER) - if self.form.title: - self._ui.title_label.set_text(self.form.title) - else: - self._ui.title_hseparator.set_no_show_all(True) - self._ui.title_hseparator.hide() + # Activate Add button only for Admins and Owners + if self._own_affiliation in ('admin', 'owner'): + self._ui.add_button.set_sensitive(True) + self._ui.add_button.set_tooltip_text('') - self.data_form_widget.show() - self._ui.config_vbox.pack_start(self.data_form_widget, True, True, 0) + visible = muc_caps_cache.supports(jid, nbxmpp.NS_REGISTER) + self._ui.reserved_name_column.set_visible(visible) + + self._form = form + self._affiliations = {} + self._new_affiliations = {} + + con = app.connections[self.account] + for affiliation in ('owner', 'admin', 'member', 'outcast'): + con.get_module('MUC').get_affiliation( + self.jid, + affiliation, + self._on_affiliations_received, + self._on_affiliations_error) + + if form is not None: + self._ui.stack.set_visible_child_name('config') + self._data_form_widget = DataFormWidget(form) + self._ui.config_grid.add(self._data_form_widget) + # self._ui.stack.set_visible_child_name('affiliation') else: - self._ui.title_label.set_no_show_all(True) - self._ui.title_label.hide() - self._ui.title_hseparator.set_no_show_all(True) - self._ui.title_hseparator.hide() - self._ui.config_hseparator.set_no_show_all(True) - self._ui.config_hseparator.hide() - - # Draw the edit affiliation list things - add_on_vbox = self._ui.add_on_vbox - - for affiliation in self.affiliation_labels: - self.start_users_dict[affiliation] = {} - hbox = Gtk.HBox(spacing=5) - add_on_vbox.pack_start(hbox, False, True, 0) - - label = Gtk.Label(label=self.affiliation_labels[affiliation]) - hbox.pack_start(label, False, True, 0) - - bb = Gtk.HButtonBox() - bb.set_layout(Gtk.ButtonBoxStyle.END) - bb.set_spacing(5) - hbox.pack_start(bb, True, True, 0) - add_button = Gtk.Button(stock=Gtk.STOCK_ADD) - add_button.connect( - 'clicked', self.on_add_button_clicked, affiliation) - bb.pack_start(add_button, True, True, 0) - self.remove_button[affiliation] = Gtk.Button(stock=Gtk.STOCK_REMOVE) - self.remove_button[affiliation].set_sensitive(False) - self.remove_button[affiliation].connect( - 'clicked', self.on_remove_button_clicked, affiliation) - bb.pack_start(self.remove_button[affiliation], True, True, 0) - - # jid, reason, nick, role - liststore = Gtk.ListStore(str, str, str, str) - self.affiliation_treeview[affiliation] = Gtk.TreeView(liststore) - self.affiliation_treeview[affiliation].get_selection().set_mode( - Gtk.SelectionMode.MULTIPLE) - self.affiliation_treeview[affiliation].connect( - 'cursor-changed', - self.on_affiliation_treeview_cursor_changed, - affiliation) - renderer = Gtk.CellRendererText() - col = Gtk.TreeViewColumn(_('JID'), renderer) - col.add_attribute(renderer, 'text', 0) - col.set_resizable(True) - col.set_sort_column_id(0) - self.affiliation_treeview[affiliation].append_column(col) - - if affiliation == 'outcast': - renderer = Gtk.CellRendererText() - renderer.set_property('editable', True) - renderer.connect('edited', self.on_cell_edited) - col = Gtk.TreeViewColumn(_('Reason'), renderer) - col.add_attribute(renderer, 'text', 1) - col.set_resizable(True) - col.set_sort_column_id(1) - self.affiliation_treeview[affiliation].append_column(col) - elif affiliation == 'member': - renderer = Gtk.CellRendererText() - col = Gtk.TreeViewColumn(_('Nick'), renderer) - col.add_attribute(renderer, 'text', 2) - col.set_resizable(True) - col.set_sort_column_id(2) - self.affiliation_treeview[affiliation].append_column(col) - renderer = Gtk.CellRendererText() - col = Gtk.TreeViewColumn(_('Role'), renderer) - col.add_attribute(renderer, 'text', 3) - col.set_resizable(True) - col.set_sort_column_id(3) - self.affiliation_treeview[affiliation].append_column(col) - - sw = Gtk.ScrolledWindow() - sw.add(self.affiliation_treeview[affiliation]) - add_on_vbox.pack_start(sw, True, True, 0) - con = app.connections[self.account] - con.get_module('MUC').get_affiliation(self.room_jid, affiliation) + self._ui.stack.get_child_by_name('config').hide() + self._ui.stack.get_child_by_name('config').set_no_show_all(True) + self._ui.stack.set_visible_child_name('affiliation') self._ui.connect_signals(self) - self.window.connect('delete-event', self.on_cancel_button_clicked) - self.window.show_all() + self.show_all() + self._ui.stack.notify('visible-child-name') - def on_cancel_button_clicked(self, *args): - if self.form: - con = app.connections[self.account] - con.get_module('MUC').cancel_config(self.room_jid) - self.window.destroy() + def _get_current_treeview(self): + page_name = self._ui.stack.get_visible_child_name() + return getattr(self._ui, '%s_treeview' % page_name) - def on_cell_edited(self, _cell, path, new_text): - model = self.affiliation_treeview['outcast'].get_model() - new_text = new_text - iter_ = model.get_iter(path) - model[iter_][1] = new_text - - def on_add_button_clicked(self, _widget, affiliation): - if affiliation == 'outcast': - title = _('Banning…') - #You can move '\n' before user@domain if that line is TOO BIG - prompt = _('Whom do you want to ban?\n\n') - elif affiliation == 'member': - title = _('Adding Member…') - prompt = _('Whom do you want to make a member?\n\n') - elif affiliation == 'owner': - title = _('Adding Owner…') - prompt = _('Whom do you want to make an owner?\n\n') + def _on_add(self, *args): + page_name = self._ui.stack.get_visible_child_name() + if page_name == 'outcast': + affiliation_edit, jid_edit = self._allowed_to_edit('outcast') + text = None + affiliation = 'outcast' else: - title = _('Adding Administrator…') - prompt = _('Whom do you want to make an administrator?\n\n') - prompt += _( - 'Can be one of the following:\n' - '1. user@domain/resource (only that resource matches).\n' - '2. user@domain (any resource matches).\n' - '3. domain/resource (only that resource matches).\n' - '4. domain (the domain itself matches, as does any user@domain,\n' - 'domain/resource, or address containing a subdomain).') + affiliation_edit, jid_edit = self._allowed_to_edit('member') + text = _('Member') + affiliation = 'member' - def on_ok(jid): - if not jid: - return - model = self.affiliation_treeview[affiliation].get_model() - model.append((jid, '', '', '')) - InputDialog(title, prompt, ok_handler=on_ok) + treeview = self._get_current_treeview() + treeview.get_model().append([None, + None, + None, + affiliation, + text, + affiliation_edit, + jid_edit]) - def on_remove_button_clicked(self, _widget, affiliation): - selection = self.affiliation_treeview[affiliation].get_selection() - model, paths = selection.get_selected_rows() - row_refs = [] + # Scroll to added row + row = treeview.get_model()[-1] + treeview.scroll_to_cell(row.path, None, False, 0, 0) + treeview.get_selection().unselect_all() + treeview.get_selection().select_path(row.path) + + def _on_remove(self, *args): + treeview = self._get_current_treeview() + model, paths = treeview.get_selection().get_selected_rows() + + owner_count = self._get_owner_count() + references = [] for path in paths: - row_refs.append(Gtk.TreeRowReference.new(model, path)) - for row_ref in row_refs: - path = row_ref.get_path() - iter_ = model.get_iter(path) + if model[path][MUCUser.AFFILIATION] == 'owner': + if owner_count < 2: + # There must be at least one owner + ErrorDialog(_('Error'), + _('A Group Chat needs at least one Owner')) + return + owner_count -= 1 + references.append(Gtk.TreeRowReference.new(model, path)) + + for ref in references: + iter_ = model.get_iter(ref.get_path()) model.remove(iter_) - self.remove_button[affiliation].set_sensitive(False) - def on_affiliation_treeview_cursor_changed(self, _widget, affiliation): - self.remove_button[affiliation].set_sensitive(True) + def _on_jid_edited(self, _renderer, path, new_text): + old_text = self._ui.affiliation_store[path][MUCUser.JID] + if new_text == old_text: + return - def affiliation_list_received(self, users_dict): - """ - Fill the affiliation treeview - """ - for jid in users_dict: - affiliation = users_dict[jid]['affiliation'] - if affiliation not in self.affiliation_labels.keys(): - # Unknown affiliation or 'none' affiliation, do not show it - continue - self.start_users_dict[affiliation][jid] = users_dict[jid] - tv = self.affiliation_treeview[affiliation] - model = tv.get_model() - reason = users_dict[jid].get('reason', '') - nick = users_dict[jid].get('nick', '') - role = users_dict[jid].get('role', '') - model.append((jid, reason, nick, role)) + if self._jid_exists(new_text): + self._raise_error() + return - def on_data_form_window_destroy(self, _widget): - del app.interface.instances[self.account]['gc_config'][self.room_jid] + self._ui.affiliation_store[path][MUCUser.JID] = new_text - def on_ok_button_clicked(self, _widget): - if self.form: - form = self.data_form_widget.data_form + def _on_outcast_jid_edited(self, _renderer, path, new_text): + old_text = self._ui.outcast_store[path][MUCUser.JID] + if new_text == old_text: + return + + if self._jid_exists(new_text): + self._raise_error() + return + + self._ui.outcast_store[path][MUCUser.JID] = new_text + self._ui.outcast_store[path][MUCUser.AFFILIATION] = 'outcast' + + def _on_nick_edited(self, _renderer, path, new_text): + self._ui.affiliation_store[path][MUCUser.NICK] = new_text + + def _on_reason_edited(self, _renderer, path, new_text): + self._ui.outcast_store[path][MUCUser.REASON] = new_text + + def _on_affiliation_changed(self, cell_renderer_combo, + path_string, new_iter): + combo_store = cell_renderer_combo.get_property('model') + affiliation_text = combo_store.get_value(new_iter, 0) + affiliation = combo_store.get_value(new_iter, 1) + + store = self._ui.affiliation_treeview.get_model() + + store[path_string][MUCUser.AFFILIATION] = affiliation + store[path_string][MUCUser.AFFILIATION_TEXT] = affiliation_text + + def _on_selection_changed(self, tree_selection): + sensitive = bool(tree_selection.count_selected_rows()) + selected_affiliations = self._get_selected_affiliations(tree_selection) + self._set_remove_button_state(sensitive, selected_affiliations) + + def _jid_exists(self, jid): + stores = [self._ui.affiliation_store, self._ui.outcast_store] + + for store in stores: + for row in store: + if row[MUCUser.JID] == jid: + return True + return False + + @staticmethod + def _get_selected_affiliations(tree_selection): + model, paths = tree_selection.get_selected_rows() + selected_affiliations = set() + for path in paths: + selected_affiliations.add(model[path][MUCUser.AFFILIATION]) + return selected_affiliations + + def _on_switch_page(self, stack, _pspec): + page_name = stack.get_visible_child_name() + self._set_button_box_state(page_name) + if page_name == 'config': + return + + treeview = getattr(self._ui, '%s_treeview' % page_name) + sensitive = bool(treeview.get_selection().count_selected_rows()) + + selected_affiliations = self._get_selected_affiliations( + treeview.get_selection()) + self._set_remove_button_state(sensitive, selected_affiliations) + + def _set_button_box_state(self, page_name): + affiliation = self._own_affiliation in ('admin', 'owner') + page = page_name != 'config' + self._ui.treeview_buttonbox.set_visible(affiliation and page) + + def _set_remove_button_state(self, sensitive, selected_affiliations): + if self._own_affiliation not in ('admin', 'owner'): + self._ui.remove_button.set_sensitive(False) + return + + self._ui.remove_button.set_tooltip_text('') + + if not sensitive: + self._ui.remove_button.set_sensitive(False) + return + + if self._own_affiliation == 'owner': + self._ui.remove_button.set_sensitive(True) + return + + if set(['owner', 'admin']).intersection(selected_affiliations): + self._ui.remove_button.set_sensitive(False) + self._ui.remove_button.set_tooltip_text( + _('You are not allowed to modify the affiliation ' + 'of Admins and Owners')) + return + + self._ui.remove_button.set_sensitive(True) + + def _get_owner_count(self): + owner_count = 0 + for row in self._ui.affiliation_store: + if row[MUCUser.AFFILIATION] == 'owner': + owner_count += 1 + return owner_count + + def _allowed_to_edit(self, affiliation): + if self._own_affiliation == 'owner': + return True, True + + if self._own_affiliation == 'admin': + if affiliation in ('admin', 'owner'): + return False, False + return False, True + return False, False + + def _on_ok(self, *args): + if self._own_affiliation in ('admin', 'owner'): + self._set_affiliations() + + if self._form is not None and self._own_affiliation == 'owner': + form = self._data_form_widget.get_submit_form() con = app.connections[self.account] - con.get_module('MUC').set_config(self.room_jid, form) - for affiliation in self.affiliation_labels: - users_dict = {} - actual_jid_list = [] - model = self.affiliation_treeview[affiliation].get_model() - iter_ = model.get_iter_first() - # add new jid - while iter_: - jid = model[iter_][0] - actual_jid_list.append(jid) - if jid not in self.start_users_dict[affiliation] or \ - (affiliation == 'outcast' and 'reason' in self.start_users_dict[ - affiliation][jid] and self.start_users_dict[affiliation][jid]\ - ['reason'] != model[iter_][1]): - users_dict[jid] = {'affiliation': affiliation} - if affiliation == 'outcast': - users_dict[jid]['reason'] = model[iter_][1] - iter_ = model.iter_next(iter_) - # remove removed one - for jid in self.start_users_dict[affiliation]: - if jid not in actual_jid_list: - users_dict[jid] = {'affiliation': 'none'} - if users_dict: - con = app.connections[self.account] - con.get_module('MUC').set_affiliation( - self.room_jid, users_dict) - self.window.destroy() + con.get_module('MUC').set_config(self.jid, form) + self.destroy() + + def _get_diff(self): + stores = [self._ui.affiliation_store, self._ui.outcast_store] + + self._new_affiliations = {} + for store in stores: + for row in store: + if not row[MUCUser.JID]: + # Ignore empty JID field + continue + + attr = 'nick' + if row[MUCUser.AFFILIATION] == 'outcast': + attr = 'reason' + + self._new_affiliations[row[MUCUser.JID]] = { + 'affiliation': row[MUCUser.AFFILIATION], + attr: row[MUCUser.NICK_OR_REASON]} + + old_jids = set(self._affiliations.keys()) + new_jids = set(self._new_affiliations.keys()) + remove = old_jids - new_jids + add = new_jids - old_jids + modified = new_jids - remove - add + + return add, remove, modified + + def _on_cancel(self, *args): + if self._form and self._own_affiliation == 'owner': + con = app.connections[self.account] + con.get_module('MUC').cancel_config(self.jid) + self.destroy() + + def _set_affiliations(self): + add, remove, modified = self._get_diff() + + diff_dict = {} + for jid in add: + diff_dict[jid] = self._new_affiliations[jid] + + for jid in remove: + diff_dict[jid] = {'affiliation': 'none'} + + for jid in modified: + if self._new_affiliations[jid] == self._affiliations[jid]: + # Not modified + continue + + diff_dict[jid] = self._new_affiliations[jid] + if self._new_affiliations[jid]['affiliation'] == 'outcast': + # New affiliation is outcast, check if the reason changed. + # In case the affiliation was 'admin', 'owner' or 'member' + # before, there is no reason. + new_reason = self._new_affiliations[jid]['reason'] + old_reason = self._affiliations[jid].get('reason') + if new_reason == old_reason: + diff_dict[jid].pop('reason', None) + + else: + # New affiliation is not outcast, check if the nick has changed. + # In case the affiliation was 'outcast' there is no nick. + new_nick = self._new_affiliations[jid]['nick'] + old_nick = self._affiliations[jid].get('nick') + if new_nick == old_nick: + diff_dict[jid].pop('nick', None) + + if not diff_dict: + # No changes were made + return + con = app.connections[self.account] + con.get_module('MUC').set_affiliation(self.jid, diff_dict) + + def _on_affiliations_error(self, affiliation, error): + log.info('Error while requesting %s affiliations: %s', + affiliation, error) + + def _on_affiliations_received(self, _account, _room_jid, + affiliation, users): + + if affiliation == 'outcast': + self._ui.stack.get_child_by_name('outcast').show() + + for jid, attrs in users.items(): + affiliation_edit, jid_edit = self._allowed_to_edit(affiliation) + if affiliation == 'outcast': + reason = attrs.get('reason') + self._ui.outcast_store.append( + [jid, + reason, + None, + affiliation, + None, + affiliation_edit, + jid_edit]) + self._affiliations[jid] = {'affiliation': affiliation, + 'reason': reason} + else: + nick = attrs.get('nick') + role = attrs.get('role') + self._ui.affiliation_store.append( + [jid, + nick, + role, + affiliation, + _(affiliation.capitalize()), + affiliation_edit, + jid_edit]) + self._affiliations[jid] = {'affiliation': affiliation, + 'nick': nick} + if role is not None: + self._ui.role_column.set_visible(True) + + @staticmethod + def _raise_error(): + ErrorDialog(_('Error'), + _('An entry with this Jabber-ID already exists')) diff --git a/gajim/gui_interface.py b/gajim/gui_interface.py index 278cc54e3..f15840ec4 100644 --- a/gajim/gui_interface.py +++ b/gajim/gui_interface.py @@ -601,16 +601,12 @@ class Interface: _('%(jid)s has been invited in this room') % { 'jid': jid}, graphics=False) del app.automatic_rooms[account][obj.jid] - elif obj.jid not in self.instances[account]['gc_config']: - self.instances[account]['gc_config'][obj.jid] = \ - GroupchatConfig(account, obj.jid, obj.dataform) - - def handle_event_gc_affiliation(self, obj): - #('GC_AFFILIATION', account, (room_jid, users_dict)) - account = obj.conn.name - if obj.room_jid in self.instances[account]['gc_config']: - self.instances[account]['gc_config'][obj.room_jid].\ - affiliation_list_received(obj.users_dict) + else: + win = app.get_app_window('GroupchatConfig', account, obj.jid) + if win is not None: + win.present() + else: + GroupchatConfig(account, obj.jid, 'owner', obj.dataform) def handle_event_gc_decline(self, obj): gc_control = self.msg_win_mgr.get_gc_control(obj.room_jid, obj.account) @@ -1531,7 +1527,6 @@ class Interface: 'message-not-sent': [self.handle_event_msgnotsent], 'message-sent': [self.handle_event_msgsent], 'metacontacts-received': [self.handle_event_metacontacts], - 'muc-admin-received': [self.handle_event_gc_affiliation], 'muc-owner-received': [self.handle_event_gc_config], 'oauth2-credentials-required': [self.handle_oauth2_credentials], 'our-show': [self.handle_event_status],