Commit 50049a12 authored by Dan Pascu's avatar Dan Pascu

Added preferences panel

parent 7af108ab
......@@ -5,20 +5,48 @@
__all__ = ['AccountExtension', 'BonjourAccountExtension']
from sipsimple.account import RTPSettings, TLSSettings
from sipsimple.account import BonjourMSRPSettings, MessageSummarySettings, MSRPSettings, NATTraversalSettings, RTPSettings, SIPSettings, TLSSettings, XCAPSettings
from sipsimple.configuration import Setting, SettingsGroup, SettingsObjectExtension
from sipsimple.configuration.datatypes import AudioCodecList, MSRPConnectionModel, MSRPTransport, SIPTransportList
from sipsimple.util import user_info
from blink.configuration.datatypes import ApplicationDataPath, CustomSoundFile, DefaultPath, HTTPURL
class BonjourMSRPSettingsExtension(BonjourMSRPSettings):
transport = Setting(type=MSRPTransport, default='tcp')
class BonjourSIPSettings(SettingsGroup):
transport_order = Setting(type=SIPTransportList, default=SIPTransportList(['tcp', 'udp', 'tls']))
class MessageSummarySettingsExtension(MessageSummarySettings):
enabled = Setting(type=bool, default=True)
class MSRPSettingsExtension(MSRPSettings):
connection_model = Setting(type=MSRPConnectionModel, default='relay')
class NATTraversalSettingsExtension(NATTraversalSettings):
use_msrp_relay_for_inbound = Setting(type=bool, default=True)
class PSTNSettings(SettingsGroup):
idd_prefix = Setting(type=unicode, default=None, nillable=True)
prefix = Setting(type=unicode, default=None, nillable=True)
class RTPSettingsExtension(RTPSettings):
audio_codec_order = Setting(type=AudioCodecList, default=None, nillable=True)
inband_dtmf = Setting(type=bool, default=True)
use_srtp_without_tls = Setting(type=bool, default=True)
class SIPSettingsExtension(SIPSettings):
always_use_my_proxy = Setting(type=bool, default=True)
register = Setting(type=bool, default=True)
class ServerSettings(SettingsGroup):
......@@ -33,17 +61,28 @@ class TLSSettingsExtension(TLSSettings):
certificate = Setting(type=ApplicationDataPath, default=None, nillable=True)
class XCAPSettingsExtension(XCAPSettings):
enabled = Setting(type=bool, default=True)
class AccountExtension(SettingsObjectExtension):
display_name = Setting(type=str, default=user_info.fullname, nillable=True)
message_summary = MessageSummarySettingsExtension
msrp = MSRPSettingsExtension
nat_traversal = NATTraversalSettingsExtension
pstn = PSTNSettings
rtp = RTPSettingsExtension
server = ServerSettings
sip = SIPSettingsExtension
sounds = SoundSettings
tls = TLSSettingsExtension
xcap = XCAPSettingsExtension
class BonjourAccountExtension(SettingsObjectExtension):
sounds = SoundSettings
msrp = BonjourMSRPSettingsExtension
rtp = RTPSettingsExtension
sip = BonjourSIPSettings
sounds = SoundSettings
......@@ -9,15 +9,35 @@ import platform
import sys
from sipsimple.configuration import Setting, SettingsGroup, SettingsObjectExtension
from sipsimple.configuration.settings import AudioSettings, LogsSettings, TLSSettings
from sipsimple.configuration.datatypes import AudioCodecList, NonNegativeInteger, PositiveInteger, Path, SampleRate
from sipsimple.configuration.settings import AudioSettings, ChatSettings, FileTransferSettings, LogsSettings, RTPSettings, TLSSettings
from blink import __version__
from blink.configuration.datatypes import ApplicationDataPath, AuthorizationToken, HTTPURL, SoundFile
from blink.resources import Resources
class AnsweringMachineSettings(SettingsGroup):
enabled = Setting(type=bool, default=False)
answer_delay = Setting(type=NonNegativeInteger, default=10)
max_recording = Setting(type=PositiveInteger, default=3)
unavailable_message = Setting(type=SoundFile, default=SoundFile(Resources.get('sounds/unavailable_message.wav')), nillable=True)
class AudioSettingsExtension(AudioSettings):
recordings_directory = Setting(type=ApplicationDataPath, default=ApplicationDataPath('recordings'), nillable=False)
recordings_directory = Setting(type=ApplicationDataPath, default=ApplicationDataPath('recordings'))
sample_rate = Setting(type=SampleRate, default=44100)
class ChatSettingsExtension(ChatSettings):
auto_accept = Setting(type=bool, default=False)
sms_replication = Setting(type=bool, default=True)
history_directory = Setting(type=ApplicationDataPath, default=ApplicationDataPath('history'))
class FileTransferSettingsExtension(FileTransferSettings):
auto_accept = Setting(type=bool, default=False)
directory = Setting(type=Path, default=None, nillable=True)
class GoogleContactsSettings(SettingsGroup):
......@@ -29,9 +49,14 @@ class LogsSettingsExtension(LogsSettings):
trace_sip = Setting(type=bool, default=False)
trace_pjsip = Setting(type=bool, default=False)
trace_msrp = Setting(type=bool, default=False)
trace_xcap = Setting(type=bool, default=False)
trace_notifications = Setting(type=bool, default=False)
class RTPSettingsExtension(RTPSettings):
audio_codec_order = Setting(type=AudioCodecList, default=AudioCodecList(('G722', 'speex', 'GSM', 'iLBC', 'PCMU', 'PCMA')))
class ServerSettings(SettingsGroup):
enrollment_url = Setting(type=HTTPURL, default="https://blink.sipthor.net/enrollment.phtml")
......@@ -39,6 +64,12 @@ class ServerSettings(SettingsGroup):
class SoundSettings(SettingsGroup):
inbound_ringtone = Setting(type=SoundFile, default=SoundFile(Resources.get('sounds/inbound_ringtone.wav')), nillable=True)
outbound_ringtone = Setting(type=SoundFile, default=SoundFile(Resources.get('sounds/outbound_ringtone.wav')), nillable=True)
message_received = Setting(type=SoundFile, default=SoundFile(Resources.get('sounds/message_received.wav')), nillable=True)
message_sent = Setting(type=SoundFile, default=SoundFile(Resources.get('sounds/message_sent.wav')), nillable=True)
file_received = Setting(type=SoundFile, default=SoundFile(Resources.get('sounds/file_received.wav')), nillable=True)
file_sent = Setting(type=SoundFile, default=SoundFile(Resources.get('sounds/file_sent.wav')), nillable=True)
play_message_alerts = Setting(type=bool, default=True)
play_file_alerts = Setting(type=bool, default=True)
class TLSSettingsExtension(TLSSettings):
......@@ -46,9 +77,13 @@ class TLSSettingsExtension(TLSSettings):
class SIPSimpleSettingsExtension(SettingsObjectExtension):
answering_machine = AnsweringMachineSettings
audio = AudioSettingsExtension
chat = ChatSettingsExtension
file_transfer = FileTransferSettingsExtension
google_contacts = GoogleContactsSettings
logs = LogsSettingsExtension
rtp = RTPSettingsExtension
server = ServerSettings
sounds = SoundSettings
tls = TLSSettingsExtension
......
......@@ -22,8 +22,9 @@ from sipsimple.configuration.settings import SIPSimpleSettings
from sipsimple.util import limit
from blink.aboutpanel import AboutPanel
from blink.accounts import AccountModel, ActiveAccountModel, AddAccountDialog, ServerToolsAccountModel, ServerToolsWindow
from blink.accounts import AccountModel, ActiveAccountModel, ServerToolsAccountModel, ServerToolsWindow
from blink.contacts import BonjourNeighbour, Contact, ContactGroup, ContactEditorDialog, ContactModel, ContactSearchModel, GoogleContactsDialog
from blink.preferences import PreferencesWindow
from blink.sessions import SessionManager, SessionModel
from blink.configuration.datatypes import InvalidToken
from blink.resources import Resources
......@@ -88,9 +89,9 @@ class MainWindow(base_class, ui_class):
# Windows, dialogs and panels
self.about_panel = AboutPanel(self)
self.add_account_dialog = AddAccountDialog(self)
self.contact_editor_dialog = ContactEditorDialog(self.contact_model, self)
self.google_contacts_dialog = GoogleContactsDialog(self)
self.preferences_window = PreferencesWindow(self.account_model, None)
self.server_tools_window = ServerToolsWindow(self.server_tools_account_model, None)
# Signals
......@@ -135,8 +136,12 @@ class MainWindow(base_class, ui_class):
# Blink menu actions
self.about_action.triggered.connect(self.about_panel.show)
self.donate_action.triggered.connect(partial(QDesktopServices.openUrl, QUrl(u'http://icanblink.com/payments.phtml')))
self.add_account_action.triggered.connect(self.add_account_dialog.open_for_add)
self.add_account_action.triggered.connect(self.preferences_window.show_add_account_dialog)
self.manage_accounts_action.triggered.connect(self.preferences_window.show_for_accounts)
self.help_action.triggered.connect(partial(QDesktopServices.openUrl, QUrl(u'http://icanblink.com/help-qt.phtml')))
self.preferences_action.triggered.connect(self.preferences_window.show)
self.auto_accept_chat_action.triggered.connect(self._AH_AutoAcceptChatTriggered)
self.auto_accept_files_action.triggered.connect(self._AH_AutoAcceptFilesTriggered)
self.release_notes_action.triggered.connect(partial(QDesktopServices.openUrl, QUrl(u'http://icanblink.com/changelog-qt.phtml')))
self.quit_action.triggered.connect(self.close)
......@@ -151,6 +156,7 @@ class MainWindow(base_class, ui_class):
self.redial_action.triggered.connect(self._AH_RedialActionTriggered)
# Tools menu actions
self.answering_machine_action.triggered.connect(self._AH_EnableAnsweringMachineTriggered)
self.sip_server_settings_action.triggered.connect(self._AH_SIPServerSettings)
self.search_for_people_action.triggered.connect(self._AH_SearchForPeople)
self.history_on_server_action.triggered.connect(self._AH_HistoryOnServer)
......@@ -204,9 +210,9 @@ class MainWindow(base_class, ui_class):
def closeEvent(self, event):
super(MainWindow, self).closeEvent(event)
self.about_panel.close()
self.add_account_dialog.close()
self.contact_editor_dialog.close()
self.google_contacts_dialog.close()
self.preferences_window.close()
self.server_tools_window.close()
def set_user_icon(self, image_file_name):
......@@ -302,6 +308,21 @@ class MainWindow(base_class, ui_class):
settings.audio.output_device = action.data().toPyObject()
call_in_auxiliary_thread(settings.save)
def _AH_AutoAcceptChatTriggered(self, checked):
settings = SIPSimpleSettings()
settings.chat.auto_accept = checked
settings.save()
def _AH_AutoAcceptFilesTriggered(self, checked):
settings = SIPSimpleSettings()
settings.file_transfer.auto_accept = checked
settings.save()
def _AH_EnableAnsweringMachineTriggered(self, checked):
settings = SIPSimpleSettings()
settings.answering_machine.enabled = checked
settings.save()
def _AH_GoogleContactsActionTriggered(self):
settings = SIPSimpleSettings()
if settings.google_contacts.authorization_token is not None:
......@@ -521,6 +542,9 @@ class MainWindow(base_class, ui_class):
settings = SIPSimpleSettings()
self.silent_action.setChecked(settings.audio.silent)
self.silent_button.setChecked(settings.audio.silent)
self.answering_machine_action.setChecked(settings.answering_machine.enabled)
self.auto_accept_chat_action.setChecked(settings.chat.auto_accept)
self.auto_accept_files_action.setChecked(settings.file_transfer.auto_accept)
if settings.google_contacts.authorization_token is None:
self.google_contacts_action.setText(u'Enable Google Contacts')
else:
......@@ -575,6 +599,12 @@ class MainWindow(base_class, ui_class):
if 'audio.alert_device' in notification.data.modified:
action = (action for action in self.alert_devices_group.actions() if action.data().toPyObject() == settings.audio.alert_device).next()
action.setChecked(True)
if 'answering_machine.enabled' in notification.data.modified:
self.answering_machine_action.setChecked(settings.answering_machine.enabled)
if 'chat.auto_accept' in notification.data.modified:
self.auto_accept_chat_action.setChecked(settings.chat.auto_accept)
if 'file_transfer.auto_accept' in notification.data.modified:
self.auto_accept_files_action.setChecked(settings.file_transfer.auto_accept)
if 'google_contacts.authorization_token' in notification.data.modified:
authorization_token = notification.sender.google_contacts.authorization_token
if authorization_token is None:
......@@ -584,10 +614,13 @@ class MainWindow(base_class, ui_class):
if authorization_token is InvalidToken:
self.google_contacts_dialog.open_for_incorrect_password()
elif isinstance(notification.sender, (Account, BonjourAccount)):
account_manager = AccountManager()
account = notification.sender
if 'enabled' in notification.data.modified:
action = (action for action in self.accounts_menu.actions() if action.data().toPyObject() is account).next()
action.setChecked(account.enabled)
if 'display_name' in notification.data.modified and account is account_manager.default_account:
self.display_name.setText(account.display_name or u'')
if set(['enabled', 'message_summary.enabled', 'message_summary.voicemail_uri']).intersection(notification.data.modified):
action = (action for action in self.voicemail_menu.actions() if action.data().toPyObject() is account).next()
action.setVisible(False if account is BonjourAccount() else account.enabled and account.message_summary.enabled)
......
# Copyright (C) 2010 AG Projects. See LICENSE for details.
#
from __future__ import with_statement
__all__ = ['PreferencesWindow', 'AccountListView', 'SIPPortEditor']
import os
import urlparse
from PyQt4 import uic
from PyQt4.QtCore import Qt, QRegExp, QVariant
from PyQt4.QtGui import QActionGroup, QButtonGroup, QFileDialog, QListView, QListWidgetItem, QMessageBox, QRegExpValidator, QSpinBox, QStyle, QStyleOptionComboBox, QValidator
from application import log
from application.notification import IObserver, NotificationCenter
from application.python.util import Null
from gnutls.crypto import X509Certificate
from gnutls.errors import GNUTLSError
from zope.interface import implements
from sipsimple.account import Account, BonjourAccount, AccountManager
from sipsimple.application import SIPApplication
from sipsimple.configuration import DefaultValue
from sipsimple.configuration.datatypes import MSRPRelayAddress, PortRange, SIPProxyAddress
from sipsimple.configuration.settings import SIPSimpleSettings
from sipsimple.util import limit
from blink.accounts import AddAccountDialog
from blink.resources import ApplicationData, Resources
from blink.logging import LogManager
from blink.util import QSingleton, call_in_auxiliary_thread, call_in_gui_thread, run_in_auxiliary_thread, run_in_gui_thread
# LineEdit and ComboBox validators
#
class IDDPrefixValidator(QRegExpValidator):
def __init__(self, parent=None):
super(IDDPrefixValidator, self).__init__(QRegExp(u'[0-9+*#]+'), parent)
def fixup(self, input):
if input.isEmpty():
input.append(u'+')
return super(IDDPrefixValidator, self).fixup(input)
class PrefixValidator(QRegExpValidator):
def __init__(self, parent=None):
super(PrefixValidator, self).__init__(QRegExp(u'(None|[0-9+*#]+)'), parent)
def fixup(self, input):
if input.isEmpty():
input.append(u'None')
return super(PrefixValidator, self).fixup(input)
class SIPAddressValidator(QRegExpValidator):
def __init__(self, parent=None):
super(SIPAddressValidator, self).__init__(QRegExp(u'^([\w\-_+%]+@[\w\-_]+(\.[\w\-_]+)*)?$', Qt.CaseInsensitive), parent)
def fixup(self, input):
if input and '@' not in input:
preferences_window = self.parent()
input.append(u'@%s' % preferences_window.selected_account.id.domain)
return super(SIPAddressValidator, self).fixup(input)
class WebURLValidator(QRegExpValidator):
def __init__(self, parent=None):
super(WebURLValidator, self).__init__(QRegExp(u'^(https?://[\w\-_]+(\.[\w\-_]+)*(:\d+)?(/.*)?)?$', Qt.CaseInsensitive), parent)
class XCAPRootValidator(WebURLValidator):
def fixup(self, input):
url = urlparse.urlparse(unicode(input))
if not (url.scheme and url.netloc):
input.clear()
return super(XCAPRootValidator, self).fixup(input)
def validate(self, input, pos):
state, pos = super(XCAPRootValidator, self).validate(input, pos)
if state == QValidator.Acceptable:
if input and input[-1] in ('?', ';', '&'):
state = QValidator.Invalid
else:
url = urlparse.urlparse(unicode(input))
if url.params or url.query or url.fragment:
state = QValidator.Invalid
elif url.port is not None:
port = int(url.port)
if not (0 < port <= 65535):
state = QValidator.Invalid
return state, pos
# Custom widgets used in preferences.ui
#
class SIPPortEditor(QSpinBox):
def __init__(self, parent=None):
super(SIPPortEditor, self).__init__(parent)
self.setRange(0, 65535)
self.sibling = Null # if there is a sibling port, its value is invalid for this port
def stepBy(self, steps):
value = self.value()
sibling_value = self.sibling.value()
if value+steps == sibling_value != 0:
steps += steps/abs(steps) # add one more unit in the right direction
if 0 < value+steps < 1024:
if steps < 0:
steps = -value
else:
steps = 1024 - value
if value+steps == sibling_value != 0:
steps += steps/abs(steps) # add one more unit in the right direction
return super(SIPPortEditor, self).stepBy(steps)
def validate(self, input, pos):
state, pos = super(SIPPortEditor, self).validate(input, pos)
if state == QValidator.Acceptable:
value = int(input)
if 0 < value < 1024:
state = QValidator.Intermediate
elif value == self.sibling.value() != 0:
state = QValidator.Intermediate
return state, pos
class AccountListView(QListView):
def __init__(self, parent=None):
super(AccountListView, self).__init__(parent)
#self.setItemDelegate(AccountDelegate(self))
#self.setDropIndicatorShown(False)
def setModel(self, model):
selection_model = self.selectionModel() or Null
selection_model.selectionChanged.disconnect(self._SH_SelectionModelSelectionChanged)
super(AccountListView, self).setModel(model)
self.selectionModel().selectionChanged.connect(self._SH_SelectionModelSelectionChanged)
def _SH_SelectionModelSelectionChanged(self, selected, deselected):
selection_model = self.selectionModel()
selection = selection_model.selection()
if selection_model.currentIndex() not in selection:
index = selection.indexes()[0] if not selection.isEmpty() else self.model().index(-1)
selection_model.setCurrentIndex(index, selection_model.Select)
ui_class, base_class = uic.loadUiType(Resources.get('preferences.ui'))
class PreferencesWindow(base_class, ui_class):
__metaclass__ = QSingleton
implements(IObserver)
def __init__(self, account_model, parent=None):
super(PreferencesWindow, self).__init__(parent)
self.load_in_progress = False
with Resources.directory:
self.setupUi()
self.setWindowTitle('Blink Preferences')
self.setWindowIconText('Blink Preferences')
self.account_list.setModel(account_model)
self.delete_account_button.setEnabled(False)
notification_center = NotificationCenter()
notification_center.add_observer(self, name='SIPApplicationDidStart')
# Dialogs
self.add_account_dialog = AddAccountDialog(self)
# Signals
self.toolbar.actionTriggered.connect(self._SH_ToolbarActionTriggered)
# Account
self.account_list.selectionModel().selectionChanged.connect(self._SH_AccountListSelectionChanged)
self.add_account_button.clicked.connect(self.show_add_account_dialog)
self.delete_account_button.clicked.connect(self._SH_DeleteAccountButtonClicked)
# Account information
self.account_enabled_button.clicked.connect(self._SH_AccountEnabledButtonClicked)
self.display_name_editor.editingFinished.connect(self._SH_DisplayNameEditorEditingFinished)
self.password_editor.editingFinished.connect(self._SH_PasswordEditorEditingFinished)
# Account media settings
self.account_audio_codecs_list.itemChanged.connect(self._SH_AccountAudioCodecsListItemChanged)
try:
self.account_audio_codecs_list.model().rowsMoved.connect(self._SH_AccountAudioCodecsListModelRowsMoved)
except AttributeError:
# Qt before 4.6 did not have the rowsMoved signal.
# To be removed when we drop support for the older QT versions. -Dan
self.account_audio_codecs_list.model().rowsInserted.connect(self._SH_AccountAudioCodecsListModelRowsInserted)
self.reset_account_audio_codecs_button.clicked.connect(self._SH_ResetAudioCodecsButtonClicked)
self.inband_dtmf_button.clicked.connect(self._SH_InbandDTMFButtonClicked)
self.srtp_encryption_button.activated[str].connect(self._SH_SRTPEncryptionButtonActivated)
# Account server settings
self.always_use_my_proxy_button.clicked.connect(self._SH_AlwaysUseMyProxyButtonClicked)
self.outbound_proxy_host_editor.editingFinished.connect(self._SH_OutboundProxyHostEditorEditingFinished)
self.outbound_proxy_port.valueChanged[int].connect(self._SH_OutboundProxyPortValueChanged)
self.outbound_proxy_transport_button.activated[str].connect(self._SH_OutboundProxyTransportButtonActivated)
self.auth_username_editor.editingFinished.connect(self._SH_AuthUsernameEditorEditingFinished)
self.always_use_my_msrp_relay_button.clicked.connect(self._SH_AlwaysUseMyMSRPRelayButtonClicked)
self.msrp_relay_host_editor.editingFinished.connect(self._SH_MSRPRelayHostEditorEditingFinished)
self.msrp_relay_port.valueChanged[int].connect(self._SH_MSRPRelayPortValueChanged)
self.msrp_relay_transport_button.activated[str].connect(self._SH_MSRPRelayTransportButtonActivated)
self.voicemail_uri_editor.editingFinished.connect(self._SH_VoicemailURIEditorEditingFinished)
self.xcap_root_editor.editingFinished.connect(self._SH_XCAPRootEditorEditingFinished)
self.server_tools_url_editor.editingFinished.connect(self._SH_ServerToolsURLEditorEditingFinished)
# Account network settings
self.use_ice_button.clicked.connect(self._SH_UseICEButtonClicked)
self.msrp_transport_button.activated[str].connect(self._SH_MSRPTransportButtonActivated)
# Account advanced settings
self.register_interval.valueChanged[int].connect(self._SH_RegisterIntervalValueChanged)
self.publish_interval.valueChanged[int].connect(self._SH_PublishIntervalValueChanged)
self.subscribe_interval.valueChanged[int].connect(self._SH_SubscribeIntervalValueChanged)
self.reregister_button.clicked.connect(self._SH_ReregisterButtonClicked)
self.idd_prefix_button.activated[str].connect(self._SH_IDDPrefixButtonActivated)
self.prefix_button.activated[str].connect(self._SH_PrefixButtonActivated)
self.account_tls_cert_file_editor.locationCleared.connect(self._SH_AccountTLSCertFileEditorLocationCleared)
self.account_tls_cert_file_browse_button.clicked.connect(self._SH_AccountTLSCertFileBrowseButtonClicked)
self.account_tls_verify_server_button.clicked.connect(self._SH_AccountTLSVerifyServerButtonClicked)
# Audio devices
self.audio_alert_device_button.activated[int].connect(self._SH_AudioAlertDeviceButtonActivated)
self.audio_input_device_button.activated[int].connect(self._SH_AudioInputDeviceButtonActivated)
self.audio_output_device_button.activated[int].connect(self._SH_AudioOutputDeviceButtonActivated)
self.audio_sample_rate_button.activated[str].connect(self._SH_AudioSampleRateButtonActivated)
self.enable_echo_cancelling_button.clicked.connect(self._SH_EnableEchoCancellingButtonClicked)
# Audio codecs
self.audio_codecs_list.itemChanged.connect(self._SH_AudioCodecsListItemChanged)
try:
self.audio_codecs_list.model().rowsMoved.connect(self._SH_AudioCodecsListModelRowsMoved)
except AttributeError:
# Qt before 4.6 did not have the rowsMoved signal.
# To be removed when we drop support for the older QT versions. -Dan
self.audio_codecs_list.model().rowsInserted.connect(self._SH_AudioCodecsListModelRowsInserted)
# Answering machine
self.enable_answering_machine_button.clicked.connect(self._SH_EnableAnsweringMachineButtonClicked)
self.answer_delay.valueChanged[int].connect(self._SH_AnswerDelayValueChanged)
self.max_recording.valueChanged[int].connect(self._SH_MaxRecordingValueChanged)
# Chat and SMS
self.auto_accept_chat_button.clicked.connect(self._SH_AutoAcceptChatButtonClicked)
self.sms_replication_button.clicked.connect(self._SH_SMSReplicationButtonClicked)
# File transfer
self.auto_accept_files_button.clicked.connect(self._SH_AutoAcceptFilesButtonClicked)
self.download_directory_editor.locationCleared.connect(self._SH_DownloadDirectoryEditorLocationCleared)
self.download_directory_browse_button.clicked.connect(self._SH_DownloadDirectoryBrowseButtonClicked)
# Alerts
self.silence_alerts_button.clicked.connect(self._SH_SilenceAlertsButtonClicked)
self.message_alerts_button.clicked.connect(self._SH_MessageAlertsButtonClicked)
self.file_alerts_button.clicked.connect(self._SH_FileAlertsButtonClicked)
# File logging
self.trace_sip_button.clicked.connect(self._SH_TraceSIPButtonClicked)
self.trace_msrp_button.clicked.connect(self._SH_TraceMSRPButtonClicked)
self.trace_xcap_button.clicked.connect(self._SH_TraceXCAPButtonClicked)
self.trace_notifications_button.clicked.connect(self._SH_TraceNotificationsButtonClicked)
self.trace_pjsip_button.clicked.connect(self._SH_TracePJSIPButtonClicked)
self.pjsip_trace_level.valueChanged[int].connect(self._SH_PJSIPTraceLevelValueChanged)
self.clear_log_files_button.clicked.connect(self._SH_ClearLogFilesButtonClicked)
# SIP and RTP
self.sip_transports_button_group.buttonClicked.connect(self._SH_SIPTransportsButtonClicked)
self.udp_port.valueChanged[int].connect(self._SH_UDPPortValueChanged)
self.tcp_port.valueChanged[int].connect(self._SH_TCPPortValueChanged)
self.tls_port.valueChanged[int].connect(self._SH_TLSPortValueChanged)
self.media_ports_start.valueChanged[int].connect(self._SH_MediaPortsStartValueChanged)
self.media_ports.valueChanged[int].connect(self._SH_MediaPortsValueChanged)
self.session_timeout.valueChanged[int].connect(self._SH_SessionTimeoutValueChanged)
self.rtp_timeout.valueChanged[int].connect(self._SH_RTPTimeoutValueChanged)
# TLS
self.tls_ca_file_editor.locationCleared.connect(self._SH_TLSCAFileEditorLocationCleared)
self.tls_ca_file_browse_button.clicked.connect(self._SH_TLSCAFileBrowseButtonClicked)
self.tls_timeout.valueChanged[float].connect(self._SH_TLSTimeoutValueChanged)
# Setup initial state (show the acounts page right after start)
self.accounts_action.trigger()
self.account_tab_widget.setCurrentIndex(0)
def setupUi(self):
super(PreferencesWindow, self).setupUi(self)
#self.rename_account_button.hide() # do not use this for the time being -Dan
self.section_group = QActionGroup(self)
self.section_group.setExclusive(True)
for index, action in enumerate(action for action in self.toolbar.actions() if not action.isSeparator()):
action.index = index
self.section_group.addAction(action)
for index in xrange(self.idd_prefix_button.count()):
text = unicode(self.idd_prefix_button.itemText(index))
self.idd_prefix_button.setItemData(index, QVariant(None if text == "+" else text))
for index in xrange(self.prefix_button.count()):
text = unicode(self.prefix_button.itemText(index))
self.prefix_button.setItemData(index, QVariant(None if text == "None" else text))
self.voicemail_uri_editor.setValidator(SIPAddressValidator(self))
self.xcap_root_editor.setValidator(XCAPRootValidator(self))
self.server_tools_url_editor.setValidator(WebURLValidator(self))
self.idd_prefix_button.setValidator(IDDPrefixValidator(self))
self.prefix_button.setValidator(PrefixValidator(self))
# Adding the button group in designer has issues on Ubuntu 10.04
self.sip_transports_button_group = QButtonGroup(self)
self.sip_transports_button_group.setObjectName("sip_transports_button_group")
self.sip_transports_button_group.setExclusive(False)
self.sip_transports_button_group.addButton(self.enable_udp_button)
self.sip_transports_button_group.addButton(self.enable_tcp_button)
self.sip_transports_button_group.addButton(self.enable_tls_button)
self.enable_udp_button.name = 'udp'
self.enable_tcp_button.name = 'tcp'
self.enable_tls_button.name = 'tls'
self.tcp_port.sibling = self.tls_port
self.tls_port.sibling = self.tcp_port
# Adjust some minimum label widths in order to better align settings in different group boxes, widgets or tabs
# account server and network tab
font_metrics = self.outbound_proxy_label.fontMetrics() # we assume all labels have the same font
labels = (self.outbound_proxy_label, self.auth_username_label, self.msrp_relay_label,
self.voicemail_uri_label, self.xcap_root_label, self.server_tools_url_label,
self.msrp_transport_label)
text_width = max(font_metrics.width(label.text()) for label in labels) + 15
self.outbound_proxy_label.setMinimumWidth(text_width)
self.msrp_transport_label.setMinimumWidth(text_width)
# account advanced tab
font_metrics = self.register_interval_label.fontMetrics() # we assume all labels have the same font
labels = (self.register_interval_label, self.publish_interval_label, self.subscribe_interval_label,
self.idd_prefix_label, self.prefix_label, self.account_tls_cert_file_label)
text_width = max(font_metrics.width(label.text()) for label in labels) + 15
self.register_interval_label.setMinimumWidth(text_width)
self.idd_prefix_label.setMinimumWidth(text_width)
self.account_tls_cert_file_label.setMinimumWidth(text_width)
# audio settings
font_metrics = self.answer_delay_label.fontMetrics() # we assume all labels have the same font
labels = (self.audio_input_device_label, self.audio_output_device_label, self.audio_alert_device_label, self.audio_sample_rate_label,
self.answer_delay_label, self.max_recording_label, self.unavailable_message_label)
text_width = max(font_metrics.width(label.text()) for label in labels)
self.audio_input_device_label.setMinimumWidth(text_width)
self.answer_delay_label.setMinimumWidth(text_width)
# advanced settings
font_metrics = self.transports_label.fontMetrics() # we assume all labels have the same font
labels = (self.transports_label, self.media_ports_label, self.session_timeout_label, self.rtp_timeout_label, self.tls_ca_file_label, self.tls_timeout_label)
text_width = max(font_metrics.width(label.text()) for label in labels)
self.transports_label.setMinimumWidth(text_width)
self.tls_ca_file_label.setMinimumWidth(text_width)
# Adjust the combo boxes for themes with too much padding (like the default theme on Ubuntu 10.04)
combo_box = self.audio_input_device_button
option = QStyleOptionComboBox()
combo_box.initStyleOption(option)
wide_padding = (combo_box.height() - combo_box.style().subControlRect(QStyle.CC_ComboBox, option, QStyle.SC_ComboBoxEditField, combo_box).height() >= 10)
if False and wide_padding: # TODO: review later and decide if its worth or not -Dan
print "found wide padding"
self.audio_alert_device_button.setStyleSheet("""QComboBox { padding: 4px 4px 4px 4px; }""")
self.audio_input_device_button.setStyleSheet("""QComboBox { padding: 4px 4px 4px 4px; }""")
self.audio_output_device_button.setStyleSheet("""QComboBox { padding: 4px 4px 4px 4px; }""")
self.audio_sample_rate_button.setStyleSheet("""QComboBox { padding: 4px 4px 4px 4px; }""")
self.unavailable_message_button.setStyleSheet("""QComboBox { padding: 4px 4px 4px 4px; }""")
def closeEvent(self, event):
super(PreferencesWindow, self).closeEvent(event)
self.add_account_dialog.close()
@property
def account_msrp_relay(self):
host = self.msrp_relay_host_editor.text()
port = self.msrp_relay_port.value()
transport = str(self.msrp_relay_transport_button.currentText()).lower()
return MSRPRelayAddress(host, port, transport) if host else None
@property
def account_outbound_proxy(self):
host = self.outbound_proxy_host_editor.text()
port = self.outbound_proxy_port.value()
transport = str(self.outbound_proxy_transport_button.currentText()).lower()
return SIPProxyAddress(host, port, transport) if host else None
@property
def selected_account(self):
try:
selected_index = self.account_list.selectionModel().selectedIndexes()[0]
except IndexError:
return None
else:
return self.account_list.model().data(selected_index, Qt.UserRole).account
def _sync_defaults(self):
settings = SIPSimpleSettings()
account_manager = AccountManager()
default_order = SIPSimpleSettings.rtp.audio_codec_order.default
default_list = SIPSimpleSettings.rtp.audio_codec_list.default
if settings.rtp.audio_codec_order is not default_order:
# user changed the default order, we need to sync with the new settings
added_codecs = set(default_order).difference(settings.rtp.audio_codec_order)
removed_codecs = set(settings.rtp.audio_codec_order).difference(default_order)
if added_codecs:
settings.rtp.audio_codec_order = DefaultValue # reset codec order
settings.save()
elif removed_codecs:
codec_order = [codec for codec in settings.rtp.audio_codec_order if codec not in removed_codecs]
codec_list = [codec for codec in settings.rtp.audio_codec_list if codec not in removed_codecs]
if codec_order == default_order:
codec_order = DefaultValue
if codec_list == default_list:
codec_list = DefaultValue
settings.rtp.audio_codec_order = codec_order
settings.rtp.audio_codec_order = codec_list
settings.save()
for account in (account for account in account_manager.iter_accounts() if account.rtp.audio_codec_order is not None):
# user changed the default order, we need to sync with the new settings
added_codecs = set(default_order).difference(account.rtp.audio_codec_order)
removed_codecs = set(account.rtp.audio_codec_order).difference(default_order)
if added_codecs:
account.rtp.audio_codec_order = DefaultValue # reset codec order
account.rtp.audio_codec_list = DefaultValue # reset codec list
account.save()
elif removed_codecs:
codec_order = [codec for codec in account.rtp.audio_codec_order if codec not in removed_codecs]
codec_list = [codec for codec in account.rtp.audio_codec_list if codec not in removed_codecs]
if codec_order == default_order and codec_list == default_list:
codec_order = DefaultValue
codec_list = DefaultValue
account.rtp.audio_codec_order = codec_order
account.rtp.audio_codec_order = codec_list
account.save()
def load_audio_devices(self):
settings = SIPSimpleSettings()
class Separator: pass
self.audio_input_device_button.clear()
self.audio_input_device_button.addItem(u'System Default', QVariant('system_default'))
self.audio_input_device_button.insertSeparator(1)
self.audio_input_device_button.setItemData(1, QVariant(Separator)) # prevent the separator from being selectable
for device in SIPApplication.engine.input_devices:
self.audio_input_device_button.addItem(device, QVariant(device))
self.audio_input_device_button.addItem(u'None', QVariant(None))
self.audio_input_device_button.setCurrentIndex(self.audio_input_device_button.findData(QVariant(settings.audio.input_device)))
self.audio_output_device_button.clear()
self.audio_output_device_button.addItem(u'System Default', QVariant('system_default'))
self.audio_output_device_button.insertSeparator(1)
self.audio_output_device_button.setItemData(1, QVariant(Separator)) # prevent the separator from being selectable
for device in SIPApplication.engine.output_devices:
self.audio_output_device_button.addItem(device, QVariant(device))
self.audio_output_device_button.addItem(u'None', QVariant(None))
self.audio_output_device_button.setCurrentIndex(self.audio_output_device_button.findData(QVariant(settings.audio.output_device)))
self.audio_alert_device_button.clear()
self.audio_alert_device_button.addItem(u'System Default', QVariant('system_default'))
self.audio_alert_device_button.insertSeparator(1)
self.audio_alert_device_button.setItemData(1, QVariant(Separator)) # prevent the separator from being selectable
for device in SIPApplication.engine.output_devices:
self.audio_alert_device_button.addItem(device, QVariant(device))
self.audio_alert_device_button.addItem(u'None', QVariant(None))
self.audio_alert_device_button.setCurrentIndex(self.audio_alert_device_button.findData(QVariant(settings.audio.alert_device)))
def load_settings(self):
"""Load settings from configuration into the UI controls"""
settings = SIPSimpleSettings()
self.load_in_progress = True
# Audio devices
self.load_audio_devices()
self.enable_echo_cancelling_button.setChecked(settings.audio.tail_length != 0)
self.audio_sample_rate_button.clear()
for rate in SIPSimpleSettings.audio.sample_rate.type.valid_values:
self.audio_sample_rate_button.addItem(str(rate), QVariant(rate))
self.audio_sample_rate_button.setCurrentIndex(self.audio_sample_rate_button.findText(str(settings.audio.sample_rate)))
# Audio codecs
self.audio_codecs_list.clear()
for codec in settings.rtp.audio_codec_order:
item = QListWidgetItem(codec, self.audio_codecs_list)
item.setCheckState(Qt.Checked if codec in settings.rtp.audio_codec_list else Qt.Unchecked)
# Asnwering Machine settings
self.enable_answering_machine_button.setChecked(settings.answering_machine.enabled)
self.answer_delay.setValue(settings.answering_machine.answer_delay)
self.max_recording.setValue(settings.answering_machine.max_recording)
# TODO: load unavailable message -Dan
# Chat and SMS settings
self.auto_accept_chat_button.setChecked(settings.chat.auto_accept)
self.sms_replication_button.setChecked(settings.chat.sms_replication)
# File transfer settings
self.auto_accept_files_button.setChecked(settings.file_transfer.auto_accept)
self.download_directory_editor.setText(settings.file_transfer.directory or u'')
# Alert settings
self.silence_alerts_button.setChecked(settings.audio.silent)
self.message_alerts_button.setChecked(settings.sounds.play_message_alerts)
self.file_alerts_button.setChecked(settings.sounds.play_file_alerts)
# File logging settings
self.trace_sip_button.setChecked(settings.logs.trace_sip)
self.trace_msrp_button.setChecked(settings.logs.trace_msrp)
self.trace_xcap_button.setChecked(settings.logs.trace_xcap)
self.trace_notifications_button.setChecked(settings.logs.trace_notifications)
self.trace_pjsip_button.setChecked(settings.logs.trace_pjsip)
self.pjsip_trace_level.setValue(limit(settings.logs.pjsip_level, min=0, max=5))
# Advanced settings
for button in self.sip_transports_button_group.buttons():
button.setChecked(button.name in settings.sip.transport_list)
if settings.sip.tcp_port and settings.sip.tcp_port==settings.sip.tls_port:
log.warning("the SIP TLS and TCP ports cannot be the same")
settings.sip.tls_port = settings.sip.tcp_port+1 if settings.sip.tcp_port<65535 else 65534
settings.save()
self.udp_port.setValue(settings.sip.udp_port)
self.tcp_port.setValue(settings.sip.tcp_port)
self.tls_port.setValue(settings.sip.tls_port)
self.media_ports_start.setValue(settings.rtp.port_range.start)
self.media_ports.setValue(settings.rtp.port_range.end - settings.rtp.port_range.start)
self.session_timeout.setValue(settings.sip.invite_timeout)
self.rtp_timeout.setValue(settings.rtp.timeout)
self.tls_ca_file_editor.setText(settings.tls.ca_list or u'')
self.tls_timeout.setValue(settings.tls.timeout / 1000.0)
self.load_in_progress = False
def load_account_settings(self, account):
"""Load the account settings from configuration into the UI controls"""
settings = SIPSimpleSettings()
bonjour_account = BonjourAccount()
self.load_in_progress = True
# Account information tab
self.account_enabled_button.setChecked(account.enabled)
self.account_enabled_button.setEnabled(True if account is not bonjour_account else BonjourAccount.mdns_available)
self.display_name_editor.setText(account.display_name or u'')
if account is not bonjour_account:
self.password_editor.setText(account.auth.password)
# Media tab
self.account_audio_codecs_list.clear()
audio_codec_order = account.rtp.audio_codec_order or settings.rtp.audio_codec_order
audio_codec_list = account.rtp.audio_codec_list or settings.rtp.audio_codec_list
for codec in audio_codec_order:
item = QListWidgetItem(codec, self.account_audio_codecs_list)
item.setCheckState(Qt.Checked if codec in audio_codec_list else Qt.Unchecked)
self.reset_account_audio_codecs_button.setEnabled(account.rtp.audio_codec_order is not None)
self.reset_account_video_codecs_button.setEnabled(False)
self.account_video_codecs_list.setEnabled(False)
self.inband_dtmf_button.setChecked(account.rtp.inband_dtmf)
self.srtp_encryption_button.setCurrentIndex(self.srtp_encryption_button.findText(account.rtp.srtp_encryption))
if account is not bonjour_account:
# Server settings tab
self.always_use_my_proxy_button.setChecked(account.sip.always_use_my_proxy)
if account.sip.outbound_proxy is None:
self.outbound_proxy_host_editor.setText(u'')
self.outbound_proxy_port.setValue(5060)
self.outbound_proxy_transport_button.setCurrentIndex(self.outbound_proxy_transport_button.findText(u'UDP'))
else:
self.outbound_proxy_host_editor.setText(account.sip.outbound_proxy.host)
self.outbound_proxy_port.setValue(account.sip.outbound_proxy.port)
self.outbound_proxy_transport_button.setCurrentIndex(self.outbound_proxy_transport_button.findText(account.sip.outbound_proxy.transport.upper()))
self.auth_username_editor.setText(account.auth.username or u'')
self.always_use_my_msrp_relay_button.setChecked(account.nat_traversal.use_msrp_relay_for_outbound)
if account.nat_traversal.msrp_relay is None:
self.msrp_relay_host_editor.setText(u'')
self.msrp_relay_port.setValue(0)
self.msrp_relay_transport_button.setCurrentIndex(self.msrp_relay_transport_button.findText(u'TLS'))
else:
self.msrp_relay_host_editor.setText(account.nat_traversal.msrp_relay.host)
self.msrp_relay_port.setValue(account.nat_traversal.msrp_relay.port)
self.msrp_relay_transport_button.setCurrentIndex(self.msrp_relay_transport_button.findText(account.nat_traversal.msrp_relay.transport.upper()))
self.voicemail_uri_editor.setText(account.message_summary.voicemail_uri or u'')
self.xcap_root_editor.setText(account.xcap.xcap_root or u'')
self.server_tools_url_editor.setText(account.server.settings_url or u'')
# Network tab
self.use_ice_button.setChecked(account.nat_traversal.use_ice)
self.msrp_transport_button.setCurrentIndex(self.msrp_transport_button.findText(account.msrp.transport.upper()))
# Advanced tab
self.register_interval.setValue(account.sip.register_interval)
self.publish_interval.setValue(account.sip.publish_interval)
self.subscribe_interval.setValue(account.sip.subscribe_interval)
self.reregister_button.setEnabled(account.enabled)
item_text = account.pstn.idd_prefix or '+'
index = self.idd_prefix_button.findText(item_text)
if index == -1:
self.idd_prefix_button.addItem(item_text)
self.idd_prefix_button.setCurrentIndex(self.idd_prefix_button.findText(item_text))
item_text = account.pstn.prefix or 'None'
index = self.prefix_button.findText(item_text)
if index == -1:
self.prefix_button.addItem(item_text)
self.prefix_button.setCurrentIndex(self.prefix_button.findText(item_text))
self._update_pstn_example_label()
self.account_tls_cert_file_editor.setText(account.tls.certificate or u'')
self.account_tls_verify_server_button.setChecked(account.tls.verify_server)
self.load_in_progress = False
def show(self):
selection_model = self.account_list.selectionModel()
if not selection_model.selectedIndexes():
model = self.account_list.model()
account_manager = AccountManager()
default_account = account_manager.default_account
try:
index = (index for index, account in enumerate(model.accounts) if account==default_account).next()
except StopIteration:
index = 0
selection_model.select(model.index(index), selection_model.ClearAndSelect)
self._update_logs_size_label()
super(PreferencesWindow, self).show()
def show_for_accounts(self):
self.accounts_action.trigger()
self.show()
def show_add_account_dialog(self):
self.add_account_dialog.open_for_add()
def show_create_account_dialog(self):
self.add_account_dialog.open_for_create()
@staticmethod
def _normalize_binary_size(size):
"""Return a human friendly string representation of size as a power of 2"""
infinite = float('infinity')
boundaries = [( 1024, '%d bytes', 1),
( 10*1024, '%.2f KB', 1024.0), ( 1024*1024, '%.1f KB', 1024.0),
( 10*1024*1024, '%.2f MB', 1024*1024.0), (1024*1024*1024, '%.1f MB', 1024*1024.0),
(10*1024*1024*1024, '%.2f GB', 1024*1024*1024.0), ( infinite, '%.1f GB', 1024*1024*1024.0)]
for boundary, format, divisor in boundaries:
if size < boundary:
return format % (size/divisor,)
else:
return "%d bytes" % size
def _update_logs_size_label(self):
logs_size = 0
for path, dirs, files in os.walk(os.path.join(ApplicationData.directory, 'logs')):
for name in dirs:
try:
logs_size += os.stat(os.path.join(path, name)).st_size
except (OSError, IOError):
pass
for name in files:
try:
logs_size += os.stat(os.path.join(path, name)).st_size
except (OSError, IOError):
pass
self.log_files_size_label.setText(u"There are currently %s of log files" % self._normalize_binary_size(logs_size))
def _update_pstn_example_label(self):
prefix = self.prefix_button.currentText()
idd_prefix = self.idd_prefix_button.currentText()
self.pstn_example_transformed_label.setText(u"%s%s442079460000" % ('' if prefix=='None' else prefix, idd_prefix))
# Signal handlers
#
def _SH_ToolbarActionTriggered(self, action):
if action == self.logging_action:
self._update_logs_size_label()
self.pages.setCurrentIndex(action.index)
def _SH_AccountListSelectionChanged(self, selected, deselected):
try:
selected_index = self.account_list.selectionModel().selectedIndexes()[0]
except IndexError:
self.delete_account_button.setEnabled(False)
self.account_tab_widget.setEnabled(False)
else:
selected_account = self.account_list.model().data(selected_index, Qt.UserRole).account
self.delete_account_button.setEnabled(selected_account is not BonjourAccount())
tab_widget = self.account_tab_widget
tab_widget.setEnabled(True)
if selected_account is BonjourAccount():
tab_widget.removeTab(tab_widget.indexOf(self.server_settings_tab))
tab_widget.removeTab(tab_widget.indexOf(self.network_tab))
tab_widget.removeTab(tab_widget.indexOf(self.advanced_tab))
self.password_label.hide()
self.password_editor.hide()
else:
if tab_widget.indexOf(self.server_settings_tab) == -1:
tab_widget.addTab(self.server_settings_tab, u"Server Settings")
if tab_widget.indexOf(self.network_tab) == -1:
tab_widget.addTab(self.network_tab, u"Network")
if tab_widget.indexOf(self.advanced_tab) == -1:
tab_widget.addTab(self.advanced_tab, u"Advanced")
self.password_label.show()
self.password_editor.show()
self.voicemail_uri_editor.inactiveText = u"Discovered by subscribing to %s" % selected_account.id
self.xcap_root_editor.inactiveText = u"Taken from the DNS TXT record for xcap.%s" % selected_account.id.domain
self.load_account_settings(selected_account)
def _SH_DeleteAccountButtonClicked(self):
model = self.account_list.model()
selected_index = self.account_list.selectionModel().selectedIndexes()[0]
selected_account = model.data(selected_index, Qt.UserRole).account
title, message = u"Remove Account", u"Permanently remove account %s?" % selected_account.id
if QMessageBox.question(self, title, message, QMessageBox.Ok|QMessageBox.Cancel) == QMessageBox.Cancel:
return
account_manager = AccountManager()
if account_manager.default_account is selected_account:
active_accounts = [account_info.account for account_info in model.accounts if account_info.account.enabled]
position = active_accounts.index(selected_account)
if position < len(active_accounts)-1:
account_manager.default_account = active_accounts[position+1]
elif position > 0:
account_manager.default_account = active_accounts[position-1]
else:
account_manager.default_account = None
try:
os.unlink(selected_account.tls.certificate.normalized)
except (AttributeError, OSError, IOError):
pass
selected_account.delete()
# Account information
def _SH_AccountEnabledButtonClicked(self, checked):
account = self.selected_account
account.enabled = checked
account.save()
def _SH_DisplayNameEditorEditingFinished(self):
account = self.selected_account
display_name = unicode(self.display_name_editor.text()) or None
if account.display_name != display_name:
account.display_name = display_name
account.save()
def _SH_PasswordEditorEditingFinished(self):
account = self.selected_account
password = unicode(self.password_editor.text())
if account.auth.password != password:
account.auth.password = password
account.save()
# Account media settings
def _SH_AccountAudioCodecsListItemChanged(self, item):
if not self.load_in_progress:
account = self.selected_account
items = [self.account_audio_codecs_list.item(row) for row in xrange(self.account_audio_codecs_list.count())]
account.rtp.audio_codec_list = [str(item.text()) for item in items if item.checkState()==Qt.Checked]
account.rtp.audio_codec_order = [str(item.text()) for item in items]
account.save()
def _SH_AccountAudioCodecsListModelRowsInserted(self, parent, start, end):
if not self.load_in_progress:
account = self.selected_account
items = [self.account_audio_codecs_list.item(row) for row in xrange(self.account_audio_codecs_list.count())]
account.rtp.audio_codec_order = [str(item.text()) for item in items]
account.rtp.audio_codec_list = [str(item.text()) for item in items if item.checkState()==Qt.Checked]
account.save()
def _SH_AccountAudioCodecsListModelRowsMoved(self, source_parent, source_start, source_end, dest_parent, dest_row):
account = self.selected_account
items = [self.account_audio_codecs_list.item(row) for row in xrange(self.account_audio_codecs_list.count())]
account.rtp.audio_codec_order = [str(item.text()) for item in items]
account.rtp.audio_codec_list = [str(item.text()) for item in items if item.checkState()==Qt.Checked]
account.save()
def _SH_ResetAudioCodecsButtonClicked(self, checked):
settings = SIPSimpleSettings()
account = self.selected_account
self.load_in_progress = True
self.account_audio_codecs_list.clear()
audio_codec_order = settings.rtp.audio_codec_order
audio_codec_list = settings.rtp.audio_codec_list
for codec in audio_codec_order:
item = QListWidgetItem(codec, self.account_audio_codecs_list)
item.setCheckState(Qt.Checked if codec in audio_codec_list else Qt.Unchecked)
self.load_in_progress = False
account.rtp.audio_codec_list = DefaultValue
account.rtp.audio_codec_order = DefaultValue
account.save()
def _SH_InbandDTMFButtonClicked(self, checked):
account = self.selected_account
account.rtp.inband_dtmf = checked
account.save()
def _SH_SRTPEncryptionButtonActivated(self, text):
account = self.selected_account
account.rtp.srtp_encryption = str(text)
account.save()
# Account server settings
def _SH_AlwaysUseMyProxyButtonClicked(self, checked):
account = self.selected_account
account.sip.always_use_my_proxy = checked
account.save()
def _SH_OutboundProxyHostEditorEditingFinished(self):
account = self.selected_account
outbound_proxy = self.account_outbound_proxy
if account.sip.outbound_proxy != outbound_proxy:
account.sip.outbound_proxy = outbound_proxy
account.save()
def _SH_OutboundProxyPortValueChanged(self, value):
if not self.load_in_progress:
account = self.selected_account
outbound_proxy = self.account_outbound_proxy
if account.sip.outbound_proxy != outbound_proxy:
account.sip.outbound_proxy = outbound_proxy
account.save()
def _SH_OutboundProxyTransportButtonActivated(self, text):
account = self.selected_account
outbound_proxy = self.account_outbound_proxy
if account.sip.outbound_proxy != outbound_proxy:
account.sip.outbound_proxy = outbound_proxy
account.save()
def _SH_AuthUsernameEditorEditingFinished(self):
account = self.selected_account
auth_username = unicode(self.auth_username_editor.text()) or None
if account.auth.username != auth_username:
account.auth.username = auth_username
account.save()
def _SH_AlwaysUseMyMSRPRelayButtonClicked(self, checked):
account = self.selected_account
account.nat_traversal.use_msrp_relay_for_outbound = checked
account.save()
def _SH_MSRPRelayHostEditorEditingFinished(self):
account = self.selected_account
msrp_relay = self.account_msrp_relay
if account.nat_traversal.msrp_relay != msrp_relay:
account.nat_traversal.msrp_relay = msrp_relay
account.save()
def _SH_MSRPRelayPortValueChanged(self, value):
if not self.load_in_progress:
account = self.selected_account
msrp_relay = self.account_msrp_relay
if account.nat_traversal.msrp_relay != msrp_relay:
account.nat_traversal.msrp_relay = msrp_relay
account.save()
def _SH_MSRPRelayTransportButtonActivated(self, text):
account = self.selected_account
msrp_relay = self.account_msrp_relay
if account.nat_traversal.msrp_relay != msrp_relay:
account.nat_traversal.msrp_relay = msrp_relay
account.save()
def _SH_VoicemailURIEditorEditingFinished(self):
account = self.selected_account
voicemail_uri = unicode(self.voicemail_uri_editor.text()) or None
if account.message_summary.voicemail_uri != voicemail_uri:
account.message_summary.voicemail_uri = voicemail_uri
account.save()
def _SH_XCAPRootEditorEditingFinished(self):
account = self.selected_account
xcap_root = unicode(self.xcap_root_editor.text()) or None
if account.xcap.xcap_root != xcap_root:
account.xcap.xcap_root = xcap_root
account.save()
def _SH_ServerToolsURLEditorEditingFinished(self):
account = self.selected_account
url = unicode(self.server_tools_url_editor.text()) or None
if account.server.settings_url != url:
account.server.settings_url = url
account.save()
# Account network settings
def _SH_UseICEButtonClicked(self, checked):
account = self.selected_account
account.nat_traversal.use_ice = checked
account.save()
def _SH_MSRPTransportButtonActivated(self, text):
account = self.selected_account
account.msrp.transport = str(text).lower()
account.save()
# Account advanced settings
def _SH_RegisterIntervalValueChanged(self, value):
if not self.load_in_progress:
account = self.selected_account
account.sip.register_interval = value
account.save()
def _SH_PublishIntervalValueChanged(self, value):
if not self.load_in_progress:
account = self.selected_account
account.sip.publish_interval = value
account.save()
def _SH_SubscribeIntervalValueChanged(self, value):
if not self.load_in_progress:
account = self.selected_account
account.sip.subscribe_interval = value
account.save()
def _SH_ReregisterButtonClicked(self):
account = self.selected_account
account.reregister()
def _SH_IDDPrefixButtonActivated(self, text):
self._update_pstn_example_label()
account = self.selected_account
idd_prefix = None if text=='+' else str(text)
if account.pstn.idd_prefix != idd_prefix:
account.pstn.idd_prefix = idd_prefix
account.save()
def _SH_PrefixButtonActivated(self, text):
self._update_pstn_example_label()
account = self.selected_account
prefix = None if text=='None' else str(text)
if account.pstn.prefix != prefix:
account.pstn.prefix = prefix
account.save()
def _SH_AccountTLSCertFileEditorLocationCleared(self):
account = self.selected_account
account.tls.certificate = None
account.save()
def _SH_AccountTLSCertFileBrowseButtonClicked(self, checked):
# TODO: open the file selection dialog in non-modal mode (and the error messages boxes as well). -Dan
account = self.selected_account
directory = os.path.dirname(account.tls.certificate.normalized) if account.tls.certificate else os.path.expanduser('~')
cert_path = unicode(QFileDialog.getOpenFileName(self, u'Select Certificate File', directory, u"TLS certificates (*.crt *.pem)")) or None
if cert_path is not None:
cert_path = os.path.normpath(cert_path)
if cert_path != account.tls.certificate:
try:
X509Certificate(open(cert_path).read())
except (OSError, IOError), e:
QMessageBox.critical(self, u"TLS Certificate Error", u"The certificate file could not be opened: %s" % e.strerror)
except GNUTLSError, e:
QMessageBox.critical(self, u"TLS Certificate Error", u"The certificate file is invalid: %s" % e)
else:
self.account_tls_cert_file_editor.setText(cert_path)
account.tls.certificate = cert_path
account.save()
def _SH_AccountTLSVerifyServerButtonClicked(self, checked):
account = self.selected_account
account.tls.verify_server = checked
account.save()
# Audio devices signal handlers
def _SH_AudioAlertDeviceButtonActivated(self, index):
device = self.audio_alert_device_button.itemData(index).toPyObject()
settings = SIPSimpleSettings()
settings.audio.alert_device = device
call_in_auxiliary_thread(settings.save) # temp until done in middleware -Dan
def _SH_AudioInputDeviceButtonActivated(self, index):
device = self.audio_input_device_button.itemData(index).toPyObject()
settings = SIPSimpleSettings()
settings.audio.input_device = device
call_in_auxiliary_thread(settings.save) # temp until done in middleware -Dan
def _SH_AudioOutputDeviceButtonActivated(self, index):
device = self.audio_output_device_button.itemData(index).toPyObject()
settings = SIPSimpleSettings()
settings.audio.output_device = device
call_in_auxiliary_thread(settings.save) # temp until done in middleware -Dan
def _SH_AudioSampleRateButtonActivated(self, text):
settings = SIPSimpleSettings()
settings.audio.sample_rate = str(text)
call_in_auxiliary_thread(settings.save) # temp until done in middleware -Dan
def _SH_EnableEchoCancellingButtonClicked(self, checked):
settings = SIPSimpleSettings()
settings.audio.tail_length = 200 if checked else 0
settings.save()
# Audio codecs signal handlers
def _SH_AudioCodecsListItemChanged(self, item):
if not self.load_in_progress:
settings = SIPSimpleSettings()
item_iterator = (self.audio_codecs_list.item(row) for row in xrange(self.audio_codecs_list.count()))
settings.rtp.audio_codec_list = [str(item.text()) for item in item_iterator if item.checkState()==Qt.Checked]
settings.save()
def _SH_AudioCodecsListModelRowsInserted(self, parent, start, end):
if not self.load_in_progress:
settings = SIPSimpleSettings()
items = [self.audio_codecs_list.item(row) for row in xrange(self.audio_codecs_list.count())]
settings.rtp.audio_codec_order = [str(item.text()) for item in items]
settings.rtp.audio_codec_list = [str(item.text()) for item in items if item.checkState()==Qt.Checked]
settings.save()
def _SH_AudioCodecsListModelRowsMoved(self, source_parent, source_start, source_end, dest_parent, dest_row):
settings = SIPSimpleSettings()
items = [self.audio_codecs_list.item(row) for row in xrange(self.audio_codecs_list.count())]
settings.rtp.audio_codec_order = [str(item.text()) for item in items]
settings.rtp.audio_codec_list = [str(item.text()) for item in items if item.checkState()==Qt.Checked]
settings.save()
# Answering machine signal handlers
def _SH_EnableAnsweringMachineButtonClicked(self, checked):
settings = SIPSimpleSettings()
settings.answering_machine.enabled = checked
settings.save()
def _SH_AnswerDelayValueChanged(self, value):
if value == 0:
self.answer_delay_seconds_label.setText(u'')
elif value == 1:
self.answer_delay_seconds_label.setText(u'second')
else:
self.answer_delay_seconds_label.setText(u'seconds')
settings = SIPSimpleSettings()
if not self.load_in_progress and settings.answering_machine.answer_delay != value:
settings.answering_machine.answer_delay = value
settings.save()
def _SH_MaxRecordingValueChanged(self, value):
self.max_recording_minutes_label.setText(u'minute' if value==1 else u'minutes')
settings = SIPSimpleSettings()
if not self.load_in_progress and settings.answering_machine.max_recording != value:
settings.answering_machine.max_recording = value
settings.save()
# Chat and SMS signal handlers
def _SH_AutoAcceptChatButtonClicked(self, checked):
settings = SIPSimpleSettings()
settings.chat.auto_accept = checked
settings.save()
def _SH_SMSReplicationButtonClicked(self, checked):
settings = SIPSimpleSettings()
settings.chat.sms_replication = checked
settings.save()
# File transfer signal handlers
def _SH_AutoAcceptFilesButtonClicked(self, checked):
settings = SIPSimpleSettings()
settings.file_transfer.auto_accept = checked
settings.save()
def _SH_DownloadDirectoryEditorLocationCleared(self):
settings = SIPSimpleSettings()
settings.file_transfer.directory = None
settings.save()
def _SH_DownloadDirectoryBrowseButtonClicked(self, checked):
# TODO: open the file selection dialog in non-modal mode. Same for the one for TLS CA list and the IconSelector from contacts. -Dan
settings = SIPSimpleSettings()
directory = settings.file_transfer.directory or os.path.expanduser('~')
directory = unicode(QFileDialog.getExistingDirectory(self, u'Select Download Directory', directory)) or None
if directory is not None:
directory = os.path.normpath(directory)
if directory != settings.file_transfer.directory:
self.download_directory_editor.setText(directory)
settings.file_transfer.directory = directory
settings.save()
# Alerts signal handlers
def _SH_SilenceAlertsButtonClicked(self, checked):
settings = SIPSimpleSettings()
settings.audio.silent = checked
settings.save()
def _SH_MessageAlertsButtonClicked(self, checked):
settings = SIPSimpleSettings()
settings.sounds.play_message_alerts = checked
settings.save()
def _SH_FileAlertsButtonClicked(self, checked):
settings = SIPSimpleSettings()
settings.sounds.play_file_alerts = checked
settings.save()
# File logging signal handlers
def _SH_TraceSIPButtonClicked(self, checked):
settings = SIPSimpleSettings()
settings.logs.trace_sip = checked
settings.save()
def _SH_TraceMSRPButtonClicked(self, checked):
settings = SIPSimpleSettings()
settings.logs.trace_msrp = checked
settings.save()
def _SH_TraceXCAPButtonClicked(self, checked):
settings = SIPSimpleSettings()
settings.logs.trace_xcap = checked
settings.save()
def _SH_TraceNotificationsButtonClicked(self, checked):
settings = SIPSimpleSettings()
settings.logs.trace_notifications = checked
settings.save()
def _SH_TracePJSIPButtonClicked(self, checked):
settings = SIPSimpleSettings()
settings.logs.trace_pjsip = checked
settings.save()
def _SH_PJSIPTraceLevelValueChanged(self, value):
settings = SIPSimpleSettings()
if not self.load_in_progress and settings.logs.pjsip_level != value:
settings.logs.pjsip_level = value
settings.save()
@run_in_auxiliary_thread
def _SH_ClearLogFilesButtonClicked(self):
log_manager = LogManager()
log_manager.stop()
for path, dirs, files in os.walk(os.path.join(ApplicationData.directory, 'logs'), topdown=False):
for name in files:
try:
os.remove(os.path.join(path, name))
except (OSError, IOError):
pass
for name in dirs:
try:
os.rmdir(os.path.join(path, name))
except (OSError, IOError):
pass
log_manager.start()
call_in_gui_thread(self._update_logs_size_label)
# SIP and RTP signal handlers
def _SH_SIPTransportsButtonClicked(self, button):
settings = SIPSimpleSettings()
settings.sip.transport_list = [button.name for button in self.sip_transports_button_group.buttons() if button.isChecked()]
settings.save()
def _SH_UDPPortValueChanged(self, value):
settings = SIPSimpleSettings()
if not self.load_in_progress and settings.sip.udp_port != value:
settings.sip.udp_port = value
settings.save()
def _SH_TCPPortValueChanged(self, value):
settings = SIPSimpleSettings()
if not self.load_in_progress and settings.sip.tcp_port != value:
settings.sip.tcp_port = value
settings.save()
def _SH_TLSPortValueChanged(self, value):
settings = SIPSimpleSettings()
if not self.load_in_progress and settings.sip.tls_port != value:
settings.sip.tls_port = value
settings.save()
def _SH_MediaPortsStartValueChanged(self, value):
self.media_ports.setMaximum(limit(65535-value, min=10, max=10000))
settings = SIPSimpleSettings()
port_range = PortRange(value, value + self.media_ports.value())
if not self.load_in_progress and settings.rtp.port_range != port_range:
settings.rtp.port_range = port_range
settings.save()
def _SH_MediaPortsValueChanged(self, value):
self.media_ports_start.setMaximum(limit(65535-value, min=10000, max=65000))
settings = SIPSimpleSettings()
port_range = PortRange(self.media_ports_start.value(), self.media_ports_start.value() + value)
if not self.load_in_progress and settings.rtp.port_range != port_range:
settings.rtp.port_range = port_range
settings.save()
def _SH_SessionTimeoutValueChanged(self, value):
settings = SIPSimpleSettings()
if not self.load_in_progress and settings.sip.invite_timeout != value:
settings.sip.invite_timeout = value
settings.save()
def _SH_RTPTimeoutValueChanged(self, value):
if value == 0:
self.rtp_timeout_seconds_label.setText(u'')
elif value == 1:
self.rtp_timeout_seconds_label.setText(u'second')
else:
self.rtp_timeout_seconds_label.setText(u'seconds')
settings = SIPSimpleSettings()
if not self.load_in_progress and settings.rtp.timeout != value:
settings.rtp.timeout = value
settings.save()
# TLS signal handlers
def _SH_TLSCAFileEditorLocationCleared(self):
settings = SIPSimpleSettings()
settings.tls.ca_list = None
settings.save()
def _SH_TLSCAFileBrowseButtonClicked(self):
# TODO: open the file selection dialog in non-modal mode (and the error messages boxes as well). -Dan
settings = SIPSimpleSettings()
directory = os.path.dirname(settings.tls.ca_list.normalized) if settings.tls.ca_list else os.path.expanduser('~')
ca_path = unicode(QFileDialog.getOpenFileName(self, u'Select Certificate Authority File', directory, u"TLS certificates (*.crt *.pem)")) or None
if ca_path is not None:
ca_path = os.path.normpath(ca_path)
if ca_path != settings.tls.ca_list:
try:
X509Certificate(open(ca_path).read())
except (OSError, IOError), e:
QMessageBox.critical(self, u"TLS Certificate Error", u"The certificate authority file could not be opened: %s" % e.strerror)
except GNUTLSError, e:
QMessageBox.critical(self, u"TLS Certificate Error", u"The certificate authority file is invalid: %s" % e)
else:
self.tls_ca_file_editor.setText(ca_path)
settings.tls.ca_list = ca_path
settings.save()
def _SH_TLSTimeoutValueChanged(self, value):
self.tls_timeout_seconds_label.setText(u'second' if value==1 else u'seconds')
settings = SIPSimpleSettings()
timeout = value * 1000
if not self.load_in_progress and settings.tls.timeout != timeout:
settings.tls.timeout = timeout
settings.save()
@run_in_gui_thread
def handle_notification(self, notification):
handler = getattr(self, '_NH_%s' % notification.name, Null)
handler(notification)
def _NH_SIPApplicationDidStart(self, notification):
self._sync_defaults()
self.load_settings()
notification_center = NotificationCenter()
notification_center.add_observer(self, name='AudioDevicesDidChange')
notification_center.add_observer(self, name='CFGSettingsObjectDidChange')
def _NH_AudioDevicesDidChange(self, notification):
self.load_audio_devices()
def _NH_CFGSettingsObjectDidChange(self, notification):
settings = SIPSimpleSettings()
if notification.sender is settings:
if 'audio.silent' in notification.data.modified:
self.silence_alerts_button.setChecked(settings.audio.silent)
if 'audio.alert_device' in notification.data.modified:
self.audio_alert_device_button.setCurrentIndex(self.audio_alert_device_button.findData(QVariant(settings.audio.alert_device)))
if 'audio.input_device' in notification.data.modified:
self.audio_input_device_button.setCurrentIndex(self.audio_input_device_button.findData(QVariant(settings.audio.input_device)))
if 'audio.output_device' in notification.data.modified:
self.audio_output_device_button.setCurrentIndex(self.audio_output_device_button.findData(QVariant(settings.audio.output_device)))
if 'answering_machine.enabled' in notification.data.modified:
self.enable_answering_machine_button.setChecked(settings.answering_machine.enabled)
if 'chat.auto_accept' in notification.data.modified:
self.auto_accept_chat_button.setChecked(settings.chat.auto_accept)
if 'file_transfer.auto_accept' in notification.data.modified:
self.auto_accept_files_button.setChecked(settings.file_transfer.auto_accept)
elif isinstance(notification.sender, (Account, BonjourAccount)) and notification.sender is self.selected_account:
account = notification.sender
if 'enabled' in notification.data.modified:
self.account_enabled_button.setChecked(account.enabled)
self.reregister_button.setEnabled(account.enabled)
if 'display_name' in notification.data.modified:
self.display_name_editor.setText(account.display_name or u'')
if 'rtp.audio_codec_list' in notification.data.modified:
self.reset_account_audio_codecs_button.setEnabled(account.rtp.audio_codec_list is not None)
del ui_class, base_class
......@@ -909,9 +909,6 @@ padding: 2px;</string>
<addaction name="separator"/>
</widget>
<widget class="QMenu" name="quick_settings_menu">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
<string>Quick &amp;settings</string>
</property>
......@@ -1083,9 +1080,6 @@ padding: 2px;</string>
</property>
</action>
<action name="preferences_action">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset>
<normaloff>icons/configure.png</normaloff>icons/configure.png</iconset>
......@@ -1140,9 +1134,6 @@ padding: 2px;</string>
<property name="checkable">
<bool>true</bool>
</property>
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Enable &amp;Answering Machine</string>
</property>
......@@ -1248,9 +1239,6 @@ padding: 2px;</string>
</property>
</action>
<action name="manage_accounts_action">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Manage accounts...</string>
</property>
......
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48px"
height="48px"
id="svg2816"
version="1.1"
inkscape:version="0.47 r22583"
sodipodi:docname="playback.svg">
<defs
id="defs2818">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 24 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="48 : 24 : 1"
inkscape:persp3d-origin="24 : 16 : 1"
id="perspective2824" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="7"
inkscape:cx="23.734792"
inkscape:cy="24"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1284"
inkscape:window-height="833"
inkscape:window-x="180"
inkscape:window-y="220"
inkscape:window-maximized="0" />
<metadata
id="metadata2821">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<path
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:2.91106749;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 11.455534,9.4555332 C 11.58353,38.544466 11.58353,38.544466 11.58353,38.544466 L 38.544468,24.190839 11.455534,9.4555332 z"
id="path2828" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48px"
height="48px"
id="svg2816"
version="1.1"
inkscape:version="0.47 r22583"
sodipodi:docname="record48.svg">
<defs
id="defs2818">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 24 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="48 : 24 : 1"
inkscape:persp3d-origin="24 : 16 : 1"
id="perspective2824" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4"
inkscape:cx="23.452561"
inkscape:cy="24"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1121"
inkscape:window-height="836"
inkscape:window-x="395"
inkscape:window-y="238"
inkscape:window-maximized="0" />
<metadata
id="metadata2821">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<path
sodipodi:type="arc"
style="fill:#f00000;fill-opacity:1;fill-rule:nonzero;stroke:#a20000;stroke-width:2.2054215;stroke-linecap:butt;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
id="path2826"
sodipodi:cx="9.7142859"
sodipodi:cy="12.714286"
sodipodi:rx="17.142857"
sodipodi:ry="17.142857"
d="m 26.857142,12.714286 a 17.142857,17.142857 0 1 1 -34.2857127,0 17.142857,17.142857 0 1 1 34.2857127,0 z"
transform="matrix(0.87692988,0,0,0.87692987,15.481253,12.850463)" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32px"
height="32px"
id="svg2816"
version="1.1"
inkscape:version="0.47 r22583"
sodipodi:docname="plus.svg">
<defs
id="defs2818">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 16 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="32 : 16 : 1"
inkscape:persp3d-origin="16 : 10.666667 : 1"
id="perspective2824" />
<inkscape:perspective
id="perspective3620"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="11.313708"
inkscape:cx="10.038652"
inkscape:cy="15.230556"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1140"
inkscape:window-height="842"
inkscape:window-x="433"
inkscape:window-y="195"
inkscape:window-maximized="0" />
<metadata
id="metadata2821">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<rect
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="rect2826-0"
width="2"
height="16"
x="15"
y="-24"
transform="matrix(0,1,-1,0,0,0)" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="18"
height="18"
id="svg2816"
version="1.1"
inkscape:version="0.47 r22583"
sodipodi:docname="plus18.svg">
<defs
id="defs2818">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 8 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="16 : 8 : 1"
inkscape:persp3d-origin="8 : 5.3333333 : 1"
id="perspective2824" />
<inkscape:perspective
id="perspective2836"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.627417"
inkscape:cx="8"
inkscape:cy="8"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="884"
inkscape:window-height="748"
inkscape:window-x="593"
inkscape:window-y="230"
inkscape:window-maximized="0" />
<metadata
id="metadata2821">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer"
transform="translate(0,2)">
<rect
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="rect2826-0"
width="2"
height="8"
x="6"
y="-13"
transform="matrix(0,1,-1,0,0,0)" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>preferences_window</class>
<widget class="QMainWindow" name="preferences_window">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>Blink Preferences</string>
</property>
<property name="windowIcon">
<iconset>
<normaloff>icons/blink48.png</normaloff>icons/blink48.png</iconset>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="window_layout">
<property name="margin">
<number>3</number>
</property>
<item row="0" column="0">
<widget class="QStackedWidget" name="pages">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="accounts_page">
<layout class="QGridLayout" name="accounts_layout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="verticalSpacing">
<number>2</number>
</property>
<item row="0" column="0">
<widget class="AccountListView" name="account_list">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QWidget" name="account_buttons_widget" native="true">
<layout class="QHBoxLayout" name="account_buttons_layout">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QToolButton" name="add_account_button">
<property name="minimumSize">
<size>
<width>29</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>29</width>
<height>24</height>
</size>
</property>
<property name="icon">
<iconset>
<normaloff>icons/plus18.svg</normaloff>icons/plus18.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>18</width>
<height>18</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="delete_account_button">
<property name="minimumSize">
<size>
<width>29</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>29</width>
<height>24</height>
</size>
</property>
<property name="icon">
<iconset>
<normaloff>icons/minus18.svg</normaloff>icons/minus18.svg</iconset>
</property>
</widget>
</item>
<item>
<spacer name="account_buttons_spacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QTabWidget" name="account_tab_widget">
<property name="currentIndex">
<number>4</number>
</property>
<widget class="QWidget" name="account_information_tab">
<attribute name="title">
<string>Account Information</string>
</attribute>
<layout class="QGridLayout" name="account_information_tab_layout">
<property name="margin">
<number>10</number>
</property>
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="account_enabled_button">
<property name="text">
<string>Use account</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<spacer name="account_information_spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0" colspan="2">
<widget class="Line" name="line">
<property name="minimumSize">
<size>
<width>0</width>
<height>10</height>
</size>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="display_name_label">
<property name="text">
<string>Display Name:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="display_name_editor"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="password_label">
<property name="text">
<string>Password:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="password_editor">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<spacer name="account_information_spacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>25</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="media_tab">
<attribute name="title">
<string>Media</string>
</attribute>
<layout class="QGridLayout" name="media_tab_layout">
<property name="margin">
<number>10</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="account_audio_codecs_label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Audio Codecs</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="account_video_codecs_label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Video Codecs</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="Line" name="line_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="Line" name="line_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QListWidget" name="account_audio_codecs_list">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum>
</property>
<item>
<property name="text">
<string>G722</string>
</property>
<property name="checkState">
<enum>Checked</enum>
</property>
</item>
<item>
<property name="text">
<string>speex</string>
</property>
<property name="checkState">
<enum>Checked</enum>
</property>
</item>
<item>
<property name="text">
<string>GSM</string>
</property>
<property name="checkState">
<enum>Unchecked</enum>
</property>
</item>
<item>
<property name="text">
<string>iLBC</string>
</property>
<property name="checkState">
<enum>Unchecked</enum>
</property>
</item>
<item>
<property name="text">
<string>PCMU</string>
</property>
<property name="checkState">
<enum>Checked</enum>
</property>
</item>
<item>
<property name="text">
<string>PCMA</string>
</property>
<property name="checkState">
<enum>Checked</enum>
</property>
</item>
</widget>
</item>
<item row="2" column="1">
<widget class="QListWidget" name="account_video_codecs_list">
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QWidget" name="reset_buttons_widget" native="true">
<layout class="QHBoxLayout" name="reset_buttons_widget_layout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="reset_account_audio_codecs_button">
<property name="text">
<string>Reset</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="codecs_note_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Note: drag codecs to change their order</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="reset_account_video_codecs_button">
<property name="text">
<string>Reset</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="4" column="0" colspan="2">
<spacer name="media_spacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>25</height>
</size>
</property>
</spacer>
</item>
<item row="8" column="0" colspan="2">
<spacer name="media_spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="0" colspan="2">
<widget class="QWidget" name="rtp_options_widget" native="true">
<layout class="QGridLayout" name="rtp_options_widget_layout">
<property name="margin">
<number>0</number>
</property>
<item row="0" column="0" colspan="3">
<widget class="QLabel" name="rtp_options_label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>RTP Options</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="3">
<widget class="Line" name="line_8">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="srtp_encryption_button">
<item>
<property name="text">
<string>disabled</string>
</property>
</item>
<item>
<property name="text">
<string>optional</string>
</property>
</item>
<item>
<property name="text">
<string>mandatory</string>
</property>
</item>
</widget>
</item>
<item row="3" column="2">
<spacer name="srtp_spacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="0">
<widget class="QLabel" name="srtp_encryption_label">
<property name="text">
<string>sRTP Encryption:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QCheckBox" name="inband_dtmf_button">
<property name="text">
<string>Send inband DTMF</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="server_settings_tab">
<attribute name="title">
<string>Server Settings</string>
</attribute>
<layout class="QGridLayout" name="server_settings_tab_layout">
<property name="margin">
<number>10</number>
</property>
<item row="0" column="0" colspan="6">
<widget class="QLabel" name="sip_proxy_label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>SIP Proxy</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="6">
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="2" column="1" colspan="5">
<widget class="QCheckBox" name="always_use_my_proxy_button">
<property name="text">
<string>Always use my proxy for outgoing sessions</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="outbound_proxy_label">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Outbound Proxy:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="outbound_proxy_host_editor"/>
</item>
<item row="3" column="2">
<widget class="QLabel" name="outbound_proxy_port_label">
<property name="text">
<string>Port:</string>
</property>
<property name="indent">
<number>3</number>
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="QSpinBox" name="outbound_proxy_port">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>5060</number>
</property>
</widget>
</item>
<item row="3" column="4">
<widget class="QLabel" name="outbound_proxy_transport_label">
<property name="text">
<string>Transport:</string>
</property>
<property name="indent">
<number>3</number>
</property>
</widget>
</item>
<item row="3" column="5">
<widget class="QComboBox" name="outbound_proxy_transport_button">
<item>
<property name="text">
<string>UDP</string>
</property>
</item>
<item>
<property name="text">
<string>TCP</string>
</property>
</item>
<item>
<property name="text">
<string>TLS</string>
</property>
</item>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="auth_username_label">
<property name="text">
<string>Auth Username:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="auth_username_editor"/>
</item>
<item row="5" column="0" colspan="6">
<spacer name="server_settings_spacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>25</height>
</size>
</property>
</spacer>
</item>
<item row="6" column="0" colspan="6">
<widget class="QLabel" name="msrp_label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>MSRP Relay</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="6">
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="8" column="1" colspan="5">
<widget class="QCheckBox" name="always_use_my_msrp_relay_button">
<property name="text">
<string>Always use my relay for outgoing sessions</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="msrp_relay_label">
<property name="text">
<string>MSRP Relay:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="LineEdit" name="msrp_relay_host_editor">
<property name="inactiveText" stdset="0">
<string>Relay address taken from DNS</string>
</property>
<property name="widgetSpacing" stdset="0">
<number>0</number>
</property>
</widget>
</item>
<item row="9" column="2">
<widget class="QLabel" name="msrp_relay_port_label">
<property name="text">
<string>Port:</string>
</property>
<property name="indent">
<number>3</number>
</property>
</widget>
</item>
<item row="9" column="3">
<widget class="QSpinBox" name="msrp_relay_port">
<property name="specialValueText">
<string>2855</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="9" column="4">
<widget class="QLabel" name="msrp_relay_transport_label">
<property name="text">
<string>Transport:</string>
</property>
<property name="indent">
<number>3</number>
</property>
</widget>
</item>
<item row="9" column="5">
<widget class="QComboBox" name="msrp_relay_transport_button">
<item>
<property name="text">
<string>TLS</string>
</property>
</item>
<item>
<property name="text">
<string>TCP</string>
</property>
</item>
</widget>
</item>
<item row="10" column="0" colspan="6">
<spacer name="server_settings_spacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>25</height>
</size>
</property>
</spacer>
</item>
<item row="11" column="0" colspan="6">
<widget class="QLabel" name="voicemail_label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Extra Server Settings</string>
</property>
</widget>
</item>
<item row="12" column="0" colspan="6">
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="13" column="0">
<widget class="QLabel" name="voicemail_uri_label">
<property name="text">
<string>Voicemail URI:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="13" column="1" colspan="5">
<widget class="LineEdit" name="voicemail_uri_editor">
<property name="inactiveText" stdset="0">
<string>Discovered by subscribing to myself</string>
</property>
<property name="widgetSpacing" stdset="0">
<number>0</number>
</property>
</widget>
</item>
<item row="14" column="0">
<widget class="QLabel" name="xcap_root_label">
<property name="text">
<string>XCAP Root URL:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="14" column="1" colspan="5">
<widget class="LineEdit" name="xcap_root_editor">
<property name="inactiveText" stdset="0">
<string>Taken from the DNS TXT record for xcap.domain</string>
</property>
<property name="widgetSpacing" stdset="0">
<number>0</number>
</property>
</widget>
</item>
<item row="15" column="0">
<widget class="QLabel" name="server_tools_url_label">
<property name="text">
<string>Server Tools URL:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="15" column="1" colspan="5">
<widget class="QLineEdit" name="server_tools_url_editor"/>
</item>
<item row="16" column="0" colspan="6">
<spacer name="server_settings_spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="network_tab">
<attribute name="title">
<string>Network</string>
</attribute>
<layout class="QGridLayout" name="network_tab_layout">
<property name="margin">
<number>10</number>
</property>
<item row="2" column="1" colspan="2">
<widget class="QCheckBox" name="use_ice_button">
<property name="text">
<string>Use ICE to improve NAT traversal capabilities for media streams</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="msrp_transport_label">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>MSRP Transport:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="msrp_transport_button">
<property name="minimumSize">
<size>
<width>70</width>
<height>0</height>
</size>
</property>
<item>
<property name="text">
<string>TLS</string>
</property>
</item>
<item>
<property name="text">
<string>TCP</string>
</property>
</item>
</widget>
</item>
<item row="3" column="2">
<spacer name="msrp_transport_spacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0" colspan="3">
<spacer name="account_network_spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0" colspan="3">
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Network Settings</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="3">
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="advanced_tab">
<attribute name="title">
<string>Advanced</string>
</attribute>
<layout class="QGridLayout" name="advanced_tab_layout">
<property name="margin">
<number>10</number>
</property>
<item row="7" column="0" colspan="3">
<spacer name="account_tls_settings_spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0" colspan="3">
<widget class="QLabel" name="account_tls_settings_label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>TLS Settings</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="3">
<widget class="Line" name="line_11">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="account_tls_cert_file_label">
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Certificate File:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="LocationBar" name="account_tls_cert_file_editor">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QPushButton" name="account_tls_cert_file_browse_button">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
<item row="5" column="1" colspan="2">
<widget class="QCheckBox" name="account_tls_verify_server_button">
<property name="text">
<string>Verify server</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<widget class="QWidget" name="sip_settings_widget" native="true">
<layout class="QGridLayout" name="sip_settings_widget_layout">
<property name="margin">
<number>0</number>
</property>
<item row="0" column="0" colspan="6">
<widget class="QLabel" name="sip_settings_label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>SIP Settings</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="6">
<spacer name="sip_settings_spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>25</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0" colspan="6">
<widget class="Line" name="line_9">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="publish_interval_seconds_label">
<property name="text">
<string>seconds</string>
</property>
</widget>
</item>
<item row="2" column="3">
<spacer name="sip_settings_spacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>10</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="subscribe_interval">
<property name="minimum">
<number>30</number>
</property>
<property name="maximum">
<number>20000</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
<property name="value">
<number>3600</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="publish_interval_label">
<property name="text">
<string>Publish interval:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="subscribe_interval_label">
<property name="text">
<string>Subscribe interval:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="register_interval_label">
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Register interval:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="register_interval_seconds_label">
<property name="text">
<string>seconds</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="register_interval">
<property name="minimum">
<number>30</number>
</property>
<property name="maximum">
<number>20000</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
<property name="value">
<number>600</number>
</property>
</widget>
</item>
<item row="2" column="4">
<widget class="QToolButton" name="reregister_button">
<property name="text">
<string>Re-register</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons/refresh.png</normaloff>icons/refresh.png</iconset>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="publish_interval">
<property name="minimum">
<number>30</number>
</property>
<property name="maximum">
<number>20000</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
<property name="value">
<number>3600</number>
</property>
</widget>
</item>
<item row="2" column="5">
<spacer name="sip_settings_spacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="2">
<widget class="QLabel" name="subscribe_interval_seconds_label">
<property name="text">
<string>seconds</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0" colspan="3">
<widget class="QWidget" name="pstn_widget" native="true">
<layout class="QGridLayout" name="pstn_widget_layout">
<property name="margin">
<number>0</number>
</property>
<item row="0" column="0" colspan="5">
<widget class="QLabel" name="pstn_label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Dialing landline and mobile numbers</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="5">
<spacer name="pstn_spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>25</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0" colspan="5">
<widget class="Line" name="line_10">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="idd_prefix_label">
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Replace preceding + with:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="idd_prefix_button">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>+</string>
</property>
</item>
<item>
<property name="text">
<string>00</string>
</property>
</item>
<item>
<property name="text">
<string>011</string>
</property>
</item>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="prefix_label">
<property name="text">
<string>External line prefix:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="prefix_button">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>0</string>
</property>
</item>
<item>
<property name="text">
<string>9</string>
</property>
</item>
</widget>
</item>
<item row="2" column="2" rowspan="2">
<widget class="QLabel" name="pstn_example_original_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>+442079460000</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="3" rowspan="2">
<widget class="QLabel" name="pstn_example_arrow_label">
<property name="maximumSize">
<size>
<width>42</width>
<height>32</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>icons/transform.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="margin">
<number>6</number>
</property>
</widget>
</item>
<item row="2" column="4" rowspan="2">
<widget class="QLabel" name="pstn_example_transformed_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>+442079460000</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="audio_page">
<layout class="QGridLayout" name="audio_layout">
<property name="margin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QGroupBox" name="audio_devices_group_box">
<property name="title">
<string>Audio Devices</string>
</property>
<layout class="QGridLayout" name="audio_devices_layout">
<item row="0" column="0">
<widget class="QLabel" name="audio_input_device_label">
<property name="minimumSize">
<size>
<width>105</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Input Device:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="audio_input_device_button"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="audio_output_device_label">
<property name="text">
<string>Output Device:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="audio_output_device_button"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="audio_alert_device_label">
<property name="text">
<string>Alert Device:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="audio_alert_device_button"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="audio_sample_rate_label">
<property name="text">
<string>Sample Rate:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="audio_sample_rate_button">
<item>
<property name="text">
<string>16000</string>
</property>
</item>
<item>
<property name="text">
<string>32000</string>
</property>
</item>
<item>
<property name="text">
<string>44100</string>
</property>
</item>
<item>
<property name="text">
<string>48000</string>
</property>
</item>
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="QCheckBox" name="enable_echo_cancelling_button">
<property name="text">
<string>Enable Echo Cancelling</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="4">
<spacer name="audio_spacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="2">
<spacer name="audio_spacer_1">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>10</width>
<height>22</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QGroupBox" name="audio_codecs_group_box">
<property name="title">
<string>Audio Codecs</string>
</property>
<layout class="QVBoxLayout" name="audio_codecs_layout">
<item>
<widget class="QListWidget" name="audio_codecs_list">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum>
</property>
<item>
<property name="text">
<string>G722</string>
</property>
<property name="checkState">
<enum>Checked</enum>
</property>
</item>
<item>
<property name="text">
<string>speex</string>
</property>
<property name="checkState">
<enum>Checked</enum>
</property>
</item>
<item>
<property name="text">
<string>GSM</string>
</property>
<property name="checkState">
<enum>Unchecked</enum>
</property>
</item>
<item>
<property name="text">
<string>iLBC</string>
</property>
<property name="checkState">
<enum>Unchecked</enum>
</property>
</item>
<item>
<property name="text">
<string>PCMU</string>
</property>
<property name="checkState">
<enum>Checked</enum>
</property>
</item>
<item>
<property name="text">
<string>PCMA</string>
</property>
<property name="checkState">
<enum>Checked</enum>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="audio_codecs_note">
<property name="text">
<string>Note: drag codecs to change their order</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="answering_machine_group_box">
<property name="title">
<string>Answering Machine</string>
</property>
<layout class="QGridLayout" name="answering_machine_layout">
<item row="0" column="1" colspan="3">
<widget class="QCheckBox" name="enable_answering_machine_button">
<property name="text">
<string>Enable Answering Machine</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="answer_delay_label">
<property name="text">
<string>Answer Delay:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="answer_delay">
<property name="specialValueText">
<string>None</string>
</property>
<property name="maximum">
<number>30</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="answer_delay_seconds_label">
<property name="text">
<string>seconds</string>
</property>
</widget>
</item>
<item row="1" column="3">
<spacer name="answer_delay_spacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0">
<widget class="QLabel" name="max_recording_label">
<property name="text">
<string>Maximum Recording:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="max_recording">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>30</number>
</property>
<property name="value">
<number>3</number>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="max_recording_minutes_label">
<property name="text">
<string>minutes</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="unavailable_message_label">
<property name="text">
<string>Unavailable Message:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="unavailable_message_button">
<item>
<property name="text">
<string>Built-in</string>
</property>
</item>
<item>
<property name="text">
<string>Custom</string>
</property>
</item>
</widget>
</item>
<item row="3" column="2">
<widget class="QWidget" name="unavailable_message_buttons_widget" native="true">
<layout class="QHBoxLayout" name="am_buttons_layout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QToolButton" name="unavailable_playback_button">
<property name="minimumSize">
<size>
<width>26</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>26</width>
<height>24</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>icons/media-playback-start.svg</normaloff>icons/media-playback-start.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>18</width>
<height>18</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="unavailable_record_button">
<property name="minimumSize">
<size>
<width>0</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>24</height>
</size>
</property>
<property name="text">
<string>Record</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons/media-record.svg</normaloff>icons/media-record.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>18</width>
<height>18</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="4" column="0" colspan="4">
<spacer name="answering_machine_spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>100</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="chat_page">
<layout class="QVBoxLayout" name="chat_layout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="chat_group_box">
<property name="title">
<string>Chat and SMS Settings</string>
</property>
<layout class="QVBoxLayout" name="chat_group_box_layout">
<item>
<widget class="QCheckBox" name="auto_accept_chat_button">
<property name="text">
<string>Automatically accept chat requests from known contacts</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="sms_replication_button">
<property name="text">
<string>Replicate sent SMS messages on my other Blink instances</string>
</property>
</widget>
</item>
<item>
<spacer name="chat_spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="file_transfers_page">
<layout class="QVBoxLayout" name="file_transfer_layout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="file_transfer_group_box">
<property name="title">
<string>File Transfer Settings</string>
</property>
<layout class="QGridLayout" name="file_transfer_box_layout">
<property name="horizontalSpacing">
<number>3</number>
</property>
<item row="0" column="1" colspan="2">
<widget class="QCheckBox" name="auto_accept_files_button">
<property name="text">
<string>Automatically accept file transfer requests from known contacts</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="download_directory_label">
<property name="text">
<string>Download Directory:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="LocationBar" name="download_directory_editor">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="download_directory_browse_button">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="3">
<spacer name="file_transfer_spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="alerts_page">
<layout class="QVBoxLayout" name="alerts_layout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="alerts_group_box">
<property name="title">
<string>Alert Settings</string>
</property>
<layout class="QGridLayout" name="alerts_group_box_layout">
<item row="0" column="0">
<widget class="QCheckBox" name="silence_alerts_button">
<property name="text">
<string>Silence all alerts (incoming ringtones, text message notifications and file transfer notifications)</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="message_alerts_button">
<property name="text">
<string>Play a sound when a chat or SMS message is sent or received</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="file_alerts_button">
<property name="text">
<string>Play a sound when a file transfer finishes</string>
</property>
</widget>
</item>
<item row="3" column="0">
<spacer name="alerts_spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="logging_page">
<layout class="QVBoxLayout" name="logging_layout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="logging_group_box">
<property name="title">
<string>File Logging Settings</string>
</property>
<layout class="QGridLayout" name="logging_group_box_layout">
<item row="0" column="0" colspan="5">
<widget class="QCheckBox" name="trace_sip_button">
<property name="text">
<string>Trace SIP</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="5">
<widget class="QCheckBox" name="trace_msrp_button">
<property name="text">
<string>Trace MSRP (used for chat, file transfer and desktop sharing)</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="5">
<widget class="QCheckBox" name="trace_xcap_button">
<property name="text">
<string>Trace XCAP (used by presence and for storing contacts)</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="5">
<widget class="QCheckBox" name="trace_notifications_button">
<property name="text">
<string>Trace Notifications</string>
</property>
</widget>
</item>
<item row="9" column="0" colspan="5">
<spacer name="file_logging_spacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="4">
<spacer name="trace_library_spacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="3">
<widget class="QSpinBox" name="pjsip_trace_level">
<property name="maximum">
<number>5</number>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QLabel" name="level_label">
<property name="text">
<string>Level:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<spacer name="trace_library_spacer_1">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>10</width>
<height>22</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="trace_pjsip_button">
<property name="text">
<string>Trace Core Library</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QPushButton" name="clear_log_files_button">
<property name="text">
<string>Clear log files</string>
</property>
</widget>
</item>
<item row="6" column="2" colspan="3">
<widget class="QLabel" name="log_files_size_label">
<property name="text">
<string>There are currently 0Mb of log files</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="5">
<spacer name="file_logging_spacer_1">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="6" column="1">
<spacer name="clear_log_files_spacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>10</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="advanced_page">
<layout class="QVBoxLayout" name="advanced_layout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="sip_group_box">
<property name="title">
<string>SIP and RTP settings</string>
</property>
<layout class="QGridLayout" name="sip_group_box_layout">
<item row="0" column="0">
<widget class="QLabel" name="transports_label">
<property name="minimumSize">
<size>
<width>130</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Transports:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="enable_udp_button">
<property name="text">
<string>Enable UDP</string>
</property>
</widget>
</item>
<item row="0" column="2">
<spacer name="transport_spacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="3">
<widget class="QLabel" name="udp_port_label">
<property name="text">
<string>UDP port:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="SIPPortEditor" name="udp_port">
<property name="specialValueText">
<string>Auto</string>
</property>
<property name="maximum">
<number>65535</number>
</property>
</widget>
</item>
<item row="0" column="5" rowspan="2">
<widget class="QLabel" name="port_note_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Note: set SIP ports to 0 for automatic allocation</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="margin">
<number>5</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="enable_tcp_button">
<property name="text">
<string>Enable TCP</string>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QLabel" name="tcp_port_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>TCP port:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="SIPPortEditor" name="tcp_port">
<property name="specialValueText">
<string>Auto</string>
</property>
<property name="maximum">
<number>65535</number>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="enable_tls_button">
<property name="text">
<string>Enable TLS</string>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QLabel" name="tls_port_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>TLS port:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="4">
<widget class="SIPPortEditor" name="tls_port">
<property name="specialValueText">
<string>Auto</string>
</property>
<property name="maximum">
<number>65535</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="media_ports_label">
<property name="toolTip">
<string>How many media ports to use and starting from what address</string>
</property>
<property name="text">
<string>RTP Ports:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="media_ports">
<property name="correctionMode">
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
<property name="value">
<number>500</number>
</property>
</widget>
</item>
<item row="3" column="2" colspan="2">
<widget class="QLabel" name="starting_at_label">
<property name="text">
<string>starting at:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="4">
<widget class="QSpinBox" name="media_ports_start">
<property name="minimum">
<number>10000</number>
</property>
<property name="maximum">
<number>65000</number>
</property>
<property name="singleStep">
<number>1000</number>
</property>
<property name="value">
<number>50000</number>
</property>
</widget>
</item>
<item row="4" column="0" colspan="6">
<spacer name="sip_spacer_1">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>13</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="0">
<widget class="QLabel" name="session_timeout_label">
<property name="text">
<string>Session Timeout:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="session_timeout">
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>180</number>
</property>
<property name="value">
<number>90</number>
</property>
</widget>
</item>
<item row="5" column="2" colspan="2">
<widget class="QLabel" name="session_timeout_seconds_label">
<property name="text">
<string>seconds</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="rtp_timeout_label">
<property name="text">
<string>RTP Timeout:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="rtp_timeout">
<property name="specialValueText">
<string>None</string>
</property>
<property name="maximum">
<number>180</number>
</property>
<property name="value">
<number>30</number>
</property>
</widget>
</item>
<item row="6" column="2" colspan="2">
<widget class="QLabel" name="rtp_timeout_seconds_label">
<property name="text">
<string>seconds</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="6">
<spacer name="sip_spacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="tls_group_box">
<property name="title">
<string>TLS Settings</string>
</property>
<layout class="QGridLayout" name="tls_group_box_layout">
<item row="0" column="0">
<widget class="QLabel" name="tls_ca_file_label">
<property name="text">
<string>Certificate Authority File:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1" colspan="3">
<widget class="LocationBar" name="tls_ca_file_editor">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QPushButton" name="tls_ca_file_browse_button">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="tls_timeout_label">
<property name="text">
<string>Connection Timeout:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="0" colspan="5">
<spacer name="tls_vertical_spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="tls_timeout">
<property name="accelerated">
<bool>true</bool>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="minimum">
<double>1.000000000000000</double>
</property>
<property name="maximum">
<double>30.000000000000000</double>
</property>
<property name="value">
<double>3.000000000000000</double>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="tls_timeout_seconds_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>seconds</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QToolBar" name="toolbar">
<property name="windowTitle">
<string>Blink Preferences</string>
</property>
<property name="movable">
<bool>false</bool>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
<property name="floatable">
<bool>false</bool>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="accounts_action"/>
<addaction name="separator"/>
<addaction name="audio_action"/>
<addaction name="chat_action"/>
<addaction name="file_transfer_action"/>
<addaction name="alerts_action"/>
<addaction name="logging_action"/>
<addaction name="advanced_action"/>
</widget>
<action name="accounts_action">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<normaloff>icons/accounts.png</normaloff>icons/accounts.png</iconset>
</property>
<property name="text">
<string>Accounts</string>
</property>
</action>
<action name="audio_action">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<normaloff>icons/audio.png</normaloff>icons/audio.png</iconset>
</property>
<property name="text">
<string>Audio</string>
</property>
</action>
<action name="chat_action">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<normaloff>icons/chat.png</normaloff>icons/chat.png</iconset>
</property>
<property name="text">
<string>Chat</string>
</property>
</action>
<action name="file_transfer_action">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<normaloff>icons/file-transfer.png</normaloff>icons/file-transfer.png</iconset>
</property>
<property name="text">
<string>File Transfer</string>
</property>
</action>
<action name="alerts_action">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<normaloff>icons/alerts.png</normaloff>icons/alerts.png</iconset>
</property>
<property name="text">
<string>Alerts</string>
</property>
</action>
<action name="logging_action">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<normaloff>icons/logs.png</normaloff>icons/logs.png</iconset>
</property>
<property name="text">
<string>Logging</string>
</property>
</action>
<action name="advanced_action">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset>
<normaloff>icons/advanced-settings.png</normaloff>icons/advanced-settings.png</iconset>
</property>
<property name="text">
<string>Advanced</string>
</property>
<property name="toolTip">
<string>Advanced</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>SIPPortEditor</class>
<extends>QSpinBox</extends>
<header>blink.preferences</header>
</customwidget>
<customwidget>
<class>LocationBar</class>
<extends>QLineEdit</extends>
<header>blink.widgets.lineedit</header>
</customwidget>
<customwidget>
<class>LineEdit</class>
<extends>QLineEdit</extends>
<header>blink.widgets.lineedit</header>
</customwidget>
<customwidget>
<class>AccountListView</class>
<extends>QListView</extends>
<header>blink.preferences</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment