2018-10-28 15:36:43 +01:00
|
|
|
# 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/>.
|
|
|
|
|
2018-11-02 22:24:28 +01:00
|
|
|
import logging
|
|
|
|
|
|
|
|
import nbxmpp
|
2019-02-11 21:05:20 +01:00
|
|
|
from nbxmpp.util import is_error_result
|
2019-01-26 21:25:33 +01:00
|
|
|
from gi.repository import Gdk
|
2018-10-28 15:36:43 +01:00
|
|
|
from gi.repository import Gtk
|
|
|
|
|
|
|
|
from gajim.common import app
|
|
|
|
from gajim.common.i18n import _
|
2018-11-02 22:24:28 +01:00
|
|
|
from gajim.common.const import MUCUser
|
|
|
|
from gajim.common.caps_cache import muc_caps_cache
|
2018-10-28 15:36:43 +01:00
|
|
|
|
2018-11-02 22:24:28 +01:00
|
|
|
from gajim.gtk.dialogs import ErrorDialog
|
|
|
|
from gajim.gtk.dataform import DataFormWidget
|
2018-10-28 15:36:43 +01:00
|
|
|
from gajim.gtk.util import get_builder
|
2019-01-26 21:25:33 +01:00
|
|
|
from gajim.gtk.util import ensure_not_destroyed
|
2018-11-02 22:24:28 +01:00
|
|
|
|
|
|
|
log = logging.getLogger('gajim.gtk.groupchat_config')
|
2018-10-28 15:36:43 +01:00
|
|
|
|
|
|
|
|
2018-11-02 22:24:28 +01:00
|
|
|
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'))
|
2019-01-26 21:25:33 +01:00
|
|
|
self._destroyed = False
|
2018-11-02 22:24:28 +01:00
|
|
|
|
2018-10-28 15:36:43 +01:00
|
|
|
self.account = account
|
2018-11-02 22:24:28 +01:00
|
|
|
self.jid = jid
|
|
|
|
self._own_affiliation = own_affiliation
|
2018-10-28 15:36:43 +01:00
|
|
|
|
2018-11-02 22:24:28 +01:00
|
|
|
self._ui = get_builder('groupchat_config.ui')
|
|
|
|
self.add(self._ui.grid)
|
2018-10-28 15:36:43 +01:00
|
|
|
|
2018-11-02 22:24:28 +01:00
|
|
|
# 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('')
|
2018-10-28 15:36:43 +01:00
|
|
|
|
2018-11-02 22:24:28 +01:00
|
|
|
visible = muc_caps_cache.supports(jid, nbxmpp.NS_REGISTER)
|
|
|
|
self._ui.reserved_name_column.set_visible(visible)
|
2018-11-04 17:45:27 +01:00
|
|
|
self._ui.info_button.set_sensitive(False)
|
2018-10-28 15:36:43 +01:00
|
|
|
|
2018-11-02 22:24:28 +01:00
|
|
|
self._form = form
|
|
|
|
self._affiliations = {}
|
|
|
|
self._new_affiliations = {}
|
2018-10-28 15:36:43 +01:00
|
|
|
|
2018-11-02 22:24:28 +01:00
|
|
|
con = app.connections[self.account]
|
|
|
|
for affiliation in ('owner', 'admin', 'member', 'outcast'):
|
|
|
|
con.get_module('MUC').get_affiliation(
|
|
|
|
self.jid,
|
|
|
|
affiliation,
|
2019-02-11 21:05:20 +01:00
|
|
|
callback=self._on_affiliations_received,
|
|
|
|
user_data=affiliation)
|
2018-10-28 15:36:43 +01:00
|
|
|
|
2018-11-02 22:24:28 +01:00
|
|
|
if form is not None:
|
|
|
|
self._ui.stack.set_visible_child_name('config')
|
|
|
|
self._data_form_widget = DataFormWidget(form)
|
2018-11-04 17:45:27 +01:00
|
|
|
self._data_form_widget.connect('is-valid', self._on_is_valid)
|
|
|
|
self._data_form_widget.validate()
|
2018-11-02 22:24:28 +01:00
|
|
|
self._ui.config_grid.add(self._data_form_widget)
|
|
|
|
else:
|
|
|
|
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)
|
2018-11-03 13:43:35 +01:00
|
|
|
self.connect('delete-event', self._cancel)
|
2019-01-26 21:25:33 +01:00
|
|
|
self.connect('destroy', self._on_destroy)
|
|
|
|
self.connect('key-press-event', self._on_key_press)
|
2018-11-02 22:24:28 +01:00
|
|
|
self.show_all()
|
|
|
|
self._ui.stack.notify('visible-child-name')
|
|
|
|
|
2018-11-04 17:45:27 +01:00
|
|
|
def _on_is_valid(self, _widget, is_valid):
|
|
|
|
self._ui.ok_button.set_sensitive(is_valid)
|
|
|
|
|
2018-11-02 22:24:28 +01:00
|
|
|
def _get_current_treeview(self):
|
|
|
|
page_name = self._ui.stack.get_visible_child_name()
|
|
|
|
return getattr(self._ui, '%s_treeview' % page_name)
|
|
|
|
|
|
|
|
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'
|
2018-10-28 15:36:43 +01:00
|
|
|
else:
|
2018-11-02 22:24:28 +01:00
|
|
|
affiliation_edit, jid_edit = self._allowed_to_edit('member')
|
|
|
|
text = _('Member')
|
|
|
|
affiliation = 'member'
|
|
|
|
|
|
|
|
treeview = self._get_current_treeview()
|
2018-11-03 13:43:35 +01:00
|
|
|
iter_ = treeview.get_model().append([None,
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
affiliation,
|
|
|
|
text,
|
|
|
|
affiliation_edit,
|
|
|
|
jid_edit])
|
2018-11-02 22:24:28 +01:00
|
|
|
|
|
|
|
# Scroll to added row
|
2018-11-03 13:43:35 +01:00
|
|
|
path = treeview.get_model().get_path(iter_)
|
|
|
|
treeview.scroll_to_cell(path, None, False, 0, 0)
|
2018-11-02 22:24:28 +01:00
|
|
|
treeview.get_selection().unselect_all()
|
2018-11-03 13:43:35 +01:00
|
|
|
treeview.get_selection().select_path(path)
|
2018-11-02 22:24:28 +01:00
|
|
|
|
|
|
|
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 = []
|
2018-10-28 15:36:43 +01:00
|
|
|
for path in paths:
|
2018-11-02 22:24:28 +01:00
|
|
|
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())
|
2018-10-28 15:36:43 +01:00
|
|
|
model.remove(iter_)
|
2018-11-02 22:24:28 +01:00
|
|
|
|
|
|
|
def _on_jid_edited(self, _renderer, path, new_text):
|
|
|
|
old_text = self._ui.affiliation_store[path][MUCUser.JID]
|
|
|
|
if new_text == old_text:
|
|
|
|
return
|
|
|
|
|
|
|
|
if self._jid_exists(new_text):
|
|
|
|
self._raise_error()
|
|
|
|
return
|
|
|
|
|
|
|
|
self._ui.affiliation_store[path][MUCUser.JID] = new_text
|
|
|
|
|
|
|
|
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)
|
2018-11-04 17:45:27 +01:00
|
|
|
self._ui.info_button.set_sensitive(page_name == 'outcast')
|
2018-11-02 22:24:28 +01:00
|
|
|
|
|
|
|
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()
|
2018-10-28 15:36:43 +01:00
|
|
|
con = app.connections[self.account]
|
2018-11-02 22:24:28 +01:00
|
|
|
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
|
|
|
|
|
2019-01-26 21:25:33 +01:00
|
|
|
def _on_key_press(self, _widget, event):
|
|
|
|
if event.keyval == Gdk.KEY_Escape:
|
|
|
|
self._on_cancel()
|
|
|
|
|
2018-11-02 22:24:28 +01:00
|
|
|
def _on_cancel(self, *args):
|
2018-11-03 13:43:35 +01:00
|
|
|
self._cancel()
|
|
|
|
self.destroy()
|
|
|
|
|
|
|
|
def _cancel(self, *args):
|
2018-11-02 22:24:28 +01:00
|
|
|
if self._form and self._own_affiliation == 'owner':
|
|
|
|
con = app.connections[self.account]
|
|
|
|
con.get_module('MUC').cancel_config(self.jid)
|
|
|
|
|
2019-01-26 21:25:33 +01:00
|
|
|
def _on_destroy(self, *args):
|
|
|
|
self._destroyed = True
|
|
|
|
|
2018-11-02 22:24:28 +01:00
|
|
|
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)
|
|
|
|
|
2019-01-26 21:25:33 +01:00
|
|
|
@ensure_not_destroyed
|
2019-02-11 21:05:20 +01:00
|
|
|
def _on_affiliations_received(self, result, affiliation):
|
|
|
|
if is_error_result(result):
|
2018-12-25 20:30:36 +01:00
|
|
|
log.info('Error while requesting %s affiliations: %s',
|
2019-02-11 21:05:20 +01:00
|
|
|
affiliation, result)
|
2018-12-25 20:30:36 +01:00
|
|
|
return
|
2018-11-02 22:24:28 +01:00
|
|
|
|
2019-02-11 21:05:20 +01:00
|
|
|
if affiliation == 'outcast':
|
2018-11-02 22:24:28 +01:00
|
|
|
self._ui.stack.get_child_by_name('outcast').show()
|
|
|
|
|
2018-12-25 20:30:36 +01:00
|
|
|
for jid, attrs in result.users.items():
|
2019-02-11 21:05:20 +01:00
|
|
|
affiliation_edit, jid_edit = self._allowed_to_edit(affiliation)
|
|
|
|
if affiliation == 'outcast':
|
2018-11-02 22:24:28 +01:00
|
|
|
reason = attrs.get('reason')
|
|
|
|
self._ui.outcast_store.append(
|
|
|
|
[jid,
|
|
|
|
reason,
|
|
|
|
None,
|
2019-02-11 21:05:20 +01:00
|
|
|
affiliation,
|
2018-11-02 22:24:28 +01:00
|
|
|
None,
|
|
|
|
affiliation_edit,
|
|
|
|
jid_edit])
|
2019-02-11 21:05:20 +01:00
|
|
|
self._affiliations[jid] = {'affiliation': affiliation,
|
2018-11-02 22:24:28 +01:00
|
|
|
'reason': reason}
|
|
|
|
else:
|
|
|
|
nick = attrs.get('nick')
|
|
|
|
role = attrs.get('role')
|
|
|
|
self._ui.affiliation_store.append(
|
|
|
|
[jid,
|
|
|
|
nick,
|
|
|
|
role,
|
2019-02-11 21:05:20 +01:00
|
|
|
affiliation,
|
|
|
|
_(affiliation.capitalize()),
|
2018-11-02 22:24:28 +01:00
|
|
|
affiliation_edit,
|
|
|
|
jid_edit])
|
2019-02-11 21:05:20 +01:00
|
|
|
self._affiliations[jid] = {'affiliation': affiliation,
|
2018-11-02 22:24:28 +01:00
|
|
|
'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'))
|