Commit 66bbc3f6 authored by Dan Pascu's avatar Dan Pascu

Implemented multiple URIs per contact

parent b2b50439
...@@ -15,10 +15,10 @@ SharedSetting.set_namespace('ag-projects:blink') ...@@ -15,10 +15,10 @@ SharedSetting.set_namespace('ag-projects:blink')
class ContactExtension(ContactExtension): class ContactExtension(ContactExtension):
#auto_answer = SharedSetting(type=bool, default=False)
default_uri = SharedSetting(type=str, nillable=True, default=None)
preferred_media = SharedSetting(type=str, default='audio')
icon = Setting(type=IconDescriptor, nillable=True, default=None) icon = Setting(type=IconDescriptor, nillable=True, default=None)
alternate_icon = Setting(type=IconDescriptor, nillable=True, default=None)
preferred_media = SharedSetting(type=str, default='audio')
#auto_answer = SharedSetting(type=bool, default=False)
class GroupExtension(GroupExtension): class GroupExtension(GroupExtension):
......
...@@ -3,12 +3,7 @@ ...@@ -3,12 +3,7 @@
"""Definitions of datatypes for use in settings extensions.""" """Definitions of datatypes for use in settings extensions."""
__all__ = ['ApplicationDataPath', 'DefaultPath', __all__ = ['ApplicationDataPath', 'DefaultPath', 'SoundFile', 'CustomSoundFile', 'HTTPURL', 'AuthorizationToken', 'InvalidToken', 'IconDescriptor', 'PresenceState', 'PresenceStateList']
'SoundFile', 'CustomSoundFile',
'HTTPURL',
'AuthorizationToken', 'InvalidToken',
'IconDescriptor',
'PresenceState', 'PresenceStateList']
import os import os
import re import re
...@@ -141,16 +136,28 @@ class AuthorizationToken(str): ...@@ -141,16 +136,28 @@ class AuthorizationToken(str):
InvalidToken = AuthorizationToken() # a valid token is never empty InvalidToken = AuthorizationToken() # a valid token is never empty
class ParsedURL(unicode):
fragment = property(lambda self: self.__parsed__.fragment)
netloc = property(lambda self: self.__parsed__.netloc)
params = property(lambda self: self.__parsed__.params)
path = property(lambda self: self.__parsed__.path)
query = property(lambda self: self.__parsed__.query)
scheme = property(lambda self: self.__parsed__.scheme)
def __init__(self, value):
self.__parsed__ = urlparse(self)
class IconDescriptor(object): class IconDescriptor(object):
def __init__(self, url, etag=None): def __init__(self, url, etag=None):
self.url = url self.url = ParsedURL(url)
self.etag = etag self.etag = etag
def __getstate__(self): def __getstate__(self):
if self.etag is None: if self.etag is None:
return unicode(self.url) return unicode(self.url)
else: else:
return u'%s,%s' % (self.__dict__['url'], self.etag) return u'%s,%s' % (self.url, self.etag)
def __setstate__(self, state): def __setstate__(self, state):
try: try:
...@@ -172,26 +179,9 @@ class IconDescriptor(object): ...@@ -172,26 +179,9 @@ class IconDescriptor(object):
def __repr__(self): def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self.url, self.etag) return '%s(%r, %r)' % (self.__class__.__name__, self.url, self.etag)
def _get_url(self):
url = self.__dict__['url']
file_scheme = 'file://'
if url.startswith(file_scheme):
url = file_scheme + ApplicationData.get(url[len(file_scheme):])
return url
def _set_url(self, url):
file_scheme = 'file://'
if url.startswith(file_scheme):
filename = os.path.normpath(url[len(file_scheme):])
if filename.startswith(ApplicationData.directory+os.path.sep):
filename = filename[len(ApplicationData.directory+os.path.sep):]
url = file_scheme + filename
self.__dict__['url'] = url
url = property(_get_url, _set_url)
del _get_url, _set_url
@property @property
def is_local(self): def is_local(self):
return self.__dict__['url'].startswith('file://') return self.url.scheme in ('', 'file')
class PresenceState(object): class PresenceState(object):
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -9,7 +9,7 @@ import os ...@@ -9,7 +9,7 @@ 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 Qt, QUrl
from PyQt4.QtGui import QAction, QActionGroup, QDesktopServices, QShortcut from PyQt4.QtGui import QAction, QActionGroup, QDesktopServices, QShortcut
from PyQt4.QtGui import QFileDialog, QIcon, QStyle, QStyleOptionComboBox, QStyleOptionFrameV2 from PyQt4.QtGui import QFileDialog, QIcon, QStyle, QStyleOptionComboBox, QStyleOptionFrameV2
...@@ -23,7 +23,7 @@ from sipsimple.configuration.settings import SIPSimpleSettings ...@@ -23,7 +23,7 @@ from sipsimple.configuration.settings import SIPSimpleSettings
from blink.aboutpanel import AboutPanel from blink.aboutpanel import AboutPanel
from blink.accounts import AccountModel, ActiveAccountModel, ServerToolsAccountModel, ServerToolsWindow from blink.accounts import AccountModel, ActiveAccountModel, ServerToolsAccountModel, ServerToolsWindow
from blink.contacts import BonjourNeighbour, Contact, Group, ContactEditorDialog, ContactModel, ContactSearchModel, GoogleContactsDialog from blink.contacts import BonjourNeighbour, Contact, ContactEditorDialog, ContactModel, ContactSearchModel, GoogleContactsDialog
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
...@@ -116,7 +116,6 @@ class MainWindow(base_class, ui_class): ...@@ -116,7 +116,6 @@ class MainWindow(base_class, ui_class):
self.conference_button.makeConference.connect(self._SH_MakeConference) self.conference_button.makeConference.connect(self._SH_MakeConference)
self.conference_button.breakConference.connect(self._SH_BreakConference) self.conference_button.breakConference.connect(self._SH_BreakConference)
self.contact_list.doubleClicked.connect(self._SH_ContactDoubleClicked) # activated is emitted on single click
self.contact_list.selectionModel().selectionChanged.connect(self._SH_ContactListSelectionChanged) self.contact_list.selectionModel().selectionChanged.connect(self._SH_ContactListSelectionChanged)
self.contact_model.itemsAdded.connect(self._SH_ContactModelAddedItems) self.contact_model.itemsAdded.connect(self._SH_ContactModelAddedItems)
self.contact_model.itemsRemoved.connect(self._SH_ContactModelRemovedItems) self.contact_model.itemsRemoved.connect(self._SH_ContactModelRemovedItems)
...@@ -134,7 +133,6 @@ class MainWindow(base_class, ui_class): ...@@ -134,7 +133,6 @@ class MainWindow(base_class, ui_class):
self.search_box.shortcut.activated.connect(self.search_box.setFocus) self.search_box.shortcut.activated.connect(self.search_box.setFocus)
self.search_list.selectionModel().selectionChanged.connect(self._SH_SearchListSelectionChanged) self.search_list.selectionModel().selectionChanged.connect(self._SH_SearchListSelectionChanged)
self.search_list.doubleClicked.connect(self._SH_ContactDoubleClicked) # activated is emitted on single click
self.server_tools_account_model.rowsInserted.connect(self._SH_ServerToolsAccountModelChanged) self.server_tools_account_model.rowsInserted.connect(self._SH_ServerToolsAccountModelChanged)
self.server_tools_account_model.rowsRemoved.connect(self._SH_ServerToolsAccountModelChanged) self.server_tools_account_model.rowsRemoved.connect(self._SH_ServerToolsAccountModelChanged)
...@@ -386,11 +384,11 @@ class MainWindow(base_class, ui_class): ...@@ -386,11 +384,11 @@ class MainWindow(base_class, ui_class):
if filename is not None: if filename is not None:
icon = icon_manager.store_file('myicon', filename) icon = icon_manager.store_file('myicon', filename)
try: try:
hash = hashlib.sha512(open(icon.filename, 'r').read()).hexdigest() hash = hashlib.sha512(open(icon.filename).read()).hexdigest()
except Exception: except Exception:
settings.presence.icon = None settings.presence.icon = None
else: else:
settings.presence.icon = IconDescriptor('file://'+icon.filename, hash) settings.presence.icon = IconDescriptor('file://' + icon.filename, hash)
else: else:
icon_manager.remove('myicon') icon_manager.remove('myicon')
icon = None icon = None
...@@ -405,41 +403,28 @@ class MainWindow(base_class, ui_class): ...@@ -405,41 +403,28 @@ class MainWindow(base_class, ui_class):
self.account_state.setState(self.account_state.state, note) self.account_state.setState(self.account_state.state, note)
def _SH_AddContactButtonClicked(self, clicked): def _SH_AddContactButtonClicked(self, clicked):
model = self.contact_model self.contact_editor_dialog.open_for_add(self.search_box.text(), None)
groups = set()
for index in self.contact_list.selectionModel().selectedIndexes():
item = model.data(index)
if isinstance(item, Group) and not item.virtual:
groups.add(item)
elif isinstance(item, Contact) and not item.group.virtual:
groups.add(item.group)
preferred_group = groups.pop() if len(groups)==1 else None
self.contact_editor_dialog.open_for_add(self.search_box.text(), preferred_group)
def _SH_AudioCallButtonClicked(self): def _SH_AudioCallButtonClicked(self):
list_view = self.contact_list if self.contacts_view.currentWidget() is self.contact_list_panel else self.search_list list_view = self.contact_list if self.contacts_view.currentWidget() is self.contact_list_panel else self.search_list
selected_indexes = list_view.selectionModel().selectedIndexes() if list_view.detail_view.isVisible():
contact = list_view.model().data(selected_indexes[0]) if selected_indexes else Null list_view.detail_view._AH_StartAudioCall()
address = contact.uri or self.search_box.text() else:
name = contact.name or None selected_indexes = list_view.selectionModel().selectedIndexes()
session_manager = SessionManager() contact = selected_indexes[0].data(Qt.UserRole) if selected_indexes else Null
session_manager.start_call(name, address, contact=contact, account=BonjourAccount() if isinstance(contact.settings, BonjourNeighbour) else None) address = contact.uri or self.search_box.text()
name = contact.name or None
session_manager = SessionManager()
session_manager.start_call(name, address, contact=contact, account=BonjourAccount() if isinstance(contact.settings, BonjourNeighbour) else None)
def _SH_BreakConference(self): def _SH_BreakConference(self):
active_session = self.session_model.data(self.session_list.selectionModel().selectedIndexes()[0]) active_session = self.session_list.selectionModel().selectedIndexes()[0].data()
self.session_model.breakConference(active_session.conference) self.session_model.breakConference(active_session.conference)
def _SH_ContactDoubleClicked(self, index):
contact = index.model().data(index)
if not isinstance(contact, Contact):
return
session_manager = SessionManager()
session_manager.start_call(contact.name, contact.uri, contact=contact, account=BonjourAccount() if isinstance(contact.settings, BonjourNeighbour) else None)
def _SH_ContactListSelectionChanged(self, selected, deselected): def _SH_ContactListSelectionChanged(self, selected, deselected):
account_manager = AccountManager() account_manager = AccountManager()
selected_items = self.contact_list.selectionModel().selectedIndexes() selected_items = self.contact_list.selectionModel().selectedIndexes()
self.enable_call_buttons(account_manager.default_account is not None and len(selected_items)==1 and isinstance(self.contact_model.data(selected_items[0]), Contact)) self.enable_call_buttons(account_manager.default_account is not None and len(selected_items)==1 and isinstance(selected_items[0].data(Qt.UserRole), Contact))
def _SH_ContactModelAddedItems(self, items): def _SH_ContactModelAddedItems(self, items):
if not self.search_box.text(): if not self.search_box.text():
...@@ -516,7 +501,9 @@ class MainWindow(base_class, ui_class): ...@@ -516,7 +501,9 @@ class MainWindow(base_class, ui_class):
else: else:
self.contacts_view.setCurrentWidget(self.contact_list_panel) self.contacts_view.setCurrentWidget(self.contact_list_panel)
selected_items = self.contact_list.selectionModel().selectedIndexes() selected_items = self.contact_list.selectionModel().selectedIndexes()
self.enable_call_buttons(account_manager.default_account is not None and len(selected_items)==1 and type(self.contact_model.data(selected_items[0])) is Contact) self.enable_call_buttons(account_manager.default_account is not None and len(selected_items)==1 and type(selected_items[0].data(Qt.UserRole)) is Contact)
self.search_list.detail_model.contact = None
self.search_list.detail_view.hide()
def _SH_SearchListSelectionChanged(self, selected, deselected): def _SH_SearchListSelectionChanged(self, selected, deselected):
account_manager = AccountManager() account_manager = AccountManager()
...@@ -532,7 +519,7 @@ class MainWindow(base_class, ui_class): ...@@ -532,7 +519,7 @@ class MainWindow(base_class, ui_class):
def _SH_SessionListSelectionChanged(self, selected, deselected): def _SH_SessionListSelectionChanged(self, selected, deselected):
selected_indexes = selected.indexes() selected_indexes = selected.indexes()
active_session = self.session_model.data(selected_indexes[0]) if selected_indexes else Null active_session = selected_indexes[0].data() if selected_indexes else Null
if active_session.conference: if active_session.conference:
self.conference_button.setEnabled(True) self.conference_button.setEnabled(True)
self.conference_button.setChecked(True) self.conference_button.setChecked(True)
...@@ -550,7 +537,7 @@ class MainWindow(base_class, ui_class): ...@@ -550,7 +537,7 @@ class MainWindow(base_class, ui_class):
self.active_sessions_label.setVisible(any(active_sessions)) self.active_sessions_label.setVisible(any(active_sessions))
self.hangup_all_button.setEnabled(any(active_sessions)) self.hangup_all_button.setEnabled(any(active_sessions))
selected_indexes = self.session_list.selectionModel().selectedIndexes() selected_indexes = self.session_list.selectionModel().selectedIndexes()
active_session = self.session_model.data(selected_indexes[0]) if selected_indexes else Null active_session = selected_indexes[0].data() if selected_indexes else Null
if active_session.conference: if active_session.conference:
self.conference_button.setEnabled(True) self.conference_button.setEnabled(True)
self.conference_button.setChecked(True) self.conference_button.setChecked(True)
...@@ -730,7 +717,7 @@ class MainWindow(base_class, ui_class): ...@@ -730,7 +717,7 @@ class MainWindow(base_class, ui_class):
self.enable_call_buttons(False) self.enable_call_buttons(False)
else: else:
selected_items = self.contact_list.selectionModel().selectedIndexes() selected_items = self.contact_list.selectionModel().selectedIndexes()
self.enable_call_buttons(len(selected_items)==1 and isinstance(self.contact_model.data(selected_items[0]), Contact)) self.enable_call_buttons(len(selected_items)==1 and isinstance(selected_items[0].data(Qt.UserRole), Contact))
def _NH_SIPAccountGotMessageSummary(self, notification): def _NH_SIPAccountGotMessageSummary(self, notification):
account = notification.sender account = notification.sender
......
...@@ -180,7 +180,7 @@ class PresencePublicationHandler(object): ...@@ -180,7 +180,7 @@ class PresencePublicationHandler(object):
icon = None icon = None
if settings.presence.icon: if settings.presence.icon:
try: try:
data = open(settings.presence.icon.url[7:], 'r').read() # strip 'file://' data = open(settings.presence.icon.url.path).read()
except Exception: except Exception:
pass pass
else: else:
...@@ -194,7 +194,7 @@ class PresencePublicationHandler(object): ...@@ -194,7 +194,7 @@ class PresencePublicationHandler(object):
if None not in (icon_data, icon_hash): if None not in (icon_data, icon_hash):
icon = IconManager().store_data('myicon', icon_data) icon = IconManager().store_data('myicon', icon_data)
if icon: if icon:
settings.presence.icon = IconDescriptor('file://'+icon.filename, icon_hash) settings.presence.icon = IconDescriptor('file://' + icon.filename, icon_hash)
else: else:
settings.presence.icon = None settings.presence.icon = None
settings.save() settings.save()
......
...@@ -961,7 +961,7 @@ class SessionModel(QAbstractListModel): ...@@ -961,7 +961,7 @@ class SessionModel(QAbstractListModel):
structureChanged = pyqtSignal() structureChanged = pyqtSignal()
# The MIME types we accept in drop operations, in the order they should be handled # The MIME types we accept in drop operations, in the order they should be handled
accepted_mime_types = ['application/x-blink-session-list', 'application/x-blink-contact-list'] accepted_mime_types = ['application/x-blink-session-list', 'application/x-blink-contact-list', 'application/x-blink-contact-uri-list']
def __init__(self, parent=None): def __init__(self, parent=None):
super(SessionModel, self).__init__(parent) super(SessionModel, self).__init__(parent)
...@@ -1103,13 +1103,30 @@ class SessionModel(QAbstractListModel): ...@@ -1103,13 +1103,30 @@ class SessionModel(QAbstractListModel):
def _DH_ApplicationXBlinkContactList(self, mime_data, action, index): def _DH_ApplicationXBlinkContactList(self, mime_data, action, index):
if not index.isValid(): if not index.isValid():
return return
try:
contacts = pickle.loads(str(mime_data.data('application/x-blink-contact-list')))
except Exception:
return
session = self.sessions[index.row()] session = self.sessions[index.row()]
contacts = pickle.loads(str(mime_data.data('application/x-blink-contact-list')))
session_manager = SessionManager() session_manager = SessionManager()
for contact in contacts: for contact in contacts:
session_manager.start_call(contact.name, contact.uri, contact=contact, conference_sibling=session) session_manager.start_call(contact.name, contact.uri, contact=contact, conference_sibling=session)
return True return True
def _DH_ApplicationXBlinkContactUriList(self, mime_data, action, index):
if not index.isValid():
return
try:
contact_uris = pickle.loads(str(mime_data.data('application/x-blink-contact-uri-list')))
except Exception:
return
session = self.sessions[index.row()]
session_manager = SessionManager()
for contact_uri in contact_uris:
contact = contact_uri.contact
session_manager.start_call(contact.name, contact_uri.uri.uri, contact=contact, conference_sibling=session)
return True
def _add_session(self, session): def _add_session(self, session):
position = len(self.sessions) position = len(self.sessions)
self.beginInsertRows(QModelIndex(), position, position) self.beginInsertRows(QModelIndex(), position, position)
...@@ -1340,7 +1357,7 @@ class SessionListView(QListView): ...@@ -1340,7 +1357,7 @@ class SessionListView(QListView):
def dragEnterEvent(self, event): def dragEnterEvent(self, event):
event_source = event.source() event_source = event.source()
accepted_mime_types = set(self.model().accepted_mime_types) accepted_mime_types = set(self.model().accepted_mime_types)
provided_mime_types = set(str(x) for x in event.mimeData().formats()) provided_mime_types = set(event.mimeData().formats())
acceptable_mime_types = accepted_mime_types & provided_mime_types acceptable_mime_types = accepted_mime_types & provided_mime_types
if not acceptable_mime_types: if not acceptable_mime_types:
event.ignore() # no acceptable mime types found event.ignore() # no acceptable mime types found
...@@ -1426,6 +1443,20 @@ class SessionListView(QListView): ...@@ -1426,6 +1443,20 @@ class SessionListView(QListView):
else: else:
session.widget.drop_indicator = True session.widget.drop_indicator = True
def _DH_ApplicationXBlinkContactUriList(self, event, index, rect, session):
model = self.model()
if not index.isValid():
rect = self.viewport().rect()
rect.setTop(self.visualRect(model.index(len(model.sessions)-1)).bottom())
event.ignore(rect)
else:
event.accept(rect)
if session.conference is not None:
for sibling in session.conference.sessions:
sibling.widget.drop_indicator = True
else:
session.widget.drop_indicator = True
def _SH_HangupShortcutActivated(self): def _SH_HangupShortcutActivated(self):
session = self.model().data(self.selectedIndexes()[0]) session = self.model().data(self.selectedIndexes()[0])
if session.conference is None: if session.conference is None:
......
...@@ -348,9 +348,7 @@ class SwitchViewButton(QPushButton): ...@@ -348,9 +348,7 @@ class SwitchViewButton(QPushButton):
self.setStyleSheet(style_sheet) self.setStyleSheet(style_sheet)
def dragEnterEvent(self, event): def dragEnterEvent(self, event):
if not self.dnd_active: if self.dnd_active:
event.ignore()
elif event.mimeData().formats() == ['application/x-blink-contact-list']:
event.accept() event.accept()
self._update_dnd() self._update_dnd()
self.dnd_timer.start() self.dnd_timer.start()
...@@ -738,3 +736,4 @@ class AccountState(StateButton): ...@@ -738,3 +736,4 @@ class AccountState(StateButton):
menu.insertAction(actions[0], action) menu.insertAction(actions[0], action)
self.stateChanged.emit() self.stateChanged.emit()
...@@ -6,46 +6,109 @@ __all__ = ['DurationLabel', 'IconSelector', 'LatencyLabel', 'PacketLossLabel', ' ...@@ -6,46 +6,109 @@ __all__ = ['DurationLabel', 'IconSelector', 'LatencyLabel', 'PacketLossLabel', '
import os import os
from datetime import timedelta from datetime import timedelta
from PyQt4.QtCore import Qt from PyQt4.QtCore import Qt, QEvent
from PyQt4.QtGui import QBrush, QColor, QFileDialog, QFontMetrics, QLabel, QLinearGradient, QPalette, QPainter, QPen, QPixmap from PyQt4.QtGui import QAction, QBrush, QColor, QFileDialog, QFontMetrics, QIcon, QLabel, QLinearGradient, QMenu, QPainter, QPalette, QPen
from blink.resources import ApplicationData, Resources from application.python.types import MarkerType
from blink.resources import IconManager
from blink.widgets.color import ColorHelperMixin
from blink.widgets.util import QtDynamicProperty from blink.widgets.util import QtDynamicProperty
class IconSelector(QLabel): class IconSelector(QLabel):
default_icon = QtDynamicProperty('default_icon', unicode) default_icon = QtDynamicProperty('default_icon', QIcon)
icon_size = QtDynamicProperty('icon_size', int)
class NotSelected: __metaclass__ = MarkerType
def __init__(self, parent=None): def __init__(self, parent=None):
super(IconSelector, self).__init__(parent) super(IconSelector, self).__init__(parent)
self.setMinimumSize(36, 36) self.addAction(QAction(u'Select icon...', self, triggered=self._SH_ChangeIconActionTriggered))
self.filename = None self.addAction(QAction(u'Use contact provided icon', self, triggered=self._SH_ClearIconActionTriggered))
self.icon_size = 48
self.default_icon = None self.default_icon = None
self.contact_icon = None
self.icon = None
self.filename = self.NotSelected
self.last_icon_directory = os.path.expanduser('~') self.last_icon_directory = os.path.expanduser('~')
def _get_icon(self):
return self.__dict__['icon']
def _set_icon(self, icon):
self.__dict__['icon'] = icon
icon = icon or self.default_icon or QIcon()
self.setPixmap(icon.pixmap(self.icon_size))
icon = property(_get_icon, _set_icon)
del _get_icon, _set_icon
def _get_filename(self): def _get_filename(self):
return self.__dict__['filename'] return self.__dict__['filename']
def _set_filename(self, filename): def _set_filename(self, filename):
self.__dict__['filename'] = filename self.__dict__['filename'] = filename
filename = ApplicationData.get(filename) if filename else Resources.get(self.default_icon) if filename is self.NotSelected:
pixmap = QPixmap() return
if pixmap.load(filename): elif filename is None:
self.setPixmap(pixmap.scaled(32, 32, Qt.KeepAspectRatio, Qt.SmoothTransformation)) self.icon = self.contact_icon
else: else:
self.setPixmap(pixmap) self.icon = QIcon(filename)
self.last_icon_directory = os.path.dirname(filename)
filename = property(_get_filename, _set_filename) filename = property(_get_filename, _set_filename)
del _get_filename, _set_filename del _get_filename, _set_filename
def init_with_contact(self, contact):
if contact is None:
self.icon = self.contact_icon = None
else:
icon_manager = IconManager()
self.contact_icon = icon_manager.get(contact.id)
self.icon = icon_manager.get(contact.id + '_alt') or self.contact_icon
if contact.alternate_icon is not None:
self.last_icon_directory = os.path.dirname(contact.alternate_icon.url.path)
self.filename = self.NotSelected
def update_from_contact(self, contact):
icon_manager = IconManager()
if self.icon is self.contact_icon:
self.icon = self.contact_icon = icon_manager.get(contact.id)
else:
self.contact_icon = icon_manager.get(contact.id)
def event(self, event):
if event.type() == QEvent.DynamicPropertyChange and event.propertyName() == 'icon_size':
self.setFixedSize(self.icon_size+12, self.icon_size+12)
self.update()
return super(IconSelector, self).event(event)
def enterEvent(self, event):
icon = self.icon or self.default_icon or QIcon()
self.setPixmap(icon.pixmap(self.icon_size, mode=QIcon.Selected))
super(IconSelector, self).enterEvent(event)
def leaveEvent(self, event):
icon = self.icon or self.default_icon or QIcon()
self.setPixmap(icon.pixmap(self.icon_size, mode=QIcon.Normal))
super(IconSelector, self).leaveEvent(event)
def mouseReleaseEvent(self, event): def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton and self.rect().contains(event.pos()): if event.button() == Qt.LeftButton and self.rect().contains(event.pos()):
filename = QFileDialog.getOpenFileName(self, u'Select Icon', self.last_icon_directory, u"Images (*.png *.tiff *.jpg *.xmp *.svg)") menu = QMenu(self)
if filename: menu.addActions(self.actions())
self.last_icon_directory = os.path.dirname(filename) menu.exec_(self.mapToGlobal(self.rect().translated(0, 2).bottomLeft()))
self.filename = filename if os.path.realpath(filename) != os.path.realpath(Resources.get(self.default_icon)) else None
super(IconSelector, self).mouseReleaseEvent(event) super(IconSelector, self).mouseReleaseEvent(event)
def _SH_ChangeIconActionTriggered(self):
filename = QFileDialog.getOpenFileName(self, u'Select Icon', self.last_icon_directory, u"Images (*.png *.tiff *.jpg *.xmp *.svg)")
if filename:
self.filename = filename
def _SH_ClearIconActionTriggered(self):
self.filename = None
class StreamInfoLabel(QLabel): class StreamInfoLabel(QLabel):
def __init__(self, parent=None): def __init__(self, parent=None):
...@@ -198,3 +261,49 @@ class ElidedLabel(QLabel): ...@@ -198,3 +261,49 @@ class ElidedLabel(QLabel):
painter.drawText(self.rect(), Qt.TextSingleLine | int(self.alignment()), self.text()) painter.drawText(self.rect(), Qt.TextSingleLine | int(self.alignment()), self.text())
class StateColor(QColor):
@property
def stroke(self):
return self.darker(200)
class StateColorMapping(dict):
def __missing__(self, key):
if key == 'offline':
return self.setdefault(key, StateColor('#d0d0d0'))
elif key == 'available':
return self.setdefault(key, StateColor('#00ff00'))
elif key == 'away':
return self.setdefault(key, StateColor('#ffff00'))
elif key == 'busy':
return self.setdefault(key, StateColor('#ff0000'))
else:
return StateColor(Qt.transparent) #StateColor('#d0d0d0')
class ContactState(QLabel, ColorHelperMixin):
state = QtDynamicProperty('color', unicode)
def __init__(self, parent=None):
super(ContactState, self).__init__(parent)
self.state_colors = StateColorMapping()
self.state = None
def event(self, event):
if event.type() == QEvent.DynamicPropertyChange and event.propertyName() == 'state':
self.update()
return super(ContactState, self).event(event)
def paintEvent(self, event):
color = self.state_colors[self.state]
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing, True)
painter.setCompositionMode(QPainter.CompositionMode_SourceOver)
gradient = QLinearGradient(0, 0, self.width(), 0)
gradient.setColorAt(0.0, Qt.transparent)
gradient.setColorAt(1.0, color)
painter.setBrush(QBrush(gradient))
gradient.setColorAt(1.0, color.stroke)
painter.setPen(QPen(QBrush(gradient), 1))
painter.drawRoundedRect(-4, 0, self.width()+4, self.height(), 3.7, 3.7)
...@@ -276,6 +276,12 @@ class SearchBox(LineEdit): ...@@ -276,6 +276,12 @@ class SearchBox(LineEdit):
self.textChanged.connect(self._SH_TextChanged) self.textChanged.connect(self._SH_TextChanged)
self.inactiveText = u"Search" self.inactiveText = u"Search"
def keyPressEvent(self, event):
if event.key() == Qt.Key_Escape:
self.clear()
else:
super(SearchBox, self).keyPressEvent(event)
def _SH_TextChanged(self, text): def _SH_TextChanged(self, text):
self.clear_button.setVisible(bool(text)) self.clear_button.setVisible(bool(text))
......
...@@ -342,6 +342,9 @@ ...@@ -342,6 +342,9 @@
</property> </property>
<item> <item>
<widget class="ContactListView" name="contact_list"> <widget class="ContactListView" name="contact_list">
<property name="mouseTracking">
<bool>true</bool>
</property>
<property name="horizontalScrollBarPolicy"> <property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum> <enum>Qt::ScrollBarAlwaysOff</enum>
</property> </property>
...@@ -477,6 +480,9 @@ ...@@ -477,6 +480,9 @@
</property> </property>
<item> <item>
<widget class="ContactSearchListView" name="search_list"> <widget class="ContactSearchListView" name="search_list">
<property name="mouseTracking">
<bool>true</bool>
</property>
<property name="dragEnabled"> <property name="dragEnabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
...@@ -1348,20 +1354,20 @@ padding: 2px;</string> ...@@ -1348,20 +1354,20 @@ padding: 2px;</string>
</customwidgets> </customwidgets>
<tabstops> <tabstops>
<tabstop>search_box</tabstop> <tabstop>search_box</tabstop>
<tabstop>search_list</tabstop>
<tabstop>add_search_contact_button</tabstop>
<tabstop>back_to_contacts_button</tabstop>
<tabstop>contact_list</tabstop>
<tabstop>account_state</tabstop> <tabstop>account_state</tabstop>
<tabstop>display_name</tabstop> <tabstop>display_name</tabstop>
<tabstop>activity_note</tabstop> <tabstop>activity_note</tabstop>
<tabstop>identity</tabstop>
<tabstop>switch_view_button</tabstop> <tabstop>switch_view_button</tabstop>
<tabstop>contact_list</tabstop>
<tabstop>add_contact_button</tabstop> <tabstop>add_contact_button</tabstop>
<tabstop>audio_call_button</tabstop> <tabstop>audio_call_button</tabstop>
<tabstop>im_session_button</tabstop> <tabstop>im_session_button</tabstop>
<tabstop>ds_session_button</tabstop> <tabstop>ds_session_button</tabstop>
<tabstop>silent_button</tabstop> <tabstop>silent_button</tabstop>
<tabstop>identity</tabstop>
<tabstop>add_search_contact_button</tabstop>
<tabstop>back_to_contacts_button</tabstop>
<tabstop>search_list</tabstop>
<tabstop>session_list</tabstop> <tabstop>session_list</tabstop>
<tabstop>hangup_all_button</tabstop> <tabstop>hangup_all_button</tabstop>
<tabstop>conference_button</tabstop> <tabstop>conference_button</tabstop>
......
...@@ -77,9 +77,9 @@ ...@@ -77,9 +77,9 @@
<property name="windowTitle"> <property name="windowTitle">
<string>Contact</string> <string>Contact</string>
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="widget_layout">
<property name="spacing"> <property name="spacing">
<number>5</number> <number>3</number>
</property> </property>
<property name="leftMargin"> <property name="leftMargin">
<number>2</number> <number>2</number>
...@@ -88,7 +88,7 @@ ...@@ -88,7 +88,7 @@
<number>0</number> <number>0</number>
</property> </property>
<property name="rightMargin"> <property name="rightMargin">
<number>2</number> <number>1</number>
</property> </property>
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
...@@ -117,9 +117,6 @@ ...@@ -117,9 +117,6 @@
<property name="spacing"> <property name="spacing">
<number>0</number> <number>0</number>
</property> </property>
<property name="rightMargin">
<number>8</number>
</property>
<item> <item>
<widget class="ElidedLabel" name="name_label"> <widget class="ElidedLabel" name="name_label">
<property name="sizePolicy"> <property name="sizePolicy">
...@@ -148,6 +145,28 @@ ...@@ -148,6 +145,28 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="ContactState" name="state_label">
<property name="minimumSize">
<size>
<width>14</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>14</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="state" stdset="0">
<string notr="true">unknown</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<customwidgets> <customwidgets>
...@@ -156,6 +175,11 @@ ...@@ -156,6 +175,11 @@
<extends>QLabel</extends> <extends>QLabel</extends>
<header>blink.widgets.labels</header> <header>blink.widgets.labels</header>
</customwidget> </customwidget>
<customwidget>
<class>ContactState</class>
<extends>QLabel</extends>
<header>blink.widgets.labels</header>
</customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>
<connections/> <connections/>
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment