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 @@
"""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 re
from urlparse import urlparse
from sipsimple.configuration.datatypes import Hostname
from sipsimple.configuration.datatypes import Hostname, List
from blink.resources import ApplicationData
......@@ -189,3 +194,39 @@ class IconDescriptor(object):
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 @@
"""Blink settings extensions."""
__all__ = ['SIPSimpleSettingsExtension']
__all__ = ['BlinkSettings', 'SIPSimpleSettingsExtension']
import platform
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.settings import AudioSettings, ChatSettings, FileTransferSettings, LogsSettings, RTPSettings, TLSSettings
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
......@@ -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'))
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 @@
__all__ = ['MainWindow']
import hashlib
import os
from functools import partial
from PyQt4 import uic
from PyQt4.QtCore import QUrl
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.python import Null, limit
......@@ -24,8 +27,9 @@ from blink.contacts import BonjourNeighbour, Contact, Group, ContactEditorDialog
from blink.history import HistoryManager
from blink.preferences import PreferencesWindow
from blink.sessions import ConferenceDialog, SessionManager, SessionModel
from blink.configuration.datatypes import InvalidToken
from blink.resources import Resources
from blink.configuration.datatypes import IconDescriptor, InvalidToken, PresenceState
from blink.configuration.settings import BlinkSettings
from blink.resources import IconManager, Resources
from blink.util import run_in_gui_thread
from blink.widgets.buttons import AccountState, SwitchViewButton
......@@ -53,7 +57,11 @@ class MainWindow(base_class, ui_class):
self.setWindowTitle('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.enable_call_buttons(False)
......@@ -95,6 +103,7 @@ class MainWindow(base_class, ui_class):
# Signals
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.add_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):
self.preferences_window.close()
self.server_tools_window.close()
def set_user_icon(self, image_file_name):
self.account_state.setIcon(QIcon(image_file_name))
def set_user_icon(self, icon):
self.account_state.setIcon(icon or self.default_icon)
def enable_call_buttons(self, enabled):
self.audio_call_button.setEnabled(enabled)
......@@ -345,14 +354,52 @@ class MainWindow(base_class, ui_class):
account = None
session_manager.start_call(None, action.entry.target_uri, account=account)
def _SH_AccountStateChanged(self, action):
self.activity_note.setText(action.note)
self.saved_account_state = None
def _SH_AccountStateChanged(self):
self.activity_note.setText(self.account_state.note)
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):
self.activity_note.clearFocus()
self.account_state.setState(self.account_state.state, self.activity_note.text())
self.saved_account_state = None
note = self.activity_note.text()
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):
model = self.contact_model
......@@ -511,12 +558,10 @@ class MainWindow(base_class, ui_class):
if self.account_state.state is not AccountState.Invisible:
if self.saved_account_state is None:
self.saved_account_state = self.account_state.state, self.activity_note.text()
self.account_state.setState(AccountState.Busy)
self.activity_note.setText(u'On the phone')
self.account_state.setState(AccountState.Busy.Internal, note=u'On the phone')
elif self.saved_account_state is not None:
state, note = self.saved_account_state
self.saved_account_state = None
self.activity_note.setText(note)
self.account_state.setState(state, note)
def _SH_SilentButtonClicked(self, silent):
......@@ -567,13 +612,15 @@ class MainWindow(base_class, ui_class):
self.display_name.setEnabled(False)
self.activity_note.setEnabled(False)
self.account_state.setEnabled(False)
else:
self.account_state.setState(AccountState.Available)
def _NH_SIPApplicationDidStart(self, notification):
self.load_audio_devices()
notification.center.add_observer(self, name='CFGSettingsObjectDidChange')
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):
for action in self.output_device_menu.actions():
......
......@@ -744,14 +744,28 @@ class StateButton(QToolButton):
class PresenceState(object):
def __init__(self, name, color, icon):
def __init__(self, name, color, icon, internal=False):
self.name = name
self.color = color
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):
return self.name
@property
def Internal(self):
return PresenceState(self.name, self.color, self.icon, True)
class AccountState(StateButton):
Invisible = PresenceState('Invisible', '#efedeb', Resources.get('icons/state-invisible.svg'))
......@@ -759,7 +773,7 @@ class AccountState(StateButton):
Away = PresenceState('Away', '#ffff00', Resources.get('icons/state-away.svg'))
Busy = PresenceState('Busy', '#ff0000', Resources.get('icons/state-busy.svg'))
stateChanged = pyqtSignal(QAction)
stateChanged = pyqtSignal()
history_size = 7
......@@ -774,37 +788,56 @@ class AccountState(StateButton):
menu.triggered.connect(self._SH_MenuTriggered)
self.setMenu(menu)
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):
if hasattr(action, 'state'):
self.setState(action.state, action.note)
self.stateChanged.emit(action)
def setState(self, state, note=None):
if state == self.state and note == self.note:
return
self.state = state
self.note = note
palette = self.palette()
palette.setColor(QPalette.Button, QColor(state.color))
self.setPalette(palette)
if not note:
return
menu = self.menu()
actions = menu.actions()[5:]
try:
action = next(action for action in actions if action.state is state and action.note == note)
except StopIteration:
action = QAction(QIcon(state.icon), note, menu)
if len(actions) == 0:
menu.addAction(action)
if note and not state.internal:
menu = self.menu()
actions = menu.actions()[5:]
try:
action = next(action for action in actions if action.state is state and action.note == note)
except StopIteration:
action = QAction(QIcon(state.icon), note, menu)
if len(actions) == 0:
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:
if len(actions) >= self.history_size:
menu.removeAction(actions[-1])
menu.insertAction(actions[0], action)
action.state = state
action.note = note
else:
if action is not actions[0]:
menu.removeAction(action)
menu.insertAction(actions[0], action)
if action is not actions[0]:
menu.removeAction(action)
menu.insertAction(actions[0], action)
self.stateChanged.emit()
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