Commit 5c56d0ae authored by Luci Stanescu's avatar Luci Stanescu

Added support for audio calls

parent f465c560
......@@ -13,9 +13,12 @@ from zope.interface import implements
from sipsimple.application import SIPApplication
from sipsimple.configuration.backend.file import FileBackend
from sipsimple.configuration.settings import SIPSimpleSettings
from blink.configuration.settings import SIPSimpleSettingsExtension
from blink.mainwindow import MainWindow
from blink.resources import ApplicationData
from blink.sessions import SessionManager
from blink.util import QSingleton, run_in_gui_thread
......@@ -29,6 +32,10 @@ class Blink(QApplication):
self.application = SIPApplication()
self.main_window = MainWindow()
SIPSimpleSettings.register_extension(SIPSimpleSettingsExtension)
session_manager = SessionManager()
session_manager.initialize(self.main_window, self.main_window.session_model)
def run(self):
from blink.util import call_in_gui_thread as call_later
call_later(self._initialize_sipsimple)
......
# Copyright (C) 2010 AG Projects. See LICENSE for details.
#
# Copyright (C) 2010 AG Projects. See LICENSE for details.
#
"""Definitions of datatypes for use in settings extensions."""
__all__ = ['ApplicationDataPath', 'SoundFile']
import os
from blink.resources import ApplicationData
class ApplicationDataPath(unicode):
def __new__(cls, path):
path = os.path.normpath(path)
if path.startswith(ApplicationData.directory+os.path.sep):
path = path[len(ApplicationData.directory+os.path.sep):]
return unicode.__new__(cls, path)
@property
def normalized(self):
return ApplicationData.get(self)
class SoundFile(object):
def __init__(self, path, volume=100):
self.path = path
self.volume = int(volume)
if self.volume < 0 or self.volume > 100:
raise ValueError('illegal volume level: %d' % self.volume)
def __getstate__(self):
return u'%s,%s' % (self.__dict__['path'], self.volume)
def __setstate__(self, state):
try:
path, volume = state.rsplit(u',', 1)
except ValueError:
self.__init__(state)
else:
self.__init__(path, volume)
def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self.path, self.volume)
def _get_path(self):
return ApplicationData.get(self.__dict__['path'])
def _set_path(self, path):
path = os.path.normpath(path)
if path.startswith(ApplicationData.directory+os.path.sep):
path = path[len(ApplicationData.directory+os.path.sep):]
self.__dict__['path'] = path
path = property(_get_path, _set_path)
del _get_path, _set_path
# Copyright (C) 2010 AG Projects. See LICENSE for details.
#
"""Blink settings extensions."""
__all__ = ['SIPSimpleSettingsExtension']
from sipsimple.configuration import Setting, SettingsGroup, SettingsObjectExtension
from sipsimple.configuration.settings import AudioSettings
from blink.configuration.datatypes import ApplicationDataPath, SoundFile
from blink.resources import Resources
class AudioSettingsExtension(AudioSettings):
recordings_directory = Setting(type=ApplicationDataPath, default=ApplicationDataPath('recordings'), nillable=False)
class SoundSettings(SettingsGroup):
outbound_ringtone = Setting(type=SoundFile, default=SoundFile(Resources.get('sounds/ring_outbound.wav')), nillable=True)
class SIPSimpleSettingsExtension(SettingsObjectExtension):
audio = AudioSettingsExtension
sounds = SoundSettings
......@@ -24,11 +24,13 @@ from functools import partial
from operator import attrgetter
from zope.interface import implements
from sipsimple.account import BonjourAccount
from sipsimple.account import AccountManager, BonjourAccount
from sipsimple.util import makedirs
from blink.resources import ApplicationData, Resources, IconCache
from blink.sessions import SessionManager
from blink.util import run_in_gui_thread
from blink.widgets.buttons import SwitchViewButton
# Functions decorated with updates_contacts_db or ignore_contacts_db_updates must
......@@ -859,6 +861,12 @@ class ContactModel(QAbstractListModel):
self.deleted_items.append(items)
self.itemsRemoved.emit(items)
def iter_contacts(self):
return (item for item in self.items if isinstance(item, Contact))
def iter_contact_groups(self):
return (item for item in self.items if isinstance(item, ContactGroup))
def load(self):
try:
try:
......@@ -1092,11 +1100,31 @@ class ContactListView(QListView):
menu.addAction(self.actions.delete_item)
menu.addAction(self.actions.undo_last_delete)
self.actions.undo_last_delete.setText(undo_delete_text)
account_manager = AccountManager()
default_account = account_manager.default_account
self.actions.start_audio_session.setEnabled(default_account is not None)
self.actions.start_chat_session.setEnabled(default_account is not None)
self.actions.send_sms.setEnabled(default_account is not None)
self.actions.send_files.setEnabled(default_account is not None)
self.actions.request_remote_desktop.setEnabled(default_account is not None)
self.actions.share_my_desktop.setEnabled(default_account is not None)
self.actions.edit_item.setEnabled(contact.editable)
self.actions.delete_item.setEnabled(contact.deletable)
self.actions.undo_last_delete.setEnabled(len(model.deleted_items) > 0)
menu.exec_(event.globalPos())
def keyPressEvent(self, event):
if event.key() in (Qt.Key_Enter, Qt.Key_Return):
selected_indexes = self.selectionModel().selectedIndexes()
if len(selected_indexes) == 1:
contact = self.model().data(selected_indexes[0])
if not isinstance(contact, Contact):
return
session_manager = SessionManager()
session_manager.start_call(contact.name, contact.uri, account=BonjourAccount() if isinstance(contact, BonjourNeighbour) else None)
else:
super(ContactListView, self).keyPressEvent(event)
def _AH_AddGroup(self):
group = ContactGroup("")
model = self.model()
......@@ -1145,6 +1173,8 @@ class ContactListView(QListView):
def _AH_StartAudioCall(self):
contact = self.model().data(self.selectionModel().selectedIndexes()[0])
session_manager = SessionManager()
session_manager.start_call(contact.name, contact.uri, account=BonjourAccount() if isinstance(contact, BonjourNeighbour) else None)
def _AH_StartChatSession(self):
contact = self.model().data(self.selectionModel().selectedIndexes()[0])
......@@ -1167,7 +1197,10 @@ class ContactListView(QListView):
for group in self.model().contact_groups:
group.restore_state()
self.needs_restore = False
self.model().main_window.switch_view_button.dnd_active = False
main_window = self.model().main_window
main_window.switch_view_button.dnd_active = False
if not main_window.session_model.sessions:
main_window.switch_view_button.view = SwitchViewButton.ContactView
def dragEnterEvent(self, event):
model = self.model()
......@@ -1374,11 +1407,31 @@ class ContactSearchListView(QListView):
menu.addAction(self.actions.delete_item)
menu.addAction(self.actions.undo_last_delete)
self.actions.undo_last_delete.setText(undo_delete_text)
account_manager = AccountManager()
default_account = account_manager.default_account
self.actions.start_audio_session.setEnabled(default_account is not None)
self.actions.start_chat_session.setEnabled(default_account is not None)
self.actions.send_sms.setEnabled(default_account is not None)
self.actions.send_files.setEnabled(default_account is not None)
self.actions.request_remote_desktop.setEnabled(default_account is not None)
self.actions.share_my_desktop.setEnabled(default_account is not None)
self.actions.edit_item.setEnabled(contact.editable)
self.actions.delete_item.setEnabled(contact.deletable)
self.actions.undo_last_delete.setEnabled(len(source_model.deleted_items) > 0)
menu.exec_(event.globalPos())
def keyPressEvent(self, event):
if event.key() in (Qt.Key_Enter, Qt.Key_Return):
selected_indexes = self.selectionModel().selectedIndexes()
if len(selected_indexes) == 1:
contact = self.model().data(selected_indexes[0])
if not isinstance(contact, Contact):
return
session_manager = SessionManager()
session_manager.start_call(contact.name, contact.uri, account=BonjourAccount() if isinstance(contact, BonjourNeighbour) else None)
else:
super(ContactSearchListView, self).keyPressEvent(event)
def _AH_EditItem(self):
model = self.model()
contact = model.data(self.selectionModel().selectedIndexes()[0])
......@@ -1397,6 +1450,8 @@ class ContactSearchListView(QListView):
def _AH_StartAudioCall(self):
contact = self.model().data(self.selectionModel().selectedIndexes()[0])
session_manager = SessionManager()
session_manager.start_call(contact.name, contact.uri, account=BonjourAccount() if isinstance(contact, BonjourNeighbour) else None)
def _AH_StartChatSession(self):
contact = self.model().data(self.selectionModel().selectedIndexes()[0])
......@@ -1415,7 +1470,10 @@ class ContactSearchListView(QListView):
def startDrag(self, supported_actions):
super(ContactSearchListView, self).startDrag(supported_actions)
self.model().main_window.switch_view_button.dnd_active = False
main_window = self.model().main_window
main_window.switch_view_button.dnd_active = False
if not main_window.session_model.sessions:
main_window.switch_view_button.view = SwitchViewButton.ContactView
def dragEnterEvent(self, event):
model = self.model()
......
This diff is collapsed.
This diff is collapsed.
# Copyright (C) 2010 AG Projects. See LICENSE for details.
#
__all__ = ['QSingleton', 'call_in_gui_thread', 'run_in_gui_thread']
__all__ = ['QSingleton', 'call_in_gui_thread', 'call_later', 'run_in_gui_thread']
from PyQt4.QtCore import QObject
from PyQt4.QtCore import QObject, QTimer
from application.python.decorator import decorator, preserve_signature
from application.python.util import Singleton
......@@ -19,6 +19,11 @@ def call_in_gui_thread(function, *args, **kw):
blink.postEvent(blink, CallFunctionEvent(function, args, kw))
def call_later(interval, function, *args, **kw):
interval = int(interval*1000)
QTimer.singleShot(interval, lambda: function(*args, **kw))
@decorator
def run_in_gui_thread(func):
@preserve_signature(func)
......
# Copyright (c) 2010 AG Projects. See LICENSE for details.
#
__all__ = ['ToolButton', 'ConferenceButton', 'StreamButton', 'SegmentButton', 'SingleSegment', 'LeftSegment', 'MiddleSegment', 'RightSegment', 'SwitchViewButton']
__all__ = ['ToolButton', 'ConferenceButton', 'StreamButton', 'SegmentButton', 'SingleSegment', 'LeftSegment', 'MiddleSegment', 'RightSegment', 'RecordButton', 'SwitchViewButton']
from PyQt4.QtCore import QTimer, pyqtSignal
from PyQt4.QtGui import QAction, QIcon, QPushButton, QStyle, QStyleOptionToolButton, QStylePainter, QToolButton
......@@ -202,6 +202,52 @@ class SegmentButton(QToolButton):
signal.emit()
class RecordButton(SegmentButton):
def __init__(self, parent=None):
super(RecordButton, self).__init__(parent)
self.timer_id = None
self.toggled.connect(self._SH_Toggled)
self.animation_icons = []
self.animation_icon_index = 0
def _get_animation_icon_index(self):
return self.__dict__['animation_icon_index']
def _set_animation_icon_index(self, index):
self.__dict__['animation_icon_index'] = index
self.update()
animation_icon_index = property(_get_animation_icon_index, _set_animation_icon_index)
del _get_animation_icon_index, _set_animation_icon_index
def setIcon(self, icon):
super(RecordButton, self).setIcon(icon)
on_icon = QIcon(icon)
off_icon = QIcon(icon)
for size in off_icon.availableSizes(QIcon.Normal, QIcon.On):
pixmap = off_icon.pixmap(size, QIcon.Normal, QIcon.Off)
off_icon.addPixmap(pixmap, QIcon.Normal, QIcon.On)
self.animation_icons = [on_icon, off_icon]
def paintEvent(self, event):
painter = QStylePainter(self)
option = QStyleOptionToolButton()
self.initStyleOption(option)
option.icon = self.animation_icons[self.animation_icon_index]
painter.drawComplexControl(QStyle.CC_ToolButton, option)
def timerEvent(self, event):
self.animation_icon_index = (self.animation_icon_index+1) % len(self.animation_icons)
def _SH_Toggled(self, checked):
if checked:
self.timer_id = self.startTimer(1000)
self.animation_icon_index = 0
else:
self.killTimer(self.timer_id)
self.timer_id = None
class SwitchViewButton(QPushButton):
ContactView = 0
SessionView = 1
......
# Copyright (c) 2010 AG Projects. See LICENSE for details.
#
__all__ = ['IconSelector', 'LatencyLabel', 'PacketLossLabel', 'StreamInfoLabel']
__all__ = ['DurationLabel', 'IconSelector', 'LatencyLabel', 'PacketLossLabel', 'StatusLabel', 'StreamInfoLabel']
import os
from datetime import timedelta
from PyQt4.QtCore import Qt
from PyQt4.QtGui import QFileDialog, QFontMetrics, QLabel, QPixmap
from PyQt4.QtGui import QColor, QFileDialog, QFontMetrics, QLabel, QPalette, QPixmap
from blink.resources import ApplicationData, Resources
from blink.widgets.util import QtDynamicProperty
......@@ -86,10 +87,29 @@ class StreamInfoLabel(QLabel):
self.setText(text)
class DurationLabel(QLabel):
def __init__(self, parent=None):
super(DurationLabel, self).__init__(parent)
self.value = timedelta(0)
def _get_value(self):
return self.__dict__['value']
def _set_value(self, value):
self.__dict__['value'] = value
seconds = value.seconds % 60
minutes = value.seconds // 60 % 60
hours = value.seconds//3600 + value.days*24
self.setText(u'%d:%02d:%02d' % (hours, minutes, seconds))
value = property(_get_value, _set_value)
del _get_value, _set_value
class LatencyLabel(QLabel):
def __init__(self, parent=None):
super(LatencyLabel, self).__init__(parent)
self.treshold = 99
self.threshold = 99
self.value = 0
def _get_value(self):
......@@ -97,7 +117,7 @@ class LatencyLabel(QLabel):
def _set_value(self, value):
self.__dict__['value'] = value
if value > self.treshold:
if value > self.threshold:
text = u'Latency %sms' % value
self.setMinimumWidth(QFontMetrics(self.font()).width(text))
self.setText(text)
......@@ -112,7 +132,7 @@ class LatencyLabel(QLabel):
class PacketLossLabel(QLabel):
def __init__(self, parent=None):
super(PacketLossLabel, self).__init__(parent)
self.treshold = 0
self.threshold = 0
self.value = 0
def _get_value(self):
......@@ -120,7 +140,7 @@ class PacketLossLabel(QLabel):
def _set_value(self, value):
self.__dict__['value'] = value
if value > self.treshold:
if value > self.threshold:
text = u'Packet loss %s%%' % value
self.setMinimumWidth(QFontMetrics(self.font()).width(text))
self.setText(text)
......@@ -132,3 +152,28 @@ class PacketLossLabel(QLabel):
del _get_value, _set_value
class StatusLabel(QLabel):
def __init__(self, parent=None):
super(StatusLabel, self).__init__(parent)
self.value = None
def _get_value(self):
return self.__dict__['value']
def _set_value(self, value):
self.__dict__['value'] = value
if value is not None:
color = QColor(value.color)
palette = self.palette()
palette.setColor(QPalette.WindowText, color)
palette.setColor(QPalette.Text, color)
self.setPalette(palette)
self.setText(unicode(value))
self.show()
else:
self.hide()
value = property(_get_value, _set_value)
del _get_value, _set_value
......@@ -2,9 +2,6 @@
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
......@@ -28,8 +25,9 @@
<property name="windowTitle">
<string>Incoming session request</string>
</property>
<property name="modal">
<bool>true</bool>
<property name="windowIcon">
<iconset>
<normaloff>icons/blink48.png</normaloff>icons/blink48.png</iconset>
</property>
<layout class="QVBoxLayout" name="dialog_layout">
<property name="spacing">
......
......@@ -396,7 +396,7 @@
<number>1</number>
</property>
<item>
<widget class="QLabel" name="duration_label">
<widget class="DurationLabel" name="duration_label">
<property name="minimumSize">
<size>
<width>38</width>
......@@ -409,16 +409,13 @@
</widget>
</item>
<item>
<widget class="QLabel" name="status_label">
<widget class="StatusLabel" name="status_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>On Hold</string>
</property>
</widget>
</item>
<item>
......@@ -524,7 +521,7 @@ QToolButton:pressed {
</widget>
</item>
<item>
<widget class="SegmentButton" name="record_button">
<widget class="RecordButton" name="record_button">
<property name="minimumSize">
<size>
<width>18</width>
......@@ -557,7 +554,8 @@ QToolButton:pressed {
</property>
<property name="icon">
<iconset>
<normaloff>icons/record.png</normaloff>icons/record.png</iconset>
<normaloff>icons/record.png</normaloff>
<normalon>../../../blink-qt/resources/icons/recording.png</normalon>icons/record.png</iconset>
</property>
<property name="iconSize">
<size>
......@@ -565,6 +563,9 @@ QToolButton:pressed {
<height>16</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
......@@ -640,6 +641,21 @@ QToolButton:pressed {
<extends>QLabel</extends>
<header>blink.widgets.labels</header>
</customwidget>
<customwidget>
<class>DurationLabel</class>
<extends>QLabel</extends>
<header>blink.widgets.labels</header>
</customwidget>
<customwidget>
<class>StatusLabel</class>
<extends>QLabel</extends>
<header>blink.widgets.labels</header>
</customwidget>
<customwidget>
<class>RecordButton</class>
<extends>QToolButton</extends>
<header>blink.widgets.buttons</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
......
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