Move some dialogs into gtk module

This commit is contained in:
Philipp Hörist 2018-07-16 23:22:33 +02:00
parent bff5f14b92
commit 3a3b5aff20
42 changed files with 3836 additions and 3369 deletions

View file

@ -6,7 +6,6 @@ from gajim.common import app
from gajim import gtkgui_helpers from gajim import gtkgui_helpers
from gajim import gui_menu_builder from gajim import gui_menu_builder
from gajim.common import passwords from gajim.common import passwords
from gajim import dialogs
from gajim import config from gajim import config
from gajim.common import helpers from gajim.common import helpers
from gajim.common import ged from gajim.common import ged
@ -14,6 +13,8 @@ from gajim.common.connection import Connection
from gajim.common.zeroconf.connection_zeroconf import ConnectionZeroconf from gajim.common.zeroconf.connection_zeroconf import ConnectionZeroconf
from gajim.options_dialog import OptionsDialog, OptionsBox from gajim.options_dialog import OptionsDialog, OptionsBox
from gajim.common.const import Option, OptionKind, OptionType from gajim.common.const import Option, OptionKind, OptionType
from gajim.gtk import ConfirmationDialog
from gajim.gtk import YesNoDialog
class AccountsWindow(Gtk.ApplicationWindow): class AccountsWindow(Gtk.ApplicationWindow):
@ -143,7 +144,7 @@ class AccountsWindow(Gtk.ApplicationWindow):
account, 'offline', _('Be right back.')) account, 'offline', _('Be right back.'))
GLib.timeout_add(500, login, account, show_before, status_before) GLib.timeout_add(500, login, account, show_before, status_before)
dialogs.YesNoDialog( YesNoDialog(
_('Relogin now?'), _('Relogin now?'),
_('If you want all the changes to apply instantly, ' _('If you want all the changes to apply instantly, '
'you must relogin.'), 'you must relogin.'),
@ -197,7 +198,7 @@ class AccountsWindow(Gtk.ApplicationWindow):
app.interface.instances[account]['remove_account'] = \ app.interface.instances[account]['remove_account'] = \
config.RemoveAccountWindow(account) config.RemoveAccountWindow(account)
if win_opened: if win_opened:
dialogs.ConfirmationDialog( ConfirmationDialog(
_('You have opened chat in account %s') % account, _('You have opened chat in account %s') % account,
_('All chat and groupchat windows will be closed. ' _('All chat and groupchat windows will be closed. '
'Do you want to continue?'), 'Do you want to continue?'),

View file

@ -33,7 +33,7 @@ from gajim.common import dataforms
from gajim.common import ged from gajim.common import ged
from gajim import gtkgui_helpers from gajim import gtkgui_helpers
from gajim import dialogs from gajim.gtk import HigDialog
from gajim import dataforms_widget from gajim import dataforms_widget
class CommandWindow: class CommandWindow:
@ -322,7 +322,7 @@ class CommandWindow:
dialog.destroy() dialog.destroy()
cb() cb()
dialog = dialogs.HigDialog(self.window, Gtk.MessageType.WARNING, dialog = HigDialog(self.window, Gtk.MessageType.WARNING,
Gtk.ButtonsType.YES_NO, _('Cancel confirmation'), Gtk.ButtonsType.YES_NO, _('Cancel confirmation'),
_('You are in process of executing command. Do you really want to ' _('You are in process of executing command. Do you really want to '
'cancel it?'), on_response_yes=on_yes) 'cancel it?'), on_response_yes=on_yes)

View file

@ -28,14 +28,21 @@ import gajim.plugins.gui
from gajim import history_window from gajim import history_window
from gajim import disco from gajim import disco
from gajim.gtk.history_sync import HistorySyncAssistant from gajim.gtk.history_sync import HistorySyncAssistant
from gajim.server_info import ServerInfoDialog from gajim.gtk.server_info import ServerInfoDialog
from gajim.gtk.mam_preferences import MamPreferences from gajim.gtk.mam_preferences import MamPreferences
from gajim.gtk import JoinGroupchatWindow
from gajim.gtk import StartChatDialog
from gajim.gtk import AddNewContactWindow
from gajim.gtk import SingleMessageWindow
from gajim.gtk import XMLConsoleWindow
from gajim.gtk import AboutDialog
from gajim.gtk import PrivacyListsWindow
# General Actions # General Actions
def on_add_contact_jid(action, param): def on_add_contact_jid(action, param):
dialogs.AddNewContactWindow(None, param.get_string()) AddNewContactWindow(None, param.get_string())
# Application Menu Actions # Application Menu Actions
@ -79,7 +86,7 @@ def on_new_chat(action, param):
if 'start_chat' in app.interface.instances: if 'start_chat' in app.interface.instances:
app.interface.instances['start_chat'].present() app.interface.instances['start_chat'].present()
else: else:
app.interface.instances['start_chat'] = dialogs.StartChatDialog() app.interface.instances['start_chat'] = StartChatDialog()
# Accounts Actions # Accounts Actions
@ -103,7 +110,7 @@ def on_send_server_message(action, param):
account = param.get_string() account = param.get_string()
server = app.config.get_per('accounts', account, 'hostname') server = app.config.get_per('accounts', account, 'hostname')
server += '/announce/online' server += '/announce/online'
dialogs.SingleMessageWindow(account, server, 'send') SingleMessageWindow(account, server, 'send')
def on_service_disco(action, param): def on_service_disco(action, param):
@ -127,23 +134,23 @@ def on_join_gc(action, param):
return return
else: else:
account = param.get_string() account = param.get_string()
window = app.get_app_window(dialogs.JoinGroupchatWindow) window = app.get_app_window(JoinGroupchatWindow)
if window is None: if window is None:
dialogs.JoinGroupchatWindow(account, None) JoinGroupchatWindow(account, None)
else: else:
window.present() window.present()
def on_add_contact(action, param): def on_add_contact(action, param):
window = app.get_app_window(dialogs.AddNewContactWindow) window = app.get_app_window(AddNewContactWindow)
if window is None: if window is None:
dialogs.AddNewContactWindow(param.get_string()) AddNewContactWindow(param.get_string())
else: else:
window.present() window.present()
def on_single_message(action, param): def on_single_message(action, param):
dialogs.SingleMessageWindow(param.get_string(), action='send') SingleMessageWindow(param.get_string(), action='send')
def on_merge_accounts(action, param): def on_merge_accounts(action, param):
@ -206,7 +213,7 @@ def on_privacy_lists(action, param):
interface.instances[account]['privacy_lists'].window.present() interface.instances[account]['privacy_lists'].window.present()
else: else:
interface.instances[account]['privacy_lists'] = \ interface.instances[account]['privacy_lists'] = \
dialogs.PrivacyListsWindow(account) PrivacyListsWindow(account)
def on_server_info(action, param): def on_server_info(action, param):
@ -224,7 +231,7 @@ def on_xml_console(action, param):
interface.instances[account]['xml_console'].present() interface.instances[account]['xml_console'].present()
else: else:
interface.instances[account]['xml_console'] = \ interface.instances[account]['xml_console'] = \
dialogs.XMLConsoleWindow(account) XMLConsoleWindow(account)
def on_manage_proxies(action, param): def on_manage_proxies(action, param):
@ -241,14 +248,14 @@ def on_set_motd(action, param):
account = param.get_string() account = param.get_string()
server = app.config.get_per('accounts', account, 'hostname') server = app.config.get_per('accounts', account, 'hostname')
server += '/announce/motd' server += '/announce/motd'
dialogs.SingleMessageWindow(account, server, 'send') SingleMessageWindow(account, server, 'send')
def on_update_motd(action, param): def on_update_motd(action, param):
account = param.get_string() account = param.get_string()
server = app.config.get_per('accounts', account, 'hostname') server = app.config.get_per('accounts', account, 'hostname')
server += '/announce/motd/update' server += '/announce/motd/update'
dialogs.SingleMessageWindow(account, server, 'send') SingleMessageWindow(account, server, 'send')
def on_delete_motd(action, param): def on_delete_motd(action, param):
@ -279,7 +286,7 @@ def on_features(action, param):
def on_about(action, param): def on_about(action, param):
dialogs.AboutDialog() AboutDialog()
# View Actions # View Actions

View file

@ -37,6 +37,8 @@ from gajim import gtkgui_helpers
from gajim import gui_menu_builder from gajim import gui_menu_builder
from gajim import message_control from gajim import message_control
from gajim import dialogs from gajim import dialogs
from gajim.gtk import ConfirmationDialog
from gajim.gtk import AddNewContactWindow
from gajim.common import app from gajim.common import app
from gajim.common import helpers from gajim.common import helpers
@ -337,7 +339,7 @@ class ChatControl(ChatControlBase):
'information-' + self.control_id).set_enabled(online) 'information-' + self.control_id).set_enabled(online)
def _on_add_to_roster(self, action, param): def _on_add_to_roster(self, action, param):
dialogs.AddNewContactWindow(self.account, self.contact.jid) AddNewContactWindow(self.account, self.contact.jid)
def _on_information(self, action, param): def _on_information(self, action, param):
app.interface.roster.on_info(None, self.contact, self.account) app.interface.roster.on_info(None, self.contact, self.account)
@ -1208,7 +1210,7 @@ class ChatControl(ChatControlBase):
def on_cancel(): def on_cancel():
on_no(self) on_no(self)
dialogs.ConfirmationDialog( ConfirmationDialog(
#%s is being replaced in the code with JID #%s is being replaced in the code with JID
_('You just received a new message from "%s"') % \ _('You just received a new message from "%s"') % \
self.contact.jid, self.contact.jid,

View file

@ -37,7 +37,7 @@ from gi.repository import Gio
from gajim import gtkgui_helpers from gajim import gtkgui_helpers
from gajim import message_control from gajim import message_control
from gajim import dialogs from gajim.gtk import NonModalConfirmationDialog
from gajim import history_window from gajim import history_window
from gajim import notify from gajim import notify
import re import re
@ -1160,7 +1160,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
prim_text = _('Really send file?') prim_text = _('Really send file?')
sec_text = _('If you send a file to %s, your real JID will ' sec_text = _('If you send a file to %s, your real JID will '
'be revealed.') % gc_contact.name 'be revealed.') % gc_contact.name
dialog = dialogs.NonModalConfirmationDialog(prim_text, dialog = NonModalConfirmationDialog(prim_text,
sec_text, on_response_ok=(_on_ok, gc_contact)) sec_text, on_response_ok=(_on_ok, gc_contact))
dialog.popup() dialog.popup()
return return

View file

@ -955,7 +955,7 @@ class GcMessageReceivedEvent(nec.NetworkIncomingEvent):
if self.msg_obj.form_node: if self.msg_obj.form_node:
# It could be a voice request. See # It could be a voice request. See
# http://www.xmpp.org/extensions/xep-0045.html#voiceapprove # http://www.xmpp.org/extensions/xep-0045.html#voiceapprove
from gajim.dialogs import SingleMessageWindow from gajim.gtk import SingleMessageWindow
SingleMessageWindow( SingleMessageWindow(
self.conn.name, self.fjid, self.conn.name, self.fjid,
action='receive', from_whom=self.fjid, action='receive', from_whom=self.fjid,

View file

@ -52,6 +52,12 @@ from gajim.gajim_themes_window import GajimThemesWindow
from gajim.advanced_configuration_window import AdvancedConfigurationWindow from gajim.advanced_configuration_window import AdvancedConfigurationWindow
from gajim import dataforms_widget from gajim import dataforms_widget
from gajim import gui_menu_builder from gajim import gui_menu_builder
from gajim.gtk import AspellDictError
from gajim.gtk import ConfirmationDialog
from gajim.gtk import ConfirmationDialogDoubleRadio
from gajim.gtk import ErrorDialog
from gajim.gtk import InputDialog
from gajim.gtk import WarningDialog
from gajim.common import helpers from gajim.common import helpers
from gajim.common import app from gajim.common import app
@ -653,7 +659,7 @@ class PreferencesWindow:
if gspell_lang is None: if gspell_lang is None:
gspell_lang = Gspell.language_get_default() gspell_lang = Gspell.language_get_default()
if gspell_lang is None: if gspell_lang is None:
dialogs.AspellDictError(lang) AspellDictError(lang)
app.config.set('use_speller', False) app.config.set('use_speller', False)
widget.set_active(False) widget.set_active(False)
else: else:
@ -1682,7 +1688,7 @@ class GroupchatConfigWindow:
return return
model = self.affiliation_treeview[affiliation].get_model() model = self.affiliation_treeview[affiliation].get_model()
model.append((jid, '', '', '')) model.append((jid, '', '', ''))
dialogs.InputDialog(title, prompt, ok_handler=on_ok) InputDialog(title, prompt, ok_handler=on_ok)
def on_remove_button_clicked(self, widget, affiliation): def on_remove_button_clicked(self, widget, affiliation):
selection = self.affiliation_treeview[affiliation].get_selection() selection = self.affiliation_treeview[affiliation].get_selection()
@ -1785,7 +1791,7 @@ class RemoveAccountWindow:
app.connections[self.account].change_status('offline', 'offline') app.connections[self.account].change_status('offline', 'offline')
if self.remove_and_unregister_radiobutton.get_active(): if self.remove_and_unregister_radiobutton.get_active():
if not self.account in app.connections: if not self.account in app.connections:
dialogs.ErrorDialog( ErrorDialog(
_('Account is disabled'), _('Account is disabled'),
_('To unregister from a server, account must be ' _('To unregister from a server, account must be '
'enabled.'), 'enabled.'),
@ -1813,7 +1819,7 @@ class RemoveAccountWindow:
if self.account in app.connections and \ if self.account in app.connections and \
app.connections[self.account].connected: app.connections[self.account].connected:
dialogs.ConfirmationDialog( ConfirmationDialog(
_('Account "%s" is connected to the server') % self.account, _('Account "%s" is connected to the server') % self.account,
_('If you remove it, the connection will be lost.'), _('If you remove it, the connection will be lost.'),
on_response_ok=remove, on_response_ok=remove,
@ -1829,7 +1835,7 @@ class RemoveAccountWindow:
# action of unregistration has failed, we don't remove the account # action of unregistration has failed, we don't remove the account
# Error message is send by connect_and_auth() # Error message is send by connect_and_auth()
if not res: if not res:
dialogs.ConfirmationDialogDoubleRadio( ConfirmationDialogDoubleRadio(
_('Connection to server %s failed') % self.account, _('Connection to server %s failed') % self.account,
_('What would you like to do?'), _('What would you like to do?'),
_('Remove only from Gajim'), _('Remove only from Gajim'),
@ -2036,7 +2042,7 @@ class ManageBookmarksWindow:
if self.server_entry.get_text() == '' or \ if self.server_entry.get_text() == '' or \
self.room_entry.get_text() == '': self.room_entry.get_text() == '':
dialogs.ErrorDialog(_('This bookmark has invalid data'), ErrorDialog(_('This bookmark has invalid data'),
_('Please be sure to fill out server and room fields or remove this' _('Please be sure to fill out server and room fields or remove this'
' bookmark.')) ' bookmark.'))
return False return False
@ -2166,7 +2172,7 @@ class ManageBookmarksWindow:
try: try:
nick = helpers.parse_resource(nick) nick = helpers.parse_resource(nick)
except helpers.InvalidFormat: except helpers.InvalidFormat:
dialogs.ErrorDialog(_('Invalid nickname'), ErrorDialog(_('Invalid nickname'),
_('Character not allowed'), transient_for=self.window) _('Character not allowed'), transient_for=self.window)
self.nick_entry.set_text(model[iter_][6]) self.nick_entry.set_text(model[iter_][6])
return True return True
@ -2182,7 +2188,7 @@ class ManageBookmarksWindow:
if not server: if not server:
return return
if '@' in server: if '@' in server:
dialogs.ErrorDialog(_('Invalid server'), ErrorDialog(_('Invalid server'),
_('Character not allowed'), transient_for=self.window) _('Character not allowed'), transient_for=self.window)
widget.set_text(server.replace('@', '')) widget.set_text(server.replace('@', ''))
@ -2193,7 +2199,7 @@ class ManageBookmarksWindow:
try: try:
room_jid = helpers.parse_jid(room_jid) room_jid = helpers.parse_jid(room_jid)
except helpers.InvalidFormat as e: except helpers.InvalidFormat as e:
dialogs.ErrorDialog(_('Invalid server'), ErrorDialog(_('Invalid server'),
_('Character not allowed'), transient_for=self.window) _('Character not allowed'), transient_for=self.window)
self.server_entry.set_text(model[iter_][2].split('@')[1]) self.server_entry.set_text(model[iter_][2].split('@')[1])
return True return True
@ -2221,7 +2227,7 @@ class ManageBookmarksWindow:
try: try:
room_jid = helpers.parse_jid(room_jid) room_jid = helpers.parse_jid(room_jid)
except helpers.InvalidFormat: except helpers.InvalidFormat:
dialogs.ErrorDialog(_('Invalid room'), ErrorDialog(_('Invalid room'),
_('Character not allowed'), transient_for=self.window) _('Character not allowed'), transient_for=self.window)
return True return True
model[iter_][2] = room_jid model[iter_][2] = room_jid
@ -2439,7 +2445,7 @@ class AccountCreationWizardWindow:
pritext = _('Invalid username') pritext = _('Invalid username')
sectext = _( sectext = _(
'You must provide a username to configure this account.') 'You must provide a username to configure this account.')
dialogs.ErrorDialog(pritext, sectext) ErrorDialog(pritext, sectext)
return return
server = self.xml.get_object('server_comboboxtext_entry').\ server = self.xml.get_object('server_comboboxtext_entry').\
get_text().strip() get_text().strip()
@ -2457,7 +2463,7 @@ class AccountCreationWizardWindow:
jid = helpers.parse_jid(jid) jid = helpers.parse_jid(jid)
except helpers.InvalidFormat as s: except helpers.InvalidFormat as s:
pritext = _('Invalid JID') pritext = _('Invalid JID')
dialogs.ErrorDialog(pritext, str(s)) ErrorDialog(pritext, str(s))
return return
self.account = server self.account = server
@ -2478,7 +2484,7 @@ class AccountCreationWizardWindow:
get_text() get_text()
if not server: if not server:
dialogs.ErrorDialog(_('Invalid server'), ErrorDialog(_('Invalid server'),
_('Please provide a server on which you want to register.')) _('Please provide a server on which you want to register.'))
return return
self.account = server self.account = server
@ -2502,7 +2508,7 @@ class AccountCreationWizardWindow:
try: try:
custom_port = int(custom_port) custom_port = int(custom_port)
except Exception: except Exception:
dialogs.ErrorDialog(_('Invalid entry'), ErrorDialog(_('Invalid entry'),
_('Custom port must be a port number.')) _('Custom port must be a port number.'))
return return
config['custom_port'] = custom_port config['custom_port'] = custom_port
@ -2535,7 +2541,7 @@ class AccountCreationWizardWindow:
with open(my_ca_certs) as f: with open(my_ca_certs) as f:
certs = f.read() certs = f.read()
if self.ssl_cert in certs: if self.ssl_cert in certs:
dialogs.ErrorDialog(_('Certificate Already in File'), ErrorDialog(_('Certificate Already in File'),
_('This certificate is already in file %s, so it\'s ' _('This certificate is already in file %s, so it\'s '
'not added again.') % my_ca_certs) 'not added again.') % my_ca_certs)
else: else:
@ -2766,7 +2772,7 @@ class AccountCreationWizardWindow:
def save_account(self, login, server, savepass, password, anonymous=False): def save_account(self, login, server, savepass, password, anonymous=False):
if self.account in app.connections: if self.account in app.connections:
dialogs.ErrorDialog(_('Account name is in use'), ErrorDialog(_('Account name is in use'),
_('You already have an account using this name.')) _('You already have an account using this name.'))
return return
con = connection.Connection(self.account) con = connection.Connection(self.account)
@ -2899,7 +2905,7 @@ class ManagePEPServicesWindow:
def node_not_removed(self, jid, node, msg): def node_not_removed(self, jid, node, msg):
if jid != app.get_jid_from_account(self.account): if jid != app.get_jid_from_account(self.account):
return return
dialogs.WarningDialog(_('PEP node was not removed'), WarningDialog(_('PEP node was not removed'),
_('PEP node %(node)s was not removed: %(message)s') % {'node': node, _('PEP node %(node)s was not removed: %(message)s') % {'node': node,
'message': msg}) 'message': msg})

View file

@ -38,7 +38,11 @@ from gajim import dialogs
import queue import queue
import urllib import urllib
from gajim import gtkgui_helpers from gajim.gtk import AddNewContactWindow
from gajim.gtk import util
from gajim.gtk.util import load_icon
from gajim.gtk.util import get_builder
from gajim.gtk.util import get_cursor
from gajim.common import app from gajim.common import app
from gajim.common import helpers from gajim.common import helpers
from gajim.common import i18n from gajim.common import i18n
@ -164,11 +168,6 @@ class ConversationTextview(GObject.GObject):
) )
) )
MESSAGE_CORRECTED_PIXBUF = gtkgui_helpers.get_icon_pixmap(
'document-edit-symbolic')
MESSAGE_ENCRYPTED_PIXBUF = gtkgui_helpers.get_icon_pixmap(
'channel-secure-croped-symbolic')
def __init__(self, account, used_in_history_window = False): def __init__(self, account, used_in_history_window = False):
""" """
If used_in_history_window is True, then we do not show Clear menuitem in If used_in_history_window is True, then we do not show Clear menuitem in
@ -330,11 +329,11 @@ class ConversationTextview(GObject.GObject):
if len(text) > 50: if len(text) > 50:
text = text[:47] + '' text = text[:47] + ''
tooltip.set_text(text) tooltip.set_text(text)
window.set_cursor(gtkgui_helpers.get_cursor('HAND2')) window.set_cursor(get_cursor('HAND2'))
self.cursor_changed = True self.cursor_changed = True
return True return True
if tag_name in ('url', 'mail', 'xmpp', 'sth_at_sth'): if tag_name in ('url', 'mail', 'xmpp', 'sth_at_sth'):
window.set_cursor(gtkgui_helpers.get_cursor('HAND2')) window.set_cursor(get_cursor('HAND2'))
self.cursor_changed = True self.cursor_changed = True
return False return False
try: try:
@ -344,7 +343,7 @@ class ConversationTextview(GObject.GObject):
except KeyError: except KeyError:
pass pass
if self.cursor_changed: if self.cursor_changed:
window.set_cursor(gtkgui_helpers.get_cursor('XTERM')) window.set_cursor(get_cursor('XTERM'))
self.cursor_changed = False self.cursor_changed = False
return False return False
@ -370,7 +369,7 @@ class ConversationTextview(GObject.GObject):
def scroll_to_end(self, force=False): def scroll_to_end(self, force=False):
if self.autoscroll or force: if self.autoscroll or force:
gtkgui_helpers.scroll_to_end(self.tv.get_parent()) util.scroll_to_end(self.tv.get_parent())
def correct_message(self, correct_id, kind, name): def correct_message(self, correct_id, kind, name):
allowed = True allowed = True
@ -674,10 +673,10 @@ class ConversationTextview(GObject.GObject):
app.interface.join_gc_minimal(self.account, room_jid) app.interface.join_gc_minimal(self.account, room_jid)
def on_add_to_roster_activate(self, widget, jid): def on_add_to_roster_activate(self, widget, jid):
dialogs.AddNewContactWindow(self.account, jid) AddNewContactWindow(self.account, jid)
def make_link_menu(self, event, kind, text): def make_link_menu(self, event, kind, text):
xml = gtkgui_helpers.get_gtk_builder('chat_context_menu.ui') xml = get_builder('chat_context_menu.ui')
menu = xml.get_object('chat_context_menu') menu = xml.get_object('chat_context_menu')
childs = menu.get_children() childs = menu.get_children()
if kind == 'url': if kind == 'url':
@ -1142,8 +1141,9 @@ class ConversationTextview(GObject.GObject):
self.print_time(text, kind, tim, simple, direction_mark, self.print_time(text, kind, tim, simple, direction_mark,
other_tags_for_time, iter_) other_tags_for_time, iter_)
icon = load_icon('channel-secure-croped-symbolic', self.tv, pixbuf=True)
if encrypted: if encrypted:
buffer_.insert_pixbuf(iter_, self.MESSAGE_ENCRYPTED_PIXBUF) buffer_.insert_pixbuf(iter_, icon)
# If there's a displaymarking, print it here. # If there's a displaymarking, print it here.
if displaymarking: if displaymarking:
@ -1185,8 +1185,9 @@ class ConversationTextview(GObject.GObject):
# Show Correction Icon # Show Correction Icon
buffer_.create_tag(tag_name=msg_stanza_id) buffer_.create_tag(tag_name=msg_stanza_id)
buffer_.insert(iter_, ' ') buffer_.insert(iter_, ' ')
icon = load_icon('document-edit-symbolic', self.tv, pixbuf=True)
buffer_.insert_pixbuf( buffer_.insert_pixbuf(
iter_, ConversationTextview.MESSAGE_CORRECTED_PIXBUF) iter_, icon)
tag_start_iter = iter_.copy() tag_start_iter = iter_.copy()
tag_start_iter.backward_chars(2) tag_start_iter.backward_chars(2)
buffer_.apply_tag_by_name(msg_stanza_id, tag_start_iter, iter_) buffer_.apply_tag_by_name(msg_stanza_id, tag_start_iter, iter_)

View file

@ -128,3 +128,8 @@ button.flat.link { padding: 0; border: 0; }
/*SendFileDialog*/ /*SendFileDialog*/
#SendFileDialog grid {padding: 12px} #SendFileDialog grid {padding: 12px}
#SendFileDialog grid list { background-color: @theme_bg_color} #SendFileDialog grid list { background-color: @theme_bg_color}
/*Icon colors*/
.success-color { color: @success_color; }
.error-color { color: @error_color; }
.warning-color { color: @warning_color; }

View file

@ -22,8 +22,8 @@ from collections import namedtuple
from gi.repository import GLib from gi.repository import GLib
from gajim.common.app import app from gajim.common.app import app
from gajim.dialogs import ErrorDialog from gajim.gtk import ErrorDialog
from gajim.dialogs import InformationDialog from gajim.gtk import InformationDialog
Message = namedtuple('Message', ['title', 'text', 'dialog']) Message = namedtuple('Message', ['title', 'text', 'dialog'])

File diff suppressed because it is too large Load diff

View file

@ -52,7 +52,8 @@ from gi.repository import Gdk
from gi.repository import GdkPixbuf from gi.repository import GdkPixbuf
from gi.repository import Pango from gi.repository import Pango
from gajim import dialogs from gajim.gtk import ErrorDialog
from gajim.gtk import InformationDialog
from gajim import gtkgui_helpers from gajim import gtkgui_helpers
from gajim import groups from gajim import groups
from gajim import adhoc_commands from gajim import adhoc_commands
@ -526,7 +527,7 @@ class ServiceDiscoveryWindow(object):
# Check connection # Check connection
if app.connections[account].connected < 2: if app.connections[account].connected < 2:
dialogs.ErrorDialog(_('You are not connected to the server'), ErrorDialog(_('You are not connected to the server'),
_('Without a connection, you can not browse available services')) _('Without a connection, you can not browse available services'))
raise RuntimeError('You must be connected to browse services') raise RuntimeError('You must be connected to browse services')
@ -722,14 +723,14 @@ _('Without a connection, you can not browse available services'))
if not self.address_comboboxtext: if not self.address_comboboxtext:
# We can't travel anywhere else. # We can't travel anywhere else.
self.destroy() self.destroy()
dialogs.ErrorDialog(_('The service could not be found'), ErrorDialog(_('The service could not be found'),
_('There is no service at the address you entered, or it is ' _('There is no service at the address you entered, or it is '
'not responding. Check the address and try again.'), 'not responding. Check the address and try again.'),
transient_for=self.window) transient_for=self.window)
return return
klass = self.cache.get_browser(identities, features) klass = self.cache.get_browser(identities, features)
if not klass: if not klass:
dialogs.ErrorDialog(_('The service is not browsable'), ErrorDialog(_('The service is not browsable'),
_('This type of service does not contain any items to browse.'), _('This type of service does not contain any items to browse.'),
transient_for=self.window) transient_for=self.window)
return return
@ -772,7 +773,7 @@ _('Without a connection, you can not browse available services'))
jid = helpers.parse_jid(jid) jid = helpers.parse_jid(jid)
except helpers.InvalidFormat as s: except helpers.InvalidFormat as s:
pritext = _('Invalid Server Name') pritext = _('Invalid Server Name')
dialogs.ErrorDialog(pritext, str(s)) ErrorDialog(pritext, str(s))
return return
self.travel(jid, '') self.travel(jid, '')
@ -782,7 +783,7 @@ _('Without a connection, you can not browse available services'))
jid = helpers.parse_jid(jid) jid = helpers.parse_jid(jid)
except helpers.InvalidFormat as s: except helpers.InvalidFormat as s:
pritext = _('Invalid Server Name') pritext = _('Invalid Server Name')
dialogs.ErrorDialog(pritext, str(s), ErrorDialog(pritext, str(s),
transient_for=self.window) transient_for=self.window)
return return
if jid == self.jid: # jid has not changed if jid == self.jid: # jid has not changed
@ -1081,7 +1082,7 @@ class AgentBrowser:
if not self.window.address_comboboxtext: if not self.window.address_comboboxtext:
# We can't travel anywhere else. # We can't travel anywhere else.
self.window.destroy() self.window.destroy()
dialogs.ErrorDialog(_('The service is not browsable'), ErrorDialog(_('The service is not browsable'),
_('This service does not contain any items to browse.'), _('This service does not contain any items to browse.'),
transient_for=self.window.window) transient_for=self.window.window)
return return
@ -1772,7 +1773,7 @@ class MucBrowser(AgentBrowser):
} }
if room_jid in con.get_module('Bookmarks').bookmarks: if room_jid in con.get_module('Bookmarks').bookmarks:
dialogs.ErrorDialog( ErrorDialog(
_('Bookmark already set'), _('Bookmark already set'),
_('Group Chat "%s" is already in your bookmarks.') % room_jid, _('Group Chat "%s" is already in your bookmarks.') % room_jid,
transient_for=self.window.window) transient_for=self.window.window)
@ -1783,7 +1784,7 @@ class MucBrowser(AgentBrowser):
gui_menu_builder.build_bookmark_menu(self.account) gui_menu_builder.build_bookmark_menu(self.account)
dialogs.InformationDialog( InformationDialog(
_('Bookmark has been added successfully'), _('Bookmark has been added successfully'),
_('You can manage your bookmarks via Actions menu in your roster.'), _('You can manage your bookmarks via Actions menu in your roster.'),
transient_for=self.window.window) transient_for=self.window.window)

View file

@ -36,7 +36,12 @@ from datetime import datetime
from gajim import gtkgui_helpers from gajim import gtkgui_helpers
from gajim import tooltips from gajim import tooltips
from gajim import dialogs from gajim.gtk import HigDialog
from gajim.gtk import InformationDialog
from gajim.gtk import YesNoDialog
from gajim.gtk import ErrorDialog
from gajim.gtk import FTOverwriteConfirmationDialog
from gajim.gtk import NonModalConfirmationDialog
from gajim.common import app from gajim.common import app
from gajim.common import helpers from gajim.common import helpers
@ -247,7 +252,7 @@ class FileTransfersWindow:
sectext += recipient sectext += recipient
if file_props.type_ == 'r': if file_props.type_ == 'r':
sectext += '\n\t' + _('Saved in: %s') % file_path sectext += '\n\t' + _('Saved in: %s') % file_path
dialog = dialogs.HigDialog(app.interface.roster.window, Gtk.MessageType.INFO, dialog = HigDialog(app.interface.roster.window, Gtk.MessageType.INFO,
Gtk.ButtonsType.NONE, _('File transfer completed'), sectext) Gtk.ButtonsType.NONE, _('File transfer completed'), sectext)
if file_props.type_ == 'r': if file_props.type_ == 'r':
button = Gtk.Button.new_with_mnemonic(_('Open _Containing Folder')) button = Gtk.Button.new_with_mnemonic(_('Open _Containing Folder'))
@ -263,14 +268,14 @@ class FileTransfersWindow:
""" """
Show error dialog to the recipient saying that transfer has been canceled Show error dialog to the recipient saying that transfer has been canceled
""" """
dialogs.InformationDialog(_('File transfer cancelled'), _('Connection with peer cannot be established.')) InformationDialog(_('File transfer cancelled'), _('Connection with peer cannot be established.'))
self.tree.get_selection().unselect_all() self.tree.get_selection().unselect_all()
def show_send_error(self, file_props): def show_send_error(self, file_props):
""" """
Show error dialog to the sender saying that transfer has been canceled Show error dialog to the sender saying that transfer has been canceled
""" """
dialogs.InformationDialog(_('File transfer cancelled'), InformationDialog(_('File transfer cancelled'),
_('Connection with peer cannot be established.')) _('Connection with peer cannot be established.'))
self.tree.get_selection().unselect_all() self.tree.get_selection().unselect_all()
@ -283,7 +288,7 @@ class FileTransfersWindow:
sectext += '\n\t' + _('Recipient: %s') % jid sectext += '\n\t' + _('Recipient: %s') % jid
if error_msg: if error_msg:
sectext += '\n\t' + _('Error message: %s') % error_msg sectext += '\n\t' + _('Error message: %s') % error_msg
dialogs.ErrorDialog(_('File transfer stopped'), sectext) ErrorDialog(_('File transfer stopped'), sectext)
self.tree.get_selection().unselect_all() self.tree.get_selection().unselect_all()
def show_hash_error(self, jid, file_props, account): def show_hash_error(self, jid, file_props, account):
@ -318,7 +323,7 @@ class FileTransfersWindow:
file_name = os.path.basename(file_props.file_name) file_name = os.path.basename(file_props.file_name)
else: else:
file_name = file_props.name file_name = file_props.name
dialogs.YesNoDialog(('File transfer error'), YesNoDialog(('File transfer error'),
_('The file %(file)s has been received, but it seems to have ' _('The file %(file)s has been received, but it seems to have '
'been damaged along the way.\nDo you want to download it again?') % \ 'been damaged along the way.\nDo you want to download it again?') % \
{'file': file_name}, on_response_yes=(on_yes, jid, file_props, {'file': file_name}, on_response_yes=(on_yes, jid, file_props,
@ -335,7 +340,7 @@ class FileTransfersWindow:
if gtkgui_helpers.file_is_locked(file_path): if gtkgui_helpers.file_is_locked(file_path):
pritext = _('Gajim can not read this file') pritext = _('Gajim can not read this file')
sextext = _('Another process is using this file.') sextext = _('Another process is using this file.')
dialogs.ErrorDialog(pritext, sextext) ErrorDialog(pritext, sextext)
return return
if isinstance(contact, str): if isinstance(contact, str):
@ -378,7 +383,7 @@ class FileTransfersWindow:
if not os.access(file_path, os.W_OK): if not os.access(file_path, os.W_OK):
file_name = GLib.markup_escape_text(os.path.basename( file_name = GLib.markup_escape_text(os.path.basename(
file_path)) file_path))
dialogs.ErrorDialog( ErrorDialog(
_('Cannot overwrite existing file "%s"' % file_name), _('Cannot overwrite existing file "%s"' % file_name),
_('A file with this name already exists and you do not ' _('A file with this name already exists and you do not '
'have permission to overwrite it.')) 'have permission to overwrite it.'))
@ -395,7 +400,7 @@ class FileTransfersWindow:
file_props.offset = dl_size file_props.offset = dl_size
self._start_receive(file_path, account, contact, file_props) self._start_receive(file_path, account, contact, file_props)
dialog = dialogs.FTOverwriteConfirmationDialog( dialog = FTOverwriteConfirmationDialog(
_('This file already exists'), _('What do you want to do?'), _('This file already exists'), _('What do you want to do?'),
propose_resume=not dl_finished, on_response=on_response) propose_resume=not dl_finished, on_response=on_response)
dialog.set_destroy_with_parent(True) dialog.set_destroy_with_parent(True)
@ -406,7 +411,7 @@ class FileTransfersWindow:
# read-only bit is used to mark special folder under # read-only bit is used to mark special folder under
# windows, not to mark that a folder is read-only. # windows, not to mark that a folder is read-only.
# See ticket #3587 # See ticket #3587
dialogs.ErrorDialog(_('Directory "%s" is not writable') % \ ErrorDialog(_('Directory "%s" is not writable') % \
dirname, _('You do not have permission to create files ' dirname, _('You do not have permission to create files '
'in this directory.')) 'in this directory.'))
return return
@ -445,7 +450,7 @@ class FileTransfersWindow:
def on_response_cancel(account, file_props): def on_response_cancel(account, file_props):
app.connections[account].send_file_rejection(file_props) app.connections[account].send_file_rejection(file_props)
dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text, dialog = NonModalConfirmationDialog(prim_text, sec_text,
on_response_ok=(on_response_ok, account, contact, file_props), on_response_ok=(on_response_ok, account, contact, file_props),
on_response_cancel=(on_response_cancel, account, file_props)) on_response_cancel=(on_response_cancel, account, file_props))
dialog.connect('delete-event', lambda widget, event: dialog.connect('delete-event', lambda widget, event:
@ -674,10 +679,10 @@ class FileTransfersWindow:
if os.path.isfile(file_path): if os.path.isfile(file_path):
stat = os.stat(file_path) stat = os.stat(file_path)
else: else:
dialogs.ErrorDialog(_('Invalid File'), _('File: ') + file_path) ErrorDialog(_('Invalid File'), _('File: ') + file_path)
return None return None
if stat[6] == 0: if stat[6] == 0:
dialogs.ErrorDialog(_('Invalid File'), ErrorDialog(_('Invalid File'),
_('It is not possible to send empty files')) _('It is not possible to send empty files'))
return None return None
file_props = FilesProp.getNewFileProp(account, file_props = FilesProp.getNewFileProp(account,

View file

@ -25,15 +25,17 @@
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import Gdk from gi.repository import Gdk
from gi.repository import Pango from gi.repository import Pango
from gajim import dialogs
from gajim import gtkgui_helpers
from gajim.common import app from gajim.common import app
from gajim.gtk import ErrorDialog
from gajim.gtk.util import get_builder
class GajimThemesWindow: class GajimThemesWindow:
def __init__(self): def __init__(self):
self.xml = gtkgui_helpers.get_gtk_builder('gajim_themes_window.ui') self.xml = get_builder('gajim_themes_window.ui')
self.window = self.xml.get_object('gajim_themes_window') self.window = self.xml.get_object('gajim_themes_window')
self.window.set_transient_for(app.interface.instances[ self.window.set_transient_for(app.interface.instances[
'preferences'].window) 'preferences'].window)
@ -94,7 +96,7 @@ class GajimThemesWindow:
if old_name == new_name: if old_name == new_name:
return return
if old_name == 'default': if old_name == 'default':
dialogs.ErrorDialog( ErrorDialog(
_('You cannot make changes to the default theme'), _('You cannot make changes to the default theme'),
_('Please create a new clean theme.')) _('Please create a new clean theme.'))
return return
@ -183,7 +185,7 @@ class GajimThemesWindow:
if not iter_: if not iter_:
return return
if self.current_theme == app.config.get('roster_theme'): if self.current_theme == app.config.get('roster_theme'):
dialogs.ErrorDialog( ErrorDialog(
_('You cannot delete your current theme'), _('You cannot delete your current theme'),
_('Pick another theme to use first.')) _('Pick another theme to use first.'))
return return

View file

@ -41,11 +41,11 @@ from gajim import gtkgui_helpers
from gajim import gui_menu_builder from gajim import gui_menu_builder
from gajim import message_control from gajim import message_control
from gajim import tooltips from gajim import tooltips
from gajim import dialogs
from gajim import config from gajim import config
from gajim import vcard from gajim import vcard
from gajim import dataforms_widget from gajim import dataforms_widget
from gajim import adhoc_commands from gajim import adhoc_commands
from gajim.gtk import AddNewContactWindow
from gajim.common.const import AvatarSize from gajim.common.const import AvatarSize
from gajim.common.caps_cache import muc_caps_cache from gajim.common.caps_cache import muc_caps_cache
import nbxmpp import nbxmpp
@ -63,6 +63,12 @@ from gajim.common import contacts
from gajim.chat_control import ChatControl from gajim.chat_control import ChatControl
from gajim.chat_control_base import ChatControlBase from gajim.chat_control_base import ChatControlBase
from gajim.filechoosers import AvatarChooserDialog from gajim.filechoosers import AvatarChooserDialog
from gajim.gtk import ErrorDialog
from gajim.gtk import InputTextDialog
from gajim.gtk import ConfirmationDialogCheck
from gajim.gtk import DoubleInputDialog
from gajim.gtk import InputDialog
from gajim.gtk import ChangeNickDialog
from gajim.command_system.implementation.hosts import PrivateChatCommands from gajim.command_system.implementation.hosts import PrivateChatCommands
from gajim.command_system.implementation.hosts import GroupChatCommands from gajim.command_system.implementation.hosts import GroupChatCommands
@ -239,7 +245,7 @@ class PrivateChatControl(ChatControl):
room, nick = app.get_room_and_nick_from_fjid(self.contact.jid) room, nick = app.get_room_and_nick_from_fjid(self.contact.jid)
gc_contact = app.contacts.get_gc_contact(self.account, room, nick) gc_contact = app.contacts.get_gc_contact(self.account, room, nick)
if not gc_contact: if not gc_contact:
dialogs.ErrorDialog( ErrorDialog(
_('Sending private message failed'), _('Sending private message failed'),
#in second %s code replaces with nickname #in second %s code replaces with nickname
_('You are no longer in group chat "%(room)s" or ' _('You are no longer in group chat "%(room)s" or '
@ -646,7 +652,7 @@ class GroupchatControl(ChatControlBase):
app.connections[self.account].send_gc_subject( app.connections[self.account].send_gc_subject(
self.room_jid, subject) self.room_jid, subject)
dialogs.InputTextDialog(_('Changing Subject'), InputTextDialog(_('Changing Subject'),
_('Please specify the new subject:'), input_str=self.subject, _('Please specify the new subject:'), input_str=self.subject,
ok_handler=on_ok, transient_for=self.parent_win.window) ok_handler=on_ok, transient_for=self.parent_win.window)
@ -657,7 +663,7 @@ class GroupchatControl(ChatControlBase):
title = _('Changing Nickname') title = _('Changing Nickname')
prompt = _('Please specify the new nickname you want to use:') prompt = _('Please specify the new nickname you want to use:')
app.interface.instances['change_nick_dialog'] = \ app.interface.instances['change_nick_dialog'] = \
dialogs.ChangeNickDialog(self.account, self.room_jid, title, ChangeNickDialog(self.account, self.room_jid, title,
prompt, change_nick=True, transient_for=self.parent_win.window) prompt, change_nick=True, transient_for=self.parent_win.window)
def _on_disconnect(self, action, param): def _on_disconnect(self, action, param):
@ -672,7 +678,7 @@ class GroupchatControl(ChatControlBase):
try: try:
jid = helpers.parse_jid(jid) jid = helpers.parse_jid(jid)
except Exception: except Exception:
dialogs.ErrorDialog(_('Invalid group chat JID'), ErrorDialog(_('Invalid group chat JID'),
_('The group chat JID has not allowed characters.')) _('The group chat JID has not allowed characters.'))
return return
app.connections[self.account].destroy_gc_room( app.connections[self.account].destroy_gc_room(
@ -683,7 +689,7 @@ class GroupchatControl(ChatControlBase):
self.force_non_minimizable = False self.force_non_minimizable = False
# Ask for a reason # Ask for a reason
dialogs.DoubleInputDialog(_('Destroying %s') % '\u200E' + \ DoubleInputDialog(_('Destroying %s') % '\u200E' + \
self.room_jid, _('You are going to remove this room permanently.' self.room_jid, _('You are going to remove this room permanently.'
'\nYou may specify a reason below:'), '\nYou may specify a reason below:'),
_('You may also enter an alternate venue:'), ok_handler=on_ok, _('You may also enter an alternate venue:'), ok_handler=on_ok,
@ -751,7 +757,7 @@ class GroupchatControl(ChatControlBase):
def _on_accept(filename): def _on_accept(filename):
sha = app.interface.save_avatar(filename, publish=True) sha = app.interface.save_avatar(filename, publish=True)
if sha is None: if sha is None:
dialogs.ErrorDialog( ErrorDialog(
_('Could not load image'), _('Could not load image'),
transient_for=self.parent_win.window) transient_for=self.parent_win.window)
return return
@ -2343,7 +2349,7 @@ class GroupchatControl(ChatControlBase):
sectext = _('If you close this window, you will be disconnected ' sectext = _('If you close this window, you will be disconnected '
'from this group chat.') 'from this group chat.')
dialogs.ConfirmationDialogCheck(pritext, sectext, ConfirmationDialogCheck(pritext, sectext,
_('_Do not ask me again'), on_response_ok=on_ok, _('_Do not ask me again'), on_response_ok=on_ok,
on_response_cancel=on_cancel, on_response_cancel=on_cancel,
transient_for=self.parent_win.window) transient_for=self.parent_win.window)
@ -2379,7 +2385,7 @@ class GroupchatControl(ChatControlBase):
app.connections[self.account].send_gc_subject(self.room_jid, app.connections[self.account].send_gc_subject(self.room_jid,
subject) subject)
dialogs.InputTextDialog(_('Changing Subject'), InputTextDialog(_('Changing Subject'),
_('Please specify the new subject:'), input_str=self.subject, _('Please specify the new subject:'), input_str=self.subject,
ok_handler=on_ok, transient_for=self.parent_win.window) ok_handler=on_ok, transient_for=self.parent_win.window)
@ -2560,7 +2566,7 @@ class GroupchatControl(ChatControlBase):
'none', reason) 'none', reason)
# ask for reason # ask for reason
dialogs.InputDialog(_('Kicking %s') % nick, InputDialog(_('Kicking %s') % nick,
_('You may specify a reason below:'), ok_handler=on_ok, _('You may specify a reason below:'), ok_handler=on_ok,
transient_for=self.parent_win.window) transient_for=self.parent_win.window)
@ -2857,7 +2863,7 @@ class GroupchatControl(ChatControlBase):
# to ban we know the real jid. so jid is not fakejid # to ban we know the real jid. so jid is not fakejid
nick = app.get_nick_from_jid(jid) nick = app.get_nick_from_jid(jid)
# ask for reason # ask for reason
dialogs.InputDialog(_('Banning %s') % nick, InputDialog(_('Banning %s') % nick,
_('You may specify a reason below:'), ok_handler=on_ok, _('You may specify a reason below:'), ok_handler=on_ok,
transient_for=self.parent_win.window) transient_for=self.parent_win.window)
@ -2922,7 +2928,7 @@ class GroupchatControl(ChatControlBase):
self._on_history_menuitem_activate(widget=widget, jid=jid) self._on_history_menuitem_activate(widget=widget, jid=jid)
def on_add_to_roster(self, widget, jid): def on_add_to_roster(self, widget, jid):
dialogs.AddNewContactWindow(self.account, jid) AddNewContactWindow(self.account, jid)
def on_block(self, widget, nick): def on_block(self, widget, nick):
fjid = self.room_jid + '/' + nick fjid = self.room_jid + '/' + nick

View file

@ -0,0 +1,56 @@
# 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>
# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
#
# 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 gajim.gtk.dialogs import ErrorDialog
from gajim.gtk.dialogs import InformationDialog
from gajim.gtk.dialogs import ChangeNickDialog
from gajim.gtk.dialogs import FTOverwriteConfirmationDialog
from gajim.gtk.dialogs import InputDialog
from gajim.gtk.dialogs import ConfirmationDialogDoubleRadio
from gajim.gtk.dialogs import InputDialogCheck
from gajim.gtk.dialogs import DoubleInputDialog
from gajim.gtk.dialogs import InputTextDialog
from gajim.gtk.dialogs import PlainConnectionDialog
from gajim.gtk.dialogs import ConfirmationDialogDoubleCheck
from gajim.gtk.dialogs import ConfirmationDialogCheck
from gajim.gtk.dialogs import YesNoDialog
from gajim.gtk.dialogs import WarningDialog
from gajim.gtk.dialogs import NonModalConfirmationDialog
from gajim.gtk.dialogs import ConfirmationDialog
from gajim.gtk.dialogs import AspellDictError
from gajim.gtk.dialogs import HigDialog
from gajim.gtk.dialogs import SSLErrorDialog
from gajim.gtk.about import AboutDialog
from gajim.gtk.join_groupchat import JoinGroupchatWindow
from gajim.gtk.add_contact import AddNewContactWindow
from gajim.gtk.start_chat import StartChatDialog
from gajim.gtk.xml_console import XMLConsoleWindow
from gajim.gtk.privacy_list import PrivacyListsWindow
from gajim.gtk.single_message import SingleMessageWindow
from gajim.gtk.server_info import ServerInfoDialog

66
gajim/gtk/about.py Normal file
View file

@ -0,0 +1,66 @@
# 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/>.
import nbxmpp
from gi.repository import Gtk
from gi.repository import GObject
from gajim.common import app
from gajim.common.const import DEVS_CURRENT
from gajim.common.const import DEVS_PAST
from gajim.common.const import ARTISTS
from gajim.common.const import THANKS
class AboutDialog(Gtk.AboutDialog):
def __init__(self):
Gtk.AboutDialog.__init__(self)
self.set_transient_for(app.interface.roster.window)
self.set_name('Gajim')
self.set_version(app.version)
self.set_copyright('Copyright © 2003-2018 Gajim Team')
self.set_license_type(Gtk.License.GPL_3_0_ONLY)
self.set_website('https://gajim.org/')
gtk_ver = '%i.%i.%i' % (
Gtk.get_major_version(),
Gtk.get_minor_version(),
Gtk.get_micro_version())
gobject_ver = '.'.join(map(str, GObject.pygobject_version))
comments = []
comments.append(_('A GTK+ XMPP client'))
comments.append(_('GTK+ Version: %s' % gtk_ver))
comments.append(_('PyGObject Version: %s') % gobject_ver)
comments.append(_('python-nbxmpp Version: %s') % nbxmpp.__version__)
self.set_comments("\n".join(comments))
self.add_credit_section(_('Current Developers'), DEVS_CURRENT)
self.add_credit_section(_('Past Developers'), DEVS_PAST)
self.add_credit_section(_('Artists'), ARTISTS)
thanks = list(THANKS)
thanks.append('')
thanks.append(_('Last but not least'))
thanks.append(_('we would like to thank all the package maintainers.'))
self.add_credit_section(_('Thanks'), thanks)
self.set_translator_credits(_('translator-credits'))
self.set_logo_icon_name('org.gajim.Gajim')
self.connect(
'response', lambda dialog, *args: Gtk.AboutDialog.do_close(dialog))
self.show()

483
gajim/gtk/add_contact.py Normal file
View file

@ -0,0 +1,483 @@
# 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 Gdk
from gi.repository import Gtk
from gajim.common import app
from gajim.common import ged
from gajim.common import helpers
from gajim.gtk import ErrorDialog
from gajim.gtk.util import get_builder
class AddNewContactWindow(Gtk.ApplicationWindow):
uid_labels = {'jabber': _('Jabber ID'),
'gadu-gadu': _('GG Number'),
'icq': _('ICQ Number')}
def __init__(self, account=None, jid=None, user_nick=None, group=None):
Gtk.ApplicationWindow.__init__(self)
self.set_application(app.app)
self.set_position(Gtk.WindowPosition.CENTER)
self.set_show_menubar(False)
self.set_resizable(False)
self.set_title(_('Add Contact'))
self.connect('destroy', self._on_destroy)
self.connect('key-press-event', self._on_key_press)
self.account = account
self.adding_jid = False
# fill accounts with active accounts
accounts = app.get_enabled_accounts_with_labels()
if not accounts:
return
if not account:
self.account = accounts[0][0]
self.xml = get_builder('add_new_contact_window.ui')
self.add(self.xml.get_object('add_contact_box'))
self.xml.connect_signals(self)
for w in ('account_combobox', 'account_label', 'prompt_label',
'uid_label', 'uid_entry', 'protocol_combobox',
'protocol_jid_combobox', 'protocol_label', 'nickname_entry',
'message_scrolledwindow', 'save_message_checkbutton',
'register_hbox', 'add_button', 'message_textview',
'connected_label', 'group_comboboxentry',
'auto_authorize_checkbutton', 'save_message_revealer',
'nickname_label', 'group_label'):
self.__dict__[w] = self.xml.get_object(w)
self.subscription_table = [self.uid_label, self.uid_entry,
self.nickname_label, self.nickname_entry,
self.group_label, self.group_comboboxentry]
self.add_button.grab_default()
self.agents = {'jabber': []}
self.gateway_prompt = {}
# types to which we are not subscribed but account has an agent for it
self.available_types = []
for acct in accounts:
for j in app.contacts.get_jid_list(acct[0]):
if app.jid_is_transport(j):
type_ = app.get_transport_name_from_jid(j, False)
if not type_:
continue
if type_ in self.agents:
self.agents[type_].append(j)
else:
self.agents[type_] = [j]
self.gateway_prompt[j] = {'desc': None, 'prompt': None}
# Now add the one to which we can register
for acct in accounts:
for type_ in app.connections[acct[0]].available_transports:
if type_ in self.agents:
continue
self.agents[type_] = []
for jid_ in app.connections[acct[0]].available_transports[type_]:
if jid_ not in self.agents[type_]:
self.agents[type_].append(jid_)
self.gateway_prompt[jid_] = {'desc': None,
'prompt': None}
self.available_types.append(type_)
uf_type = {'jabber': 'XMPP', 'gadu-gadu': 'Gadu Gadu', 'icq': 'ICQ'}
# Jabber as first
liststore = self.protocol_combobox.get_model()
liststore.append(['XMPP', 'xmpp', 'jabber'])
for type_ in self.agents:
if type_ == 'jabber':
continue
if type_ in uf_type:
liststore.append([uf_type[type_], type_ + '-online', type_])
else:
liststore.append([type_, type_ + '-online', type_])
if account:
for service in self.agents[type_]:
app.connections[account].request_gateway_prompt(service)
self.protocol_combobox.set_active(0)
self.auto_authorize_checkbutton.show()
if jid:
self.jid_escaped = True
type_ = app.get_transport_name_from_jid(jid)
if not type_:
type_ = 'jabber'
if type_ == 'jabber':
self.uid_entry.set_text(jid)
else:
uid, transport = app.get_name_and_server_from_jid(jid)
self.uid_entry.set_text(uid.replace('%', '@', 1))
# set protocol_combobox
model = self.protocol_combobox.get_model()
iter_ = model.get_iter_first()
i = 0
while iter_:
if model[iter_][2] == type_:
self.protocol_combobox.set_active(i)
break
iter_ = model.iter_next(iter_)
i += 1
# set protocol_jid_combobox
self.protocol_jid_combobox.set_active(0)
model = self.protocol_jid_combobox.get_model()
iter_ = model.get_iter_first()
i = 0
while iter_:
if model[iter_][0] == transport:
self.protocol_jid_combobox.set_active(i)
break
iter_ = model.iter_next(iter_)
i += 1
if user_nick:
self.nickname_entry.set_text(user_nick)
self.nickname_entry.grab_focus()
else:
self.jid_escaped = False
self.uid_entry.grab_focus()
group_names = []
for acct in accounts:
for g in app.groups[acct[0]].keys():
if g not in helpers.special_groups and g not in group_names:
group_names.append(g)
group_names.sort()
i = 0
for g in group_names:
self.group_comboboxentry.append_text(g)
if group == g:
self.group_comboboxentry.set_active(i)
i += 1
self.show_all()
self.prompt_label.hide()
self.save_message_revealer.hide()
if len(accounts) > 1:
liststore = self.account_combobox.get_model()
for acc in accounts:
liststore.append(acc)
self.account_combobox.set_active_id(self.account)
else:
self.account_label.hide()
self.account_combobox.hide()
if len(self.agents) == 1:
self.protocol_label.hide()
self.protocol_combobox.hide()
self.protocol_jid_combobox.hide()
if self.account:
message_buffer = self.message_textview.get_buffer()
msg = helpers.from_one_line(helpers.get_subscription_request_msg(
self.account))
message_buffer.set_text(msg)
app.ged.register_event_handler('gateway-prompt-received', ged.GUI1,
self._nec_gateway_prompt_received)
app.ged.register_event_handler('presence-received', ged.GUI1,
self._nec_presence_received)
def _on_destroy(self, widget):
app.ged.remove_event_handler('presence-received', ged.GUI1,
self._nec_presence_received)
app.ged.remove_event_handler('gateway-prompt-received', ged.GUI1,
self._nec_gateway_prompt_received)
def on_register_button_clicked(self, widget):
model = self.protocol_jid_combobox.get_model()
row = self.protocol_jid_combobox.get_active()
jid = model[row][0]
app.connections[self.account].request_register_agent_info(jid)
def _on_key_press(self, widget, event):
if event.keyval == Gdk.KEY_Escape:
self.destroy()
def on_cancel_button_clicked(self, widget):
"""
When Cancel button is clicked
"""
self.destroy()
def on_message_textbuffer_changed(self, widget):
self.save_message_revealer.show()
self.save_message_revealer.set_reveal_child(True)
def on_add_button_clicked(self, widget):
"""
When Subscribe button is clicked
"""
jid = self.uid_entry.get_text().strip()
if not jid:
ErrorDialog(
_('%s Missing') % self.uid_label.get_text(),
_('You must supply the %s of the new contact.' %
self.uid_label.get_text())
)
return
model = self.protocol_combobox.get_model()
row = self.protocol_combobox.get_active_iter()
type_ = model[row][2]
if type_ != 'jabber':
model = self.protocol_jid_combobox.get_model()
row = self.protocol_jid_combobox.get_active()
transport = model[row][0]
if self.account and not self.jid_escaped:
self.adding_jid = (jid, transport, type_)
app.connections[self.account].request_gateway_prompt(
transport, jid)
else:
jid = jid.replace('@', '%') + '@' + transport
self._add_jid(jid, type_)
else:
self._add_jid(jid, type_)
def _add_jid(self, jid, type_):
# check if jid is conform to RFC and stringprep it
try:
jid = helpers.parse_jid(jid)
except helpers.InvalidFormat as s:
pritext = _('Invalid User ID')
ErrorDialog(pritext, str(s))
return
# No resource in jid
if jid.find('/') >= 0:
pritext = _('Invalid User ID')
ErrorDialog(pritext, _('The user ID must not contain a resource.'))
return
if jid == app.get_jid_from_account(self.account):
pritext = _('Invalid User ID')
ErrorDialog(pritext, _('You cannot add yourself to your roster.'))
return
if not app.account_is_connected(self.account):
ErrorDialog(
_('Account Offline'),
_('Your account must be online to add new contacts.')
)
return
nickname = self.nickname_entry.get_text() or ''
# get value of account combobox, if account was not specified
if not self.account:
model = self.account_combobox.get_model()
index = self.account_combobox.get_active()
self.account = model[index][1]
# Check if jid is already in roster
if jid in app.contacts.get_jid_list(self.account):
c = app.contacts.get_first_contact_from_jid(self.account, jid)
if _('Not in Roster') not in c.groups and c.sub in ('both', 'to'):
ErrorDialog(
_('Contact already in roster'),
_('This contact is already listed in your roster.'))
return
if type_ == 'jabber':
message_buffer = self.message_textview.get_buffer()
start_iter = message_buffer.get_start_iter()
end_iter = message_buffer.get_end_iter()
message = message_buffer.get_text(start_iter, end_iter, True)
if self.save_message_checkbutton.get_active():
msg = helpers.to_one_line(message)
app.config.set_per('accounts', self.account,
'subscription_request_msg', msg)
else:
message = ''
group = self.group_comboboxentry.get_child().get_text()
groups = []
if group:
groups = [group]
auto_auth = self.auto_authorize_checkbutton.get_active()
app.interface.roster.req_sub(
self, jid, message, self.account,
groups=groups, nickname=nickname, auto_auth=auto_auth)
self.destroy()
def on_account_combobox_changed(self, widget):
account = widget.get_active_id()
message_buffer = self.message_textview.get_buffer()
message_buffer.set_text(helpers.get_subscription_request_msg(account))
self.account = account
def on_protocol_jid_combobox_changed(self, widget):
model = widget.get_model()
iter_ = widget.get_active_iter()
if not iter_:
return
jid_ = model[iter_][0]
model = self.protocol_combobox.get_model()
iter_ = self.protocol_combobox.get_active_iter()
type_ = model[iter_][2]
desc = None
if self.agents[type_] and jid_ in self.gateway_prompt:
desc = self.gateway_prompt[jid_]['desc']
if desc:
self.prompt_label.set_markup(desc)
self.prompt_label.show()
else:
self.prompt_label.hide()
prompt = None
if self.agents[type_] and jid_ in self.gateway_prompt:
prompt = self.gateway_prompt[jid_]['prompt']
if not prompt:
if type_ in self.uid_labels:
prompt = self.uid_labels[type_]
else:
prompt = _('User ID:')
self.uid_label.set_text(prompt)
def on_protocol_combobox_changed(self, widget):
model = widget.get_model()
iter_ = widget.get_active_iter()
type_ = model[iter_][2]
model = self.protocol_jid_combobox.get_model()
model.clear()
if len(self.agents[type_]):
for jid_ in self.agents[type_]:
model.append([jid_])
self.protocol_jid_combobox.set_active(0)
desc = None
if self.agents[type_]:
jid_ = self.agents[type_][0]
if jid_ in self.gateway_prompt:
desc = self.gateway_prompt[jid_]['desc']
if desc:
self.prompt_label.set_markup(desc)
self.prompt_label.show()
else:
self.prompt_label.hide()
if len(self.agents[type_]) > 1:
self.protocol_jid_combobox.show()
else:
self.protocol_jid_combobox.hide()
prompt = None
if self.agents[type_]:
jid_ = self.agents[type_][0]
if jid_ in self.gateway_prompt:
prompt = self.gateway_prompt[jid_]['prompt']
if not prompt:
if type_ in self.uid_labels:
prompt = self.uid_labels[type_]
else:
prompt = _('User ID:')
self.uid_label.set_text(prompt)
if type_ == 'jabber':
self.message_scrolledwindow.show()
self.save_message_checkbutton.show()
else:
self.message_scrolledwindow.hide()
self.save_message_checkbutton.hide()
if type_ in self.available_types:
self.register_hbox.show()
self.auto_authorize_checkbutton.hide()
self.connected_label.hide()
self._subscription_table_hide()
self.add_button.set_sensitive(False)
else:
self.register_hbox.hide()
if type_ != 'jabber':
model = self.protocol_jid_combobox.get_model()
row = self.protocol_jid_combobox.get_active()
jid = model[row][0]
contact = app.contacts.get_first_contact_from_jid(
self.account, jid)
if contact is None or contact.show in ('offline', 'error'):
self._subscription_table_hide()
self.connected_label.show()
self.add_button.set_sensitive(False)
self.auto_authorize_checkbutton.hide()
return
self._subscription_table_show()
self.auto_authorize_checkbutton.show()
self.connected_label.hide()
self.add_button.set_sensitive(True)
def transport_signed_in(self, jid):
model = self.protocol_jid_combobox.get_model()
row = self.protocol_jid_combobox.get_active()
_jid = model[row][0]
if _jid == jid:
self.register_hbox.hide()
self.connected_label.hide()
self._subscription_table_show()
self.auto_authorize_checkbutton.show()
self.add_button.set_sensitive(True)
def transport_signed_out(self, jid):
model = self.protocol_jid_combobox.get_model()
row = self.protocol_jid_combobox.get_active()
_jid = model[row][0]
if _jid == jid:
self._subscription_table_hide()
self.auto_authorize_checkbutton.hide()
self.connected_label.show()
self.add_button.set_sensitive(False)
def _nec_presence_received(self, obj):
if app.jid_is_transport(obj.jid):
if obj.old_show == 0 and obj.new_show > 1:
self.transport_signed_in(obj.jid)
elif obj.old_show > 1 and obj.new_show == 0:
self.transport_signed_out(obj.jid)
def _nec_gateway_prompt_received(self, obj):
if self.adding_jid:
jid, transport, type_ = self.adding_jid
if obj.stanza.getError():
ErrorDialog(
_('Error while adding transport contact'),
_('This error occured while adding a contact for transport '
'%(transport)s:\n\n%(error)s') % {
'transport': transport,
'error': obj.stanza.getErrorMsg()})
return
if obj.prompt_jid:
self._add_jid(obj.prompt_jid, type_)
else:
jid = jid.replace('@', '%') + '@' + transport
self._add_jid(jid, type_)
elif obj.jid in self.gateway_prompt:
if obj.desc:
self.gateway_prompt[obj.jid]['desc'] = obj.desc
if obj.prompt:
self.gateway_prompt[obj.jid]['prompt'] = obj.prompt
def _subscription_table_hide(self):
for widget in self.subscription_table:
widget.hide()
def _subscription_table_show(self):
for widget in self.subscription_table:
widget.show()

933
gajim/gtk/dialogs.py Normal file
View file

@ -0,0 +1,933 @@
# 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 gajim.common import app
from gajim.common import helpers
from gajim.gtk.util import get_builder
from gajim.gtk.util import load_icon
class HigDialog(Gtk.MessageDialog):
def __init__(self, parent, type_, buttons, pritext, sectext,
on_response_ok=None, on_response_cancel=None, on_response_yes=None,
on_response_no=None):
self.call_cancel_on_destroy = True
Gtk.MessageDialog.__init__(self, transient_for=parent,
modal=True, destroy_with_parent=True,
message_type=type_, buttons=buttons, text=pritext)
self.format_secondary_markup(sectext)
self.possible_responses = {Gtk.ResponseType.OK: on_response_ok,
Gtk.ResponseType.CANCEL: on_response_cancel,
Gtk.ResponseType.YES: on_response_yes,
Gtk.ResponseType.NO: on_response_no}
self.connect('response', self.on_response)
self.connect('destroy', self.on_dialog_destroy)
def on_response(self, dialog, response_id):
if response_id not in self.possible_responses:
return
if not self.possible_responses[response_id]:
self.destroy()
elif isinstance(self.possible_responses[response_id], tuple):
if len(self.possible_responses[response_id]) == 1:
self.possible_responses[response_id][0](dialog)
else:
self.possible_responses[response_id][0](dialog,
*self.possible_responses[response_id][1:])
else:
self.possible_responses[response_id](dialog)
def on_dialog_destroy(self, widget):
if not self.call_cancel_on_destroy:
return
cancel_handler = self.possible_responses[Gtk.ResponseType.CANCEL]
if not cancel_handler:
return False
if isinstance(cancel_handler, tuple):
cancel_handler[0](None, *cancel_handler[1:])
else:
cancel_handler(None)
def popup(self):
"""
Show dialog
"""
vb = self.get_children()[0].get_children()[0] # Give focus to top vbox
# vb.set_flags(Gtk.CAN_FOCUS)
vb.grab_focus()
self.show_all()
class AspellDictError:
def __init__(self, lang):
ErrorDialog(
_('Dictionary for language "%s" not available') % lang,
_('You have to install the dictionary "%s" to use spellchecking, '
'or choose another language by setting the speller_language '
'option.\n\n'
'Highlighting misspelled words feature will not be used') % lang)
class ConfirmationDialog(HigDialog):
"""
HIG compliant confirmation dialog
"""
def __init__(self, pritext, sectext='', on_response_ok=None,
on_response_cancel=None, transient_for=None):
self.user_response_ok = on_response_ok
self.user_response_cancel = on_response_cancel
HigDialog.__init__(self, transient_for,
Gtk.MessageType.QUESTION, Gtk.ButtonsType.OK_CANCEL, pritext, sectext,
self.on_response_ok, self.on_response_cancel)
self.popup()
def on_response_ok(self, widget):
if self.user_response_ok:
if isinstance(self.user_response_ok, tuple):
self.user_response_ok[0](*self.user_response_ok[1:])
else:
self.user_response_ok()
self.call_cancel_on_destroy = False
self.destroy()
def on_response_cancel(self, widget):
if self.user_response_cancel:
if isinstance(self.user_response_cancel, tuple):
self.user_response_cancel[0](*self.user_response_ok[1:])
else:
self.user_response_cancel()
self.call_cancel_on_destroy = False
self.destroy()
class NonModalConfirmationDialog(HigDialog):
"""
HIG compliant non modal confirmation dialog
"""
def __init__(self, pritext, sectext='', on_response_ok=None,
on_response_cancel=None):
self.user_response_ok = on_response_ok
self.user_response_cancel = on_response_cancel
if hasattr(app.interface, 'roster') and app.interface.roster:
parent = app.interface.roster.window
else:
parent = None
HigDialog.__init__(self, parent, Gtk.MessageType.QUESTION,
Gtk.ButtonsType.OK_CANCEL, pritext, sectext, self.on_response_ok,
self.on_response_cancel)
self.set_modal(False)
def on_response_ok(self, widget):
if self.user_response_ok:
if isinstance(self.user_response_ok, tuple):
self.user_response_ok[0](*self.user_response_ok[1:])
else:
self.user_response_ok()
self.call_cancel_on_destroy = False
self.destroy()
def on_response_cancel(self, widget):
if self.user_response_cancel:
if isinstance(self.user_response_cancel, tuple):
self.user_response_cancel[0](*self.user_response_cancel[1:])
else:
self.user_response_cancel()
self.call_cancel_on_destroy = False
self.destroy()
class WarningDialog(HigDialog):
"""
HIG compliant warning dialog
"""
def __init__(self, pritext, sectext='', transient_for=None):
if not transient_for and hasattr(app.interface, 'roster') and \
app.interface.roster:
transient_for = app.interface.roster.window
HigDialog.__init__(self, transient_for, Gtk.MessageType.WARNING,
Gtk.ButtonsType.OK, pritext, sectext)
self.set_modal(False)
self.popup()
class InformationDialog(HigDialog):
"""
HIG compliant info dialog
"""
def __init__(self, pritext, sectext='', transient_for=None):
if transient_for:
parent = transient_for
elif hasattr(app.interface, 'roster') and app.interface.roster:
parent = app.interface.roster.window
else:
parent = None
HigDialog.__init__(self, parent, Gtk.MessageType.INFO, Gtk.ButtonsType.OK,
pritext, sectext)
self.set_modal(False)
self.popup()
class ErrorDialog(HigDialog):
"""
HIG compliant error dialog
"""
def __init__(self, pritext, sectext='', on_response_ok=None,
on_response_cancel=None, transient_for=None):
if transient_for:
parent = transient_for
elif hasattr(app.interface, 'roster') and app.interface.roster:
parent = app.interface.roster.window
else:
parent = None
HigDialog.__init__(self, parent, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK,
pritext, sectext, on_response_ok=on_response_ok,
on_response_cancel=on_response_cancel)
self.popup()
class YesNoDialog(HigDialog):
"""
HIG compliant YesNo dialog
"""
def __init__(self, pritext, sectext='', checktext='', text_label=None,
on_response_yes=None, on_response_no=None, type_=Gtk.MessageType.QUESTION,
transient_for=None):
self.user_response_yes = on_response_yes
self.user_response_no = on_response_no
if transient_for:
parent = transient_for
elif hasattr(app.interface, 'roster') and app.interface.roster:
parent = app.interface.roster.window
else:
parent = None
HigDialog.__init__(self, parent, type_, Gtk.ButtonsType.YES_NO, pritext,
sectext, on_response_yes=self.on_response_yes,
on_response_no=self.on_response_no)
vbox = self.get_content_area()
if checktext:
self.checkbutton = Gtk.CheckButton.new_with_mnemonic(checktext)
vbox.pack_start(self.checkbutton, False, True, 0)
else:
self.checkbutton = None
if text_label:
label = Gtk.Label(label=text_label)
vbox.pack_start(label, False, True, 0)
buff = Gtk.TextBuffer()
self.textview = Gtk.TextView.new_with_buffer(buff)
frame = Gtk.Frame()
frame.set_shadow_type(Gtk.ShadowType.IN)
frame.add(self.textview)
vbox.pack_start(frame, False, True, 0)
else:
self.textview = None
self.set_modal(False)
self.popup()
def on_response_yes(self, widget):
if self.user_response_yes:
if self.textview:
buff = self.textview.get_buffer()
start, end = buff.get_bounds()
txt = self.textview.get_buffer().get_text(start, end, True)
if isinstance(self.user_response_yes, tuple):
if self.textview:
self.user_response_yes[0](self.is_checked(), txt,
*self.user_response_yes[1:])
else:
self.user_response_yes[0](self.is_checked(),
*self.user_response_yes[1:])
else:
if self.textview:
self.user_response_yes(self.is_checked(), txt)
else:
self.user_response_yes(self.is_checked())
self.call_cancel_on_destroy = False
self.destroy()
def on_response_no(self, widget):
if self.user_response_no:
if self.textview:
buff = self.textview.get_buffer()
start, end = buff.get_bounds()
txt = self.textview.get_buffer().get_text(start, end, True)
if isinstance(self.user_response_no, tuple):
if self.textview:
self.user_response_no[0](txt, *self.user_response_no[1:])
else:
self.user_response_no[0](*self.user_response_no[1:])
else:
if self.textview:
self.user_response_no(txt)
else:
self.user_response_no()
self.call_cancel_on_destroy = False
self.destroy()
def is_checked(self):
"""
Get active state of the checkbutton
"""
if not self.checkbutton:
return False
return self.checkbutton.get_active()
class ConfirmationDialogCheck(ConfirmationDialog):
"""
HIG compliant confirmation dialog with checkbutton
"""
def __init__(self, pritext, sectext='', checktext='', on_response_ok=None,
on_response_cancel=None, is_modal=True, transient_for=None):
self.user_response_ok = on_response_ok
self.user_response_cancel = on_response_cancel
if transient_for:
parent = transient_for
elif hasattr(app.interface, 'roster') and app.interface.roster:
parent = app.interface.roster.window
else:
parent = None
HigDialog.__init__(self, parent, Gtk.MessageType.QUESTION,
Gtk.ButtonsType.OK_CANCEL, pritext, sectext, self.on_response_ok,
self.on_response_cancel)
self.set_default_response(Gtk.ResponseType.OK)
ok_button = self.get_widget_for_response(Gtk.ResponseType.OK)
ok_button.grab_focus()
self.checkbutton = Gtk.CheckButton.new_with_mnemonic(checktext)
self.get_content_area().pack_start(self.checkbutton, False, True, 0)
self.set_modal(is_modal)
self.popup()
def on_response_ok(self, widget):
if self.user_response_ok:
if isinstance(self.user_response_ok, tuple):
self.user_response_ok[0](self.is_checked(),
*self.user_response_ok[1:])
else:
self.user_response_ok(self.is_checked())
self.call_cancel_on_destroy = False
self.destroy()
def on_response_cancel(self, widget):
if self.user_response_cancel:
if isinstance(self.user_response_cancel, tuple):
self.user_response_cancel[0](self.is_checked(),
*self.user_response_cancel[1:])
else:
self.user_response_cancel(self.is_checked())
self.call_cancel_on_destroy = False
self.destroy()
def is_checked(self):
"""
Get active state of the checkbutton
"""
return self.checkbutton.get_active()
class ConfirmationDialogDoubleCheck(ConfirmationDialog):
"""
HIG compliant confirmation dialog with 2 checkbuttons
"""
def __init__(self, pritext, sectext='', checktext1='', checktext2='',
tooltip1='', tooltip2='', on_response_ok=None, on_response_cancel=None,
is_modal=True):
self.user_response_ok = on_response_ok
self.user_response_cancel = on_response_cancel
if hasattr(app.interface, 'roster') and app.interface.roster:
parent = app.interface.roster.window
else:
parent = None
HigDialog.__init__(self, parent, Gtk.MessageType.QUESTION,
Gtk.ButtonsType.OK_CANCEL, pritext, sectext, self.on_response_ok,
self.on_response_cancel)
self.set_default_response(Gtk.ResponseType.OK)
ok_button = self.get_widget_for_response(Gtk.ResponseType.OK)
ok_button.grab_focus()
vbox = self.get_content_area()
if checktext1:
self.checkbutton1 = Gtk.CheckButton.new_with_mnemonic(checktext1)
if tooltip1:
self.checkbutton1.set_tooltip_text(tooltip1)
vbox.pack_start(self.checkbutton1, False, True, 0)
else:
self.checkbutton1 = None
if checktext2:
self.checkbutton2 = Gtk.CheckButton.new_with_mnemonic(checktext2)
if tooltip2:
self.checkbutton2.set_tooltip_text(tooltip2)
vbox.pack_start(self.checkbutton2, False, True, 0)
else:
self.checkbutton2 = None
self.set_modal(is_modal)
self.popup()
def on_response_ok(self, widget):
if self.user_response_ok:
if isinstance(self.user_response_ok, tuple):
self.user_response_ok[0](self.is_checked(),
*self.user_response_ok[1:])
else:
self.user_response_ok(self.is_checked())
self.call_cancel_on_destroy = False
self.destroy()
def on_response_cancel(self, widget):
if self.user_response_cancel:
if isinstance(self.user_response_cancel, tuple):
self.user_response_cancel[0](*self.user_response_cancel[1:])
else:
self.user_response_cancel()
self.call_cancel_on_destroy = False
self.destroy()
def is_checked(self):
''' Get active state of the checkbutton '''
if self.checkbutton1:
is_checked_1 = self.checkbutton1.get_active()
else:
is_checked_1 = False
if self.checkbutton2:
is_checked_2 = self.checkbutton2.get_active()
else:
is_checked_2 = False
return [is_checked_1, is_checked_2]
class PlainConnectionDialog(ConfirmationDialogDoubleCheck):
"""
Dialog that is shown when using an insecure connection
"""
def __init__(self, account, on_ok, on_cancel):
pritext = _('Insecure connection')
sectext = _('You are about to connect to the account %(account)s '
'(%(server)s) insecurely. This means conversations will not be '
'encrypted, and is strongly discouraged.\nAre you sure you want '
'to do that?') % {'account': account,
'server': app.get_hostname_from_account(account)}
checktext1 = _('Yes, I really want to connect insecurely')
tooltip1 = _('Gajim will NOT connect unless you check this box')
checktext2 = _('_Do not ask me again')
ConfirmationDialogDoubleCheck.__init__(self, pritext, sectext,
checktext1, checktext2, tooltip1=tooltip1, on_response_ok=on_ok,
on_response_cancel=on_cancel, is_modal=False)
self.ok_button = self.get_widget_for_response(Gtk.ResponseType.OK)
self.ok_button.set_sensitive(False)
self.checkbutton1.connect('clicked', self.on_checkbutton_clicked)
self.set_title(_('Insecure connection'))
def on_checkbutton_clicked(self, widget):
self.ok_button.set_sensitive(widget.get_active())
class ConfirmationDialogDoubleRadio(ConfirmationDialog):
"""
HIG compliant confirmation dialog with 2 radios
"""
def __init__(self, pritext, sectext='', radiotext1='', radiotext2='',
on_response_ok=None, on_response_cancel=None, is_modal=True, transient_for=None):
self.user_response_ok = on_response_ok
self.user_response_cancel = on_response_cancel
if transient_for is None:
transient_for = app.app.get_active_window()
HigDialog.__init__(self, transient_for, Gtk.MessageType.QUESTION,
Gtk.ButtonsType.OK_CANCEL, pritext, sectext, self.on_response_ok,
self.on_response_cancel)
self.set_default_response(Gtk.ResponseType.OK)
ok_button = self.get_widget_for_response(Gtk.ResponseType.OK)
ok_button.grab_focus()
vbox = self.get_content_area()
self.radiobutton1 = Gtk.RadioButton(label=radiotext1)
vbox.pack_start(self.radiobutton1, False, True, 0)
self.radiobutton2 = Gtk.RadioButton(group=self.radiobutton1,
label=radiotext2)
vbox.pack_start(self.radiobutton2, False, True, 0)
self.set_modal(is_modal)
self.popup()
def on_response_ok(self, widget):
if self.user_response_ok:
if isinstance(self.user_response_ok, tuple):
self.user_response_ok[0](self.is_checked(),
*self.user_response_ok[1:])
else:
self.user_response_ok(self.is_checked())
self.call_cancel_on_destroy = False
self.destroy()
def on_response_cancel(self, widget):
if self.user_response_cancel:
if isinstance(self.user_response_cancel, tuple):
self.user_response_cancel[0](*self.user_response_cancel[1:])
else:
self.user_response_cancel()
self.call_cancel_on_destroy = False
self.destroy()
def is_checked(self):
''' Get active state of the checkbutton '''
if self.radiobutton1:
is_checked_1 = self.radiobutton1.get_active()
else:
is_checked_1 = False
if self.radiobutton2:
is_checked_2 = self.radiobutton2.get_active()
else:
is_checked_2 = False
return [is_checked_1, is_checked_2]
class FTOverwriteConfirmationDialog(ConfirmationDialog):
"""
HIG compliant confirmation dialog to overwrite or resume a file transfert
"""
def __init__(self, pritext, sectext='', propose_resume=True,
on_response=None, transient_for=None):
if transient_for:
parent = transient_for
elif hasattr(app.interface, 'roster') and app.interface.roster:
parent = app.interface.roster.window
else:
parent = None
HigDialog.__init__(self, parent, Gtk.MessageType.QUESTION,
Gtk.ButtonsType.CANCEL, pritext, sectext)
self.on_response = on_response
if propose_resume:
b = Gtk.Button(label='', stock=Gtk.STOCK_REFRESH)
align = b.get_children()[0]
hbox = align.get_children()[0]
label = hbox.get_children()[1]
label.set_text(_('_Resume'))
label.set_use_underline(True)
self.add_action_widget(b, 100)
b = Gtk.Button(label='', stock=Gtk.STOCK_SAVE_AS)
align = b.get_children()[0]
hbox = align.get_children()[0]
label = hbox.get_children()[1]
label.set_text(_('Re_place'))
label.set_use_underline(True)
self.add_action_widget(b, 200)
self.connect('response', self.on_dialog_response)
self.show_all()
def on_dialog_response(self, dialog, response):
if self.on_response:
if isinstance(self.on_response, tuple):
self.on_response[0](response, *self.on_response[1:])
else:
self.on_response(response)
self.call_cancel_on_destroy = False
self.destroy()
class CommonInputDialog:
"""
Common Class for Input dialogs
"""
def __init__(self, title, label_str, is_modal, ok_handler, cancel_handler,
transient_for=None):
self.dialog = self.xml.get_object('input_dialog')
label = self.xml.get_object('label')
self.dialog.set_title(title)
label.set_markup(label_str)
self.cancel_handler = cancel_handler
self.vbox = self.xml.get_object('vbox')
if transient_for:
self.dialog.set_transient_for(transient_for)
else:
self.dialog.set_transient_for(app.interface.roster.window)
self.ok_handler = ok_handler
okbutton = self.xml.get_object('okbutton')
okbutton.connect('clicked', self.on_okbutton_clicked)
cancelbutton = self.xml.get_object('cancelbutton')
cancelbutton.connect('clicked', self.on_cancelbutton_clicked)
self.xml.connect_signals(self)
self.dialog.show_all()
def on_input_dialog_destroy(self, widget):
if self.cancel_handler:
self.cancel_handler()
def on_okbutton_clicked(self, widget):
user_input = self.get_text()
if user_input:
user_input = user_input
self.cancel_handler = None
self.dialog.destroy()
if isinstance(self.ok_handler, tuple):
self.ok_handler[0](user_input, *self.ok_handler[1:])
else:
self.ok_handler(user_input)
def on_cancelbutton_clicked(self, widget):
self.dialog.destroy()
def destroy(self):
self.dialog.destroy()
class InputDialog(CommonInputDialog):
"""
Class for Input dialog
"""
def __init__(self, title, label_str, input_str=None, is_modal=True,
ok_handler=None, cancel_handler=None, transient_for=None):
self.xml = get_builder('input_dialog.ui')
CommonInputDialog.__init__(self, title, label_str, is_modal,
ok_handler, cancel_handler,
transient_for=transient_for)
self.input_entry = self.xml.get_object('input_entry')
if input_str:
self.set_entry(input_str)
def on_input_dialog_delete_event(self, widget, event):
'''
may be implemented by subclasses
'''
pass
def set_entry(self, value):
self.input_entry.set_text(value)
self.input_entry.select_region(0, -1) # select all
def get_text(self):
return self.input_entry.get_text()
class InputDialogCheck(InputDialog):
"""
Class for Input dialog
"""
def __init__(self, title, label_str, checktext='', input_str=None,
is_modal=True, ok_handler=None, cancel_handler=None,
transient_for=None):
self.xml = get_builder('input_dialog.ui')
InputDialog.__init__(self, title, label_str, input_str=input_str,
is_modal=is_modal, ok_handler=ok_handler,
cancel_handler=cancel_handler,
transient_for=transient_for)
self.input_entry = self.xml.get_object('input_entry')
if input_str:
self.input_entry.set_text(input_str)
self.input_entry.select_region(0, -1) # select all
if checktext:
self.checkbutton = Gtk.CheckButton.new_with_mnemonic(checktext)
self.vbox.pack_start(self.checkbutton, False, True, 0)
self.checkbutton.show()
def on_okbutton_clicked(self, widget):
user_input = self.get_text()
if user_input:
user_input = user_input
self.cancel_handler = None
self.dialog.destroy()
if isinstance(self.ok_handler, tuple):
self.ok_handler[0](user_input, self.is_checked(), *self.ok_handler[1:])
else:
self.ok_handler(user_input, self.is_checked())
def get_text(self):
return self.input_entry.get_text()
def is_checked(self):
"""
Get active state of the checkbutton
"""
try:
return self.checkbutton.get_active()
except Exception:
# There is no checkbutton
return False
class ChangeNickDialog(InputDialogCheck):
"""
Class for changing room nickname in case of conflict
"""
def __init__(self, account, room_jid, title, prompt, check_text=None,
change_nick=False, transient_for=None):
"""
change_nick must be set to True when we are already occupant of the room
and we are changing our nick
"""
InputDialogCheck.__init__(self, title, '', checktext=check_text,
input_str='', is_modal=True, ok_handler=None,
cancel_handler=None,
transient_for=transient_for)
self.room_queue = [(account, room_jid, prompt, change_nick)]
self.check_next()
def on_input_dialog_delete_event(self, widget, event):
self.on_cancelbutton_clicked(widget)
return True
def setup_dialog(self):
self.gc_control = app.interface.msg_win_mgr.get_gc_control(
self.room_jid, self.account)
if not self.gc_control and \
self.room_jid in app.interface.minimized_controls[self.account]:
self.gc_control = \
app.interface.minimized_controls[self.account][self.room_jid]
if not self.gc_control:
self.check_next()
return
label = self.xml.get_object('label')
label.set_markup(self.prompt)
self.set_entry(self.gc_control.nick + \
app.config.get('gc_proposed_nick_char'))
def check_next(self):
if len(self.room_queue) == 0:
self.cancel_handler = None
self.dialog.destroy()
if 'change_nick_dialog' in app.interface.instances:
del app.interface.instances['change_nick_dialog']
return
self.account, self.room_jid, self.prompt, self.change_nick = \
self.room_queue.pop(0)
self.setup_dialog()
if app.new_room_nick is not None and not app.gc_connected[
self.account][self.room_jid] and self.gc_control.nick != \
app.new_room_nick:
self.dialog.hide()
self.on_ok(app.new_room_nick, True)
else:
self.dialog.show()
def on_okbutton_clicked(self, widget):
nick = self.get_text()
if nick:
nick = nick
# send presence to room
try:
nick = helpers.parse_resource(nick)
except Exception:
# invalid char
ErrorDialog(_('Invalid nickname'),
_('The nickname contains invalid characters.'))
return
self.on_ok(nick, self.is_checked())
def on_ok(self, nick, is_checked):
if is_checked:
app.new_room_nick = nick
app.connections[self.account].join_gc(nick, self.room_jid, None,
change_nick=self.change_nick)
if app.gc_connected[self.account][self.room_jid]:
# We are changing nick, we will change self.nick when we receive
# presence that inform that it works
self.gc_control.new_nick = nick
else:
# We are connecting, we will not get a changed nick presence so
# change it NOW. We don't already have a nick so it's harmless
self.gc_control.nick = nick
self.check_next()
def on_cancelbutton_clicked(self, widget):
self.gc_control.new_nick = ''
self.check_next()
def add_room(self, account, room_jid, prompt, change_nick=False):
if (account, room_jid, prompt, change_nick) not in self.room_queue:
self.room_queue.append((account, room_jid, prompt, change_nick))
class InputTextDialog(CommonInputDialog):
"""
Class for multilines Input dialog (more place than InputDialog)
"""
def __init__(self, title, label_str, input_str=None, is_modal=True,
ok_handler=None, cancel_handler=None, transient_for=None):
self.xml = get_builder('input_text_dialog.ui')
CommonInputDialog.__init__(self, title, label_str, is_modal,
ok_handler, cancel_handler,
transient_for=transient_for)
self.input_buffer = self.xml.get_object('input_textview').get_buffer()
if input_str:
self.input_buffer.set_text(input_str)
start_iter, end_iter = self.input_buffer.get_bounds()
self.input_buffer.select_range(start_iter, end_iter) # select all
def get_text(self):
start_iter, end_iter = self.input_buffer.get_bounds()
return self.input_buffer.get_text(start_iter, end_iter, True)
class DoubleInputDialog:
"""
Class for Double Input dialog
"""
def __init__(self, title, label_str1, label_str2, input_str1=None,
input_str2=None, is_modal=True, ok_handler=None, cancel_handler=None,
transient_for=None):
self.xml = get_builder('dubbleinput_dialog.ui')
self.dialog = self.xml.get_object('dubbleinput_dialog')
label1 = self.xml.get_object('label1')
self.input_entry1 = self.xml.get_object('input_entry1')
label2 = self.xml.get_object('label2')
self.input_entry2 = self.xml.get_object('input_entry2')
self.dialog.set_title(title)
label1.set_markup(label_str1)
label2.set_markup(label_str2)
self.cancel_handler = cancel_handler
if input_str1:
self.input_entry1.set_text(input_str1)
self.input_entry1.select_region(0, -1) # select all
if input_str2:
self.input_entry2.set_text(input_str2)
self.input_entry2.select_region(0, -1) # select all
if transient_for:
self.dialog.set_transient_for(transient_for)
self.dialog.set_modal(is_modal)
self.ok_handler = ok_handler
okbutton = self.xml.get_object('okbutton')
okbutton.connect('clicked', self.on_okbutton_clicked)
cancelbutton = self.xml.get_object('cancelbutton')
cancelbutton.connect('clicked', self.on_cancelbutton_clicked)
self.xml.connect_signals(self)
self.dialog.show_all()
def on_dubbleinput_dialog_destroy(self, widget):
if not self.cancel_handler:
return False
if isinstance(self.cancel_handler, tuple):
self.cancel_handler[0](*self.cancel_handler[1:])
else:
self.cancel_handler()
def on_okbutton_clicked(self, widget):
user_input1 = self.input_entry1.get_text()
user_input2 = self.input_entry2.get_text()
self.cancel_handler = None
self.dialog.destroy()
if not self.ok_handler:
return
if isinstance(self.ok_handler, tuple):
self.ok_handler[0](user_input1, user_input2, *self.ok_handler[1:])
else:
self.ok_handler(user_input1, user_input2)
def on_cancelbutton_clicked(self, widget):
self.dialog.destroy()
if not self.cancel_handler:
return
if isinstance(self.cancel_handler, tuple):
self.cancel_handler[0](*self.cancel_handler[1:])
else:
self.cancel_handler()
class CertificatDialog(InformationDialog):
def __init__(self, parent, account, cert):
issuer = cert.get_issuer()
subject = cert.get_subject()
InformationDialog.__init__(self,
_('Certificate for account %s') % account, _('''<b>Issued to:</b>
Common Name (CN): %(scn)s
Organization (O): %(sorg)s
Organizationl Unit (OU): %(sou)s
Serial Number: %(sn)s
<b>Issued by:</b>
Common Name (CN): %(icn)s
Organization (O): %(iorg)s
Organizationl Unit (OU): %(iou)s
<b>Validity:</b>
Issued on: %(io)s
Expires on: %(eo)s
<b>Fingerprint</b>
SHA-1 Fingerprint: %(sha1)s
SHA-256 Fingerprint: %(sha256)s
''') % {
'scn': subject.commonName, 'sorg': subject.organizationName,
'sou': subject.organizationalUnitName,
'sn': cert.get_serial_number(), 'icn': issuer.commonName,
'iorg': issuer.organizationName,
'iou': issuer.organizationalUnitName,
'io': cert.get_notBefore().decode('utf-8'),
'eo': cert.get_notAfter().decode('utf-8'),
'sha1': cert.digest('sha1').decode('utf-8'),
'sha256': cert.digest('sha256').decode('utf-8')})
surface = load_icon('application-certificate', self, size=32)
if surface is not None:
img = Gtk.Image.new_from_surface(surface)
img.show_all()
self.set_image(img)
self.set_transient_for(parent)
self.set_title(_('Certificate for account %s') % account)
class SSLErrorDialog(ConfirmationDialogDoubleCheck):
def __init__(self, account, certificate, pritext, sectext, checktext1,
checktext2, on_response_ok=None, on_response_cancel=None):
self.account = account
self.cert = certificate
ConfirmationDialogDoubleCheck.__init__(
self, pritext, sectext,
checktext1, checktext2, on_response_ok=on_response_ok,
on_response_cancel=on_response_cancel, is_modal=False)
b = Gtk.Button(_('View cert…'))
b.connect('clicked', self.on_cert_clicked)
b.show_all()
area = self.get_action_area()
area.pack_start(b, True, True, 0)
def on_cert_clicked(self, button):
CertificatDialog(self, self.account, self.cert)

View file

@ -1,5 +1,3 @@
# Copyright (C) 2017 Philipp Hörist <philipp AT hoerist.com>
#
# This file is part of Gajim. # This file is part of Gajim.
# #
# Gajim is free software; you can redistribute it and/or modify # Gajim is free software; you can redistribute it and/or modify
@ -25,7 +23,7 @@ from gajim.common import ged
from gajim.common.const import ArchiveState from gajim.common.const import ArchiveState
from gajim.gtk.util import load_icon from gajim.gtk.util import load_icon
log = logging.getLogger('gajim.c.message_archiving') log = logging.getLogger('gajim.gtk.history_sync')
class Pages(IntEnum): class Pages(IntEnum):

325
gajim/gtk/join_groupchat.py Normal file
View file

@ -0,0 +1,325 @@
# 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/>.
import nbxmpp
from gi.repository import Gtk
from gi.repository import Gdk
from gajim.common import app
from gajim.common import helpers
from gajim.common import ged
from gajim.common.caps_cache import muc_caps_cache
from gajim.common.exceptions import GajimGeneralException
from gajim.gtk import ErrorDialog
from gajim.gtk.util import get_builder
class JoinGroupchatWindow(Gtk.ApplicationWindow):
def __init__(self, account, room_jid, password=None, automatic=None,
transient_for=None):
Gtk.ApplicationWindow.__init__(self)
self.set_name('JoinGroupchat')
self.set_application(app.app)
self.set_show_menubar(False)
self.set_resizable(False)
self.set_position(Gtk.WindowPosition.CENTER)
self.set_title(_('Join Group Chat'))
if transient_for:
self.set_transient_for(transient_for)
self.automatic = automatic
self.password = password
self.requested_jid = None
self.room_jid = room_jid
self.account = account
if self.room_jid is None:
self.minimal_mode = False
else:
self.minimal_mode = True
glade_objects = ['main_box', 'account_label', 'account_combo',
'server_label', 'server_combo', 'room_entry',
'recently_button', 'recently_popover',
'recently_treeview', 'search_button', 'password_label',
'password_entry', 'nick_entry', 'bookmark_switch',
'autojoin_switch']
self.builder = get_builder('join_groupchat_window.ui')
for obj in glade_objects:
setattr(self, obj, self.builder.get_object(obj))
self.add(self.main_box)
# Show widgets depending on the mode the window is in
if not self.minimal_mode:
self.recently_button.show()
self.search_button.show()
accounts = app.get_enabled_accounts_with_labels()
account_liststore = self.account_combo.get_model()
for acc in accounts:
account_liststore.append(acc)
if not accounts:
return
if not self.account:
self.account = accounts[0][0]
self.builder.connect_signals(self)
self.connect('key-press-event', self._on_key_press_event)
self.connect('destroy', self._on_destroy)
if not self.minimal_mode:
app.ged.register_event_handler('agent-info-received', ged.GUI1,
self._nec_agent_info_received)
app.ged.register_event_handler('agent-info-error-received', ged.GUI1,
self._nec_agent_info_error_received)
# Hide account combobox if there is only one account
if len(accounts) == 1:
self.account_combo.hide()
self.account_label.hide()
self.account_combo.set_active_id(self.account)
if self.minimal_mode:
if '@' in self.room_jid:
(room, server) = self.room_jid.split('@')
self.room_entry.set_text(room)
if not muc_caps_cache.supports(
self.room_jid, 'muc_passwordprotected'):
self.password_entry.hide()
self.password_label.hide()
self.nick_entry.grab_focus()
else:
self.password_entry.grab_focus()
else:
server = self.room_jid
self.room_entry.grab_focus()
self.server_combo.insert_text(0, server)
self.server_combo.set_active(0)
if self.password is not None:
self.password_entry.set_text(self.password)
# Set bookmark switch sensitive if server supports bookmarks
acc = self.account_combo.get_active_id()
if not app.connections[acc].private_storage_supported:
self.bookmark_switch.set_sensitive(False)
self.autojoin_switch.set_sensitive(False)
self.show_all()
def set_room(self, room_jid):
room, server = app.get_name_and_server_from_jid(room_jid)
self.room_entry.set_text(room)
self.server_combo.get_child().set_text(server)
def _fill_recent_and_servers(self, account):
recently_liststore = self.recently_treeview.get_model()
recently_liststore.clear()
self.server_combo.remove_all()
recent = app.get_recent_groupchats(account)
servers = []
for groupchat in recent:
label = '%s@%s' % (groupchat.room, groupchat.server)
recently_liststore.append([groupchat.server,
groupchat.room,
groupchat.nickname,
label])
servers.append(groupchat.server)
self.recently_button.set_sensitive(bool(recent))
for server in set(servers):
self.server_combo.append_text(server)
# Add own Server to ComboBox
muc_domain = app.get_muc_domain(account)
if muc_domain is not None:
self.server_combo.insert_text(0, muc_domain)
def _on_recent_selected(self, treeview, *args):
(model, iter_) = treeview.get_selection().get_selected()
self.server_combo.get_child().set_text(model[iter_][0])
self.room_entry.set_text(model[iter_][1])
self.nick_entry.set_text(model[iter_][2])
self.recently_popover.popdown()
def _on_account_combo_changed(self, combo):
account = combo.get_active_id()
self.account = account
self.nick_entry.set_text(app.nicks[account])
self._fill_recent_and_servers(account)
def _on_jid_detection_changed(self, widget):
text = widget.get_text()
if text.startswith('xmpp:'):
text = text[5:]
if '@' in text:
room, server = text.split('@', 1)
server = server.split('?')[0]
widget.set_text('')
if room:
self.room_entry.set_text(room)
if server:
self.server_combo.get_child().set_text(server)
else:
self.server_combo.grab_focus()
def _on_key_press_event(self, widget, event):
if event.keyval == Gdk.KEY_Escape:
self.destroy()
if event.keyval == Gdk.KEY_Return:
self._on_join_clicked()
return True
def _on_join_clicked(self, *args):
account = self.account_combo.get_active_id()
nickname = self.nick_entry.get_text()
invisible_show = app.SHOW_LIST.index('invisible')
if app.connections[account].connected == invisible_show:
app.interface.raise_dialog('join-while-invisible')
return
server = self.server_combo.get_active_text()
room = self.room_entry.get_text()
if room == '':
ErrorDialog(_('Invalid Room'),
_('Please choose a room'), transient_for=self)
return
self.room_jid = '%s@%s' % (room, server)
self.room_jid = self.room_jid.lower()
if app.in_groupchat(account, self.room_jid):
# If we already in the groupchat, join_gc_room will bring
# it to front
app.interface.join_gc_room(account, self.room_jid, nickname, '')
self.destroy()
return
if nickname == '':
ErrorDialog(_('Invalid Nickname'),
_('Please choose a nickname'), transient_for=self)
return
try:
helpers.parse_resource(nickname)
except helpers.InvalidFormat as error:
ErrorDialog(_('Invalid Nickname'), str(error), transient_for=self)
return
try:
helpers.parse_jid(self.room_jid)
except helpers.InvalidFormat as error:
ErrorDialog(_('Invalid JID'), str(error), transient_for=self)
return
if not app.account_is_connected(account):
ErrorDialog(
_('You are not connected to the server'),
_('You can not join a group chat unless you are connected.'),
transient_for=self)
return
password = self.password_entry.get_text()
self._add_bookmark(account, nickname, password)
app.add_recent_groupchat(account, self.room_jid, nickname)
if self.automatic:
app.automatic_rooms[self.account][self.room_jid] = self.automatic
app.interface.join_gc_room(account, self.room_jid, nickname, password)
self.destroy()
def _on_cancel_clicked(self, *args):
self.destroy()
def _on_bookmark_activate(self, switch, param):
bookmark_state = switch.get_active()
self.autojoin_switch.set_sensitive(bookmark_state)
if not bookmark_state:
self.autojoin_switch.set_active(False)
def _add_bookmark(self, account, nickname, password):
con = app.connections[account]
if not con.private_storage_supported:
return
add_bookmark = self.bookmark_switch.get_active()
if not add_bookmark:
return
autojoin = int(self.autojoin_switch.get_active())
# Add as bookmark, with autojoin and not minimized
name = app.get_nick_from_jid(self.room_jid)
con.get_module('Bookmarks').add_bookmark(
name, self.room_jid, autojoin, 1, password, nickname)
def _on_destroy(self, *args):
if not self.minimal_mode:
app.ged.remove_event_handler('agent-info-received', ged.GUI1,
self._nec_agent_info_received)
app.ged.remove_event_handler('agent-info-error-received', ged.GUI1,
self._nec_agent_info_error_received)
def _on_search_clicked(self, widget):
server = self.server_combo.get_active_text().strip()
self.requested_jid = server
app.connections[self.account].discoverInfo(server)
def _nec_agent_info_error_received(self, obj):
if obj.conn.name != self.account:
return
if obj.jid != self.requested_jid:
return
self.requested_jid = None
ErrorDialog(_('Wrong server'),
_('%s is not a groupchat server') % obj.jid,
transient_for=self)
def _nec_agent_info_received(self, obj):
if obj.conn.name != self.account:
return
if obj.jid != self.requested_jid:
return
self.requested_jid = None
if nbxmpp.NS_MUC not in obj.features:
ErrorDialog(_('Wrong server'),
_('%s is not a groupchat server') % obj.jid,
transient_for=self)
return
if obj.jid in app.interface.instances[self.account]['disco']:
app.interface.instances[self.account]['disco'][obj.jid].window.\
present()
else:
try:
# Object will add itself to the window dict
from gajim.disco import ServiceDiscoveryWindow
ServiceDiscoveryWindow(
self.account, obj.jid,
initial_identities=[{'category': 'conference',
'type': 'text'}])
except GajimGeneralException:
pass

View file

@ -21,7 +21,7 @@ from gajim.common import app
from gajim.common import ged from gajim.common import ged
from gajim.gtk.util import get_builder from gajim.gtk.util import get_builder
from gajim.dialogs import HigDialog from gajim.gtk import HigDialog
log = logging.getLogger('gajim.gtk.mam_preferences') log = logging.getLogger('gajim.gtk.mam_preferences')

547
gajim/gtk/privacy_list.py Normal file
View file

@ -0,0 +1,547 @@
# 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 Gdk
from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import GObject
from gajim.common import app
from gajim.common import ged
from gajim.gtk import ErrorDialog
from gajim.gtk.util import get_builder
class PrivacyListWindow:
def __init__(self, account, privacy_list_name, action):
'''action is 'EDIT' or 'NEW' depending on if we create a new priv list
or edit an already existing one'''
self.account = account
self.privacy_list_name = privacy_list_name
# Dicts and Default Values
self.active_rule = ''
self.global_rules = {}
self.list_of_groups = {}
self.max_order = 0
# Default Edit Values
self.edit_rule_type = 'jid'
self.allow_deny = 'allow'
# Connect to gtk builder
self.xml = get_builder('privacy_list_window.ui')
self.window = self.xml.get_object('privacy_list_edit_window')
# Add Widgets
for widget_to_add in ('title_hbox', 'privacy_lists_title_label',
'list_of_rules_label', 'add_edit_rule_label', 'delete_open_buttons_hbox',
'privacy_list_active_checkbutton', 'privacy_list_default_checkbutton',
'list_of_rules_combobox', 'delete_open_buttons_hbox',
'delete_rule_button', 'open_rule_button', 'edit_allow_radiobutton',
'edit_deny_radiobutton', 'edit_type_jabberid_radiobutton',
'edit_type_jabberid_entry', 'edit_type_group_radiobutton',
'edit_type_group_combobox', 'edit_type_subscription_radiobutton',
'edit_type_subscription_combobox', 'edit_type_select_all_radiobutton',
'edit_queries_send_checkbutton', 'edit_send_messages_checkbutton',
'edit_view_status_checkbutton', 'edit_all_checkbutton',
'edit_order_spinbutton', 'new_rule_button', 'save_rule_button',
'privacy_list_refresh_button', 'privacy_list_close_button',
'edit_send_status_checkbutton', 'add_edit_vbox',
'privacy_list_active_checkbutton', 'privacy_list_default_checkbutton'):
self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add)
self.privacy_lists_title_label.set_label(
_('Privacy List <b><i>%s</i></b>') %
GLib.markup_escape_text(self.privacy_list_name))
if len(app.connections) > 1:
title = _('Privacy List for %s') % self.account
else:
title = _('Privacy List')
self.delete_rule_button.set_sensitive(False)
self.open_rule_button.set_sensitive(False)
self.privacy_list_active_checkbutton.set_sensitive(False)
self.privacy_list_default_checkbutton.set_sensitive(False)
self.list_of_rules_combobox.set_sensitive(False)
# set jabber id completion
jids_list_store = Gtk.ListStore(GObject.TYPE_STRING)
for jid in app.contacts.get_jid_list(self.account):
jids_list_store.append([jid])
jid_entry_completion = Gtk.EntryCompletion()
jid_entry_completion.set_text_column(0)
jid_entry_completion.set_model(jids_list_store)
jid_entry_completion.set_popup_completion(True)
self.edit_type_jabberid_entry.set_completion(jid_entry_completion)
if action == 'EDIT':
self.refresh_rules()
model = self.edit_type_group_combobox.get_model()
count = 0
for group in app.groups[self.account]:
self.list_of_groups[group] = count
count += 1
model.append([group])
self.edit_type_group_combobox.set_active(0)
self.window.set_title(title)
app.ged.register_event_handler('privacy-list-received', ged.GUI1,
self._nec_privacy_list_received)
app.ged.register_event_handler('privacy-lists-received', ged.GUI1,
self._nec_privacy_lists_received)
self.window.show_all()
self.add_edit_vbox.hide()
self.xml.connect_signals(self)
def on_key_press_event(self, widget, event):
if event.keyval == Gdk.KEY_Escape:
self.window.destroy()
def on_privacy_list_edit_window_destroy(self, widget):
key_name = 'privacy_list_%s' % self.privacy_list_name
if key_name in app.interface.instances[self.account]:
del app.interface.instances[self.account][key_name]
app.ged.remove_event_handler('privacy-list-received', ged.GUI1,
self._nec_privacy_list_received)
app.ged.remove_event_handler('privacy-lists-received', ged.GUI1,
self._nec_privacy_lists_received)
def _nec_privacy_lists_received(self, obj):
if obj.conn.name != self.account:
return
if obj.active_list == self.privacy_list_name:
self.privacy_list_active_checkbutton.set_active(True)
else:
self.privacy_list_active_checkbutton.set_active(False)
if obj.default_list == self.privacy_list_name:
self.privacy_list_default_checkbutton.set_active(True)
else:
self.privacy_list_default_checkbutton.set_active(False)
def privacy_list_received(self, rules):
model = self.list_of_rules_combobox.get_model()
model.clear()
self.global_rules = {}
for rule in rules:
if 'type' in rule:
text_item = _('Order: %(order)s, action: %(action)s, type: %(type)s'
', value: %(value)s') % {'order': rule['order'],
'action': rule['action'], 'type': rule['type'],
'value': rule['value']}
else:
text_item = _('Order: %(order)s, action: %(action)s') % \
{'order': rule['order'], 'action': rule['action']}
if int(rule['order']) > self.max_order:
self.max_order = int(rule['order'])
self.global_rules[text_item] = rule
model.append([text_item])
if len(rules) == 0:
self.title_hbox.set_sensitive(False)
self.list_of_rules_combobox.set_sensitive(False)
self.delete_rule_button.set_sensitive(False)
self.open_rule_button.set_sensitive(False)
self.privacy_list_active_checkbutton.set_sensitive(False)
self.privacy_list_default_checkbutton.set_sensitive(False)
else:
self.list_of_rules_combobox.set_active(0)
self.title_hbox.set_sensitive(True)
self.list_of_rules_combobox.set_sensitive(True)
self.delete_rule_button.set_sensitive(True)
self.open_rule_button.set_sensitive(True)
self.privacy_list_active_checkbutton.set_sensitive(True)
self.privacy_list_default_checkbutton.set_sensitive(True)
self.reset_fields()
con = app.connections[self.account]
con.get_module('PrivacyLists').get_privacy_lists()
def _nec_privacy_list_received(self, obj):
if obj.conn.name != self.account:
return
if obj.list_name != self.privacy_list_name:
return
self.privacy_list_received(obj.rules)
def refresh_rules(self):
con = app.connections[self.account]
con.get_module('PrivacyLists').get_privacy_list(self.privacy_list_name)
def on_delete_rule_button_clicked(self, widget):
model = self.list_of_rules_combobox.get_model()
iter_ = self.list_of_rules_combobox.get_active_iter()
_rule = model[iter_][0]
tags = []
for rule in self.global_rules:
if rule != _rule:
tags.append(self.global_rules[rule])
con = app.connections[self.account]
con.get_module('PrivacyLists').set_privacy_list(
self.privacy_list_name, tags)
self.privacy_list_received(tags)
self.add_edit_vbox.hide()
if not tags: # we removed latest rule
if 'privacy_lists' in app.interface.instances[self.account]:
win = app.interface.instances[self.account]['privacy_lists']
win.remove_privacy_list_from_combobox(self.privacy_list_name)
win.draw_widgets()
def on_open_rule_button_clicked(self, widget):
self.add_edit_rule_label.set_label(_('<b>Edit a rule</b>'))
active_num = self.list_of_rules_combobox.get_active()
if active_num == -1:
self.active_rule = ''
else:
model = self.list_of_rules_combobox.get_model()
iter_ = self.list_of_rules_combobox.get_active_iter()
self.active_rule = model[iter_][0]
if self.active_rule != '':
rule_info = self.global_rules[self.active_rule]
self.edit_order_spinbutton.set_value(int(rule_info['order']))
if 'type' in rule_info:
if rule_info['type'] == 'jid':
self.edit_type_jabberid_radiobutton.set_active(True)
self.edit_type_jabberid_entry.set_text(rule_info['value'])
elif rule_info['type'] == 'group':
self.edit_type_group_radiobutton.set_active(True)
if rule_info['value'] in self.list_of_groups:
self.edit_type_group_combobox.set_active(
self.list_of_groups[rule_info['value']])
else:
self.edit_type_group_combobox.set_active(0)
elif rule_info['type'] == 'subscription':
self.edit_type_subscription_radiobutton.set_active(True)
sub_value = rule_info['value']
if sub_value == 'none':
self.edit_type_subscription_combobox.set_active(0)
elif sub_value == 'both':
self.edit_type_subscription_combobox.set_active(1)
elif sub_value == 'from':
self.edit_type_subscription_combobox.set_active(2)
elif sub_value == 'to':
self.edit_type_subscription_combobox.set_active(3)
else:
self.edit_type_select_all_radiobutton.set_active(True)
else:
self.edit_type_select_all_radiobutton.set_active(True)
self.edit_send_messages_checkbutton.set_active(False)
self.edit_queries_send_checkbutton.set_active(False)
self.edit_view_status_checkbutton.set_active(False)
self.edit_send_status_checkbutton.set_active(False)
self.edit_all_checkbutton.set_active(False)
if not rule_info['child']:
self.edit_all_checkbutton.set_active(True)
else:
if 'presence-out' in rule_info['child']:
self.edit_send_status_checkbutton.set_active(True)
if 'presence-in' in rule_info['child']:
self.edit_view_status_checkbutton.set_active(True)
if 'iq' in rule_info['child']:
self.edit_queries_send_checkbutton.set_active(True)
if 'message' in rule_info['child']:
self.edit_send_messages_checkbutton.set_active(True)
if rule_info['action'] == 'allow':
self.edit_allow_radiobutton.set_active(True)
else:
self.edit_deny_radiobutton.set_active(True)
self.add_edit_vbox.show()
def on_edit_all_checkbutton_toggled(self, widget):
if widget.get_active():
self.edit_send_messages_checkbutton.set_active(True)
self.edit_queries_send_checkbutton.set_active(True)
self.edit_view_status_checkbutton.set_active(True)
self.edit_send_status_checkbutton.set_active(True)
self.edit_send_messages_checkbutton.set_sensitive(False)
self.edit_queries_send_checkbutton.set_sensitive(False)
self.edit_view_status_checkbutton.set_sensitive(False)
self.edit_send_status_checkbutton.set_sensitive(False)
else:
self.edit_send_messages_checkbutton.set_active(False)
self.edit_queries_send_checkbutton.set_active(False)
self.edit_view_status_checkbutton.set_active(False)
self.edit_send_status_checkbutton.set_active(False)
self.edit_send_messages_checkbutton.set_sensitive(True)
self.edit_queries_send_checkbutton.set_sensitive(True)
self.edit_view_status_checkbutton.set_sensitive(True)
self.edit_send_status_checkbutton.set_sensitive(True)
def on_privacy_list_active_checkbutton_toggled(self, widget):
name = None
if widget.get_active():
name = self.privacy_list_name
con = app.connections[self.account]
con.get_module('PrivacyLists').set_active_list(name)
def on_privacy_list_default_checkbutton_toggled(self, widget):
name = None
if widget.get_active():
name = self.privacy_list_name
con = app.connections[self.account]
con.get_module('PrivacyLists').set_default_list(name)
def on_new_rule_button_clicked(self, widget):
self.reset_fields()
self.add_edit_vbox.show()
def reset_fields(self):
self.edit_type_jabberid_entry.set_text('')
self.edit_allow_radiobutton.set_active(True)
self.edit_type_jabberid_radiobutton.set_active(True)
self.active_rule = ''
self.edit_send_messages_checkbutton.set_active(False)
self.edit_queries_send_checkbutton.set_active(False)
self.edit_view_status_checkbutton.set_active(False)
self.edit_send_status_checkbutton.set_active(False)
self.edit_all_checkbutton.set_active(False)
self.edit_order_spinbutton.set_value(self.max_order + 1)
self.edit_type_group_combobox.set_active(0)
self.edit_type_subscription_combobox.set_active(0)
self.add_edit_rule_label.set_label(_('<b>Add a rule</b>'))
def get_current_tags(self):
if self.edit_type_jabberid_radiobutton.get_active():
edit_type = 'jid'
edit_value = self.edit_type_jabberid_entry.get_text()
elif self.edit_type_group_radiobutton.get_active():
edit_type = 'group'
model = self.edit_type_group_combobox.get_model()
iter_ = self.edit_type_group_combobox.get_active_iter()
edit_value = model[iter_][0]
elif self.edit_type_subscription_radiobutton.get_active():
edit_type = 'subscription'
subs = ['none', 'both', 'from', 'to']
edit_value = subs[self.edit_type_subscription_combobox.get_active()]
elif self.edit_type_select_all_radiobutton.get_active():
edit_type = ''
edit_value = ''
edit_order = str(self.edit_order_spinbutton.get_value_as_int())
if self.edit_allow_radiobutton.get_active():
edit_deny = 'allow'
else:
edit_deny = 'deny'
child = []
if not self.edit_all_checkbutton.get_active():
if self.edit_send_messages_checkbutton.get_active():
child.append('message')
if self.edit_queries_send_checkbutton.get_active():
child.append('iq')
if self.edit_send_status_checkbutton.get_active():
child.append('presence-out')
if self.edit_view_status_checkbutton.get_active():
child.append('presence-in')
if edit_type != '':
return {'order': edit_order, 'action': edit_deny,
'type': edit_type, 'value': edit_value, 'child': child}
return {'order': edit_order, 'action': edit_deny, 'child': child}
def on_save_rule_button_clicked(self, widget):
tags = []
current_tags = self.get_current_tags()
if int(current_tags['order']) > self.max_order:
self.max_order = int(current_tags['order'])
if self.active_rule == '':
tags.append(current_tags)
for rule in self.global_rules:
if rule != self.active_rule:
tags.append(self.global_rules[rule])
else:
tags.append(current_tags)
con = app.connections[self.account]
con.get_module('PrivacyLists').set_privacy_list(
self.privacy_list_name, tags)
self.refresh_rules()
self.add_edit_vbox.hide()
if 'privacy_lists' in app.interface.instances[self.account]:
win = app.interface.instances[self.account]['privacy_lists']
win.add_privacy_list_to_combobox(self.privacy_list_name)
win.draw_widgets()
def on_list_of_rules_combobox_changed(self, widget):
self.add_edit_vbox.hide()
def on_edit_type_radiobutton_changed(self, widget, radiobutton):
active_bool = widget.get_active()
if active_bool:
self.edit_rule_type = radiobutton
def on_edit_allow_radiobutton_changed(self, widget, radiobutton):
active_bool = widget.get_active()
if active_bool:
self.allow_deny = radiobutton
def on_close_button_clicked(self, widget):
self.window.destroy()
class PrivacyListsWindow:
"""
Window that is the main window for Privacy Lists; we can list there the
privacy lists and ask to create a new one or edit an already there one
"""
def __init__(self, account):
self.account = account
self.privacy_lists_save = []
self.xml = get_builder('privacy_lists_window.ui')
self.window = self.xml.get_object('privacy_lists_first_window')
for widget_to_add in (
'list_of_privacy_lists_combobox', 'delete_privacy_list_button',
'open_privacy_list_button', 'new_privacy_list_button',
'new_privacy_list_entry', 'privacy_lists_refresh_button',
'close_privacy_lists_window_button'):
self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add)
self.draw_privacy_lists_in_combobox([])
self.privacy_lists_refresh()
self.enabled = True
if len(app.connections) > 1:
title = _('Privacy Lists for %s') % self.account
else:
title = _('Privacy Lists')
self.window.set_title(title)
app.ged.register_event_handler('privacy-lists-received', ged.GUI1,
self._nec_privacy_lists_received)
app.ged.register_event_handler('privacy-list-removed', ged.GUI1,
self._nec_privacy_lists_removed)
self.window.show_all()
self.xml.connect_signals(self)
def on_key_press_event(self, widget, event):
if event.keyval == Gdk.KEY_Escape:
self.window.destroy()
def on_privacy_lists_first_window_destroy(self, widget):
if 'privacy_lists' in app.interface.instances[self.account]:
del app.interface.instances[self.account]['privacy_lists']
app.ged.remove_event_handler('privacy-lists-received', ged.GUI1,
self._nec_privacy_lists_received)
app.ged.remove_event_handler('privacy-list-removed', ged.GUI1,
self._nec_privacy_lists_removed)
def remove_privacy_list_from_combobox(self, privacy_list):
if privacy_list not in self.privacy_lists_save:
return
model = self.list_of_privacy_lists_combobox.get_model()
for entry in model:
if entry[0] == privacy_list:
model.remove(entry.iter)
self.privacy_lists_save.remove(privacy_list)
def add_privacy_list_to_combobox(self, privacy_list):
if privacy_list in self.privacy_lists_save:
return
model = self.list_of_privacy_lists_combobox.get_model()
model.append([privacy_list])
self.privacy_lists_save.append(privacy_list)
def draw_privacy_lists_in_combobox(self, privacy_lists):
self.list_of_privacy_lists_combobox.set_active(-1)
self.list_of_privacy_lists_combobox.get_model().clear()
self.privacy_lists_save = []
for add_item in privacy_lists:
self.add_privacy_list_to_combobox(add_item)
self.draw_widgets()
def draw_widgets(self):
if len(self.privacy_lists_save) == 0:
self.list_of_privacy_lists_combobox.set_sensitive(False)
self.open_privacy_list_button.set_sensitive(False)
self.delete_privacy_list_button.set_sensitive(False)
else:
self.list_of_privacy_lists_combobox.set_sensitive(True)
self.list_of_privacy_lists_combobox.set_active(0)
self.open_privacy_list_button.set_sensitive(True)
self.delete_privacy_list_button.set_sensitive(True)
def on_close_button_clicked(self, widget):
self.window.destroy()
def on_delete_privacy_list_button_clicked(self, widget):
active_list = self.privacy_lists_save[
self.list_of_privacy_lists_combobox.get_active()]
con = app.connections[self.account]
con.get_module('PrivacyLists').del_privacy_list(active_list)
def privacy_list_removed(self, active_list):
self.privacy_lists_save.remove(active_list)
self.privacy_lists_received(self.privacy_lists_save)
def _nec_privacy_lists_removed(self, obj):
if obj.conn.name != self.account:
return
self.privacy_list_removed(obj.list_name)
def privacy_lists_received(self, lists):
privacy_lists = []
for privacy_list in lists:
privacy_lists.append(privacy_list)
self.draw_privacy_lists_in_combobox(privacy_lists)
def _nec_privacy_lists_received(self, obj):
if obj.conn.name != self.account:
return
self.privacy_lists_received(obj.lists)
def privacy_lists_refresh(self):
con = app.connections[self.account]
con.get_module('PrivacyLists').get_privacy_lists()
def on_new_privacy_list_button_clicked(self, widget):
name = self.new_privacy_list_entry.get_text()
if not name:
ErrorDialog(
_('Invalid List Name'),
_('You must enter a name to create a privacy list.'),
transient_for=self.window)
return
key_name = 'privacy_list_%s' % name
if key_name in app.interface.instances[self.account]:
app.interface.instances[self.account][key_name].window.present()
else:
app.interface.instances[self.account][key_name] = \
PrivacyListWindow(self.account, name, 'NEW')
self.new_privacy_list_entry.set_text('')
def on_privacy_lists_refresh_button_clicked(self, widget):
self.privacy_lists_refresh()
def on_open_privacy_list_button_clicked(self, widget):
name = self.privacy_lists_save[
self.list_of_privacy_lists_combobox.get_active()]
key_name = 'privacy_list_%s' % name
if key_name in app.interface.instances[self.account]:
app.interface.instances[self.account][key_name].window.present()
else:
app.interface.instances[self.account][key_name] = \
PrivacyListWindow(self.account, name, 'EDIT')

View file

@ -1,34 +1,28 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2017 Philipp Hörist <philipp AT hoerist.com>
#
# This file is part of Gajim. # This file is part of Gajim.
# #
# Gajim is free software: you can redistribute it and/or modify # Gajim is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published
# the Free Software Foundation, either version 3 of the License, or # by the Free Software Foundation; version 3 only.
# (at your option) any later version.
# #
# Gajim is distributed in the hope that it will be useful, # Gajim is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>. # along with Gajim. If not, see <http://www.gnu.org/licenses/>.
import logging
from collections import namedtuple from collections import namedtuple
from datetime import timedelta from datetime import timedelta
import logging
from gi.repository import Gtk
import nbxmpp import nbxmpp
from gi.repository import Gtk
from gajim.common import app from gajim.common import app
from gajim.common import ged from gajim.common import ged
from gajim.gtkgui_helpers import get_icon_pixmap, Color
log = logging.getLogger('gajim.serverinfo') log = logging.getLogger('gajim.gtk.serverinfo')
class ServerInfoDialog(Gtk.Dialog): class ServerInfoDialog(Gtk.Dialog):
@ -221,16 +215,18 @@ class FeatureItem(Gtk.Grid):
def set_feature(self, available, enabled): def set_feature(self, available, enabled):
if not available: if not available:
self.icon.set_from_pixbuf( self.icon.set_from_icon_name('window-close-symbolic',
get_icon_pixmap('window-close-symbolic', color=[Color.RED])) Gtk.IconSize.MENU)
self.icon.get_style_context().add_class('error-color')
elif enabled is False: elif enabled is False:
self.icon.set_from_pixbuf( self.icon.set_from_icon_name('dialog-warning-symbolic',
get_icon_pixmap('dialog-warning-symbolic', Gtk.IconSize.MENU)
color=[Color.ORANGE]))
self.tooltip += _('\nDisabled in config') self.tooltip += _('\nDisabled in config')
self.icon.get_style_context().add_class('warning-color')
else: else:
self.icon.set_from_pixbuf( self.icon.set_from_icon_name('emblem-ok-symbolic',
get_icon_pixmap('emblem-ok-symbolic', color=[Color.GREEN])) Gtk.IconSize.MENU)
self.icon.get_style_context().add_class('success-color')
def update(self, feature): def update(self, feature):
self.tooltip = feature.tooltip self.tooltip = feature.tooltip

335
gajim/gtk/single_message.py Normal file
View file

@ -0,0 +1,335 @@
# 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 Gdk
from gi.repository import GLib
from gajim.common import app
from gajim.common import dataforms
from gajim.common import helpers
from gajim.common.connection_handlers_events import MessageOutgoingEvent
from gajim.gtk import ErrorDialog
from gajim.gtk import AspellDictError
from gajim.gtk.util import get_builder
from gajim.gtk.util import get_iconset_name_for
from gajim.gtk.util import get_completion_liststore
from gajim.gtk.util import move_window
from gajim.gtk.util import resize_window
from gajim.dataforms_widget import DataFormWidget
from gajim.conversation_textview import ConversationTextview
if app.is_installed('GSPELL'):
from gi.repository import Gspell
class SingleMessageWindow:
"""
SingleMessageWindow can send or show a received singled message depending on
action argument which can be 'send' or 'receive'
"""
# Keep a reference on windows so garbage collector don't restroy them
instances = []
def __init__(self, account, to='', action='', from_whom='', subject='',
message='', resource='', session=None, form_node=None):
self.instances.append(self)
self.account = account
self.action = action
self.subject = subject
self.message = message
self.to = to
self.from_whom = from_whom
self.resource = resource
self.session = session
self.xml = get_builder('single_message_window.ui')
self.window = self.xml.get_object('single_message_window')
self.count_chars_label = self.xml.get_object('count_chars_label')
self.from_label = self.xml.get_object('from_label')
self.from_entry = self.xml.get_object('from_entry')
self.to_label = self.xml.get_object('to_label')
self.to_entry = self.xml.get_object('to_entry')
self.subject_entry = self.xml.get_object('subject_entry')
self.message_scrolledwindow = self.xml.get_object(
'message_scrolledwindow')
self.message_textview = self.xml.get_object('message_textview')
self.message_tv_buffer = self.message_textview.get_buffer()
self.conversation_scrolledwindow = self.xml.get_object(
'conversation_scrolledwindow')
self.conversation_textview = ConversationTextview(
account, used_in_history_window=True)
self.conversation_textview.tv.show()
self.conversation_tv_buffer = self.conversation_textview.tv.get_buffer()
self.xml.get_object('conversation_scrolledwindow').add(
self.conversation_textview.tv)
self.form_widget = None
parent_box = self.xml.get_object('conversation_scrolledwindow').\
get_parent()
if form_node:
dataform = dataforms.ExtendForm(node=form_node)
dataform.type_ = 'submit'
self.form_widget = DataFormWidget(dataform)
self.form_widget.show_all()
parent_box.add(self.form_widget)
parent_box.child_set_property(self.form_widget, 'position',
parent_box.child_get_property(self.xml.get_object(
'conversation_scrolledwindow'), 'position'))
self.action = 'form'
self.send_button = self.xml.get_object('send_button')
self.reply_button = self.xml.get_object('reply_button')
self.send_and_close_button = self.xml.get_object('send_and_close_button')
self.cancel_button = self.xml.get_object('cancel_button')
self.close_button = self.xml.get_object('close_button')
self.message_tv_buffer.connect('changed', self.update_char_counter)
if isinstance(to, list):
jid = ', '.join( [i[0].get_full_jid() for i in to])
self.to_entry.set_text(jid)
self.to_entry.set_sensitive(False)
else:
self.to_entry.set_text(to)
if app.config.get('use_speller') and app.is_installed('GSPELL') and action == 'send':
lang = app.config.get('speller_language')
gspell_lang = Gspell.language_lookup(lang)
if gspell_lang is None:
AspellDictError(lang)
else:
spell_buffer = Gspell.TextBuffer.get_from_gtk_text_buffer(
self.message_textview.get_buffer())
spell_buffer.set_spell_checker(Gspell.Checker.new(gspell_lang))
spell_view = Gspell.TextView.get_from_gtk_text_view(
self.message_textview)
spell_view.set_inline_spell_checking(True)
spell_view.set_enable_language_menu(True)
self.prepare_widgets_for(self.action)
# set_text(None) raises TypeError exception
if self.subject is None:
self.subject = ''
self.subject_entry.set_text(self.subject)
if to == '':
liststore = get_completion_liststore(self.to_entry)
self.completion_dict = helpers.get_contact_dict_for_account(account)
keys = sorted(self.completion_dict.keys())
for jid in keys:
contact = self.completion_dict[jid]
status_icon = get_iconset_name_for(contact.show)
liststore.append((status_icon, jid))
else:
self.completion_dict = {}
self.xml.connect_signals(self)
# get window position and size from config
resize_window(self.window,
app.config.get('single-msg-width'),
app.config.get('single-msg-height'))
move_window(self.window,
app.config.get('single-msg-x-position'),
app.config.get('single-msg-y-position'))
self.window.show_all()
def on_single_message_window_destroy(self, widget):
self.instances.remove(self)
c = app.contacts.get_contact_with_highest_priority(self.account,
self.from_whom)
if not c:
# Groupchat is maybe already destroyed
return
if c.is_groupchat() and self.from_whom not in \
app.interface.minimized_controls[self.account] and self.action == \
'receive' and app.events.get_nb_roster_events(self.account,
self.from_whom, types=['chat', 'normal']) == 0:
app.interface.roster.remove_groupchat(self.from_whom, self.account)
def set_cursor_to_end(self):
end_iter = self.message_tv_buffer.get_end_iter()
self.message_tv_buffer.place_cursor(end_iter)
def save_pos(self):
# save the window size and position
x, y = self.window.get_position()
app.config.set('single-msg-x-position', x)
app.config.set('single-msg-y-position', y)
width, height = self.window.get_size()
app.config.set('single-msg-width', width)
app.config.set('single-msg-height', height)
def on_single_message_window_delete_event(self, window, ev):
self.save_pos()
def prepare_widgets_for(self, action):
if len(app.connections) > 1:
if action == 'send':
title = _('Single Message using account %s') % self.account
else:
title = _('Single Message in account %s') % self.account
else:
title = _('Single Message')
if action == 'send': # prepare UI for Sending
title = _('Send %s') % title
self.send_button.show()
self.send_and_close_button.show()
self.to_label.show()
self.to_entry.show()
self.reply_button.hide()
self.from_label.hide()
self.from_entry.hide()
self.conversation_scrolledwindow.hide()
self.message_scrolledwindow.show()
if self.message: # we come from a reply?
self.message_textview.grab_focus()
self.cancel_button.hide()
self.close_button.show()
self.message_tv_buffer.set_text(self.message)
GLib.idle_add(self.set_cursor_to_end)
else: # we write a new message (not from reply)
self.close_button.hide()
if self.to: # do we already have jid?
self.subject_entry.grab_focus()
elif action == 'receive': # prepare UI for Receiving
title = _('Received %s') % title
self.reply_button.show()
self.from_label.show()
self.from_entry.show()
self.send_button.hide()
self.send_and_close_button.hide()
self.to_label.hide()
self.to_entry.hide()
self.conversation_scrolledwindow.show()
self.message_scrolledwindow.hide()
if self.message:
self.conversation_textview.print_real_text(self.message)
fjid = self.from_whom
if self.resource:
fjid += '/' + self.resource # Full jid of sender (with resource)
self.from_entry.set_text(fjid)
self.from_entry.set_property('editable', False)
self.subject_entry.set_property('editable', False)
self.reply_button.grab_focus()
self.cancel_button.hide()
self.close_button.show()
elif action == 'form': # prepare UI for Receiving
title = _('Form %s') % title
self.send_button.show()
self.send_and_close_button.show()
self.to_label.show()
self.to_entry.show()
self.reply_button.hide()
self.from_label.hide()
self.from_entry.hide()
self.conversation_scrolledwindow.hide()
self.message_scrolledwindow.hide()
self.window.set_title(title)
def on_cancel_button_clicked(self, widget):
self.save_pos()
self.window.destroy()
def on_close_button_clicked(self, widget):
self.save_pos()
self.window.destroy()
def update_char_counter(self, widget):
characters_no = self.message_tv_buffer.get_char_count()
self.count_chars_label.set_text(str(characters_no))
def send_single_message(self):
if app.connections[self.account].connected <= 1:
# if offline or connecting
ErrorDialog(_('Connection not available'),
_('Please make sure you are connected with "%s".') % self.account)
return True
if isinstance(self.to, list):
sender_list = []
for i in self.to:
if i[0].resource:
sender_list.append(i[0].jid + '/' + i[0].resource)
else:
sender_list.append(i[0].jid)
else:
sender_list = [j.strip() for j in self.to_entry.get_text().split(
',')]
subject = self.subject_entry.get_text()
begin, end = self.message_tv_buffer.get_bounds()
message = self.message_tv_buffer.get_text(begin, end, True)
if self.form_widget:
form_node = self.form_widget.data_form
else:
form_node = None
recipient_list = []
for to_whom_jid in sender_list:
if to_whom_jid in self.completion_dict:
to_whom_jid = self.completion_dict[to_whom_jid].jid
try:
to_whom_jid = helpers.parse_jid(to_whom_jid)
except helpers.InvalidFormat:
ErrorDialog(_('Invalid JID'),
_('It is not possible to send a message to %s, this JID is not '
'valid.') % to_whom_jid)
return True
if '/announce/' in to_whom_jid:
app.connections[self.account].send_motd(to_whom_jid, subject,
message)
continue
recipient_list.append(to_whom_jid)
app.nec.push_outgoing_event(MessageOutgoingEvent(None,
account=self.account, jid=recipient_list, message=message,
type_='normal', subject=subject, form_node=form_node))
self.subject_entry.set_text('') # we sent ok, clear the subject
self.message_tv_buffer.set_text('') # we sent ok, clear the textview
def on_send_button_clicked(self, widget):
self.send_single_message()
def on_reply_button_clicked(self, widget):
# we create a new blank window to send and we preset RE: and to jid
self.subject = _('RE: %s') % self.subject
self.message = _('%s wrote:\n') % self.from_whom + self.message
# add > at the begining of each line
self.message = self.message.replace('\n', '\n> ') + '\n\n'
self.window.destroy()
SingleMessageWindow(self.account, to=self.from_whom, action='send',
from_whom=self.from_whom, subject=self.subject, message=self.message,
session=self.session)
def on_send_and_close_button_clicked(self, widget):
if self.send_single_message():
return
self.save_pos()
self.window.destroy()
def on_single_message_window_key_press_event(self, widget, event):
if event.keyval == Gdk.KEY_Escape: # ESCAPE
self.save_pos()
self.window.destroy()

343
gajim/gtk/start_chat.py Normal file
View file

@ -0,0 +1,343 @@
# 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/>.
import locale
from gi.repository import Gdk
from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import Pango
from gajim.common import app
from gajim.common import helpers
from gajim.common.const import AvatarSize
from gajim.gtk.util import get_iconset_name_for
from gajim.gtk.util import get_builder
class StartChatDialog(Gtk.ApplicationWindow):
def __init__(self):
Gtk.ApplicationWindow.__init__(self)
self.set_name('StartChatDialog')
self.set_application(app.app)
self.set_position(Gtk.WindowPosition.CENTER)
self.set_show_menubar(False)
self.set_title(_('Start new Conversation'))
self.set_default_size(-1, 400)
self.ready_to_destroy = False
self.builder = get_builder('start_chat_dialog.ui')
self.listbox = self.builder.get_object('listbox')
self.search_entry = self.builder.get_object('search_entry')
self.box = self.builder.get_object('box')
self.add(self.box)
self.new_contact_row_visible = False
self.new_contact_rows = {}
self.new_groupchat_rows = {}
self.accounts = app.connections.keys()
self.add_contacts()
self.add_groupchats()
self.search_entry.connect('search-changed',
self._on_search_changed)
self.search_entry.connect('next-match',
self._select_new_match, 'next')
self.search_entry.connect('previous-match',
self._select_new_match, 'prev')
self.search_entry.connect('stop-search',
lambda *args: self.search_entry.set_text(''))
self.listbox.set_filter_func(self._filter_func, None)
self.listbox.set_sort_func(self._sort_func, None)
self.listbox.connect('row-activated', self._on_row_activated)
self.connect('key-press-event', self._on_key_press)
self.connect('destroy', self._destroy)
self.select_first_row()
self.show_all()
def add_contacts(self):
show_account = len(self.accounts) > 1
for account in self.accounts:
self.new_contact_rows[account] = None
for jid in app.contacts.get_jid_list(account):
contact = app.contacts.get_contact_with_highest_priority(
account, jid)
if contact.is_groupchat():
continue
row = ContactRow(account, contact, jid,
contact.get_shown_name(), show_account)
self.listbox.add(row)
def add_groupchats(self):
show_account = len(self.accounts) > 1
for account in self.accounts:
self.new_groupchat_rows[account] = None
con = app.connections[account]
bookmarks = con.get_module('Bookmarks').bookmarks
groupchats = {}
for jid, bookmark in bookmarks.items():
groupchats[jid] = bookmark['name']
for jid in app.contacts.get_gc_list(account):
if jid in groupchats:
continue
groupchats[jid] = None
for jid in groupchats:
name = groupchats[jid]
if not name:
name = app.get_nick_from_jid(jid)
row = ContactRow(account, None, jid, name,
show_account, True)
self.listbox.add(row)
def _on_row_activated(self, listbox, row):
row = row.get_child()
self._start_new_chat(row)
def _on_key_press(self, widget, event):
if event.keyval in (Gdk.KEY_Down, Gdk.KEY_Tab):
self.search_entry.emit('next-match')
return True
elif (event.state == Gdk.ModifierType.SHIFT_MASK and
event.keyval == Gdk.KEY_ISO_Left_Tab):
self.search_entry.emit('previous-match')
return True
elif event.keyval == Gdk.KEY_Up:
self.search_entry.emit('previous-match')
return True
elif event.keyval == Gdk.KEY_Escape:
if self.search_entry.get_text() != '':
self.search_entry.emit('stop-search')
else:
self.destroy()
return True
elif event.keyval == Gdk.KEY_Return:
row = self.listbox.get_selected_row()
if row is not None:
row.emit('activate')
return True
else:
self.search_entry.grab_focus_without_selecting()
def _start_new_chat(self, row):
if row.new:
if not app.account_is_connected(row.account):
app.interface.raise_dialog('start-chat-not-connected')
return
try:
helpers.parse_jid(row.jid)
except helpers.InvalidFormat as e:
app.interface.raise_dialog('invalid-jid-with-error', str(e))
return
if row.groupchat:
app.interface.join_gc_minimal(row.account, row.jid)
else:
app.interface.new_chat_from_jid(row.account, row.jid)
self.ready_to_destroy = True
def _on_search_changed(self, entry):
search_text = entry.get_text()
if '@' in search_text:
self._add_new_jid_row()
self._update_new_jid_rows(search_text)
else:
self._remove_new_jid_row()
self.listbox.invalidate_filter()
def _add_new_jid_row(self):
if self.new_contact_row_visible:
return
for account in self.new_contact_rows:
show_account = len(self.accounts) > 1
row = ContactRow(account, None, '', None, show_account)
self.new_contact_rows[account] = row
group_row = ContactRow(account, None, '', None, show_account, True)
self.new_groupchat_rows[account] = group_row
self.listbox.add(row)
self.listbox.add(group_row)
row.get_parent().show_all()
self.new_contact_row_visible = True
def _remove_new_jid_row(self):
if not self.new_contact_row_visible:
return
for account in self.new_contact_rows:
self.listbox.remove(self.new_contact_rows[account].get_parent())
self.listbox.remove(self.new_groupchat_rows[account].get_parent())
self.new_contact_row_visible = False
def _update_new_jid_rows(self, search_text):
for account in self.new_contact_rows:
self.new_contact_rows[account].update_jid(search_text)
self.new_groupchat_rows[account].update_jid(search_text)
def _select_new_match(self, entry, direction):
selected_row = self.listbox.get_selected_row()
index = selected_row.get_index()
if direction == 'next':
index += 1
else:
index -= 1
while True:
new_selected_row = self.listbox.get_row_at_index(index)
if new_selected_row is None:
return
if new_selected_row.get_child_visible():
self.listbox.select_row(new_selected_row)
new_selected_row.grab_focus()
return
if direction == 'next':
index += 1
else:
index -= 1
def select_first_row(self):
first_row = self.listbox.get_row_at_y(0)
self.listbox.select_row(first_row)
def _filter_func(self, row, user_data):
search_text = self.search_entry.get_text().lower()
search_text_list = search_text.split()
row_text = row.get_child().get_search_text().lower()
for text in search_text_list:
if text not in row_text:
GLib.timeout_add(50, self.select_first_row)
return
GLib.timeout_add(50, self.select_first_row)
return True
@staticmethod
def _sort_func(row1, row2, user_data):
name1 = row1.get_child().get_search_text()
name2 = row2.get_child().get_search_text()
account1 = row1.get_child().account
account2 = row2.get_child().account
is_groupchat1 = row1.get_child().groupchat
is_groupchat2 = row2.get_child().groupchat
new1 = row1.get_child().new
new2 = row2.get_child().new
result = locale.strcoll(account1.lower(), account2.lower())
if result != 0:
return result
if new1 != new2:
return 1 if new1 else -1
if is_groupchat1 != is_groupchat2:
return 1 if is_groupchat1 else -1
return locale.strcoll(name1.lower(), name2.lower())
@staticmethod
def _destroy(*args):
del app.interface.instances['start_chat']
class ContactRow(Gtk.Grid):
def __init__(self, account, contact, jid, name, show_account,
groupchat=False):
Gtk.Grid.__init__(self)
self.set_column_spacing(12)
self.set_size_request(260, -1)
self.account = account
self.account_label = app.config.get_per(
'accounts', account, 'account_label') or account
self.show_account = show_account
self.jid = jid
self.contact = contact
self.name = name
self.groupchat = groupchat
self.new = jid == ''
if self.groupchat:
muc_icon = get_iconset_name_for(
'muc-inactive' if self.new else 'muc-active')
image = Gtk.Image.new_from_icon_name(muc_icon, Gtk.IconSize.DND)
else:
scale = self.get_scale_factor()
avatar = app.contacts.get_avatar(
account, jid, AvatarSize.ROSTER, scale)
if avatar is None:
image = Gtk.Image.new_from_icon_name(
'avatar-default', Gtk.IconSize.DND)
else:
image = Gtk.Image.new_from_surface(avatar)
image.set_size_request(AvatarSize.ROSTER, AvatarSize.ROSTER)
self.add(image)
middle_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
middle_box.set_hexpand(True)
if self.name is None:
if self.groupchat:
self.name = _('New Groupchat')
else:
self.name = _('New Contact')
self.name_label = Gtk.Label(self.name)
self.name_label.set_ellipsize(Pango.EllipsizeMode.END)
self.name_label.set_xalign(0)
self.name_label.set_width_chars(25)
self.name_label.set_halign(Gtk.Align.START)
self.name_label.get_style_context().add_class('bold16')
status = contact.show if contact else 'offline'
css_class = helpers.get_css_show_color(status)
if css_class is not None:
self.name_label.get_style_context().add_class(css_class)
middle_box.add(self.name_label)
self.jid_label = Gtk.Label(jid)
self.jid_label.set_ellipsize(Pango.EllipsizeMode.END)
self.jid_label.set_xalign(0)
self.jid_label.set_width_chars(25)
self.jid_label.set_halign(Gtk.Align.START)
middle_box.add(self.jid_label)
self.add(middle_box)
if show_account:
account_label = Gtk.Label(self.account_label)
account_label.set_halign(Gtk.Align.START)
account_label.set_valign(Gtk.Align.START)
right_box = Gtk.Box()
right_box.set_vexpand(True)
right_box.add(account_label)
self.add(right_box)
self.show_all()
def update_jid(self, jid):
self.jid = jid
self.jid_label.set_text(jid)
def get_search_text(self):
if self.contact is None and not self.groupchat:
return self.jid
if self.show_account:
return '%s %s %s' % (self.name, self.jid, self.account_label)
return '%s %s' % (self.name, self.jid)

View file

@ -15,11 +15,13 @@
import os import os
import sys import sys
import logging import logging
from gi.repository import Gtk
from gi.repository import GLib
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from gi.repository import Gdk
from gi.repository import Gtk
from gi.repository import GLib
from gajim.common import app
from gajim.common import i18n from gajim.common import i18n
from gajim.common import configpaths from gajim.common import configpaths
@ -29,7 +31,7 @@ _icon_theme.append_search_path(configpaths.get('ICONS'))
log = logging.getLogger('gajim.gtk.util') log = logging.getLogger('gajim.gtk.util')
def load_icon(icon_name, widget, size=16, def load_icon(icon_name, widget, size=16, pixbuf=False,
flags=Gtk.IconLookupFlags.FORCE_SIZE): flags=Gtk.IconLookupFlags.FORCE_SIZE):
scale = widget.get_scale_factor() scale = widget.get_scale_factor()
@ -40,6 +42,8 @@ def load_icon(icon_name, widget, size=16,
try: try:
iconinfo = _icon_theme.lookup_icon_for_scale( iconinfo = _icon_theme.lookup_icon_for_scale(
icon_name, size, scale, flags) icon_name, size, scale, flags)
if pixbuf:
return iconinfo.load_icon()
return iconinfo.load_surface(None) return iconinfo.load_surface(None)
except GLib.GError as e: except GLib.GError as e:
log.error('Unable to load icon %s: %s', icon_name, str(e)) log.error('Unable to load icon %s: %s', icon_name, str(e))
@ -75,3 +79,127 @@ def _translate(gui_file, widget):
builder.add_objects_from_file(gui_file, [widget]) builder.add_objects_from_file(gui_file, [widget])
return builder return builder
return Gtk.Builder.new_from_file(gui_file) return Gtk.Builder.new_from_file(gui_file)
def get_iconset_name_for(name):
if name == 'not in roster':
name = 'notinroster'
iconset = app.config.get('iconset')
if not iconset:
iconset = app.config.DEFAULT_ICONSET
return '%s-%s' % (iconset, name)
def get_total_screen_geometry():
screen = Gdk.Screen.get_default()
window = Gdk.Screen.get_root_window(screen)
w, h = window.get_width(), window.get_height()
log.debug('Get screen geometry: %s %s', w, h)
return w, h
def resize_window(window, w, h):
"""
Resize window, but also checks if huge window or negative values
"""
screen_w, screen_h = get_total_screen_geometry()
if not w or not h:
return
if w > screen_w:
w = screen_w
if h > screen_h:
h = screen_h
window.resize(abs(w), abs(h))
def move_window(window, x, y):
"""
Move the window, but also check if out of screen
"""
screen_w, screen_h = get_total_screen_geometry()
if x < 0:
x = 0
if y < 0:
y = 0
w, h = window.get_size()
if x + w > screen_w:
x = screen_w - w
if y + h > screen_h:
y = screen_h - h
window.move(x, y)
def get_completion_liststore(entry):
"""
Create a completion model for entry widget completion list consists of
(Pixbuf, Text) rows
"""
completion = Gtk.EntryCompletion()
liststore = Gtk.ListStore(str, str)
render_pixbuf = Gtk.CellRendererPixbuf()
completion.pack_start(render_pixbuf, False)
completion.add_attribute(render_pixbuf, 'icon_name', 0)
render_text = Gtk.CellRendererText()
completion.pack_start(render_text, True)
completion.add_attribute(render_text, 'text', 1)
completion.set_property('text_column', 1)
completion.set_model(liststore)
entry.set_completion(completion)
return liststore
def get_cursor(attr):
display = Gdk.Display.get_default()
cursor = getattr(Gdk.CursorType, attr)
return Gdk.Cursor.new_for_display(display, cursor)
def scroll_to_end(widget):
"""Scrolls to the end of a GtkScrolledWindow.
Args:
widget (GtkScrolledWindow)
Returns:
bool: The return value is False so it can be used with GLib.idle_add.
"""
adj_v = widget.get_vadjustment()
if adj_v is None:
# This can happen when the Widget is already destroyed when called
# from GLib.idle_add
return False
max_scroll_pos = adj_v.get_upper() - adj_v.get_page_size()
adj_v.set_value(max_scroll_pos)
adj_h = widget.get_hadjustment()
adj_h.set_value(0)
return False
def at_the_end(widget):
"""Determines if a Scrollbar in a GtkScrolledWindow is at the end.
Args:
widget (GtkScrolledWindow)
Returns:
bool: The return value is True if at the end, False if not.
"""
adj_v = widget.get_vadjustment()
max_scroll_pos = adj_v.get_upper() - adj_v.get_page_size()
at_the_end = (adj_v.get_value() == max_scroll_pos)
return at_the_end
def get_image_button(icon_name, tooltip, toggle=False):
if toggle:
button = Gtk.ToggleButton()
image = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.MENU)
button.set_image(image)
else:
button = Gtk.Button.new_from_icon_name(
icon_name, Gtk.IconSize.MENU)
button.set_tooltip_text(tooltip)
return button

273
gajim/gtk/xml_console.py Normal file
View file

@ -0,0 +1,273 @@
# 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/>.
import time
import nbxmpp
from gi.repository import Gdk
from gi.repository import Gtk
from gi.repository import GLib
from gajim.common import app
from gajim.common import ged
from gajim.common.const import Option, OptionKind, OptionType
from gajim.gtk import ErrorDialog
from gajim.gtk import util
from gajim.gtk.util import get_builder
from gajim.gtk.util import get_image_button
from gajim.options_dialog import OptionsDialog
UNDECLARED = 'http://www.gajim.org/xmlns/undeclared'
class XMLConsoleWindow(Gtk.Window):
def __init__(self, account):
Gtk.Window.__init__(self)
self.account = account
self.enabled = True
self.presence = True
self.message = True
self.iq = True
self.stream = True
self.incoming = True
self.outgoing = True
self.filter_dialog = None
glade_objects = ['textview', 'input', 'scrolled_input', 'headerbar',
'scrolled', 'actionbar', 'paned', 'box', 'menubutton']
self.builder = get_builder('xml_console_window.ui')
for obj in glade_objects:
setattr(self, obj, self.builder.get_object(obj))
self.set_titlebar(self.headerbar)
jid = app.get_jid_from_account(account)
self.headerbar.set_subtitle(jid)
self.set_default_size(600, 600)
self.add(self.box)
self.paned.set_position(self.paned.get_property('max-position'))
button = get_image_button(
'edit-clear-all-symbolic', _('Clear'))
button.connect('clicked', self.on_clear)
self.actionbar.pack_start(button)
button = get_image_button(
'applications-system-symbolic', _('Filter'))
button.connect('clicked', self.on_filter_options)
self.actionbar.pack_start(button)
button = get_image_button(
'document-edit-symbolic', _('XML Input'), toggle=True)
button.connect('toggled', self.on_input)
self.actionbar.pack_start(button)
button = get_image_button('emblem-ok-symbolic', _('Send'))
button.connect('clicked', self.on_send)
self.actionbar.pack_end(button)
self.actionbar.pack_start(self.menubutton)
self.create_tags()
self.show_all()
self.scrolled_input.hide()
self.menubutton.hide()
self.connect("destroy", self.on_destroy)
self.connect('key_press_event', self.on_key_press_event)
self.builder.connect_signals(self)
app.ged.register_event_handler(
'stanza-received', ged.GUI1, self._nec_stanza_received)
app.ged.register_event_handler(
'stanza-sent', ged.GUI1, self._nec_stanza_sent)
def create_tags(self):
buffer_ = self.textview.get_buffer()
in_color = app.config.get('inmsgcolor')
out_color = app.config.get('outmsgcolor')
tags = ['presence', 'message', 'stream', 'iq']
tag = buffer_.create_tag('incoming')
tag.set_property('foreground', in_color)
tag = buffer_.create_tag('outgoing')
tag.set_property('foreground', out_color)
for tag_name in tags:
buffer_.create_tag(tag_name)
def on_key_press_event(self, widget, event):
if event.keyval == Gdk.KEY_Escape:
self.destroy()
def on_row_activated(self, listbox, row):
text = row.get_child().get_text()
input_text = None
if text == 'Presence':
input_text = (
'<presence>\n'
'<show></show>\n'
'<status></status>\n'
'<priority></priority>\n'
'</presence>')
elif text == 'Message':
input_text = (
'<message to="" type="">\n'
'<body></body>\n'
'</message>')
elif text == 'Iq':
input_text = (
'<iq to="" type="">\n'
'<query xmlns=""></query>\n'
'</iq>')
if input_text is not None:
buffer_ = self.input.get_buffer()
buffer_.set_text(input_text)
self.input.grab_focus()
def on_send(self, *args):
if app.connections[self.account].connected <= 1:
# if offline or connecting
ErrorDialog(
_('Connection not available'),
_('Please make sure you are connected with "%s".') %
self.account)
return
buffer_ = self.input.get_buffer()
begin_iter, end_iter = buffer_.get_bounds()
stanza = buffer_.get_text(begin_iter, end_iter, True)
if stanza:
try:
node = nbxmpp.Protocol(node=stanza)
if node.getNamespace() == UNDECLARED:
node.setNamespace(nbxmpp.NS_CLIENT)
except Exception as error:
ErrorDialog(_('Invalid Node'), str(error))
return
app.connections[self.account].connection.send(node)
buffer_.set_text('')
def on_input(self, button, *args):
if button.get_active():
self.paned.get_child2().show()
self.menubutton.show()
self.input.grab_focus()
else:
self.paned.get_child2().hide()
self.menubutton.hide()
def on_filter_options(self, *args):
if self.filter_dialog:
self.filter_dialog.present()
return
options = [
Option(OptionKind.SWITCH, 'Presence',
OptionType.VALUE, self.presence,
callback=self.on_option, data='presence'),
Option(OptionKind.SWITCH, 'Message',
OptionType.VALUE, self.message,
callback=self.on_option, data='message'),
Option(OptionKind.SWITCH, 'Iq', OptionType.VALUE, self.iq,
callback=self.on_option, data='iq'),
Option(OptionKind.SWITCH, 'Stream\nManagement',
OptionType.VALUE, self.stream,
callback=self.on_option, data='stream'),
Option(OptionKind.SWITCH, 'In', OptionType.VALUE, self.incoming,
callback=self.on_option, data='incoming'),
Option(OptionKind.SWITCH, 'Out', OptionType.VALUE, self.outgoing,
callback=self.on_option, data='outgoing'),
]
self.filter_dialog = OptionsDialog(self, 'Filter',
Gtk.DialogFlags.DESTROY_WITH_PARENT,
options, self.account)
self.filter_dialog.connect('destroy', self.on_filter_destroyed)
def on_filter_destroyed(self, win):
self.filter_dialog = None
def on_clear(self, *args):
self.textview.get_buffer().set_text('')
def on_destroy(self, *args):
del app.interface.instances[self.account]['xml_console']
app.ged.remove_event_handler(
'stanza-received', ged.GUI1, self._nec_stanza_received)
app.ged.remove_event_handler(
'stanza-sent', ged.GUI1, self._nec_stanza_sent)
def on_enable(self, switch, param):
self.enabled = switch.get_active()
def on_option(self, value, data):
setattr(self, data, value)
value = not value
table = self.textview.get_buffer().get_tag_table()
tag = table.lookup(data)
if data in ('incoming', 'outgoing'):
if value:
tag.set_priority(table.get_size() - 1)
else:
tag.set_priority(0)
tag.set_property('invisible', value)
def _nec_stanza_received(self, obj):
if obj.conn.name != self.account:
return
self.print_stanza(obj.stanza_str, 'incoming')
def _nec_stanza_sent(self, obj):
if obj.conn.name != self.account:
return
self.print_stanza(obj.stanza_str, 'outgoing')
def print_stanza(self, stanza, kind):
# kind must be 'incoming' or 'outgoing'
if not self.enabled:
return
if not stanza:
return
at_the_end = util.at_the_end(self.scrolled)
buffer_ = self.textview.get_buffer()
end_iter = buffer_.get_end_iter()
type_ = kind
if stanza.startswith('<presence'):
type_ = 'presence'
elif stanza.startswith('<message'):
type_ = 'message'
elif stanza.startswith('<iq'):
type_ = 'iq'
elif stanza.startswith('<r') or stanza.startswith('<a'):
type_ = 'stream'
stanza = '<!-- {kind} {time} -->\n{stanza}\n\n'.format(
kind=kind.capitalize(),
time=time.strftime('%c'),
stanza=stanza.replace('><', '>\n<'))
buffer_.insert_with_tags_by_name(end_iter, stanza, type_, kind)
if at_the_end:
GLib.idle_add(util.scroll_to_end, self.scrolled)

View file

@ -479,7 +479,9 @@ def scale_pixbuf_from_data(data, size):
return scale_pixbuf(pixbuf, size) return scale_pixbuf(pixbuf, size)
def on_avatar_save_as_menuitem_activate(widget, avatar, default_name=''): def on_avatar_save_as_menuitem_activate(widget, avatar, default_name=''):
from gajim import dialogs from gajim.gtk import ErrorDialog
from gajim.gtk import ConfirmationDialog
from gajim.gtk import FTOverwriteConfirmationDialog
def on_continue(response, file_path): def on_continue(response, file_path):
if response < 0: if response < 0:
return return
@ -509,7 +511,7 @@ def on_avatar_save_as_menuitem_activate(widget, avatar, default_name=''):
new_file_path = '.'.join(file_path.split('.')[:-1]) + '.png' new_file_path = '.'.join(file_path.split('.')[:-1]) + '.png'
def on_ok(file_path, pixbuf): def on_ok(file_path, pixbuf):
pixbuf.savev(file_path, 'png', [], []) pixbuf.savev(file_path, 'png', [], [])
dialogs.ConfirmationDialog(_('Extension not supported'), ConfirmationDialog(_('Extension not supported'),
_('Image cannot be saved in %(type)s format. Save as ' _('Image cannot be saved in %(type)s format. Save as '
'%(new_filename)s?') % {'type': image_format, '%(new_filename)s?') % {'type': image_format,
'new_filename': new_file_path}, 'new_filename': new_file_path},
@ -520,18 +522,18 @@ def on_avatar_save_as_menuitem_activate(widget, avatar, default_name=''):
# check if we have write permissions # check if we have write permissions
if not os.access(file_path, os.W_OK): if not os.access(file_path, os.W_OK):
file_name = os.path.basename(file_path) file_name = os.path.basename(file_path)
dialogs.ErrorDialog(_('Cannot overwrite existing file "%s"') % \ ErrorDialog(_('Cannot overwrite existing file "%s"') % \
file_name, _('A file with this name already exists and you ' file_name, _('A file with this name already exists and you '
'do not have permission to overwrite it.')) 'do not have permission to overwrite it.'))
return return
dialog2 = dialogs.FTOverwriteConfirmationDialog( dialog2 = FTOverwriteConfirmationDialog(
_('This file already exists'), _('What do you want to do?'), _('This file already exists'), _('What do you want to do?'),
propose_resume=False, on_response=(on_continue, file_path)) propose_resume=False, on_response=(on_continue, file_path))
dialog2.set_destroy_with_parent(True) dialog2.set_destroy_with_parent(True)
else: else:
dirname = os.path.dirname(file_path) dirname = os.path.dirname(file_path)
if not os.access(dirname, os.W_OK): if not os.access(dirname, os.W_OK):
dialogs.ErrorDialog(_('Directory "%s" is not writable') % \ ErrorDialog(_('Directory "%s" is not writable') % \
dirname, _('You do not have permission to create files in ' dirname, _('You do not have permission to create files in '
'this directory.')) 'this directory.'))
return return

View file

@ -69,6 +69,7 @@ from gajim import notify
from gajim import message_control from gajim import message_control
from gajim.dialog_messages import get_dialog from gajim.dialog_messages import get_dialog
from gajim.dialogs import ProgressWindow from gajim.dialogs import ProgressWindow
from gajim.filechoosers import FileChooserDialog from gajim.filechoosers import FileChooserDialog
from gajim.chat_control_base import ChatControlBase from gajim.chat_control_base import ChatControlBase
@ -111,6 +112,18 @@ from threading import Thread
from gajim.common import ged from gajim.common import ged
from gajim.common.caps_cache import muc_caps_cache from gajim.common.caps_cache import muc_caps_cache
from gajim.gtk import JoinGroupchatWindow
from gajim.gtk import ErrorDialog
from gajim.gtk import WarningDialog
from gajim.gtk import InformationDialog
from gajim.gtk import InputDialog
from gajim.gtk import YesNoDialog
from gajim.gtk import InputTextDialog
from gajim.gtk import PlainConnectionDialog
from gajim.gtk import SSLErrorDialog
from gajim.gtk import ConfirmationDialogDoubleCheck
from gajim.gtk import ChangeNickDialog
from gajim.common import configpaths from gajim.common import configpaths
from gajim.common import optparser from gajim.common import optparser
@ -129,7 +142,7 @@ class Interface:
#('DB_ERROR', account, error) #('DB_ERROR', account, error)
if self.db_error_dialog: if self.db_error_dialog:
return return
self.db_error_dialog = dialogs.ErrorDialog(_('Database Error'), error) self.db_error_dialog = ErrorDialog(_('Database Error'), error)
def destroyed(win): def destroyed(win):
self.db_error_dialog = None self.db_error_dialog = None
self.db_error_dialog.connect('destroy', destroyed) self.db_error_dialog.connect('destroy', destroyed)
@ -144,11 +157,11 @@ class Interface:
return return
if obj.level == 'error': if obj.level == 'error':
cls = dialogs.ErrorDialog cls = ErrorDialog
elif obj.level == 'warn': elif obj.level == 'warn':
cls = dialogs.WarningDialog cls = WarningDialog
elif obj.level == 'info': elif obj.level == 'info':
cls = dialogs.InformationDialog cls = InformationDialog
else: else:
return return
@ -169,7 +182,7 @@ class Interface:
self.instances['change_nick_dialog'].add_room(account, room_jid, self.instances['change_nick_dialog'].add_room(account, room_jid,
prompt) prompt)
else: else:
self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog( self.instances['change_nick_dialog'] = ChangeNickDialog(
account, room_jid, title, prompt, transient_for=parent_win) account, room_jid, title, prompt, transient_for=parent_win)
@staticmethod @staticmethod
@ -188,7 +201,7 @@ class Interface:
sec_msg = _('Do you accept this request on account %s?') % account sec_msg = _('Do you accept this request on account %s?') % account
if obj.msg: if obj.msg:
sec_msg = obj.msg + '\n' + sec_msg sec_msg = obj.msg + '\n' + sec_msg
dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for ' dialog = YesNoDialog(_('HTTP (%(method)s) Authorization for '
'%(url)s (ID: %(id)s)') % {'method': obj.method, 'url': obj.url, '%(url)s (ID: %(id)s)') % {'method': obj.method, 'url': obj.url,
'id': obj.iq_id}, sec_msg, on_response_yes=(on_yes, obj), 'id': obj.iq_id}, sec_msg, on_response_yes=(on_yes, obj),
on_response_no=(response, obj, 'no')) on_response_no=(response, obj, 'no'))
@ -285,14 +298,14 @@ class Interface:
def on_close(dummy): def on_close(dummy):
gc_control.error_dialog.destroy() gc_control.error_dialog.destroy()
gc_control.error_dialog = None gc_control.error_dialog = None
gc_control.error_dialog = dialogs.ErrorDialog(pritext, sectext, gc_control.error_dialog = ErrorDialog(pritext, sectext,
on_response_ok=on_close, on_response_cancel=on_close) on_response_ok=on_close, on_response_cancel=on_close)
gc_control.error_dialog.set_modal(False) gc_control.error_dialog.set_modal(False)
if gc_control.parent_win: if gc_control.parent_win:
gc_control.error_dialog.set_transient_for( gc_control.error_dialog.set_transient_for(
gc_control.parent_win.window) gc_control.parent_win.window)
else: else:
d = dialogs.ErrorDialog(pritext, sectext) d = ErrorDialog(pritext, sectext)
if gc_control and gc_control.parent_win: if gc_control and gc_control.parent_win:
d.set_transient_for(gc_control.parent_win.window) d.set_transient_for(gc_control.parent_win.window)
d.set_modal(False) d.set_modal(False)
@ -318,7 +331,7 @@ class Interface:
if gc_control.error_dialog: if gc_control.error_dialog:
gc_control.error_dialog.destroy() gc_control.error_dialog.destroy()
gc_control.error_dialog = dialogs.InputDialog(_('Password Required'), gc_control.error_dialog = InputDialog(_('Password Required'),
_('A Password is required to join the room %s. Please type it.') % \ _('A Password is required to join the room %s. Please type it.') % \
room_jid, is_modal=False, ok_handler=on_ok, room_jid, is_modal=False, ok_handler=on_ok,
cancel_handler=on_cancel) cancel_handler=on_cancel)
@ -530,7 +543,7 @@ class Interface:
status='online', ask='to', resource=obj.resource, keyID=keyID) status='online', ask='to', resource=obj.resource, keyID=keyID)
app.contacts.add_contact(account, contact1) app.contacts.add_contact(account, contact1)
self.roster.add_contact(obj.jid, account) self.roster.add_contact(obj.jid, account)
dialogs.InformationDialog(_('Authorization accepted'), InformationDialog(_('Authorization accepted'),
_('The contact "%s" has authorized you to see their status.') _('The contact "%s" has authorized you to see their status.')
% obj.jid) % obj.jid)
@ -538,7 +551,7 @@ class Interface:
def on_yes(is_checked, list_): def on_yes(is_checked, list_):
self.roster.on_req_usub(None, list_) self.roster.on_req_usub(None, list_)
list_ = [(contact, account)] list_ = [(contact, account)]
dialogs.YesNoDialog( YesNoDialog(
_('Contact "%s" removed subscription from you') % contact.jid, _('Contact "%s" removed subscription from you') % contact.jid,
_('You will always see them as offline.\nDo you want to ' _('You will always see them as offline.\nDo you want to '
'remove them from your contact list?'), 'remove them from your contact list?'),
@ -575,7 +588,7 @@ class Interface:
config.ServiceRegistrationWindow(obj.agent, obj.config, config.ServiceRegistrationWindow(obj.agent, obj.config,
obj.conn.name, obj.is_form) obj.conn.name, obj.is_form)
else: else:
dialogs.ErrorDialog(_('Contact with "%s" cannot be established') % \ ErrorDialog(_('Contact with "%s" cannot be established') % \
obj.agent, _('Check your connection or try again later.')) obj.agent, _('Check your connection or try again later.'))
def handle_event_gc_config(self, obj): def handle_event_gc_config(self, obj):
@ -673,7 +686,7 @@ class Interface:
'\n') '\n')
sectext += _('You are currently connected without your OpenPGP ' sectext += _('You are currently connected without your OpenPGP '
'key.') 'key.')
dialogs.WarningDialog(_('Wrong passphrase'), sectext) WarningDialog(_('Wrong passphrase'), sectext)
else: else:
account = obj.conn.name account = obj.conn.name
app.notification.popup( app.notification.popup(
@ -716,7 +729,7 @@ class Interface:
def on_no(): def on_no():
obj.callback(False) obj.callback(False)
dialogs.YesNoDialog(_('Untrusted OpenPGP key'), _('The OpenPGP key ' YesNoDialog(_('Untrusted OpenPGP key'), _('The OpenPGP key '
'used to encrypt this chat is not trusted. Do you really want to ' 'used to encrypt this chat is not trusted. Do you really want to '
'encrypt this message?'), checktext=_('_Do not ask me again'), 'encrypt this message?'), checktext=_('_Do not ask me again'),
on_response_yes=on_yes, on_response_no=on_no) on_response_yes=on_yes, on_response_no=on_no)
@ -764,7 +777,7 @@ class Interface:
instruction = _('Please copy / paste the refresh token from the website' instruction = _('Please copy / paste the refresh token from the website'
' that has just been opened.') ' that has just been opened.')
self.pass_dialog[account] = dialogs.InputTextDialog( self.pass_dialog[account] = InputTextDialog(
_('Oauth2 Credentials'), instruction, is_modal=False, _('Oauth2 Credentials'), instruction, is_modal=False,
ok_handler=on_ok, cancel_handler=on_cancel) ok_handler=on_ok, cancel_handler=on_cancel)
@ -917,7 +930,7 @@ class Interface:
@staticmethod @staticmethod
def handle_event_file_error(title, message): def handle_event_file_error(title, message):
dialogs.ErrorDialog(title, message) ErrorDialog(title, message)
def handle_event_file_progress(self, account, file_props): def handle_event_file_progress(self, account, file_props):
if time.time() - self.last_ftwindow_update > 0.5: if time.time() - self.last_ftwindow_update > 0.5:
@ -1185,7 +1198,7 @@ class Interface:
def on_cancel(): def on_cancel():
obj.conn.change_status('offline', '') obj.conn.change_status('offline', '')
dlg = dialogs.InputDialog(_('Username Conflict'), dlg = InputDialog(_('Username Conflict'),
_('Please type a new username for your local account'), _('Please type a new username for your local account'),
input_str=obj.alt_name, is_modal=True, ok_handler=on_ok, input_str=obj.alt_name, is_modal=True, ok_handler=on_ok,
cancel_handler=on_cancel, transient_for=self.roster.window) cancel_handler=on_cancel, transient_for=self.roster.window)
@ -1337,7 +1350,7 @@ class Interface:
with open(my_ca_certs, encoding='utf-8') as f: with open(my_ca_certs, encoding='utf-8') as f:
certs = f.read() certs = f.read()
if pem in certs: if pem in certs:
dialogs.ErrorDialog(_('Certificate Already in File'), ErrorDialog(_('Certificate Already in File'),
_('This certificate is already in file %s, so it\'s ' _('This certificate is already in file %s, so it\'s '
'not added again.') % my_ca_certs) 'not added again.') % my_ca_certs)
else: else:
@ -1384,7 +1397,7 @@ class Interface:
if 'ssl_error' in self.instances[account]['online_dialog']: if 'ssl_error' in self.instances[account]['online_dialog']:
self.instances[account]['online_dialog']['ssl_error'].destroy() self.instances[account]['online_dialog']['ssl_error'].destroy()
self.instances[account]['online_dialog']['ssl_error'] = \ self.instances[account]['online_dialog']['ssl_error'] = \
dialogs.SSLErrorDialog(obj.conn.name, obj.cert, pritext, SSLErrorDialog(obj.conn.name, obj.cert, pritext,
sectext, checktext1, checktext2, on_response_ok=on_ok, sectext, checktext1, checktext2, on_response_ok=on_ok,
on_response_cancel=on_cancel) on_response_cancel=on_cancel)
self.instances[account]['online_dialog']['ssl_error'].set_title( self.instances[account]['online_dialog']['ssl_error'].set_title(
@ -1393,7 +1406,7 @@ class Interface:
def handle_event_non_anonymous_server(self, obj): def handle_event_non_anonymous_server(self, obj):
account = obj.conn.name account = obj.conn.name
server = app.config.get_per('accounts', account, 'hostname') server = app.config.get_per('accounts', account, 'hostname')
dialogs.ErrorDialog(_('Non Anonymous Server'), sectext='Server "%s"' ErrorDialog(_('Non Anonymous Server'), sectext='Server "%s"'
'does not support anonymous connection' % server, 'does not support anonymous connection' % server,
transient_for=self.roster.window) transient_for=self.roster.window)
@ -1426,7 +1439,7 @@ class Interface:
self.instances[obj.conn.name]['online_dialog']['plain_connection'].\ self.instances[obj.conn.name]['online_dialog']['plain_connection'].\
destroy() destroy()
self.instances[obj.conn.name]['online_dialog']['plain_connection'] = \ self.instances[obj.conn.name]['online_dialog']['plain_connection'] = \
dialogs.PlainConnectionDialog(obj.conn.name, on_ok, on_cancel) PlainConnectionDialog(obj.conn.name, on_ok, on_cancel)
def handle_event_insecure_ssl_connection(self, obj): def handle_event_insecure_ssl_connection(self, obj):
# ('INSECURE_SSL_CONNECTION', account, (connection, connection_type)) # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type))
@ -1464,7 +1477,7 @@ class Interface:
self.instances[obj.conn.name]['online_dialog']['insecure_ssl'].\ self.instances[obj.conn.name]['online_dialog']['insecure_ssl'].\
destroy() destroy()
self.instances[obj.conn.name]['online_dialog']['insecure_ssl'] = \ self.instances[obj.conn.name]['online_dialog']['insecure_ssl'] = \
dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1, ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel, checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
is_modal=False) is_modal=False)
@ -1506,7 +1519,7 @@ class Interface:
self.instances[obj.conn.name]['online_dialog']\ self.instances[obj.conn.name]['online_dialog']\
['insecure_password'].destroy() ['insecure_password'].destroy()
self.instances[obj.conn.name]['online_dialog']['insecure_password'] = \ self.instances[obj.conn.name]['online_dialog']['insecure_password'] = \
dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1, ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel, checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
is_modal=False) is_modal=False)
@ -1749,7 +1762,7 @@ class Interface:
try: try:
room_jid = helpers.parse_jid(room_jid) room_jid = helpers.parse_jid(room_jid)
except helpers.InvalidFormat: except helpers.InvalidFormat:
dialogs.ErrorDialog('Invalid JID', ErrorDialog('Invalid JID',
transient_for=app.app.get_active_window()) transient_for=app.app.get_active_window())
return return
@ -1757,7 +1770,7 @@ class Interface:
if account is not None and account not in connected_accounts: if account is not None and account not in connected_accounts:
connected_accounts = None connected_accounts = None
if not connected_accounts: if not connected_accounts:
dialogs.ErrorDialog( ErrorDialog(
_('You are not connected to the server'), _('You are not connected to the server'),
_('You can not join a group chat unless you are connected.'), _('You can not join a group chat unless you are connected.'),
transient_for=app.app.get_active_window()) transient_for=app.app.get_active_window())
@ -1765,10 +1778,10 @@ class Interface:
def _on_discover_result(): def _on_discover_result():
if not muc_caps_cache.is_cached(room_jid): if not muc_caps_cache.is_cached(room_jid):
dialogs.ErrorDialog(_('JID is not a Groupchat'), ErrorDialog(_('JID is not a Groupchat'),
transient_for=app.app.get_active_window()) transient_for=app.app.get_active_window())
return return
dialogs.JoinGroupchatWindow(account, room_jid, password=password, JoinGroupchatWindow(account, room_jid, password=password,
transient_for=transient_for) transient_for=transient_for)
disco_account = connected_accounts[0] if account is None else account disco_account = connected_accounts[0] if account is None else account
@ -1928,7 +1941,7 @@ class Interface:
path = helpers.get_emoticon_theme_path(emot_theme) path = helpers.get_emoticon_theme_path(emot_theme)
if not emoticons.load(path, ascii_emoticons): if not emoticons.load(path, ascii_emoticons):
dialogs.WarningDialog( WarningDialog(
_('Emoticons disabled'), _('Emoticons disabled'),
_('Your configured emoticons theme could not be loaded.' _('Your configured emoticons theme could not be loaded.'
' See the log for more details.'), ' See the log for more details.'),
@ -1948,7 +1961,7 @@ class Interface:
if app.contacts.get_contact(account, room_jid) and \ if app.contacts.get_contact(account, room_jid) and \
not app.contacts.get_contact(account, room_jid).is_groupchat(): not app.contacts.get_contact(account, room_jid).is_groupchat():
dialogs.ErrorDialog(_('This is not a group chat'), ErrorDialog(_('This is not a group chat'),
_('%(room_jid)s is already in your roster. Please check ' _('%(room_jid)s is already in your roster. Please check '
'if %(room_jid)s is a correct group chat name. If it is, ' 'if %(room_jid)s is a correct group chat name. If it is, '
'delete it from your roster and try joining the group chat ' 'delete it from your roster and try joining the group chat '
@ -1973,7 +1986,7 @@ class Interface:
invisible_show = app.SHOW_LIST.index('invisible') invisible_show = app.SHOW_LIST.index('invisible')
if app.connections[account].connected == invisible_show: if app.connections[account].connected == invisible_show:
dialogs.ErrorDialog( ErrorDialog(
_('You cannot join a group chat while you are invisible')) _('You cannot join a group chat while you are invisible'))
return return
@ -2331,7 +2344,7 @@ class Interface:
print(err_str, file=sys.stderr) print(err_str, file=sys.stderr)
# it is good to notify the user # it is good to notify the user
# in case he or she cannot see the output of the console # in case he or she cannot see the output of the console
error_dialog = dialogs.ErrorDialog(_('Could not save your settings and ' error_dialog = ErrorDialog(_('Could not save your settings and '
'preferences'), err_str) 'preferences'), err_str)
error_dialog.run() error_dialog.run()
sys.exit() sys.exit()
@ -2923,7 +2936,7 @@ class PassphraseRequest:
self.complete(passphrase) self.complete(passphrase)
return return
elif result == 'expired': elif result == 'expired':
dialogs.ErrorDialog(_('OpenPGP key expired'), ErrorDialog(_('OpenPGP key expired'),
_('Your OpenPGP key has expired, you will be connected to ' _('Your OpenPGP key has expired, you will be connected to '
'%s without OpenPGP.') % account) '%s without OpenPGP.') % account)
# Don't try to connect with GPG # Don't try to connect with GPG

View file

@ -85,7 +85,9 @@ if is_standalone():
from gajim.common import app from gajim.common import app
from gajim.common.const import JIDConstant, KindConstant from gajim.common.const import JIDConstant, KindConstant
from gajim.common import helpers from gajim.common import helpers
from gajim import dialogs from gajim.gtk import YesNoDialog
from gajim.gtk import ErrorDialog
from gajim.gtk import ConfirmationDialog
from gajim.filechoosers import FileSaveDialog from gajim.filechoosers import FileSaveDialog
from gajim import gtkgui_helpers from gajim import gtkgui_helpers
@ -111,8 +113,8 @@ class HistoryManager:
log_db_path = configpaths.get('LOG_DB') log_db_path = configpaths.get('LOG_DB')
if not os.path.exists(log_db_path): if not os.path.exists(log_db_path):
dialogs.ErrorDialog(_('Cannot find history logs database'), ErrorDialog(_('Cannot find history logs database'),
'%s does not exist.' % log_db_path) '%s does not exist.' % log_db_path)
sys.exit() sys.exit()
xml = gtkgui_helpers.get_gtk_builder('history_manager.ui') xml = gtkgui_helpers.get_gtk_builder('history_manager.ui')
@ -250,7 +252,7 @@ class HistoryManager:
if is_standalone(): if is_standalone():
Gtk.main_quit() Gtk.main_quit()
dialog = dialogs.YesNoDialog( dialog = YesNoDialog(
_('Do you want to clean up the database? ' _('Do you want to clean up the database? '
'(STRONGLY NOT RECOMMENDED IF GAJIM IS RUNNING)'), '(STRONGLY NOT RECOMMENDED IF GAJIM IS RUNNING)'),
_('Normally allocated database size will not be freed, ' _('Normally allocated database size will not be freed, '
@ -581,7 +583,7 @@ class HistoryManager:
else: else:
pri_text = _( pri_text = _(
'Do you wish to delete all correspondence with the selected contacts?') 'Do you wish to delete all correspondence with the selected contacts?')
dialog = dialogs.ConfirmationDialog('', dialog = ConfirmationDialog('',
_('This can not be undone.'), on_response_ok=(on_ok, _('This can not be undone.'), on_response_ok=(on_ok,
liststore, list_of_paths)) liststore, list_of_paths))
dialog.set_title(_('Deletion Confirmation')) dialog.set_title(_('Deletion Confirmation'))
@ -620,7 +622,7 @@ class HistoryManager:
pri_text = i18n.ngettext( pri_text = i18n.ngettext(
'Do you really want to delete the selected message?', 'Do you really want to delete the selected message?',
'Do you really want to delete the selected messages?', paths_len) 'Do you really want to delete the selected messages?', paths_len)
dialog = dialogs.ConfirmationDialog(pri_text, dialog = ConfirmationDialog(pri_text,
_('This is an irreversible operation.'), on_response_ok=(on_ok, _('This is an irreversible operation.'), on_response_ok=(on_ok,
liststore, list_of_paths)) liststore, list_of_paths))
dialog.set_title(_('Deletion Confirmation')) dialog.set_title(_('Deletion Confirmation'))

View file

@ -35,7 +35,7 @@ from enum import IntEnum, unique
from gajim import gtkgui_helpers from gajim import gtkgui_helpers
from gajim import conversation_textview from gajim import conversation_textview
from gajim import dialogs from gajim.gtk import ErrorDialog
from gajim.common import app from gajim.common import app
from gajim.common import helpers from gajim.common import helpers
@ -386,7 +386,7 @@ class HistoryWindow:
log_days = app.logger.get_days_with_logs( log_days = app.logger.get_days_with_logs(
self.account, self.jid, year, month) self.account, self.jid, year, month)
except exceptions.PysqliteOperationalError as e: except exceptions.PysqliteOperationalError as e:
dialogs.ErrorDialog(_('Disk Error'), str(e)) ErrorDialog(_('Disk Error'), str(e))
return return
for date in log_days: for date in log_days:
@ -439,7 +439,7 @@ class HistoryWindow:
logs = app.logger.get_date_has_logs( logs = app.logger.get_date_has_logs(
self.account, self.jid, _date) self.account, self.jid, _date)
except exceptions.PysqliteOperationalError as e: except exceptions.PysqliteOperationalError as e:
dialogs.ErrorDialog(_('Disk Error'), str(e)) ErrorDialog(_('Disk Error'), str(e))
return return
gtk_month = gtkgui_helpers.make_python_month_gtk_month(_date.month) gtk_month = gtkgui_helpers.make_python_month_gtk_month(_date.month)

View file

@ -51,10 +51,12 @@ if __name__ == '__main__':
from gajim.common import configpaths from gajim.common import configpaths
configpaths.init() configpaths.init()
from gajim.common import app from gajim.common import app
from gajim import gtkgui_helpers from gajim.gtk.util import load_icon
from gajim.gtkgui_helpers import get_icon_pixmap from gajim.gtk.util import get_cursor
from gajim.gtk.util import get_builder
from gajim.common import helpers from gajim.common import helpers
from gajim import dialogs from gajim.gtk import JoinGroupchatWindow
from gajim.gtk import AddNewContactWindow
import logging import logging
log = logging.getLogger('gajim.htmlview') log = logging.getLogger('gajim.htmlview')
@ -554,7 +556,7 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
if alt: if alt:
alt += '\n' alt += '\n'
alt += _('Loading') alt += _('Loading')
pixbuf = get_icon_pixmap('gtk-no') pixbuf = load_icon('image-missing', self.textview, pixbuf=True)
if mem: if mem:
# Caveat: GdkPixbuf is known not to be safe to load # Caveat: GdkPixbuf is known not to be safe to load
# images from network... this program is now potentially # images from network... this program is now potentially
@ -889,11 +891,11 @@ class HtmlTextView(Gtk.TextView):
text = text[:47] + '' text = text[:47] + ''
tooltip.set_text(text) tooltip.set_text(text)
if not self._changed_cursor: if not self._changed_cursor:
window.set_cursor(gtkgui_helpers.get_cursor('HAND2')) window.set_cursor(get_cursor('HAND2'))
self._changed_cursor = True self._changed_cursor = True
return True return True
if self._changed_cursor: if self._changed_cursor:
window.set_cursor(gtkgui_helpers.get_cursor('XTERM')) window.set_cursor(get_cursor('XTERM'))
self._changed_cursor = False self._changed_cursor = False
return False return False
@ -908,14 +910,13 @@ class HtmlTextView(Gtk.TextView):
# app.interface.new_chat_from_jid(self.account, jid) # app.interface.new_chat_from_jid(self.account, jid)
def on_join_group_chat_menuitem_activate(self, widget, room_jid): def on_join_group_chat_menuitem_activate(self, widget, room_jid):
dialogs.JoinGroupchatWindow(None, room_jid) JoinGroupchatWindow(None, room_jid)
def on_add_to_roster_activate(self, widget, jid): def on_add_to_roster_activate(self, widget, jid):
dialogs.AddNewContactWindow(self.account, jid) AddNewContactWindow(self.account, jid)
def make_link_menu(self, event, kind, text): def make_link_menu(self, event, kind, text):
from gajim.gtkgui_helpers import get_gtk_builder xml = get_builder('chat_context_menu.ui')
xml = get_gtk_builder('chat_context_menu.ui')
menu = xml.get_object('chat_context_menu') menu = xml.get_object('chat_context_menu')
childs = menu.get_children() childs = menu.get_children()
if kind == 'url': if kind == 'url':
@ -1114,13 +1115,13 @@ if __name__ == '__main__':
y = pointer[2] y = pointer[2]
tags = htmlview.tv.get_iter_at_location(x, y)[1].get_tags() tags = htmlview.tv.get_iter_at_location(x, y)[1].get_tags()
if change_cursor: if change_cursor:
w.set_cursor(gtkgui_helpers.get_cursor('XTERM')) w.set_cursor(get_cursor('XTERM'))
change_cursor = None change_cursor = None
tag_table = htmlview.tv.get_buffer().get_tag_table() tag_table = htmlview.tv.get_buffer().get_tag_table()
for tag in tags: for tag in tags:
try: try:
if tag.is_anchor: if tag.is_anchor:
w.set_cursor(gtkgui_helpers.get_cursor('HAND2')) w.set_cursor(get_cursor('HAND2'))
change_cursor = tag change_cursor = tag
elif tag == tag_table.lookup('focus-out-line'): elif tag == tag_table.lookup('focus-out-line'):
over_line = True over_line = True

View file

@ -37,7 +37,7 @@ from gi.repository import GLib
from gajim import common from gajim import common
from gajim import gtkgui_helpers from gajim import gtkgui_helpers
from gajim import message_control from gajim import message_control
from gajim import dialogs from gajim.gtk import YesNoDialog
from gajim.chat_control_base import ChatControlBase from gajim.chat_control_base import ChatControlBase
from gajim.chat_control import ChatControl from gajim.chat_control import ChatControl
from gajim.common import app from gajim.common import app
@ -240,7 +240,7 @@ class MessageWindow(object):
ctrl.minimize() ctrl.minimize()
# destroy window # destroy window
return False return False
dialogs.YesNoDialog( YesNoDialog(
_('You are going to close several tabs'), _('You are going to close several tabs'),
_('Do you really want to close them all?'), _('Do you really want to close them all?'),
checktext=_('_Do not ask me again'), on_response_yes=on_yes1, checktext=_('_Do not ask me again'), on_response_yes=on_yes1,

View file

@ -5,7 +5,7 @@ from gajim import gtkgui_helpers
from gajim.common.const import OptionKind, OptionType from gajim.common.const import OptionKind, OptionType
from gajim.common.exceptions import GajimGeneralException from gajim.common.exceptions import GajimGeneralException
from gajim import dialogs from gajim import dialogs
from gajim.gtk import ErrorDialog
class OptionsDialog(Gtk.ApplicationWindow): class OptionsDialog(Gtk.ApplicationWindow):
def __init__(self, parent, title, flags, options, account, def __init__(self, parent, title, flags, options, account,
@ -558,7 +558,7 @@ class GPGOption(DialogOption):
secret_keys[_('None')] = _('None') secret_keys[_('None')] = _('None')
if not secret_keys: if not secret_keys:
dialogs.ErrorDialog( ErrorDialog(
_('Failed to get secret keys'), _('Failed to get secret keys'),
_('There is no OpenPGP secret key available.'), _('There is no OpenPGP secret key available.'),
transient_for=parent) transient_for=parent)

View file

@ -26,16 +26,16 @@ GUI classes related to plug-in management.
__all__ = ['PluginsWindow'] __all__ = ['PluginsWindow']
from gi.repository import Pango
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import GdkPixbuf from gi.repository import GdkPixbuf
from gi.repository import GLib, Gdk from gi.repository import Gdk
import os import os
from enum import IntEnum, unique from enum import IntEnum, unique
from gajim import gtkgui_helpers from gajim import gtkgui_helpers
from gajim.dialogs import WarningDialog, YesNoDialog from gajim.gtk import WarningDialog
from gajim.gtk import YesNoDialog
from gajim.filechoosers import ArchiveChooserDialog from gajim.filechoosers import ArchiveChooserDialog
from gajim.common import app from gajim.common import app
from gajim.common import configpaths from gajim.common import configpaths
@ -44,6 +44,7 @@ from gajim.plugins.helpers import GajimPluginActivateException
from gajim.plugins.plugins_i18n import _ from gajim.plugins.plugins_i18n import _
from gajim.common.exceptions import PluginsystemError from gajim.common.exceptions import PluginsystemError
@unique @unique
class Column(IntEnum): class Column(IntEnum):
PLUGIN = 0 PLUGIN = 0

View file

@ -30,7 +30,8 @@ from gi.repository import Gdk
from gi.repository import GLib from gi.repository import GLib
from gajim import gtkgui_helpers from gajim import gtkgui_helpers
from gajim import dialogs from gajim.gtk import ErrorDialog
from gajim.gtk import InformationDialog
from gajim.filechoosers import AvatarChooserDialog from gajim.filechoosers import AvatarChooserDialog
from gajim.common.const import AvatarSize from gajim.common.const import AvatarSize
from gajim.common import app from gajim.common import app
@ -127,7 +128,7 @@ class ProfileWindow(Gtk.ApplicationWindow):
def on_ok(path_to_file): def on_ok(path_to_file):
sha = app.interface.save_avatar(path_to_file, publish=True) sha = app.interface.save_avatar(path_to_file, publish=True)
if sha is None: if sha is None:
dialogs.ErrorDialog( ErrorDialog(
_('Could not load image'), transient_for=self) _('Could not load image'), transient_for=self)
return return
@ -181,7 +182,7 @@ class ProfileWindow(Gtk.ApplicationWindow):
except ValueError: except ValueError:
if not widget.is_focus(): if not widget.is_focus():
pritext = _('Wrong date format') pritext = _('Wrong date format')
dialogs.ErrorDialog(pritext, _('Format of the date must be ' ErrorDialog(pritext, _('Format of the date must be '
'YYYY-MM-DD'), transient_for=self) 'YYYY-MM-DD'), transient_for=self)
GLib.idle_add(lambda: widget.grab_focus()) GLib.idle_add(lambda: widget.grab_focus())
return True return True
@ -321,7 +322,7 @@ class ProfileWindow(Gtk.ApplicationWindow):
# Operation in progress # Operation in progress
return return
if app.connections[self.account].connected < 2: if app.connections[self.account].connected < 2:
dialogs.ErrorDialog(_('You are not connected to the server'), ErrorDialog(_('You are not connected to the server'),
_('Without a connection, you can not publish your contact ' _('Without a connection, you can not publish your contact '
'information.'), transient_for=self) 'information.'), transient_for=self)
return return
@ -363,7 +364,7 @@ class ProfileWindow(Gtk.ApplicationWindow):
GLib.source_remove(self.update_progressbar_timeout_id) GLib.source_remove(self.update_progressbar_timeout_id)
self.progressbar.set_fraction(0) self.progressbar.set_fraction(0)
self.update_progressbar_timeout_id = None self.update_progressbar_timeout_id = None
dialogs.InformationDialog(_('vCard publication failed'), InformationDialog(_('vCard publication failed'),
_('There was an error while publishing your personal information, ' _('There was an error while publishing your personal information, '
'try again later.'), transient_for=self) 'try again later.'), transient_for=self)

View file

@ -33,7 +33,7 @@ import mimetypes
from gajim.common import app from gajim.common import app
from gajim.common import helpers from gajim.common import helpers
from gajim.dialogs import AddNewContactWindow from gajim.gtk import AddNewContactWindow
from gajim.common import ged from gajim.common import ged
from gajim.common.connection_handlers_events import MessageOutgoingEvent from gajim.common.connection_handlers_events import MessageOutgoingEvent
from gajim.common.connection_handlers_events import GcMessageOutgoingEvent from gajim.common.connection_handlers_events import GcMessageOutgoingEvent

View file

@ -58,6 +58,17 @@ from gajim import cell_renderer_image
from gajim import tooltips from gajim import tooltips
from gajim import message_control from gajim import message_control
from gajim import adhoc_commands from gajim import adhoc_commands
from gajim.gtk import JoinGroupchatWindow
from gajim.gtk import ConfirmationDialogCheck
from gajim.gtk import ConfirmationDialog
from gajim.gtk import ErrorDialog
from gajim.gtk import InputDialog
from gajim.gtk import WarningDialog
from gajim.gtk import InformationDialog
from gajim.gtk import NonModalConfirmationDialog
from gajim.gtk import SingleMessageWindow
from gajim.gtk import AddNewContactWindow
from gajim.common.const import AvatarSize from gajim.common.const import AvatarSize
from gajim.common import app from gajim.common import app
@ -1952,7 +1963,7 @@ class RosterWindow:
ft = app.interface.instances['file_transfers'] ft = app.interface.instances['file_transfers']
event = app.events.get_first_event(account, jid, event.type_) event = app.events.get_first_event(account, jid, event.type_)
if event.type_ == 'normal': if event.type_ == 'normal':
dialogs.SingleMessageWindow(account, jid, SingleMessageWindow(account, jid,
action='receive', from_whom=jid, subject=event.subject, action='receive', from_whom=jid, subject=event.subject,
message=event.message, resource=event.resource, message=event.message, resource=event.resource,
session=event.session, form_node=event.form_node) session=event.session, form_node=event.form_node)
@ -2025,7 +2036,7 @@ class RosterWindow:
Authorize a contact (by re-sending auth menuitem) Authorize a contact (by re-sending auth menuitem)
""" """
app.connections[account].send_authorization(jid) app.connections[account].send_authorization(jid)
dialogs.InformationDialog(_('Authorization sent'), InformationDialog(_('Authorization sent'),
_('"%s" will now see your status.') %jid) _('"%s" will now see your status.') %jid)
def req_sub(self, widget, jid, txt, account, groups=None, nickname=None, def req_sub(self, widget, jid, txt, account, groups=None, nickname=None,
@ -2049,7 +2060,7 @@ class RosterWindow:
app.contacts.add_contact(account, contact) app.contacts.add_contact(account, contact)
else: else:
if not _('Not in Roster') in contact.get_shown_groups(): if not _('Not in Roster') in contact.get_shown_groups():
dialogs.InformationDialog(_('Subscription request has been ' InformationDialog(_('Subscription request has been '
'sent'), _('If "%s" accepts this request you will know his ' 'sent'), _('If "%s" accepts this request you will know his '
'or her status.') % jid) 'or her status.') % jid)
return return
@ -2064,7 +2075,7 @@ class RosterWindow:
Revoke a contact's authorization Revoke a contact's authorization
""" """
app.connections[account].refuse_authorization(jid) app.connections[account].refuse_authorization(jid)
dialogs.InformationDialog(_('Authorization removed'), InformationDialog(_('Authorization removed'),
_('Now "%s" will always see you as offline.') %jid) _('Now "%s" will always see you as offline.') %jid)
def set_state(self, account, state): def set_state(self, account, state):
@ -2089,7 +2100,7 @@ class RosterWindow:
keyid = app.config.get_per('accounts', account, 'keyid') keyid = app.config.get_per('accounts', account, 'keyid')
if keyid and not app.connections[account].gpg: if keyid and not app.connections[account].gpg:
dialogs.WarningDialog(_('OpenPGP is not usable'), WarningDialog(_('OpenPGP is not usable'),
_('Gajim needs python-gnupg >= 0.3.8\n' _('Gajim needs python-gnupg >= 0.3.8\n'
'Beware there is an incompatible Python package called gnupg.\n' 'Beware there is an incompatible Python package called gnupg.\n'
'You will be connected to %s without OpenPGP.') % account) 'You will be connected to %s without OpenPGP.') % account)
@ -2298,7 +2309,7 @@ class RosterWindow:
self.get_status_message(status, on_response) self.get_status_message(status, on_response)
if status == 'invisible' and self.connected_rooms(account): if status == 'invisible' and self.connected_rooms(account):
dialogs.ConfirmationDialog( ConfirmationDialog(
_('You are participating in one or more group chats'), _('You are participating in one or more group chats'),
_('Changing your status to invisible will result in ' _('Changing your status to invisible will result in '
'disconnection from those group chats. Are you sure you want ' 'disconnection from those group chats. Are you sure you want '
@ -2398,7 +2409,7 @@ class RosterWindow:
if checked: if checked:
app.config.set('quit_on_roster_x_button', True) app.config.set('quit_on_roster_x_button', True)
self.on_quit_request() self.on_quit_request()
dialogs.ConfirmationDialogCheck(_('Really quit Gajim?'), ConfirmationDialogCheck(_('Really quit Gajim?'),
_('Are you sure you want to quit Gajim?'), _('Are you sure you want to quit Gajim?'),
_('Always close Gajim'), on_response_ok=on_ok) _('Always close Gajim'), on_response_ok=on_ok)
return True # do NOT destroy the window return True # do NOT destroy the window
@ -2488,7 +2499,7 @@ class RosterWindow:
break break
if transfer_active: if transfer_active:
dialogs.ConfirmationDialog(_('You have running file transfers'), ConfirmationDialog(_('You have running file transfers'),
_('If you quit now, the file(s) being transferred will ' _('If you quit now, the file(s) being transferred will '
'be stopped. Do you still want to quit?'), 'be stopped. Do you still want to quit?'),
on_response_ok=(on_continue3, message, pep_dict)) on_response_ok=(on_continue3, message, pep_dict))
@ -2520,7 +2531,7 @@ class RosterWindow:
break break
if unread or recent: if unread or recent:
dialogs.ConfirmationDialog(_('You have unread messages'), ConfirmationDialog(_('You have unread messages'),
_('Messages will only be available for reading them later ' _('Messages will only be available for reading them later '
'if you have history enabled and contact is in your ' 'if you have history enabled and contact is in your '
'roster.'), on_response_ok=(on_continue2, 'roster.'), on_response_ok=(on_continue2,
@ -2694,7 +2705,7 @@ class RosterWindow:
return return
if obj.mtype == 'normal' and obj.popup: if obj.mtype == 'normal' and obj.popup:
# it's single message to be autopopuped # it's single message to be autopopuped
dialogs.SingleMessageWindow(obj.conn.name, obj.jid, SingleMessageWindow(obj.conn.name, obj.jid,
action='receive', from_whom=obj.jid, subject=obj.subject, action='receive', from_whom=obj.jid, subject=obj.subject,
message=obj.msgtxt, resource=obj.resource, session=obj.session, message=obj.msgtxt, resource=obj.resource, session=obj.session,
form_node=obj.form_node) form_node=obj.form_node)
@ -2798,7 +2809,7 @@ class RosterWindow:
has_unread_events = True has_unread_events = True
break break
if has_unread_events: if has_unread_events:
dialogs.ErrorDialog(_('You have unread messages'), ErrorDialog(_('You have unread messages'),
_('You must read them before removing this transport.')) _('You must read them before removing this transport.'))
return return
if len(list_) == 1: if len(list_) == 1:
@ -2813,7 +2824,7 @@ class RosterWindow:
jids = jids[:-1] + '.' jids = jids[:-1] + '.'
sectext = _('You will no longer be able to send and receive ' sectext = _('You will no longer be able to send and receive '
'messages to contacts from these transports: %s') % jids 'messages to contacts from these transports: %s') % jids
dialogs.ConfirmationDialog(pritext, sectext, ConfirmationDialog(pritext, sectext,
on_response_ok = (remove, list_), transient_for=self.window) on_response_ok = (remove, list_), transient_for=self.window)
def _nec_blocking(self, obj): def _nec_blocking(self, obj):
@ -2864,7 +2875,7 @@ class RosterWindow:
' to continue?') ' to continue?')
sectext = _('This contact will see you offline and you will not ' sectext = _('This contact will see you offline and you will not '
'receive messages it sends you.') 'receive messages it sends you.')
dialogs.ConfirmationDialogCheck(pritext, sectext, ConfirmationDialogCheck(pritext, sectext,
_('_Do not ask me again'), on_response_ok=_block_it) _('_Do not ask me again'), on_response_ok=_block_it)
def on_unblock(self, widget, list_, group=None): def on_unblock(self, widget, list_, group=None):
@ -2943,7 +2954,7 @@ class RosterWindow:
if 'rename' in app.interface.instances: if 'rename' in app.interface.instances:
del app.interface.instances['rename'] del app.interface.instances['rename']
app.interface.instances['rename'] = dialogs.InputDialog(title, app.interface.instances['rename'] = InputDialog(title,
message, old_text, False, (on_renamed, account, row_type, jid, message, old_text, False, (on_renamed, account, row_type, jid,
old_text), on_canceled, transient_for=self.window) old_text), on_canceled, transient_for=self.window)
@ -2958,7 +2969,7 @@ class RosterWindow:
app.connections[account].unsubscribe(contact.jid) app.connections[account].unsubscribe(contact.jid)
self.remove_contact(contact.jid, account, backend=True) self.remove_contact(contact.jid, account, backend=True)
dialogs.ConfirmationDialogCheck(_('Remove Group'), ConfirmationDialogCheck(_('Remove Group'),
_('Do you want to remove group %s from the roster?') % group, _('Do you want to remove group %s from the roster?') % group,
_('Also remove all contacts in this group from your roster'), _('Also remove all contacts in this group from your roster'),
on_response_ok=on_ok) on_response_ok=on_ok)
@ -3039,14 +3050,14 @@ class RosterWindow:
def on_send_single_message_menuitem_activate(self, widget, account, def on_send_single_message_menuitem_activate(self, widget, account,
contact=None): contact=None):
if contact is None: if contact is None:
dialogs.SingleMessageWindow(account, action='send') SingleMessageWindow(account, action='send')
elif isinstance(contact, list): elif isinstance(contact, list):
dialogs.SingleMessageWindow(account, contact, 'send') SingleMessageWindow(account, contact, 'send')
else: else:
jid = contact.jid jid = contact.jid
if contact.jid == app.get_jid_from_account(account): if contact.jid == app.get_jid_from_account(account):
jid += '/' + contact.resource jid += '/' + contact.resource
dialogs.SingleMessageWindow(account, jid, 'send') SingleMessageWindow(account, jid, 'send')
def on_send_file_menuitem_activate(self, widget, contact, account, def on_send_file_menuitem_activate(self, widget, contact, account,
resource=None): resource=None):
@ -3077,7 +3088,7 @@ class RosterWindow:
app.interface.instances[account]['join_gc'].destroy() app.interface.instances[account]['join_gc'].destroy()
else: else:
app.interface.instances[account]['join_gc'] = \ app.interface.instances[account]['join_gc'] = \
dialogs.JoinGroupchatWindow( JoinGroupchatWindow(
account, None, automatic={'invities': jid_list}) account, None, automatic={'invities': jid_list})
break break
@ -3147,7 +3158,7 @@ class RosterWindow:
dialogs.ChangeStatusMessageDialog(on_response, show) dialogs.ChangeStatusMessageDialog(on_response, show)
def on_add_to_roster(self, widget, contact, account): def on_add_to_roster(self, widget, contact, account):
dialogs.AddNewContactWindow(account, contact.jid, contact.name) AddNewContactWindow(account, contact.jid, contact.name)
def on_roster_treeview_key_press_event(self, widget, event): def on_roster_treeview_key_press_event(self, widget, event):
""" """
@ -3410,17 +3421,17 @@ class RosterWindow:
'your roster.\n') % {'name': contact.get_shown_name(), 'your roster.\n') % {'name': contact.get_shown_name(),
'jid': contact.jid} 'jid': contact.jid}
if contact.sub == 'to': if contact.sub == 'to':
dialogs.ConfirmationDialog(pritext, sectext + \ ConfirmationDialog(pritext, sectext + \
_('By removing this contact you also remove authorization ' _('By removing this contact you also remove authorization '
'resulting in them always seeing you as offline.'), 'resulting in them always seeing you as offline.'),
on_response_ok=(on_ok2, list_)) on_response_ok=(on_ok2, list_))
elif _('Not in Roster') in contact.get_shown_groups(): elif _('Not in Roster') in contact.get_shown_groups():
# Contact is not in roster # Contact is not in roster
dialogs.ConfirmationDialog(pritext, sectext + \ ConfirmationDialog(pritext, sectext + \
_('Do you want to continue?'), on_response_ok=(on_ok2, _('Do you want to continue?'), on_response_ok=(on_ok2,
list_)) list_))
else: else:
dialogs.ConfirmationDialogCheck(pritext, sectext + \ ConfirmationDialogCheck(pritext, sectext + \
_('By removing this contact you also by default remove ' _('By removing this contact you also by default remove '
'authorization resulting in them always seeing you as' 'authorization resulting in them always seeing you as'
' offline.'), ' offline.'),
@ -3436,7 +3447,7 @@ class RosterWindow:
sectext = _('By removing these contacts:%s\nyou also remove ' sectext = _('By removing these contacts:%s\nyou also remove '
'authorization resulting in them always seeing you as ' 'authorization resulting in them always seeing you as '
'offline.') % jids 'offline.') % jids
dialogs.ConfirmationDialog(pritext, sectext, ConfirmationDialog(pritext, sectext,
on_response_ok=(on_ok2, list_)) on_response_ok=(on_ok2, list_))
def on_send_custom_status(self, widget, contact_list, show, group=None): def on_send_custom_status(self, widget, contact_list, show, group=None):
@ -3497,7 +3508,7 @@ class RosterWindow:
sectext = _('This contact will temporarily see you as %(status)s, ' sectext = _('This contact will temporarily see you as %(status)s, '
'but only until you change your status. Then they will see ' 'but only until you change your status. Then they will see '
'your global status.') % {'status': show} 'your global status.') % {'status': show}
dialogs.ConfirmationDialogCheck(pritext, sectext, ConfirmationDialogCheck(pritext, sectext,
_('_Do not ask me again'), on_response_ok=send_it) _('_Do not ask me again'), on_response_ok=send_it)
def on_status_combobox_changed(self, widget): def on_status_combobox_changed(self, widget):
@ -3513,7 +3524,7 @@ class RosterWindow:
return return
accounts = list(app.connections.keys()) accounts = list(app.connections.keys())
if len(accounts) == 0: if len(accounts) == 0:
dialogs.ErrorDialog(_('No account available'), ErrorDialog(_('No account available'),
_('You must create an account before you can chat with other ' _('You must create an account before you can chat with other '
'contacts.')) 'contacts.'))
self.update_status_combobox() self.update_status_combobox()
@ -3593,7 +3604,7 @@ class RosterWindow:
def on_cancel(): def on_cancel():
self.update_status_combobox() self.update_status_combobox()
dialogs.ConfirmationDialog( ConfirmationDialog(
_('You are participating in one or more group chats'), _('You are participating in one or more group chats'),
_('Changing your status to invisible will result in ' _('Changing your status to invisible will result in '
'disconnection from those group chats. Are you sure you ' 'disconnection from those group chats. Are you sure you '
@ -3637,7 +3648,7 @@ class RosterWindow:
config.ManagePEPServicesWindow(account) config.ManagePEPServicesWindow(account)
def on_add_new_contact(self, widget, account): def on_add_new_contact(self, widget, account):
dialogs.AddNewContactWindow(account) AddNewContactWindow(account)
def on_join_gc_activate(self, widget, account): def on_join_gc_activate(self, widget, account):
""" """
@ -4140,7 +4151,7 @@ class RosterWindow:
if not app.connections[account_source].private_storage_supported or \ if not app.connections[account_source].private_storage_supported or \
not app.connections[account_dest].private_storage_supported: not app.connections[account_dest].private_storage_supported:
dialogs.WarningDialog(_('Metacontacts storage not supported by ' WarningDialog(_('Metacontacts storage not supported by '
'your server'), 'your server'),
_('Your server does not support storing metacontacts ' _('Your server does not support storing metacontacts '
'information. So this information will not be saved on next ' 'information. So this information will not be saved on next '
@ -4243,7 +4254,7 @@ class RosterWindow:
sectext = _('Metacontacts are a way to regroup several contacts in one ' sectext = _('Metacontacts are a way to regroup several contacts in one '
'line. Generally it is used when the same person has several ' 'line. Generally it is used when the same person has several '
'XMPP- or transport -accounts.') 'XMPP- or transport -accounts.')
dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, dlg = ConfirmationDialogCheck(pritext, sectext,
_('_Do not ask me again'), on_response_ok=merge_contacts) _('_Do not ask me again'), on_response_ok=merge_contacts)
if not confirm_metacontacts: # First time we see this window if not confirm_metacontacts: # First time we see this window
dlg.checkbutton.set_active(True) dlg.checkbutton.set_active(True)
@ -4362,7 +4373,7 @@ class RosterWindow:
if not os.path.isfile(path): if not os.path.isfile(path):
bad_uris.append(a_uri) bad_uris.append(a_uri)
if len(bad_uris): if len(bad_uris):
dialogs.ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris)) ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris))
return return
def _on_send_files(account, jid, uris): def _on_send_files(account, jid, uris):
c = app.contacts.get_contact_with_highest_priority(account, c = app.contacts.get_contact_with_highest_priority(account,
@ -4380,7 +4391,7 @@ class RosterWindow:
for uri in uri_splitted: for uri in uri_splitted:
path = helpers.get_file_path_from_dnd_dropped_uri(uri) path = helpers.get_file_path_from_dnd_dropped_uri(uri)
sec_text += '\n' + os.path.basename(path) sec_text += '\n' + os.path.basename(path)
dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text, dialog = NonModalConfirmationDialog(prim_text, sec_text,
on_response_ok=(_on_send_files, account_dest, jid_dest, on_response_ok=(_on_send_files, account_dest, jid_dest,
uri_splitted)) uri_splitted))
dialog.popup() dialog.popup()
@ -4475,7 +4486,7 @@ class RosterWindow:
if (type_dest == 'account' or not self.regroup) and \ if (type_dest == 'account' or not self.regroup) and \
account_source != account_dest: account_source != account_dest:
# add to account in specified group # add to account in specified group
dialogs.AddNewContactWindow(account=account_dest, jid=jid_source, AddNewContactWindow(account=account_dest, jid=jid_source,
user_nick=c_source.name, group=grp_dest) user_nick=c_source.name, group=grp_dest)
return return

View file

@ -28,10 +28,11 @@ from gajim.common import dataforms
from gajim.common import ged from gajim.common import ged
from gajim import gtkgui_helpers from gajim import gtkgui_helpers
from gajim import dialogs
from gajim import vcard from gajim import vcard
from gajim import config from gajim import config
from gajim import dataforms_widget from gajim import dataforms_widget
from gajim.gtk import AddNewContactWindow
class SearchWindow: class SearchWindow:
def __init__(self, account, jid): def __init__(self, account, jid):
@ -116,7 +117,7 @@ class SearchWindow:
if not iter_: if not iter_:
return return
jid = model[iter_][self.jid_column] jid = model[iter_][self.jid_column]
dialogs.AddNewContactWindow(self.account, jid) AddNewContactWindow(self.account, jid)
def on_information_button_clicked(self, widget): def on_information_button_clicked(self, widget):
(model, iter_) = self.result_treeview.get_selection().get_selected() (model, iter_) = self.result_treeview.get_selection().get_selected()

View file

@ -35,7 +35,7 @@ from gajim.common.connection_handlers_events import ChatstateReceivedEvent, \
from gajim.common.const import KindConstant from gajim.common.const import KindConstant
from gajim import message_control from gajim import message_control
from gajim import notify from gajim import notify
from gajim import dialogs from gajim.gtk import SingleMessageWindow
class ChatControlSession(object): class ChatControlSession(object):
@ -358,7 +358,7 @@ class ChatControlSession(object):
popup = helpers.allow_popup_window(self.conn.name) popup = helpers.allow_popup_window(self.conn.name)
if msg_type == 'normal' and popup: # it's single message to be autopopuped if msg_type == 'normal' and popup: # it's single message to be autopopuped
dialogs.SingleMessageWindow(self.conn.name, contact.jid, SingleMessageWindow(self.conn.name, contact.jid,
action='receive', from_whom=jid, subject=subject, message=msg, action='receive', from_whom=jid, subject=subject, message=msg,
resource=resource, session=self, form_node=form_node) resource=resource, session=self, form_node=form_node)
return return

View file

@ -34,6 +34,8 @@ from gajim import tooltips
from gajim import gtkgui_helpers from gajim import gtkgui_helpers
from gajim.common import app from gajim.common import app
from gajim.common import helpers from gajim.common import helpers
from gajim.gtk import SingleMessageWindow
class StatusIcon: class StatusIcon:
""" """
@ -163,7 +165,7 @@ class StatusIcon:
jid, account) jid, account)
def on_single_message_menuitem_activate(self, widget, account): def on_single_message_menuitem_activate(self, widget, account):
dialogs.SingleMessageWindow(account, action='send') SingleMessageWindow(account, action='send')
def on_new_chat(self, widget, account): def on_new_chat(self, widget, account):
app.app.activate_action('start-chat') app.app.activate_action('start-chat')