Commit 9c840d9b authored by Saul Ibarra's avatar Saul Ibarra

Added initial presence support

parent 8272c5a2
......@@ -42,4 +42,12 @@ Traceback (most recent call last):
File "/home/dan/work/voip/blink-qt/sipsimple/bonjour.py", line 1125, in DNSServiceRegister
TypeError: an integer is required
Presence
--------
- Is picking the most recent timestamp a good winning method?
- Calculate user idleness
- Add a GUI element for the offline note
- Delete own icon if we don't get anything back from XCAP?
- Unify settings for inbound and outbound presence
......@@ -41,6 +41,7 @@ from blink.configuration.datatypes import InvalidToken
from blink.configuration.settings import SIPSimpleSettingsExtension
from blink.logging import LogManager
from blink.mainwindow import MainWindow
from blink.presence import PresenceManager
from blink.resources import ApplicationData
from blink.sessions import SessionManager
from blink.update import UpdateManager
......@@ -96,6 +97,7 @@ class Blink(QApplication):
self.main_window = MainWindow()
self.ip_address_monitor = IPAddressMonitor()
self.log_manager = LogManager()
self.presence_manager = PresenceManager()
self.update_manager = UpdateManager()
self.main_window.check_for_updates_action.triggered.connect(self.update_manager.check_for_updates)
......@@ -212,6 +214,7 @@ class Blink(QApplication):
def _NH_SIPApplicationWillStart(self, notification):
self.log_manager.start()
self.presence_manager.start()
@run_in_gui_thread
def _NH_SIPApplicationDidStart(self, notification):
......@@ -229,6 +232,9 @@ class Blink(QApplication):
def _NH_SIPApplicationWillEnd(self, notification):
self.ip_address_monitor.stop()
def _NH_SIPApplicationDidEnd(self, notification):
self.presence_manager.stop()
def _initialize_sipsimple(self):
if not os.path.exists(ApplicationData.get('config')):
self.first_run = True
......
......@@ -5,7 +5,7 @@
__all__ = ['AccountExtension', 'BonjourAccountExtension']
from sipsimple.account import BonjourMSRPSettings, MessageSummarySettings, MSRPSettings, RTPSettings, SIPSettings, TLSSettings, XCAPSettings
from sipsimple.account import BonjourMSRPSettings, MessageSummarySettings, MSRPSettings, PresenceSettings, RTPSettings, SIPSettings, TLSSettings, XCAPSettings
from sipsimple.configuration import Setting, SettingsGroup, SettingsObjectExtension
from sipsimple.configuration.datatypes import AudioCodecList, Hostname, MSRPConnectionModel, MSRPTransport, NonNegativeInteger, SIPTransportList, SRTPEncryption
from sipsimple.util import user_info
......@@ -29,6 +29,10 @@ class MSRPSettingsExtension(MSRPSettings):
connection_model = Setting(type=MSRPConnectionModel, default='relay')
class PresenceSettingsExtension(PresenceSettings):
enabled = 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)
......@@ -71,6 +75,7 @@ class AccountExtension(SettingsObjectExtension):
message_summary = MessageSummarySettingsExtension
msrp = MSRPSettingsExtension
pstn = PSTNSettings
presence = PresenceSettingsExtension
rtp = RTPSettingsExtension
server = ServerSettings
sip = SIPSettingsExtension
......
......@@ -94,6 +94,7 @@ class SIPSimpleSettingsExtension(SettingsObjectExtension):
class BlinkPresenceSettings(SettingsGroup):
current_state = Setting(type=PresenceState, default=PresenceState('Available'))
state_history = Setting(type=PresenceStateList, default=PresenceStateList())
offline_note = Setting(type=unicode, nillable=True)
icon = Setting(type=IconDescriptor, nillable=True)
......
......@@ -790,7 +790,8 @@ class Contact(object):
def __init__(self, contact, group):
self.settings = contact
self.group = group
self.status = 'unknown'
self.state = 'unknown'
self.note = None
notification_center = NotificationCenter()
notification_center.add_observer(ObserverWeakrefProxy(self), sender=contact)
......@@ -818,7 +819,7 @@ class Contact(object):
return '%s(%r, %r)' % (self.__class__.__name__, self.settings, self.group)
def __getstate__(self):
return (self.settings.id, dict(group=self.group, status=self.status))
return (self.settings.id, dict(group=self.group, state=self.state))
def __setstate__(self, state):
contact_id, state = state
......@@ -851,7 +852,7 @@ class Contact(object):
@property
def info(self):
return self.uri
return self.note or self.uri
@property
def uri(self):
......@@ -909,6 +910,19 @@ class Contact(object):
self.__dict__.pop('pixmap', None)
notification.center.post_notification('BlinkContactDidChange', sender=self)
def _NH_AddressbookContactGotPresenceUpdate(self, notification):
if notification.data.state in ('available', 'away', 'busy', 'offline'):
self.__dict__['state'] = notification.data.state
else:
self.__dict__['state'] = 'unknown'
self.note = notification.data.note
if notification.data.icon_data:
icon = IconManager().store_data(self.settings.id, notification.data.icon_data)
if icon:
self.settings.icon = notification.data.icon_descriptor
self.settings.save()
notification.center.post_notification('BlinkContactDidChange', sender=self)
ui_class, base_class = uic.loadUiType(Resources.get('google_contacts_dialog.ui'))
......@@ -1293,9 +1307,9 @@ class ContactDelegate(QStyledItemDelegate):
widget.render(pixmap)
painter.drawPixmap(option.rect, pixmap)
if contact.status not in ('offline', 'unknown'):
if contact.state not in ('offline', 'unknown'):
status_colors = dict(available='#00ff00', away='#ffff00', busy='#ff0000')
color = QColor(status_colors[contact.status])
color = QColor(status_colors[contact.state])
painter.setRenderHint(QPainter.Antialiasing, True)
painter.setBrush(color)
painter.setPen(color.darker(200))
......@@ -2708,11 +2722,16 @@ class ContactEditorDialog(base_class, ui_class):
self.display_name_editor.setText(contact.name)
if contact.settings.icon is not None and contact.settings.icon.is_local:
self.icon_selector.filename = contact.settings.icon.url[len('file://'):]
elif contact.settings.icon:
icon = IconManager().get(contact.settings.id)
if icon:
self.icon_selector.setPixmap(icon.pixmap(32))
else:
self.icon_selector.filename = None
self.preferred_media.setCurrentIndex(self.preferred_media.findText(contact.settings.preferred_media.title()))
self.accept_button.setText(u'Ok')
self.accept_button.setEnabled(True)
self.subscribe_presence.setChecked(contact.settings.presence.subscribe)
self.show()
def reset_icon(self):
......@@ -2736,6 +2755,12 @@ class ContactEditorDialog(base_class, ui_class):
uri.uri = self.sip_address_editor.text()
contact.name = self.display_name_editor.text()
contact.preferred_media = self.preferred_media.currentText().lower()
if self.subscribe_presence.isChecked():
contact.presence.policy = 'allow'
contact.presence.subscribe = True
else:
contact.presence.policy = 'default'
contact.presence.subscribe = False
if self.icon_selector.filename is not None:
icon_file = ApplicationData.get(self.icon_selector.filename)
icon_descriptor = IconDescriptor('file://' + icon_file)
......
......@@ -29,6 +29,7 @@ from blink.preferences import PreferencesWindow
from blink.sessions import ConferenceDialog, SessionManager, SessionModel
from blink.configuration.datatypes import IconDescriptor, InvalidToken, PresenceState
from blink.configuration.settings import BlinkSettings
from blink.presence import PendingWatcherDialog
from blink.resources import IconManager, Resources
from blink.util import run_in_gui_thread
from blink.widgets.buttons import AccountState, SwitchViewButton
......@@ -47,8 +48,11 @@ class MainWindow(base_class, ui_class):
notification_center.add_observer(self, name='SIPApplicationWillStart')
notification_center.add_observer(self, name='SIPApplicationDidStart')
notification_center.add_observer(self, name='SIPAccountGotMessageSummary')
notification_center.add_observer(self, name='SIPAccountGotPendingWatcher')
notification_center.add_observer(self, sender=AccountManager())
self.pending_watcher_dialogs = []
self.mwi_icons = [QIcon(Resources.get('icons/mwi-%d.png' % i)) for i in xrange(0, 11)]
self.mwi_icons.append(QIcon(Resources.get('icons/mwi-many.png')))
......@@ -58,7 +62,7 @@ class MainWindow(base_class, ui_class):
self.setWindowTitle('Blink')
self.setWindowIconText('Blink')
self.default_icon_path = Resources.get('icons/avatar.jpg')
self.default_icon_path = Resources.get('icons/default-avatar.png')
self.default_icon = QIcon(self.default_icon_path)
self.last_icon_directory = os.path.expanduser('~')
self.set_user_icon(IconManager().get('myicon'))
......@@ -212,6 +216,8 @@ class MainWindow(base_class, ui_class):
self.google_contacts_dialog.close()
self.preferences_window.close()
self.server_tools_window.close()
for dialog in self.pending_watcher_dialogs[:]:
dialog.close()
def set_user_icon(self, icon):
self.account_state.setIcon(icon or self.default_icon)
......@@ -590,6 +596,9 @@ class MainWindow(base_class, ui_class):
action = self.received_calls_menu.addAction(unicode(entry))
action.entry = entry
def _SH_PendingWatcherDialogFinished(self, dialog, code):
self.pending_watcher_dialogs.remove(dialog)
@run_in_gui_thread
def handle_notification(self, notification):
handler = getattr(self, '_NH_%s' % notification.name, Null)
......@@ -646,6 +655,7 @@ class MainWindow(base_class, ui_class):
def _NH_CFGSettingsObjectDidChange(self, notification):
settings = SIPSimpleSettings()
blink_settings = BlinkSettings()
if notification.sender is settings:
if 'audio.silent' in notification.data.modified:
self.silent_action.setChecked(settings.audio.silent)
......@@ -673,6 +683,15 @@ class MainWindow(base_class, ui_class):
self.google_contacts_action.setText(u'Disable Google Contacts')
if authorization_token is InvalidToken:
self.google_contacts_dialog.open_for_incorrect_password()
elif notification.sender is blink_settings:
if 'presence.current_state' in notification.data.modified:
state = getattr(AccountState, blink_settings.presence.current_state.state, AccountState.Available)
self.account_state.setState(state, blink_settings.presence.current_state.note)
if 'presence.icon' in notification.data.modified:
self.set_user_icon(IconManager().get('myicon'))
if 'presence.offline_note' in notification.data.modified:
# TODO: set offline note -Saul
pass
elif isinstance(notification.sender, (Account, BonjourAccount)):
account_manager = AccountManager()
account = notification.sender
......@@ -730,6 +749,13 @@ class MainWindow(base_class, ui_class):
new_messages = 0
action.setIcon(self.mwi_icons[new_messages])
def _NH_SIPAccountGotPendingWatcher(self, notification):
dialog = PendingWatcherDialog(notification.sender, notification.data.uri, notification.data.display_name)
dialog.finished.connect(partial(self._SH_PendingWatcherDialogFinished, dialog))
self.pending_watcher_dialogs.append(dialog)
dialog.show()
del ui_class, base_class
This diff is collapsed.
This diff is collapsed.
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