Commit ae271926 authored by Dan Pascu's avatar Dan Pascu

Implemented call transfer

parent 71ffd6d9
...@@ -3030,6 +3030,8 @@ class ContactDetailModel(QAbstractListModel): ...@@ -3030,6 +3030,8 @@ class ContactDetailModel(QAbstractListModel):
class ContactListView(QListView): class ContactListView(QListView):
implements(IObserver)
def __init__(self, parent=None): def __init__(self, parent=None):
super(ContactListView, self).__init__(parent) super(ContactListView, self).__init__(parent)
self.setItemDelegate(ContactDelegate(self)) self.setItemDelegate(ContactDelegate(self))
...@@ -3053,9 +3055,14 @@ class ContactListView(QListView): ...@@ -3053,9 +3055,14 @@ class ContactListView(QListView):
self.actions.send_files = QAction("Send File(s)...", self, triggered=self._AH_SendFiles) self.actions.send_files = QAction("Send File(s)...", self, triggered=self._AH_SendFiles)
self.actions.request_screen = QAction("Request Screen", self, triggered=self._AH_RequestScreen) self.actions.request_screen = QAction("Request Screen", self, triggered=self._AH_RequestScreen)
self.actions.share_my_screen = QAction("Share My Screen", self, triggered=self._AH_ShareMyScreen) self.actions.share_my_screen = QAction("Share My Screen", self, triggered=self._AH_ShareMyScreen)
self.actions.transfer_call = QAction("Transfer Active Call", self, triggered=self._AH_TransferCall)
self.drop_indicator_index = QModelIndex() self.drop_indicator_index = QModelIndex()
self.needs_restore = False self.needs_restore = False
self.doubleClicked.connect(self._SH_DoubleClicked) # activated is emitted on single click self.doubleClicked.connect(self._SH_DoubleClicked) # activated is emitted on single click
notification_center = NotificationCenter()
notification_center.add_observer(self, 'BlinkSessionDidChangeState')
notification_center.add_observer(self, 'BlinkSessionDidRemoveStream')
notification_center.add_observer(self, 'BlinkActiveSessionDidChange')
def selectionChanged(self, selected, deselected): def selectionChanged(self, selected, deselected):
super(ContactListView, self).selectionChanged(selected, deselected) super(ContactListView, self).selectionChanged(selected, deselected)
...@@ -3125,6 +3132,7 @@ class ContactListView(QListView): ...@@ -3125,6 +3132,7 @@ class ContactListView(QListView):
menu.addAction(self.actions.send_files) menu.addAction(self.actions.send_files)
menu.addAction(self.actions.request_screen) menu.addAction(self.actions.request_screen)
menu.addAction(self.actions.share_my_screen) menu.addAction(self.actions.share_my_screen)
menu.addAction(self.actions.transfer_call)
menu.addSeparator() menu.addSeparator()
menu.addAction(self.actions.add_group) menu.addAction(self.actions.add_group)
menu.addAction(self.actions.add_contact) menu.addAction(self.actions.add_contact)
...@@ -3133,7 +3141,9 @@ class ContactListView(QListView): ...@@ -3133,7 +3141,9 @@ class ContactListView(QListView):
menu.addAction(self.actions.undo_last_delete) menu.addAction(self.actions.undo_last_delete)
self.actions.undo_last_delete.setText(undo_delete_text) self.actions.undo_last_delete.setText(undo_delete_text)
account_manager = AccountManager() account_manager = AccountManager()
session_manager = SessionManager()
can_call = account_manager.default_account is not None and contact.uri is not None can_call = account_manager.default_account is not None and contact.uri is not None
can_transfer = contact.uri is not None and session_manager.active_session is not None and session_manager.active_session.state == 'connected'
self.actions.start_audio_call.setEnabled(can_call) self.actions.start_audio_call.setEnabled(can_call)
self.actions.start_video_call.setEnabled(can_call) self.actions.start_video_call.setEnabled(can_call)
self.actions.start_chat_session.setEnabled(can_call) self.actions.start_chat_session.setEnabled(can_call)
...@@ -3141,6 +3151,7 @@ class ContactListView(QListView): ...@@ -3141,6 +3151,7 @@ class ContactListView(QListView):
self.actions.send_files.setEnabled(can_call) self.actions.send_files.setEnabled(can_call)
self.actions.request_screen.setEnabled(can_call) self.actions.request_screen.setEnabled(can_call)
self.actions.share_my_screen.setEnabled(can_call) self.actions.share_my_screen.setEnabled(can_call)
self.actions.transfer_call.setEnabled(can_transfer)
self.actions.edit_item.setEnabled(contact.editable) self.actions.edit_item.setEnabled(contact.editable)
self.actions.delete_item.setEnabled(contact.deletable) self.actions.delete_item.setEnabled(contact.deletable)
self.actions.undo_last_delete.setEnabled(len(model.deleted_items) > 0) self.actions.undo_last_delete.setEnabled(len(model.deleted_items) > 0)
...@@ -3385,6 +3396,11 @@ class ContactListView(QListView): ...@@ -3385,6 +3396,11 @@ class ContactListView(QListView):
session_manager = SessionManager() session_manager = SessionManager()
session_manager.create_session(contact, contact.uri, [StreamDescription('screen-sharing', mode='server'), StreamDescription('audio')]) session_manager.create_session(contact, contact.uri, [StreamDescription('screen-sharing', mode='server'), StreamDescription('audio')])
def _AH_TransferCall(self):
contact = self.selectionModel().selectedIndexes()[0].data(Qt.UserRole)
session_manager = SessionManager()
session_manager.active_session.transfer(contact.uri)
def _DH_ApplicationXBlinkGroupList(self, event, index, rect, item): def _DH_ApplicationXBlinkGroupList(self, event, index, rect, item):
model = self.model() model = self.model()
groups = model.items[GroupList] groups = model.items[GroupList]
...@@ -3455,8 +3471,39 @@ class ContactListView(QListView): ...@@ -3455,8 +3471,39 @@ class ContactListView(QListView):
session_manager = SessionManager() session_manager = SessionManager()
session_manager.create_session(item, item.uri, item.preferred_media.stream_descriptions, connect=item.preferred_media.autoconnect) session_manager.create_session(item, item.uri, item.preferred_media.stream_descriptions, connect=item.preferred_media.autoconnect)
@run_in_gui_thread
def handle_notification(self, notification):
handler = getattr(self, '_NH_%s' % notification.name, Null)
handler(notification)
def _NH_BlinkSessionDidChangeState(self, notification):
session_manager = SessionManager()
if notification.sender is session_manager.active_session and self.context_menu.isVisible():
selected_items = [index.data(Qt.UserRole) for index in self.selectionModel().selectedIndexes()]
if len(selected_items) == 1 and isinstance(selected_items[0], Contact):
contact = selected_items[0]
self.actions.transfer_call.setEnabled(contact.uri is not None and notification.sender.state == 'connected')
def _NH_BlinkSessionDidRemoveStream(self, notification):
session_manager = SessionManager()
if notification.sender is session_manager.active_session and self.context_menu.isVisible():
selected_items = [index.data(Qt.UserRole) for index in self.selectionModel().selectedIndexes()]
if len(selected_items) == 1 and isinstance(selected_items[0], Contact):
contact = selected_items[0]
self.actions.transfer_call.setEnabled(contact.uri is not None and 'audio' in notification.sender.streams)
def _NH_BlinkActiveSessionDidChange(self, notification):
if self.context_menu.isVisible():
selected_items = [index.data(Qt.UserRole) for index in self.selectionModel().selectedIndexes()]
if len(selected_items) == 1 and isinstance(selected_items[0], Contact):
contact = selected_items[0]
active_session = notification.data.active_session
self.actions.transfer_call.setEnabled(contact.uri is not None and active_session is not None and active_session.state == 'connected')
class ContactSearchListView(QListView): class ContactSearchListView(QListView):
implements(IObserver)
def __init__(self, parent=None): def __init__(self, parent=None):
super(ContactSearchListView, self).__init__(parent) super(ContactSearchListView, self).__init__(parent)
self.setItemDelegate(ContactDelegate(self)) self.setItemDelegate(ContactDelegate(self))
...@@ -3478,8 +3525,13 @@ class ContactSearchListView(QListView): ...@@ -3478,8 +3525,13 @@ class ContactSearchListView(QListView):
self.actions.send_files = QAction("Send File(s)...", self, triggered=self._AH_SendFiles) self.actions.send_files = QAction("Send File(s)...", self, triggered=self._AH_SendFiles)
self.actions.request_screen = QAction("Request Screen", self, triggered=self._AH_RequestScreen) self.actions.request_screen = QAction("Request Screen", self, triggered=self._AH_RequestScreen)
self.actions.share_my_screen = QAction("Share My Screen", self, triggered=self._AH_ShareMyScreen) self.actions.share_my_screen = QAction("Share My Screen", self, triggered=self._AH_ShareMyScreen)
self.actions.transfer_call = QAction("Transfer Active Call", self, triggered=self._AH_TransferCall)
self.drop_indicator_index = QModelIndex() self.drop_indicator_index = QModelIndex()
self.doubleClicked.connect(self._SH_DoubleClicked) # activated is emitted on single click self.doubleClicked.connect(self._SH_DoubleClicked) # activated is emitted on single click
notification_center = NotificationCenter()
notification_center.add_observer(self, 'BlinkSessionDidChangeState')
notification_center.add_observer(self, 'BlinkSessionDidRemoveStream')
notification_center.add_observer(self, 'BlinkActiveSessionDidChange')
def selectionChanged(self, selected, deselected): def selectionChanged(self, selected, deselected):
super(ContactSearchListView, self).selectionChanged(selected, deselected) super(ContactSearchListView, self).selectionChanged(selected, deselected)
...@@ -3536,13 +3588,16 @@ class ContactSearchListView(QListView): ...@@ -3536,13 +3588,16 @@ class ContactSearchListView(QListView):
menu.addAction(self.actions.send_files) menu.addAction(self.actions.send_files)
menu.addAction(self.actions.request_screen) menu.addAction(self.actions.request_screen)
menu.addAction(self.actions.share_my_screen) menu.addAction(self.actions.share_my_screen)
menu.addAction(self.actions.transfer_call)
menu.addSeparator() menu.addSeparator()
menu.addAction(self.actions.edit_item) menu.addAction(self.actions.edit_item)
menu.addAction(self.actions.delete_item) menu.addAction(self.actions.delete_item)
menu.addAction(self.actions.undo_last_delete) menu.addAction(self.actions.undo_last_delete)
self.actions.undo_last_delete.setText(undo_delete_text) self.actions.undo_last_delete.setText(undo_delete_text)
account_manager = AccountManager() account_manager = AccountManager()
session_manager = SessionManager()
can_call = account_manager.default_account is not None and contact.uri is not None can_call = account_manager.default_account is not None and contact.uri is not None
can_transfer = contact.uri is not None and session_manager.active_session is not None and session_manager.active_session.state == 'connected'
self.actions.start_audio_call.setEnabled(can_call) self.actions.start_audio_call.setEnabled(can_call)
self.actions.start_video_call.setEnabled(can_call) self.actions.start_video_call.setEnabled(can_call)
self.actions.start_chat_session.setEnabled(can_call) self.actions.start_chat_session.setEnabled(can_call)
...@@ -3550,6 +3605,7 @@ class ContactSearchListView(QListView): ...@@ -3550,6 +3605,7 @@ class ContactSearchListView(QListView):
self.actions.send_files.setEnabled(can_call) self.actions.send_files.setEnabled(can_call)
self.actions.request_screen.setEnabled(can_call) self.actions.request_screen.setEnabled(can_call)
self.actions.share_my_screen.setEnabled(can_call) self.actions.share_my_screen.setEnabled(can_call)
self.actions.transfer_call.setEnabled(can_transfer)
self.actions.edit_item.setEnabled(contact.editable) self.actions.edit_item.setEnabled(contact.editable)
self.actions.delete_item.setEnabled(contact.deletable) self.actions.delete_item.setEnabled(contact.deletable)
self.actions.undo_last_delete.setEnabled(len(source_model.deleted_items) > 0) self.actions.undo_last_delete.setEnabled(len(source_model.deleted_items) > 0)
...@@ -3730,6 +3786,11 @@ class ContactSearchListView(QListView): ...@@ -3730,6 +3786,11 @@ class ContactSearchListView(QListView):
session_manager = SessionManager() session_manager = SessionManager()
session_manager.create_session(contact, contact.uri, [StreamDescription('screen-sharing', mode='server'), StreamDescription('audio')]) session_manager.create_session(contact, contact.uri, [StreamDescription('screen-sharing', mode='server'), StreamDescription('audio')])
def _AH_TransferCall(self):
contact = self.selectionModel().selectedIndexes()[0].data(Qt.UserRole)
session_manager = SessionManager()
session_manager.active_session.transfer(contact.uri)
def _DH_TextUriList(self, event, index, rect, item): def _DH_TextUriList(self, event, index, rect, item):
if index.isValid(): if index.isValid():
event.accept(rect) event.accept(rect)
...@@ -3746,8 +3807,39 @@ class ContactSearchListView(QListView): ...@@ -3746,8 +3807,39 @@ class ContactSearchListView(QListView):
session_manager = SessionManager() session_manager = SessionManager()
session_manager.create_session(item, item.uri, item.preferred_media.stream_descriptions, connect=item.preferred_media.autoconnect) session_manager.create_session(item, item.uri, item.preferred_media.stream_descriptions, connect=item.preferred_media.autoconnect)
@run_in_gui_thread
def handle_notification(self, notification):
handler = getattr(self, '_NH_%s' % notification.name, Null)
handler(notification)
def _NH_BlinkSessionDidChangeState(self, notification):
session_manager = SessionManager()
if notification.sender is session_manager.active_session and self.context_menu.isVisible():
selected_items = [index.data(Qt.UserRole) for index in self.selectionModel().selectedIndexes()]
if len(selected_items) == 1 and isinstance(selected_items[0], Contact):
contact = selected_items[0]
self.actions.transfer_call.setEnabled(contact.uri is not None and notification.sender.state == 'connected')
def _NH_BlinkSessionDidRemoveStream(self, notification):
session_manager = SessionManager()
if notification.sender is session_manager.active_session and self.context_menu.isVisible():
selected_items = [index.data(Qt.UserRole) for index in self.selectionModel().selectedIndexes()]
if len(selected_items) == 1 and isinstance(selected_items[0], Contact):
contact = selected_items[0]
self.actions.transfer_call.setEnabled(contact.uri is not None and 'audio' in notification.sender.streams)
def _NH_BlinkActiveSessionDidChange(self, notification):
if self.context_menu.isVisible():
selected_items = [index.data(Qt.UserRole) for index in self.selectionModel().selectedIndexes()]
if len(selected_items) == 1 and isinstance(selected_items[0], Contact):
contact = selected_items[0]
active_session = notification.data.active_session
self.actions.transfer_call.setEnabled(contact.uri is not None and active_session is not None and active_session.state == 'connected')
class ContactDetailView(QListView): class ContactDetailView(QListView):
implements(IObserver)
def __init__(self, contact_list): def __init__(self, contact_list):
super(ContactDetailView, self).__init__(contact_list.parent()) super(ContactDetailView, self).__init__(contact_list.parent())
palette = self.palette() palette = self.palette()
...@@ -3778,9 +3870,14 @@ class ContactDetailView(QListView): ...@@ -3778,9 +3870,14 @@ class ContactDetailView(QListView):
self.actions.send_files = QAction("Send File(s)...", self, triggered=self._AH_SendFiles) self.actions.send_files = QAction("Send File(s)...", self, triggered=self._AH_SendFiles)
self.actions.request_screen = QAction("Request Screen", self, triggered=self._AH_RequestScreen) self.actions.request_screen = QAction("Request Screen", self, triggered=self._AH_RequestScreen)
self.actions.share_my_screen = QAction("Share My Screen", self, triggered=self._AH_ShareMyScreen) self.actions.share_my_screen = QAction("Share My Screen", self, triggered=self._AH_ShareMyScreen)
self.actions.transfer_call = QAction("Transfer Active Call", self, triggered=self._AH_TransferCall)
self.drop_indicator_index = QModelIndex() self.drop_indicator_index = QModelIndex()
self.doubleClicked.connect(self._SH_DoubleClicked) # activated is emitted on single click self.doubleClicked.connect(self._SH_DoubleClicked) # activated is emitted on single click
contact_list.installEventFilter(self) contact_list.installEventFilter(self)
notification_center = NotificationCenter()
notification_center.add_observer(self, 'BlinkSessionDidChangeState')
notification_center.add_observer(self, 'BlinkSessionDidRemoveStream')
notification_center.add_observer(self, 'BlinkActiveSessionDidChange')
def setModel(self, model): def setModel(self, model):
old_model = self.model() or Null old_model = self.model() or Null
...@@ -3812,6 +3909,7 @@ class ContactDetailView(QListView): ...@@ -3812,6 +3909,7 @@ class ContactDetailView(QListView):
def contextMenuEvent(self, event): def contextMenuEvent(self, event):
account_manager = AccountManager() account_manager = AccountManager()
session_manager = SessionManager()
model = self.model() model = self.model()
selected_indexes = self.selectionModel().selectedIndexes() selected_indexes = self.selectionModel().selectedIndexes()
selected_item = selected_indexes[0].data(Qt.UserRole) if selected_indexes else None selected_item = selected_indexes[0].data(Qt.UserRole) if selected_indexes else None
...@@ -3825,6 +3923,7 @@ class ContactDetailView(QListView): ...@@ -3825,6 +3923,7 @@ class ContactDetailView(QListView):
menu.addAction(self.actions.send_files) menu.addAction(self.actions.send_files)
menu.addAction(self.actions.request_screen) menu.addAction(self.actions.request_screen)
menu.addAction(self.actions.share_my_screen) menu.addAction(self.actions.share_my_screen)
menu.addAction(self.actions.transfer_call)
menu.addSeparator() menu.addSeparator()
if isinstance(selected_item, ContactURI) and model.contact_detail.editable: if isinstance(selected_item, ContactURI) and model.contact_detail.editable:
menu.addAction(self.actions.make_uri_default) menu.addAction(self.actions.make_uri_default)
...@@ -3832,6 +3931,7 @@ class ContactDetailView(QListView): ...@@ -3832,6 +3931,7 @@ class ContactDetailView(QListView):
menu.addAction(self.actions.edit_contact) menu.addAction(self.actions.edit_contact)
menu.addAction(self.actions.delete_contact) menu.addAction(self.actions.delete_contact)
can_call = account_manager.default_account is not None and contact_has_uris can_call = account_manager.default_account is not None and contact_has_uris
can_transfer = contact_has_uris and session_manager.active_session is not None and session_manager.active_session.state == 'connected'
self.actions.start_audio_call.setEnabled(can_call) self.actions.start_audio_call.setEnabled(can_call)
self.actions.start_video_call.setEnabled(can_call) self.actions.start_video_call.setEnabled(can_call)
self.actions.start_chat_session.setEnabled(can_call) self.actions.start_chat_session.setEnabled(can_call)
...@@ -3839,6 +3939,7 @@ class ContactDetailView(QListView): ...@@ -3839,6 +3939,7 @@ class ContactDetailView(QListView):
self.actions.send_files.setEnabled(can_call) self.actions.send_files.setEnabled(can_call)
self.actions.request_screen.setEnabled(can_call) self.actions.request_screen.setEnabled(can_call)
self.actions.share_my_screen.setEnabled(can_call) self.actions.share_my_screen.setEnabled(can_call)
self.actions.transfer_call.setEnabled(can_transfer)
self.actions.edit_contact.setEnabled(model.contact_detail.editable) self.actions.edit_contact.setEnabled(model.contact_detail.editable)
self.actions.delete_contact.setEnabled(model.contact_detail.deletable) self.actions.delete_contact.setEnabled(model.contact_detail.deletable)
menu.exec_(event.globalPos()) menu.exec_(event.globalPos())
...@@ -4004,6 +4105,17 @@ class ContactDetailView(QListView): ...@@ -4004,6 +4105,17 @@ class ContactDetailView(QListView):
session_manager = SessionManager() session_manager = SessionManager()
session_manager.create_session(contact, selected_uri, [StreamDescription('screen-sharing', mode='server'), StreamDescription('audio')]) session_manager.create_session(contact, selected_uri, [StreamDescription('screen-sharing', mode='server'), StreamDescription('audio')])
def _AH_TransferCall(self):
contact = self.contact_list.selectionModel().selectedIndexes()[0].data(Qt.UserRole)
selected_indexes = self.selectionModel().selectedIndexes()
item = selected_indexes[0].data(Qt.UserRole) if selected_indexes else None
if isinstance(item, ContactURI):
selected_uri = item.uri
else:
selected_uri = contact.uri
session_manager = SessionManager()
session_manager.active_session.transfer(selected_uri)
def _DH_ApplicationXBlinkSession(self, event, index, rect, item): def _DH_ApplicationXBlinkSession(self, event, index, rect, item):
event.ignore(rect) event.ignore(rect)
...@@ -4044,6 +4156,29 @@ class ContactDetailView(QListView): ...@@ -4044,6 +4156,29 @@ class ContactDetailView(QListView):
session_manager = SessionManager() session_manager = SessionManager()
session_manager.create_session(contact, selected_uri, contact.preferred_media.stream_descriptions, connect=contact.preferred_media.autoconnect) session_manager.create_session(contact, selected_uri, contact.preferred_media.stream_descriptions, connect=contact.preferred_media.autoconnect)
@run_in_gui_thread
def handle_notification(self, notification):
handler = getattr(self, '_NH_%s' % notification.name, Null)
handler(notification)
def _NH_BlinkSessionDidChangeState(self, notification):
session_manager = SessionManager()
if notification.sender is session_manager.active_session and self.context_menu.isVisible():
contact_has_uris = self.model().rowCount() > 1
self.actions.transfer_call.setEnabled(contact_has_uris and notification.sender.state == 'connected')
def _NH_BlinkSessionDidRemoveStream(self, notification):
session_manager = SessionManager()
if notification.sender is session_manager.active_session and self.context_menu.isVisible():
contact_has_uris = self.model().rowCount() > 1
self.actions.transfer_call.setEnabled(contact_has_uris and 'audio' in notification.sender.streams)
def _NH_BlinkActiveSessionDidChange(self, notification):
if self.context_menu.isVisible():
contact_has_uris = self.model().rowCount() > 1
active_session = notification.data.active_session
self.actions.transfer_call.setEnabled(contact_has_uris and active_session is not None and active_session.state == 'connected')
# The contact editor dialog # The contact editor dialog
# #
......
...@@ -54,6 +54,7 @@ class MainWindow(base_class, ui_class): ...@@ -54,6 +54,7 @@ class MainWindow(base_class, ui_class):
notification_center.add_observer(self, name='SIPAccountGotPendingWatcher') notification_center.add_observer(self, name='SIPAccountGotPendingWatcher')
notification_center.add_observer(self, name='BlinkSessionNewOutgoing') notification_center.add_observer(self, name='BlinkSessionNewOutgoing')
notification_center.add_observer(self, name='BlinkSessionDidReinitializeForOutgoing') notification_center.add_observer(self, name='BlinkSessionDidReinitializeForOutgoing')
notification_center.add_observer(self, name='BlinkSessionTransferNewOutgoing')
notification_center.add_observer(self, name='BlinkFileTransferNewIncoming') notification_center.add_observer(self, name='BlinkFileTransferNewIncoming')
notification_center.add_observer(self, name='BlinkFileTransferNewOutgoing') notification_center.add_observer(self, name='BlinkFileTransferNewOutgoing')
notification_center.add_observer(self, sender=AccountManager()) notification_center.add_observer(self, sender=AccountManager())
...@@ -907,6 +908,10 @@ class MainWindow(base_class, ui_class): ...@@ -907,6 +908,10 @@ class MainWindow(base_class, ui_class):
def _NH_BlinkSessionDidReinitializeForOutgoing(self, notification): def _NH_BlinkSessionDidReinitializeForOutgoing(self, notification):
self.search_box.clear() self.search_box.clear()
def _NH_BlinkSessionTransferNewOutgoing(self, notification):
self.search_box.clear()
self.switch_view_button.view = SwitchViewButton.SessionView
def _NH_BlinkFileTransferNewIncoming(self, notification): def _NH_BlinkFileTransferNewIncoming(self, notification):
self.filetransfer_window.show(activate=QApplication.activeWindow() is not None) self.filetransfer_window.show(activate=QApplication.activeWindow() is not None)
......
...@@ -37,7 +37,7 @@ from sipsimple.configuration.datatypes import Path ...@@ -37,7 +37,7 @@ from sipsimple.configuration.datatypes import Path
from sipsimple.configuration.settings import SIPSimpleSettings from sipsimple.configuration.settings import SIPSimpleSettings
from sipsimple.core import SIPCoreError, SIPURI, ToHeader from sipsimple.core import SIPCoreError, SIPURI, ToHeader
from sipsimple.lookup import DNSLookup from sipsimple.lookup import DNSLookup
from sipsimple.session import Session from sipsimple.session import Session, IllegalStateError
from sipsimple.streams import MediaStreamRegistry from sipsimple.streams import MediaStreamRegistry
from sipsimple.streams.msrp.filetransfer import FileSelector from sipsimple.streams.msrp.filetransfer import FileSelector
from sipsimple.streams.msrp.screensharing import ExternalVNCServerHandler, ExternalVNCViewerHandler, ScreenSharingStream from sipsimple.streams.msrp.screensharing import ExternalVNCServerHandler, ExternalVNCViewerHandler, ScreenSharingStream
...@@ -470,6 +470,9 @@ class BlinkSession(BlinkSessionBase): ...@@ -470,6 +470,9 @@ class BlinkSession(BlinkSessionBase):
self.remote_hold = False self.remote_hold = False
self.recording = False self.recording = False
self.transfer_state = None
self.transfer_direction = None
self.info = SessionInfo() self.info = SessionInfo()
self._sibling = None self._sibling = None
...@@ -655,6 +658,43 @@ class BlinkSession(BlinkSessionBase): ...@@ -655,6 +658,43 @@ class BlinkSession(BlinkSessionBase):
notification_center.post_notification('BlinkSessionNewOutgoing', sender=self) notification_center.post_notification('BlinkSessionNewOutgoing', sender=self)
notification_center.post_notification('BlinkSessionInfoUpdated', sender=self, data=NotificationData(elements={'session', 'media', 'statistics'})) notification_center.post_notification('BlinkSessionInfoUpdated', sender=self, data=NotificationData(elements={'session', 'media', 'statistics'}))
def init_transfer(self, sip_session, streams, contact, contact_uri, reinitialize=False):
assert self.state in (None, 'initialized', 'ended')
assert self.contact is None or contact.settings is self.contact.settings
notification_center = NotificationCenter()
if reinitialize:
notification_center.post_notification('BlinkSessionWillReinitialize', sender=self)
self._initialize(reinitialize=True)
else:
self._delete_when_done = len(streams) == 1 and streams[0].type == 'audio'
self.direction = 'outgoing'
self.sip_session = sip_session
self.account = sip_session.account
self.contact = contact
self.contact_uri = contact_uri
self.uri = self._normalize_uri(contact_uri.uri)
self.stream_descriptions = StreamSet(StreamDescription(stream.type) for stream in streams)
self.state = 'initialized'
if reinitialize:
notification_center.post_notification('BlinkSessionDidReinitializeForOutgoing', sender=self)
else:
notification_center.post_notification('BlinkSessionNewOutgoing', sender=self)
self.streams.extend(streams)
self.info._update(self)
notification_center.post_notification('BlinkSessionInfoUpdated', sender=self, data=NotificationData(elements={'session', 'media', 'statistics'}))
self.state = 'connecting/dns_lookup'
notification_center.post_notification('BlinkSessionWillConnect', sender=self, data=NotificationData(sibling=None))
notification_center.post_notification('BlinkSessionConnectionProgress', sender=self, data=NotificationData(stage='dns_lookup'))
self.stream_descriptions = None
self.state = 'connecting'
notification_center.post_notification('BlinkSessionConnectionProgress', sender=self, data=NotificationData(stage='connecting'))
def connect(self): def connect(self):
assert self.direction == 'outgoing' and self.state == 'initialized' assert self.direction == 'outgoing' and self.state == 'initialized'
notification_center = NotificationCenter() notification_center = NotificationCenter()
...@@ -768,6 +808,15 @@ class BlinkSession(BlinkSessionBase): ...@@ -768,6 +808,15 @@ class BlinkSession(BlinkSessionBase):
self.recording = False self.recording = False
audio_stream.stop_recording() audio_stream.stop_recording()
def transfer(self, contact_uri, replaced_session=None):
if self.state != 'connected':
return
replaced_sip_session = None if replaced_session is None else replaced_session.sip_session
try:
self.sip_session.transfer(self._parse_uri(contact_uri.uri), replaced_session=replaced_sip_session)
except IllegalStateError:
pass
def end(self, delete=False): def end(self, delete=False):
if self.state == 'ending': if self.state == 'ending':
self._delete_requested = delete self._delete_requested = delete
...@@ -981,6 +1030,30 @@ class BlinkSession(BlinkSessionBase): ...@@ -981,6 +1030,30 @@ class BlinkSession(BlinkSessionBase):
elif self.streams.types.isdisjoint({'audio', 'video'}): elif self.streams.types.isdisjoint({'audio', 'video'}):
self.unhold() self.unhold()
def _NH_SIPSessionTransferNewIncoming(self, notification):
self.transfer_state = 'active'
self.transfer_direction = 'incoming'
notification.center.post_notification('BlinkSessionTransferNewIncoming', sender=self, data=notification.data)
def _NH_SIPSessionTransferNewOutgoing(self, notification):
self.transfer_state = 'active'
self.transfer_direction = 'outgoing'
notification.center.post_notification('BlinkSessionTransferNewOutgoing', sender=self, data=notification.data)
def _NH_SIPSessionTransferDidStart(self, notification):
notification.center.post_notification('BlinkSessionTransferDidStart', sender=self, data=notification.data)
def _NH_SIPSessionTransferDidEnd(self, notification):
self.transfer_state = 'completed'
notification.center.post_notification('BlinkSessionTransferDidEnd', sender=self, data=notification.data)
def _NH_SIPSessionTransferDidFail(self, notification):
self.transfer_state = 'failed'
notification.center.post_notification('BlinkSessionTransferDidFail', sender=self, data=notification.data)
def _NH_SIPSessionTransferGotProgress(self, notification):
notification.center.post_notification('BlinkSessionTransferGotProgress', sender=self, data=notification.data)
def _NH_MediaStreamDidStart(self, notification): def _NH_MediaStreamDidStart(self, notification):
stream = notification.sender stream = notification.sender
audio_stream = self.streams.get('audio') audio_stream = self.streams.get('audio')
...@@ -1558,7 +1631,7 @@ class DraggedAudioSessionWidget(base_class, ui_class): ...@@ -1558,7 +1631,7 @@ class DraggedAudioSessionWidget(base_class, ui_class):
if self.in_conference: if self.in_conference:
self.note_label.setText(u'Drop outside the conference to detach') self.note_label.setText(u'Drop outside the conference to detach')
else: else:
self.note_label.setText(u'Drop on a session to conference them') self.note_label.setText(u'<p><b>Drop</b>:&nbsp;Conference&nbsp; <b>Alt+Drop</b>:&nbsp;Transfer</p>')
def paintEvent(self, event): def paintEvent(self, event):
painter = QPainter(self) painter = QPainter(self)
...@@ -1602,6 +1675,9 @@ class AudioSessionItem(object): ...@@ -1602,6 +1675,9 @@ class AudioSessionItem(object):
self.blink_session = session self.blink_session = session
self.blink_session.items.audio = self self.blink_session.items.audio = self
self.status_context = None
self.__saved_status = None # to store skipped status messages during a context change
self.widget = Null self.widget = Null
self.status = None self.status = None
self.type = 'Audio' self.type = 'Audio'
...@@ -1676,8 +1752,17 @@ class AudioSessionItem(object): ...@@ -1676,8 +1752,17 @@ class AudioSessionItem(object):
return self.__dict__['status'] return self.__dict__['status']
def _set_status(self, value): def _set_status(self, value):
if self.__dict__.get('status', Null) == value: old_status = self.__dict__.get('status', Null)
new_status = value
if old_status == new_status:
return
if old_status is not None and old_status is not Null:
context = None if new_status is None else new_status.context
if self.status_context == old_status.context != context:
self.__saved_status = value # preserve the status that is skipped because of context mismatch
return return
elif old_status.context != context and context is not None:
self.__saved_status = old_status # preserve the status that was there prior to switching the context
self.__dict__['status'] = value self.__dict__['status'] = value
self.widget.status_label.value = value self.widget.status_label.value = value
...@@ -1796,7 +1881,8 @@ class AudioSessionItem(object): ...@@ -1796,7 +1881,8 @@ class AudioSessionItem(object):
def _reset_status(self, expected_status): def _reset_status(self, expected_status):
if self.status == expected_status: if self.status == expected_status:
self.status = None self.status = self.__saved_status
self.__saved_status = None
def _SH_HangupButtonClicked(self): def _SH_HangupButtonClicked(self):
self.end() self.end()
...@@ -1928,6 +2014,26 @@ class AudioSessionItem(object): ...@@ -1928,6 +2014,26 @@ class AudioSessionItem(object):
self.status = Status(notification.data.reason) self.status = Status(notification.data.reason)
self._cleanup() self._cleanup()
def _NH_BlinkSessionTransferNewOutgoing(self, notification):
if self.blink_session.state == 'connected':
self.status_context = 'transfer'
self.status = Status('Transfer: Trying', context='transfer')
def _NH_BlinkSessionTransferDidEnd(self, notification):
if self.blink_session.transfer_direction == 'outgoing':
self.status = Status('Transfer: Succeeded', context='transfer')
def _NH_BlinkSessionTransferDidFail(self, notification):
if self.blink_session.state == 'connected' and self.blink_session.transfer_direction == 'outgoing':
reason = 'Decline' if notification.data.code == 603 else notification.data.reason
self.status = Status("Transfer: {}".format(reason), context='transfer')
call_later(3, self._reset_status, self.status)
self.status_context = None
def _NH_BlinkSessionTransferGotProgress(self, notification):
if self.blink_session.state == 'connected' and notification.data.code < 200: # final answers are handled in DidEnd and DiDFail
self.status = Status("Transfer: {}".format(notification.data.reason), context='transfer')
def _NH_MediaStreamWillEnd(self, notification): def _NH_MediaStreamWillEnd(self, notification):
stream = notification.sender stream = notification.sender
if stream.type == 'audio' and stream.blink_session.items.audio is self: if stream.type == 'audio' and stream.blink_session.items.audio is self:
...@@ -2022,7 +2128,7 @@ class AudioSessionModel(QAbstractListModel): ...@@ -2022,7 +2128,7 @@ class AudioSessionModel(QAbstractListModel):
return None return None
def supportedDropActions(self): def supportedDropActions(self):
return Qt.CopyAction | Qt.MoveAction return Qt.CopyAction | Qt.MoveAction | Qt.LinkAction
def mimeTypes(self): def mimeTypes(self):
return ['application/x-blink-session-list'] return ['application/x-blink-session-list']
...@@ -2057,7 +2163,10 @@ class AudioSessionModel(QAbstractListModel): ...@@ -2057,7 +2163,10 @@ class AudioSessionModel(QAbstractListModel):
selection_model = session_list.selectionModel() selection_model = session_list.selectionModel()
source = session_list.dragged_session source = session_list.dragged_session
target = self.sessions[index.row()] if index.isValid() else None target = self.sessions[index.row()] if index.isValid() else None
if source.client_conference is None: # the dragged session is not in a conference yet
if action == Qt.LinkAction: # call transfer
source.blink_session.transfer(target.blink_session.contact_uri, replaced_session=target.blink_session)
elif source.client_conference is None: # the dragged session is not in a conference yet
if target.client_conference is not None: if target.client_conference is not None:
source_row = self.sessions.index(source) source_row = self.sessions.index(source)
target_row = self.sessions.index(target.client_conference.sessions[-1].items.audio) + 1 target_row = self.sessions.index(target.client_conference.sessions[-1].items.audio) + 1
...@@ -2088,6 +2197,7 @@ class AudioSessionModel(QAbstractListModel): ...@@ -2088,6 +2197,7 @@ class AudioSessionModel(QAbstractListModel):
session.active = source.active or target.active session.active = source.active or target.active
if source.active: if source.active:
source.client_conference.unhold() source.client_conference.unhold()
self.structureChanged.emit()
else: # the dragged session is in a conference else: # the dragged session is in a conference
dragged = source dragged = source
sibling = next(session.items.audio for session in dragged.client_conference.sessions if session.items.audio is not dragged) sibling = next(session.items.audio for session in dragged.client_conference.sessions if session.items.audio is not dragged)
...@@ -2517,8 +2627,6 @@ class AudioSessionListView(QListView): ...@@ -2517,8 +2627,6 @@ class AudioSessionListView(QListView):
elif event_source is not self and 'application/x-blink-session-list' in provided_mime_types: elif event_source is not self and 'application/x-blink-session-list' in provided_mime_types:
event.ignore() # we don't handle drops for blink sessions from other sources event.ignore() # we don't handle drops for blink sessions from other sources
else: else:
if event_source is self:
event.setDropAction(Qt.MoveAction)
event.accept() event.accept()
def dragLeaveEvent(self, event): def dragLeaveEvent(self, event):
...@@ -2528,8 +2636,6 @@ class AudioSessionListView(QListView): ...@@ -2528,8 +2636,6 @@ class AudioSessionListView(QListView):
def dragMoveEvent(self, event): def dragMoveEvent(self, event):
super(AudioSessionListView, self).dragMoveEvent(event) super(AudioSessionListView, self).dragMoveEvent(event)
if event.source() is self:
event.setDropAction(Qt.MoveAction)
model = self.model() model = self.model()
...@@ -2551,6 +2657,9 @@ class AudioSessionListView(QListView): ...@@ -2551,6 +2657,9 @@ class AudioSessionListView(QListView):
def dropEvent(self, event): def dropEvent(self, event):
model = self.model() model = self.model()
if event.source() is self: if event.source() is self:
if event.keyboardModifiers() & Qt.AltModifier:
event.setDropAction(Qt.LinkAction)
else:
event.setDropAction(Qt.MoveAction) event.setDropAction(Qt.MoveAction)
for session in self.model().sessions: for session in self.model().sessions:
session.widget.drop_indicator = False session.widget.drop_indicator = False
...@@ -2565,9 +2674,19 @@ class AudioSessionListView(QListView): ...@@ -2565,9 +2674,19 @@ class AudioSessionListView(QListView):
rect = self.viewport().rect() rect = self.viewport().rect()
rect.setTop(self.visualRect(model.index(len(model.sessions)-1)).bottom()) rect.setTop(self.visualRect(model.index(len(model.sessions)-1)).bottom())
if dragged_session.client_conference is not None: if dragged_session.client_conference is not None:
event.setDropAction(Qt.MoveAction)
event.accept(rect) event.accept(rect)
else: else:
event.ignore(rect) event.ignore(rect)
elif event.keyboardModifiers() & Qt.AltModifier and dragged_session.client_conference is None:
if dragged_session is session or session.client_conference is not None or session.blink_session.state != 'connected':
event.ignore(rect)
elif dragged_session.blink_session.transfer_state in ('active', 'completed') or session.blink_session.transfer_state in ('active', 'completed'):
event.ignore(rect)
else:
session.widget.drop_indicator = True
event.setDropAction(Qt.LinkAction) # it might not be LinkAction if other keyboard modifiers are active
event.accept(rect)
else: else:
conference = dragged_session.client_conference or Null conference = dragged_session.client_conference or Null
if dragged_session is session or session.blink_session in conference.sessions: if dragged_session is session or session.blink_session in conference.sessions:
...@@ -2579,6 +2698,7 @@ class AudioSessionListView(QListView): ...@@ -2579,6 +2698,7 @@ class AudioSessionListView(QListView):
sibling.items.audio.widget.drop_indicator = True sibling.items.audio.widget.drop_indicator = True
else: else:
session.widget.drop_indicator = True session.widget.drop_indicator = True
event.setDropAction(Qt.MoveAction)
event.accept(rect) event.accept(rect)
def _DH_ApplicationXBlinkContactList(self, event, index, rect, session): def _DH_ApplicationXBlinkContactList(self, event, index, rect, session):
...@@ -5091,6 +5211,88 @@ class IncomingFileTransferRequest(QObject): ...@@ -5091,6 +5211,88 @@ class IncomingFileTransferRequest(QObject):
self.rejected.emit(self, self.dialog.reject_mode) self.rejected.emit(self, self.dialog.reject_mode)
ui_class, base_class = uic.loadUiType(Resources.get('incoming_calltransfer_dialog.ui'))
class IncomingCallTransferDialog(IncomingDialogBase, ui_class):
def __init__(self, parent=None):
super(IncomingCallTransferDialog, self).__init__(parent)
self.setWindowFlags(Qt.WindowStaysOnTopHint)
self.setAttribute(Qt.WA_DeleteOnClose)
with Resources.directory:
self.setupUi(self)
font = self.username_label.font()
font.setPointSizeF(self.uri_label.fontInfo().pointSizeF() + 3)
font.setFamily("Sans Serif")
self.username_label.setFont(font)
font = self.transfer_label.font()
font.setPointSizeF(self.uri_label.fontInfo().pointSizeF() - 1)
self.transfer_label.setFont(font)
self.slot = None
self.reject_mode = 'reject'
def show(self, activate=True):
self.setAttribute(Qt.WA_ShowWithoutActivating, not activate)
super(IncomingCallTransferDialog, self).show()
del ui_class, base_class
class IncomingCallTransferRequest(QObject):
finished = pyqtSignal(object)
accepted = pyqtSignal(object)
rejected = pyqtSignal(object, str)
priority = 0
stream_types = {'audio'}
def __init__(self, dialog, contact, contact_uri, session):
super(IncomingCallTransferRequest, self).__init__()
self.dialog = dialog
self.contact = contact
self.contact_uri = contact_uri
self.session = session
self.dialog.setWindowTitle(u'Incoming Call Transfer')
self.dialog.setWindowIconText(u'Incoming Call Transfer')
self.dialog.uri_label.setText(contact_uri.uri)
self.dialog.username_label.setText(contact.name)
if contact.pixmap:
self.dialog.user_icon.setPixmap(contact.pixmap)
self.dialog.transfer_label.setText(u'would like to transfer you to {.uri}'.format(contact_uri))
self.dialog.finished.connect(self._SH_DialogFinished)
self.dialog.accepted.connect(self._SH_DialogAccepted)
self.dialog.rejected.connect(self._SH_DialogRejected)
def __eq__(self, other):
return self is other
def __ne__(self, other):
return self is not other
def __lt__(self, other):
return self.priority < other.priority
def __le__(self, other):
return self.priority <= other.priority
def __gt__(self, other):
return self.priority > other.priority
def __ge__(self, other):
return self.priority >= other.priority
def _SH_DialogFinished(self):
self.finished.emit(self)
def _SH_DialogAccepted(self):
self.accepted.emit(self)
def _SH_DialogRejected(self):
self.rejected.emit(self, self.dialog.reject_mode)
ui_class, base_class = uic.loadUiType(Resources.get('conference_dialog.ui')) ui_class, base_class = uic.loadUiType(Resources.get('conference_dialog.ui'))
class ConferenceDialog(base_class, ui_class): class ConferenceDialog(base_class, ui_class):
...@@ -5210,6 +5412,7 @@ class SessionManager(object): ...@@ -5210,6 +5412,7 @@ class SessionManager(object):
self._filetransfer_tone_timer.setSingleShot(True) self._filetransfer_tone_timer.setSingleShot(True)
notification_center = NotificationCenter() notification_center = NotificationCenter()
notification_center.add_observer(self, name='SIPSessionNewOutgoing')
notification_center.add_observer(self, name='SIPSessionNewIncoming') notification_center.add_observer(self, name='SIPSessionNewIncoming')
notification_center.add_observer(self, name='SIPSessionDidFail') notification_center.add_observer(self, name='SIPSessionDidFail')
...@@ -5395,6 +5598,18 @@ class SessionManager(object): ...@@ -5395,6 +5598,18 @@ class SessionManager(object):
if mode == 'reject': if mode == 'reject':
incoming_request.session.reject(603) incoming_request.session.reject(603)
def _SH_IncomingCallTransferRequestAccepted(self, incoming_request):
try:
incoming_request.session.accept_transfer()
except IllegalStateError:
pass
def _SH_IncomingCallTransferRequestRejected(self, incoming_request, mode):
try:
incoming_request.session.reject_transfer()
except IllegalStateError:
pass
@run_in_gui_thread @run_in_gui_thread
def handle_notification(self, notification): def handle_notification(self, notification):
handler = getattr(self, '_NH_%s' % notification.name, Null) handler = getattr(self, '_NH_%s' % notification.name, Null)
...@@ -5457,6 +5672,20 @@ class SessionManager(object): ...@@ -5457,6 +5672,20 @@ class SessionManager(object):
incoming_request.dialog.show(activate=QApplication.activeWindow() is not None and self.incoming_requests.index(incoming_request) == 0) incoming_request.dialog.show(activate=QApplication.activeWindow() is not None and self.incoming_requests.index(incoming_request) == 0)
self.update_ringtone() self.update_ringtone()
def _NH_SIPSessionNewOutgoing(self, notification):
sip_session = notification.sender
if sip_session.transfer_info is not None:
from blink.contacts import URIUtils
contact, contact_uri = URIUtils.find_contact(sip_session.remote_identity.uri)
try:
blink_session = next(session for session in self.sessions if session.reusable and session.contact.settings is contact.settings)
reinitialize = True
except StopIteration:
blink_session = BlinkSession()
reinitialize = False
blink_session.init_transfer(sip_session, notification.data.streams, contact, contact_uri, reinitialize=reinitialize)
def _NH_SIPSessionDidFail(self, notification): def _NH_SIPSessionDidFail(self, notification):
if notification.sender.direction == 'incoming': if notification.sender.direction == 'incoming':
for incoming_request in self.incoming_requests[notification.sender]: for incoming_request in self.incoming_requests[notification.sender]:
...@@ -5510,6 +5739,28 @@ class SessionManager(object): ...@@ -5510,6 +5739,28 @@ class SessionManager(object):
self.sessions.remove(notification.sender) self.sessions.remove(notification.sender)
notification.center.remove_observer(self, sender=notification.sender) notification.center.remove_observer(self, sender=notification.sender)
def _NH_BlinkSessionTransferNewIncoming(self, notification):
from blink.contacts import URIUtils
session = notification.sender.sip_session
contact, contact_uri = URIUtils.find_contact(notification.data.transfer_destination)
dialog = IncomingCallTransferDialog() # Build the dialog without a parent in order to be displayed on the current workspace on Linux.
incoming_request = IncomingCallTransferRequest(dialog, contact, contact_uri, session)
incoming_request.finished.connect(self._SH_IncomingRequestFinished)
incoming_request.accepted.connect(self._SH_IncomingCallTransferRequestAccepted)
incoming_request.rejected.connect(self._SH_IncomingCallTransferRequestRejected)
bisect.insort_right(self.incoming_requests, incoming_request)
incoming_request.dialog.show(activate=QApplication.activeWindow() is not None and self.incoming_requests.index(incoming_request) == 0)
self.update_ringtone()
def _NH_BlinkSessionTransferDidFail(self, notification):
for request in self.incoming_requests[notification.sender.sip_session, IncomingCallTransferRequest]:
request.dialog.hide()
self.incoming_requests.remove(request)
self.update_ringtone()
def _NH_BlinkFileTransferWasCreated(self, notification): def _NH_BlinkFileTransferWasCreated(self, notification):
self.file_transfers.append(notification.sender) self.file_transfers.append(notification.sender)
notification.center.add_observer(self, sender=notification.sender) notification.center.add_observer(self, sender=notification.sender)
......
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>480</width>
<height>165</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>480</width>
<height>165</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>165</height>
</size>
</property>
<property name="windowTitle">
<string>Incoming Call Transfer</string>
</property>
<property name="windowIcon">
<iconset>
<normaloff>icons/blink48.png</normaloff>icons/blink48.png</iconset>
</property>
<layout class="QVBoxLayout" name="dialog_layout">
<property name="spacing">
<number>34</number>
</property>
<property name="margin">
<number>8</number>
</property>
<item>
<widget class="QFrame" name="frame">
<property name="styleSheet">
<string>QFrame#frame {
background-color: #f8f8f8;
border-color: #545454;
border-radius: 4px;
border-width: 2px;
border-style: solid;
}
</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<layout class="QGridLayout" name="frame_layout">
<property name="topMargin">
<number>7</number>
</property>
<property name="bottomMargin">
<number>7</number>
</property>
<property name="verticalSpacing">
<number>0</number>
</property>
<item row="0" column="1">
<widget class="QLabel" name="username_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<family>Sans Serif</family>
<pointsize>12</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Caller name</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="uri_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>68</red>
<green>68</green>
<blue>68</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>68</red>
<green>68</green>
<blue>68</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>68</red>
<green>68</green>
<blue>68</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>68</red>
<green>68</green>
<blue>68</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>118</red>
<green>118</green>
<blue>117</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>118</red>
<green>118</green>
<blue>117</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="text">
<string>Caller URI</string>
</property>
<property name="indent">
<number>1</number>
</property>
</widget>
</item>
<item row="0" column="0" rowspan="2">
<widget class="QLabel" name="user_icon">
<property name="minimumSize">
<size>
<width>36</width>
<height>36</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>36</width>
<height>36</height>
</size>
</property>
<property name="pixmap">
<pixmap>icons/default-avatar.png</pixmap>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<spacer name="frame_spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="transfer_label">
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>68</red>
<green>68</green>
<blue>68</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>68</red>
<green>68</green>
<blue>68</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>68</red>
<green>68</green>
<blue>68</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>68</red>
<green>68</green>
<blue>68</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>118</red>
<green>118</green>
<blue>117</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>118</red>
<green>118</green>
<blue>117</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="text">
<string extracomment="is offering to share his screen">would like to transfer you to user@domain</string>
</property>
<property name="indent">
<number>3</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="button_layout">
<property name="spacing">
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="reject_button">
<property name="minimumSize">
<size>
<width>85</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>24</height>
</size>
</property>
<property name="text">
<string>Reject</string>
</property>
</widget>
</item>
<item>
<spacer name="button_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>
<widget class="QPushButton" name="accept_button">
<property name="minimumSize">
<size>
<width>85</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>24</height>
</size>
</property>
<property name="text">
<string>Accept</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
<tabstop>accept_button</tabstop>
<tabstop>reject_button</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>accept_button</sender>
<signal>clicked()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>345</x>
<y>117</y>
</hint>
<hint type="destinationlabel">
<x>196</x>
<y>67</y>
</hint>
</hints>
</connection>
<connection>
<sender>reject_button</sender>
<signal>clicked()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>48</x>
<y>117</y>
</hint>
<hint type="destinationlabel">
<x>196</x>
<y>67</y>
</hint>
</hints>
</connection>
</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