Commit 701e260b authored by Dan Pascu's avatar Dan Pascu

Added session information panel into the chat window

parent 4e0aa19e
......@@ -6,8 +6,8 @@ __all__ = ['ChatWindow']
import os
from PyQt4 import uic
from PyQt4.QtCore import Qt, QEvent, QPointF, QPropertyAnimation, QRect, QSettings, QTimer, pyqtSignal
from PyQt4.QtGui import QAction, QDesktopServices, QIcon, QListView, QMenu, QPainter, QPalette, QPen, QPolygonF, QTextCursor, QTextDocument, QTextEdit
from PyQt4.QtCore import Qt, QEasingCurve, QEvent, QPointF, QPropertyAnimation, QRect, QSettings, QTimer, pyqtSignal
from PyQt4.QtGui import QAction, QColor, QDesktopServices, QIcon, QListView, QMenu, QPainter, QPalette, QPen, QPolygonF, QTextCursor, QTextDocument, QTextEdit
from PyQt4.QtWebKit import QWebPage, QWebSettings, QWebView
from abc import ABCMeta, abstractmethod
......@@ -28,6 +28,7 @@ from blink.resources import IconManager, Resources
from blink.sessions import ChatSessionModel, ChatSessionListView, StreamDescription
from blink.util import run_in_gui_thread
from blink.widgets.color import ColorHelperMixin
from blink.widgets.graph import Graph
from blink.widgets.util import ContextMenuActions
......@@ -604,30 +605,41 @@ ui_class, base_class = uic.loadUiType(Resources.get('chat_window.ui'))
class ChatWindow(base_class, ui_class, ColorHelperMixin):
implements(IObserver)
sliding_panels = True
def __init__(self, parent=None):
super(ChatWindow, self).__init__(parent)
with Resources.directory:
self.setupUi()
self.selected_item = None
self.session_model = ChatSessionModel(self)
self.session_list.setModel(self.session_model)
self.session_widget.installEventFilter(self)
self.state_label.installEventFilter(self)
self.mute_button.clicked.connect(self._SH_MuteButtonClicked)
self.hold_button.clicked.connect(self._SH_HoldButtonClicked)
self.record_button.clicked.connect(self._SH_RecordButtonClicked)
self.control_button.clicked.connect(self._SH_ControlButtonClicked)
self.participants_panel_info_button.clicked.connect(self._SH_InfoButtonClicked)
self.participants_panel_files_button.clicked.connect(self._SH_FilesButtonClicked)
self.files_panel_info_button.clicked.connect(self._SH_InfoButtonClicked)
self.files_panel_participants_button.clicked.connect(self._SH_ParticipantsButtonClicked)
self.info_panel_files_button.clicked.connect(self._SH_FilesButtonClicked)
self.info_panel_participants_button.clicked.connect(self._SH_ParticipantsButtonClicked)
self.latency_graph.updated.connect(self._SH_LatencyGraphUpdated)
self.packet_loss_graph.updated.connect(self._SH_PacketLossGraphUpdated)
self.traffic_graph.updated.connect(self._SH_TrafficGraphUpdated)
self.session_model.sessionAdded.connect(self._SH_SessionModelSessionAdded)
self.session_model.sessionRemoved.connect(self._SH_SessionModelSessionRemoved)
self.session_model.sessionAboutToBeRemoved.connect(self._SH_SessionModelSessionAboutToBeRemoved)
self.session_list.selectionModel().selectionChanged.connect(self._SH_SessionListSelectionChanged)
self.dummy_tab = ChatWidget(None, self.tab_widget)
self.dummy_tab.setDisabled(True)
self.tab_widget.addTab(self.dummy_tab, "Dummy")
self.tab_widget.setCurrentWidget(self.dummy_tab)
geometry = QSettings().value("chat_window/geometry")
if geometry:
self.restoreGeometry(geometry)
notification_center = NotificationCenter()
notification_center.add_observer(self, name='SIPApplicationDidStart')
notification_center.add_observer(self, name='BlinkSessionNewIncoming')
......@@ -644,9 +656,22 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
notification_center.add_observer(self, name='MediaStreamDidFail')
notification_center.add_observer(self, name='MediaStreamDidEnd')
#self.splitter.splitterMoved.connect(self._SH_SplitterMoved) # check this and decide on what size to have in the window (see Notes) -Dan
def _SH_SplitterMoved(self, pos, index):
print "-- splitter:", pos, index, self.splitter.sizes()
def setupUi(self):
super(ChatWindow, self).setupUi(self)
self.control_icon = QIcon(Resources.get('icons/cog.svg'))
self.cancel_icon = QIcon(Resources.get('icons/cancel.png'))
self.lock_grey_icon = QIcon(Resources.get('icons/lock-grey-12.svg'))
self.lock_green_icon = QIcon(Resources.get('icons/lock-green-12.svg'))
# re-apply the stylesheet for self.session_info_container_widget to account for all its subwidget role properties that were set after it
self.info_panel_container_widget.setStyleSheet(self.info_panel_container_widget.styleSheet())
# fix the SVG icons as the generated code loads them as pixmaps, losing their ability to scale -Dan
def svg_icon(filename_off, filename_on):
icon = QIcon()
......@@ -657,7 +682,7 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
self.mute_button.setIcon(svg_icon(Resources.get('icons/mic-on.svg'), Resources.get('icons/mic-off.svg')))
self.hold_button.setIcon(svg_icon(Resources.get('icons/pause.svg'), Resources.get('icons/paused.svg')))
self.record_button.setIcon(svg_icon(Resources.get('icons/record.svg'), Resources.get('icons/recording.svg')))
self.control_button.setIcon(QIcon(Resources.get('icons/cog.svg')))
self.control_button.setIcon(self.control_icon)
self.control_menu = QMenu(self.control_button)
self.control_button.setMenu(self.control_menu)
......@@ -671,9 +696,41 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
self.session_list = ChatSessionListView(self)
self.session_list.setObjectName('session_list')
self.slide_direction = self.session_details.RightToLeft # decide if we slide from one direction only -Dan
self.slide_direction = self.session_details.Automatic
self.session_details.animationDuration = 300
self.session_details.animationEasingCurve = QEasingCurve.OutCirc
self.audio_latency_graph = Graph([], color=QColor(0, 100, 215), over_boundary_color=QColor(255, 0, 100))
self.video_latency_graph = Graph([], color=QColor(0, 215, 100), over_boundary_color=QColor(255, 100, 0))
self.audio_packet_loss_graph = Graph([], color=QColor(0, 100, 215), over_boundary_color=QColor(255, 0, 100))
self.video_packet_loss_graph = Graph([], color=QColor(0, 215, 100), over_boundary_color=QColor(255, 100, 0))
self.incoming_traffic_graph = Graph([], color=QColor(255, 50, 50))
self.outgoing_traffic_graph = Graph([], color=QColor(0, 100, 215))
self.latency_graph.add_graph(self.audio_latency_graph)
self.latency_graph.add_graph(self.video_latency_graph)
self.packet_loss_graph.add_graph(self.audio_packet_loss_graph)
self.packet_loss_graph.add_graph(self.video_packet_loss_graph)
# the graph added 2nd will be displayed on top
self.traffic_graph.add_graph(self.incoming_traffic_graph)
self.traffic_graph.add_graph(self.outgoing_traffic_graph)
self.info_panel_files_button.hide()
self.info_panel_participants_button.hide()
self.participants_panel_files_button.hide()
while self.tab_widget.count():
self.tab_widget.removeTab(0) # remove the tab(s) added in designer
self.tab_widget.tabBar().hide()
self.dummy_tab = ChatWidget(None, self.tab_widget)
self.dummy_tab.setDisabled(True)
self.tab_widget.addTab(self.dummy_tab, "Dummy")
self.tab_widget.setCurrentWidget(self.dummy_tab)
self.session_list.hide()
self.new_messages_button.hide()
......@@ -699,6 +756,7 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
notification_center.add_observer(self, sender=new_session.blink_session)
self._update_widgets_for_session()
self._update_control_menu()
self._update_session_info_panel(elements={'session', 'media', 'statistics', 'status'}, update_visibility=True)
selected_session = property(_get_selected_session, _set_selected_session)
del _get_selected_session, _set_selected_session
......@@ -740,12 +798,12 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
state = blink_session.state
if state=='connecting/*' and blink_session.direction=='outgoing' or state=='connected/sent_proposal':
self.control_button.setMenu(None)
self.control_button.setIcon(QIcon(Resources.get('icons/cancel.png')))
self.control_button.setIcon(self.cancel_icon)
elif state == 'connected/received_proposal':
self.control_button.setEnabled(False)
else:
self.control_button.setEnabled(True)
self.control_button.setIcon(QIcon(Resources.get('icons/cog.svg')))
self.control_button.setIcon(self.control_icon)
menu.clear()
if state not in ('connecting/*', 'connected/*'):
menu.addAction(self.control_button.actions.connect)
......@@ -757,6 +815,112 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
#menu.addAction(self.control_button.actions.dump_session) # remove this later -Dan
self.control_button.setMenu(menu)
def _update_session_info_panel(self, elements={}, update_visibility=False):
blink_session = self.selected_session.blink_session
have_session = blink_session.state in ('connecting/*', 'connected/*', 'ending')
have_audio = 'audio' in blink_session.streams
have_chat = 'chat' in blink_session.streams
if update_visibility:
self.status_value_label.setEnabled(have_session)
self.duration_value_label.setEnabled(have_session)
self.account_value_label.setEnabled(have_session)
self.remote_agent_value_label.setEnabled(have_session)
self.sip_addresses_value_label.setEnabled(have_session)
self.audio_value_widget.setEnabled(have_audio)
self.audio_addresses_value_label.setEnabled(have_audio)
self.audio_ice_status_value_label.setEnabled(have_audio)
self.chat_value_widget.setEnabled(have_chat)
self.chat_addresses_value_label.setEnabled(have_chat)
session_info = blink_session.info
audio_info = blink_session.info.streams.audio
video_info = blink_session.info.streams.video
chat_info = blink_session.info.streams.chat
if 'status' in elements and blink_session.state in ('initialized', 'connecting/*', 'connected/*', 'ended'):
state_map = {'initialized': 'Disconnected',
'connecting/dns_lookup': 'Finding destination',
'connecting': 'Connecting',
'connecting/ringing': 'Ringing',
'connecting/starting': 'Starting media',
'connected': 'Connected'}
if blink_session.state == 'ended':
self.status_value_label.setForegroundRole(QPalette.AlternateBase if blink_session.state.error else QPalette.WindowText)
self.status_value_label.setText(blink_session.state.reason)
elif blink_session.state in state_map:
self.status_value_label.setForegroundRole(QPalette.WindowText)
self.status_value_label.setText(state_map[blink_session.state])
want_duration = blink_session.state == 'connected/*' or blink_session.state == 'ended' and not blink_session.state.error
self.status_title_label.setVisible(not want_duration)
self.status_value_label.setVisible(not want_duration)
self.duration_title_label.setVisible(want_duration)
self.duration_value_label.setVisible(want_duration)
if 'session' in elements:
self.account_value_label.setText(blink_session.account.id)
self.remote_agent_value_label.setText(session_info.remote_user_agent or u'N/A')
if session_info.local_address and session_info.remote_address:
self.sip_addresses_value_label.setText(u'%s \u21c4 %s:%s' % (session_info.local_address, session_info.transport, session_info.remote_address))
elif session_info.local_address:
self.sip_addresses_value_label.setText(u'%s \u21c4 N/A' % session_info.local_address)
else:
self.sip_addresses_value_label.setText(u'N/A')
if 'media' in elements:
self.audio_value_label.setText(audio_info.codec or 'N/A')
self.audio_encryption_label.setVisible(audio_info.encryption is not None)
if audio_info.local_address and audio_info.remote_address:
self.audio_addresses_value_label.setText(u'%s \u21c4 %s' % (audio_info.local_address, audio_info.remote_address))
else:
self.audio_addresses_value_label.setText(u'N/A')
if audio_info.ice_status == None:
self.audio_ice_status_value_label.setText(u'N/A')
elif audio_info.ice_status == 'disabled':
self.audio_ice_status_value_label.setText(u'Disabled')
elif audio_info.ice_status == 'gathering':
self.audio_ice_status_value_label.setText(u'Gathering candidates')
elif audio_info.ice_status == 'gathering_complete':
self.audio_ice_status_value_label.setText(u'Gathered candidates')
elif audio_info.ice_status == 'negotiating':
self.audio_ice_status_value_label.setText(u'Negotiating')
elif audio_info.ice_status == 'succeeded':
if 'relay' in {candidate.type.lower() for candidate in (audio_info.local_rtp_candidate, audio_info.remote_rtp_candidate)}:
self.audio_ice_status_value_label.setText(u'Using relay')
else:
self.audio_ice_status_value_label.setText(u'Peer to peer')
elif audio_info.ice_status == 'failed':
self.audio_ice_status_value_label.setText(u"Couldn't negotiate ICE")
if any(len(path) > 1 for path in (chat_info.full_local_path, chat_info.full_remote_path)):
self.chat_value_label.setText(u'Using relay')
elif chat_info.full_local_path and chat_info.full_remote_path:
self.chat_value_label.setText(u'Peer to peer')
else:
self.chat_value_label.setText(u'N/A')
self.chat_encryption_label.setVisible(chat_info.remote_address is not None and chat_info.transport=='tls')
if chat_info.local_address and chat_info.remote_address:
self.chat_addresses_value_label.setText(u'%s \u21c4 %s:%s' % (chat_info.local_address, chat_info.transport, chat_info.remote_address))
else:
self.chat_addresses_value_label.setText(u'N/A')
if 'statistics' in elements:
self.duration_value_label.value = session_info.duration
self.audio_latency_graph.data = audio_info.latency
self.video_latency_graph.data = video_info.latency
self.audio_packet_loss_graph.data = audio_info.packet_loss
self.video_packet_loss_graph.data = video_info.packet_loss
self.incoming_traffic_graph.data = audio_info.incoming_traffic
self.outgoing_traffic_graph.data = audio_info.outgoing_traffic
self.latency_graph.update()
self.packet_loss_graph.update()
self.traffic_graph.update()
def show(self):
super(ChatWindow, self).show()
self.raise_()
......@@ -782,7 +946,7 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
self._EH_CloseSession()
else:
self._EH_ShowSessions()
elif event_type == QEvent.Paint and self.session_widget.hovered:
elif event_type == QEvent.Paint: # and self.session_widget.hovered:
watched.event(event)
self.drawSessionWidgetIndicators()
return True
......@@ -863,24 +1027,10 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
def _NH_BlinkSessionNewIncoming(self, notification):
if 'chat' in notification.sender.streams.types:
# temporary show the session list view until we have a detail view -Dan
if not self.session_list.isVisibleTo(self):
self.session_list.animation.setDirection(QPropertyAnimation.Forward)
self.session_list.animation.setStartValue(self.session_widget.geometry())
self.session_list.animation.setEndValue(self.session_panel.rect())
self.session_list.show()
self.session_list.animation.start()
self.show()
def _NH_BlinkSessionNewOutgoing(self, notification):
if 'chat' in notification.sender.stream_descriptions.types:
# temporary show the session list view until we have a detail view -Dan
if not self.session_list.isVisibleTo(self):
self.session_list.animation.setDirection(QPropertyAnimation.Forward)
self.session_list.animation.setStartValue(self.session_widget.geometry())
self.session_list.animation.setEndValue(self.session_panel.rect())
self.session_list.show()
self.session_list.animation.start()
self.show()
def _NH_BlinkSessionDidReinitializeForIncoming(self, notification):
......@@ -925,10 +1075,15 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
def _NH_BlinkSessionDidRemoveStream(self, notification):
self._update_control_menu()
self._update_session_info_panel(update_visibility=True)
def _NH_BlinkSessionDidChangeState(self, notification):
# even if we use this, we also need to listen for BlinkSessionDidRemoveStream as that transition doesn't change the state at all -Dan
self._update_control_menu()
self._update_session_info_panel(elements={'status'}, update_visibility=True)
def _NH_BlinkSessionInfoUpdated(self, notification):
self._update_session_info_panel(elements=notification.data.elements)
def _NH_ChatSessionItemDidChange(self, notification):
self._update_widgets_for_session()
......@@ -1015,6 +1170,37 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
# signal handlers
#
def _SH_InfoButtonClicked(self, checked):
if self.sliding_panels:
self.session_details.slideInWidget(self.info_panel, direction=self.slide_direction)
else:
self.session_details.setCurrentWidget(self.info_panel)
def _SH_FilesButtonClicked(self, checked):
if self.sliding_panels:
self.session_details.slideInWidget(self.files_panel, direction=self.slide_direction)
else:
self.session_details.setCurrentWidget(self.files_panel)
def _SH_ParticipantsButtonClicked(self, checked):
if self.sliding_panels:
self.session_details.slideInWidget(self.participants_panel, direction=self.slide_direction)
else:
self.session_details.setCurrentWidget(self.participants_panel)
def _SH_LatencyGraphUpdated(self):
self.latency_label.setText(u'Network Latency: %dms, max=%dms' % (max(self.audio_latency_graph.last_value, self.video_latency_graph.last_value), self.latency_graph.max_value))
def _SH_PacketLossGraphUpdated(self):
self.packet_loss_label.setText(u'Packet Loss: %.1f%%, max=%.1f%%' % (max(self.audio_packet_loss_graph.last_value, self.video_packet_loss_graph.last_value), self.packet_loss_graph.max_value))
def _SH_TrafficGraphUpdated(self):
#incoming_traffic = TrafficNormalizer.normalize(self.incoming_traffic_graph.last_value)
#outgoing_traffic = TrafficNormalizer.normalize(self.outgoing_traffic_graph.last_value)
incoming_traffic = TrafficNormalizer.normalize(self.incoming_traffic_graph.last_value*8, bits_per_second=True)
outgoing_traffic = TrafficNormalizer.normalize(self.outgoing_traffic_graph.last_value*8, bits_per_second=True)
self.traffic_label.setText(u"""<p>Traffic: <span style="color: #d70000;">\u2193</span> %s <span style="color: #0064d7;">\u2191</span> %s</p>""" % (incoming_traffic, outgoing_traffic))
def _SH_MuteButtonClicked(self, checked):
settings = SIPSimpleSettings()
settings.audio.muted = checked
......@@ -1121,3 +1307,16 @@ class ChatWindow(base_class, ui_class, ColorHelperMixin):
del ui_class, base_class
class TrafficNormalizer(object):
boundaries = [( 1024, '%d%ss', 1),
( 10*1024, '%.2fk%ss', 1024.0), ( 1024*1024, '%.1fk%ss', 1024.0),
( 10*1024*1024, '%.2fM%ss', 1024*1024.0), ( 1024*1024*1024, '%.1fM%ss', 1024*1024.0),
(10*1024*1024*1024, '%.2fG%ss', 1024*1024*1024.0), (float('infinity'), '%.1fG%ss', 1024*1024*1024.0)]
@classmethod
def normalize(cls, value, bits_per_second=False):
for boundary, format, divisor in cls.boundaries:
if value < boundary:
return format % (value/divisor, 'bp' if bits_per_second else 'B/')
......@@ -9,7 +9,7 @@ import os
import re
import string
from collections import defaultdict
from collections import defaultdict, deque
from datetime import datetime, timedelta
from functools import partial
from itertools import chain, izip, repeat
......@@ -42,6 +42,129 @@ from blink.widgets.color import ColorHelperMixin
from blink.widgets.util import ContextMenuActions
class RTPStreamInfo(object):
dataset_size = 5000
average_interval = 10
def __init__(self):
self.ice_status = None
self.encryption = None
self.codec_name = None
self.sample_rate = None
self.local_address = None
self.remote_address = None
self.local_rtp_candidate = None
self.remote_rtp_candidate = None
self.latency = deque(maxlen=self.dataset_size)
self.packet_loss = deque(maxlen=self.dataset_size)
self.jitter = deque(maxlen=self.dataset_size)
self.incoming_traffic = deque(maxlen=self.dataset_size)
self.outgoing_traffic = deque(maxlen=self.dataset_size)
self.bytes_sent = 0
self.bytes_received = 0
self._total_packets = 0
self._total_packets_lost = 0
self._total_packets_discarded = 0
self._average_loss_queue = deque(maxlen=self.average_interval)
@property
def codec(self):
return '%s %dkHz' % (self.codec_name, self.sample_rate/1000) if self.codec_name else None
def _update(self, stream):
if stream is not None:
self.codec_name = stream.codec
self.sample_rate = stream.sample_rate
self.local_address = stream.local_rtp_address
self.remote_address = stream.remote_rtp_address
self.encryption = 'SRTP' if stream.srtp_active else None
if stream.session and not stream.session.account.nat_traversal.use_ice:
self.ice_status = 'disabled'
def _update_statistics(self, statistics):
if statistics:
packets = statistics['rx']['packets'] - self._total_packets
packets_lost = statistics['rx']['packets_lost'] - self._total_packets_lost
packets_discarded = statistics['rx']['packets_discarded'] - self._total_packets_discarded
self._average_loss_queue.append(100.0 * packets_lost / (packets + packets_lost - packets_discarded) if packets_lost else 0)
self._total_packets += packets
self._total_packets_lost += packets_lost
self._total_packets_discarded += packets_discarded
self.latency.append(statistics['rtt']['last'] / 1000 / 2)
self.jitter.append(statistics['rx']['jitter']['last'] / 1000)
self.incoming_traffic.append(float(statistics['rx']['bytes'] - self.bytes_received)) # bytes/second
self.outgoing_traffic.append(float(statistics['tx']['bytes'] - self.bytes_sent)) # bytes/second
self.bytes_sent = statistics['tx']['bytes']
self.bytes_received = statistics['rx']['bytes']
self.packet_loss.append(sum(self._average_loss_queue) / self.average_interval)
def _reset(self):
self.__init__()
class MSRPStreamInfo(object):
def __init__(self):
self.local_address = None
self.remote_address = None
self.transport = None
self.full_local_path = []
self.full_remote_path = []
def _update(self, stream):
if stream is not None:
if stream.msrp:
self.transport = stream.transport
self.local_address = stream.msrp.local_uri.host
self.remote_address = stream.msrp.next_host().host
self.full_local_path = stream.msrp.full_local_path
self.full_remote_path = stream.msrp.full_remote_path
elif stream.session:
self.transport = stream.transport
self.local_address = stream.local_uri.host
def _reset(self):
self.__init__()
class StreamsInfo(object):
__slots__ = 'audio', 'video', 'chat'
def __init__(self):
self.audio = RTPStreamInfo()
self.video = RTPStreamInfo()
self.chat = MSRPStreamInfo()
def __getitem__(self, key):
try:
return getattr(self, key)
except AttributeError:
raise KeyError(key)
def _update(self, streams):
self.audio._update(streams.get('audio'))
self.video._update(streams.get('video'))
self.chat._update(streams.get('chat'))
class SessionInfo(object):
def __init__(self):
self.duration = timedelta(0)
self.local_address = None
self.remote_address = None
self.transport = None
self.remote_user_agent = None
self.streams = StreamsInfo()
def _update(self, session):
sip_session = session.sip_session
if sip_session is not None:
self.transport = sip_session.transport
self.local_address = session.account.contact[self.transport].host
self.remote_address = sip_session.peer_address # consider reading from sip_session.route if peer_address is None (route can also be None) -Dan
self.remote_user_agent = sip_session.remote_user_agent
self.streams._update(session.streams)
class StreamDescription(object):
def __init__(self, type, **kw):
self.type = type
......@@ -59,65 +182,6 @@ class StreamDescription(object):
return "%s(%r)" % (self.__class__.__name__, self.type)
class StreamInfo(object):
def __init__(self, status=None, ice_status=None, encryption=None, hd=False, codec=''):
self.status = status
self.ice_status = ice_status
self.encryption = encryption
self.hd = hd
self.codec = codec
@classmethod
def from_stream(cls, stream):
instance = cls()
instance.update(stream)
return instance
def update(self, stream):
if stream.type not in ('audio', 'video'):
raise ValueError('only audio and video streams are supported')
if stream.type == 'audio':
self.ice_status = 'Active' if stream.ice_active else 'Inactive'
self.encryption = 'SRTP' if stream.srtp_active else None
self.hd = stream.sample_rate/1000 >= 16
self.codec = '%s %dkHz' % (stream.codec, stream.sample_rate/1000)
elif stream.type == 'video':
self.ice_status = 'Active' if stream.ice_active else 'Inactive'
self.encryption = 'SRTP' if stream.srtp_active else None
self.hd = stream.clock_rate/1024 >= 512
self.codec = '%s %dkbit' % (stream.codec, stream.clock_rate/1024)
def __repr__(self):
return "{0.__class__.__name__}(status={0.status!r}, ice_status={0.ice_status!r}, encryption={0.encryption!r}, hd={0.hd!r}, codec={0.codec!r})".format(self)
class StreamStatistics(object):
def __init__(self, audio=None, video=None):
self.audio = audio
self.video = video
class RTPStatistics(object):
def __init__(self, latency=0, packet_loss=0, jitter=0, sent_bytes=0, received_bytes=0):
self.latency = latency
self.packet_loss = packet_loss
self.jitter = jitter
self.sent_bytes = sent_bytes
self.received_bytes = received_bytes
@classmethod
def load(cls, data):
latency = data['rtt']['last'] / 1000
packet_loss = int(data['rx']['packets_lost']*100.0/data['rx']['packets']) if data['rx']['packets'] else 0
jitter = data['rx']['jitter']['last'] / 1000
sent_bytes = data['tx']['bytes']
received_bytes = data['rx']['bytes']
return cls(latency, packet_loss, jitter, sent_bytes, received_bytes)
def __repr__(self):
return "{0.__class__.__name__}(latency={0.latency!r}, packet_loss={0.packet_loss!r}, jitter={0.jitter!r}, sent_bytes={0.sent_bytes!r}, received_bytes={0.received_bytes!r})".format(self)
class StreamSet(object):
def __init__(self, streams):
self._stream_map = {stream.type: stream for stream in streams}
......@@ -181,8 +245,6 @@ class StreamContainer(object):
old_stream = self._stream_map.get(stream.type, None)
if old_stream is not None:
notification_center.remove_observer(self._session, sender=old_stream)
if stream.type in ('audio', 'video'):
stream.info = StreamInfo()
stream.blink_session = self._session
self._stream_map[stream.type] = stream
notification_center.add_observer(self._session, sender=stream)
......@@ -271,8 +333,6 @@ class BlinkSession(QObject):
implements(IObserver)
# check what should be a signal and what a notification -Dan
streamInfoUpdated = pyqtSignal(object)
infoUpdated = pyqtSignal(object, object) # duration, stream statistics
conferenceChanged = pyqtSignal(object, object) # old_conference, new_conference
streams = StreamListDescriptor()
......@@ -313,7 +373,7 @@ class BlinkSession(QObject):
self.remote_hold = False
self.recording = False
self.duration = timedelta(0)
self.info = SessionInfo()
self._sibling = None
......@@ -437,6 +497,10 @@ class BlinkSession(QObject):
def reusable(self):
return self.persistent and self.state in (None, 'initialized', 'ended')
@property
def duration(self):
return self.info.duration
def init_incoming(self, sip_session, streams, contact, contact_uri, reinitialize=False):
assert self.state in (None, 'initialized', 'ended')
assert self.contact is None or contact.settings is self.contact.settings
......@@ -453,11 +517,13 @@ class BlinkSession(QObject):
self.contact_uri = contact_uri
self.uri = self._parse_uri(contact_uri.uri)
self.streams.extend(streams)
self.info._update(self)
self.state = 'connecting'
if reinitialize:
notification_center.post_notification('BlinkSessionDidReinitializeForIncoming', sender=self)
else:
notification_center.post_notification('BlinkSessionNewIncoming', sender=self)
notification_center.post_notification('BlinkSessionInfoUpdated', sender=self, data=NotificationData(elements={'session', 'media', 'statistics'}))
notification_center.post_notification('BlinkSessionConnectionProgress', sender=self, data=NotificationData(stage='connecting'))
self.sip_session.accept(streams)
......@@ -480,10 +546,12 @@ class BlinkSession(QObject):
self.stream_descriptions = StreamSet(stream_descriptions)
self._sibling = sibling
self.state = 'initialized'
self.info._update(self)
if reinitialize:
notification_center.post_notification('BlinkSessionDidReinitializeForOutgoing', sender=self)
else:
notification_center.post_notification('BlinkSessionNewOutgoing', sender=self)
notification_center.post_notification('BlinkSessionInfoUpdated', sender=self, data=NotificationData(elements={'session', 'media', 'statistics'}))
def connect(self):
assert self.direction == 'outgoing' and self.state == 'initialized'
......@@ -514,11 +582,13 @@ class BlinkSession(QObject):
assert self.state == 'connected'
if stream_description.type in self.streams:
raise RuntimeError('session already has a stream of type %s' % stream_description.type)
self.info.streams[stream_description.type]._reset()
stream = stream_description.create_stream()
self.sip_session.add_stream(stream)
self.streams.add(stream)
notification_center = NotificationCenter()
notification_center.post_notification('BlinkSessionWillAddStream', sender=self, data=NotificationData(stream=stream))
notification_center.post_notification('BlinkSessionInfoUpdated', sender=self, data=NotificationData(elements={'media', 'statistics'}))
def remove_stream(self, stream):
assert self.state == 'connected'
......@@ -536,8 +606,10 @@ class BlinkSession(QObject):
self.sip_session.accept_proposal(streams)
notification_center = NotificationCenter()
for stream in streams:
self.info.streams[stream.type]._reset()
self.streams.add(stream)
notification_center.post_notification('BlinkSessionWillAddStream', sender=self, data=NotificationData(stream=stream))
notification_center.post_notification('BlinkSessionInfoUpdated', sender=self, data=NotificationData(elements={'media', 'statistics'}))
def hold(self):
if self.sip_session is not None and not self.local_hold:
......@@ -635,7 +707,11 @@ class BlinkSession(QObject):
self.remote_hold = False
self.recording = False
self.state = 'ended'
state = BlinkSessionState('ended')
state.reason = reason
state.error = error
self.state = state
notification_center.post_notification('BlinkSessionDidEnd', sender=self, data=NotificationData(reason=reason, error=error))
if not self.persistent:
......@@ -666,18 +742,11 @@ class BlinkSession(QObject):
return uri
def _SH_TimerFired(self):
self.duration += timedelta(seconds=1)
audio_stream = self.streams.get('audio')
if audio_stream is not None and audio_stream.statistics is not None:
audio_stats = RTPStatistics.load(audio_stream.statistics)
else:
audio_stats = None
video_stream = self.streams.get('video')
if video_stream is not None and video_stream.statistics is not None:
video_stats = RTPStatistics.load(video_stream.statistics)
else:
video_stats = None
self.infoUpdated.emit(self.duration, StreamStatistics(audio=audio_stats, video=video_stats))
self.info.duration += timedelta(seconds=1)
self.info.streams.audio._update_statistics(self.streams.get('audio', Null).statistics)
self.info.streams.video._update_statistics(self.streams.get('video', Null).statistics)
notification_center = NotificationCenter()
notification_center.post_notification('BlinkSessionInfoUpdated', sender=self, data=NotificationData(elements={'statistics'}))
@run_in_gui_thread
def handle_notification(self, notification):
......@@ -701,7 +770,9 @@ class BlinkSession(QObject):
def _NH_SIPSessionNewOutgoing(self, notification):
self.state = 'connecting'
self.info._update(self)
notification.center.post_notification('BlinkSessionConnectionProgress', sender=self, data=NotificationData(stage='connecting'))
notification.center.post_notification('BlinkSessionInfoUpdated', sender=self, data=NotificationData(elements={'session'}))
def _NH_SIPSessionGotProvisionalResponse(self, notification):
if notification.data.code == 180:
......@@ -710,10 +781,14 @@ class BlinkSession(QObject):
elif notification.data.code == 183:
self.state = 'connecting/early_media'
notification.center.post_notification('BlinkSessionConnectionProgress', sender=self, data=NotificationData(stage='early_media'))
self.info._update(self)
notification.center.post_notification('BlinkSessionInfoUpdated', sender=self, data=NotificationData(elements={'session', 'media'}))
def _NH_SIPSessionWillStart(self, notification):
self.state = 'connecting/starting'
self.info._update(self)
notification.center.post_notification('BlinkSessionConnectionProgress', sender=self, data=NotificationData(stage='starting'))
notification.center.post_notification('BlinkSessionInfoUpdated', sender=self, data=NotificationData(elements={'session', 'media'}))
def _NH_SIPSessionDidStart(self, notification):
for stream in set(self.streams).difference(notification.data.streams):
......@@ -721,16 +796,9 @@ class BlinkSession(QObject):
if self.state not in ('ending', 'ended', 'deleted'):
self.state = 'connected'
self.timer.start()
audio_stream = self.streams.get('audio')
if audio_stream is not None:
audio_stream.info.update(audio_stream)
self.streamInfoUpdated.emit(audio_stream)
video_stream = self.streams.get('video')
if video_stream is not None:
video_stream.info.update(video_stream)
self.streamInfoUpdated.emit(video_stream)
notification_center = NotificationCenter()
notification_center.post_notification('BlinkSessionDidConnect', sender=self)
self.info._update(self)
notification.center.post_notification('BlinkSessionDidConnect', sender=self)
notification.center.post_notification('BlinkSessionInfoUpdated', sender=self, data=NotificationData(elements={'session', 'media'}))
def _NH_SIPSessionDidFail(self, notification):
if notification.data.failure_reason == 'user request':
......@@ -761,14 +829,6 @@ class BlinkSession(QObject):
accepted_streams = notification.data.accepted_streams
proposed_streams = notification.data.proposed_streams
if self.state not in ('ending', 'ended', 'deleted'):
audio_stream = self.streams.get('audio')
if audio_stream is not None and audio_stream in accepted_streams:
audio_stream.info.update(audio_stream)
self.streamInfoUpdated.emit(audio_stream)
video_stream = self.streams.get('video')
if video_stream is not None and video_stream in accepted_streams:
video_stream.info.update.emit(video_stream)
self.streamInfoUpdated(video_stream)
self.state = 'connected'
for stream in proposed_streams:
if stream in accepted_streams:
......@@ -776,6 +836,9 @@ class BlinkSession(QObject):
else:
self.streams.remove(stream)
notification.center.post_notification('BlinkSessionDidNotAddStream', sender=self, data=NotificationData(stream=stream))
if accepted_streams:
self.info.streams._update(self.streams)
notification.center.post_notification('BlinkSessionInfoUpdated', sender=self, data=NotificationData(elements={'media'}))
def _NH_SIPSessionProposalRejected(self, notification):
for stream in set(notification.data.proposed_streams).intersection(self.streams):
......@@ -803,17 +866,26 @@ class BlinkSession(QObject):
self.unhold()
def _NH_AudioStreamICENegotiationStateDidChange(self, notification):
audio_stream = notification.sender
state = notification.data.state
if state == 'GATHERING':
audio_stream.info.status = 'Gathering ICE candidates'
self.info.streams.audio.ice_status = 'gathering'
notification.center.post_notification('BlinkSessionInfoUpdated', sender=self, data=NotificationData(elements={'media'}))
if state == 'GATHERING_COMPLETE':
self.info.streams.audio.ice_status = 'gathering_complete'
notification.center.post_notification('BlinkSessionInfoUpdated', sender=self, data=NotificationData(elements={'media'}))
elif state == 'NEGOTIATING':
audio_stream.info.status = 'Negotiating ICE'
elif state == 'RUNNING':
audio_stream.info.status = 'ICE negotiation succeeded'
elif state == 'FAILED':
audio_stream.info.status = 'ICE negotiation failed'
self.streamInfoUpdated.emit(audio_stream)
self.info.streams.audio.ice_status = 'negotiating'
notification.center.post_notification('BlinkSessionInfoUpdated', sender=self, data=NotificationData(elements={'media'}))
def _NH_AudioStreamICENegotiationDidSucceed(self, notification):
self.info.streams.audio.ice_status = 'succeeded'
self.info.streams.audio.local_rtp_candidate = notification.sender.local_rtp_candidate
self.info.streams.audio.remote_rtp_candidate = notification.sender.remote_rtp_candidate
notification.center.post_notification('BlinkSessionInfoUpdated', sender=self, data=NotificationData(elements={'media'}))
def _NH_AudioStreamICENegotiationDidFail(self, notification):
self.info.streams.audio.ice_status = 'failed'
notification.center.post_notification('BlinkSessionInfoUpdated', sender=self, data=NotificationData(elements={'media'}))
def _NH_AudioStreamGotDTMF(self, notification):
digit_map = {'*': 'star'}
......@@ -893,9 +965,6 @@ class AudioSessionItem(object):
self.__deleted__ = False
self.blink_session.infoUpdated.connect(self._SH_BlinkSessionInfoUpdated)
self.blink_session.streamInfoUpdated.connect(self._SH_BlinkSessionStreamInfoUpdated)
notification_center = NotificationCenter()
notification_center.add_observer(self, sender=self.blink_session)
......@@ -1033,7 +1102,7 @@ class AudioSessionItem(object):
@property
def duration(self):
return self.blink_session.duration
return self.blink_session.info.duration
def end(self):
# this needs to consider the case where the audio stream is being added. in that case we need to cancel the proposal -Dan
......@@ -1061,8 +1130,6 @@ class AudioSessionItem(object):
self.widget.hold_button.setEnabled(False)
self.widget.record_button.setEnabled(False)
self.widget.hangup_button.setEnabled(False)
self.blink_session.infoUpdated.disconnect(self._SH_BlinkSessionInfoUpdated)
self.blink_session.streamInfoUpdated.disconnect(self._SH_BlinkSessionStreamInfoUpdated)
def _reset_status(self):
if not self.blink_session.on_hold:
......@@ -1087,20 +1154,6 @@ class AudioSessionItem(object):
else:
self.blink_session.stop_recording()
def _SH_BlinkSessionInfoUpdated(self, duration, statistics):
self.widget.duration_label.value = duration
# TODO: compute packet loss and latency statistics -Saul
def _SH_BlinkSessionStreamInfoUpdated(self, stream):
if stream is self.audio_stream:
if stream.info.status:
self.status = Status(stream.info.status)
else:
self.status = None
self.type = 'HD Audio' if stream.info.hd else 'Audio'
self.codec_info = stream.info.codec
self.srtp = stream.info.encryption == 'SRTP'
def handle_notification(self, notification):
handler = getattr(self, '_NH_%s' % notification.name, Null)
handler(notification)
......@@ -1119,6 +1172,16 @@ class AudioSessionItem(object):
else:
self.status = None
def _NH_BlinkSessionInfoUpdated(self, notification):
if 'media' in notification.data.elements:
audio_info = self.blink_session.info.streams.audio
self.type = 'HD Audio' if audio_info.sample_rate >= 16000 else 'Audio'
self.codec_info = audio_info.codec
self.srtp = audio_info.encryption == 'SRTP'
if 'statistics' in notification.data.elements:
self.widget.duration_label.value = self.blink_session.info.duration
# TODO: compute packet loss and latency statistics -Saul
def _NH_BlinkSessionDidChangeHoldState(self, notification):
self.widget.hold_button.setChecked(notification.data.local_hold)
if self.blink_session.state == 'connected':
......@@ -2619,6 +2682,7 @@ class ChatSessionListView(QListView):
self.setMouseTracking(True)
self.setAlternatingRowColors(True)
self.setAutoFillBackground(True)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
#self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) # default
......
# Copyright (c) 2014 AG Projects. See LICENSE for details.
#
__all__ = ['SlidingStackedWidget']
from PyQt4.QtCore import QEasingCurve, QParallelAnimationGroup, QPropertyAnimation, QPoint, pyqtSignal
from PyQt4.QtGui import QStackedWidget
from blink.widgets.util import QtDynamicProperty
class SlidingStackedWidget(QStackedWidget):
animationEasingCurve = QtDynamicProperty('animationEasingCurve', int)
animationDuration = QtDynamicProperty('animationDuration', int)
verticalMode = QtDynamicProperty('verticalMode', bool)
wrap = QtDynamicProperty('wrap', bool)
animationFinished = pyqtSignal()
LeftToRight, RightToLeft, TopToBottom, BottomToTop, Automatic = range(5)
def __init__(self, parent=None):
super(SlidingStackedWidget, self).__init__(parent)
self.animationEasingCurve = QEasingCurve.Linear
self.animationDuration = 250
self.verticalMode = False
self.wrap = False
self._active = False
self._animation_group = QParallelAnimationGroup()
self._animation_group.finished.connect(self._SH_AnimationGroupFinished)
def slideInNext(self):
next_index = self.currentIndex() + 1
if self.wrap or next_index < self.count():
self.slideInIndex(next_index % self.count(), direction=self.BottomToTop if self.verticalMode else self.RightToLeft)
def slideInPrev(self):
previous_index = self.currentIndex() - 1
if self.wrap or previous_index >= 0:
self.slideInIndex(previous_index % self.count(), direction=self.TopToBottom if self.verticalMode else self.LeftToRight)
def slideInIndex(self, index, direction=Automatic):
self.slideInWidget(self.widget(index), direction)
def slideInWidget(self, widget, direction=Automatic):
if self.indexOf(widget) == -1 or widget is self.currentWidget():
return
if self._active:
return
self._active = True
prev_widget = self.currentWidget()
next_widget = widget
if direction == self.Automatic:
if self.indexOf(prev_widget) < self.indexOf(next_widget):
direction = self.BottomToTop if self.verticalMode else self.RightToLeft
else:
direction = self.TopToBottom if self.verticalMode else self.LeftToRight
width = self.frameRect().width()
height = self.frameRect().height()
# the following is important, to ensure that the new widget has correct geometry information when sliding in the first time
next_widget.setGeometry(0, 0, width, height)
if direction in (self.TopToBottom, self.BottomToTop):
offset = QPoint(0, height if direction==self.TopToBottom else -height)
elif direction in (self.LeftToRight, self.RightToLeft):
offset = QPoint(width if direction==self.LeftToRight else -width, 0)
# re-position the next widget outside of the display area
prev_widget_position = prev_widget.pos()
next_widget_position = next_widget.pos()
next_widget.move(next_widget_position - offset)
next_widget.show()
next_widget.raise_()
prev_widget_animation = QPropertyAnimation(prev_widget, "pos")
prev_widget_animation.setDuration(self.animationDuration)
prev_widget_animation.setEasingCurve(QEasingCurve(self.animationEasingCurve))
prev_widget_animation.setStartValue(prev_widget_position)
prev_widget_animation.setEndValue(prev_widget_position + offset)
next_widget_animation = QPropertyAnimation(next_widget, "pos")
next_widget_animation.setDuration(self.animationDuration)
next_widget_animation.setEasingCurve(QEasingCurve(self.animationEasingCurve))
next_widget_animation.setStartValue(next_widget_position - offset)
next_widget_animation.setEndValue(next_widget_position)
self._animation_group.clear()
self._animation_group.addAnimation(prev_widget_animation)
self._animation_group.addAnimation(next_widget_animation)
self._animation_group.start()
def _SH_AnimationGroupFinished(self):
prev_widget_animation = self._animation_group.animationAt(0)
next_widget_animation = self._animation_group.animationAt(1)
prev_widget = prev_widget_animation.targetObject()
next_widget = next_widget_animation.targetObject()
self.setCurrentWidget(next_widget)
prev_widget.hide() # this may have been done already by QStackedWidget when changing the current widget above -Dan
prev_widget.move(prev_widget_animation.startValue()) # move the outshifted widget back to its original position
self._animation_group.clear()
self._active = False
self.animationFinished.emit()
# Copyright (c) 2014 AG Projects. See LICENSE for details.
#
__all__ = ['Graph', 'GraphWidget', 'HeightScaler', 'LogarithmicScaler', 'MaxScaler', 'SoftScaler']
from PyQt4.QtCore import Qt, QLine, QPointF, QMetaObject, pyqtSignal
from PyQt4.QtGui import QColor, QLinearGradient, QPainterPath, QPen, QPolygonF, QStyle, QStyleOption, QStylePainter, QWidget
from abc import ABCMeta, abstractmethod
from application.python import limit
from collections import deque
from itertools import chain, islice
from math import ceil, log10, modf
from blink.widgets.color import ColorHelperMixin
from blink.widgets.util import QtDynamicProperty
class HeightScaler(object):
__metaclass__ = ABCMeta
@abstractmethod
def get_height(self, max_value):
return NotImplemented
class LogarithmicScaler(HeightScaler):
"""A scaler that returns the closest next power of 10"""
def get_height(self, max_value):
return 10 ** int(ceil(log10(max_value or 1)))
class MaxScaler(HeightScaler):
"""A scaler that returns the max_value"""
def get_height(self, max_value):
return max_value
class SoftScaler(HeightScaler):
"""A scaler that returns the closest next value from the series: { ..., 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, ... }"""
def __init__(self):
self.point_20 = log10(2)
self.point_50 = log10(5)
def get_height(self, max_value):
fraction, integer = modf(log10(max_value or 0.9))
if fraction < -self.point_50:
return 10**integer / 5
elif fraction < -self.point_20:
return 10**integer / 2
elif fraction < 0:
return 10**integer
elif fraction < self.point_20:
return 10**integer * 2
elif fraction < self.point_50:
return 10**integer * 5
else:
return 10**integer * 10
class Graph(object):
def __init__(self, data, color, over_boundary_color=None, fill_envelope=False, enabled=True):
self.data = data
self.color = color
self.over_boundary_color = over_boundary_color or color
self.fill_envelope = fill_envelope
self.enabled = enabled
@property
def max_value(self):
return max(self.data) if self.data else 0
@property
def last_value(self):
return self.data[-1] if self.data else 0
class GraphWidget(QWidget, ColorHelperMixin):
graphStyle = QtDynamicProperty('graphStyle', type=int)
graphHeight = QtDynamicProperty('graphHeight', type=float)
minHeight = QtDynamicProperty('minHeight', type=float)
lineThickness = QtDynamicProperty('lineThickness', type=float)
horizontalPixelsPerUnit = QtDynamicProperty('horizontalPixelsPerUnit', type=int)
boundary = QtDynamicProperty('boundary', type=float)
boundaryColor = QtDynamicProperty('boundaryColor', type=QColor)
smoothEnvelope = QtDynamicProperty('smoothEnvelope', type=bool)
smoothFactor = QtDynamicProperty('smoothFactor', type=float)
fillEnvelope = QtDynamicProperty('fillEnvelope', type=bool)
fillTransparency = QtDynamicProperty('fillTransparency', type=int)
EnvelopeStyle, BarStyle = range(2)
AutomaticHeight = 0
updated = pyqtSignal()
def __init__(self, parent=None):
super(GraphWidget, self).__init__(parent)
self.graphStyle = self.EnvelopeStyle
self.graphHeight = self.AutomaticHeight
self.minHeight = 0
self.lineThickness = 1.6
self.horizontalPixelsPerUnit = 2
self.boundary = None
self.boundaryColor = None
self.smoothEnvelope = True
self.smoothFactor = 0.1
self.fillEnvelope = True
self.fillTransparency = 50
self.scaler = SoftScaler()
self.graphs = []
self.__dict__['graph_width'] = 0
self.__dict__['graph_height'] = 0
self.__dict__['max_value'] = 0
def _get_scaler(self):
return self.__dict__['scaler']
def _set_scaler(self, scaler):
if not isinstance(scaler, HeightScaler):
raise TypeError("scaler must be a HeightScaler instance")
self.__dict__['scaler'] = scaler
scaler = property(_get_scaler, _set_scaler)
del _get_scaler, _set_scaler
@property
def graph_width(self):
return self.__dict__['graph_width']
@property
def graph_height(self):
return self.__dict__['graph_height']
@property
def max_value(self):
return self.__dict__['max_value']
def paintEvent(self, event):
option = QStyleOption()
option.initFrom(self)
contents_rect = self.style().subElementRect(QStyle.SE_FrameContents, option, self) or self.contentsRect() # the SE_FrameContents rect is Null unless the stylesheet defines decorations
if self.graphStyle == self.BarStyle:
graph_width = self.__dict__['graph_width'] = int(ceil(float(contents_rect.width()) / self.horizontalPixelsPerUnit))
else:
graph_width = self.__dict__['graph_width'] = int(ceil(float(contents_rect.width() - 1) / self.horizontalPixelsPerUnit) + 1)
max_value = self.__dict__['max_value'] = max(chain([0], *(islice(reversed(graph.data), graph_width) for graph in self.graphs if graph.enabled)))
if self.graphHeight == self.AutomaticHeight or self.graphHeight < 0:
graph_height = self.__dict__['graph_height'] = max(self.scaler.get_height(max_value), self.minHeight)
else:
graph_height = self.__dict__['graph_height'] = max(self.graphHeight, self.minHeight)
if self.graphStyle == self.BarStyle:
height_scaling = float(contents_rect.height()) / graph_height
else:
height_scaling = float(contents_rect.height() - self.lineThickness) / graph_height
painter = QStylePainter(self)
painter.drawPrimitive(QStyle.PE_Widget, option)
painter.setClipRect(contents_rect)
painter.save()
painter.translate(contents_rect.x() + contents_rect.width() - 1, contents_rect.y() + contents_rect.height() - 1)
painter.scale(-1, -1)
painter.setRenderHint(QStylePainter.Antialiasing, self.graphStyle != self.BarStyle)
for graph in (graph for graph in self.graphs if graph.enabled and graph.data):
if self.boundary is not None and 0 < self.boundary < graph_height:
boundary_width = min(5.0/height_scaling, self.boundary-0, graph_height-self.boundary)
pen_color = QLinearGradient(0, (self.boundary - boundary_width) * height_scaling, 0, (self.boundary + boundary_width) * height_scaling)
pen_color.setColorAt(0, graph.color)
pen_color.setColorAt(1, graph.over_boundary_color)
brush_color = QLinearGradient(0, (self.boundary - boundary_width) * height_scaling, 0, (self.boundary + boundary_width) * height_scaling)
brush_color.setColorAt(0, self.color_with_alpha(graph.color, self.fillTransparency))
brush_color.setColorAt(1, self.color_with_alpha(graph.over_boundary_color, self.fillTransparency))
else:
pen_color = graph.color
brush_color = self.color_with_alpha(graph.color, self.fillTransparency)
dataset = islice(reversed(graph.data), graph_width)
if self.graphStyle == self.BarStyle:
lines = [QLine(x*self.horizontalPixelsPerUnit, 0, x*self.horizontalPixelsPerUnit, y*height_scaling) for x, y in enumerate(dataset)]
painter.setPen(QPen(pen_color, self.lineThickness))
painter.drawLines(lines)
else:
painter.translate(0, +self.lineThickness/2 - 1)
if self.smoothEnvelope and self.smoothFactor > 0:
min_value = 0
max_value = graph_height * height_scaling
cx_offset = self.horizontalPixelsPerUnit / 3.0
smoothness = self.smoothFactor
last_values = deque(3*[dataset.next() * height_scaling], maxlen=3) # last 3 values: 0 last, 1 previous, 2 previous previous
envelope = QPainterPath()
envelope.moveTo(0, last_values[0])
for x, y in enumerate(dataset, 1):
x = x * self.horizontalPixelsPerUnit
y = y * height_scaling * (1 - smoothness) + last_values[0] * smoothness
last_values.appendleft(y)
c1x = x - cx_offset * 2
c2x = x - cx_offset
c1y = limit((1 + smoothness) * last_values[1] - smoothness * last_values[2], min_value, max_value) # same gradient as previous previous value to previous value
c2y = limit((1 - smoothness) * last_values[0] + smoothness * last_values[1], min_value, max_value) # same gradient as previous value to last value
envelope.cubicTo(c1x, c1y, c2x, c2y, x, y)
else:
envelope = QPainterPath()
envelope.addPolygon(QPolygonF([QPointF(x*self.horizontalPixelsPerUnit, y*height_scaling) for x, y in enumerate(dataset)]))
if self.fillEnvelope or graph.fill_envelope:
first_element = envelope.elementAt(0)
last_element = envelope.elementAt(envelope.elementCount() - 1)
fill_path = QPainterPath()
fill_path.moveTo(last_element.x, last_element.y)
fill_path.lineTo(last_element.x + 1, last_element.y)
fill_path.lineTo(last_element.x + 1, -self.lineThickness)
fill_path.lineTo(-self.lineThickness, -self.lineThickness)
fill_path.lineTo(-self.lineThickness, first_element.y)
fill_path.connectPath(envelope)
painter.fillPath(fill_path, brush_color)
painter.strokePath(envelope, QPen(pen_color, self.lineThickness, join=Qt.RoundJoin))
painter.translate(0, -self.lineThickness/2 + 1)
if self.boundary is not None and self.boundaryColor:
painter.setRenderHint(QStylePainter.Antialiasing, False)
painter.setPen(QPen(self.boundaryColor, 1.0))
painter.drawLine(0, self.boundary*height_scaling, contents_rect.width(), self.boundary*height_scaling)
painter.restore()
# queue the 'updated' signal to be emited after returning to the main loop
QMetaObject.invokeMethod(self, 'updated', Qt.QueuedConnection)
def add_graph(self, graph):
if not isinstance(graph, Graph):
raise TypeError("graph should be an instance of Graph")
self.graphs.append(graph)
if graph.enabled:
self.update()
def remove_graph(self, graph):
self.graphs.remove(graph)
self.update()
def clear(self):
self.graphs = []
self.update()
......@@ -124,7 +124,7 @@ QToolButton:pressed {
<widget class="QWidget" name="session_widget" native="true">
<property name="minimumSize">
<size>
<width>210</width>
<width>230</width>
<height>0</height>
</size>
</property>
......@@ -509,12 +509,1335 @@ QWidget#session_widget_no:hover {
</widget>
</item>
<item>
<widget class="QStackedWidget" name="info_panels">
<widget class="SlidingStackedWidget" name="session_details">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
<number>2</number>
</property>
<widget class="QWidget" name="chat_panel"/>
<widget class="QWidget" name="conference_panel"/>
<widget class="QWidget" name="participants_panel">
<layout class="QVBoxLayout" name="participants_panel_layout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="participants_panel_title_widget" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>18</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">QWidget#participants_panel_title_widget {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border: 1px outset #303030;
background-color: #505050;
border: 1px outset #202020;
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #404040, stop:1 #606060);
}
QWidget {
color: white;
}
</string>
</property>
<layout class="QHBoxLayout" name="participants_panel_title_layout">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="participants_panel_title_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Participants</string>
</property>
<property name="indent">
<number>3</number>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="participants_panel_files_button">
<property name="minimumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="toolTip">
<string>Conference Files</string>
</property>
<property name="styleSheet">
<string notr="true">QToolButton {
background: transparent;
border: 2px solid #808080;
border-radius: 4px;
margin: 0px;
padding: 1px;
}
QToolButton:hover {
border: 2px solid #c0c0c0;
}
QToolButton:pressed {
border: 2px solid #c0c0c0;
background: qradialgradient(cx: 0.5, cy: 0.5, radius: 1, fx:0.5, fy:0.5, stop:0 #909090, stop:1 transparent);
background-origin: border;
}
</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>icons/downloads16.svg</normaloff>icons/downloads16.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="participants_panel_info_button">
<property name="minimumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="toolTip">
<string>Session Information</string>
</property>
<property name="styleSheet">
<string notr="true">QToolButton {
background: transparent;
border: 2px solid #808080;
border-radius: 4px;
margin: 0px;
padding: 1px;
}
QToolButton:hover {
border: 2px solid #c0c0c0;
}
QToolButton:pressed {
border: 2px solid #c0c0c0;
background: qradialgradient(cx: 0.5, cy: 0.5, radius: 1, fx:0.5, fy:0.5, stop:0 #909090, stop:1 transparent);
background-origin: border;
}
</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>icons/info16.svg</normaloff>icons/info16.svg</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QListView" name="participants_list">
<property name="styleSheet">
<string notr="true">QListView { border: 1px inset palette(dark); border-radius: 3px; }</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="files_panel">
<layout class="QVBoxLayout" name="files_panel_layout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="files_panel_title_widget" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>18</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">QWidget#files_panel_title_widget {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border: 1px outset #303030;
background-color: #505050;
border: 1px outset #202020;
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #404040, stop:1 #606060);
}
QWidget {
color: white;
}
</string>
</property>
<layout class="QHBoxLayout" name="files_panel_title_layout">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="files_panel_title_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Conference Files</string>
</property>
<property name="indent">
<number>3</number>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="files_panel_participants_button">
<property name="minimumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="toolTip">
<string>Conference Participants</string>
</property>
<property name="styleSheet">
<string notr="true">QToolButton {
background: transparent;
border: 2px solid #808080;
border-radius: 4px;
margin: 0px;
padding: 1px;
}
QToolButton:hover {
border: 2px solid #c0c0c0;
}
QToolButton:pressed {
border: 2px solid #c0c0c0;
background: qradialgradient(cx: 0.5, cy: 0.5, radius: 1, fx:0.5, fy:0.5, stop:0 #909090, stop:1 transparent);
background-origin: border;
}
</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>icons/participants16.svg</normaloff>icons/participants16.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="files_panel_info_button">
<property name="minimumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="toolTip">
<string>Session Information</string>
</property>
<property name="styleSheet">
<string notr="true">QToolButton {
background: transparent;
border: 2px solid #808080;
border-radius: 4px;
margin: 0px;
padding: 1px;
}
QToolButton:hover {
border: 2px solid #c0c0c0;
}
QToolButton:pressed {
border: 2px solid #c0c0c0;
background: qradialgradient(cx: 0.5, cy: 0.5, radius: 1, fx:0.5, fy:0.5, stop:0 #909090, stop:1 transparent);
background-origin: border;
}
</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>icons/info16.svg</normaloff>icons/info16.svg</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QListView" name="files_list">
<property name="styleSheet">
<string notr="true">QListView { border: 1px inset palette(dark); border-radius: 3px; }</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="info_panel">
<layout class="QVBoxLayout" name="info_panel_layout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="info_panel_title_widget" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>18</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">QWidget#info_panel_title_widget {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border: 1px outset #303030;
background-color: #505050;
border: 1px outset #202020;
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #404040, stop:1 #606060);
}
QWidget {
color: white;
}
</string>
</property>
<layout class="QHBoxLayout" name="info_panel_title_layout">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="info_panel_title_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Session Information</string>
</property>
<property name="indent">
<number>2</number>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="info_panel_files_button">
<property name="minimumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="toolTip">
<string>Conference Files</string>
</property>
<property name="styleSheet">
<string notr="true">QToolButton {
background: transparent;
border: 2px solid #808080;
border-radius: 4px;
margin: 0px;
padding: 1px;
}
QToolButton:hover {
border: 2px solid #c0c0c0;
}
QToolButton:pressed {
border: 2px solid #c0c0c0;
background: qradialgradient(cx: 0.5, cy: 0.5, radius: 1, fx:0.5, fy:0.5, stop:0 #909090, stop:1 transparent);
background-origin: border;
}
</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>icons/downloads16.svg</normaloff>icons/downloads16.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="info_panel_participants_button">
<property name="minimumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="toolTip">
<string>Conference Participants</string>
</property>
<property name="styleSheet">
<string notr="true">QToolButton {
background: transparent;
border: 2px solid #808080;
border-radius: 4px;
margin: 0px;
padding: 1px;
}
QToolButton:hover {
border: 2px solid #c0c0c0;
}
QToolButton:pressed {
border: 2px solid #c0c0c0;
background: qradialgradient(cx: 0.5, cy: 0.5, radius: 1, fx:0.5, fy:0.5, stop:0 #909090, stop:1 transparent);
background-origin: border;
}
</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>icons/participants16.svg</normaloff>icons/participants16.svg</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="info_panel_container_widget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">#info_panel_container_widget &gt; [role=&quot;title&quot;] {
margin-top: 10px;
font-weight: bold;
}
#info_panel_container_widget &gt; [role=&quot;name&quot;] {
background-color: rbga(0, 0, 0, 10);
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
border: 1px solid rgba(0, 0, 0, 50);
border-right: 0px;
padding-right: 7px;
}
#info_panel_container_widget &gt; [role=&quot;value&quot;] {
background-color: rbga(0, 0, 0, 10);
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
border: 1px solid rgba(0, 0, 0, 50);
border-left: 0px;
padding-right: 2px;
}
#info_panel_container_widget &gt; [role=&quot;alt-title&quot;] {
background-color: transparent;
border-bottom: 1px solid rgba(0, 0, 0, 50);
padding-bottom: 1px;
margin-top: 8px;
font-weight: bold;
}
#info_panel_container_widget &gt; [role=&quot;alt-name&quot;] {
background-color: transparent;
border-bottom: 1px solid rgba(0, 0, 0, 50);
padding-bottom: 1px;
padding-right: 7px;
}
#info_panel_container_widget &gt; [role=&quot;alt-value&quot;] {
background-color: transparent;
border-bottom: 1px solid rgba(0, 0, 0, 50);
padding-bottom: 1px;
padding-right: 2px;
}
</string>
</property>
<layout class="QGridLayout" name="info_panel_container_layout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="horizontalSpacing">
<number>0</number>
</property>
<property name="verticalSpacing">
<number>2</number>
</property>
<item row="12" column="0" colspan="2">
<widget class="QWidget" name="packet_loss_widget" native="true">
<layout class="QVBoxLayout" name="packet_loss_layout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="packet_loss_label">
<property name="styleSheet">
<string notr="true">QLabel {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border: 1px solid #606060;
background-color: rgba(0, 0, 0, 60);
}
</string>
</property>
<property name="text">
<string>Packet Loss: 0.0%, max=0.0%</string>
</property>
<property name="indent">
<number>5</number>
</property>
</widget>
</item>
<item>
<widget class="GraphWidget" name="packet_loss_graph" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>50</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">QWidget#packet_loss_graph {
border: 1px solid #606060;
border-top: 1px solid transparent;
background-color: #f8f8f8;
}</string>
</property>
<property name="minHeight" stdset="0">
<double>10.000000000000000</double>
</property>
<property name="boundary" stdset="0">
<double>3.000000000000000</double>
</property>
<property name="lineThickness" stdset="0">
<double>1.600000000000000</double>
</property>
<property name="smoothEnvelope" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="account_title_label">
<property name="text">
<string>Account</string>
</property>
<property name="role" stdset="0">
<string notr="true">name</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="duration_title_label">
<property name="text">
<string>Duration</string>
</property>
<property name="role" stdset="0">
<string notr="true">name</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="ElidedLabel" name="chat_addresses_value_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>10.0.0.1 ⇄ tls:1.2.3.4</string>
</property>
<property name="indent">
<number>0</number>
</property>
<property name="role" stdset="0">
<string notr="true">value</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="remote_agent_title_label">
<property name="text">
<string>Remote Agent</string>
</property>
<property name="role" stdset="0">
<string notr="true">name</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="ElidedLabel" name="remote_agent_value_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Blink Qt 1.0.0</string>
</property>
<property name="indent">
<number>0</number>
</property>
<property name="role" stdset="0">
<string notr="true">value</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="sip_addresses_title_label">
<property name="text">
<string>Addresses</string>
</property>
<property name="role" stdset="0">
<string notr="true">name</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="ElidedLabel" name="account_value_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>user@domain</string>
</property>
<property name="indent">
<number>0</number>
</property>
<property name="role" stdset="0">
<string notr="true">value</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="DurationLabel" name="duration_value_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>01:27</string>
</property>
<property name="indent">
<number>0</number>
</property>
<property name="role" stdset="0">
<string notr="true">value</string>
</property>
</widget>
</item>
<item row="14" column="0" colspan="2">
<spacer name="info_panel_spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="8" column="1">
<widget class="QWidget" name="audio_value_widget" native="true">
<property name="role" stdset="0">
<string notr="true">value</string>
</property>
<layout class="QHBoxLayout" name="audio_value_layout">
<property name="spacing">
<number>2</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="ElidedLabel" name="audio_value_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>opus 48kHz</string>
</property>
<property name="indent">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="audio_encryption_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>icons/lock-grey-12.svg</pixmap>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="audio_title_label">
<property name="text">
<string>Audio</string>
</property>
<property name="role" stdset="0">
<string notr="true">name</string>
</property>
</widget>
</item>
<item row="11" column="0" colspan="2">
<widget class="QWidget" name="latency_widget" native="true">
<layout class="QVBoxLayout" name="latency_layout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="latency_label">
<property name="styleSheet">
<string notr="true">QLabel {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border: 1px solid #606060;
background-color: rgba(0, 0, 0, 60);
}
</string>
</property>
<property name="text">
<string>Network Latency: 0ms, max=0ms</string>
</property>
<property name="indent">
<number>5</number>
</property>
</widget>
</item>
<item>
<widget class="GraphWidget" name="latency_graph" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>50</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">QWidget#latency_graph {
border: 1px solid #606060;
border-top: 1px solid transparent;
background-color: #f8f8f8;
}</string>
</property>
<property name="boundary" stdset="0">
<double>300.000000000000000</double>
</property>
<property name="minHeight" stdset="0">
<double>10.000000000000000</double>
</property>
<property name="lineThickness" stdset="0">
<double>1.600000000000000</double>
</property>
<property name="smoothEnvelope" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="4" column="1">
<widget class="ElidedLabel" name="sip_addresses_value_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>10.0.0.1 ⇄ tls:1.2.3.4:5061</string>
</property>
<property name="indent">
<number>0</number>
</property>
<property name="role" stdset="0">
<string notr="true">value</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="chat_addresses_title_label">
<property name="text">
<string>Addresses</string>
</property>
<property name="indent">
<number>12</number>
</property>
<property name="role" stdset="0">
<string notr="true">name</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="chat_title_label">
<property name="text">
<string>Chat</string>
</property>
<property name="role" stdset="0">
<string notr="true">name</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="ElidedLabel" name="audio_addresses_value_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>10.0.0.1 ⇄ 1.2.3.4</string>
</property>
<property name="indent">
<number>0</number>
</property>
<property name="role" stdset="0">
<string notr="true">value</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="audio_addresses_title_label">
<property name="text">
<string>Addresses</string>
</property>
<property name="indent">
<number>12</number>
</property>
<property name="role" stdset="0">
<string notr="true">name</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="ElidedLabel" name="audio_ice_status_value_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Peer to peer</string>
</property>
<property name="indent">
<number>0</number>
</property>
<property name="role" stdset="0">
<string notr="true">value</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="audio_ice_status_title_label">
<property name="text">
<string>ICE Status</string>
</property>
<property name="indent">
<number>12</number>
</property>
<property name="role" stdset="0">
<string notr="true">name</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QWidget" name="chat_value_widget" native="true">
<property name="role" stdset="0">
<string notr="true">value</string>
</property>
<layout class="QHBoxLayout" name="chat_value_layout">
<property name="spacing">
<number>2</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="ElidedLabel" name="chat_value_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Using relay</string>
</property>
<property name="indent">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="chat_encryption_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>icons/lock-grey-12.svg</pixmap>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="13" column="0" colspan="2">
<widget class="QWidget" name="traffic_widget" native="true">
<layout class="QVBoxLayout" name="speed_layout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="traffic_label">
<property name="styleSheet">
<string notr="true">QLabel {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border: 1px solid #606060;
background-color: rgba(0, 0, 0, 60);
}
</string>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Traffic: &lt;span style=&quot; color:#d70000;&quot;&gt;&lt;/span&gt; 0kbps &lt;span style=&quot; color:#0064d7;&quot;&gt;&lt;/span&gt; 0kbps&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="indent">
<number>5</number>
</property>
</widget>
</item>
<item>
<widget class="GraphWidget" name="traffic_graph" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>50</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">QWidget#traffic_graph {
border: 1px solid #606060;
border-top: 1px solid transparent;
background-color: #f8f8f8;
}</string>
</property>
<property name="lineThickness" stdset="0">
<double>1.600000000000000</double>
</property>
<property name="fillTransparency" stdset="0">
<number>25</number>
</property>
<property name="smoothEnvelope" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="ElidedLabel" name="status_value_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="palette">
<palette>
<active>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="10">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="10">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="10">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>144</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="10">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="10">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="10">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>144</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="10">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="10">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="10">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>144</red>
<green>96</green>
<blue>96</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="text">
<string>Connected</string>
</property>
<property name="indent">
<number>0</number>
</property>
<property name="role" stdset="0">
<string notr="true">value</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="status_title_label">
<property name="text">
<string>Status</string>
</property>
<property name="role" stdset="0">
<string notr="true">name</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<spacer name="session_spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
......@@ -711,6 +2034,9 @@ QToolButton:hover {
</layout>
</widget>
<widget class="QTabWidget" name="tab_widget">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="tabPosition">
<enum>QTabWidget::South</enum>
</property>
......@@ -748,6 +2074,9 @@ QToolButton:hover {
<height>64</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="url">
<url>
<string>about:blank</string>
......@@ -824,6 +2153,23 @@ QToolButton:hover {
<extends>QToolButton</extends>
<header>blink.widgets.buttons</header>
</customwidget>
<customwidget>
<class>SlidingStackedWidget</class>
<extends>QStackedWidget</extends>
<header>blink.widgets.containers</header>
<container>1</container>
</customwidget>
<customwidget>
<class>GraphWidget</class>
<extends>QWidget</extends>
<header>blink.widgets.graph</header>
<container>1</container>
</customwidget>
<customwidget>
<class>DurationLabel</class>
<extends>QLabel</extends>
<header>blink.widgets.labels</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>chat_input</tabstop>
......@@ -832,8 +2178,6 @@ QToolButton:hover {
<tabstop>hold_button</tabstop>
<tabstop>record_button</tabstop>
<tabstop>new_messages_button</tabstop>
<tabstop>tab_widget</tabstop>
<tabstop>chat_view</tabstop>
</tabstops>
<resources/>
<connections/>
......
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
id="svg7357"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="downloads16-arrow.svg"
enable-background="new">
<defs
id="defs7359">
<filter
inkscape:collect="always"
id="filter3767">
<feBlend
inkscape:collect="always"
mode="multiply"
in2="BackgroundImage"
id="feBlend3769" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="44.4375"
inkscape:cx="4.3656821"
inkscape:cy="8"
inkscape:document-units="px"
inkscape:current-layer="g4409"
showgrid="true"
inkscape:window-width="1301"
inkscape:window-height="921"
inkscape:window-x="179"
inkscape:window-y="0"
inkscape:window-maximized="0"
showguides="true"
inkscape:guide-bbox="true">
<inkscape:grid
type="xygrid"
id="grid7365"
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingy="0.5px"
spacingx="0.5px" />
<sodipodi:guide
orientation="0,1"
position="2,8"
id="guide3773" />
</sodipodi:namedview>
<metadata
id="metadata7362">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Downloads arrow"
inkscape:groupmode="layer"
id="g4409"
transform="translate(0,-1036.3622)"
style="display:inline;filter:url(#filter3767)">
<rect
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6"
id="rect4417"
width="2.9999068"
height="10"
x="6.5000467"
y="1036.8622"
ry="0"
rx="0" />
<path
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.99999988;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:1.6"
d="M 14,1042.8622 8.0000001,1051.8621 2,1042.8622 z"
id="path4415"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
id="svg7357"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="info16-letter.svg"
enable-background="new">
<defs
id="defs7359">
<filter
inkscape:collect="always"
id="filter3767">
<feBlend
inkscape:collect="always"
mode="multiply"
in2="BackgroundImage"
id="feBlend3769" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="44.4375"
inkscape:cx="6.9669334"
inkscape:cy="8"
inkscape:document-units="px"
inkscape:current-layer="g3890"
showgrid="true"
inkscape:window-width="1301"
inkscape:window-height="921"
inkscape:window-x="206"
inkscape:window-y="104"
inkscape:window-maximized="0"
showguides="true"
inkscape:guide-bbox="true">
<inkscape:grid
type="xygrid"
id="grid7365"
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingy="0.5px"
spacingx="0.5px" />
</sodipodi:namedview>
<metadata
id="metadata7362">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
style="display:inline;filter:url(#filter3767)"
transform="translate(0,-1036.3622)"
id="g3890"
inkscape:groupmode="layer"
inkscape:label="Info">
<text
transform="scale(1.0445226,0.95737516)"
sodipodi:linespacing="125%"
id="text3894"
y="1099.1454"
x="3.746093"
style="font-size:23.57173729000000151px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;opacity:0.97000001999999996;fill:#ffffff;fill-opacity:1;stroke:none;font-family:URW Palladio L;-inkscape-font-specification:URW Palladio L Bold"
xml:space="preserve"><tspan
y="1099.1454"
x="3.746093"
id="tspan3896"
sodipodi:role="line">i</tspan></text>
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="12"
height="12"
id="svg2"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="lock-blue-12.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="74.666667"
inkscape:cx="4.3794643"
inkscape:cy="6"
inkscape:document-units="px"
inkscape:current-layer="g7515"
showgrid="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1578"
inkscape:window-height="1106"
inkscape:window-x="131"
inkscape:window-y="42"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid2985"
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="0.5px"
spacingy="0.5px" />
<sodipodi:guide
orientation="0,1"
position="-1,6"
id="guide2987" />
<sodipodi:guide
orientation="1,0"
position="6,6.5"
id="guide2989" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
style="display:inline"
transform="translate(0,-1040.3622)"
id="g7515"
inkscape:groupmode="layer"
inkscape:label="Lock blue">
<path
style="fill:none;stroke:#0055aa;stroke-width:1.20000017;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 8.0500001,1046.7622 0,-3.191 c 0,-0.5636 -0.5323592,-1.1286 -1.0512822,-1.3866 -0.596438,-0.2965 -1.4009978,-0.2965 -1.9974358,0 -0.518923,0.258 -1.0512821,0.823 -1.0512821,1.3866 l 0,3.191"
id="path7517"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csaasc" />
<rect
y="1046.8622"
x="2.5"
height="4"
width="7"
id="rect7519"
style="fill:#0076ee;fill-opacity:1;fill-rule:nonzero;stroke:#0055aa;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="12"
height="12"
id="svg2"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="lock-green-12.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="74.666667"
inkscape:cx="4.3794643"
inkscape:cy="6"
inkscape:document-units="px"
inkscape:current-layer="g5403"
showgrid="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1578"
inkscape:window-height="1106"
inkscape:window-x="131"
inkscape:window-y="42"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid2985"
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="0.5px"
spacingy="0.5px" />
<sodipodi:guide
orientation="0,1"
position="-1,6"
id="guide2987" />
<sodipodi:guide
orientation="1,0"
position="6,6.5"
id="guide2989" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
style="display:inline"
transform="translate(0,-1040.3622)"
id="g5403"
inkscape:groupmode="layer"
inkscape:label="Lock green">
<path
style="fill:none;stroke:#008000;stroke-width:1.20000017;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 8.0500001,1046.7622 0,-3.191 c 0,-0.5636 -0.5323592,-1.1286 -1.0512822,-1.3866 -0.596438,-0.2965 -1.4009978,-0.2965 -1.9974358,0 -0.518923,0.258 -1.0512821,0.823 -1.0512821,1.3866 l 0,3.191"
id="path5405"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csaasc" />
<rect
y="1046.8622"
x="2.5"
height="4"
width="7"
id="rect5407"
style="fill:#00c000;fill-opacity:1;fill-rule:nonzero;stroke:#008000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.60000000000000009" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="12"
height="12"
id="svg2"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="lock-grey-12.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="74.666667"
inkscape:cx="4.3794643"
inkscape:cy="6"
inkscape:document-units="px"
inkscape:current-layer="g3874"
showgrid="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1578"
inkscape:window-height="1106"
inkscape:window-x="131"
inkscape:window-y="42"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid2985"
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="0.5px"
spacingy="0.5px" />
<sodipodi:guide
orientation="0,1"
position="-1,6"
id="guide2987" />
<sodipodi:guide
orientation="1,0"
position="6,6.5"
id="guide2989" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Lock grey"
inkscape:groupmode="layer"
id="g3874"
transform="translate(0,-1040.3622)"
style="display:inline">
<path
sodipodi:nodetypes="csaasc"
inkscape:connector-curvature="0"
id="path3876"
d="m 8.0500001,1046.7622 0,-3.191 c 0,-0.5636 -0.5323592,-1.1286 -1.0512822,-1.3866 -0.596438,-0.2965 -1.4009978,-0.2965 -1.9974358,0 -0.518923,0.258 -1.0512821,0.823 -1.0512821,1.3866 l 0,3.191"
style="fill:none;stroke:#333333;stroke-width:1.20000017;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
<rect
style="fill:#545454;fill-opacity:1;fill-rule:nonzero;stroke:#333333;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6"
id="rect3878"
width="7"
height="4"
x="2.5"
y="1046.8622" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="12"
height="12"
id="svg2"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="lock-orange-12.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="74.666667"
inkscape:cx="4.3794643"
inkscape:cy="6"
inkscape:document-units="px"
inkscape:current-layer="g6866"
showgrid="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1578"
inkscape:window-height="1106"
inkscape:window-x="131"
inkscape:window-y="42"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid2985"
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="0.5px"
spacingy="0.5px" />
<sodipodi:guide
orientation="0,1"
position="-1,6"
id="guide2987" />
<sodipodi:guide
orientation="1,0"
position="6,6.5"
id="guide2989" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Lock orange"
inkscape:groupmode="layer"
id="g6866"
transform="translate(0,-1040.3622)"
style="display:inline">
<path
sodipodi:nodetypes="csaasc"
inkscape:connector-curvature="0"
id="path6868"
d="m 8.0500001,1046.7622 0,-3.191 c 0,-0.5636 -0.5323592,-1.1286 -1.0512822,-1.3866 -0.596438,-0.2965 -1.4009978,-0.2965 -1.9974358,0 -0.518923,0.258 -1.0512821,0.823 -1.0512821,1.3866 l 0,3.191"
style="fill:none;stroke:#aa5500;stroke-width:1.20000017;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
<rect
style="fill:#ee7600;fill-opacity:1;fill-rule:nonzero;stroke:#aa5500;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.60000000000000009"
id="rect6870"
width="7"
height="4"
x="2.5"
y="1046.8622" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="12"
height="12"
id="svg2"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="lock-red-12.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="74.666667"
inkscape:cx="4.3794643"
inkscape:cy="6"
inkscape:document-units="px"
inkscape:current-layer="g7033"
showgrid="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1578"
inkscape:window-height="1106"
inkscape:window-x="131"
inkscape:window-y="42"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid2985"
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="0.5px"
spacingy="0.5px" />
<sodipodi:guide
orientation="0,1"
position="-1,6"
id="guide2987" />
<sodipodi:guide
orientation="1,0"
position="6,6.5"
id="guide2989" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
style="display:inline"
transform="translate(0,-1040.3622)"
id="g7033"
inkscape:groupmode="layer"
inkscape:label="Lock red">
<path
style="fill:none;stroke:#800000;stroke-width:1.20000017;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 8.0500001,1046.7622 0,-3.191 c 0,-0.5636 -0.5323592,-1.1286 -1.0512822,-1.3866 -0.596438,-0.2965 -1.4009978,-0.2965 -1.9974358,0 -0.518923,0.258 -1.0512821,0.823 -1.0512821,1.3866 l 0,3.191"
id="path7035"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csaasc" />
<rect
y="1046.8622"
x="2.5"
height="4"
width="7"
id="rect7037"
style="fill:#c00000;fill-opacity:1;fill-rule:nonzero;stroke:#800000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
height="16"
id="Layer_1"
version="1.1"
viewBox="0 0 16 16"
width="16"
x="0px"
xml:space="preserve"
y="0px"
inkscape:version="0.48.4 r9939"
sodipodi:docname="people.svg"
inkscape:label="Layer"><metadata
id="metadata19"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs17" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1575"
inkscape:window-height="1099"
id="namedview15"
showgrid="true"
inkscape:zoom="54.6875"
inkscape:cx="5.7691429"
inkscape:cy="8"
inkscape:window-x="133"
inkscape:window-y="24"
inkscape:window-maximized="0"
inkscape:current-layer="layer1"><inkscape:grid
type="xygrid"
id="grid3018"
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="0.5px"
spacingy="0.5px" /></sodipodi:namedview><g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="Person"
style="display:inline"><path
d="m 13.364029,12.379035 c -0.644604,-0.216591 -1.697842,-0.283234 -2.158273,-0.455398 -0.322303,-0.122179 -0.834533,-0.249912 -0.995685,-0.44429 -0.166905,-0.188823 -0.166905,-1.5605687 -0.166905,-1.5605687 0,0 0.402877,-0.3609857 0.569783,-0.7664005 0.166907,-0.4054147 0.27626,-1.5050329 0.27626,-1.5050329 0,0 0.03453,0.011107 0.08633,0.011107 0.120864,0 0.339569,-0.07775 0.443166,-0.5886844 0.126618,-0.6275598 0.368345,-0.9552238 0.305036,-1.4161749 -0.04605,-0.3110031 -0.184173,-0.3609858 -0.264748,-0.3609858 -0.04029,0 -0.06907,0.011107 -0.06907,0.011107 0,0 0.328058,-0.4665047 0.328058,-2.054842 C 11.717988,1.6161056 10.411512,0.00555364 8.0000018,4.0000001e-8 5.5827357,0.00554704 4.2820167,1.6161056 4.2820167,3.2488719 c 0,1.5827837 0.3280576,2.054842 0.3280576,2.054842 0,0 -0.028776,-0.011107 -0.069064,-0.011107 -0.086331,0 -0.2244605,0.049983 -0.2647483,0.3609858 -0.06331,0.4609511 0.1726619,0.7941687 0.3050361,1.4161749 0.1035968,0.5109337 0.3223022,0.5886844 0.4431654,0.5886844 0.051798,0 0.086331,-0.011107 0.086331,-0.011107 0,0 0.1093525,1.1051718 0.276259,1.5050329 0.1669064,0.4054148 0.5697836,0.7664005 0.5697836,0.7664005 0,0 0,1.3717456 -0.1669065,1.5605686 C 5.6230213,11.668171 5.1165472,11.801458 4.794245,11.923637 4.3338133,12.095801 3.2805757,12.162444 2.6359714,12.379035 1.991367,12.595627 2.5e-8,13.500867 2.5e-8,16 H 16 c 0,-2.499133 -1.985612,-3.404373 -2.635971,-3.620965 z"
id="path9-4"
inkscape:connector-curvature="0"
style="fill:#f0f0f0;fill-opacity:1" /></g></svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
id="svg4157"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="arrows.svg">
<defs
id="defs4159" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="52.9375"
inkscape:cx="4.9492326"
inkscape:cy="8"
inkscape:document-units="px"
inkscape:current-layer="g4922"
showgrid="true"
inkscape:window-width="1687"
inkscape:window-height="1057"
inkscape:window-x="15"
inkscape:window-y="1"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid4165"
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="0.5px"
spacingy="0.5px" />
</sodipodi:namedview>
<metadata
id="metadata4162">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
style="display:inline"
transform="translate(0,-1036.3622)"
id="g4922"
inkscape:groupmode="layer"
inkscape:label="Up arrow light">
<path
inkscape:connector-curvature="0"
id="path4924"
d="m 2.7,1047.6622 5.2999994,-5.6 5.3000006,5.6"
style="fill:none;stroke:#727272;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path4926"
d="m 2.7000001,1046.6622 5.2999992,-5.6 5.3000007,5.6"
style="fill:none;stroke:#c6c6c6;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
<g
inkscape:label="Down arrow light "
inkscape:groupmode="layer"
id="g5036"
transform="translate(0,-1036.3622)"
style="display:none">
<path
style="fill:none;stroke:#727272;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 2.7,1043.0622 5.2999994,5.6 5.3000006,-5.6"
id="path5038"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#c6c6c6;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 2.7000001,1042.0622 5.2999992,5.6 5.3000007,-5.6"
id="path5040"
inkscape:connector-curvature="0" />
</g>
<g
inkscape:label="Left arrow light"
inkscape:groupmode="layer"
id="g5042"
transform="translate(0,-1036.3622)"
style="display:none">
<path
style="fill:none;stroke:#727272;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 9.8,1050.1622 -5.6,-5.3 5.6,-5.3"
id="path5044"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#c6c6c6;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 9.8,1049.1622 -5.6,-5.3 5.6,-5.3"
id="path5046"
inkscape:connector-curvature="0" />
</g>
<g
style="display:none"
transform="translate(0,-1036.3622)"
id="g5054"
inkscape:groupmode="layer"
inkscape:label="Right arrow light">
<path
inkscape:connector-curvature="0"
id="path5056"
d="m 6.2,1050.1622 5.6,-5.3 -5.6,-5.3"
style="fill:none;stroke:#727272;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path5058"
d="m 6.2,1049.1622 5.6,-5.3 -5.6,-5.3"
style="fill:none;stroke:#c6c6c6;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
<g
inkscape:label="Up arrow light #2"
inkscape:groupmode="layer"
id="g4751"
transform="translate(0,-1036.3622)"
style="display:none">
<path
style="fill:none;stroke:#727272;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 2.6999999,1047.9122 5.299999,-5.6 5.3000001,5.6"
id="path4753"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#c6c6c6;stroke-width:2.4000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 2.7000004,1046.4122 5.2999994,-5.6 5.3000002,5.6"
id="path4755"
inkscape:connector-curvature="0" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
id="svg7357"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="downloads.svg"
enable-background="new">
<defs
id="defs7359">
<filter
inkscape:collect="always"
id="filter3767">
<feBlend
inkscape:collect="always"
mode="multiply"
in2="BackgroundImage"
id="feBlend3769" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="44.4375"
inkscape:cx="8"
inkscape:cy="8"
inkscape:document-units="px"
inkscape:current-layer="g4409"
showgrid="true"
inkscape:window-width="1301"
inkscape:window-height="921"
inkscape:window-x="179"
inkscape:window-y="0"
inkscape:window-maximized="0"
showguides="true"
inkscape:guide-bbox="true">
<inkscape:grid
type="xygrid"
id="grid7365"
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingy="0.5px"
spacingx="0.5px" />
<sodipodi:guide
orientation="0,1"
position="2,8"
id="guide3773" />
</sodipodi:namedview>
<metadata
id="metadata7362">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Downloads"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1036.3622)"
style="display:none;filter:url(#filter3767)">
<path
sodipodi:type="arc"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path7367"
sodipodi:cx="7.5"
sodipodi:cy="8.5"
sodipodi:rx="5.5"
sodipodi:ry="5.5"
d="m 13,8.5 a 5.5,5.5 0 1 1 -11,0 5.5,5.5 0 1 1 11,0 z"
transform="translate(0.5,1035.8622)" />
<g
id="g4041">
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="rect3768"
d="m 11,1044.3622 -3.0000001,4 -2.9999999,-4 z"
style="fill:#202020;fill-opacity:1;fill-rule:nonzero;stroke:#202020;stroke-width:0.99999994;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:1.6" />
<rect
rx="0"
ry="0"
y="1040.8622"
x="7.25"
height="5"
width="1.5000001"
id="rect3771"
style="fill:#202020;fill-opacity:1;fill-rule:nonzero;stroke:#202020;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6" />
</g>
</g>
<g
style="display:none;filter:url(#filter3767)"
transform="translate(0,-1036.3622)"
id="g3795"
inkscape:groupmode="layer"
inkscape:label="Downloads 2">
<path
transform="matrix(1.1818182,0,0,1.1818182,-0.8636365,1034.3167)"
d="m 13,8.5 a 5.5,5.5 0 1 1 -11,0 5.5,5.5 0 1 1 11,0 z"
sodipodi:ry="5.5"
sodipodi:rx="5.5"
sodipodi:cy="8.5"
sodipodi:cx="7.5"
id="path3797"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.84615386;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc" />
<g
id="g3803"
transform="matrix(1.1666857,0,0,1.1999732,-1.3334856,-209.14443)">
<path
style="fill:#202020;fill-opacity:1;fill-rule:nonzero;stroke:#202020;stroke-width:0.84515679;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:1.6"
d="m 11,1044.3622 -3.0000001,4 -2.9999999,-4 z"
id="path3799"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<rect
style="fill:#202020;fill-opacity:1;fill-rule:nonzero;stroke:#202020;stroke-width:0.84515685;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6"
id="rect3801"
width="1.5000001"
height="5"
x="7.25"
y="1040.8622"
ry="0"
rx="0" />
</g>
</g>
<g
inkscape:label="Downloads 3"
inkscape:groupmode="layer"
id="g3826"
transform="translate(0,-1036.3622)"
style="display:none;filter:url(#filter3767)">
<path
sodipodi:type="arc"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.73333335;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path3828"
sodipodi:cx="7.5"
sodipodi:cy="8.5"
sodipodi:rx="5.5"
sodipodi:ry="5.5"
d="m 13,8.5 a 5.5,5.5 0 1 1 -11,0 5.5,5.5 0 1 1 11,0 z"
transform="matrix(1.3636364,0,0,1.3636364,-2.227273,1032.7713)" />
<g
id="g3830"
transform="matrix(1.249973,0,0,1.266636,-1.999784,-278.78121)">
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path3832"
d="m 11,1044.3622 -3.0000001,4 -2.9999999,-4 z"
style="fill:#202020;fill-opacity:1;fill-rule:nonzero;stroke:#202020;stroke-width:0.79473758;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:1.6" />
<rect
rx="0"
ry="0"
y="1040.8622"
x="7.25"
height="5"
width="1.5000001"
id="rect3834"
style="fill:#202020;fill-opacity:1;fill-rule:nonzero;stroke:#202020;stroke-width:0.79473764;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6" />
</g>
</g>
<g
style="display:none;filter:url(#filter3767)"
transform="translate(0,-1036.3622)"
id="g4045"
inkscape:groupmode="layer"
inkscape:label="Downloads 3 copy">
<path
transform="matrix(1.3636364,0,0,1.3636364,-2.227273,1032.7713)"
d="m 13,8.5 a 5.5,5.5 0 1 1 -11,0 5.5,5.5 0 1 1 11,0 z"
sodipodi:ry="5.5"
sodipodi:rx="5.5"
sodipodi:cy="8.5"
sodipodi:cx="7.5"
id="path4047"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.73333335;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc" />
<g
id="g4405">
<path
style="fill:#202020;fill-opacity:1;fill-rule:nonzero;stroke:#202020;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:1.6"
d="m 11.999913,1043.8622 -3.9999131,6 -3.9999128,-6 z"
id="path4051"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<rect
style="fill:#202020;fill-opacity:1;fill-rule:nonzero;stroke:#202020;stroke-width:1.00000012;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6"
id="rect4053"
width="1.9999566"
height="6.9999995"
x="7.0000219"
y="1039.8622"
ry="0"
rx="0" />
</g>
</g>
<g
inkscape:label="Downloads arrow"
inkscape:groupmode="layer"
id="g4409"
transform="translate(0,-1036.3622)"
style="display:inline;filter:url(#filter3767)">
<rect
style="fill:#f0f0f0;fill-opacity:1;fill-rule:nonzero;stroke:#f0f0f0;stroke-width:1.00000011999999994;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.60000000000000009"
id="rect4417"
width="2.9999068"
height="10"
x="6.5000467"
y="1036.8622"
ry="0"
rx="0" />
<path
style="fill:#f0f0f0;fill-opacity:1;fill-rule:nonzero;stroke:#f0f0f0;stroke-width:0.99999987999999995;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:1.60000000000000009"
d="M 14,1042.8622 8.0000001,1051.8621 2,1042.8622 z"
id="path4415"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
</g>
<g
style="display:none;filter:url(#filter3767)"
transform="translate(0,-1036.3622)"
id="g4546"
inkscape:groupmode="layer"
inkscape:label="Downloads arrow copy">
<path
style="fill:#202020;fill-opacity:1;fill-rule:nonzero;stroke:#202020;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6"
d="m 6.5625,1037.1122 0,5.8 -4.3125,0 5.75,8.7 5.75,-8.7 -4.3124999,0 0,-5.8 -2.8750001,0 z"
id="rect4550"
inkscape:connector-curvature="0" />
</g>
<g
style="display:none;filter:url(#filter3767)"
transform="translate(0,-1036.3622)"
id="g3775"
inkscape:groupmode="layer"
inkscape:label="Downloads alt">
<path
transform="translate(0.5,1035.8622)"
d="m 13,8.5 a 5.5,5.5 0 1 1 -11,0 5.5,5.5 0 1 1 11,0 z"
sodipodi:ry="5.5"
sodipodi:rx="5.5"
sodipodi:cy="8.5"
sodipodi:cx="7.5"
id="path3777"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc" />
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path3779"
d="m 11,1043.8622 -3.0000002,5 -2.9999997,-5 z"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:1.6" />
<rect
rx="0"
ry="0"
y="1039.8622"
x="7.25"
height="5"
width="1.5000001"
id="rect3781"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
id="svg7357"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="info.svg"
enable-background="new">
<defs
id="defs7359">
<filter
inkscape:collect="always"
id="filter3767">
<feBlend
inkscape:collect="always"
mode="multiply"
in2="BackgroundImage"
id="feBlend3769" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="44.4375"
inkscape:cx="8"
inkscape:cy="7.0998594"
inkscape:document-units="px"
inkscape:current-layer="g3890"
showgrid="true"
inkscape:window-width="1301"
inkscape:window-height="921"
inkscape:window-x="179"
inkscape:window-y="0"
inkscape:window-maximized="0"
showguides="true"
inkscape:guide-bbox="true">
<inkscape:grid
type="xygrid"
id="grid7365"
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingy="0.5px"
spacingx="0.5px" />
</sodipodi:namedview>
<metadata
id="metadata7362">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Info"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1036.3622)"
style="display:none;filter:url(#filter3767)">
<path
sodipodi:type="arc"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path7367"
sodipodi:cx="7.5"
sodipodi:cy="8.5"
sodipodi:rx="5.5"
sodipodi:ry="5.5"
d="m 13,8.5 a 5.5,5.5 0 1 1 -11,0 5.5,5.5 0 1 1 11,0 z"
transform="matrix(1.0909091,0,0,1.0909091,-0.18181825,1035.0895)" />
<text
xml:space="preserve"
style="font-size:11.98871613px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;opacity:0.97000002;fill:#000000;fill-opacity:1;stroke:none;font-family:URW Palladio L;-inkscape-font-specification:URW Palladio L Bold"
x="6.0098729"
y="1048.5763"
id="text3763"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3765"
x="6.0098729"
y="1048.5763">i</tspan></text>
</g>
<g
style="display:none;filter:url(#filter3767)"
transform="translate(0,-1036.3622)"
id="g3759"
inkscape:groupmode="layer"
inkscape:label="Info 2">
<path
transform="matrix(1.4545455,0,0,1.4545455,-2.9090913,1031.9986)"
d="m 13,8.5 a 5.5,5.5 0 1 1 -11,0 5.5,5.5 0 1 1 11,0 z"
sodipodi:ry="5.5"
sodipodi:rx="5.5"
sodipodi:cy="8.5"
sodipodi:cx="7.5"
id="path3761"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
sodipodi:type="arc" />
<text
sodipodi:linespacing="125%"
id="text3764"
y="1061.6149"
x="5.4226074"
style="font-size:14.97957802px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;opacity:0.97000002;fill:#000000;fill-opacity:1;stroke:none;font-family:URW Palladio L;-inkscape-font-specification:URW Palladio L Bold"
xml:space="preserve"
transform="scale(1.0114781,0.98865214)"><tspan
y="1061.6149"
x="5.4226074"
id="tspan3766"
sodipodi:role="line">i</tspan></text>
</g>
<g
inkscape:label="Info 3"
inkscape:groupmode="layer"
id="g3768"
transform="translate(0,-1036.3622)"
style="display:none;filter:url(#filter3767)">
<path
sodipodi:type="arc"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path3770"
sodipodi:cx="7.5"
sodipodi:cy="8.5"
sodipodi:rx="5.5"
sodipodi:ry="5.5"
d="m 13,8.5 a 5.5,5.5 0 1 1 -11,0 5.5,5.5 0 1 1 11,0 z"
transform="matrix(1.2727273,0,0,1.2727273,-1.5454547,1033.544)" />
<text
xml:space="preserve"
style="font-size:14.61857033px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;opacity:0.97000002;fill:#000000;fill-opacity:1;stroke:none;font-family:URW Palladio L;-inkscape-font-specification:URW Palladio L Bold"
x="5.2919226"
y="1087.5747"
id="text3772"
sodipodi:linespacing="125%"
transform="scale(1.0364567,0.96482565)"><tspan
sodipodi:role="line"
id="tspan3774"
x="5.2919226"
y="1087.5747">i</tspan></text>
</g>
<g
style="display:inline;filter:url(#filter3767)"
transform="translate(0,-1036.3622)"
id="g3890"
inkscape:groupmode="layer"
inkscape:label="Info letter">
<text
transform="scale(1.0445226,0.95737516)"
sodipodi:linespacing="125%"
id="text3894"
y="1099.1454"
x="3.746093"
style="font-size:23.57173729000000151px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:start;opacity:0.97000001999999996;fill:#ffffff;fill-opacity:1;stroke:none;font-family:URW Palladio L;-inkscape-font-specification:URW Palladio L Bold"
xml:space="preserve"><tspan
y="1099.1454"
x="3.746093"
id="tspan3896"
sodipodi:role="line">i</tspan></text>
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32"
height="32"
id="svg2"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="lock.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="28"
inkscape:cx="16"
inkscape:cy="16"
inkscape:document-units="px"
inkscape:current-layer="g3874"
showgrid="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1578"
inkscape:window-height="1106"
inkscape:window-x="131"
inkscape:window-y="42"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid2985"
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="0.5px"
spacingy="0.5px" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
style="display:none"
transform="translate(0,-1020.3622)"
id="g3974"
inkscape:groupmode="layer"
inkscape:label="Lock grey open">
<path
style="fill:none;stroke:#333333;stroke-width:3.36001468;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 21.740024,1037.4822 0,-8.9348 c 0,-1.5781 -1.490612,-3.1601 -2.943602,-3.8825 -1.670034,-0.8302 -3.92281,-0.8302 -5.592844,0 -1.45299,0.7224 -2.943602,2.3044 -2.943602,3.8825"
id="path3976"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csaac" />
<rect
y="1037.7622"
x="6.1999588"
height="11.200047"
width="19.600082"
id="rect3978"
style="fill:#545454;fill-opacity:1;fill-rule:nonzero;stroke:#333333;stroke-width:2.80001187;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6" />
</g>
<g
inkscape:label="Lock grey"
inkscape:groupmode="layer"
id="g3874"
transform="translate(0,-1020.3622)"
style="display:inline">
<path
sodipodi:nodetypes="csaasc"
inkscape:connector-curvature="0"
id="path3876"
d="m 21.740024,1037.4822 0,-8.9348 c 0,-1.5781 -1.490612,-3.1601 -2.943602,-3.8825 -1.670034,-0.8302 -3.92281,-0.8302 -5.592844,0 -1.45299,0.7224 -2.943602,2.3044 -2.943602,3.8825 l 0,8.9348"
style="fill:none;stroke:#333333;stroke-width:3.36001468;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
<rect
style="fill:#545454;fill-opacity:1;fill-rule:nonzero;stroke:#333333;stroke-width:2.79999995;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6"
id="rect3878"
width="19.600082"
height="11.200047"
x="6.1999588"
y="1037.7622" />
</g>
<g
style="display:none"
transform="translate(0,-1020.3622)"
id="g5403"
inkscape:groupmode="layer"
inkscape:label="Lock green">
<path
style="fill:none;stroke:#008000;stroke-width:3.36001468;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 21.740024,1037.4822 0,-8.9348 c 0,-1.5781 -1.490612,-3.1601 -2.943602,-3.8825 -1.670034,-0.8302 -3.92281,-0.8302 -5.592844,0 -1.45299,0.7224 -2.943602,2.3044 -2.943602,3.8825 l 0,8.9348"
id="path5405"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csaasc" />
<rect
y="1037.7622"
x="6.1999588"
height="11.200047"
width="19.600082"
id="rect5407"
style="fill:#00c000;fill-opacity:1;fill-rule:nonzero;stroke:#008000;stroke-width:2.80001187;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6" />
</g>
<g
style="display:none"
transform="translate(0,-1020.3622)"
id="g7515"
inkscape:groupmode="layer"
inkscape:label="Lock blue">
<path
style="fill:none;stroke:#0055aa;stroke-width:3.36001468;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 21.740024,1037.4822 0,-8.9348 c 0,-1.5781 -1.490612,-3.1601 -2.943602,-3.8825 -1.670034,-0.8302 -3.92281,-0.8302 -5.592844,0 -1.45299,0.7224 -2.943602,2.3044 -2.943602,3.8825 l 0,8.9348"
id="path7517"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csaasc" />
<rect
y="1037.7622"
x="6.1999588"
height="11.200047"
width="19.600082"
id="rect7519"
style="fill:#0076ee;fill-opacity:1;fill-rule:nonzero;stroke:#0055aa;stroke-width:2.80001187;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6" />
</g>
<g
inkscape:label="Lock orange"
inkscape:groupmode="layer"
id="g6866"
transform="translate(0,-1020.3622)"
style="display:none">
<path
sodipodi:nodetypes="csaasc"
inkscape:connector-curvature="0"
id="path6868"
d="m 21.740024,1037.4822 0,-8.9348 c 0,-1.5781 -1.490612,-3.1601 -2.943602,-3.8825 -1.670034,-0.8302 -3.92281,-0.8302 -5.592844,0 -1.45299,0.7224 -2.943602,2.3044 -2.943602,3.8825 l 0,8.9348"
style="fill:none;stroke:#aa5500;stroke-width:3.36001468;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
<rect
style="fill:#ee7600;fill-opacity:1;fill-rule:nonzero;stroke:#aa5500;stroke-width:2.80001187;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6"
id="rect6870"
width="19.600082"
height="11.200047"
x="6.1999588"
y="1037.7622" />
</g>
<g
style="display:none"
transform="translate(0,-1020.3622)"
id="g7033"
inkscape:groupmode="layer"
inkscape:label="Lock red">
<path
style="fill:none;stroke:#800000;stroke-width:3.36001468;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 21.740024,1037.4822 0,-8.9348 c 0,-1.5781 -1.490612,-3.1601 -2.943602,-3.8825 -1.670034,-0.8302 -3.92281,-0.8302 -5.592844,0 -1.45299,0.7224 -2.943602,2.3044 -2.943602,3.8825 l 0,8.9348"
id="path7035"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csaasc" />
<rect
y="1037.7622"
x="6.1999588"
height="11.200047"
width="19.600082"
id="rect7037"
style="fill:#c00000;fill-opacity:1;fill-rule:nonzero;stroke:#800000;stroke-width:2.80001187;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6" />
</g>
<g
style="display:none"
transform="translate(0,-1020.3622)"
id="g7832"
inkscape:groupmode="layer"
inkscape:label="Lock grey copy">
<g
id="g3961"
transform="matrix(2.6666761,0,0,2.6666761,-5.66e-5,-1753.9469)">
<path
sodipodi:nodetypes="csaasc"
inkscape:connector-curvature="0"
id="path7834"
d="m 8.6,1046.7622 0,-4.191 c 0,-0.5636 -0.6751873,-1.1286 -1.3333336,-1.3866 -0.7564579,-0.2965 -1.7768752,-0.2965 -2.5333329,0 C 4.0751872,1041.4426 3.4,1042.0076 3.4,1042.5712 l 0,4.191"
style="fill:none;stroke:#333333;stroke-width:1.20000017;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
<rect
style="fill:#545454;fill-opacity:1;fill-rule:nonzero;stroke:#333333;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6"
id="rect7836"
width="9"
height="5"
x="1.5"
y="1046.8622" />
</g>
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="12"
height="12"
id="svg2"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="lock12.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="74.666667"
inkscape:cx="6"
inkscape:cy="6"
inkscape:document-units="px"
inkscape:current-layer="g3874"
showgrid="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1578"
inkscape:window-height="1106"
inkscape:window-x="131"
inkscape:window-y="42"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid2985"
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="0.5px"
spacingy="0.5px" />
<sodipodi:guide
orientation="0,1"
position="-1,6"
id="guide2987" />
<sodipodi:guide
orientation="1,0"
position="6,6.5"
id="guide2989" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
style="display:none"
transform="translate(0,-1040.3622)"
id="g3974"
inkscape:groupmode="layer"
inkscape:label="Lock grey open">
<path
style="fill:none;stroke:#333333;stroke-width:1.20000017;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 8.0500001,1046.7622 0,-3.191 c 0,-0.5636 -0.5323592,-1.1286 -1.0512822,-1.3866 -0.596438,-0.2965 -1.4009978,-0.2965 -1.9974358,0 -0.518923,0.258 -1.0512821,0.823 -1.0512821,1.3866"
id="path3976"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csaac" />
<rect
y="1046.8622"
x="2.5"
height="4"
width="7"
id="rect3978"
style="fill:#545454;fill-opacity:1;fill-rule:nonzero;stroke:#333333;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6" />
</g>
<g
inkscape:label="Lock grey"
inkscape:groupmode="layer"
id="g3874"
transform="translate(0,-1040.3622)"
style="display:inline">
<path
sodipodi:nodetypes="csaasc"
inkscape:connector-curvature="0"
id="path3876"
d="m 8.0500001,1046.7622 0,-3.191 c 0,-0.5636 -0.5323592,-1.1286 -1.0512822,-1.3866 -0.596438,-0.2965 -1.4009978,-0.2965 -1.9974358,0 -0.518923,0.258 -1.0512821,0.823 -1.0512821,1.3866 l 0,3.191"
style="fill:none;stroke:#333333;stroke-width:1.20000017;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
<rect
style="fill:#545454;fill-opacity:1;fill-rule:nonzero;stroke:#333333;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6"
id="rect3878"
width="7"
height="4"
x="2.5"
y="1046.8622" />
</g>
<g
style="display:none"
transform="translate(0,-1040.3622)"
id="g5403"
inkscape:groupmode="layer"
inkscape:label="Lock green">
<path
style="fill:none;stroke:#008000;stroke-width:1.20000017;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 8.0500001,1046.7622 0,-3.191 c 0,-0.5636 -0.5323592,-1.1286 -1.0512822,-1.3866 -0.596438,-0.2965 -1.4009978,-0.2965 -1.9974358,0 -0.518923,0.258 -1.0512821,0.823 -1.0512821,1.3866 l 0,3.191"
id="path5405"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csaasc" />
<rect
y="1046.8622"
x="2.5"
height="4"
width="7"
id="rect5407"
style="fill:#00c000;fill-opacity:1;fill-rule:nonzero;stroke:#008000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6" />
</g>
<g
style="display:none"
transform="translate(0,-1040.3622)"
id="g7515"
inkscape:groupmode="layer"
inkscape:label="Lock blue">
<path
style="fill:none;stroke:#0055aa;stroke-width:1.20000017;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 8.0500001,1046.7622 0,-3.191 c 0,-0.5636 -0.5323592,-1.1286 -1.0512822,-1.3866 -0.596438,-0.2965 -1.4009978,-0.2965 -1.9974358,0 -0.518923,0.258 -1.0512821,0.823 -1.0512821,1.3866 l 0,3.191"
id="path7517"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csaasc" />
<rect
y="1046.8622"
x="2.5"
height="4"
width="7"
id="rect7519"
style="fill:#0076ee;fill-opacity:1;fill-rule:nonzero;stroke:#0055aa;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6" />
</g>
<g
inkscape:label="Lock orange"
inkscape:groupmode="layer"
id="g6866"
transform="translate(0,-1040.3622)"
style="display:none">
<path
sodipodi:nodetypes="csaasc"
inkscape:connector-curvature="0"
id="path6868"
d="m 8.0500001,1046.7622 0,-3.191 c 0,-0.5636 -0.5323592,-1.1286 -1.0512822,-1.3866 -0.596438,-0.2965 -1.4009978,-0.2965 -1.9974358,0 -0.518923,0.258 -1.0512821,0.823 -1.0512821,1.3866 l 0,3.191"
style="fill:none;stroke:#aa5500;stroke-width:1.20000017;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
<rect
style="fill:#ee7600;fill-opacity:1;fill-rule:nonzero;stroke:#aa5500;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6"
id="rect6870"
width="7"
height="4"
x="2.5"
y="1046.8622" />
</g>
<g
style="display:none"
transform="translate(0,-1040.3622)"
id="g7033"
inkscape:groupmode="layer"
inkscape:label="Lock red">
<path
style="fill:none;stroke:#800000;stroke-width:1.20000017;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 8.0500001,1046.7622 0,-3.191 c 0,-0.5636 -0.5323592,-1.1286 -1.0512822,-1.3866 -0.596438,-0.2965 -1.4009978,-0.2965 -1.9974358,0 -0.518923,0.258 -1.0512821,0.823 -1.0512821,1.3866 l 0,3.191"
id="path7035"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csaasc" />
<rect
y="1046.8622"
x="2.5"
height="4"
width="7"
id="rect7037"
style="fill:#c00000;fill-opacity:1;fill-rule:nonzero;stroke:#800000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6" />
</g>
<g
style="display:none"
transform="translate(0,-1040.3622)"
id="g7832"
inkscape:groupmode="layer"
inkscape:label="Lock grey copy">
<path
style="fill:none;stroke:#333333;stroke-width:1.20000017;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 8.6,1046.7622 0,-4.191 c 0,-0.5636 -0.6751873,-1.1286 -1.3333336,-1.3866 -0.7564579,-0.2965 -1.7768752,-0.2965 -2.5333329,0 C 4.0751872,1041.4426 3.4,1042.0076 3.4,1042.5712 l 0,4.191"
id="path7834"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csaasc" />
<rect
y="1046.8622"
x="1.5"
height="5"
width="9"
id="rect7836"
style="fill:#545454;fill-opacity:1;fill-rule:nonzero;stroke:#333333;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
height="16"
id="Layer_1"
version="1.1"
viewBox="0 0 16 16"
width="16"
x="0px"
xml:space="preserve"
y="0px"
inkscape:version="0.48.4 r9939"
sodipodi:docname="people.svg"
inkscape:label="Layer"><metadata
id="metadata19"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs17" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1575"
inkscape:window-height="1099"
id="namedview15"
showgrid="true"
inkscape:zoom="54.6875"
inkscape:cx="7.9817143"
inkscape:cy="8"
inkscape:window-x="133"
inkscape:window-y="24"
inkscape:window-maximized="0"
inkscape:current-layer="layer1"><inkscape:grid
type="xygrid"
id="grid3018"
empspacing="10"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="0.5px"
spacingy="0.5px" /></sodipodi:namedview><g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="Person"
style="display:inline"><path
d="m 13.364029,12.379035 c -0.644604,-0.216591 -1.697842,-0.283234 -2.158273,-0.455398 -0.322303,-0.122179 -0.834533,-0.249912 -0.995685,-0.44429 -0.166905,-0.188823 -0.166905,-1.5605687 -0.166905,-1.5605687 0,0 0.402877,-0.3609857 0.569783,-0.7664005 0.166907,-0.4054147 0.27626,-1.5050329 0.27626,-1.5050329 0,0 0.03453,0.011107 0.08633,0.011107 0.120864,0 0.339569,-0.07775 0.443166,-0.5886844 0.126618,-0.6275598 0.368345,-0.9552238 0.305036,-1.4161749 -0.04605,-0.3110031 -0.184173,-0.3609858 -0.264748,-0.3609858 -0.04029,0 -0.06907,0.011107 -0.06907,0.011107 0,0 0.328058,-0.4665047 0.328058,-2.054842 C 11.717988,1.6161056 10.411512,0.00555364 8.0000018,4.0000001e-8 5.5827357,0.00554704 4.2820167,1.6161056 4.2820167,3.2488719 c 0,1.5827837 0.3280576,2.054842 0.3280576,2.054842 0,0 -0.028776,-0.011107 -0.069064,-0.011107 -0.086331,0 -0.2244605,0.049983 -0.2647483,0.3609858 -0.06331,0.4609511 0.1726619,0.7941687 0.3050361,1.4161749 0.1035968,0.5109337 0.3223022,0.5886844 0.4431654,0.5886844 0.051798,0 0.086331,-0.011107 0.086331,-0.011107 0,0 0.1093525,1.1051718 0.276259,1.5050329 0.1669064,0.4054148 0.5697836,0.7664005 0.5697836,0.7664005 0,0 0,1.3717456 -0.1669065,1.5605686 C 5.6230213,11.668171 5.1165472,11.801458 4.794245,11.923637 4.3338133,12.095801 3.2805757,12.162444 2.6359714,12.379035 1.991367,12.595627 2.5e-8,13.500867 2.5e-8,16 H 16 c 0,-2.499133 -1.985612,-3.404373 -2.635971,-3.620965 z"
id="path9-4"
inkscape:connector-curvature="0"
style="fill:#f0f0f0;fill-opacity:1" /></g><g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Person 2"
style="display:none"><path
sodipodi:type="arc"
style="fill:#f0f0f0;fill-opacity:1;fill-rule:nonzero;stroke:#f0f0f0;stroke-width:0.26457518;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6;display:inline"
id="path5460"
sodipodi:cx="2.5"
sodipodi:cy="4.25"
sodipodi:rx="1.5"
sodipodi:ry="1.75"
d="m 1,4.2499999 a 1.5,1.75 0 0 1 3,1e-7 l -1.5,0 z"
transform="matrix(4.9999999,0,0,2.8571428,-4.4999998,3.357143)"
sodipodi:start="3.1415927"
sodipodi:end="6.2831853" /><path
sodipodi:type="arc"
style="fill:#f0f0f0;fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline"
id="path4906"
sodipodi:cx="6"
sodipodi:cy="2.5"
sodipodi:rx="2.5"
sodipodi:ry="2.5"
d="m 8.5,2.5 a 2.5,2.5 0 1 1 -5,0 2.5,2.5 0 1 1 5,0 z"
transform="matrix(1.8,0,0,1.8,-2.8,0)" /><rect
style="fill:#f0f0f0;fill-opacity:1;fill-rule:nonzero;stroke:#f0f0f0;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6;display:inline"
id="rect5512"
width="3"
height="7"
x="6.5"
y="4.5"
rx="0.66666669"
ry="0.32408535" /></g><g
style="display:none"
inkscape:label="Person 3"
id="g4045"
inkscape:groupmode="layer"><path
sodipodi:end="6.2831853"
sodipodi:start="3.1415927"
transform="matrix(4.9999999,0,0,2.8571428,-4.4999998,3.357143)"
d="m 1,4.2499999 a 1.5,1.75 0 0 1 3,1e-7 l -1.5,0 z"
sodipodi:ry="1.75"
sodipodi:rx="1.5"
sodipodi:cy="4.25"
sodipodi:cx="2.5"
id="path4047"
style="fill:#f0f0f0;fill-opacity:1;fill-rule:nonzero;stroke:#f0f0f0;stroke-width:0.26457518;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:1.6;display:inline"
sodipodi:type="arc" /><path
transform="matrix(1.8,0,0,1.8,-2.8,0)"
d="m 8.5,2.5 a 2.5,2.5 0 1 1 -5,0 2.5,2.5 0 1 1 5,0 z"
sodipodi:ry="2.5"
sodipodi:rx="2.5"
sodipodi:cy="2.5"
sodipodi:cx="6"
id="path4049"
style="fill:#f0f0f0;fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline"
sodipodi:type="arc" /></g></svg>
\ No newline at end of file
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