Commit 8272c5a2 authored by Saul Ibarra's avatar Saul Ibarra

Save presence state history and user icon in configuration

parent 09e75669
...@@ -3,13 +3,18 @@ ...@@ -3,13 +3,18 @@
"""Definitions of datatypes for use in settings extensions.""" """Definitions of datatypes for use in settings extensions."""
__all__ = ['ApplicationDataPath', 'SoundFile', 'DefaultPath', 'CustomSoundFile', 'HTTPURL', 'AuthorizationToken', 'InvalidToken', 'IconDescriptor'] __all__ = ['ApplicationDataPath', 'DefaultPath',
'SoundFile', 'CustomSoundFile',
'HTTPURL',
'AuthorizationToken', 'InvalidToken',
'IconDescriptor',
'PresenceState', 'PresenceStateList']
import os import os
import re import re
from urlparse import urlparse from urlparse import urlparse
from sipsimple.configuration.datatypes import Hostname from sipsimple.configuration.datatypes import Hostname, List
from blink.resources import ApplicationData from blink.resources import ApplicationData
...@@ -189,3 +194,39 @@ class IconDescriptor(object): ...@@ -189,3 +194,39 @@ class IconDescriptor(object):
return self.__dict__['url'].startswith('file://') return self.__dict__['url'].startswith('file://')
class PresenceState(object):
def __init__(self, state, note=None):
self.state = unicode(state)
self.note = note
def __getstate__(self):
if not self.note:
return unicode(self.state)
else:
return u'%s,%s' % (self.state, self.note)
def __setstate__(self, data):
try:
state, note = data.split(u',', 1)
except ValueError:
self.__init__(data)
else:
self.__init__(state, note)
def __eq__(self, other):
if isinstance(other, PresenceState):
return self.state==other.state and self.note==other.note
return NotImplemented
def __ne__(self, other):
equal = self.__eq__(other)
return NotImplemented if equal is NotImplemented else not equal
def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self.state, self.note)
class PresenceStateList(List):
type = PresenceState
...@@ -3,17 +3,17 @@ ...@@ -3,17 +3,17 @@
"""Blink settings extensions.""" """Blink settings extensions."""
__all__ = ['SIPSimpleSettingsExtension'] __all__ = ['BlinkSettings', 'SIPSimpleSettingsExtension']
import platform import platform
import sys import sys
from sipsimple.configuration import Setting, SettingsGroup, SettingsObjectExtension from sipsimple.configuration import Setting, SettingsGroup, SettingsObject, SettingsObjectExtension
from sipsimple.configuration.datatypes import AudioCodecList, NonNegativeInteger, PositiveInteger, Path, SampleRate from sipsimple.configuration.datatypes import AudioCodecList, NonNegativeInteger, PositiveInteger, Path, SampleRate
from sipsimple.configuration.settings import AudioSettings, ChatSettings, FileTransferSettings, LogsSettings, RTPSettings, TLSSettings from sipsimple.configuration.settings import AudioSettings, ChatSettings, FileTransferSettings, LogsSettings, RTPSettings, TLSSettings
from blink import __version__ from blink import __version__
from blink.configuration.datatypes import ApplicationDataPath, AuthorizationToken, HTTPURL, SoundFile from blink.configuration.datatypes import ApplicationDataPath, AuthorizationToken, HTTPURL, IconDescriptor, SoundFile, PresenceState, PresenceStateList
from blink.resources import Resources from blink.resources import Resources
...@@ -91,3 +91,14 @@ class SIPSimpleSettingsExtension(SettingsObjectExtension): ...@@ -91,3 +91,14 @@ class SIPSimpleSettingsExtension(SettingsObjectExtension):
user_agent = Setting(type=str, default='Blink %s (%s)' % (__version__, platform.system() if sys.platform!='darwin' else 'MacOSX Qt')) user_agent = Setting(type=str, default='Blink %s (%s)' % (__version__, platform.system() if sys.platform!='darwin' else 'MacOSX Qt'))
class BlinkPresenceSettings(SettingsGroup):
current_state = Setting(type=PresenceState, default=PresenceState('Available'))
state_history = Setting(type=PresenceStateList, default=PresenceStateList())
icon = Setting(type=IconDescriptor, nillable=True)
class BlinkSettings(SettingsObject):
__id__ = 'BlinkSettings'
presence = BlinkPresenceSettings
...@@ -3,12 +3,15 @@ ...@@ -3,12 +3,15 @@
__all__ = ['MainWindow'] __all__ = ['MainWindow']
import hashlib
import os
from functools import partial from functools import partial
from PyQt4 import uic from PyQt4 import uic
from PyQt4.QtCore import QUrl from PyQt4.QtCore import QUrl
from PyQt4.QtGui import QAction, QActionGroup, QDesktopServices, QShortcut from PyQt4.QtGui import QAction, QActionGroup, QDesktopServices, QShortcut
from PyQt4.QtGui import QIcon, QStyle, QStyleOptionComboBox, QStyleOptionFrameV2 from PyQt4.QtGui import QFileDialog, QIcon, QStyle, QStyleOptionComboBox, QStyleOptionFrameV2
from application.notification import IObserver, NotificationCenter from application.notification import IObserver, NotificationCenter
from application.python import Null, limit from application.python import Null, limit
...@@ -24,8 +27,9 @@ from blink.contacts import BonjourNeighbour, Contact, Group, ContactEditorDialog ...@@ -24,8 +27,9 @@ from blink.contacts import BonjourNeighbour, Contact, Group, ContactEditorDialog
from blink.history import HistoryManager from blink.history import HistoryManager
from blink.preferences import PreferencesWindow from blink.preferences import PreferencesWindow
from blink.sessions import ConferenceDialog, SessionManager, SessionModel from blink.sessions import ConferenceDialog, SessionManager, SessionModel
from blink.configuration.datatypes import InvalidToken from blink.configuration.datatypes import IconDescriptor, InvalidToken, PresenceState
from blink.resources import Resources from blink.configuration.settings import BlinkSettings
from blink.resources import IconManager, Resources
from blink.util import run_in_gui_thread from blink.util import run_in_gui_thread
from blink.widgets.buttons import AccountState, SwitchViewButton from blink.widgets.buttons import AccountState, SwitchViewButton
...@@ -53,7 +57,11 @@ class MainWindow(base_class, ui_class): ...@@ -53,7 +57,11 @@ class MainWindow(base_class, ui_class):
self.setWindowTitle('Blink') self.setWindowTitle('Blink')
self.setWindowIconText('Blink') self.setWindowIconText('Blink')
self.set_user_icon(Resources.get("icons/avatar.jpg")) # ":/resources/icons/avatar.png"
self.default_icon_path = Resources.get('icons/avatar.jpg')
self.default_icon = QIcon(self.default_icon_path)
self.last_icon_directory = os.path.expanduser('~')
self.set_user_icon(IconManager().get('myicon'))
self.active_sessions_label.hide() self.active_sessions_label.hide()
self.enable_call_buttons(False) self.enable_call_buttons(False)
...@@ -95,6 +103,7 @@ class MainWindow(base_class, ui_class): ...@@ -95,6 +103,7 @@ class MainWindow(base_class, ui_class):
# Signals # Signals
self.account_state.stateChanged.connect(self._SH_AccountStateChanged) self.account_state.stateChanged.connect(self._SH_AccountStateChanged)
self.account_state.clicked.connect(self._SH_AccountStateClicked)
self.activity_note.editingFinished.connect(self._SH_ActivityNoteEditingFinished) self.activity_note.editingFinished.connect(self._SH_ActivityNoteEditingFinished)
self.add_contact_button.clicked.connect(self._SH_AddContactButtonClicked) self.add_contact_button.clicked.connect(self._SH_AddContactButtonClicked)
self.add_search_contact_button.clicked.connect(self._SH_AddContactButtonClicked) self.add_search_contact_button.clicked.connect(self._SH_AddContactButtonClicked)
...@@ -204,8 +213,8 @@ class MainWindow(base_class, ui_class): ...@@ -204,8 +213,8 @@ class MainWindow(base_class, ui_class):
self.preferences_window.close() self.preferences_window.close()
self.server_tools_window.close() self.server_tools_window.close()
def set_user_icon(self, image_file_name): def set_user_icon(self, icon):
self.account_state.setIcon(QIcon(image_file_name)) self.account_state.setIcon(icon or self.default_icon)
def enable_call_buttons(self, enabled): def enable_call_buttons(self, enabled):
self.audio_call_button.setEnabled(enabled) self.audio_call_button.setEnabled(enabled)
...@@ -345,14 +354,52 @@ class MainWindow(base_class, ui_class): ...@@ -345,14 +354,52 @@ class MainWindow(base_class, ui_class):
account = None account = None
session_manager.start_call(None, action.entry.target_uri, account=account) session_manager.start_call(None, action.entry.target_uri, account=account)
def _SH_AccountStateChanged(self, action): def _SH_AccountStateChanged(self):
self.activity_note.setText(action.note) self.activity_note.setText(self.account_state.note)
self.saved_account_state = None if self.account_state.state is AccountState.Invisible:
self.activity_note.inactiveText = u'(invisible)'
self.activity_note.setEnabled(False)
else:
if not self.activity_note.isEnabled():
self.activity_note.inactiveText = u'Add an activity note here'
self.activity_note.setEnabled(True)
if not self.account_state.state.internal:
self.saved_account_state = None
settings = BlinkSettings()
if self.saved_account_state:
settings.presence.current_state = PresenceState(*self.saved_account_state)
else:
settings.presence.current_state = PresenceState(self.account_state.state, self.account_state.note)
settings.presence.state_history = [PresenceState(state, note) for state, note in self.account_state.history]
settings.save()
def _SH_AccountStateClicked(self, checked):
filename = QFileDialog.getOpenFileName(self, u'Select Icon', self.last_icon_directory, u"Images (*.png *.tiff *.jpg *.xmp *.svg)")
if filename:
self.last_icon_directory = os.path.dirname(filename)
filename = filename if os.path.realpath(filename) != os.path.realpath(self.default_icon_path) else None
settings = BlinkSettings()
icon_manager = IconManager()
if filename is not None:
icon = icon_manager.store_file('myicon', filename)
try:
hash = hashlib.sha512(open(icon.filename, 'r').read()).hexdigest()
except Exception:
settings.presence.icon = None
else:
settings.presence.icon = IconDescriptor('file://'+icon.filename, hash)
else:
icon_manager.remove('myicon')
icon = None
settings.presence.icon = None
settings.save()
def _SH_ActivityNoteEditingFinished(self): def _SH_ActivityNoteEditingFinished(self):
self.activity_note.clearFocus() self.activity_note.clearFocus()
self.account_state.setState(self.account_state.state, self.activity_note.text()) note = self.activity_note.text()
self.saved_account_state = None if note != self.account_state.note:
self.account_state.setState(self.account_state.state, note)
self.saved_account_state = None
def _SH_AddContactButtonClicked(self, clicked): def _SH_AddContactButtonClicked(self, clicked):
model = self.contact_model model = self.contact_model
...@@ -511,12 +558,10 @@ class MainWindow(base_class, ui_class): ...@@ -511,12 +558,10 @@ class MainWindow(base_class, ui_class):
if self.account_state.state is not AccountState.Invisible: if self.account_state.state is not AccountState.Invisible:
if self.saved_account_state is None: if self.saved_account_state is None:
self.saved_account_state = self.account_state.state, self.activity_note.text() self.saved_account_state = self.account_state.state, self.activity_note.text()
self.account_state.setState(AccountState.Busy) self.account_state.setState(AccountState.Busy.Internal, note=u'On the phone')
self.activity_note.setText(u'On the phone')
elif self.saved_account_state is not None: elif self.saved_account_state is not None:
state, note = self.saved_account_state state, note = self.saved_account_state
self.saved_account_state = None self.saved_account_state = None
self.activity_note.setText(note)
self.account_state.setState(state, note) self.account_state.setState(state, note)
def _SH_SilentButtonClicked(self, silent): def _SH_SilentButtonClicked(self, silent):
...@@ -567,13 +612,15 @@ class MainWindow(base_class, ui_class): ...@@ -567,13 +612,15 @@ class MainWindow(base_class, ui_class):
self.display_name.setEnabled(False) self.display_name.setEnabled(False)
self.activity_note.setEnabled(False) self.activity_note.setEnabled(False)
self.account_state.setEnabled(False) self.account_state.setEnabled(False)
else:
self.account_state.setState(AccountState.Available)
def _NH_SIPApplicationDidStart(self, notification): def _NH_SIPApplicationDidStart(self, notification):
self.load_audio_devices() self.load_audio_devices()
notification.center.add_observer(self, name='CFGSettingsObjectDidChange') notification.center.add_observer(self, name='CFGSettingsObjectDidChange')
notification.center.add_observer(self, name='AudioDevicesDidChange') notification.center.add_observer(self, name='AudioDevicesDidChange')
settings = BlinkSettings()
self.account_state.history = [(item.state, item.note) for item in settings.presence.state_history]
state = getattr(AccountState, settings.presence.current_state.state, AccountState.Available)
self.account_state.setState(state, settings.presence.current_state.note)
def _NH_AudioDevicesDidChange(self, notification): def _NH_AudioDevicesDidChange(self, notification):
for action in self.output_device_menu.actions(): for action in self.output_device_menu.actions():
......
...@@ -744,14 +744,28 @@ class StateButton(QToolButton): ...@@ -744,14 +744,28 @@ class StateButton(QToolButton):
class PresenceState(object): class PresenceState(object):
def __init__(self, name, color, icon): def __init__(self, name, color, icon, internal=False):
self.name = name self.name = name
self.color = color self.color = color
self.icon = icon self.icon = icon
self.internal = internal
def __eq__(self, other):
if isinstance(other, PresenceState):
return self.name == other.name
return NotImplemented
def __ne__(self, other):
equal = self.__eq__(other)
return NotImplemented if equal is NotImplemented else not equal
def __repr__(self): def __repr__(self):
return self.name return self.name
@property
def Internal(self):
return PresenceState(self.name, self.color, self.icon, True)
class AccountState(StateButton): class AccountState(StateButton):
Invisible = PresenceState('Invisible', '#efedeb', Resources.get('icons/state-invisible.svg')) Invisible = PresenceState('Invisible', '#efedeb', Resources.get('icons/state-invisible.svg'))
...@@ -759,7 +773,7 @@ class AccountState(StateButton): ...@@ -759,7 +773,7 @@ class AccountState(StateButton):
Away = PresenceState('Away', '#ffff00', Resources.get('icons/state-away.svg')) Away = PresenceState('Away', '#ffff00', Resources.get('icons/state-away.svg'))
Busy = PresenceState('Busy', '#ff0000', Resources.get('icons/state-busy.svg')) Busy = PresenceState('Busy', '#ff0000', Resources.get('icons/state-busy.svg'))
stateChanged = pyqtSignal(QAction) stateChanged = pyqtSignal()
history_size = 7 history_size = 7
...@@ -774,37 +788,56 @@ class AccountState(StateButton): ...@@ -774,37 +788,56 @@ class AccountState(StateButton):
menu.triggered.connect(self._SH_MenuTriggered) menu.triggered.connect(self._SH_MenuTriggered)
self.setMenu(menu) self.setMenu(menu)
self.state = self.Invisible self.state = self.Invisible
self.note = None
def _get_history(self):
return [(action.state.name, action.note) for action in self.menu().actions()[5:]]
def _set_history(self, values):
menu = self.menu()
for action in menu.actions()[5:]:
menu.removeAction(action)
for state_name, note in values:
try:
state = getattr(self, state_name)
except AttributeError:
continue
action = QAction(QIcon(state.icon), note, menu)
action.state = state
action.note = note
menu.addAction(action)
history = property(_get_history, _set_history)
del _get_history, _set_history
def _SH_MenuTriggered(self, action): def _SH_MenuTriggered(self, action):
if hasattr(action, 'state'): if hasattr(action, 'state'):
self.setState(action.state, action.note) self.setState(action.state, action.note)
self.stateChanged.emit(action)
def setState(self, state, note=None): def setState(self, state, note=None):
if state == self.state and note == self.note:
return
self.state = state self.state = state
self.note = note
palette = self.palette() palette = self.palette()
palette.setColor(QPalette.Button, QColor(state.color)) palette.setColor(QPalette.Button, QColor(state.color))
self.setPalette(palette) self.setPalette(palette)
if not note: if note and not state.internal:
return menu = self.menu()
menu = self.menu() actions = menu.actions()[5:]
actions = menu.actions()[5:] try:
try: action = next(action for action in actions if action.state is state and action.note == note)
action = next(action for action in actions if action.state is state and action.note == note) except StopIteration:
except StopIteration: action = QAction(QIcon(state.icon), note, menu)
action = QAction(QIcon(state.icon), note, menu) if len(actions) == 0:
if len(actions) == 0: menu.addAction(action)
menu.addAction(action) else:
if len(actions) >= self.history_size:
menu.removeAction(actions[-1])
menu.insertAction(actions[0], action)
action.state = state
action.note = note
else: else:
if len(actions) >= self.history_size: if action is not actions[0]:
menu.removeAction(actions[-1]) menu.removeAction(action)
menu.insertAction(actions[0], action) menu.insertAction(actions[0], action)
action.state = state self.stateChanged.emit()
action.note = note
else:
if action is not actions[0]:
menu.removeAction(action)
menu.insertAction(actions[0], action)
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