labels.py 11.3 KB
Newer Older
1 2

import os
3
from datetime import timedelta
4

Dan Pascu's avatar
Dan Pascu committed
5 6 7
from PyQt5.QtCore import Qt, QEvent
from PyQt5.QtGui import QBrush, QColor, QFontMetrics, QIcon, QLinearGradient, QPainter, QPalette, QPen
from PyQt5.QtWidgets import QAction, QFileDialog, QLabel, QMenu
8

9 10
from application.python.types import MarkerType

11 12
from sipsimple.configuration.datatypes import Path

13 14
from blink.resources import IconManager
from blink.widgets.color import ColorHelperMixin
15
from blink.widgets.util import QtDynamicProperty, ContextMenuActions
16 17


18 19 20
__all__ = ['DurationLabel', 'IconSelector', 'LatencyLabel', 'PacketLossLabel', 'Status', 'StatusLabel', 'StreamInfoLabel', 'ElidedLabel', 'ContactState']


21
class IconSelector(QLabel):
22 23 24 25
    default_icon = QtDynamicProperty('default_icon', QIcon)
    icon_size = QtDynamicProperty('icon_size', int)

    class NotSelected: __metaclass__ = MarkerType
26 27

    def __init__(self, parent=None):
Dan Pascu's avatar
Dan Pascu committed
28
        super(IconSelector, self).__init__(parent)
29 30 31
        self.actions = ContextMenuActions()
        self.actions.select_icon = QAction(u'Select icon...', self, triggered=self._SH_ChangeIconActionTriggered)
        self.actions.remove_icon = QAction(u'Use contact provided icon', self, triggered=self._SH_RemoveIconActionTriggered)
32
        self.icon_size = 48
33
        self.default_icon = None
34 35 36
        self.contact_icon = None
        self.icon = None
        self.filename = self.NotSelected
37
        self.last_icon_directory = Path('~').normalized
38

39 40 41 42 43 44 45 46 47 48 49
    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

50 51 52 53 54
    def _get_filename(self):
        return self.__dict__['filename']

    def _set_filename(self, filename):
        self.__dict__['filename'] = filename
55 56 57 58
        if filename is self.NotSelected:
            return
        elif filename is None:
            self.icon = self.contact_icon
59
        else:
60 61
            self.icon = QIcon(filename)
            self.last_icon_directory = os.path.dirname(filename)
62 63 64 65

    filename = property(_get_filename, _set_filename)
    del _get_filename, _set_filename

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
    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)

100 101
    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton and self.rect().contains(event.pos()):
102
            self.actions.remove_icon.setEnabled(self.icon is not self.contact_icon)
103
            menu = QMenu(self)
104 105
            menu.addAction(self.actions.select_icon)
            menu.addAction(self.actions.remove_icon)
106
            menu.exec_(self.mapToGlobal(self.rect().translated(0, 2).bottomLeft()))
107 108
        super(IconSelector, self).mouseReleaseEvent(event)

109
    def _SH_ChangeIconActionTriggered(self):
Dan Pascu's avatar
Dan Pascu committed
110
        filename = QFileDialog.getOpenFileName(self, u'Select Icon', self.last_icon_directory, u"Images (*.png *.tiff *.jpg *.xmp *.svg)")[0]
111 112 113
        if filename:
            self.filename = filename

114
    def _SH_RemoveIconActionTriggered(self):
115 116
        self.filename = None

117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157

class StreamInfoLabel(QLabel):
    def __init__(self, parent=None):
        super(StreamInfoLabel, self).__init__(parent)
        self.session_type = None
        self.codec_info = ''

    def _get_session_type(self):
        return self.__dict__['session_type']

    def _set_session_type(self, value):
        self.__dict__['session_type'] = value
        self.update_content()

    session_type = property(_get_session_type, _set_session_type)
    del _get_session_type, _set_session_type

    def _get_codec_info(self):
        return self.__dict__['codec_info']

    def _set_codec_info(self, value):
        self.__dict__['codec_info'] = value
        self.update_content()

    codec_info = property(_get_codec_info, _set_codec_info)
    del _get_codec_info, _set_codec_info

    def resizeEvent(self, event):
        super(StreamInfoLabel, self).resizeEvent(event)
        self.update_content()

    def update_content(self):
        if self.session_type and self.codec_info:
            text = u'%s (%s)' % (self.session_type, self.codec_info)
            if self.width() < QFontMetrics(self.font()).width(text):
                text = self.session_type
        else:
            text = self.session_type or u''
        self.setText(text)


158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
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


177 178 179
class LatencyLabel(QLabel):
    def __init__(self, parent=None):
        super(LatencyLabel, self).__init__(parent)
180
        self.threshold = 99
181 182 183 184 185 186 187
        self.value = 0

    def _get_value(self):
        return self.__dict__['value']

    def _set_value(self, value):
        self.__dict__['value'] = value
188
        if value > self.threshold:
189 190 191 192 193 194 195 196 197 198 199 200 201 202
            text = u'Latency %sms' % value
            self.setMinimumWidth(QFontMetrics(self.font()).width(text))
            self.setText(text)
            self.show()
        else:
            self.hide()

    value = property(_get_value, _set_value)
    del _get_value, _set_value


class PacketLossLabel(QLabel):
    def __init__(self, parent=None):
        super(PacketLossLabel, self).__init__(parent)
203
        self.threshold = 3
204 205 206 207 208 209 210
        self.value = 0

    def _get_value(self):
        return self.__dict__['value']

    def _set_value(self, value):
        self.__dict__['value'] = value
211
        if value > self.threshold:
212 213 214 215 216 217 218 219 220 221 222
            text = u'Packet loss %s%%' % value
            self.setMinimumWidth(QFontMetrics(self.font()).width(text))
            self.setText(text)
            self.show()
        else:
            self.hide()

    value = property(_get_value, _set_value)
    del _get_value, _set_value


223
class Status(unicode):
224
    def __new__(cls, value, color='black', context=None):
225
        instance = super(Status, cls).__new__(cls, value)
226
        instance.color = color
227
        instance.context = context
228 229
        return instance

230 231
    def __eq__(self, other):
        if isinstance(other, Status):
232
            return super(Status, self).__eq__(other) and self.color == other.color and self.context == other.context
233 234 235 236 237 238 239
        elif isinstance(other, basestring):
            return super(Status, self).__eq__(other)
        return NotImplemented

    def __ne__(self, other):
        return not (self == other)

240

241 242 243 244 245 246 247 248 249 250 251 252 253
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()
254 255 256 257
            palette.setColor(QPalette.Active, QPalette.WindowText, color)
            palette.setColor(QPalette.Active, QPalette.Text, color)
            palette.setColor(QPalette.Inactive, QPalette.WindowText, color)
            palette.setColor(QPalette.Inactive, QPalette.Text, color)
258 259 260
            self.setPalette(palette)
            self.setText(unicode(value))
        else:
261
            self.setText(u'')
262 263 264 265 266

    value = property(_get_value, _set_value)
    del _get_value, _set_value


267 268 269 270 271 272 273 274
class ElidedLabel(QLabel):
    """A label that elides the text using a fading gradient"""

    def paintEvent(self, event):
        painter = QPainter(self)
        font_metrics = QFontMetrics(self.font())
        if font_metrics.width(self.text()) > self.contentsRect().width():
            label_width = self.size().width()
275
            fade_start = 1 - 50.0/label_width if label_width > 50 else 0.0
276
            gradient = QLinearGradient(0, 0, label_width, 0)
277
            gradient.setColorAt(fade_start, self.palette().color(self.foregroundRole()))
278 279
            gradient.setColorAt(1.0, Qt.transparent)
            painter.setPen(QPen(QBrush(gradient), 1.0))
280
        painter.drawText(self.contentsRect(), Qt.TextSingleLine | int(self.alignment()), self.text())
281 282


283 284 285 286 287
class StateColor(QColor):
    @property
    def stroke(self):
        return self.darker(200)

288

289 290 291 292 293 294 295 296 297 298 299
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:
300
            return StateColor(Qt.transparent)  # StateColor('#d0d0d0')
301 302 303


class ContactState(QLabel, ColorHelperMixin):
304
    state = QtDynamicProperty('state', unicode)
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328

    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)