diff --git a/gajim/accounts_window.py b/gajim/accounts_window.py index df5d553ad..118143619 100644 --- a/gajim/accounts_window.py +++ b/gajim/accounts_window.py @@ -6,7 +6,6 @@ from gajim.common import app from gajim import gtkgui_helpers from gajim import gui_menu_builder from gajim.common import passwords -from gajim import dialogs from gajim import config from gajim.common import helpers 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.options_dialog import OptionsDialog, OptionsBox from gajim.common.const import Option, OptionKind, OptionType +from gajim.gtk import ConfirmationDialog +from gajim.gtk import YesNoDialog class AccountsWindow(Gtk.ApplicationWindow): @@ -143,7 +144,7 @@ class AccountsWindow(Gtk.ApplicationWindow): account, 'offline', _('Be right back.')) GLib.timeout_add(500, login, account, show_before, status_before) - dialogs.YesNoDialog( + YesNoDialog( _('Relogin now?'), _('If you want all the changes to apply instantly, ' 'you must relogin.'), @@ -197,7 +198,7 @@ class AccountsWindow(Gtk.ApplicationWindow): app.interface.instances[account]['remove_account'] = \ config.RemoveAccountWindow(account) if win_opened: - dialogs.ConfirmationDialog( + ConfirmationDialog( _('You have opened chat in account %s') % account, _('All chat and groupchat windows will be closed. ' 'Do you want to continue?'), diff --git a/gajim/adhoc_commands.py b/gajim/adhoc_commands.py index 0db00eeee..b74e8c9b7 100644 --- a/gajim/adhoc_commands.py +++ b/gajim/adhoc_commands.py @@ -33,7 +33,7 @@ from gajim.common import dataforms from gajim.common import ged from gajim import gtkgui_helpers -from gajim import dialogs +from gajim.gtk import HigDialog from gajim import dataforms_widget class CommandWindow: @@ -322,7 +322,7 @@ class CommandWindow: dialog.destroy() cb() - dialog = dialogs.HigDialog(self.window, Gtk.MessageType.WARNING, + dialog = HigDialog(self.window, Gtk.MessageType.WARNING, Gtk.ButtonsType.YES_NO, _('Cancel confirmation'), _('You are in process of executing command. Do you really want to ' 'cancel it?'), on_response_yes=on_yes) diff --git a/gajim/app_actions.py b/gajim/app_actions.py index cc7b9763f..b733ff939 100644 --- a/gajim/app_actions.py +++ b/gajim/app_actions.py @@ -28,14 +28,21 @@ import gajim.plugins.gui from gajim import history_window from gajim import disco 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 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 def on_add_contact_jid(action, param): - dialogs.AddNewContactWindow(None, param.get_string()) + AddNewContactWindow(None, param.get_string()) # Application Menu Actions @@ -79,7 +86,7 @@ def on_new_chat(action, param): if 'start_chat' in app.interface.instances: app.interface.instances['start_chat'].present() else: - app.interface.instances['start_chat'] = dialogs.StartChatDialog() + app.interface.instances['start_chat'] = StartChatDialog() # Accounts Actions @@ -103,7 +110,7 @@ def on_send_server_message(action, param): account = param.get_string() server = app.config.get_per('accounts', account, 'hostname') server += '/announce/online' - dialogs.SingleMessageWindow(account, server, 'send') + SingleMessageWindow(account, server, 'send') def on_service_disco(action, param): @@ -127,23 +134,23 @@ def on_join_gc(action, param): return else: account = param.get_string() - window = app.get_app_window(dialogs.JoinGroupchatWindow) + window = app.get_app_window(JoinGroupchatWindow) if window is None: - dialogs.JoinGroupchatWindow(account, None) + JoinGroupchatWindow(account, None) else: window.present() def on_add_contact(action, param): - window = app.get_app_window(dialogs.AddNewContactWindow) + window = app.get_app_window(AddNewContactWindow) if window is None: - dialogs.AddNewContactWindow(param.get_string()) + AddNewContactWindow(param.get_string()) else: window.present() 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): @@ -206,7 +213,7 @@ def on_privacy_lists(action, param): interface.instances[account]['privacy_lists'].window.present() else: interface.instances[account]['privacy_lists'] = \ - dialogs.PrivacyListsWindow(account) + PrivacyListsWindow(account) def on_server_info(action, param): @@ -224,7 +231,7 @@ def on_xml_console(action, param): interface.instances[account]['xml_console'].present() else: interface.instances[account]['xml_console'] = \ - dialogs.XMLConsoleWindow(account) + XMLConsoleWindow(account) def on_manage_proxies(action, param): @@ -241,14 +248,14 @@ def on_set_motd(action, param): account = param.get_string() server = app.config.get_per('accounts', account, 'hostname') server += '/announce/motd' - dialogs.SingleMessageWindow(account, server, 'send') + SingleMessageWindow(account, server, 'send') def on_update_motd(action, param): account = param.get_string() server = app.config.get_per('accounts', account, 'hostname') server += '/announce/motd/update' - dialogs.SingleMessageWindow(account, server, 'send') + SingleMessageWindow(account, server, 'send') def on_delete_motd(action, param): @@ -279,7 +286,7 @@ def on_features(action, param): def on_about(action, param): - dialogs.AboutDialog() + AboutDialog() # View Actions diff --git a/gajim/chat_control.py b/gajim/chat_control.py index 386c272b9..ef8d2d7e3 100644 --- a/gajim/chat_control.py +++ b/gajim/chat_control.py @@ -37,6 +37,8 @@ from gajim import gtkgui_helpers from gajim import gui_menu_builder from gajim import message_control from gajim import dialogs +from gajim.gtk import ConfirmationDialog +from gajim.gtk import AddNewContactWindow from gajim.common import app from gajim.common import helpers @@ -337,7 +339,7 @@ class ChatControl(ChatControlBase): 'information-' + self.control_id).set_enabled(online) 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): app.interface.roster.on_info(None, self.contact, self.account) @@ -1208,7 +1210,7 @@ class ChatControl(ChatControlBase): def on_cancel(): on_no(self) - dialogs.ConfirmationDialog( + ConfirmationDialog( #%s is being replaced in the code with JID _('You just received a new message from "%s"') % \ self.contact.jid, diff --git a/gajim/chat_control_base.py b/gajim/chat_control_base.py index 61bb51ac0..9cb8f2b2e 100644 --- a/gajim/chat_control_base.py +++ b/gajim/chat_control_base.py @@ -37,7 +37,7 @@ from gi.repository import Gio from gajim import gtkgui_helpers from gajim import message_control -from gajim import dialogs +from gajim.gtk import NonModalConfirmationDialog from gajim import history_window from gajim import notify import re @@ -1160,7 +1160,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): prim_text = _('Really send file?') sec_text = _('If you send a file to %s, your real JID will ' 'be revealed.') % gc_contact.name - dialog = dialogs.NonModalConfirmationDialog(prim_text, + dialog = NonModalConfirmationDialog(prim_text, sec_text, on_response_ok=(_on_ok, gc_contact)) dialog.popup() return diff --git a/gajim/common/connection_handlers_events.py b/gajim/common/connection_handlers_events.py index ad44d5a7f..42b1ab36d 100644 --- a/gajim/common/connection_handlers_events.py +++ b/gajim/common/connection_handlers_events.py @@ -955,7 +955,7 @@ class GcMessageReceivedEvent(nec.NetworkIncomingEvent): if self.msg_obj.form_node: # It could be a voice request. See # http://www.xmpp.org/extensions/xep-0045.html#voiceapprove - from gajim.dialogs import SingleMessageWindow + from gajim.gtk import SingleMessageWindow SingleMessageWindow( self.conn.name, self.fjid, action='receive', from_whom=self.fjid, diff --git a/gajim/config.py b/gajim/config.py index e203edfd2..e5c874bb8 100644 --- a/gajim/config.py +++ b/gajim/config.py @@ -52,6 +52,12 @@ from gajim.gajim_themes_window import GajimThemesWindow from gajim.advanced_configuration_window import AdvancedConfigurationWindow from gajim import dataforms_widget 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 app @@ -653,7 +659,7 @@ class PreferencesWindow: if gspell_lang is None: gspell_lang = Gspell.language_get_default() if gspell_lang is None: - dialogs.AspellDictError(lang) + AspellDictError(lang) app.config.set('use_speller', False) widget.set_active(False) else: @@ -1682,7 +1688,7 @@ class GroupchatConfigWindow: return model = self.affiliation_treeview[affiliation].get_model() 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): selection = self.affiliation_treeview[affiliation].get_selection() @@ -1785,7 +1791,7 @@ class RemoveAccountWindow: app.connections[self.account].change_status('offline', 'offline') if self.remove_and_unregister_radiobutton.get_active(): if not self.account in app.connections: - dialogs.ErrorDialog( + ErrorDialog( _('Account is disabled'), _('To unregister from a server, account must be ' 'enabled.'), @@ -1813,7 +1819,7 @@ class RemoveAccountWindow: if self.account in app.connections and \ app.connections[self.account].connected: - dialogs.ConfirmationDialog( + ConfirmationDialog( _('Account "%s" is connected to the server') % self.account, _('If you remove it, the connection will be lost.'), on_response_ok=remove, @@ -1829,7 +1835,7 @@ class RemoveAccountWindow: # action of unregistration has failed, we don't remove the account # Error message is send by connect_and_auth() if not res: - dialogs.ConfirmationDialogDoubleRadio( + ConfirmationDialogDoubleRadio( _('Connection to server %s failed') % self.account, _('What would you like to do?'), _('Remove only from Gajim'), @@ -2036,7 +2042,7 @@ class ManageBookmarksWindow: if self.server_entry.get_text() == '' or \ 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' ' bookmark.')) return False @@ -2166,7 +2172,7 @@ class ManageBookmarksWindow: try: nick = helpers.parse_resource(nick) except helpers.InvalidFormat: - dialogs.ErrorDialog(_('Invalid nickname'), + ErrorDialog(_('Invalid nickname'), _('Character not allowed'), transient_for=self.window) self.nick_entry.set_text(model[iter_][6]) return True @@ -2182,7 +2188,7 @@ class ManageBookmarksWindow: if not server: return if '@' in server: - dialogs.ErrorDialog(_('Invalid server'), + ErrorDialog(_('Invalid server'), _('Character not allowed'), transient_for=self.window) widget.set_text(server.replace('@', '')) @@ -2193,7 +2199,7 @@ class ManageBookmarksWindow: try: room_jid = helpers.parse_jid(room_jid) except helpers.InvalidFormat as e: - dialogs.ErrorDialog(_('Invalid server'), + ErrorDialog(_('Invalid server'), _('Character not allowed'), transient_for=self.window) self.server_entry.set_text(model[iter_][2].split('@')[1]) return True @@ -2221,7 +2227,7 @@ class ManageBookmarksWindow: try: room_jid = helpers.parse_jid(room_jid) except helpers.InvalidFormat: - dialogs.ErrorDialog(_('Invalid room'), + ErrorDialog(_('Invalid room'), _('Character not allowed'), transient_for=self.window) return True model[iter_][2] = room_jid @@ -2439,7 +2445,7 @@ class AccountCreationWizardWindow: pritext = _('Invalid username') sectext = _( 'You must provide a username to configure this account.') - dialogs.ErrorDialog(pritext, sectext) + ErrorDialog(pritext, sectext) return server = self.xml.get_object('server_comboboxtext_entry').\ get_text().strip() @@ -2457,7 +2463,7 @@ class AccountCreationWizardWindow: jid = helpers.parse_jid(jid) except helpers.InvalidFormat as s: pritext = _('Invalid JID') - dialogs.ErrorDialog(pritext, str(s)) + ErrorDialog(pritext, str(s)) return self.account = server @@ -2478,7 +2484,7 @@ class AccountCreationWizardWindow: get_text() if not server: - dialogs.ErrorDialog(_('Invalid server'), + ErrorDialog(_('Invalid server'), _('Please provide a server on which you want to register.')) return self.account = server @@ -2502,7 +2508,7 @@ class AccountCreationWizardWindow: try: custom_port = int(custom_port) except Exception: - dialogs.ErrorDialog(_('Invalid entry'), + ErrorDialog(_('Invalid entry'), _('Custom port must be a port number.')) return config['custom_port'] = custom_port @@ -2535,7 +2541,7 @@ class AccountCreationWizardWindow: with open(my_ca_certs) as f: certs = f.read() 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 ' 'not added again.') % my_ca_certs) else: @@ -2766,7 +2772,7 @@ class AccountCreationWizardWindow: def save_account(self, login, server, savepass, password, anonymous=False): 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.')) return con = connection.Connection(self.account) @@ -2899,7 +2905,7 @@ class ManagePEPServicesWindow: def node_not_removed(self, jid, node, msg): if jid != app.get_jid_from_account(self.account): 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, 'message': msg}) diff --git a/gajim/conversation_textview.py b/gajim/conversation_textview.py index 85457b217..7034c0062 100644 --- a/gajim/conversation_textview.py +++ b/gajim/conversation_textview.py @@ -38,7 +38,11 @@ from gajim import dialogs import queue 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 helpers 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): """ 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: text = text[:47] + '…' tooltip.set_text(text) - window.set_cursor(gtkgui_helpers.get_cursor('HAND2')) + window.set_cursor(get_cursor('HAND2')) self.cursor_changed = True return True 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 return False try: @@ -344,7 +343,7 @@ class ConversationTextview(GObject.GObject): except KeyError: pass if self.cursor_changed: - window.set_cursor(gtkgui_helpers.get_cursor('XTERM')) + window.set_cursor(get_cursor('XTERM')) self.cursor_changed = False return False @@ -370,7 +369,7 @@ class ConversationTextview(GObject.GObject): def scroll_to_end(self, force=False): 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): allowed = True @@ -674,10 +673,10 @@ class ConversationTextview(GObject.GObject): app.interface.join_gc_minimal(self.account, room_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): - xml = gtkgui_helpers.get_gtk_builder('chat_context_menu.ui') + xml = get_builder('chat_context_menu.ui') menu = xml.get_object('chat_context_menu') childs = menu.get_children() if kind == 'url': @@ -1142,8 +1141,9 @@ class ConversationTextview(GObject.GObject): self.print_time(text, kind, tim, simple, direction_mark, other_tags_for_time, iter_) + icon = load_icon('channel-secure-croped-symbolic', self.tv, pixbuf=True) if encrypted: - buffer_.insert_pixbuf(iter_, self.MESSAGE_ENCRYPTED_PIXBUF) + buffer_.insert_pixbuf(iter_, icon) # If there's a displaymarking, print it here. if displaymarking: @@ -1185,8 +1185,9 @@ class ConversationTextview(GObject.GObject): # Show Correction Icon buffer_.create_tag(tag_name=msg_stanza_id) buffer_.insert(iter_, ' ') + icon = load_icon('document-edit-symbolic', self.tv, pixbuf=True) buffer_.insert_pixbuf( - iter_, ConversationTextview.MESSAGE_CORRECTED_PIXBUF) + iter_, icon) tag_start_iter = iter_.copy() tag_start_iter.backward_chars(2) buffer_.apply_tag_by_name(msg_stanza_id, tag_start_iter, iter_) diff --git a/gajim/data/style/gajim.css b/gajim/data/style/gajim.css index 1f81ee7af..fc0baf96d 100644 --- a/gajim/data/style/gajim.css +++ b/gajim/data/style/gajim.css @@ -128,3 +128,8 @@ button.flat.link { padding: 0; border: 0; } /*SendFileDialog*/ #SendFileDialog grid {padding: 12px} #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; } diff --git a/gajim/dialog_messages.py b/gajim/dialog_messages.py index ffa5eff15..eec5a4bfd 100644 --- a/gajim/dialog_messages.py +++ b/gajim/dialog_messages.py @@ -22,8 +22,8 @@ from collections import namedtuple from gi.repository import GLib from gajim.common.app import app -from gajim.dialogs import ErrorDialog -from gajim.dialogs import InformationDialog +from gajim.gtk import ErrorDialog +from gajim.gtk import InformationDialog Message = namedtuple('Message', ['title', 'text', 'dialog']) diff --git a/gajim/dialogs.py b/gajim/dialogs.py index ff116f7eb..e86585a79 100644 --- a/gajim/dialogs.py +++ b/gajim/dialogs.py @@ -31,42 +31,33 @@ from gi.repository import Gtk from gi.repository import Gdk -from gi.repository import GdkPixbuf from gi.repository import GObject from gi.repository import GLib -from gi.repository import Pango import os import nbxmpp -import time -import locale +import logging from gajim import gtkgui_helpers from gajim import vcard -from gajim import conversation_textview from gajim import dataforms_widget from random import randrange -from gajim.common import pep from gajim.common import ged from gajim.common import const -from gajim.options_dialog import OptionsDialog -from gajim.common.const import Option, OptionKind, OptionType, ACTIVITIES +from gajim.common.const import ACTIVITIES from gajim.common.const import MOODS from gajim.common import app from gajim.common import helpers from gajim.common import i18n from gajim.common import dataforms -from gajim.common.const import AvatarSize -from gajim.common.caps_cache import muc_caps_cache from gajim.common.exceptions import GajimGeneralException -from gajim.common.connection_handlers_events import MessageOutgoingEvent -if app.is_installed('GSPELL'): - from gi.repository import Gspell +# Compat with Gajim 1.0.3 for plugins +from gajim.gtk import * + -import logging log = logging.getLogger('gajim.dialogs') @@ -845,1388 +836,6 @@ class ChangeStatusMessageDialog(TimeoutDialog): ChangeMoodDialog(on_response, self.pep_dict['mood'], self.pep_dict['mood_text']) -class AddNewContactWindow(Gtk.ApplicationWindow): - """ - Class for AddNewContactWindow - """ - - 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_add_new_contact_window_destroy) - self.connect('key-press-event', self.on_add_new_contact_window_key_press_event) - - 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 = gtkgui_helpers.get_gtk_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 not jid_ 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_add_new_contact_window_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_add_new_contact_window_key_press_event(self, widget, event): - if event.keyval == Gdk.KEY_Escape: # 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() - -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'), const.DEVS_CURRENT) - self.add_credit_section(_('Past Developers'), const.DEVS_PAST) - self.add_credit_section(_('Artists'), const.ARTISTS) - - thanks = const.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() - - -class Dialog(Gtk.Dialog): - def __init__(self, parent, title, buttons, default=None, - on_response_ok=None, on_response_cancel=None): - Gtk.Dialog.__init__(self, title, parent, - Gtk.DialogFlags.DESTROY_WITH_PARENT) - - self.user_response_ok = on_response_ok - self.user_response_cancel = on_response_cancel - self.set_border_width(6) - self.get_content_area().set_spacing(12) - self.set_resizable(False) - - for stock, response in buttons: - b = self.add_button(stock, response) - - if default is not None: - self.set_default_response(default) - else: - self.set_default_response(buttons[-1][1]) - - self.connect('response', self.on_response) - - def on_response(self, widget, response_id): - if response_id == Gtk.ResponseType.OK: - 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.destroy() - elif response_id == Gtk.ResponseType.CANCEL: - 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.destroy() - - def just_destroy(self, widget): - self.destroy() - - def get_button(self, index): - buttons = self.get_action_area().get_children() - return index < len(buttons) and buttons[index] or None - - -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 not response_id 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 = gtkgui_helpers.get_gtk_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 = gtkgui_helpers.get_gtk_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 = gtkgui_helpers.get_gtk_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 = gtkgui_helpers.get_gtk_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 SubscriptionRequestWindow(Gtk.ApplicationWindow): def __init__(self, jid, text, account, user_nick=None): @@ -2311,306 +920,6 @@ class SubscriptionRequestWindow(Gtk.ApplicationWindow): app.interface.roster.remove_contact(self.jid, self.account) self.destroy() -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 = gtkgui_helpers.get_gtk_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 - class SynchroniseSelectAccountDialog: def __init__(self, account): # 'account' can be None if we are about to create our first one @@ -2752,321 +1061,6 @@ class SynchroniseSelectContactsDialog: iter_ = model.iter_next(iter_) self.dialog.destroy() -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 = gtkgui_helpers.get_gtk_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 = gtkgui_helpers.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) - class ChangePasswordDialog: def __init__(self, account, on_response, transient_for=None): # 'account' can be None if we are about to create our first one @@ -3102,545 +1096,6 @@ class ChangePasswordDialog: dialog.destroy() self.on_response(password1) -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 = gtkgui_helpers.get_gtk_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 = 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 = dataforms_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 = gtkgui_helpers.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 = gtkgui_helpers.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 - gtkgui_helpers.resize_window(self.window, - app.config.get('single-msg-width'), - app.config.get('single-msg-height')) - gtkgui_helpers.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 not self.from_whom 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() - - -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 = gtkgui_helpers.get_gtk_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 = gtkgui_helpers.get_image_button( - 'edit-clear-all-symbolic', _('Clear')) - button.connect('clicked', self.on_clear) - self.actionbar.pack_start(button) - - button = gtkgui_helpers.get_image_button( - 'applications-system-symbolic', _('Filter')) - button.connect('clicked', self.on_filter_options) - self.actionbar.pack_start(button) - - button = gtkgui_helpers.get_image_button( - 'document-edit-symbolic', _('XML Input'), toggle=True) - button.connect('toggled', self.on_input) - self.actionbar.pack_start(button) - - button = gtkgui_helpers.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 = ( - '\n' - '\n' - '\n' - '\n' - '') - elif text == 'Message': - input_text = ( - '\n' - '\n' - '') - elif text == 'Iq': - input_text = ( - '\n' - '\n' - '') - - 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() == 'http://www.gajim.org/xmlns/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): - buffer_ = 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 = gtkgui_helpers.at_the_end(self.scrolled) - - buffer_ = self.textview.get_buffer() - end_iter = buffer_.get_end_iter() - - type_ = kind - if stanza.startswith('<', '>\n<')) - buffer_.insert_with_tags_by_name(end_iter, stanza, type_, kind) - - if at_the_end: - GLib.idle_add(gtkgui_helpers.scroll_to_end, self.scrolled) #Action that can be done with an incoming list of contacts TRANSLATED_ACTION = {'add': _('add'), 'modify': _('modify'), @@ -3871,532 +1326,6 @@ class RosterItemExchangeWindow: self.window.destroy() -class PrivacyListWindow: - """ - Window that is used for creating NEW or EDITING already there privacy lists - """ - - 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 = gtkgui_helpers.get_gtk_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 %s') % \ - 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( - _('Edit a rule')) - 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( - _('Add a rule')) - - 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 = gtkgui_helpers.get_gtk_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') - class InvitationReceivedDialog: def __init__(self, account, room_jid, contact_fjid, password=None, comment=None, is_continued=False): @@ -4614,6 +1543,51 @@ class TransformChatToMUC: str(randrange(9999999)) self._nec_unique_room_id_supported(obj) +class Dialog(Gtk.Dialog): + def __init__(self, parent, title, buttons, default=None, + on_response_ok=None, on_response_cancel=None): + Gtk.Dialog.__init__(self, title, parent, + Gtk.DialogFlags.DESTROY_WITH_PARENT) + + self.user_response_ok = on_response_ok + self.user_response_cancel = on_response_cancel + self.set_border_width(6) + self.get_content_area().set_spacing(12) + self.set_resizable(False) + + for stock, response in buttons: + b = self.add_button(stock, response) + + if default is not None: + self.set_default_response(default) + else: + self.set_default_response(buttons[-1][1]) + + self.connect('response', self.on_response) + + def on_response(self, widget, response_id): + if response_id == Gtk.ResponseType.OK: + 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.destroy() + elif response_id == Gtk.ResponseType.CANCEL: + 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.destroy() + + def just_destroy(self, widget): + self.destroy() + + def get_button(self, index): + buttons = self.get_action_area().get_children() + return index < len(buttons) and buttons[index] or None + class DataFormWindow(Dialog): def __init__(self, form, on_response_ok): self.df_response_ok = on_response_ok @@ -4792,66 +1766,6 @@ class VoIPCallReceivedDialog(object): dialog.destroy() -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, _('''Issued to: -Common Name (CN): %(scn)s -Organization (O): %(sorg)s -Organizationl Unit (OU): %(sou)s -Serial Number: %(sn)s - -Issued by: -Common Name (CN): %(icn)s -Organization (O): %(iorg)s -Organizationl Unit (OU): %(iou)s - -Validity: -Issued on: %(io)s -Expires on: %(eo)s - -Fingerprint -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')}) - pix = gtkgui_helpers.get_icon_pixmap('application-certificate', size=32, - quiet=True) - if pix: - img = Gtk.Image.new_from_pixbuf(pix) - 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): - d = CertificatDialog(self, self.account, self.cert) - class ProgressWindow(Gtk.ApplicationWindow): def __init__(self, file): Gtk.ApplicationWindow.__init__(self) diff --git a/gajim/disco.py b/gajim/disco.py index 9c7fb3062..d3fd47295 100644 --- a/gajim/disco.py +++ b/gajim/disco.py @@ -52,7 +52,8 @@ from gi.repository import Gdk from gi.repository import GdkPixbuf 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 groups from gajim import adhoc_commands @@ -526,7 +527,7 @@ class ServiceDiscoveryWindow(object): # Check connection 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')) 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: # We can't travel anywhere else. 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 ' 'not responding. Check the address and try again.'), transient_for=self.window) return klass = self.cache.get_browser(identities, features) 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.'), transient_for=self.window) return @@ -772,7 +773,7 @@ _('Without a connection, you can not browse available services')) jid = helpers.parse_jid(jid) except helpers.InvalidFormat as s: pritext = _('Invalid Server Name') - dialogs.ErrorDialog(pritext, str(s)) + ErrorDialog(pritext, str(s)) return self.travel(jid, '') @@ -782,7 +783,7 @@ _('Without a connection, you can not browse available services')) jid = helpers.parse_jid(jid) except helpers.InvalidFormat as s: pritext = _('Invalid Server Name') - dialogs.ErrorDialog(pritext, str(s), + ErrorDialog(pritext, str(s), transient_for=self.window) return if jid == self.jid: # jid has not changed @@ -1081,7 +1082,7 @@ class AgentBrowser: if not self.window.address_comboboxtext: # We can't travel anywhere else. 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.'), transient_for=self.window.window) return @@ -1772,7 +1773,7 @@ class MucBrowser(AgentBrowser): } if room_jid in con.get_module('Bookmarks').bookmarks: - dialogs.ErrorDialog( + ErrorDialog( _('Bookmark already set'), _('Group Chat "%s" is already in your bookmarks.') % room_jid, transient_for=self.window.window) @@ -1783,7 +1784,7 @@ class MucBrowser(AgentBrowser): gui_menu_builder.build_bookmark_menu(self.account) - dialogs.InformationDialog( + InformationDialog( _('Bookmark has been added successfully'), _('You can manage your bookmarks via Actions menu in your roster.'), transient_for=self.window.window) diff --git a/gajim/filetransfers_window.py b/gajim/filetransfers_window.py index 530971430..3329cc88c 100644 --- a/gajim/filetransfers_window.py +++ b/gajim/filetransfers_window.py @@ -36,7 +36,12 @@ from datetime import datetime from gajim import gtkgui_helpers 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 helpers @@ -247,7 +252,7 @@ class FileTransfersWindow: sectext += recipient if file_props.type_ == 'r': 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) if file_props.type_ == 'r': 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 """ - 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() def show_send_error(self, file_props): """ 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.')) self.tree.get_selection().unselect_all() @@ -283,7 +288,7 @@ class FileTransfersWindow: sectext += '\n\t' + _('Recipient: %s') % jid if 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() def show_hash_error(self, jid, file_props, account): @@ -318,7 +323,7 @@ class FileTransfersWindow: file_name = os.path.basename(file_props.file_name) else: 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 ' 'been damaged along the way.\nDo you want to download it again?') % \ {'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): pritext = _('Gajim can not read this file') sextext = _('Another process is using this file.') - dialogs.ErrorDialog(pritext, sextext) + ErrorDialog(pritext, sextext) return if isinstance(contact, str): @@ -378,7 +383,7 @@ class FileTransfersWindow: if not os.access(file_path, os.W_OK): file_name = GLib.markup_escape_text(os.path.basename( file_path)) - dialogs.ErrorDialog( + ErrorDialog( _('Cannot overwrite existing file "%s"' % file_name), _('A file with this name already exists and you do not ' 'have permission to overwrite it.')) @@ -395,7 +400,7 @@ class FileTransfersWindow: file_props.offset = dl_size self._start_receive(file_path, account, contact, file_props) - dialog = dialogs.FTOverwriteConfirmationDialog( + dialog = FTOverwriteConfirmationDialog( _('This file already exists'), _('What do you want to do?'), propose_resume=not dl_finished, on_response=on_response) dialog.set_destroy_with_parent(True) @@ -406,7 +411,7 @@ class FileTransfersWindow: # read-only bit is used to mark special folder under # windows, not to mark that a folder is read-only. # 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 ' 'in this directory.')) return @@ -445,7 +450,7 @@ class FileTransfersWindow: def on_response_cancel(account, 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_cancel=(on_response_cancel, account, file_props)) dialog.connect('delete-event', lambda widget, event: @@ -674,10 +679,10 @@ class FileTransfersWindow: if os.path.isfile(file_path): stat = os.stat(file_path) else: - dialogs.ErrorDialog(_('Invalid File'), _('File: ') + file_path) + ErrorDialog(_('Invalid File'), _('File: ') + file_path) return None if stat[6] == 0: - dialogs.ErrorDialog(_('Invalid File'), + ErrorDialog(_('Invalid File'), _('It is not possible to send empty files')) return None file_props = FilesProp.getNewFileProp(account, diff --git a/gajim/gajim_themes_window.py b/gajim/gajim_themes_window.py index 4dfe3909b..2cc22ad0c 100644 --- a/gajim/gajim_themes_window.py +++ b/gajim/gajim_themes_window.py @@ -25,15 +25,17 @@ from gi.repository import Gtk from gi.repository import Gdk from gi.repository import Pango -from gajim import dialogs -from gajim import gtkgui_helpers from gajim.common import app +from gajim.gtk import ErrorDialog +from gajim.gtk.util import get_builder + + class GajimThemesWindow: 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.set_transient_for(app.interface.instances[ 'preferences'].window) @@ -94,7 +96,7 @@ class GajimThemesWindow: if old_name == new_name: return if old_name == 'default': - dialogs.ErrorDialog( + ErrorDialog( _('You cannot make changes to the default theme'), _('Please create a new clean theme.')) return @@ -183,7 +185,7 @@ class GajimThemesWindow: if not iter_: return if self.current_theme == app.config.get('roster_theme'): - dialogs.ErrorDialog( + ErrorDialog( _('You cannot delete your current theme'), _('Pick another theme to use first.')) return diff --git a/gajim/groupchat_control.py b/gajim/groupchat_control.py index 61df955c7..e866fb85d 100644 --- a/gajim/groupchat_control.py +++ b/gajim/groupchat_control.py @@ -41,11 +41,11 @@ from gajim import gtkgui_helpers from gajim import gui_menu_builder from gajim import message_control from gajim import tooltips -from gajim import dialogs from gajim import config from gajim import vcard from gajim import dataforms_widget from gajim import adhoc_commands +from gajim.gtk import AddNewContactWindow from gajim.common.const import AvatarSize from gajim.common.caps_cache import muc_caps_cache import nbxmpp @@ -63,6 +63,12 @@ from gajim.common import contacts from gajim.chat_control import ChatControl from gajim.chat_control_base import ChatControlBase 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 GroupChatCommands @@ -239,7 +245,7 @@ class PrivateChatControl(ChatControl): room, nick = app.get_room_and_nick_from_fjid(self.contact.jid) gc_contact = app.contacts.get_gc_contact(self.account, room, nick) if not gc_contact: - dialogs.ErrorDialog( + ErrorDialog( _('Sending private message failed'), #in second %s code replaces with nickname _('You are no longer in group chat "%(room)s" or ' @@ -646,7 +652,7 @@ class GroupchatControl(ChatControlBase): app.connections[self.account].send_gc_subject( self.room_jid, subject) - dialogs.InputTextDialog(_('Changing Subject'), + InputTextDialog(_('Changing Subject'), _('Please specify the new subject:'), input_str=self.subject, ok_handler=on_ok, transient_for=self.parent_win.window) @@ -657,7 +663,7 @@ class GroupchatControl(ChatControlBase): title = _('Changing Nickname') prompt = _('Please specify the new nickname you want to use:') 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) def _on_disconnect(self, action, param): @@ -672,7 +678,7 @@ class GroupchatControl(ChatControlBase): try: jid = helpers.parse_jid(jid) except Exception: - dialogs.ErrorDialog(_('Invalid group chat JID'), + ErrorDialog(_('Invalid group chat JID'), _('The group chat JID has not allowed characters.')) return app.connections[self.account].destroy_gc_room( @@ -683,7 +689,7 @@ class GroupchatControl(ChatControlBase): self.force_non_minimizable = False # Ask for a reason - dialogs.DoubleInputDialog(_('Destroying %s') % '\u200E' + \ + DoubleInputDialog(_('Destroying %s') % '\u200E' + \ self.room_jid, _('You are going to remove this room permanently.' '\nYou may specify a reason below:'), _('You may also enter an alternate venue:'), ok_handler=on_ok, @@ -751,7 +757,7 @@ class GroupchatControl(ChatControlBase): def _on_accept(filename): sha = app.interface.save_avatar(filename, publish=True) if sha is None: - dialogs.ErrorDialog( + ErrorDialog( _('Could not load image'), transient_for=self.parent_win.window) return @@ -2343,7 +2349,7 @@ class GroupchatControl(ChatControlBase): sectext = _('If you close this window, you will be disconnected ' 'from this group chat.') - dialogs.ConfirmationDialogCheck(pritext, sectext, + ConfirmationDialogCheck(pritext, sectext, _('_Do not ask me again'), on_response_ok=on_ok, on_response_cancel=on_cancel, transient_for=self.parent_win.window) @@ -2379,7 +2385,7 @@ class GroupchatControl(ChatControlBase): app.connections[self.account].send_gc_subject(self.room_jid, subject) - dialogs.InputTextDialog(_('Changing Subject'), + InputTextDialog(_('Changing Subject'), _('Please specify the new subject:'), input_str=self.subject, ok_handler=on_ok, transient_for=self.parent_win.window) @@ -2560,7 +2566,7 @@ class GroupchatControl(ChatControlBase): 'none', reason) # ask for reason - dialogs.InputDialog(_('Kicking %s') % nick, + InputDialog(_('Kicking %s') % nick, _('You may specify a reason below:'), ok_handler=on_ok, 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 nick = app.get_nick_from_jid(jid) # ask for reason - dialogs.InputDialog(_('Banning %s') % nick, + InputDialog(_('Banning %s') % nick, _('You may specify a reason below:'), ok_handler=on_ok, transient_for=self.parent_win.window) @@ -2922,7 +2928,7 @@ class GroupchatControl(ChatControlBase): self._on_history_menuitem_activate(widget=widget, jid=jid) def on_add_to_roster(self, widget, jid): - dialogs.AddNewContactWindow(self.account, jid) + AddNewContactWindow(self.account, jid) def on_block(self, widget, nick): fjid = self.room_jid + '/' + nick diff --git a/gajim/gtk/__init__.py b/gajim/gtk/__init__.py index e69de29bb..9ddae92c9 100644 --- a/gajim/gtk/__init__.py +++ b/gajim/gtk/__init__.py @@ -0,0 +1,56 @@ +# Copyright (C) 2003-2005 Vincent Hanquez +# Copyright (C) 2003-2014 Yann Leboulanger +# Copyright (C) 2005 Alex Mauer +# Copyright (C) 2005-2006 Dimitur Kirov +# Travis Shirk +# Copyright (C) 2005-2008 Nikos Kouremenos +# Copyright (C) 2006-2008 Jean-Marie Traissard +# Copyright (C) 2007 Lukas Petrovicky +# Copyright (C) 2007-2008 Brendan Taylor +# Julien Pivotto +# Stephan Erb +# Copyright (C) 2008 Jonathan Schleifer +# Copyright (C) 2018 Philipp Hörist +# +# 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 . + +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 diff --git a/gajim/gtk/about.py b/gajim/gtk/about.py new file mode 100644 index 000000000..5f12a6afd --- /dev/null +++ b/gajim/gtk/about.py @@ -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 . + +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() diff --git a/gajim/gtk/add_contact.py b/gajim/gtk/add_contact.py new file mode 100644 index 000000000..f094f07e7 --- /dev/null +++ b/gajim/gtk/add_contact.py @@ -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 . + +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() diff --git a/gajim/gtk/dialogs.py b/gajim/gtk/dialogs.py new file mode 100644 index 000000000..731fb7b4c --- /dev/null +++ b/gajim/gtk/dialogs.py @@ -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 . + +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, _('''Issued to: +Common Name (CN): %(scn)s +Organization (O): %(sorg)s +Organizationl Unit (OU): %(sou)s +Serial Number: %(sn)s + +Issued by: +Common Name (CN): %(icn)s +Organization (O): %(iorg)s +Organizationl Unit (OU): %(iou)s + +Validity: +Issued on: %(io)s +Expires on: %(eo)s + +Fingerprint +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) diff --git a/gajim/gtk/history_sync.py b/gajim/gtk/history_sync.py index 09e0db981..918da6cad 100644 --- a/gajim/gtk/history_sync.py +++ b/gajim/gtk/history_sync.py @@ -1,5 +1,3 @@ -# Copyright (C) 2017 Philipp Hörist -# # This file is part of Gajim. # # 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.gtk.util import load_icon -log = logging.getLogger('gajim.c.message_archiving') +log = logging.getLogger('gajim.gtk.history_sync') class Pages(IntEnum): diff --git a/gajim/gtk/join_groupchat.py b/gajim/gtk/join_groupchat.py new file mode 100644 index 000000000..bb4072a33 --- /dev/null +++ b/gajim/gtk/join_groupchat.py @@ -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 . + +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 diff --git a/gajim/gtk/mam_preferences.py b/gajim/gtk/mam_preferences.py index b19f35c3a..cb461ca9f 100644 --- a/gajim/gtk/mam_preferences.py +++ b/gajim/gtk/mam_preferences.py @@ -21,7 +21,7 @@ from gajim.common import app from gajim.common import ged from gajim.gtk.util import get_builder -from gajim.dialogs import HigDialog +from gajim.gtk import HigDialog log = logging.getLogger('gajim.gtk.mam_preferences') diff --git a/gajim/gtk/privacy_list.py b/gajim/gtk/privacy_list.py new file mode 100644 index 000000000..d76ea61d1 --- /dev/null +++ b/gajim/gtk/privacy_list.py @@ -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 . + +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 %s') % + 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(_('Edit a rule')) + 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(_('Add a rule')) + + 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') diff --git a/gajim/server_info.py b/gajim/gtk/server_info.py similarity index 91% rename from gajim/server_info.py rename to gajim/gtk/server_info.py index d88904acc..152c961be 100644 --- a/gajim/server_info.py +++ b/gajim/gtk/server_info.py @@ -1,34 +1,28 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2017 Philipp Hörist -# # 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, either version 3 of the License, or -# (at your option) any later version. +# 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 +# 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 . +import logging from collections import namedtuple from datetime import timedelta -import logging -from gi.repository import Gtk import nbxmpp +from gi.repository import Gtk from gajim.common import app 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): @@ -221,16 +215,18 @@ class FeatureItem(Gtk.Grid): def set_feature(self, available, enabled): if not available: - self.icon.set_from_pixbuf( - get_icon_pixmap('window-close-symbolic', color=[Color.RED])) + self.icon.set_from_icon_name('window-close-symbolic', + Gtk.IconSize.MENU) + self.icon.get_style_context().add_class('error-color') elif enabled is False: - self.icon.set_from_pixbuf( - get_icon_pixmap('dialog-warning-symbolic', - color=[Color.ORANGE])) + self.icon.set_from_icon_name('dialog-warning-symbolic', + Gtk.IconSize.MENU) self.tooltip += _('\nDisabled in config') + self.icon.get_style_context().add_class('warning-color') else: - self.icon.set_from_pixbuf( - get_icon_pixmap('emblem-ok-symbolic', color=[Color.GREEN])) + self.icon.set_from_icon_name('emblem-ok-symbolic', + Gtk.IconSize.MENU) + self.icon.get_style_context().add_class('success-color') def update(self, feature): self.tooltip = feature.tooltip diff --git a/gajim/gtk/single_message.py b/gajim/gtk/single_message.py new file mode 100644 index 000000000..a10efcc3f --- /dev/null +++ b/gajim/gtk/single_message.py @@ -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 . + +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() diff --git a/gajim/gtk/start_chat.py b/gajim/gtk/start_chat.py new file mode 100644 index 000000000..e85ac26f8 --- /dev/null +++ b/gajim/gtk/start_chat.py @@ -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 . + +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) diff --git a/gajim/gtk/util.py b/gajim/gtk/util.py index c736f0f24..e127598fc 100644 --- a/gajim/gtk/util.py +++ b/gajim/gtk/util.py @@ -15,11 +15,13 @@ import os import sys import logging - -from gi.repository import Gtk -from gi.repository import GLib 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 configpaths @@ -29,7 +31,7 @@ _icon_theme.append_search_path(configpaths.get('ICONS')) 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): scale = widget.get_scale_factor() @@ -40,6 +42,8 @@ def load_icon(icon_name, widget, size=16, try: iconinfo = _icon_theme.lookup_icon_for_scale( icon_name, size, scale, flags) + if pixbuf: + return iconinfo.load_icon() return iconinfo.load_surface(None) except GLib.GError as 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]) return builder 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 diff --git a/gajim/gtk/xml_console.py b/gajim/gtk/xml_console.py new file mode 100644 index 000000000..6f1700e01 --- /dev/null +++ b/gajim/gtk/xml_console.py @@ -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 . + +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 = ( + '\n' + '\n' + '\n' + '\n' + '') + elif text == 'Message': + input_text = ( + '\n' + '\n' + '') + elif text == 'Iq': + input_text = ( + '\n' + '\n' + '') + + 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('<', '>\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) diff --git a/gajim/gtkgui_helpers.py b/gajim/gtkgui_helpers.py index 108577f49..be73370c3 100644 --- a/gajim/gtkgui_helpers.py +++ b/gajim/gtkgui_helpers.py @@ -479,7 +479,9 @@ def scale_pixbuf_from_data(data, size): return scale_pixbuf(pixbuf, size) 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): if response < 0: 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' def on_ok(file_path, pixbuf): pixbuf.savev(file_path, 'png', [], []) - dialogs.ConfirmationDialog(_('Extension not supported'), + ConfirmationDialog(_('Extension not supported'), _('Image cannot be saved in %(type)s format. Save as ' '%(new_filename)s?') % {'type': image_format, '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 if not os.access(file_path, os.W_OK): 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 ' 'do not have permission to overwrite it.')) return - dialog2 = dialogs.FTOverwriteConfirmationDialog( + dialog2 = FTOverwriteConfirmationDialog( _('This file already exists'), _('What do you want to do?'), propose_resume=False, on_response=(on_continue, file_path)) dialog2.set_destroy_with_parent(True) else: dirname = os.path.dirname(file_path) 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 ' 'this directory.')) return diff --git a/gajim/gui_interface.py b/gajim/gui_interface.py index 7896307ed..2d37176db 100644 --- a/gajim/gui_interface.py +++ b/gajim/gui_interface.py @@ -69,6 +69,7 @@ from gajim import notify from gajim import message_control from gajim.dialog_messages import get_dialog from gajim.dialogs import ProgressWindow + from gajim.filechoosers import FileChooserDialog from gajim.chat_control_base import ChatControlBase @@ -111,6 +112,18 @@ from threading import Thread from gajim.common import ged 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 optparser @@ -129,7 +142,7 @@ class Interface: #('DB_ERROR', account, error) if self.db_error_dialog: return - self.db_error_dialog = dialogs.ErrorDialog(_('Database Error'), error) + self.db_error_dialog = ErrorDialog(_('Database Error'), error) def destroyed(win): self.db_error_dialog = None self.db_error_dialog.connect('destroy', destroyed) @@ -144,11 +157,11 @@ class Interface: return if obj.level == 'error': - cls = dialogs.ErrorDialog + cls = ErrorDialog elif obj.level == 'warn': - cls = dialogs.WarningDialog + cls = WarningDialog elif obj.level == 'info': - cls = dialogs.InformationDialog + cls = InformationDialog else: return @@ -169,7 +182,7 @@ class Interface: self.instances['change_nick_dialog'].add_room(account, room_jid, prompt) else: - self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog( + self.instances['change_nick_dialog'] = ChangeNickDialog( account, room_jid, title, prompt, transient_for=parent_win) @staticmethod @@ -188,7 +201,7 @@ class Interface: sec_msg = _('Do you accept this request on account %s?') % account if obj.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, 'id': obj.iq_id}, sec_msg, on_response_yes=(on_yes, obj), on_response_no=(response, obj, 'no')) @@ -285,14 +298,14 @@ class Interface: def on_close(dummy): gc_control.error_dialog.destroy() 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) gc_control.error_dialog.set_modal(False) if gc_control.parent_win: gc_control.error_dialog.set_transient_for( gc_control.parent_win.window) else: - d = dialogs.ErrorDialog(pritext, sectext) + d = ErrorDialog(pritext, sectext) if gc_control and gc_control.parent_win: d.set_transient_for(gc_control.parent_win.window) d.set_modal(False) @@ -318,7 +331,7 @@ class Interface: if gc_control.error_dialog: 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.') % \ room_jid, is_modal=False, ok_handler=on_ok, cancel_handler=on_cancel) @@ -530,7 +543,7 @@ class Interface: status='online', ask='to', resource=obj.resource, keyID=keyID) app.contacts.add_contact(account, contact1) self.roster.add_contact(obj.jid, account) - dialogs.InformationDialog(_('Authorization accepted'), + InformationDialog(_('Authorization accepted'), _('The contact "%s" has authorized you to see their status.') % obj.jid) @@ -538,7 +551,7 @@ class Interface: def on_yes(is_checked, list_): self.roster.on_req_usub(None, list_) list_ = [(contact, account)] - dialogs.YesNoDialog( + YesNoDialog( _('Contact "%s" removed subscription from you') % contact.jid, _('You will always see them as offline.\nDo you want to ' 'remove them from your contact list?'), @@ -575,7 +588,7 @@ class Interface: config.ServiceRegistrationWindow(obj.agent, obj.config, obj.conn.name, obj.is_form) 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.')) def handle_event_gc_config(self, obj): @@ -673,7 +686,7 @@ class Interface: '\n') sectext += _('You are currently connected without your OpenPGP ' 'key.') - dialogs.WarningDialog(_('Wrong passphrase'), sectext) + WarningDialog(_('Wrong passphrase'), sectext) else: account = obj.conn.name app.notification.popup( @@ -716,7 +729,7 @@ class Interface: def on_no(): 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 ' 'encrypt this message?'), checktext=_('_Do not ask me again'), 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' ' that has just been opened.') - self.pass_dialog[account] = dialogs.InputTextDialog( + self.pass_dialog[account] = InputTextDialog( _('Oauth2 Credentials'), instruction, is_modal=False, ok_handler=on_ok, cancel_handler=on_cancel) @@ -917,7 +930,7 @@ class Interface: @staticmethod def handle_event_file_error(title, message): - dialogs.ErrorDialog(title, message) + ErrorDialog(title, message) def handle_event_file_progress(self, account, file_props): if time.time() - self.last_ftwindow_update > 0.5: @@ -1185,7 +1198,7 @@ class Interface: def on_cancel(): obj.conn.change_status('offline', '') - dlg = dialogs.InputDialog(_('Username Conflict'), + dlg = InputDialog(_('Username Conflict'), _('Please type a new username for your local account'), input_str=obj.alt_name, is_modal=True, ok_handler=on_ok, 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: certs = f.read() 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 ' 'not added again.') % my_ca_certs) else: @@ -1384,7 +1397,7 @@ class Interface: if 'ssl_error' in self.instances[account]['online_dialog']: self.instances[account]['online_dialog']['ssl_error'].destroy() 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, on_response_cancel=on_cancel) self.instances[account]['online_dialog']['ssl_error'].set_title( @@ -1393,7 +1406,7 @@ class Interface: def handle_event_non_anonymous_server(self, obj): account = obj.conn.name 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, transient_for=self.roster.window) @@ -1426,7 +1439,7 @@ class Interface: self.instances[obj.conn.name]['online_dialog']['plain_connection'].\ destroy() 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): # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type)) @@ -1464,7 +1477,7 @@ class Interface: self.instances[obj.conn.name]['online_dialog']['insecure_ssl'].\ destroy() 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, is_modal=False) @@ -1506,7 +1519,7 @@ class Interface: self.instances[obj.conn.name]['online_dialog']\ ['insecure_password'].destroy() 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, is_modal=False) @@ -1749,7 +1762,7 @@ class Interface: try: room_jid = helpers.parse_jid(room_jid) except helpers.InvalidFormat: - dialogs.ErrorDialog('Invalid JID', + ErrorDialog('Invalid JID', transient_for=app.app.get_active_window()) return @@ -1757,7 +1770,7 @@ class Interface: if account is not None and account not in connected_accounts: connected_accounts = None if not connected_accounts: - dialogs.ErrorDialog( + ErrorDialog( _('You are not connected to the server'), _('You can not join a group chat unless you are connected.'), transient_for=app.app.get_active_window()) @@ -1765,10 +1778,10 @@ class Interface: def _on_discover_result(): 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()) return - dialogs.JoinGroupchatWindow(account, room_jid, password=password, + JoinGroupchatWindow(account, room_jid, password=password, transient_for=transient_for) 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) if not emoticons.load(path, ascii_emoticons): - dialogs.WarningDialog( + WarningDialog( _('Emoticons disabled'), _('Your configured emoticons theme could not be loaded.' ' See the log for more details.'), @@ -1948,7 +1961,7 @@ class Interface: if app.contacts.get_contact(account, room_jid) and \ 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 ' 'if %(room_jid)s is a correct group chat name. If it is, ' 'delete it from your roster and try joining the group chat ' @@ -1973,7 +1986,7 @@ class Interface: invisible_show = app.SHOW_LIST.index('invisible') if app.connections[account].connected == invisible_show: - dialogs.ErrorDialog( + ErrorDialog( _('You cannot join a group chat while you are invisible')) return @@ -2331,7 +2344,7 @@ class Interface: print(err_str, file=sys.stderr) # it is good to notify the user # 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) error_dialog.run() sys.exit() @@ -2923,7 +2936,7 @@ class PassphraseRequest: self.complete(passphrase) return elif result == 'expired': - dialogs.ErrorDialog(_('OpenPGP key expired'), + ErrorDialog(_('OpenPGP key expired'), _('Your OpenPGP key has expired, you will be connected to ' '%s without OpenPGP.') % account) # Don't try to connect with GPG diff --git a/gajim/history_manager.py b/gajim/history_manager.py index b603a68c9..76ca8dad9 100644 --- a/gajim/history_manager.py +++ b/gajim/history_manager.py @@ -85,7 +85,9 @@ if is_standalone(): from gajim.common import app from gajim.common.const import JIDConstant, KindConstant 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 import gtkgui_helpers @@ -111,8 +113,8 @@ class HistoryManager: log_db_path = configpaths.get('LOG_DB') if not os.path.exists(log_db_path): - dialogs.ErrorDialog(_('Cannot find history logs database'), - '%s does not exist.' % log_db_path) + ErrorDialog(_('Cannot find history logs database'), + '%s does not exist.' % log_db_path) sys.exit() xml = gtkgui_helpers.get_gtk_builder('history_manager.ui') @@ -250,7 +252,7 @@ class HistoryManager: if is_standalone(): Gtk.main_quit() - dialog = dialogs.YesNoDialog( + dialog = YesNoDialog( _('Do you want to clean up the database? ' '(STRONGLY NOT RECOMMENDED IF GAJIM IS RUNNING)'), _('Normally allocated database size will not be freed, ' @@ -581,7 +583,7 @@ class HistoryManager: else: pri_text = _( '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, liststore, list_of_paths)) dialog.set_title(_('Deletion Confirmation')) @@ -620,7 +622,7 @@ class HistoryManager: pri_text = i18n.ngettext( 'Do you really want to delete the selected message?', '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, liststore, list_of_paths)) dialog.set_title(_('Deletion Confirmation')) diff --git a/gajim/history_window.py b/gajim/history_window.py index 3e68bd389..173870cf8 100644 --- a/gajim/history_window.py +++ b/gajim/history_window.py @@ -35,7 +35,7 @@ from enum import IntEnum, unique from gajim import gtkgui_helpers from gajim import conversation_textview -from gajim import dialogs +from gajim.gtk import ErrorDialog from gajim.common import app from gajim.common import helpers @@ -386,7 +386,7 @@ class HistoryWindow: log_days = app.logger.get_days_with_logs( self.account, self.jid, year, month) except exceptions.PysqliteOperationalError as e: - dialogs.ErrorDialog(_('Disk Error'), str(e)) + ErrorDialog(_('Disk Error'), str(e)) return for date in log_days: @@ -439,7 +439,7 @@ class HistoryWindow: logs = app.logger.get_date_has_logs( self.account, self.jid, _date) except exceptions.PysqliteOperationalError as e: - dialogs.ErrorDialog(_('Disk Error'), str(e)) + ErrorDialog(_('Disk Error'), str(e)) return gtk_month = gtkgui_helpers.make_python_month_gtk_month(_date.month) diff --git a/gajim/htmltextview.py b/gajim/htmltextview.py index 3332f7b13..1958a01e0 100644 --- a/gajim/htmltextview.py +++ b/gajim/htmltextview.py @@ -51,10 +51,12 @@ if __name__ == '__main__': from gajim.common import configpaths configpaths.init() from gajim.common import app -from gajim import gtkgui_helpers -from gajim.gtkgui_helpers import get_icon_pixmap +from gajim.gtk.util import load_icon +from gajim.gtk.util import get_cursor +from gajim.gtk.util import get_builder from gajim.common import helpers -from gajim import dialogs +from gajim.gtk import JoinGroupchatWindow +from gajim.gtk import AddNewContactWindow import logging log = logging.getLogger('gajim.htmlview') @@ -554,7 +556,7 @@ class HtmlHandler(xml.sax.handler.ContentHandler): if alt: alt += '\n' alt += _('Loading') - pixbuf = get_icon_pixmap('gtk-no') + pixbuf = load_icon('image-missing', self.textview, pixbuf=True) if mem: # Caveat: GdkPixbuf is known not to be safe to load # images from network... this program is now potentially @@ -889,11 +891,11 @@ class HtmlTextView(Gtk.TextView): text = text[:47] + '…' tooltip.set_text(text) if not self._changed_cursor: - window.set_cursor(gtkgui_helpers.get_cursor('HAND2')) + window.set_cursor(get_cursor('HAND2')) self._changed_cursor = True return True if self._changed_cursor: - window.set_cursor(gtkgui_helpers.get_cursor('XTERM')) + window.set_cursor(get_cursor('XTERM')) self._changed_cursor = False return False @@ -908,14 +910,13 @@ class HtmlTextView(Gtk.TextView): # app.interface.new_chat_from_jid(self.account, 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): - dialogs.AddNewContactWindow(self.account, jid) + AddNewContactWindow(self.account, jid) def make_link_menu(self, event, kind, text): - from gajim.gtkgui_helpers import get_gtk_builder - xml = get_gtk_builder('chat_context_menu.ui') + xml = get_builder('chat_context_menu.ui') menu = xml.get_object('chat_context_menu') childs = menu.get_children() if kind == 'url': @@ -1114,13 +1115,13 @@ if __name__ == '__main__': y = pointer[2] tags = htmlview.tv.get_iter_at_location(x, y)[1].get_tags() if change_cursor: - w.set_cursor(gtkgui_helpers.get_cursor('XTERM')) + w.set_cursor(get_cursor('XTERM')) change_cursor = None tag_table = htmlview.tv.get_buffer().get_tag_table() for tag in tags: try: if tag.is_anchor: - w.set_cursor(gtkgui_helpers.get_cursor('HAND2')) + w.set_cursor(get_cursor('HAND2')) change_cursor = tag elif tag == tag_table.lookup('focus-out-line'): over_line = True diff --git a/gajim/message_window.py b/gajim/message_window.py index e91882f75..cc417ce31 100644 --- a/gajim/message_window.py +++ b/gajim/message_window.py @@ -37,7 +37,7 @@ from gi.repository import GLib from gajim import common from gajim import gtkgui_helpers 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 import ChatControl from gajim.common import app @@ -240,7 +240,7 @@ class MessageWindow(object): ctrl.minimize() # destroy window return False - dialogs.YesNoDialog( + YesNoDialog( _('You are going to close several tabs'), _('Do you really want to close them all?'), checktext=_('_Do not ask me again'), on_response_yes=on_yes1, diff --git a/gajim/options_dialog.py b/gajim/options_dialog.py index a7124bec1..a5e66ab5b 100644 --- a/gajim/options_dialog.py +++ b/gajim/options_dialog.py @@ -5,7 +5,7 @@ from gajim import gtkgui_helpers from gajim.common.const import OptionKind, OptionType from gajim.common.exceptions import GajimGeneralException from gajim import dialogs - +from gajim.gtk import ErrorDialog class OptionsDialog(Gtk.ApplicationWindow): def __init__(self, parent, title, flags, options, account, @@ -558,7 +558,7 @@ class GPGOption(DialogOption): secret_keys[_('None')] = _('None') if not secret_keys: - dialogs.ErrorDialog( + ErrorDialog( _('Failed to get secret keys'), _('There is no OpenPGP secret key available.'), transient_for=parent) diff --git a/gajim/plugins/gui.py b/gajim/plugins/gui.py index 5df3b3b38..70a0b290d 100644 --- a/gajim/plugins/gui.py +++ b/gajim/plugins/gui.py @@ -26,16 +26,16 @@ GUI classes related to plug-in management. __all__ = ['PluginsWindow'] -from gi.repository import Pango from gi.repository import Gtk from gi.repository import GdkPixbuf -from gi.repository import GLib, Gdk +from gi.repository import Gdk import os from enum import IntEnum, unique 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.common import app from gajim.common import configpaths @@ -44,6 +44,7 @@ from gajim.plugins.helpers import GajimPluginActivateException from gajim.plugins.plugins_i18n import _ from gajim.common.exceptions import PluginsystemError + @unique class Column(IntEnum): PLUGIN = 0 diff --git a/gajim/profile_window.py b/gajim/profile_window.py index ca6a595a8..088914d3c 100644 --- a/gajim/profile_window.py +++ b/gajim/profile_window.py @@ -30,7 +30,8 @@ from gi.repository import Gdk from gi.repository import GLib 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.common.const import AvatarSize from gajim.common import app @@ -127,7 +128,7 @@ class ProfileWindow(Gtk.ApplicationWindow): def on_ok(path_to_file): sha = app.interface.save_avatar(path_to_file, publish=True) if sha is None: - dialogs.ErrorDialog( + ErrorDialog( _('Could not load image'), transient_for=self) return @@ -181,7 +182,7 @@ class ProfileWindow(Gtk.ApplicationWindow): except ValueError: if not widget.is_focus(): 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) GLib.idle_add(lambda: widget.grab_focus()) return True @@ -321,7 +322,7 @@ class ProfileWindow(Gtk.ApplicationWindow): # Operation in progress return 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 ' 'information.'), transient_for=self) return @@ -363,7 +364,7 @@ class ProfileWindow(Gtk.ApplicationWindow): GLib.source_remove(self.update_progressbar_timeout_id) self.progressbar.set_fraction(0) self.update_progressbar_timeout_id = None - dialogs.InformationDialog(_('vCard publication failed'), + InformationDialog(_('vCard publication failed'), _('There was an error while publishing your personal information, ' 'try again later.'), transient_for=self) diff --git a/gajim/remote_control.py b/gajim/remote_control.py index 7fb8bead1..9582f4c62 100644 --- a/gajim/remote_control.py +++ b/gajim/remote_control.py @@ -33,7 +33,7 @@ import mimetypes from gajim.common import app from gajim.common import helpers -from gajim.dialogs import AddNewContactWindow +from gajim.gtk import AddNewContactWindow from gajim.common import ged from gajim.common.connection_handlers_events import MessageOutgoingEvent from gajim.common.connection_handlers_events import GcMessageOutgoingEvent diff --git a/gajim/roster_window.py b/gajim/roster_window.py index 58c0aa6f7..e6779481a 100644 --- a/gajim/roster_window.py +++ b/gajim/roster_window.py @@ -58,6 +58,17 @@ from gajim import cell_renderer_image from gajim import tooltips from gajim import message_control 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 import app @@ -1952,7 +1963,7 @@ class RosterWindow: ft = app.interface.instances['file_transfers'] event = app.events.get_first_event(account, jid, event.type_) if event.type_ == 'normal': - dialogs.SingleMessageWindow(account, jid, + SingleMessageWindow(account, jid, action='receive', from_whom=jid, subject=event.subject, message=event.message, resource=event.resource, session=event.session, form_node=event.form_node) @@ -2025,7 +2036,7 @@ class RosterWindow: Authorize a contact (by re-sending auth menuitem) """ app.connections[account].send_authorization(jid) - dialogs.InformationDialog(_('Authorization sent'), + InformationDialog(_('Authorization sent'), _('"%s" will now see your status.') %jid) def req_sub(self, widget, jid, txt, account, groups=None, nickname=None, @@ -2049,7 +2060,7 @@ class RosterWindow: app.contacts.add_contact(account, contact) else: 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 ' 'or her status.') % jid) return @@ -2064,7 +2075,7 @@ class RosterWindow: Revoke a contact's authorization """ app.connections[account].refuse_authorization(jid) - dialogs.InformationDialog(_('Authorization removed'), + InformationDialog(_('Authorization removed'), _('Now "%s" will always see you as offline.') %jid) def set_state(self, account, state): @@ -2089,7 +2100,7 @@ class RosterWindow: keyid = app.config.get_per('accounts', account, 'keyid') 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' 'Beware there is an incompatible Python package called gnupg.\n' 'You will be connected to %s without OpenPGP.') % account) @@ -2298,7 +2309,7 @@ class RosterWindow: self.get_status_message(status, on_response) if status == 'invisible' and self.connected_rooms(account): - dialogs.ConfirmationDialog( + ConfirmationDialog( _('You are participating in one or more group chats'), _('Changing your status to invisible will result in ' 'disconnection from those group chats. Are you sure you want ' @@ -2398,7 +2409,7 @@ class RosterWindow: if checked: app.config.set('quit_on_roster_x_button', True) self.on_quit_request() - dialogs.ConfirmationDialogCheck(_('Really quit Gajim?'), + ConfirmationDialogCheck(_('Really quit Gajim?'), _('Are you sure you want to quit Gajim?'), _('Always close Gajim'), on_response_ok=on_ok) return True # do NOT destroy the window @@ -2488,7 +2499,7 @@ class RosterWindow: break 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 ' 'be stopped. Do you still want to quit?'), on_response_ok=(on_continue3, message, pep_dict)) @@ -2520,7 +2531,7 @@ class RosterWindow: break if unread or recent: - dialogs.ConfirmationDialog(_('You have unread messages'), + ConfirmationDialog(_('You have unread messages'), _('Messages will only be available for reading them later ' 'if you have history enabled and contact is in your ' 'roster.'), on_response_ok=(on_continue2, @@ -2694,7 +2705,7 @@ class RosterWindow: return if obj.mtype == 'normal' and obj.popup: # 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, message=obj.msgtxt, resource=obj.resource, session=obj.session, form_node=obj.form_node) @@ -2798,7 +2809,7 @@ class RosterWindow: has_unread_events = True break if has_unread_events: - dialogs.ErrorDialog(_('You have unread messages'), + ErrorDialog(_('You have unread messages'), _('You must read them before removing this transport.')) return if len(list_) == 1: @@ -2813,7 +2824,7 @@ class RosterWindow: jids = jids[:-1] + '.' sectext = _('You will no longer be able to send and receive ' 'messages to contacts from these transports: %s') % jids - dialogs.ConfirmationDialog(pritext, sectext, + ConfirmationDialog(pritext, sectext, on_response_ok = (remove, list_), transient_for=self.window) def _nec_blocking(self, obj): @@ -2864,7 +2875,7 @@ class RosterWindow: ' to continue?') sectext = _('This contact will see you offline and you will not ' 'receive messages it sends you.') - dialogs.ConfirmationDialogCheck(pritext, sectext, + ConfirmationDialogCheck(pritext, sectext, _('_Do not ask me again'), on_response_ok=_block_it) def on_unblock(self, widget, list_, group=None): @@ -2943,7 +2954,7 @@ class RosterWindow: if 'rename' in app.interface.instances: 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, old_text), on_canceled, transient_for=self.window) @@ -2958,7 +2969,7 @@ class RosterWindow: app.connections[account].unsubscribe(contact.jid) 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, _('Also remove all contacts in this group from your roster'), on_response_ok=on_ok) @@ -3039,14 +3050,14 @@ class RosterWindow: def on_send_single_message_menuitem_activate(self, widget, account, contact=None): if contact is None: - dialogs.SingleMessageWindow(account, action='send') + SingleMessageWindow(account, action='send') elif isinstance(contact, list): - dialogs.SingleMessageWindow(account, contact, 'send') + SingleMessageWindow(account, contact, 'send') else: jid = contact.jid if contact.jid == app.get_jid_from_account(account): jid += '/' + contact.resource - dialogs.SingleMessageWindow(account, jid, 'send') + SingleMessageWindow(account, jid, 'send') def on_send_file_menuitem_activate(self, widget, contact, account, resource=None): @@ -3077,7 +3088,7 @@ class RosterWindow: app.interface.instances[account]['join_gc'].destroy() else: app.interface.instances[account]['join_gc'] = \ - dialogs.JoinGroupchatWindow( + JoinGroupchatWindow( account, None, automatic={'invities': jid_list}) break @@ -3147,7 +3158,7 @@ class RosterWindow: dialogs.ChangeStatusMessageDialog(on_response, show) 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): """ @@ -3410,17 +3421,17 @@ class RosterWindow: 'your roster.\n') % {'name': contact.get_shown_name(), 'jid': contact.jid} if contact.sub == 'to': - dialogs.ConfirmationDialog(pritext, sectext + \ + ConfirmationDialog(pritext, sectext + \ _('By removing this contact you also remove authorization ' 'resulting in them always seeing you as offline.'), on_response_ok=(on_ok2, list_)) elif _('Not in Roster') in contact.get_shown_groups(): # Contact is not in roster - dialogs.ConfirmationDialog(pritext, sectext + \ + ConfirmationDialog(pritext, sectext + \ _('Do you want to continue?'), on_response_ok=(on_ok2, list_)) else: - dialogs.ConfirmationDialogCheck(pritext, sectext + \ + ConfirmationDialogCheck(pritext, sectext + \ _('By removing this contact you also by default remove ' 'authorization resulting in them always seeing you as' ' offline.'), @@ -3436,7 +3447,7 @@ class RosterWindow: sectext = _('By removing these contacts:%s\nyou also remove ' 'authorization resulting in them always seeing you as ' 'offline.') % jids - dialogs.ConfirmationDialog(pritext, sectext, + ConfirmationDialog(pritext, sectext, on_response_ok=(on_ok2, list_)) 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, ' 'but only until you change your status. Then they will see ' 'your global status.') % {'status': show} - dialogs.ConfirmationDialogCheck(pritext, sectext, + ConfirmationDialogCheck(pritext, sectext, _('_Do not ask me again'), on_response_ok=send_it) def on_status_combobox_changed(self, widget): @@ -3513,7 +3524,7 @@ class RosterWindow: return accounts = list(app.connections.keys()) if len(accounts) == 0: - dialogs.ErrorDialog(_('No account available'), + ErrorDialog(_('No account available'), _('You must create an account before you can chat with other ' 'contacts.')) self.update_status_combobox() @@ -3593,7 +3604,7 @@ class RosterWindow: def on_cancel(): self.update_status_combobox() - dialogs.ConfirmationDialog( + ConfirmationDialog( _('You are participating in one or more group chats'), _('Changing your status to invisible will result in ' 'disconnection from those group chats. Are you sure you ' @@ -3637,7 +3648,7 @@ class RosterWindow: config.ManagePEPServicesWindow(account) def on_add_new_contact(self, widget, account): - dialogs.AddNewContactWindow(account) + AddNewContactWindow(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 \ 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 does not support storing metacontacts ' '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 ' 'line. Generally it is used when the same person has several ' 'XMPP- or transport -accounts.') - dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, + dlg = ConfirmationDialogCheck(pritext, sectext, _('_Do not ask me again'), on_response_ok=merge_contacts) if not confirm_metacontacts: # First time we see this window dlg.checkbutton.set_active(True) @@ -4362,7 +4373,7 @@ class RosterWindow: if not os.path.isfile(path): bad_uris.append(a_uri) if len(bad_uris): - dialogs.ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris)) + ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris)) return def _on_send_files(account, jid, uris): c = app.contacts.get_contact_with_highest_priority(account, @@ -4380,7 +4391,7 @@ class RosterWindow: for uri in uri_splitted: path = helpers.get_file_path_from_dnd_dropped_uri(uri) 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, uri_splitted)) dialog.popup() @@ -4475,7 +4486,7 @@ class RosterWindow: if (type_dest == 'account' or not self.regroup) and \ account_source != account_dest: # 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) return diff --git a/gajim/search_window.py b/gajim/search_window.py index 7837e3867..092d48d54 100644 --- a/gajim/search_window.py +++ b/gajim/search_window.py @@ -28,10 +28,11 @@ from gajim.common import dataforms from gajim.common import ged from gajim import gtkgui_helpers -from gajim import dialogs from gajim import vcard from gajim import config from gajim import dataforms_widget +from gajim.gtk import AddNewContactWindow + class SearchWindow: def __init__(self, account, jid): @@ -116,7 +117,7 @@ class SearchWindow: if not iter_: return jid = model[iter_][self.jid_column] - dialogs.AddNewContactWindow(self.account, jid) + AddNewContactWindow(self.account, jid) def on_information_button_clicked(self, widget): (model, iter_) = self.result_treeview.get_selection().get_selected() diff --git a/gajim/session.py b/gajim/session.py index c5897f907..10770e939 100644 --- a/gajim/session.py +++ b/gajim/session.py @@ -35,7 +35,7 @@ from gajim.common.connection_handlers_events import ChatstateReceivedEvent, \ from gajim.common.const import KindConstant from gajim import message_control from gajim import notify -from gajim import dialogs +from gajim.gtk import SingleMessageWindow class ChatControlSession(object): @@ -358,7 +358,7 @@ class ChatControlSession(object): popup = helpers.allow_popup_window(self.conn.name) 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, resource=resource, session=self, form_node=form_node) return diff --git a/gajim/statusicon.py b/gajim/statusicon.py index ea1764975..747e79d0f 100644 --- a/gajim/statusicon.py +++ b/gajim/statusicon.py @@ -34,6 +34,8 @@ from gajim import tooltips from gajim import gtkgui_helpers from gajim.common import app from gajim.common import helpers +from gajim.gtk import SingleMessageWindow + class StatusIcon: """ @@ -163,7 +165,7 @@ class StatusIcon: jid, 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): app.app.activate_action('start-chat')