Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
V
vmj-qt
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kulya
vmj-qt
Commits
9c840d9b
Commit
9c840d9b
authored
May 21, 2013
by
Saul Ibarra
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added initial presence support
parent
8272c5a2
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
1146 additions
and
7 deletions
+1146
-7
Notes
Notes
+8
-0
__init__.py
blink/__init__.py
+6
-0
account.py
blink/configuration/account.py
+6
-1
settings.py
blink/configuration/settings.py
+1
-0
contacts.py
blink/contacts.py
+30
-5
mainwindow.py
blink/mainwindow.py
+27
-1
presence.py
blink/presence.py
+578
-0
pending_watcher.ui
resources/pending_watcher.ui
+490
-0
No files found.
Notes
View file @
9c840d9b
...
...
@@ -42,4 +42,12 @@ Traceback (most recent call last):
File "/home/dan/work/voip/blink-qt/sipsimple/bonjour.py", line 1125, in DNSServiceRegister
TypeError: an integer is required
Presence
--------
- Is picking the most recent timestamp a good winning method?
- Calculate user idleness
- Add a GUI element for the offline note
- Delete own icon if we don't get anything back from XCAP?
- Unify settings for inbound and outbound presence
blink/__init__.py
View file @
9c840d9b
...
...
@@ -41,6 +41,7 @@ from blink.configuration.datatypes import InvalidToken
from
blink.configuration.settings
import
SIPSimpleSettingsExtension
from
blink.logging
import
LogManager
from
blink.mainwindow
import
MainWindow
from
blink.presence
import
PresenceManager
from
blink.resources
import
ApplicationData
from
blink.sessions
import
SessionManager
from
blink.update
import
UpdateManager
...
...
@@ -96,6 +97,7 @@ class Blink(QApplication):
self
.
main_window
=
MainWindow
()
self
.
ip_address_monitor
=
IPAddressMonitor
()
self
.
log_manager
=
LogManager
()
self
.
presence_manager
=
PresenceManager
()
self
.
update_manager
=
UpdateManager
()
self
.
main_window
.
check_for_updates_action
.
triggered
.
connect
(
self
.
update_manager
.
check_for_updates
)
...
...
@@ -212,6 +214,7 @@ class Blink(QApplication):
def
_NH_SIPApplicationWillStart
(
self
,
notification
):
self
.
log_manager
.
start
()
self
.
presence_manager
.
start
()
@
run_in_gui_thread
def
_NH_SIPApplicationDidStart
(
self
,
notification
):
...
...
@@ -229,6 +232,9 @@ class Blink(QApplication):
def
_NH_SIPApplicationWillEnd
(
self
,
notification
):
self
.
ip_address_monitor
.
stop
()
def
_NH_SIPApplicationDidEnd
(
self
,
notification
):
self
.
presence_manager
.
stop
()
def
_initialize_sipsimple
(
self
):
if
not
os
.
path
.
exists
(
ApplicationData
.
get
(
'config'
)):
self
.
first_run
=
True
...
...
blink/configuration/account.py
View file @
9c840d9b
...
...
@@ -5,7 +5,7 @@
__all__
=
[
'AccountExtension'
,
'BonjourAccountExtension'
]
from
sipsimple.account
import
BonjourMSRPSettings
,
MessageSummarySettings
,
MSRPSettings
,
RTPSettings
,
SIPSettings
,
TLSSettings
,
XCAPSettings
from
sipsimple.account
import
BonjourMSRPSettings
,
MessageSummarySettings
,
MSRPSettings
,
PresenceSettings
,
RTPSettings
,
SIPSettings
,
TLSSettings
,
XCAPSettings
from
sipsimple.configuration
import
Setting
,
SettingsGroup
,
SettingsObjectExtension
from
sipsimple.configuration.datatypes
import
AudioCodecList
,
Hostname
,
MSRPConnectionModel
,
MSRPTransport
,
NonNegativeInteger
,
SIPTransportList
,
SRTPEncryption
from
sipsimple.util
import
user_info
...
...
@@ -29,6 +29,10 @@ class MSRPSettingsExtension(MSRPSettings):
connection_model
=
Setting
(
type
=
MSRPConnectionModel
,
default
=
'relay'
)
class
PresenceSettingsExtension
(
PresenceSettings
):
enabled
=
Setting
(
type
=
bool
,
default
=
True
)
class
PSTNSettings
(
SettingsGroup
):
idd_prefix
=
Setting
(
type
=
unicode
,
default
=
None
,
nillable
=
True
)
prefix
=
Setting
(
type
=
unicode
,
default
=
None
,
nillable
=
True
)
...
...
@@ -71,6 +75,7 @@ class AccountExtension(SettingsObjectExtension):
message_summary
=
MessageSummarySettingsExtension
msrp
=
MSRPSettingsExtension
pstn
=
PSTNSettings
presence
=
PresenceSettingsExtension
rtp
=
RTPSettingsExtension
server
=
ServerSettings
sip
=
SIPSettingsExtension
...
...
blink/configuration/settings.py
View file @
9c840d9b
...
...
@@ -94,6 +94,7 @@ class SIPSimpleSettingsExtension(SettingsObjectExtension):
class
BlinkPresenceSettings
(
SettingsGroup
):
current_state
=
Setting
(
type
=
PresenceState
,
default
=
PresenceState
(
'Available'
))
state_history
=
Setting
(
type
=
PresenceStateList
,
default
=
PresenceStateList
())
offline_note
=
Setting
(
type
=
unicode
,
nillable
=
True
)
icon
=
Setting
(
type
=
IconDescriptor
,
nillable
=
True
)
...
...
blink/contacts.py
View file @
9c840d9b
...
...
@@ -790,7 +790,8 @@ class Contact(object):
def
__init__
(
self
,
contact
,
group
):
self
.
settings
=
contact
self
.
group
=
group
self
.
status
=
'unknown'
self
.
state
=
'unknown'
self
.
note
=
None
notification_center
=
NotificationCenter
()
notification_center
.
add_observer
(
ObserverWeakrefProxy
(
self
),
sender
=
contact
)
...
...
@@ -818,7 +819,7 @@ class Contact(object):
return
'
%
s(
%
r,
%
r)'
%
(
self
.
__class__
.
__name__
,
self
.
settings
,
self
.
group
)
def
__getstate__
(
self
):
return
(
self
.
settings
.
id
,
dict
(
group
=
self
.
group
,
stat
us
=
self
.
status
))
return
(
self
.
settings
.
id
,
dict
(
group
=
self
.
group
,
stat
e
=
self
.
state
))
def
__setstate__
(
self
,
state
):
contact_id
,
state
=
state
...
...
@@ -851,7 +852,7 @@ class Contact(object):
@
property
def
info
(
self
):
return
self
.
uri
return
self
.
note
or
self
.
uri
@
property
def
uri
(
self
):
...
...
@@ -909,6 +910,19 @@ class Contact(object):
self
.
__dict__
.
pop
(
'pixmap'
,
None
)
notification
.
center
.
post_notification
(
'BlinkContactDidChange'
,
sender
=
self
)
def
_NH_AddressbookContactGotPresenceUpdate
(
self
,
notification
):
if
notification
.
data
.
state
in
(
'available'
,
'away'
,
'busy'
,
'offline'
):
self
.
__dict__
[
'state'
]
=
notification
.
data
.
state
else
:
self
.
__dict__
[
'state'
]
=
'unknown'
self
.
note
=
notification
.
data
.
note
if
notification
.
data
.
icon_data
:
icon
=
IconManager
()
.
store_data
(
self
.
settings
.
id
,
notification
.
data
.
icon_data
)
if
icon
:
self
.
settings
.
icon
=
notification
.
data
.
icon_descriptor
self
.
settings
.
save
()
notification
.
center
.
post_notification
(
'BlinkContactDidChange'
,
sender
=
self
)
ui_class
,
base_class
=
uic
.
loadUiType
(
Resources
.
get
(
'google_contacts_dialog.ui'
))
...
...
@@ -1293,9 +1307,9 @@ class ContactDelegate(QStyledItemDelegate):
widget
.
render
(
pixmap
)
painter
.
drawPixmap
(
option
.
rect
,
pixmap
)
if
contact
.
stat
us
not
in
(
'offline'
,
'unknown'
):
if
contact
.
stat
e
not
in
(
'offline'
,
'unknown'
):
status_colors
=
dict
(
available
=
'#00ff00'
,
away
=
'#ffff00'
,
busy
=
'#ff0000'
)
color
=
QColor
(
status_colors
[
contact
.
stat
us
])
color
=
QColor
(
status_colors
[
contact
.
stat
e
])
painter
.
setRenderHint
(
QPainter
.
Antialiasing
,
True
)
painter
.
setBrush
(
color
)
painter
.
setPen
(
color
.
darker
(
200
))
...
...
@@ -2708,11 +2722,16 @@ class ContactEditorDialog(base_class, ui_class):
self
.
display_name_editor
.
setText
(
contact
.
name
)
if
contact
.
settings
.
icon
is
not
None
and
contact
.
settings
.
icon
.
is_local
:
self
.
icon_selector
.
filename
=
contact
.
settings
.
icon
.
url
[
len
(
'file://'
):]
elif
contact
.
settings
.
icon
:
icon
=
IconManager
()
.
get
(
contact
.
settings
.
id
)
if
icon
:
self
.
icon_selector
.
setPixmap
(
icon
.
pixmap
(
32
))
else
:
self
.
icon_selector
.
filename
=
None
self
.
preferred_media
.
setCurrentIndex
(
self
.
preferred_media
.
findText
(
contact
.
settings
.
preferred_media
.
title
()))
self
.
accept_button
.
setText
(
u'Ok'
)
self
.
accept_button
.
setEnabled
(
True
)
self
.
subscribe_presence
.
setChecked
(
contact
.
settings
.
presence
.
subscribe
)
self
.
show
()
def
reset_icon
(
self
):
...
...
@@ -2736,6 +2755,12 @@ class ContactEditorDialog(base_class, ui_class):
uri
.
uri
=
self
.
sip_address_editor
.
text
()
contact
.
name
=
self
.
display_name_editor
.
text
()
contact
.
preferred_media
=
self
.
preferred_media
.
currentText
()
.
lower
()
if
self
.
subscribe_presence
.
isChecked
():
contact
.
presence
.
policy
=
'allow'
contact
.
presence
.
subscribe
=
True
else
:
contact
.
presence
.
policy
=
'default'
contact
.
presence
.
subscribe
=
False
if
self
.
icon_selector
.
filename
is
not
None
:
icon_file
=
ApplicationData
.
get
(
self
.
icon_selector
.
filename
)
icon_descriptor
=
IconDescriptor
(
'file://'
+
icon_file
)
...
...
blink/mainwindow.py
View file @
9c840d9b
...
...
@@ -29,6 +29,7 @@ from blink.preferences import PreferencesWindow
from
blink.sessions
import
ConferenceDialog
,
SessionManager
,
SessionModel
from
blink.configuration.datatypes
import
IconDescriptor
,
InvalidToken
,
PresenceState
from
blink.configuration.settings
import
BlinkSettings
from
blink.presence
import
PendingWatcherDialog
from
blink.resources
import
IconManager
,
Resources
from
blink.util
import
run_in_gui_thread
from
blink.widgets.buttons
import
AccountState
,
SwitchViewButton
...
...
@@ -47,8 +48,11 @@ class MainWindow(base_class, ui_class):
notification_center
.
add_observer
(
self
,
name
=
'SIPApplicationWillStart'
)
notification_center
.
add_observer
(
self
,
name
=
'SIPApplicationDidStart'
)
notification_center
.
add_observer
(
self
,
name
=
'SIPAccountGotMessageSummary'
)
notification_center
.
add_observer
(
self
,
name
=
'SIPAccountGotPendingWatcher'
)
notification_center
.
add_observer
(
self
,
sender
=
AccountManager
())
self
.
pending_watcher_dialogs
=
[]
self
.
mwi_icons
=
[
QIcon
(
Resources
.
get
(
'icons/mwi-
%
d.png'
%
i
))
for
i
in
xrange
(
0
,
11
)]
self
.
mwi_icons
.
append
(
QIcon
(
Resources
.
get
(
'icons/mwi-many.png'
)))
...
...
@@ -58,7 +62,7 @@ class MainWindow(base_class, ui_class):
self
.
setWindowTitle
(
'Blink'
)
self
.
setWindowIconText
(
'Blink'
)
self
.
default_icon_path
=
Resources
.
get
(
'icons/
avatar.jp
g'
)
self
.
default_icon_path
=
Resources
.
get
(
'icons/
default-avatar.pn
g'
)
self
.
default_icon
=
QIcon
(
self
.
default_icon_path
)
self
.
last_icon_directory
=
os
.
path
.
expanduser
(
'~'
)
self
.
set_user_icon
(
IconManager
()
.
get
(
'myicon'
))
...
...
@@ -212,6 +216,8 @@ class MainWindow(base_class, ui_class):
self
.
google_contacts_dialog
.
close
()
self
.
preferences_window
.
close
()
self
.
server_tools_window
.
close
()
for
dialog
in
self
.
pending_watcher_dialogs
[:]:
dialog
.
close
()
def
set_user_icon
(
self
,
icon
):
self
.
account_state
.
setIcon
(
icon
or
self
.
default_icon
)
...
...
@@ -590,6 +596,9 @@ class MainWindow(base_class, ui_class):
action
=
self
.
received_calls_menu
.
addAction
(
unicode
(
entry
))
action
.
entry
=
entry
def
_SH_PendingWatcherDialogFinished
(
self
,
dialog
,
code
):
self
.
pending_watcher_dialogs
.
remove
(
dialog
)
@
run_in_gui_thread
def
handle_notification
(
self
,
notification
):
handler
=
getattr
(
self
,
'_NH_
%
s'
%
notification
.
name
,
Null
)
...
...
@@ -646,6 +655,7 @@ class MainWindow(base_class, ui_class):
def
_NH_CFGSettingsObjectDidChange
(
self
,
notification
):
settings
=
SIPSimpleSettings
()
blink_settings
=
BlinkSettings
()
if
notification
.
sender
is
settings
:
if
'audio.silent'
in
notification
.
data
.
modified
:
self
.
silent_action
.
setChecked
(
settings
.
audio
.
silent
)
...
...
@@ -673,6 +683,15 @@ class MainWindow(base_class, ui_class):
self
.
google_contacts_action
.
setText
(
u'Disable Google Contacts'
)
if
authorization_token
is
InvalidToken
:
self
.
google_contacts_dialog
.
open_for_incorrect_password
()
elif
notification
.
sender
is
blink_settings
:
if
'presence.current_state'
in
notification
.
data
.
modified
:
state
=
getattr
(
AccountState
,
blink_settings
.
presence
.
current_state
.
state
,
AccountState
.
Available
)
self
.
account_state
.
setState
(
state
,
blink_settings
.
presence
.
current_state
.
note
)
if
'presence.icon'
in
notification
.
data
.
modified
:
self
.
set_user_icon
(
IconManager
()
.
get
(
'myicon'
))
if
'presence.offline_note'
in
notification
.
data
.
modified
:
# TODO: set offline note -Saul
pass
elif
isinstance
(
notification
.
sender
,
(
Account
,
BonjourAccount
)):
account_manager
=
AccountManager
()
account
=
notification
.
sender
...
...
@@ -730,6 +749,13 @@ class MainWindow(base_class, ui_class):
new_messages
=
0
action
.
setIcon
(
self
.
mwi_icons
[
new_messages
])
def
_NH_SIPAccountGotPendingWatcher
(
self
,
notification
):
dialog
=
PendingWatcherDialog
(
notification
.
sender
,
notification
.
data
.
uri
,
notification
.
data
.
display_name
)
dialog
.
finished
.
connect
(
partial
(
self
.
_SH_PendingWatcherDialogFinished
,
dialog
))
self
.
pending_watcher_dialogs
.
append
(
dialog
)
dialog
.
show
()
del
ui_class
,
base_class
blink/presence.py
0 → 100644
View file @
9c840d9b
# Copyright (C) 2013 AG Projects. See LICENSE for details.
#
__all__
=
[
'PresenceManager'
,
'PendingWatcherDialog'
]
import
base64
import
hashlib
import
re
import
socket
import
uuid
from
PyQt4
import
uic
from
PyQt4.QtCore
import
Qt
,
QTimer
from
application.notification
import
IObserver
,
NotificationCenter
,
NotificationData
from
application.python
import
Null
,
limit
from
datetime
import
datetime
from
eventlib.green
import
urllib2
from
itertools
import
chain
from
twisted.internet
import
reactor
from
zope.interface
import
implements
from
sipsimple
import
addressbook
from
sipsimple.account
import
AccountManager
,
BonjourAccount
from
sipsimple.account.xcap
import
Icon
,
OfflineStatus
from
sipsimple.configuration.settings
import
SIPSimpleSettings
from
sipsimple.payloads
import
caps
,
cipid
,
pidf
,
prescontent
,
rpid
from
sipsimple.threading
import
run_in_twisted_thread
from
sipsimple.threading.green
import
run_in_green_thread
from
sipsimple.util
import
ISOTimestamp
from
blink.configuration.datatypes
import
IconDescriptor
,
PresenceState
from
blink.configuration.settings
import
BlinkSettings
from
blink.resources
import
IconManager
,
Resources
from
blink.util
import
run_in_gui_thread
epoch
=
datetime
.
fromtimestamp
(
0
)
sip_prefix_re
=
re
.
compile
(
"^sips?:"
)
unknown_icon
=
"blink://unknown"
class
PresencePublicationHandler
(
object
):
implements
(
IObserver
)
def
start
(
self
):
notification_center
=
NotificationCenter
()
notification_center
.
add_observer
(
self
,
name
=
'SIPAccountWillActivate'
)
notification_center
.
add_observer
(
self
,
name
=
'SIPAccountWillDeactivate'
)
notification_center
.
add_observer
(
self
,
name
=
'SystemDidWakeUpFromSleep'
)
notification_center
.
add_observer
(
self
,
name
=
'XCAPManagerDidDiscoverServerCapabilities'
)
notification_center
.
add_observer
(
self
,
name
=
'XCAPManagerDidReloadData'
)
notification_center
.
add_observer
(
self
,
sender
=
BlinkSettings
(),
name
=
'CFGSettingsObjectDidChange'
)
try
:
self
.
hostname
=
socket
.
gethostname
()
except
Exception
:
self
.
hostname
=
'localhost'
def
stop
(
self
):
notification_center
=
NotificationCenter
()
notification_center
.
remove_observer
(
self
,
name
=
'SIPAccountWillActivate'
)
notification_center
.
remove_observer
(
self
,
name
=
'SIPAccountWillDeactivate'
)
notification_center
.
remove_observer
(
self
,
name
=
'SystemDidWakeUpFromSleep'
)
notification_center
.
remove_observer
(
self
,
name
=
'XCAPManagerDidDiscoverServerCapabilities'
)
notification_center
.
remove_observer
(
self
,
name
=
'XCAPManagerDidReloadData'
)
notification_center
.
remove_observer
(
self
,
sender
=
BlinkSettings
(),
name
=
'CFGSettingsObjectDidChange'
)
def
publish
(
self
,
account
=
None
):
if
not
account
:
account_manager
=
AccountManager
()
bonjour_account
=
BonjourAccount
()
accounts
=
[
account
for
account
in
account_manager
.
get_accounts
()
if
account
is
not
bonjour_account
and
account
.
enabled
and
account
.
presence
.
enabled
]
else
:
accounts
=
[
account
]
for
account
in
accounts
:
account
.
presence_state
=
self
.
build_pidf
(
account
)
def
build_pidf
(
self
,
account
):
blink_settings
=
BlinkSettings
()
presence_state
=
blink_settings
.
presence
.
current_state
.
state
presence_note
=
blink_settings
.
presence
.
current_state
.
note
if
presence_state
==
'Invisible'
:
# Publish an empty offline state so that other clients are also synced
return
self
.
build_offline_pidf
(
account
,
False
)
doc
=
pidf
.
PIDF
(
str
(
account
.
uri
))
timestamp
=
ISOTimestamp
.
now
()
person
=
pidf
.
Person
(
'PID-
%
s'
%
hashlib
.
md5
(
account
.
id
)
.
hexdigest
())
person
.
timestamp
=
pidf
.
PersonTimestamp
(
timestamp
)
doc
.
add
(
person
)
status
=
pidf
.
Status
(
basic
=
'open'
)
status
.
extended
=
presence_state
.
lower
()
person
.
activities
=
rpid
.
Activities
()
person
.
activities
.
add
(
unicode
(
status
.
extended
))
settings
=
SIPSimpleSettings
()
instance_id
=
str
(
uuid
.
UUID
(
settings
.
instance_id
))
service
=
pidf
.
Service
(
'SID-
%
s'
%
instance_id
,
status
=
status
)
if
presence_note
:
service
.
notes
.
add
(
presence_note
)
service
.
timestamp
=
pidf
.
ServiceTimestamp
(
timestamp
)
service
.
contact
=
pidf
.
Contact
(
str
(
account
.
contact
.
public_gruu
or
account
.
uri
))
if
account
.
display_name
:
service
.
display_name
=
cipid
.
DisplayName
(
account
.
display_name
)
if
account
.
icon
:
service
.
icon
=
cipid
.
Icon
(
"
%
s#blink-icon
%
s"
%
(
account
.
icon
.
url
,
account
.
icon
.
etag
))
else
:
service
.
icon
=
cipid
.
Icon
(
unknown_icon
)
service
.
device_info
=
pidf
.
DeviceInfo
(
instance_id
,
description
=
self
.
hostname
,
user_agent
=
settings
.
user_agent
)
service
.
device_info
.
time_offset
=
pidf
.
TimeOffset
()
service
.
capabilities
=
caps
.
ServiceCapabilities
(
audio
=
True
,
text
=
False
)
service
.
capabilities
.
message
=
False
service
.
capabilities
.
file_transfer
=
False
service
.
capabilities
.
screen_sharing_server
=
False
service
.
capabilities
.
screen_sharing_client
=
False
# TODO: Add real user input data -Saul
service
.
user_input
=
rpid
.
UserInput
()
service
.
user_input
.
idle_threshold
=
600
service
.
add
(
pidf
.
DeviceID
(
instance_id
))
doc
.
add
(
service
)
device
=
pidf
.
Device
(
'DID-
%
s'
%
instance_id
,
device_id
=
pidf
.
DeviceID
(
instance_id
))
device
.
timestamp
=
pidf
.
DeviceTimestamp
(
timestamp
)
device
.
notes
.
add
(
u'
%
s at
%
s'
%
(
settings
.
user_agent
,
self
.
hostname
))
doc
.
add
(
device
)
return
doc
def
build_offline_pidf
(
self
,
account
,
offline_note
=
None
):
doc
=
pidf
.
PIDF
(
str
(
account
.
uri
))
timestamp
=
ISOTimestamp
.
now
()
account_hash
=
hashlib
.
md5
(
account
.
id
)
.
hexdigest
()
person
=
pidf
.
Person
(
'PID-
%
s'
%
account_hash
)
person
.
timestamp
=
pidf
.
PersonTimestamp
(
timestamp
)
doc
.
add
(
person
)
person
.
activities
=
rpid
.
Activities
()
person
.
activities
.
add
(
u'offline'
)
service
=
pidf
.
Service
(
'SID-
%
s'
%
account_hash
)
service
.
status
=
pidf
.
Status
(
basic
=
'closed'
)
service
.
status
.
extended
=
u'offline'
service
.
contact
=
pidf
.
Contact
(
str
(
account
.
uri
))
service
.
capabilities
=
caps
.
ServiceCapabilities
()
service
.
timestamp
=
pidf
.
ServiceTimestamp
(
timestamp
)
if
offline_note
:
service
.
notes
.
add
(
offline_note
)
doc
.
add
(
service
)
return
doc
def
set_xcap_offline_note
(
self
,
account
=
None
):
settings
=
BlinkSettings
()
if
not
account
:
account_manager
=
AccountManager
()
accounts
=
[
account
for
account
in
account_manager
.
get_accounts
()
if
hasattr
(
account
,
'xcap'
)
and
account
.
xcap
.
discovered
]
else
:
accounts
=
[
account
]
for
account
in
accounts
:
if
settings
.
presence
.
offline_note
:
account
.
xcap_manager
.
set_offline_status
(
OfflineStatus
(
self
.
build_offline_pidf
(
account
,
settings
.
presence
.
offline_note
)))
else
:
account
.
xcap_manager
.
set_offline_status
(
None
)
def
set_xcap_icon
(
self
,
account
=
None
):
settings
=
BlinkSettings
()
if
not
account
:
account_manager
=
AccountManager
()
accounts
=
[
account
for
account
in
account_manager
.
get_accounts
()
if
hasattr
(
account
,
'xcap'
)
and
account
.
xcap
.
discovered
]
else
:
accounts
=
[
account
]
icon
=
None
if
settings
.
presence
.
icon
:
try
:
data
=
open
(
settings
.
presence
.
icon
.
url
[
7
:],
'r'
)
.
read
()
# strip 'file://'
except
Exception
:
pass
else
:
icon
=
Icon
(
data
,
'image/png'
)
for
account
in
accounts
:
account
.
xcap_manager
.
set_status_icon
(
icon
)
@
run_in_gui_thread
def
_save_icon
(
self
,
icon_data
,
icon_hash
):
settings
=
BlinkSettings
()
if
None
not
in
(
icon_data
,
icon_hash
):
icon
=
IconManager
()
.
store_data
(
'myicon'
,
icon_data
)
if
icon
:
settings
.
presence
.
icon
=
IconDescriptor
(
'file://'
+
icon
.
filename
,
icon_hash
)
else
:
settings
.
presence
.
icon
=
None
settings
.
save
()
@
run_in_twisted_thread
def
handle_notification
(
self
,
notification
):
handler
=
getattr
(
self
,
'_NH_
%
s'
%
notification
.
name
,
Null
)
handler
(
notification
)
def
_NH_CFGSettingsObjectDidChange
(
self
,
notification
):
settings
=
BlinkSettings
()
if
notification
.
sender
is
settings
:
if
'presence.offline_note'
in
notification
.
data
.
modified
:
self
.
set_xcap_offline_note
()
if
'presence.icon'
in
notification
.
data
.
modified
:
self
.
set_xcap_icon
()
if
'presence.current_state'
in
notification
.
data
.
modified
:
self
.
publish
()
else
:
account
=
notification
.
sender
if
'presence.enabled'
in
notification
.
data
.
modified
:
if
account
.
presence
.
enabled
:
self
.
publish
(
account
)
# The account itself will unpublish when presence is disabled
return
if
set
([
'xcap.enabled'
,
'xcap.xcap_root'
])
.
intersection
(
notification
.
data
.
modified
):
account
.
icon
=
None
if
set
([
'display_name'
,
'xcap.enabled'
,
'xcap.discovered'
])
.
intersection
(
notification
.
data
.
modified
):
if
'xcap.discovered'
in
notification
.
data
.
modified
and
account
.
xcap
.
enabled
and
account
.
xcap
.
discovered
:
# TODO: group these in a transaction? Needs to be done in the file-io thread -Saul
self
.
set_xcap_offline_note
(
account
)
self
.
set_xcap_icon
(
account
)
self
.
publish
(
account
)
def
_NH_SIPAccountWillActivate
(
self
,
notification
):
if
notification
.
sender
is
not
BonjourAccount
():
account
=
notification
.
sender
notification
.
center
.
add_observer
(
self
,
sender
=
account
,
name
=
'CFGSettingsObjectDidChange'
)
notification
.
center
.
add_observer
(
self
,
sender
=
account
,
name
=
'SIPAccountGotSelfPresenceState'
)
account
.
icon
=
None
def
_NH_SIPAccountWillDeactivate
(
self
,
notification
):
if
notification
.
sender
is
not
BonjourAccount
():
account
=
notification
.
sender
notification
.
center
.
remove_observer
(
self
,
sender
=
account
,
name
=
'CFGSettingsObjectDidChange'
)
notification
.
center
.
remove_observer
(
self
,
sender
=
account
,
name
=
'SIPAccountGotSelfPresenceState'
)
account
.
icon
=
None
def
_NH_SIPAccountGotSelfPresenceState
(
self
,
notification
):
pidf_doc
=
notification
.
data
.
pidf
services
=
[
service
for
service
in
pidf_doc
.
services
if
service
.
status
.
extended
is
not
None
]
if
not
services
:
return
settings
=
BlinkSettings
()
services
.
sort
(
key
=
lambda
obj
:
obj
.
timestamp
.
value
if
obj
.
timestamp
else
epoch
,
reverse
=
True
)
service
=
services
[
0
]
if
service
.
id
in
(
'SID-
%
s'
%
uuid
.
UUID
(
SIPSimpleSettings
()
.
instance_id
),
'SID-
%
s'
%
hashlib
.
md5
(
notification
.
sender
.
id
)
.
hexdigest
()):
# Our current state is the winning one
return
status
=
unicode
(
service
.
status
.
extended
)
.
title
()
note
=
None
if
not
service
.
notes
else
unicode
(
list
(
service
.
notes
)[
0
])
if
status
==
'Offline'
:
status
=
'Invisible'
note
=
None
new_state
=
PresenceState
(
status
,
note
)
settings
.
presence
.
current_state
=
new_state
if
new_state
.
note
:
try
:
state
=
next
(
state
for
state
in
settings
.
presence
.
state_history
if
state
==
new_state
)
except
StopIteration
:
settings
.
presence
.
state_history
=
[
new_state
]
+
settings
.
presence
.
state_history
else
:
settings
.
presence
.
state_history
=
[
new_state
]
+
[
state
for
state
in
settings
.
presence
.
state_history
if
state
!=
new_state
]
settings
.
save
()
def
_NH_XCAPManagerDidDiscoverServerCapabilities
(
self
,
notification
):
account
=
notification
.
sender
.
account
if
account
.
enabled
and
account
.
presence
.
enabled
:
self
.
publish
(
account
)
def
_NH_XCAPManagerDidReloadData
(
self
,
notification
):
account
=
notification
.
sender
.
account
settings
=
BlinkSettings
()
offline_status
=
notification
.
data
.
offline_status
status_icon
=
notification
.
data
.
status_icon
offline_note
=
None
if
offline_status
:
offline_pidf
=
offline_status
.
pidf
try
:
service
=
next
(
offline_pidf
.
services
)
note
=
next
(
iter
(
service
.
notes
))
except
StopIteration
:
pass
else
:
offline_note
=
unicode
(
note
)
settings
.
presence
.
offline_note
=
offline_note
settings
.
save
()
if
status_icon
:
icon_desc
=
IconDescriptor
(
notification
.
sender
.
status_icon
.
uri
,
notification
.
sender
.
status_icon
.
etag
)
icon_hash
=
hashlib
.
sha512
(
status_icon
.
data
)
.
hexdigest
()
if
settings
.
presence
.
icon
and
settings
.
presence
.
icon
.
etag
==
icon_hash
:
# Icon didn't change
pass
else
:
self
.
_save_icon
(
status_icon
.
data
,
icon_hash
)
if
icon_desc
!=
account
.
icon
:
account
.
icon
=
icon_desc
self
.
publish
(
account
)
else
:
# TODO: remove local icon?
pass
class
PresenceSubscriptionHandler
(
object
):
implements
(
IObserver
)
def
__init__
(
self
):
self
.
_pidf_map
=
{}
self
.
_winfo_map
=
{}
self
.
_winfo_timers
=
{}
def
start
(
self
):
notification_center
=
NotificationCenter
()
notification_center
.
add_observer
(
self
,
name
=
'SIPAccountWillActivate'
)
notification_center
.
add_observer
(
self
,
name
=
'SIPAccountWillDeactivate'
)
notification_center
.
add_observer
(
self
,
name
=
'SIPAccountGotPresenceState'
)
notification_center
.
add_observer
(
self
,
name
=
'SIPAccountGotPresenceWinfo'
)
def
stop
(
self
):
notification_center
=
NotificationCenter
()
notification_center
.
remove_observer
(
self
,
name
=
'SIPAccountWillActivate'
)
notification_center
.
remove_observer
(
self
,
name
=
'SIPAccountWillDeactivate'
)
notification_center
.
remove_observer
(
self
,
name
=
'SIPAccountGotPresenceState'
)
notification_center
.
remove_observer
(
self
,
name
=
'SIPAccountGotPresenceWinfo'
)
self
.
_pidf_map
.
clear
()
self
.
_winfo_map
.
clear
()
for
timer
in
self
.
_winfo_timers
.
values
():
if
timer
.
active
():
timer
.
cancel
()
self
.
_winfo_timers
.
clear
()
def
_download_icon
(
self
,
url
,
etag
):
headers
=
{
'If-None-Match'
:
etag
}
if
etag
else
{}
req
=
urllib2
.
Request
(
url
,
headers
=
headers
)
try
:
response
=
urllib2
.
urlopen
(
req
)
content
=
response
.
read
()
info
=
response
.
info
()
except
(
urllib2
.
HTTPError
,
urllib2
.
URLError
):
return
None
,
None
content_type
=
info
.
getheader
(
'content-type'
)
etag
=
info
.
getheader
(
'etag'
)
if
etag
.
startswith
(
'W/'
):
etag
=
etag
[
2
:]
etag
=
etag
.
replace
(
'
\"
'
,
''
)
if
content_type
==
prescontent
.
PresenceContentDocument
.
content_type
:
try
:
pres_content
=
prescontent
.
PresenceContentDocument
.
parse
(
content
)
content
=
base64
.
decodestring
(
pres_content
.
data
.
value
)
except
Exception
:
return
None
,
None
return
content
,
etag
@
run_in_green_thread
def
_process_presence_data
(
self
,
uris
=
None
):
addressbook_manager
=
addressbook
.
AddressbookManager
()
notification_center
=
NotificationCenter
()
current_pidf_map
=
{}
contact_pidf_map
=
{}
# If no URIs were provided, process all of them
if
not
uris
:
uris
=
list
(
chain
(
*
(
item
.
iterkeys
()
for
item
in
self
.
_pidf_map
.
itervalues
())))
for
uri
,
pidf_list
in
chain
(
*
(
x
.
iteritems
()
for
x
in
self
.
_pidf_map
.
itervalues
())):
current_pidf_map
.
setdefault
(
uri
,
[])
.
extend
(
pidf_list
)
for
uri
in
uris
:
pidf_list
=
current_pidf_map
.
get
(
uri
,
[])
for
contact
in
(
contact
for
contact
in
addressbook_manager
.
get_contacts
()
if
uri
in
(
sip_prefix_re
.
sub
(
''
,
contact_uri
.
uri
)
for
contact_uri
in
contact
.
uris
)):
contact_pidf_map
.
setdefault
(
contact
,
[])
.
extend
(
pidf_list
)
for
contact
,
pidf_list
in
contact_pidf_map
.
iteritems
():
if
not
pidf_list
:
state
=
note
=
icon_descriptor
=
icon_data
=
None
else
:
services
=
list
(
chain
(
*
(
list
(
pidf_doc
.
services
)
for
pidf_doc
in
pidf_list
)))
services
.
sort
(
key
=
lambda
obj
:
obj
.
timestamp
.
value
if
obj
.
timestamp
else
epoch
,
reverse
=
True
)
service
=
services
[
0
]
if
service
.
status
.
extended
:
state
=
unicode
(
service
.
status
.
extended
)
else
:
state
=
'available'
if
service
.
status
.
basic
==
'open'
else
'offline'
note
=
unicode
(
next
(
iter
(
service
.
notes
)))
if
service
.
notes
else
None
icon
=
unicode
(
service
.
icon
)
if
service
.
icon
else
None
icon_data
=
icon_descriptor
=
None
if
icon
and
icon
!=
unknown_icon
and
(
not
contact
.
icon
or
(
contact
.
icon
and
not
contact
.
icon
.
is_local
)):
if
'blink-icon'
in
icon
and
contact
.
icon
and
icon
==
contact
.
icon
.
url
:
# Fast path, icon hasn't changed
pass
else
:
icon_data
,
etag
=
self
.
_download_icon
(
icon
,
contact
.
icon
.
etag
if
contact
.
icon
else
None
)
if
icon_data
:
icon_descriptor
=
IconDescriptor
(
icon
,
etag
)
data
=
NotificationData
(
state
=
state
,
note
=
note
,
icon_descriptor
=
icon_descriptor
,
icon_data
=
icon_data
)
notification_center
.
post_notification
(
'AddressbookContactGotPresenceUpdate'
,
sender
=
contact
,
data
=
data
)
def
handle_notification
(
self
,
notification
):
handler
=
getattr
(
self
,
'_NH_
%
s'
%
notification
.
name
,
Null
)
handler
(
notification
)
def
_NH_CFGSettingsObjectDidChange
(
self
,
notification
):
account
=
notification
.
sender
if
'__id__'
in
notification
.
data
.
modified
:
old_id
=
notification
.
data
.
modified
[
'__id__'
]
.
old
self
.
_pidf_map
.
pop
(
old_id
,
None
)
self
.
_winfo_map
.
pop
(
old_id
,
None
)
self
.
_process_presence_data
()
return
if
set
([
'enabled'
,
'presence.enabled'
])
.
intersection
(
notification
.
data
.
modified
):
if
not
account
.
enabled
or
not
account
.
presence
.
enabled
:
self
.
_pidf_map
.
pop
(
account
.
id
,
None
)
self
.
_winfo_map
.
pop
(
account
.
id
,
None
)
self
.
_process_presence_data
()
def
_NH_SIPAccountWillActivate
(
self
,
notification
):
if
notification
.
sender
is
not
BonjourAccount
():
notification
.
center
.
add_observer
(
self
,
sender
=
notification
.
sender
,
name
=
'CFGSettingsObjectDidChange'
)
notification
.
center
.
add_observer
(
self
,
sender
=
notification
.
sender
,
name
=
'SIPAccountGotPresenceState'
)
notification
.
center
.
add_observer
(
self
,
sender
=
notification
.
sender
,
name
=
'SIPAccountGotPresenceWinfo'
)
def
_NH_SIPAccountWillDeactivate
(
self
,
notification
):
if
notification
.
sender
is
not
BonjourAccount
():
notification
.
center
.
remove_observer
(
self
,
sender
=
notification
.
sender
,
name
=
'CFGSettingsObjectDidChange'
)
notification
.
center
.
remove_observer
(
self
,
sender
=
notification
.
sender
,
name
=
'SIPAccountGotPresenceState'
)
notification
.
center
.
remove_observer
(
self
,
sender
=
notification
.
sender
,
name
=
'SIPAccountGotPresenceWinfo'
)
def
_NH_SIPAccountGotPresenceState
(
self
,
notification
):
account
=
notification
.
sender
new_pidf_map
=
dict
((
sip_prefix_re
.
sub
(
''
,
uri
),
resource
.
pidf_list
)
for
uri
,
resource
in
notification
.
data
.
resource_map
.
iteritems
())
if
notification
.
data
.
full_state
:
self
.
_pidf_map
.
setdefault
(
account
.
id
,
{})
.
clear
()
self
.
_pidf_map
[
account
.
id
]
.
update
(
new_pidf_map
)
self
.
_process_presence_data
(
new_pidf_map
.
keys
())
def
_NH_SIPAccountGotPresenceWinfo
(
self
,
notification
):
addressbook_manager
=
addressbook
.
AddressbookManager
()
account
=
notification
.
sender
watcher_list
=
notification
.
data
.
watcher_list
if
notification
.
data
.
state
==
'full'
:
self
.
_winfo_map
.
setdefault
(
account
.
id
,
{})
.
clear
()
for
watcher
in
watcher_list
:
uri
=
sip_prefix_re
.
sub
(
''
,
watcher
.
sipuri
)
if
uri
!=
account
.
id
:
# Skip own URI, XCAP may be down and policy may not be inplace yet
self
.
_winfo_map
[
account
.
id
]
.
setdefault
(
watcher
.
status
,
set
())
.
add
(
uri
)
pending_watchers
=
self
.
_winfo_map
[
account
.
id
]
.
setdefault
(
'pending'
,
set
())
|
self
.
_winfo_map
[
account
.
id
]
.
setdefault
(
'waiting'
,
set
())
for
uri
in
pending_watchers
:
# check if there is a policy
try
:
next
(
policy
for
policy
in
addressbook_manager
.
get_policies
()
if
policy
.
uri
==
uri
and
policy
.
presence
.
policy
!=
'default'
)
except
StopIteration
:
# check if there is a contact
try
:
next
(
contact
for
contact
in
addressbook_manager
.
get_contacts
()
if
contact
.
presence
.
policy
!=
'default'
and
uri
in
(
addr
.
uri
for
addr
in
contact
.
uris
))
except
StopIteration
:
# TODO: add display name -Saul
if
uri
not
in
self
.
_winfo_timers
:
self
.
_winfo_timers
[
uri
]
=
reactor
.
callLater
(
600
,
self
.
_winfo_timers
.
pop
,
uri
,
None
)
notification
.
center
.
post_notification
(
'SIPAccountGotPendingWatcher'
,
sender
=
account
,
data
=
NotificationData
(
uri
=
uri
,
display_name
=
None
,
event
=
'presence'
))
class
PresenceManager
(
object
):
def
__init__
(
self
):
self
.
publication_handler
=
PresencePublicationHandler
()
self
.
subscription_handler
=
PresenceSubscriptionHandler
()
def
start
(
self
):
self
.
publication_handler
.
start
()
self
.
subscription_handler
.
start
()
def
stop
(
self
):
self
.
publication_handler
.
stop
()
self
.
subscription_handler
.
stop
()
ui_class
,
base_class
=
uic
.
loadUiType
(
Resources
.
get
(
'pending_watcher.ui'
))
class
PendingWatcherDialog
(
base_class
,
ui_class
):
def
__init__
(
self
,
account
,
uri
,
display_name
,
parent
=
None
):
super
(
PendingWatcherDialog
,
self
)
.
__init__
(
parent
)
self
.
setWindowFlags
(
Qt
.
WindowStaysOnTopHint
)
self
.
setAttribute
(
Qt
.
WA_DeleteOnClose
)
with
Resources
.
directory
:
self
.
setupUi
(
self
)
addressbook_manager
=
addressbook
.
AddressbookManager
()
try
:
self
.
contact
=
next
(
contact
for
contact
in
addressbook_manager
.
get_contacts
()
if
uri
in
(
addr
.
uri
for
addr
in
contact
.
uris
))
except
StopIteration
:
self
.
contact
=
None
else
:
display_name
=
self
.
contact
.
name
icon
=
IconManager
()
.
get
(
self
.
contact
.
id
)
if
icon
:
self
.
user_icon
.
setPixmap
(
icon
.
pixmap
(
32
))
self
.
account_label
.
setText
(
u'For account
%
s'
%
account
.
id
)
self
.
name_label
.
setText
(
display_name
or
uri
)
self
.
uri_label
.
setText
(
uri
)
font
=
self
.
name_label
.
font
()
font
.
setPointSizeF
(
self
.
uri_label
.
fontInfo
()
.
pointSizeF
()
+
3
)
font
.
setFamily
(
"Sans Serif"
)
self
.
name_label
.
setFont
(
font
)
self
.
accept_button
.
released
.
connect
(
self
.
_accept_watcher
)
self
.
block_button
.
released
.
connect
(
self
.
_block_watcher
)
self
.
position
=
None
self
.
timer
=
QTimer
()
self
.
timer
.
timeout
.
connect
(
self
.
_SH_TimerFired
)
self
.
timer
.
start
(
60000
)
def
_SH_TimerFired
(
self
):
self
.
timer
.
stop
()
self
.
close
()
def
_accept_watcher
(
self
):
self
.
timer
.
stop
()
if
not
self
.
contact
:
self
.
contact
=
addressbook
.
Contact
()
self
.
contact
.
name
=
self
.
name_label
.
text
()
self
.
contact
.
uris
=
[
addressbook
.
ContactURI
(
uri
=
self
.
uri_label
.
text
())]
self
.
contact
.
presence
.
policy
=
'allow'
self
.
contact
.
presence
.
subscribe
=
True
self
.
contact
.
save
()
def
_block_watcher
(
self
):
self
.
timer
.
stop
()
policy
=
addressbook
.
Policy
()
policy
.
uri
=
self
.
uri_label
.
text
()
policy
.
name
=
self
.
name_label
.
text
()
policy
.
presence
.
policy
=
'block'
policy
.
save
()
def
show
(
self
,
position
=
1
):
from
blink
import
Blink
blink
=
Blink
()
screen_geometry
=
blink
.
desktop
()
.
screenGeometry
(
self
)
available_geometry
=
blink
.
desktop
()
.
availableGeometry
(
self
)
main_window_geometry
=
blink
.
main_window
.
geometry
()
main_window_framegeometry
=
blink
.
main_window
.
frameGeometry
()
horizontal_decorations
=
main_window_framegeometry
.
width
()
-
main_window_geometry
.
width
()
vertical_decorations
=
main_window_framegeometry
.
height
()
-
main_window_geometry
.
height
()
width
=
limit
(
self
.
sizeHint
()
.
width
(),
min
=
self
.
minimumSize
()
.
width
(),
max
=
min
(
self
.
maximumSize
()
.
width
(),
available_geometry
.
width
()
-
horizontal_decorations
))
height
=
limit
(
self
.
sizeHint
()
.
height
(),
min
=
self
.
minimumSize
()
.
height
(),
max
=
min
(
self
.
maximumSize
()
.
height
(),
available_geometry
.
height
()
-
vertical_decorations
))
total_width
=
width
+
horizontal_decorations
total_height
=
height
+
vertical_decorations
x
=
limit
(
screen_geometry
.
center
()
.
x
()
-
total_width
/
2
,
min
=
available_geometry
.
left
(),
max
=
available_geometry
.
right
()
-
total_width
)
if
position
is
None
:
y
=
-
1
elif
position
%
2
==
0
:
y
=
screen_geometry
.
center
()
.
y
()
+
(
position
-
1
)
*
total_height
/
2
else
:
y
=
screen_geometry
.
center
()
.
y
()
-
position
*
total_height
/
2
if
available_geometry
.
top
()
<=
y
<=
available_geometry
.
bottom
()
-
total_height
:
self
.
setGeometry
(
x
,
y
,
width
,
height
)
else
:
self
.
resize
(
width
,
height
)
self
.
position
=
position
super
(
PendingWatcherDialog
,
self
)
.
show
()
del
ui_class
,
base_class
resources/pending_watcher.ui
0 → 100644
View file @
9c840d9b
<?xml version="1.0" encoding="UTF-8"?>
<ui
version=
"4.0"
>
<class>
Dialog
</class>
<widget
class=
"QDialog"
name=
"Dialog"
>
<property
name=
"geometry"
>
<rect>
<x>
0
</x>
<y>
0
</y>
<width>
480
</width>
<height>
170
</height>
</rect>
</property>
<property
name=
"minimumSize"
>
<size>
<width>
480
</width>
<height>
170
</height>
</size>
</property>
<property
name=
"maximumSize"
>
<size>
<width>
16777215
</width>
<height>
170
</height>
</size>
</property>
<property
name=
"windowTitle"
>
<string>
Availability subscription request
</string>
</property>
<property
name=
"windowIcon"
>
<iconset>
<normaloff>
icons/blink48.png
</normaloff>
icons/blink48.png
</iconset>
</property>
<layout
class=
"QVBoxLayout"
name=
"dialog_layout"
>
<property
name=
"spacing"
>
<number>
10
</number>
</property>
<item>
<widget
class=
"QFrame"
name=
"frame"
>
<property
name=
"styleSheet"
>
<string>
QFrame#frame {
background-color: #f8f8f8;
border-color: #545454;
border-radius: 4px;
border-width: 2px;
border-style: solid;
}
</string>
</property>
<property
name=
"frameShape"
>
<enum>
QFrame::StyledPanel
</enum>
</property>
<property
name=
"frameShadow"
>
<enum>
QFrame::Sunken
</enum>
</property>
<layout
class=
"QGridLayout"
name=
"frame_layout"
>
<property
name=
"topMargin"
>
<number>
7
</number>
</property>
<property
name=
"bottomMargin"
>
<number>
7
</number>
</property>
<property
name=
"verticalSpacing"
>
<number>
0
</number>
</property>
<item
row=
"0"
column=
"0"
rowspan=
"2"
>
<layout
class=
"QVBoxLayout"
name=
"user_icon_layout"
>
<property
name=
"spacing"
>
<number>
0
</number>
</property>
<item>
<widget
class=
"QLabel"
name=
"user_icon"
>
<property
name=
"minimumSize"
>
<size>
<width>
36
</width>
<height>
36
</height>
</size>
</property>
<property
name=
"maximumSize"
>
<size>
<width>
36
</width>
<height>
36
</height>
</size>
</property>
<property
name=
"pixmap"
>
<pixmap>
icons/default-avatar.png
</pixmap>
</property>
<property
name=
"alignment"
>
<set>
Qt::AlignCenter
</set>
</property>
</widget>
</item>
<item>
<spacer
name=
"user_icon_spacer"
>
<property
name=
"orientation"
>
<enum>
Qt::Vertical
</enum>
</property>
<property
name=
"sizeType"
>
<enum>
QSizePolicy::Minimum
</enum>
</property>
<property
name=
"sizeHint"
stdset=
"0"
>
<size>
<width>
0
</width>
<height>
0
</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item
row=
"0"
column=
"1"
>
<widget
class=
"QLabel"
name=
"name_label"
>
<property
name=
"sizePolicy"
>
<sizepolicy
hsizetype=
"Expanding"
vsizetype=
"Fixed"
>
<horstretch>
0
</horstretch>
<verstretch>
0
</verstretch>
</sizepolicy>
</property>
<property
name=
"minimumSize"
>
<size>
<width>
0
</width>
<height>
26
</height>
</size>
</property>
<property
name=
"font"
>
<font>
<family>
Sans Serif
</family>
<pointsize>
12
</pointsize>
<weight>
75
</weight>
<bold>
true
</bold>
</font>
</property>
<property
name=
"text"
>
<string>
Watcher name
</string>
</property>
<property
name=
"alignment"
>
<set>
Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
</set>
</property>
</widget>
</item>
<item
row=
"1"
column=
"1"
>
<widget
class=
"QLabel"
name=
"uri_label"
>
<property
name=
"sizePolicy"
>
<sizepolicy
hsizetype=
"Expanding"
vsizetype=
"Fixed"
>
<horstretch>
0
</horstretch>
<verstretch>
0
</verstretch>
</sizepolicy>
</property>
<property
name=
"palette"
>
<palette>
<active>
<colorrole
role=
"WindowText"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
68
</red>
<green>
68
</green>
<blue>
68
</blue>
</color>
</brush>
</colorrole>
<colorrole
role=
"Text"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
68
</red>
<green>
68
</green>
<blue>
68
</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole
role=
"WindowText"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
68
</red>
<green>
68
</green>
<blue>
68
</blue>
</color>
</brush>
</colorrole>
<colorrole
role=
"Text"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
68
</red>
<green>
68
</green>
<blue>
68
</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole
role=
"WindowText"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
118
</red>
<green>
118
</green>
<blue>
117
</blue>
</color>
</brush>
</colorrole>
<colorrole
role=
"Text"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
118
</red>
<green>
118
</green>
<blue>
117
</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property
name=
"text"
>
<string>
Watcher URI
</string>
</property>
<property
name=
"indent"
>
<number>
1
</number>
</property>
</widget>
</item>
<item
row=
"3"
column=
"0"
colspan=
"3"
>
<widget
class=
"QLabel"
name=
"description_label"
>
<property
name=
"palette"
>
<palette>
<active>
<colorrole
role=
"WindowText"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
68
</red>
<green>
68
</green>
<blue>
68
</blue>
</color>
</brush>
</colorrole>
<colorrole
role=
"Text"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
68
</red>
<green>
68
</green>
<blue>
68
</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole
role=
"WindowText"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
68
</red>
<green>
68
</green>
<blue>
68
</blue>
</color>
</brush>
</colorrole>
<colorrole
role=
"Text"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
68
</red>
<green>
68
</green>
<blue>
68
</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole
role=
"WindowText"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
118
</red>
<green>
118
</green>
<blue>
117
</blue>
</color>
</brush>
</colorrole>
<colorrole
role=
"Text"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
118
</red>
<green>
118
</green>
<blue>
117
</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property
name=
"text"
>
<string
extracomment=
"is offering to share his desktop"
>
Wants to subscribe to your availability information
</string>
</property>
<property
name=
"indent"
>
<number>
3
</number>
</property>
</widget>
</item>
<item
row=
"2"
column=
"0"
colspan=
"3"
>
<spacer
name=
"frame_spacer"
>
<property
name=
"orientation"
>
<enum>
Qt::Vertical
</enum>
</property>
<property
name=
"sizeHint"
stdset=
"0"
>
<size>
<width>
407
</width>
<height>
22
</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget
class=
"QLabel"
name=
"account_label"
>
<property
name=
"palette"
>
<palette>
<active>
<colorrole
role=
"WindowText"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
68
</red>
<green>
68
</green>
<blue>
68
</blue>
</color>
</brush>
</colorrole>
<colorrole
role=
"Text"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
68
</red>
<green>
68
</green>
<blue>
68
</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole
role=
"WindowText"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
68
</red>
<green>
68
</green>
<blue>
68
</blue>
</color>
</brush>
</colorrole>
<colorrole
role=
"Text"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
68
</red>
<green>
68
</green>
<blue>
68
</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole
role=
"WindowText"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
118
</red>
<green>
118
</green>
<blue>
117
</blue>
</color>
</brush>
</colorrole>
<colorrole
role=
"Text"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
118
</red>
<green>
118
</green>
<blue>
117
</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property
name=
"text"
>
<string>
For account user@domain.com
</string>
</property>
<property
name=
"indent"
>
<number>
3
</number>
</property>
</widget>
</item>
<item>
<layout
class=
"QHBoxLayout"
name=
"button_layout"
>
<property
name=
"spacing"
>
<number>
5
</number>
</property>
<item>
<spacer
name=
"button_spacer"
>
<property
name=
"orientation"
>
<enum>
Qt::Horizontal
</enum>
</property>
<property
name=
"sizeHint"
stdset=
"0"
>
<size>
<width>
40
</width>
<height>
20
</height>
</size>
</property>
</spacer>
</item>
<item>
<widget
class=
"QPushButton"
name=
"block_button"
>
<property
name=
"minimumSize"
>
<size>
<width>
85
</width>
<height>
24
</height>
</size>
</property>
<property
name=
"maximumSize"
>
<size>
<width>
16777215
</width>
<height>
24
</height>
</size>
</property>
<property
name=
"toolTip"
>
<string>
Refuse the call but leave other devices ringing
</string>
</property>
<property
name=
"text"
>
<string>
Block
</string>
</property>
</widget>
</item>
<item>
<widget
class=
"QPushButton"
name=
"accept_button"
>
<property
name=
"minimumSize"
>
<size>
<width>
85
</width>
<height>
24
</height>
</size>
</property>
<property
name=
"maximumSize"
>
<size>
<width>
16777215
</width>
<height>
24
</height>
</size>
</property>
<property
name=
"text"
>
<string>
Accept
</string>
</property>
<property
name=
"default"
>
<bool>
true
</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
<tabstop>
accept_button
</tabstop>
<tabstop>
block_button
</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>
accept_button
</sender>
<signal>
clicked()
</signal>
<receiver>
Dialog
</receiver>
<slot>
accept()
</slot>
<hints>
<hint
type=
"sourcelabel"
>
<x>
345
</x>
<y>
117
</y>
</hint>
<hint
type=
"destinationlabel"
>
<x>
196
</x>
<y>
67
</y>
</hint>
</hints>
</connection>
<connection>
<sender>
block_button
</sender>
<signal>
clicked()
</signal>
<receiver>
Dialog
</receiver>
<slot>
reject()
</slot>
<hints>
<hint
type=
"sourcelabel"
>
<x>
138
</x>
<y>
117
</y>
</hint>
<hint
type=
"destinationlabel"
>
<x>
196
</x>
<y>
67
</y>
</hint>
</hints>
</connection>
</connections>
</ui>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment