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
66bbc3f6
Commit
66bbc3f6
authored
Jun 17, 2013
by
Dan Pascu
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implemented multiple URIs per contact
parent
b2b50439
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
1845 additions
and
599 deletions
+1845
-599
addressbook.py
blink/configuration/addressbook.py
+3
-3
datatypes.py
blink/configuration/datatypes.py
+16
-26
contacts.py
blink/contacts.py
+1440
-353
mainwindow.py
blink/mainwindow.py
+22
-35
presence.py
blink/presence.py
+2
-2
sessions.py
blink/sessions.py
+34
-3
buttons.py
blink/widgets/buttons.py
+2
-3
labels.py
blink/widgets/labels.py
+124
-15
lineedit.py
blink/widgets/lineedit.py
+6
-0
blink.ui
resources/blink.ui
+11
-5
contact.ui
resources/contact.ui
+30
-6
contact_editor.ui
resources/contact_editor.ui
+155
-148
No files found.
blink/configuration/addressbook.py
View file @
66bbc3f6
...
...
@@ -15,10 +15,10 @@ SharedSetting.set_namespace('ag-projects:blink')
class
ContactExtension
(
ContactExtension
):
#auto_answer = SharedSetting(type=bool, default=False)
default_uri
=
SharedSetting
(
type
=
str
,
nillable
=
True
,
default
=
None
)
preferred_media
=
SharedSetting
(
type
=
str
,
default
=
'audio'
)
icon
=
Setting
(
type
=
IconDescriptor
,
nillable
=
True
,
default
=
None
)
alternate_icon
=
Setting
(
type
=
IconDescriptor
,
nillable
=
True
,
default
=
None
)
preferred_media
=
SharedSetting
(
type
=
str
,
default
=
'audio'
)
#auto_answer = SharedSetting(type=bool, default=False)
class
GroupExtension
(
GroupExtension
):
...
...
blink/configuration/datatypes.py
View file @
66bbc3f6
...
...
@@ -3,12 +3,7 @@
"""Definitions of datatypes for use in settings extensions."""
__all__
=
[
'ApplicationDataPath'
,
'DefaultPath'
,
'SoundFile'
,
'CustomSoundFile'
,
'HTTPURL'
,
'AuthorizationToken'
,
'InvalidToken'
,
'IconDescriptor'
,
'PresenceState'
,
'PresenceStateList'
]
__all__
=
[
'ApplicationDataPath'
,
'DefaultPath'
,
'SoundFile'
,
'CustomSoundFile'
,
'HTTPURL'
,
'AuthorizationToken'
,
'InvalidToken'
,
'IconDescriptor'
,
'PresenceState'
,
'PresenceStateList'
]
import
os
import
re
...
...
@@ -141,16 +136,28 @@ class AuthorizationToken(str):
InvalidToken
=
AuthorizationToken
()
# a valid token is never empty
class
ParsedURL
(
unicode
):
fragment
=
property
(
lambda
self
:
self
.
__parsed__
.
fragment
)
netloc
=
property
(
lambda
self
:
self
.
__parsed__
.
netloc
)
params
=
property
(
lambda
self
:
self
.
__parsed__
.
params
)
path
=
property
(
lambda
self
:
self
.
__parsed__
.
path
)
query
=
property
(
lambda
self
:
self
.
__parsed__
.
query
)
scheme
=
property
(
lambda
self
:
self
.
__parsed__
.
scheme
)
def
__init__
(
self
,
value
):
self
.
__parsed__
=
urlparse
(
self
)
class
IconDescriptor
(
object
):
def
__init__
(
self
,
url
,
etag
=
None
):
self
.
url
=
url
self
.
url
=
ParsedURL
(
url
)
self
.
etag
=
etag
def
__getstate__
(
self
):
if
self
.
etag
is
None
:
return
unicode
(
self
.
url
)
else
:
return
u'
%
s,
%
s'
%
(
self
.
__dict__
[
'url'
]
,
self
.
etag
)
return
u'
%
s,
%
s'
%
(
self
.
url
,
self
.
etag
)
def
__setstate__
(
self
,
state
):
try
:
...
...
@@ -172,26 +179,9 @@ class IconDescriptor(object):
def
__repr__
(
self
):
return
'
%
s(
%
r,
%
r)'
%
(
self
.
__class__
.
__name__
,
self
.
url
,
self
.
etag
)
def
_get_url
(
self
):
url
=
self
.
__dict__
[
'url'
]
file_scheme
=
'file://'
if
url
.
startswith
(
file_scheme
):
url
=
file_scheme
+
ApplicationData
.
get
(
url
[
len
(
file_scheme
):])
return
url
def
_set_url
(
self
,
url
):
file_scheme
=
'file://'
if
url
.
startswith
(
file_scheme
):
filename
=
os
.
path
.
normpath
(
url
[
len
(
file_scheme
):])
if
filename
.
startswith
(
ApplicationData
.
directory
+
os
.
path
.
sep
):
filename
=
filename
[
len
(
ApplicationData
.
directory
+
os
.
path
.
sep
):]
url
=
file_scheme
+
filename
self
.
__dict__
[
'url'
]
=
url
url
=
property
(
_get_url
,
_set_url
)
del
_get_url
,
_set_url
@
property
def
is_local
(
self
):
return
self
.
__dict__
[
'url'
]
.
startswith
(
'file://
'
)
return
self
.
url
.
scheme
in
(
''
,
'file
'
)
class
PresenceState
(
object
):
...
...
blink/contacts.py
View file @
66bbc3f6
This source diff could not be displayed because it is too large. You can
view the blob
instead.
blink/mainwindow.py
View file @
66bbc3f6
...
...
@@ -9,7 +9,7 @@ import os
from
functools
import
partial
from
PyQt4
import
uic
from
PyQt4.QtCore
import
QUrl
from
PyQt4.QtCore
import
Q
t
,
Q
Url
from
PyQt4.QtGui
import
QAction
,
QActionGroup
,
QDesktopServices
,
QShortcut
from
PyQt4.QtGui
import
QFileDialog
,
QIcon
,
QStyle
,
QStyleOptionComboBox
,
QStyleOptionFrameV2
...
...
@@ -23,7 +23,7 @@ from sipsimple.configuration.settings import SIPSimpleSettings
from
blink.aboutpanel
import
AboutPanel
from
blink.accounts
import
AccountModel
,
ActiveAccountModel
,
ServerToolsAccountModel
,
ServerToolsWindow
from
blink.contacts
import
BonjourNeighbour
,
Contact
,
Group
,
ContactEditorDialog
,
ContactModel
,
ContactSearchModel
,
GoogleContactsDialog
from
blink.contacts
import
BonjourNeighbour
,
Contact
,
ContactEditorDialog
,
ContactModel
,
ContactSearchModel
,
GoogleContactsDialog
from
blink.history
import
HistoryManager
from
blink.preferences
import
PreferencesWindow
from
blink.sessions
import
ConferenceDialog
,
SessionManager
,
SessionModel
...
...
@@ -116,7 +116,6 @@ class MainWindow(base_class, ui_class):
self
.
conference_button
.
makeConference
.
connect
(
self
.
_SH_MakeConference
)
self
.
conference_button
.
breakConference
.
connect
(
self
.
_SH_BreakConference
)
self
.
contact_list
.
doubleClicked
.
connect
(
self
.
_SH_ContactDoubleClicked
)
# activated is emitted on single click
self
.
contact_list
.
selectionModel
()
.
selectionChanged
.
connect
(
self
.
_SH_ContactListSelectionChanged
)
self
.
contact_model
.
itemsAdded
.
connect
(
self
.
_SH_ContactModelAddedItems
)
self
.
contact_model
.
itemsRemoved
.
connect
(
self
.
_SH_ContactModelRemovedItems
)
...
...
@@ -134,7 +133,6 @@ class MainWindow(base_class, ui_class):
self
.
search_box
.
shortcut
.
activated
.
connect
(
self
.
search_box
.
setFocus
)
self
.
search_list
.
selectionModel
()
.
selectionChanged
.
connect
(
self
.
_SH_SearchListSelectionChanged
)
self
.
search_list
.
doubleClicked
.
connect
(
self
.
_SH_ContactDoubleClicked
)
# activated is emitted on single click
self
.
server_tools_account_model
.
rowsInserted
.
connect
(
self
.
_SH_ServerToolsAccountModelChanged
)
self
.
server_tools_account_model
.
rowsRemoved
.
connect
(
self
.
_SH_ServerToolsAccountModelChanged
)
...
...
@@ -386,11 +384,11 @@ class MainWindow(base_class, ui_class):
if
filename
is
not
None
:
icon
=
icon_manager
.
store_file
(
'myicon'
,
filename
)
try
:
hash
=
hashlib
.
sha512
(
open
(
icon
.
filename
,
'r'
)
.
read
())
.
hexdigest
()
hash
=
hashlib
.
sha512
(
open
(
icon
.
filename
)
.
read
())
.
hexdigest
()
except
Exception
:
settings
.
presence
.
icon
=
None
else
:
settings
.
presence
.
icon
=
IconDescriptor
(
'file://'
+
icon
.
filename
,
hash
)
settings
.
presence
.
icon
=
IconDescriptor
(
'file://'
+
icon
.
filename
,
hash
)
else
:
icon_manager
.
remove
(
'myicon'
)
icon
=
None
...
...
@@ -405,41 +403,28 @@ class MainWindow(base_class, ui_class):
self
.
account_state
.
setState
(
self
.
account_state
.
state
,
note
)
def
_SH_AddContactButtonClicked
(
self
,
clicked
):
model
=
self
.
contact_model
groups
=
set
()
for
index
in
self
.
contact_list
.
selectionModel
()
.
selectedIndexes
():
item
=
model
.
data
(
index
)
if
isinstance
(
item
,
Group
)
and
not
item
.
virtual
:
groups
.
add
(
item
)
elif
isinstance
(
item
,
Contact
)
and
not
item
.
group
.
virtual
:
groups
.
add
(
item
.
group
)
preferred_group
=
groups
.
pop
()
if
len
(
groups
)
==
1
else
None
self
.
contact_editor_dialog
.
open_for_add
(
self
.
search_box
.
text
(),
preferred_group
)
self
.
contact_editor_dialog
.
open_for_add
(
self
.
search_box
.
text
(),
None
)
def
_SH_AudioCallButtonClicked
(
self
):
list_view
=
self
.
contact_list
if
self
.
contacts_view
.
currentWidget
()
is
self
.
contact_list_panel
else
self
.
search_list
selected_indexes
=
list_view
.
selectionModel
()
.
selectedIndexes
()
contact
=
list_view
.
model
()
.
data
(
selected_indexes
[
0
])
if
selected_indexes
else
Null
address
=
contact
.
uri
or
self
.
search_box
.
text
()
name
=
contact
.
name
or
None
session_manager
=
SessionManager
()
session_manager
.
start_call
(
name
,
address
,
contact
=
contact
,
account
=
BonjourAccount
()
if
isinstance
(
contact
.
settings
,
BonjourNeighbour
)
else
None
)
if
list_view
.
detail_view
.
isVisible
():
list_view
.
detail_view
.
_AH_StartAudioCall
()
else
:
selected_indexes
=
list_view
.
selectionModel
()
.
selectedIndexes
()
contact
=
selected_indexes
[
0
]
.
data
(
Qt
.
UserRole
)
if
selected_indexes
else
Null
address
=
contact
.
uri
or
self
.
search_box
.
text
()
name
=
contact
.
name
or
None
session_manager
=
SessionManager
()
session_manager
.
start_call
(
name
,
address
,
contact
=
contact
,
account
=
BonjourAccount
()
if
isinstance
(
contact
.
settings
,
BonjourNeighbour
)
else
None
)
def
_SH_BreakConference
(
self
):
active_session
=
self
.
session_
model
.
data
(
self
.
session_list
.
selectionModel
()
.
selectedIndexes
()[
0
]
)
active_session
=
self
.
session_
list
.
selectionModel
()
.
selectedIndexes
()[
0
]
.
data
(
)
self
.
session_model
.
breakConference
(
active_session
.
conference
)
def
_SH_ContactDoubleClicked
(
self
,
index
):
contact
=
index
.
model
()
.
data
(
index
)
if
not
isinstance
(
contact
,
Contact
):
return
session_manager
=
SessionManager
()
session_manager
.
start_call
(
contact
.
name
,
contact
.
uri
,
contact
=
contact
,
account
=
BonjourAccount
()
if
isinstance
(
contact
.
settings
,
BonjourNeighbour
)
else
None
)
def
_SH_ContactListSelectionChanged
(
self
,
selected
,
deselected
):
account_manager
=
AccountManager
()
selected_items
=
self
.
contact_list
.
selectionModel
()
.
selectedIndexes
()
self
.
enable_call_buttons
(
account_manager
.
default_account
is
not
None
and
len
(
selected_items
)
==
1
and
isinstance
(
sel
f
.
contact_model
.
data
(
selected_items
[
0
]
),
Contact
))
self
.
enable_call_buttons
(
account_manager
.
default_account
is
not
None
and
len
(
selected_items
)
==
1
and
isinstance
(
sel
ected_items
[
0
]
.
data
(
Qt
.
UserRole
),
Contact
))
def
_SH_ContactModelAddedItems
(
self
,
items
):
if
not
self
.
search_box
.
text
():
...
...
@@ -516,7 +501,9 @@ class MainWindow(base_class, ui_class):
else
:
self
.
contacts_view
.
setCurrentWidget
(
self
.
contact_list_panel
)
selected_items
=
self
.
contact_list
.
selectionModel
()
.
selectedIndexes
()
self
.
enable_call_buttons
(
account_manager
.
default_account
is
not
None
and
len
(
selected_items
)
==
1
and
type
(
self
.
contact_model
.
data
(
selected_items
[
0
]))
is
Contact
)
self
.
enable_call_buttons
(
account_manager
.
default_account
is
not
None
and
len
(
selected_items
)
==
1
and
type
(
selected_items
[
0
]
.
data
(
Qt
.
UserRole
))
is
Contact
)
self
.
search_list
.
detail_model
.
contact
=
None
self
.
search_list
.
detail_view
.
hide
()
def
_SH_SearchListSelectionChanged
(
self
,
selected
,
deselected
):
account_manager
=
AccountManager
()
...
...
@@ -532,7 +519,7 @@ class MainWindow(base_class, ui_class):
def
_SH_SessionListSelectionChanged
(
self
,
selected
,
deselected
):
selected_indexes
=
selected
.
indexes
()
active_session
=
sel
f
.
session_model
.
data
(
selected_indexes
[
0
]
)
if
selected_indexes
else
Null
active_session
=
sel
ected_indexes
[
0
]
.
data
(
)
if
selected_indexes
else
Null
if
active_session
.
conference
:
self
.
conference_button
.
setEnabled
(
True
)
self
.
conference_button
.
setChecked
(
True
)
...
...
@@ -550,7 +537,7 @@ class MainWindow(base_class, ui_class):
self
.
active_sessions_label
.
setVisible
(
any
(
active_sessions
))
self
.
hangup_all_button
.
setEnabled
(
any
(
active_sessions
))
selected_indexes
=
self
.
session_list
.
selectionModel
()
.
selectedIndexes
()
active_session
=
sel
f
.
session_model
.
data
(
selected_indexes
[
0
]
)
if
selected_indexes
else
Null
active_session
=
sel
ected_indexes
[
0
]
.
data
(
)
if
selected_indexes
else
Null
if
active_session
.
conference
:
self
.
conference_button
.
setEnabled
(
True
)
self
.
conference_button
.
setChecked
(
True
)
...
...
@@ -730,7 +717,7 @@ class MainWindow(base_class, ui_class):
self
.
enable_call_buttons
(
False
)
else
:
selected_items
=
self
.
contact_list
.
selectionModel
()
.
selectedIndexes
()
self
.
enable_call_buttons
(
len
(
selected_items
)
==
1
and
isinstance
(
sel
f
.
contact_model
.
data
(
selected_items
[
0
]
),
Contact
))
self
.
enable_call_buttons
(
len
(
selected_items
)
==
1
and
isinstance
(
sel
ected_items
[
0
]
.
data
(
Qt
.
UserRole
),
Contact
))
def
_NH_SIPAccountGotMessageSummary
(
self
,
notification
):
account
=
notification
.
sender
...
...
blink/presence.py
View file @
66bbc3f6
...
...
@@ -180,7 +180,7 @@ class PresencePublicationHandler(object):
icon
=
None
if
settings
.
presence
.
icon
:
try
:
data
=
open
(
settings
.
presence
.
icon
.
url
[
7
:],
'r'
)
.
read
()
# strip 'file://'
data
=
open
(
settings
.
presence
.
icon
.
url
.
path
)
.
read
()
except
Exception
:
pass
else
:
...
...
@@ -194,7 +194,7 @@ class PresencePublicationHandler(object):
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
)
settings
.
presence
.
icon
=
IconDescriptor
(
'file://'
+
icon
.
filename
,
icon_hash
)
else
:
settings
.
presence
.
icon
=
None
settings
.
save
()
...
...
blink/sessions.py
View file @
66bbc3f6
...
...
@@ -961,7 +961,7 @@ class SessionModel(QAbstractListModel):
structureChanged
=
pyqtSignal
()
# The MIME types we accept in drop operations, in the order they should be handled
accepted_mime_types
=
[
'application/x-blink-session-list'
,
'application/x-blink-contact-list'
]
accepted_mime_types
=
[
'application/x-blink-session-list'
,
'application/x-blink-contact-list'
,
'application/x-blink-contact-uri-list'
]
def
__init__
(
self
,
parent
=
None
):
super
(
SessionModel
,
self
)
.
__init__
(
parent
)
...
...
@@ -1103,13 +1103,30 @@ class SessionModel(QAbstractListModel):
def
_DH_ApplicationXBlinkContactList
(
self
,
mime_data
,
action
,
index
):
if
not
index
.
isValid
():
return
try
:
contacts
=
pickle
.
loads
(
str
(
mime_data
.
data
(
'application/x-blink-contact-list'
)))
except
Exception
:
return
session
=
self
.
sessions
[
index
.
row
()]
contacts
=
pickle
.
loads
(
str
(
mime_data
.
data
(
'application/x-blink-contact-list'
)))
session_manager
=
SessionManager
()
for
contact
in
contacts
:
session_manager
.
start_call
(
contact
.
name
,
contact
.
uri
,
contact
=
contact
,
conference_sibling
=
session
)
return
True
def
_DH_ApplicationXBlinkContactUriList
(
self
,
mime_data
,
action
,
index
):
if
not
index
.
isValid
():
return
try
:
contact_uris
=
pickle
.
loads
(
str
(
mime_data
.
data
(
'application/x-blink-contact-uri-list'
)))
except
Exception
:
return
session
=
self
.
sessions
[
index
.
row
()]
session_manager
=
SessionManager
()
for
contact_uri
in
contact_uris
:
contact
=
contact_uri
.
contact
session_manager
.
start_call
(
contact
.
name
,
contact_uri
.
uri
.
uri
,
contact
=
contact
,
conference_sibling
=
session
)
return
True
def
_add_session
(
self
,
session
):
position
=
len
(
self
.
sessions
)
self
.
beginInsertRows
(
QModelIndex
(),
position
,
position
)
...
...
@@ -1340,7 +1357,7 @@ class SessionListView(QListView):
def
dragEnterEvent
(
self
,
event
):
event_source
=
event
.
source
()
accepted_mime_types
=
set
(
self
.
model
()
.
accepted_mime_types
)
provided_mime_types
=
set
(
str
(
x
)
for
x
in
event
.
mimeData
()
.
formats
())
provided_mime_types
=
set
(
event
.
mimeData
()
.
formats
())
acceptable_mime_types
=
accepted_mime_types
&
provided_mime_types
if
not
acceptable_mime_types
:
event
.
ignore
()
# no acceptable mime types found
...
...
@@ -1426,6 +1443,20 @@ class SessionListView(QListView):
else
:
session
.
widget
.
drop_indicator
=
True
def
_DH_ApplicationXBlinkContactUriList
(
self
,
event
,
index
,
rect
,
session
):
model
=
self
.
model
()
if
not
index
.
isValid
():
rect
=
self
.
viewport
()
.
rect
()
rect
.
setTop
(
self
.
visualRect
(
model
.
index
(
len
(
model
.
sessions
)
-
1
))
.
bottom
())
event
.
ignore
(
rect
)
else
:
event
.
accept
(
rect
)
if
session
.
conference
is
not
None
:
for
sibling
in
session
.
conference
.
sessions
:
sibling
.
widget
.
drop_indicator
=
True
else
:
session
.
widget
.
drop_indicator
=
True
def
_SH_HangupShortcutActivated
(
self
):
session
=
self
.
model
()
.
data
(
self
.
selectedIndexes
()[
0
])
if
session
.
conference
is
None
:
...
...
blink/widgets/buttons.py
View file @
66bbc3f6
...
...
@@ -348,9 +348,7 @@ class SwitchViewButton(QPushButton):
self
.
setStyleSheet
(
style_sheet
)
def
dragEnterEvent
(
self
,
event
):
if
not
self
.
dnd_active
:
event
.
ignore
()
elif
event
.
mimeData
()
.
formats
()
==
[
'application/x-blink-contact-list'
]:
if
self
.
dnd_active
:
event
.
accept
()
self
.
_update_dnd
()
self
.
dnd_timer
.
start
()
...
...
@@ -738,3 +736,4 @@ class AccountState(StateButton):
menu
.
insertAction
(
actions
[
0
],
action
)
self
.
stateChanged
.
emit
()
blink/widgets/labels.py
View file @
66bbc3f6
...
...
@@ -6,46 +6,109 @@ __all__ = ['DurationLabel', 'IconSelector', 'LatencyLabel', 'PacketLossLabel', '
import
os
from
datetime
import
timedelta
from
PyQt4.QtCore
import
Qt
from
PyQt4.QtGui
import
Q
Brush
,
QColor
,
QFileDialog
,
QFontMetrics
,
QLabel
,
QLinearGradient
,
QPalette
,
QPainter
,
QPen
,
QPixmap
from
PyQt4.QtCore
import
Qt
,
QEvent
from
PyQt4.QtGui
import
Q
Action
,
QBrush
,
QColor
,
QFileDialog
,
QFontMetrics
,
QIcon
,
QLabel
,
QLinearGradient
,
QMenu
,
QPainter
,
QPalette
,
QPen
from
blink.resources
import
ApplicationData
,
Resources
from
application.python.types
import
MarkerType
from
blink.resources
import
IconManager
from
blink.widgets.color
import
ColorHelperMixin
from
blink.widgets.util
import
QtDynamicProperty
class
IconSelector
(
QLabel
):
default_icon
=
QtDynamicProperty
(
'default_icon'
,
unicode
)
default_icon
=
QtDynamicProperty
(
'default_icon'
,
QIcon
)
icon_size
=
QtDynamicProperty
(
'icon_size'
,
int
)
class
NotSelected
:
__metaclass__
=
MarkerType
def
__init__
(
self
,
parent
=
None
):
super
(
IconSelector
,
self
)
.
__init__
(
parent
)
self
.
setMinimumSize
(
36
,
36
)
self
.
filename
=
None
self
.
addAction
(
QAction
(
u'Select icon...'
,
self
,
triggered
=
self
.
_SH_ChangeIconActionTriggered
))
self
.
addAction
(
QAction
(
u'Use contact provided icon'
,
self
,
triggered
=
self
.
_SH_ClearIconActionTriggered
))
self
.
icon_size
=
48
self
.
default_icon
=
None
self
.
contact_icon
=
None
self
.
icon
=
None
self
.
filename
=
self
.
NotSelected
self
.
last_icon_directory
=
os
.
path
.
expanduser
(
'~'
)
def
_get_icon
(
self
):
return
self
.
__dict__
[
'icon'
]
def
_set_icon
(
self
,
icon
):
self
.
__dict__
[
'icon'
]
=
icon
icon
=
icon
or
self
.
default_icon
or
QIcon
()
self
.
setPixmap
(
icon
.
pixmap
(
self
.
icon_size
))
icon
=
property
(
_get_icon
,
_set_icon
)
del
_get_icon
,
_set_icon
def
_get_filename
(
self
):
return
self
.
__dict__
[
'filename'
]
def
_set_filename
(
self
,
filename
):
self
.
__dict__
[
'filename'
]
=
filename
filename
=
ApplicationData
.
get
(
filename
)
if
filename
else
Resources
.
get
(
self
.
default_icon
)
pixmap
=
QPixmap
()
if
pixmap
.
load
(
filename
)
:
self
.
setPixmap
(
pixmap
.
scaled
(
32
,
32
,
Qt
.
KeepAspectRatio
,
Qt
.
SmoothTransformation
))
if
filename
is
self
.
NotSelected
:
return
elif
filename
is
None
:
self
.
icon
=
self
.
contact_icon
else
:
self
.
setPixmap
(
pixmap
)
self
.
icon
=
QIcon
(
filename
)
self
.
last_icon_directory
=
os
.
path
.
dirname
(
filename
)
filename
=
property
(
_get_filename
,
_set_filename
)
del
_get_filename
,
_set_filename
def
init_with_contact
(
self
,
contact
):
if
contact
is
None
:
self
.
icon
=
self
.
contact_icon
=
None
else
:
icon_manager
=
IconManager
()
self
.
contact_icon
=
icon_manager
.
get
(
contact
.
id
)
self
.
icon
=
icon_manager
.
get
(
contact
.
id
+
'_alt'
)
or
self
.
contact_icon
if
contact
.
alternate_icon
is
not
None
:
self
.
last_icon_directory
=
os
.
path
.
dirname
(
contact
.
alternate_icon
.
url
.
path
)
self
.
filename
=
self
.
NotSelected
def
update_from_contact
(
self
,
contact
):
icon_manager
=
IconManager
()
if
self
.
icon
is
self
.
contact_icon
:
self
.
icon
=
self
.
contact_icon
=
icon_manager
.
get
(
contact
.
id
)
else
:
self
.
contact_icon
=
icon_manager
.
get
(
contact
.
id
)
def
event
(
self
,
event
):
if
event
.
type
()
==
QEvent
.
DynamicPropertyChange
and
event
.
propertyName
()
==
'icon_size'
:
self
.
setFixedSize
(
self
.
icon_size
+
12
,
self
.
icon_size
+
12
)
self
.
update
()
return
super
(
IconSelector
,
self
)
.
event
(
event
)
def
enterEvent
(
self
,
event
):
icon
=
self
.
icon
or
self
.
default_icon
or
QIcon
()
self
.
setPixmap
(
icon
.
pixmap
(
self
.
icon_size
,
mode
=
QIcon
.
Selected
))
super
(
IconSelector
,
self
)
.
enterEvent
(
event
)
def
leaveEvent
(
self
,
event
):
icon
=
self
.
icon
or
self
.
default_icon
or
QIcon
()
self
.
setPixmap
(
icon
.
pixmap
(
self
.
icon_size
,
mode
=
QIcon
.
Normal
))
super
(
IconSelector
,
self
)
.
leaveEvent
(
event
)
def
mouseReleaseEvent
(
self
,
event
):
if
event
.
button
()
==
Qt
.
LeftButton
and
self
.
rect
()
.
contains
(
event
.
pos
()):
filename
=
QFileDialog
.
getOpenFileName
(
self
,
u'Select Icon'
,
self
.
last_icon_directory
,
u"Images (*.png *.tiff *.jpg *.xmp *.svg)"
)
if
filename
:
self
.
last_icon_directory
=
os
.
path
.
dirname
(
filename
)
self
.
filename
=
filename
if
os
.
path
.
realpath
(
filename
)
!=
os
.
path
.
realpath
(
Resources
.
get
(
self
.
default_icon
))
else
None
menu
=
QMenu
(
self
)
menu
.
addActions
(
self
.
actions
())
menu
.
exec_
(
self
.
mapToGlobal
(
self
.
rect
()
.
translated
(
0
,
2
)
.
bottomLeft
()))
super
(
IconSelector
,
self
)
.
mouseReleaseEvent
(
event
)
def
_SH_ChangeIconActionTriggered
(
self
):
filename
=
QFileDialog
.
getOpenFileName
(
self
,
u'Select Icon'
,
self
.
last_icon_directory
,
u"Images (*.png *.tiff *.jpg *.xmp *.svg)"
)
if
filename
:
self
.
filename
=
filename
def
_SH_ClearIconActionTriggered
(
self
):
self
.
filename
=
None
class
StreamInfoLabel
(
QLabel
):
def
__init__
(
self
,
parent
=
None
):
...
...
@@ -198,3 +261,49 @@ class ElidedLabel(QLabel):
painter
.
drawText
(
self
.
rect
(),
Qt
.
TextSingleLine
|
int
(
self
.
alignment
()),
self
.
text
())
class
StateColor
(
QColor
):
@
property
def
stroke
(
self
):
return
self
.
darker
(
200
)
class
StateColorMapping
(
dict
):
def
__missing__
(
self
,
key
):
if
key
==
'offline'
:
return
self
.
setdefault
(
key
,
StateColor
(
'#d0d0d0'
))
elif
key
==
'available'
:
return
self
.
setdefault
(
key
,
StateColor
(
'#00ff00'
))
elif
key
==
'away'
:
return
self
.
setdefault
(
key
,
StateColor
(
'#ffff00'
))
elif
key
==
'busy'
:
return
self
.
setdefault
(
key
,
StateColor
(
'#ff0000'
))
else
:
return
StateColor
(
Qt
.
transparent
)
#StateColor('#d0d0d0')
class
ContactState
(
QLabel
,
ColorHelperMixin
):
state
=
QtDynamicProperty
(
'color'
,
unicode
)
def
__init__
(
self
,
parent
=
None
):
super
(
ContactState
,
self
)
.
__init__
(
parent
)
self
.
state_colors
=
StateColorMapping
()
self
.
state
=
None
def
event
(
self
,
event
):
if
event
.
type
()
==
QEvent
.
DynamicPropertyChange
and
event
.
propertyName
()
==
'state'
:
self
.
update
()
return
super
(
ContactState
,
self
)
.
event
(
event
)
def
paintEvent
(
self
,
event
):
color
=
self
.
state_colors
[
self
.
state
]
painter
=
QPainter
(
self
)
painter
.
setRenderHint
(
QPainter
.
Antialiasing
,
True
)
painter
.
setCompositionMode
(
QPainter
.
CompositionMode_SourceOver
)
gradient
=
QLinearGradient
(
0
,
0
,
self
.
width
(),
0
)
gradient
.
setColorAt
(
0.0
,
Qt
.
transparent
)
gradient
.
setColorAt
(
1.0
,
color
)
painter
.
setBrush
(
QBrush
(
gradient
))
gradient
.
setColorAt
(
1.0
,
color
.
stroke
)
painter
.
setPen
(
QPen
(
QBrush
(
gradient
),
1
))
painter
.
drawRoundedRect
(
-
4
,
0
,
self
.
width
()
+
4
,
self
.
height
(),
3.7
,
3.7
)
blink/widgets/lineedit.py
View file @
66bbc3f6
...
...
@@ -276,6 +276,12 @@ class SearchBox(LineEdit):
self
.
textChanged
.
connect
(
self
.
_SH_TextChanged
)
self
.
inactiveText
=
u"Search"
def
keyPressEvent
(
self
,
event
):
if
event
.
key
()
==
Qt
.
Key_Escape
:
self
.
clear
()
else
:
super
(
SearchBox
,
self
)
.
keyPressEvent
(
event
)
def
_SH_TextChanged
(
self
,
text
):
self
.
clear_button
.
setVisible
(
bool
(
text
))
...
...
resources/blink.ui
View file @
66bbc3f6
...
...
@@ -342,6 +342,9 @@
</property>
<item>
<widget
class=
"ContactListView"
name=
"contact_list"
>
<property
name=
"mouseTracking"
>
<bool>
true
</bool>
</property>
<property
name=
"horizontalScrollBarPolicy"
>
<enum>
Qt::ScrollBarAlwaysOff
</enum>
</property>
...
...
@@ -477,6 +480,9 @@
</property>
<item>
<widget
class=
"ContactSearchListView"
name=
"search_list"
>
<property
name=
"mouseTracking"
>
<bool>
true
</bool>
</property>
<property
name=
"dragEnabled"
>
<bool>
true
</bool>
</property>
...
...
@@ -1348,20 +1354,20 @@ padding: 2px;</string>
</customwidgets>
<tabstops>
<tabstop>
search_box
</tabstop>
<tabstop>
search_list
</tabstop>
<tabstop>
add_search_contact_button
</tabstop>
<tabstop>
back_to_contacts_button
</tabstop>
<tabstop>
contact_list
</tabstop>
<tabstop>
account_state
</tabstop>
<tabstop>
display_name
</tabstop>
<tabstop>
activity_note
</tabstop>
<tabstop>
identity
</tabstop>
<tabstop>
switch_view_button
</tabstop>
<tabstop>
contact_list
</tabstop>
<tabstop>
add_contact_button
</tabstop>
<tabstop>
audio_call_button
</tabstop>
<tabstop>
im_session_button
</tabstop>
<tabstop>
ds_session_button
</tabstop>
<tabstop>
silent_button
</tabstop>
<tabstop>
identity
</tabstop>
<tabstop>
add_search_contact_button
</tabstop>
<tabstop>
back_to_contacts_button
</tabstop>
<tabstop>
search_list
</tabstop>
<tabstop>
session_list
</tabstop>
<tabstop>
hangup_all_button
</tabstop>
<tabstop>
conference_button
</tabstop>
...
...
resources/contact.ui
View file @
66bbc3f6
...
...
@@ -77,9 +77,9 @@
<property
name=
"windowTitle"
>
<string>
Contact
</string>
</property>
<layout
class=
"QHBoxLayout"
name=
"
horizontalL
ayout"
>
<layout
class=
"QHBoxLayout"
name=
"
widget_l
ayout"
>
<property
name=
"spacing"
>
<number>
5
</number>
<number>
3
</number>
</property>
<property
name=
"leftMargin"
>
<number>
2
</number>
...
...
@@ -88,7 +88,7 @@
<number>
0
</number>
</property>
<property
name=
"rightMargin"
>
<number>
2
</number>
<number>
1
</number>
</property>
<property
name=
"bottomMargin"
>
<number>
0
</number>
...
...
@@ -117,9 +117,6 @@
<property
name=
"spacing"
>
<number>
0
</number>
</property>
<property
name=
"rightMargin"
>
<number>
8
</number>
</property>
<item>
<widget
class=
"ElidedLabel"
name=
"name_label"
>
<property
name=
"sizePolicy"
>
...
...
@@ -148,6 +145,28 @@
</item>
</layout>
</item>
<item>
<widget
class=
"ContactState"
name=
"state_label"
>
<property
name=
"minimumSize"
>
<size>
<width>
14
</width>
<height>
0
</height>
</size>
</property>
<property
name=
"maximumSize"
>
<size>
<width>
14
</width>
<height>
16777215
</height>
</size>
</property>
<property
name=
"text"
>
<string/>
</property>
<property
name=
"state"
stdset=
"0"
>
<string
notr=
"true"
>
unknown
</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
...
...
@@ -156,6 +175,11 @@
<extends>
QLabel
</extends>
<header>
blink.widgets.labels
</header>
</customwidget>
<customwidget>
<class>
ContactState
</class>
<extends>
QLabel
</extends>
<header>
blink.widgets.labels
</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
...
...
resources/contact_editor.ui
View file @
66bbc3f6
...
...
@@ -6,14 +6,14 @@
<rect>
<x>
0
</x>
<y>
0
</y>
<width>
4
35
</width>
<height>
3
25
</height>
<width>
4
60
</width>
<height>
3
50
</height>
</rect>
</property>
<property
name=
"minimumSize"
>
<size>
<width>
4
35
</width>
<height>
3
25
</height>
<width>
4
60
</width>
<height>
3
50
</height>
</size>
</property>
<property
name=
"windowTitle"
>
...
...
@@ -32,56 +32,68 @@
<property
name=
"bottomMargin"
>
<number>
10
</number>
</property>
<item
row=
"
6"
column=
"1"
colspan=
"2
"
>
<widget
class=
"
QCheckBox"
name=
"subscribe_presence
"
>
<property
name=
"
enabled
"
>
<
bool>
false
</bool
>
<item
row=
"
0"
column=
"1
"
>
<widget
class=
"
LineEdit"
name=
"name_editor
"
>
<property
name=
"
inactiveText"
stdset=
"0
"
>
<
string>
Contact Name
</string
>
</property>
<property
name=
"
text
"
>
<
string>
Subscribe To Presence
</string
>
<property
name=
"
widgetSpacing"
stdset=
"0
"
>
<
number>
0
</number
>
</property>
</widget>
</item>
<item
row=
"
8"
column=
"0"
colspan=
"3
"
>
<
spacer
name=
"grid_spacer
"
>
<property
name=
"
orientation
"
>
<
enum>
Qt::Vertical
</enum
>
<item
row=
"
2"
column=
"0
"
>
<
widget
class=
"QLabel"
name=
"addresses_label
"
>
<property
name=
"
text
"
>
<
string>
Addresses:
</string
>
</property>
<property
name=
"sizeHint"
stdset=
"0"
>
<size>
<width>
412
</width>
<height>
20
</height>
</size>
<property
name=
"alignment"
>
<set>
Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing
</set>
</property>
</
spacer
>
</
widget
>
</item>
<item
row=
"3"
column=
"0"
colspan=
"3"
>
<spacer
name=
"section_spacer"
>
<property
name=
"orientation"
>
<enum>
Qt::Vertical
</enum>
<item
row=
"0"
column=
"2"
rowspan=
"2"
>
<widget
class=
"IconSelector"
name=
"icon_selector"
>
<property
name=
"sizePolicy"
>
<sizepolicy
hsizetype=
"Minimum"
vsizetype=
"Minimum"
>
<horstretch>
0
</horstretch>
<verstretch>
0
</verstretch>
</sizepolicy>
</property>
<property
name=
"sizeType"
>
<enum>
QSizePolicy::Fixed
</enum>
<property
name=
"minimumSize"
>
<size>
<width>
60
</width>
<height>
60
</height>
</size>
</property>
<property
name=
"
sizeHint"
stdset=
"0
"
>
<property
name=
"
maximumSize
"
>
<size>
<width>
412
</width>
<height>
2
0
</height>
<width>
60
</width>
<height>
6
0
</height>
</size>
</property>
</spacer>
</item>
<item
row=
"1"
column=
"0"
>
<widget
class=
"QLabel"
name=
"sip_address_label"
>
<property
name=
"text"
>
<string>
SIP Address:
</string>
<property
name=
"frameShape"
>
<enum>
QFrame::StyledPanel
</enum>
</property>
<property
name=
"frameShadow"
>
<enum>
QFrame::Sunken
</enum>
</property>
<property
name=
"pixmap"
>
<pixmap>
icons/default-avatar.png
</pixmap>
</property>
<property
name=
"alignment"
>
<set>
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
</set>
<set>
Qt::AlignCenter
</set>
</property>
<property
name=
"default_icon"
stdset=
"0"
>
<iconset>
<normaloff>
icons/default-avatar.png
</normaloff>
icons/default-avatar.png
</iconset>
</property>
<property
name=
"icon_size"
stdset=
"0"
>
<number>
48
</number>
</property>
</widget>
</item>
<item
row=
"4"
column=
"1"
colspan=
"
2
"
>
<item
row=
"4"
column=
"1"
colspan=
"
3
"
>
<widget
class=
"QComboBox"
name=
"preferred_media"
>
<property
name=
"sizePolicy"
>
<sizepolicy
hsizetype=
"Expanding"
vsizetype=
"Fixed"
>
...
...
@@ -91,37 +103,122 @@
</property>
<item>
<property
name=
"text"
>
<string>
Audio
</string>
<string>
Start voice calls by default
</string>
</property>
</item>
<item>
<property
name=
"text"
>
<string>
Cha
t
</string>
<string>
Start chat sessions by defaul
t
</string>
</property>
</item>
</widget>
</item>
<item
row=
"3"
column=
"1"
colspan=
"3"
>
<widget
class=
"QCheckBox"
name=
"presence"
>
<property
name=
"text"
>
<string>
Exchange Availability Information
</string>
</property>
<property
name=
"checked"
>
<bool>
true
</bool>
</property>
</widget>
</item>
<item
row=
"6"
column=
"0"
colspan=
"4"
>
<spacer
name=
"section_spacer"
>
<property
name=
"orientation"
>
<enum>
Qt::Vertical
</enum>
</property>
<property
name=
"sizeType"
>
<enum>
QSizePolicy::MinimumExpanding
</enum>
</property>
<property
name=
"sizeHint"
stdset=
"0"
>
<size>
<width>
412
</width>
<height>
20
</height>
</size>
</property>
</spacer>
</item>
<item
row=
"2"
column=
"1"
colspan=
"3"
>
<widget
class=
"ContactURITableView"
name=
"addresses_table"
>
<property
name=
"palette"
>
<palette>
<active>
<colorrole
role=
"AlternateBase"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
238
</red>
<green>
238
</green>
<blue>
238
</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole
role=
"AlternateBase"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
238
</red>
<green>
238
</green>
<blue>
238
</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole
role=
"AlternateBase"
>
<brush
brushstyle=
"SolidPattern"
>
<color
alpha=
"255"
>
<red>
238
</red>
<green>
238
</green>
<blue>
238
</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property
name=
"alternatingRowColors"
>
<bool>
true
</bool>
</property>
<property
name=
"selectionBehavior"
>
<enum>
QAbstractItemView::SelectRows
</enum>
</property>
<property
name=
"showGrid"
>
<bool>
false
</bool>
</property>
<attribute
name=
"verticalHeaderVisible"
>
<bool>
false
</bool>
</attribute>
<attribute
name=
"verticalHeaderDefaultSectionSize"
>
<number>
22
</number>
</attribute>
</widget>
</item>
<item
row=
"0"
column=
"0"
>
<widget
class=
"QLabel"
name=
"
display_
name_label"
>
<widget
class=
"QLabel"
name=
"name_label"
>
<property
name=
"text"
>
<string>
Display
Name:
</string>
<string>
Name:
</string>
</property>
<property
name=
"alignment"
>
<set>
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
</set>
</property>
</widget>
</item>
<item
row=
"
1"
column=
"1
"
>
<
widget
class=
"LineEdit"
name=
"sip_address_edito
r"
>
<property
name=
"
inactiveText"
stdset=
"0
"
>
<
string>
user@domain
</string
>
<item
row=
"
7"
column=
"0"
colspan=
"4
"
>
<
spacer
name=
"grid_space
r"
>
<property
name=
"
orientation
"
>
<
enum>
Qt::Vertical
</enum
>
</property>
<property
name=
"widgetSpacing"
stdset=
"0"
>
<number>
0
</number>
<property
name=
"sizeHint"
stdset=
"0"
>
<size>
<width>
412
</width>
<height>
20
</height>
</size>
</property>
</
widget
>
</
spacer
>
</item>
<item
row=
"
9"
column=
"0"
colspan=
"3
"
>
<item
row=
"
8"
column=
"0"
colspan=
"4
"
>
<layout
class=
"QHBoxLayout"
name=
"button_box_layout"
>
<property
name=
"spacing"
>
<number>
6
</number>
...
...
@@ -176,99 +273,6 @@
</item>
</layout>
</item>
<item
row=
"7"
column=
"1"
colspan=
"2"
>
<widget
class=
"QCheckBox"
name=
"subscribe_dialogs"
>
<property
name=
"enabled"
>
<bool>
false
</bool>
</property>
<property
name=
"text"
>
<string>
Subscribe To Dialogs
</string>
</property>
</widget>
</item>
<item
row=
"4"
column=
"0"
>
<widget
class=
"QLabel"
name=
"preferred_media_label"
>
<property
name=
"text"
>
<string>
Preferred Media:
</string>
</property>
<property
name=
"alignment"
>
<set>
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
</set>
</property>
</widget>
</item>
<item
row=
"0"
column=
"2"
rowspan=
"3"
>
<layout
class=
"QVBoxLayout"
name=
"icon_layout"
>
<property
name=
"spacing"
>
<number>
3
</number>
</property>
<property
name=
"leftMargin"
>
<number>
5
</number>
</property>
<property
name=
"rightMargin"
>
<number>
5
</number>
</property>
<item>
<widget
class=
"IconSelector"
name=
"icon_selector"
>
<property
name=
"minimumSize"
>
<size>
<width>
60
</width>
<height>
60
</height>
</size>
</property>
<property
name=
"maximumSize"
>
<size>
<width>
60
</width>
<height>
60
</height>
</size>
</property>
<property
name=
"toolTip"
>
<string>
Click to select a picture
</string>
</property>
<property
name=
"frameShape"
>
<enum>
QFrame::StyledPanel
</enum>
</property>
<property
name=
"frameShadow"
>
<enum>
QFrame::Sunken
</enum>
</property>
<property
name=
"pixmap"
>
<pixmap>
icons/default-avatar.png
</pixmap>
</property>
<property
name=
"alignment"
>
<set>
Qt::AlignCenter
</set>
</property>
<property
name=
"default_icon"
stdset=
"0"
>
<string>
icons/default-avatar.png
</string>
</property>
</widget>
</item>
<item>
<widget
class=
"QPushButton"
name=
"clear_button"
>
<property
name=
"maximumSize"
>
<size>
<width>
60
</width>
<height>
20
</height>
</size>
</property>
<property
name=
"text"
>
<string>
Clear
</string>
</property>
<property
name=
"autoDefault"
>
<bool>
false
</bool>
</property>
</widget>
</item>
</layout>
</item>
<item
row=
"0"
column=
"1"
>
<widget
class=
"LineEdit"
name=
"display_name_editor"
>
<property
name=
"inactiveText"
stdset=
"0"
>
<string>
Contact Name
</string>
</property>
<property
name=
"widgetSpacing"
stdset=
"0"
>
<number>
0
</number>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
...
...
@@ -282,16 +286,19 @@
<extends>
QLabel
</extends>
<header>
blink.widgets.labels
</header>
</customwidget>
<customwidget>
<class>
ContactURITableView
</class>
<extends>
QTableView
</extends>
<header>
blink.contacts
</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>
display_
name_editor
</tabstop>
<tabstop>
sip_address_editor
</tabstop>
<tabstop>
clear_button
</tabstop>
<tabstop>
name_editor
</tabstop>
<tabstop>
addresses_table
</tabstop>
<tabstop>
presence
</tabstop>
<tabstop>
preferred_media
</tabstop>
<tabstop>
subscribe_presence
</tabstop>
<tabstop>
subscribe_dialogs
</tabstop>
<tabstop>
reject_button
</tabstop>
<tabstop>
accept_button
</tabstop>
<tabstop>
reject_button
</tabstop>
</tabstops>
<resources/>
<connections>
...
...
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