gajim3/gajim/dialogs.py

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()