471 lines
18 KiB
Python
471 lines
18 KiB
Python
|
# Copyright (C) 2003-2005 Vincent Hanquez <tab AT snarc.org>
|
||
|
# Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
|
||
|
# Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net>
|
||
|
# Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
|
||
|
# Travis Shirk <travis AT pobox.com>
|
||
|
# Copyright (C) 2005-2008 Nikos Kouremenos <kourem AT gmail.com>
|
||
|
# Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
|
||
|
# Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
|
||
|
# Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
|
||
|
# Julien Pivotto <roidelapluie AT gmail.com>
|
||
|
# Stephan Erb <steve-e AT h3c.de>
|
||
|
# Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
|
||
|
#
|
||
|
# 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 typing import Dict # pylint: disable=unused-import
|
||
|
from typing import List # pylint: disable=unused-import
|
||
|
from typing import Tuple # pylint: disable=unused-import
|
||
|
|
||
|
import uuid
|
||
|
import logging
|
||
|
|
||
|
from gi.repository import Gtk
|
||
|
from gi.repository import Gdk
|
||
|
|
||
|
from gajim.common.i18n import _
|
||
|
|
||
|
from gajim.common import app
|
||
|
from gajim.common import helpers
|
||
|
from gajim.common.exceptions import GajimGeneralException
|
||
|
|
||
|
from gajim.gui.dialogs import ErrorDialog
|
||
|
from gajim.gui.util import get_icon_name
|
||
|
from gajim.gui.util import get_builder
|
||
|
from gajim.gui.util import get_app_window
|
||
|
|
||
|
log = logging.getLogger('gajim.dialogs')
|
||
|
|
||
|
|
||
|
class EditGroupsDialog:
|
||
|
"""
|
||
|
Class for the edit group dialog window
|
||
|
"""
|
||
|
|
||
|
def __init__(self, list_):
|
||
|
"""
|
||
|
list_ is a list of (contact, account) tuples
|
||
|
"""
|
||
|
self.xml = get_builder('edit_groups_dialog.ui')
|
||
|
self.dialog = self.xml.get_object('edit_groups_dialog')
|
||
|
self.dialog.set_transient_for(app.interface.roster.window)
|
||
|
self.list_ = list_
|
||
|
self.changes_made = False
|
||
|
self.treeview = self.xml.get_object('groups_treeview')
|
||
|
if len(list_) == 1:
|
||
|
contact = list_[0][0]
|
||
|
self.xml.get_object('nickname_label').set_markup(
|
||
|
_('Contact name: <i>%s</i>') % contact.get_shown_name())
|
||
|
self.xml.get_object('jid_label').set_markup(
|
||
|
_('XMPP Address: <i>%s</i>') % contact.jid)
|
||
|
else:
|
||
|
self.xml.get_object('nickname_label').set_no_show_all(True)
|
||
|
self.xml.get_object('nickname_label').hide()
|
||
|
self.xml.get_object('jid_label').set_no_show_all(True)
|
||
|
self.xml.get_object('jid_label').hide()
|
||
|
|
||
|
self.xml.connect_signals(self)
|
||
|
self.init_list()
|
||
|
|
||
|
self.dialog.show_all()
|
||
|
if self.changes_made:
|
||
|
for (contact, account) in self.list_:
|
||
|
con = app.connections[account]
|
||
|
con.get_module('Roster').update_contact(
|
||
|
contact.jid, contact.name, contact.groups)
|
||
|
|
||
|
def on_edit_groups_dialog_response(self, widget, response_id):
|
||
|
if response_id == Gtk.ResponseType.CLOSE:
|
||
|
self.dialog.destroy()
|
||
|
|
||
|
def remove_group(self, group):
|
||
|
"""
|
||
|
Remove group group from all contacts and all their brothers
|
||
|
"""
|
||
|
for (contact, account) in self.list_:
|
||
|
app.interface.roster.remove_contact_from_groups(contact.jid,
|
||
|
account, [group])
|
||
|
|
||
|
# FIXME: Ugly workaround.
|
||
|
# pylint: disable=undefined-loop-variable
|
||
|
app.interface.roster.draw_group(_('General'), account)
|
||
|
|
||
|
def add_group(self, group):
|
||
|
"""
|
||
|
Add group group to all contacts and all their brothers
|
||
|
"""
|
||
|
for (contact, account) in self.list_:
|
||
|
app.interface.roster.add_contact_to_groups(contact.jid, account,
|
||
|
[group])
|
||
|
|
||
|
# FIXME: Ugly workaround.
|
||
|
# Maybe we haven't been in any group (defaults to General)
|
||
|
# pylint: disable=undefined-loop-variable
|
||
|
app.interface.roster.draw_group(_('General'), account)
|
||
|
|
||
|
def on_add_button_clicked(self, widget):
|
||
|
group = self.xml.get_object('group_entry').get_text()
|
||
|
if not group:
|
||
|
return
|
||
|
# Do not allow special groups
|
||
|
if group in helpers.special_groups:
|
||
|
return
|
||
|
# check if it already exists
|
||
|
model = self.treeview.get_model()
|
||
|
iter_ = model.get_iter_first()
|
||
|
while iter_:
|
||
|
if model.get_value(iter_, 0) == group:
|
||
|
return
|
||
|
iter_ = model.iter_next(iter_)
|
||
|
self.changes_made = True
|
||
|
model.append((group, True, False))
|
||
|
self.add_group(group)
|
||
|
self.init_list() # Re-draw list to sort new item
|
||
|
|
||
|
def group_toggled_cb(self, cell, path):
|
||
|
self.changes_made = True
|
||
|
model = self.treeview.get_model()
|
||
|
if model[path][2]:
|
||
|
model[path][2] = False
|
||
|
model[path][1] = True
|
||
|
else:
|
||
|
model[path][1] = not model[path][1]
|
||
|
group = model[path][0]
|
||
|
if model[path][1]:
|
||
|
self.add_group(group)
|
||
|
else:
|
||
|
self.remove_group(group)
|
||
|
|
||
|
def init_list(self):
|
||
|
store = Gtk.ListStore(str, bool, bool)
|
||
|
self.treeview.set_model(store)
|
||
|
for column in self.treeview.get_columns():
|
||
|
# Clear treeview when re-drawing
|
||
|
self.treeview.remove_column(column)
|
||
|
accounts = []
|
||
|
# Store groups in a list so we can sort them and the number of contacts in
|
||
|
# it
|
||
|
groups = {}
|
||
|
for (contact, account) in self.list_:
|
||
|
if account not in accounts:
|
||
|
accounts.append(account)
|
||
|
for g in app.groups[account].keys():
|
||
|
if g in groups:
|
||
|
continue
|
||
|
groups[g] = 0
|
||
|
c_groups = contact.groups
|
||
|
for g in c_groups:
|
||
|
groups[g] += 1
|
||
|
group_list = []
|
||
|
# Remove special groups if they are empty
|
||
|
for group in groups:
|
||
|
if group not in helpers.special_groups or groups[group] > 0:
|
||
|
group_list.append(group)
|
||
|
group_list.sort()
|
||
|
for group in group_list:
|
||
|
iter_ = store.append()
|
||
|
store.set(iter_, 0, group) # Group name
|
||
|
if groups[group] == 0:
|
||
|
store.set(iter_, 1, False)
|
||
|
else:
|
||
|
store.set(iter_, 1, True)
|
||
|
if groups[group] == len(self.list_):
|
||
|
# all contacts are in this group
|
||
|
store.set(iter_, 2, False)
|
||
|
else:
|
||
|
store.set(iter_, 2, True)
|
||
|
column = Gtk.TreeViewColumn(_('Group'))
|
||
|
column.set_expand(True)
|
||
|
self.treeview.append_column(column)
|
||
|
renderer = Gtk.CellRendererText()
|
||
|
column.pack_start(renderer, True)
|
||
|
column.add_attribute(renderer, 'text', 0)
|
||
|
|
||
|
column = Gtk.TreeViewColumn(_('In the group'))
|
||
|
column.set_expand(False)
|
||
|
self.treeview.append_column(column)
|
||
|
renderer = Gtk.CellRendererToggle()
|
||
|
column.pack_start(renderer, True)
|
||
|
renderer.set_property('activatable', True)
|
||
|
renderer.connect('toggled', self.group_toggled_cb)
|
||
|
column.add_attribute(renderer, 'active', 1)
|
||
|
column.add_attribute(renderer, 'inconsistent', 2)
|
||
|
|
||
|
|
||
|
class SynchroniseSelectAccountDialog:
|
||
|
def __init__(self, account):
|
||
|
# 'account' can be None if we are about to create our first one
|
||
|
if not app.account_is_available(account):
|
||
|
ErrorDialog(_('You are not connected to the server'),
|
||
|
_('Without a connection, you can not synchronise your contacts.'))
|
||
|
raise GajimGeneralException('You are not connected to the server')
|
||
|
self.account = account
|
||
|
self.xml = get_builder('synchronise_select_account_dialog.ui')
|
||
|
self.dialog = self.xml.get_object('synchronise_select_account_dialog')
|
||
|
self.dialog.set_transient_for(get_app_window('AccountsWindow'))
|
||
|
self.accounts_treeview = self.xml.get_object('accounts_treeview')
|
||
|
model = Gtk.ListStore(str, str, bool)
|
||
|
self.accounts_treeview.set_model(model)
|
||
|
# columns
|
||
|
renderer = Gtk.CellRendererText()
|
||
|
self.accounts_treeview.insert_column_with_attributes(-1, _('Name'),
|
||
|
renderer, text=0)
|
||
|
renderer = Gtk.CellRendererText()
|
||
|
self.accounts_treeview.insert_column_with_attributes(-1, _('Server'),
|
||
|
renderer, text=1)
|
||
|
|
||
|
self.xml.connect_signals(self)
|
||
|
self.init_accounts()
|
||
|
self.dialog.show_all()
|
||
|
|
||
|
def on_accounts_window_key_press_event(self, widget, event):
|
||
|
if event.keyval == Gdk.KEY_Escape:
|
||
|
self.window.destroy()
|
||
|
|
||
|
def init_accounts(self):
|
||
|
"""
|
||
|
Initialize listStore with existing accounts
|
||
|
"""
|
||
|
model = self.accounts_treeview.get_model()
|
||
|
model.clear()
|
||
|
for remote_account in app.connections:
|
||
|
if remote_account == self.account:
|
||
|
# Do not show the account we're sync'ing
|
||
|
continue
|
||
|
iter_ = model.append()
|
||
|
model.set(iter_, 0, remote_account, 1,
|
||
|
app.get_hostname_from_account(remote_account))
|
||
|
|
||
|
def on_cancel_button_clicked(self, widget):
|
||
|
self.dialog.destroy()
|
||
|
|
||
|
def on_ok_button_clicked(self, widget):
|
||
|
sel = self.accounts_treeview.get_selection()
|
||
|
(model, iter_) = sel.get_selected()
|
||
|
if not iter_:
|
||
|
return
|
||
|
remote_account = model.get_value(iter_, 0)
|
||
|
|
||
|
if not app.account_is_available(remote_account):
|
||
|
ErrorDialog(_('This account is not connected to the server'),
|
||
|
_('You cannot synchronize with an account unless it is connected.'))
|
||
|
return
|
||
|
|
||
|
try:
|
||
|
SynchroniseSelectContactsDialog(self.account, remote_account)
|
||
|
except GajimGeneralException:
|
||
|
# if we showed ErrorDialog, there will not be dialog instance
|
||
|
return
|
||
|
self.dialog.destroy()
|
||
|
|
||
|
@staticmethod
|
||
|
def on_destroy(widget):
|
||
|
del app.interface.instances['import_contacts']
|
||
|
|
||
|
|
||
|
class SynchroniseSelectContactsDialog:
|
||
|
def __init__(self, account, remote_account):
|
||
|
self.local_account = account
|
||
|
self.remote_account = remote_account
|
||
|
self.xml = get_builder('synchronise_select_contacts_dialog.ui')
|
||
|
self.dialog = self.xml.get_object('synchronise_select_contacts_dialog')
|
||
|
self.contacts_treeview = self.xml.get_object('contacts_treeview')
|
||
|
model = Gtk.ListStore(bool, str)
|
||
|
self.contacts_treeview.set_model(model)
|
||
|
# columns
|
||
|
renderer1 = Gtk.CellRendererToggle()
|
||
|
renderer1.set_property('activatable', True)
|
||
|
renderer1.connect('toggled', self.toggled_callback)
|
||
|
self.contacts_treeview.insert_column_with_attributes(-1,
|
||
|
_('Synchronise'), renderer1, active=0)
|
||
|
renderer2 = Gtk.CellRendererText()
|
||
|
self.contacts_treeview.insert_column_with_attributes(-1, _('Name'),
|
||
|
renderer2, text=1)
|
||
|
|
||
|
self.xml.connect_signals(self)
|
||
|
self.init_contacts()
|
||
|
self.dialog.show_all()
|
||
|
|
||
|
def toggled_callback(self, cell, path):
|
||
|
model = self.contacts_treeview.get_model()
|
||
|
iter_ = model.get_iter(path)
|
||
|
model[iter_][0] = not cell.get_active()
|
||
|
|
||
|
def on_contacts_window_key_press_event(self, widget, event):
|
||
|
if event.keyval == Gdk.KEY_Escape:
|
||
|
self.window.destroy()
|
||
|
|
||
|
def init_contacts(self):
|
||
|
"""
|
||
|
Initialize listStore with existing accounts
|
||
|
"""
|
||
|
model = self.contacts_treeview.get_model()
|
||
|
model.clear()
|
||
|
|
||
|
# recover local contacts
|
||
|
local_jid_list = app.contacts.get_contacts_jid_list(self.local_account)
|
||
|
|
||
|
remote_jid_list = app.contacts.get_contacts_jid_list(
|
||
|
self.remote_account)
|
||
|
for remote_jid in remote_jid_list:
|
||
|
if remote_jid not in local_jid_list:
|
||
|
iter_ = model.append()
|
||
|
model.set(iter_, 0, True, 1, remote_jid)
|
||
|
|
||
|
def on_cancel_button_clicked(self, widget):
|
||
|
self.dialog.destroy()
|
||
|
|
||
|
def on_ok_button_clicked(self, widget):
|
||
|
model = self.contacts_treeview.get_model()
|
||
|
iter_ = model.get_iter_first()
|
||
|
while iter_:
|
||
|
if model[iter_][0]:
|
||
|
# it is selected
|
||
|
remote_jid = model[iter_][1]
|
||
|
message = 'I\'m synchronizing my contacts from my %s account, could you please add this address to your contact list?' % \
|
||
|
app.get_hostname_from_account(self.remote_account)
|
||
|
remote_contact = app.contacts.get_first_contact_from_jid(
|
||
|
self.remote_account, remote_jid)
|
||
|
# keep same groups and same nickname
|
||
|
app.interface.roster.req_sub(self, remote_jid, message,
|
||
|
self.local_account, groups=remote_contact.groups,
|
||
|
nickname=remote_contact.name, auto_auth=True)
|
||
|
iter_ = model.iter_next(iter_)
|
||
|
self.dialog.destroy()
|
||
|
|
||
|
|
||
|
class TransformChatToMUC:
|
||
|
# Keep a reference on windows so garbage collector don't restroy them
|
||
|
instances = [] # type: List[TransformChatToMUC]
|
||
|
def __init__(self, account, jids, preselected=None):
|
||
|
"""
|
||
|
This window is used to transform a one-to-one chat to a MUC. We do 2
|
||
|
things: first select the server and then make a guests list
|
||
|
"""
|
||
|
|
||
|
self.instances.append(self)
|
||
|
self.account = account
|
||
|
self.auto_jids = jids
|
||
|
self.preselected_jids = preselected
|
||
|
|
||
|
self.xml = get_builder('chat_to_muc_window.ui')
|
||
|
self.window = self.xml.get_object('chat_to_muc_window')
|
||
|
|
||
|
for widget_to_add in ('invite_button', 'cancel_button',
|
||
|
'server_list_comboboxentry', 'guests_treeview', 'guests_store',
|
||
|
'server_and_guests_hseparator', 'server_select_label'):
|
||
|
self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add)
|
||
|
|
||
|
server_list = []
|
||
|
self.servers = Gtk.ListStore(str)
|
||
|
self.server_list_comboboxentry.set_model(self.servers)
|
||
|
cell = Gtk.CellRendererText()
|
||
|
self.server_list_comboboxentry.pack_start(cell, True)
|
||
|
self.server_list_comboboxentry.add_attribute(cell, 'text', 0)
|
||
|
|
||
|
# get the muc server of our server
|
||
|
con = app.connections[account]
|
||
|
service_jid = con.get_module('MUC').service_jid
|
||
|
if service_jid is not None:
|
||
|
server_list.append(str(service_jid))
|
||
|
|
||
|
# add servers or recently joined groupchats
|
||
|
recently_groupchat = app.settings.get_account_setting(
|
||
|
account, 'recent_groupchats').split()
|
||
|
for g in recently_groupchat:
|
||
|
server = app.get_server_from_jid(g)
|
||
|
if server not in server_list and not server.startswith('irc'):
|
||
|
server_list.append(server)
|
||
|
# add a default server
|
||
|
if not server_list:
|
||
|
server_list.append('conference.jabber.org')
|
||
|
|
||
|
for s in server_list:
|
||
|
self.servers.append([s])
|
||
|
|
||
|
self.server_list_comboboxentry.set_active(0)
|
||
|
|
||
|
# set treeview
|
||
|
# name, jid
|
||
|
|
||
|
self.guests_store.set_sort_column_id(1, Gtk.SortType.ASCENDING)
|
||
|
self.guests_treeview.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
|
||
|
|
||
|
# All contacts beside the following can be invited:
|
||
|
# transports, zeroconf contacts, minimized groupchats
|
||
|
def invitable(contact, contact_transport=None):
|
||
|
return (contact.jid not in self.auto_jids and
|
||
|
contact.jid != app.get_jid_from_account(account) and
|
||
|
contact.jid not in app.interface.minimized_controls[account] and
|
||
|
not contact.is_transport() and
|
||
|
contact_transport in ('jabber', None))
|
||
|
|
||
|
# set jabber id and pseudos
|
||
|
for account_ in app.contacts.get_accounts():
|
||
|
if app.connections[account_].is_zeroconf:
|
||
|
continue
|
||
|
for jid in app.contacts.get_jid_list(account_):
|
||
|
contact = app.contacts.get_contact_with_highest_priority(
|
||
|
account_, jid)
|
||
|
contact_transport = app.get_transport_name_from_jid(jid)
|
||
|
# Add contact if it can be invited
|
||
|
if invitable(contact, contact_transport) and \
|
||
|
contact.show not in ('offline', 'error'):
|
||
|
icon_name = get_icon_name(contact.show)
|
||
|
name = contact.name
|
||
|
if name == '':
|
||
|
name = jid.split('@')[0]
|
||
|
iter_ = self.guests_store.append([icon_name, name, jid])
|
||
|
# preselect treeview rows
|
||
|
if self.preselected_jids and jid in self.preselected_jids:
|
||
|
path = self.guests_store.get_path(iter_)
|
||
|
self.guests_treeview.get_selection().select_path(path)
|
||
|
|
||
|
# show all
|
||
|
self.window.show_all()
|
||
|
|
||
|
self.xml.connect_signals(self)
|
||
|
|
||
|
def on_chat_to_muc_window_destroy(self, widget):
|
||
|
self.instances.remove(self)
|
||
|
|
||
|
def on_chat_to_muc_window_key_press_event(self, widget, event):
|
||
|
if event.keyval == Gdk.KEY_Escape: # ESCAPE
|
||
|
self.window.destroy()
|
||
|
|
||
|
def on_invite_button_clicked(self, widget):
|
||
|
row = self.server_list_comboboxentry.get_child().get_displayed_row()
|
||
|
model = self.server_list_comboboxentry.get_model()
|
||
|
server = model[row][0].strip()
|
||
|
if server == '':
|
||
|
return
|
||
|
|
||
|
guest_list = []
|
||
|
guests = self.guests_treeview.get_selection().get_selected_rows()
|
||
|
for guest in guests[1]:
|
||
|
iter_ = self.guests_store.get_iter(guest)
|
||
|
guest_list.append(self.guests_store[iter_][2])
|
||
|
for guest in self.auto_jids:
|
||
|
guest_list.append(guest)
|
||
|
room_jid = str(uuid.uuid4()) + '@' + server
|
||
|
app.automatic_rooms[self.account][room_jid] = {}
|
||
|
app.automatic_rooms[self.account][room_jid]['invities'] = guest_list
|
||
|
app.automatic_rooms[self.account][room_jid]['continue_tag'] = True
|
||
|
app.interface.create_groupchat(self.account, room_jid)
|
||
|
self.window.destroy()
|
||
|
|
||
|
def on_cancel_button_clicked(self, widget):
|
||
|
self.window.destroy()
|