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
8e38a1ae
Commit
8e38a1ae
authored
Aug 03, 2010
by
Saul Ibarra
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added integration with Google Contacts
parent
f8ba1d97
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
889 additions
and
7 deletions
+889
-7
datatypes.py
blink/configuration/datatypes.py
+31
-1
settings.py
blink/configuration/settings.py
+7
-1
contacts.py
blink/contacts.py
+394
-4
data.py
blink/google/gdata/contacts/data.py
+6
-0
mainwindow.py
blink/mainwindow.py
+31
-1
resources.py
blink/resources.py
+36
-0
blink.ui
resources/blink.ui
+7
-0
google_contacts_dialog.ui
resources/google_contacts_dialog.ui
+377
-0
No files found.
blink/configuration/datatypes.py
View file @
8e38a1ae
...
@@ -3,7 +3,7 @@
...
@@ -3,7 +3,7 @@
"""Definitions of datatypes for use in settings extensions."""
"""Definitions of datatypes for use in settings extensions."""
__all__
=
[
'ApplicationDataPath'
,
'SoundFile'
,
'DefaultPath'
,
'CustomSoundFile'
,
'HTTPURL'
]
__all__
=
[
'ApplicationDataPath'
,
'SoundFile'
,
'DefaultPath'
,
'CustomSoundFile'
,
'HTTPURL'
,
'InvalidToken'
,
'AuthorizationToken'
]
import
os
import
os
import
re
import
re
...
@@ -113,3 +113,33 @@ class HTTPURL(unicode):
...
@@ -113,3 +113,33 @@ class HTTPURL(unicode):
return
value
return
value
class
InvalidToken
(
object
):
def
__repr__
(
self
):
return
self
.
__class__
.
__name__
class
AuthorizationToken
(
object
):
def
__init__
(
self
,
token
=
None
):
self
.
token
=
token
def
__getstate__
(
self
):
if
self
.
token
is
InvalidToken
:
return
u'invalid'
else
:
return
u'value:
%
s'
%
(
self
.
__dict__
[
'token'
])
def
__setstate__
(
self
,
state
):
match
=
re
.
match
(
r'^(?P<type>invalid|value:)(?P<token>.+?)?$'
,
state
)
if
match
is
None
:
raise
ValueError
(
'illegal value:
%
r'
%
state
)
data
=
match
.
groupdict
()
if
data
.
pop
(
'type'
)
==
'invalid'
:
data
[
'token'
]
=
InvalidToken
self
.
__init__
(
data
[
'token'
])
def
__nonzero__
(
self
):
return
self
.
token
is
not
InvalidToken
def
__repr__
(
self
):
return
'
%
s(
%
r)'
%
(
self
.
__class__
.
__name__
,
self
.
token
)
blink/configuration/settings.py
View file @
8e38a1ae
...
@@ -12,7 +12,7 @@ from sipsimple.configuration import Setting, SettingsGroup, SettingsObjectExtens
...
@@ -12,7 +12,7 @@ from sipsimple.configuration import Setting, SettingsGroup, SettingsObjectExtens
from
sipsimple.configuration.settings
import
AudioSettings
,
LogsSettings
,
TLSSettings
from
sipsimple.configuration.settings
import
AudioSettings
,
LogsSettings
,
TLSSettings
from
blink
import
__version__
from
blink
import
__version__
from
blink.configuration.datatypes
import
ApplicationDataPath
,
HTTPURL
,
SoundFile
from
blink.configuration.datatypes
import
ApplicationDataPath
,
AuthorizationToken
,
HTTPURL
,
SoundFile
from
blink.resources
import
Resources
from
blink.resources
import
Resources
...
@@ -20,6 +20,11 @@ class AudioSettingsExtension(AudioSettings):
...
@@ -20,6 +20,11 @@ class AudioSettingsExtension(AudioSettings):
recordings_directory
=
Setting
(
type
=
ApplicationDataPath
,
default
=
ApplicationDataPath
(
'recordings'
),
nillable
=
False
)
recordings_directory
=
Setting
(
type
=
ApplicationDataPath
,
default
=
ApplicationDataPath
(
'recordings'
),
nillable
=
False
)
class
GoogleContactsSettings
(
SettingsGroup
):
authorization_token
=
Setting
(
type
=
AuthorizationToken
,
default
=
None
,
nillable
=
True
)
username
=
Setting
(
type
=
unicode
,
default
=
None
,
nillable
=
True
)
class
LogsSettingsExtension
(
LogsSettings
):
class
LogsSettingsExtension
(
LogsSettings
):
trace_sip
=
Setting
(
type
=
bool
,
default
=
False
)
trace_sip
=
Setting
(
type
=
bool
,
default
=
False
)
trace_pjsip
=
Setting
(
type
=
bool
,
default
=
False
)
trace_pjsip
=
Setting
(
type
=
bool
,
default
=
False
)
...
@@ -42,6 +47,7 @@ class TLSSettingsExtension(TLSSettings):
...
@@ -42,6 +47,7 @@ class TLSSettingsExtension(TLSSettings):
class
SIPSimpleSettingsExtension
(
SettingsObjectExtension
):
class
SIPSimpleSettingsExtension
(
SettingsObjectExtension
):
audio
=
AudioSettingsExtension
audio
=
AudioSettingsExtension
google_contacts
=
GoogleContactsSettings
logs
=
LogsSettingsExtension
logs
=
LogsSettingsExtension
server
=
ServerSettings
server
=
ServerSettings
sounds
=
SoundSettings
sounds
=
SoundSettings
...
...
blink/contacts.py
View file @
8e38a1ae
...
@@ -3,11 +3,13 @@
...
@@ -3,11 +3,13 @@
from
__future__
import
with_statement
from
__future__
import
with_statement
__all__
=
[
'BonjourGroup'
,
'BonjourNeighbour'
,
'Contact'
,
'ContactGroup'
,
'ContactModel'
,
'ContactSearchModel'
,
'ContactListView'
,
'ContactSearchListView'
,
'ContactEditorDialog'
]
__all__
=
[
'BonjourGroup'
,
'BonjourNeighbour'
,
'Contact'
,
'ContactGroup'
,
'ContactModel'
,
'ContactSearchModel'
,
'ContactListView'
,
'ContactSearchListView'
,
'ContactEditorDialog'
,
'GoogleContactsDialog'
]
import
cPickle
as
pickle
import
cPickle
as
pickle
import
errno
import
errno
import
os
import
os
import
re
import
socket
import
sys
import
sys
from
PyQt4
import
uic
from
PyQt4
import
uic
...
@@ -19,17 +21,31 @@ from application.notification import IObserver, NotificationCenter
...
@@ -19,17 +21,31 @@ from application.notification import IObserver, NotificationCenter
from
application.python.decorator
import
decorator
,
preserve_signature
from
application.python.decorator
import
decorator
,
preserve_signature
from
application.python.util
import
Null
from
application.python.util
import
Null
from
application.system
import
unlink
from
application.system
import
unlink
from
collections
import
deque
from
eventlet
import
api
from
eventlet.green
import
urllib2
from
functools
import
partial
from
functools
import
partial
from
operator
import
attrgetter
from
operator
import
attrgetter
from
twisted.internet
import
reactor
from
twisted.internet.error
import
ConnectionLost
from
zope.interface
import
implements
from
zope.interface
import
implements
from
sipsimple.account
import
AccountManager
,
BonjourAccount
from
sipsimple.account
import
AccountManager
,
BonjourAccount
from
sipsimple.util
import
makedirs
from
sipsimple.configuration.settings
import
SIPSimpleSettings
from
sipsimple.util
import
makedirs
,
run_in_green_thread
,
run_in_twisted_thread
from
blink.configuration.datatypes
import
AuthorizationToken
,
InvalidToken
from
blink.resources
import
ApplicationData
,
Resources
,
IconCache
from
blink.resources
import
ApplicationData
,
Resources
,
IconCache
from
blink.sessions
import
SessionManager
from
blink.sessions
import
SessionManager
from
blink.util
import
run_in_auxiliary_thread
,
run_in_gui_thread
from
blink.util
import
QSingleton
,
call_in_gui_thread
,
call_later
,
run_in_auxiliary_thread
,
run_in_gui_thread
from
blink.widgets.buttons
import
SwitchViewButton
from
blink.widgets.buttons
import
SwitchViewButton
from
blink.widgets.labels
import
Status
from
blink.google.gdata.client
import
BadAuthentication
,
CaptchaChallenge
,
RequestError
,
Unauthorized
from
blink.google.gdata.contacts.client
import
ContactsClient
from
blink.google.gdata.contacts.data
import
ContactsFeed
from
blink.google.gdata.contacts.service
import
ContactsQuery
from
blink.google.gdata.gauth
import
ClientLoginToken
# Functions decorated with updates_contacts_db or ignore_contacts_db_updates must
# Functions decorated with updates_contacts_db or ignore_contacts_db_updates must
...
@@ -223,6 +239,368 @@ class BonjourNeighbour(Contact):
...
@@ -223,6 +239,368 @@ class BonjourNeighbour(Contact):
return
"
%
s (
%
s)"
%
(
self
.
name
,
self
.
hostname
)
return
"
%
s (
%
s)"
%
(
self
.
name
,
self
.
hostname
)
class
GoogleContactsGroup
(
ContactGroup
):
savable
=
True
movable
=
True
editable
=
True
deletable
=
False
def
__init__
(
self
,
name
,
collapsed
=
False
):
super
(
GoogleContactsGroup
,
self
)
.
__init__
(
name
,
collapsed
)
self
.
id
=
None
self
.
update_timestamp
=
None
def
__reduce__
(
self
):
return
(
self
.
__class__
,
(
self
.
name
,
self
.
user_collapsed
),
dict
(
id
=
self
.
id
,
update_timestamp
=
self
.
update_timestamp
))
class
GoogleContact
(
Contact
):
savable
=
True
movable
=
False
editable
=
False
deletable
=
False
def
__init__
(
self
,
id
,
group
,
name
,
uri
,
company
=
None
,
uri_type
=
None
,
image
=
None
,
image_etag
=
None
):
super
(
GoogleContact
,
self
)
.
__init__
(
group
,
name
,
uri
,
image
)
self
.
id
=
id
self
.
company
=
company
self
.
uri_type
=
uri_type
self
.
image_etag
=
image_etag
def
__reduce__
(
self
):
return
(
self
.
__class__
,
(
self
.
id
,
self
.
group
,
self
.
name
,
self
.
uri
,
self
.
company
,
self
.
uri_type
,
self
.
image
,
self
.
image_etag
),
dict
(
preferred_media
=
self
.
preferred_media
,
sip_aliases
=
self
.
sip_aliases
))
def
__unicode__
(
self
):
return
u'
%
s <
%
s>'
%
(
self
.
name_detail
,
self
.
uri_detail
)
@
property
def
name_detail
(
self
):
if
self
.
company
:
return
'
%
s (
%
s)'
%
(
self
.
name
,
self
.
company
)
if
self
.
name
else
self
.
company
else
:
return
self
.
name
or
self
.
uri
@
property
def
uri_detail
(
self
):
return
"
%
s (
%
s)"
%
(
self
.
uri
,
self
.
uri_type
)
if
self
.
uri_type
else
self
.
uri
class
GoogleContactsManager
(
object
):
implements
(
IObserver
)
def
__init__
(
self
,
model
):
self
.
client
=
ContactsClient
()
self
.
contact_model
=
model
self
.
entries_map
=
dict
()
self
.
greenlet
=
None
self
.
stop_adding_contacts
=
False
self
.
_load_timer
=
None
notification_center
=
NotificationCenter
()
notification_center
.
add_observer
(
self
,
name
=
'CFGSettingsObjectDidChange'
)
notification_center
.
add_observer
(
self
,
name
=
'SIPApplicationWillStart'
)
notification_center
.
add_observer
(
self
,
name
=
'SIPApplicationWillEnd'
)
@
property
def
group
(
self
):
return
self
.
contact_model
.
google_contacts_group
def
initialize
(
self
):
self
.
entries_map
.
clear
()
for
contact
in
(
item
for
item
in
self
.
contact_model
.
items
if
type
(
item
)
is
GoogleContact
):
self
.
entries_map
.
setdefault
(
contact
.
id
,
[])
.
append
(
contact
)
@
staticmethod
def
normalize_uri_label
(
label
):
try
:
label
=
label
.
lower
()
label
=
label
.
split
(
'#'
)[
1
]
label
=
re
.
sub
(
'_'
,
' '
,
label
)
except
AttributeError
:
label
=
''
except
IndexError
:
label
=
re
.
sub
(
'
\
/'
,
''
,
label
)
finally
:
label
=
re
.
sub
(
'generic'
,
''
,
label
)
return
label
.
strip
()
@
run_in_twisted_thread
def
handle_notification
(
self
,
notification
):
handler
=
getattr
(
self
,
'_NH_
%
s'
%
notification
.
name
,
Null
)
handler
(
notification
)
def
_NH_SIPApplicationWillStart
(
self
,
notification
):
settings
=
SIPSimpleSettings
()
authorization_token
=
settings
.
google_contacts
.
authorization_token
if
authorization_token
:
call_in_gui_thread
(
self
.
contact_model
.
addGroup
,
self
.
contact_model
.
google_contacts_group
)
self
.
load_contacts
()
elif
authorization_token
is
None
:
self
.
remove_group
()
def
_NH_CFGSettingsObjectDidChange
(
self
,
notification
):
if
'google_contacts.authorization_token'
in
notification
.
data
.
modified
:
authorization_token
=
notification
.
sender
.
google_contacts
.
authorization_token
if
self
.
_load_timer
is
not
None
and
self
.
_load_timer
.
active
():
self
.
_load_timer
.
cancel
()
self
.
_load_timer
=
None
if
authorization_token
:
call_in_gui_thread
(
self
.
contact_model
.
addGroup
,
self
.
contact_model
.
google_contacts_group
)
self
.
stop_adding_contacts
=
False
self
.
load_contacts
()
elif
authorization_token
is
None
:
if
self
.
_load_timer
is
not
None
and
self
.
_load_timer
.
active
():
self
.
_load_timer
.
cancel
()
self
.
_load_timer
=
None
if
self
.
greenlet
is
not
None
:
api
.
kill
(
self
.
greenlet
,
api
.
GreenletExit
())
self
.
greenlet
=
None
self
.
stop_adding_contacts
=
False
self
.
remove_group
()
def
_NH_SIPApplicationWillEnd
(
self
,
notification
):
if
self
.
greenlet
is
not
None
:
api
.
kill
(
self
.
greenlet
,
api
.
GreenletExit
())
@
run_in_green_thread
def
load_contacts
(
self
):
if
self
.
greenlet
is
not
None
:
api
.
kill
(
self
.
greenlet
,
api
.
GreenletExit
())
self
.
greenlet
=
api
.
getcurrent
()
if
self
.
_load_timer
is
not
None
and
self
.
_load_timer
.
active
():
self
.
_load_timer
.
cancel
()
self
.
_load_timer
=
None
settings
=
SIPSimpleSettings
()
self
.
client
.
auth_token
=
ClientLoginToken
(
settings
.
google_contacts
.
authorization_token
.
token
)
try
:
if
self
.
group
.
id
is
None
:
self
.
group
.
id
=
(
entry
.
id
.
text
for
entry
in
self
.
client
.
get_groups
()
.
entry
if
entry
.
title
.
text
==
'System Group: My Contacts'
)
.
next
()
query_params
=
dict
(
showdeleted
=
'true'
)
query
=
ContactsQuery
(
feed
=
self
.
client
.
get_feed_uri
(
kind
=
'contacts'
),
group
=
self
.
group
.
id
,
params
=
query_params
)
previous_update
=
self
.
contact_model
.
google_contacts_group
.
update_timestamp
if
previous_update
:
query
.
updated_min
=
previous_update
feed
=
self
.
client
.
get_feed
(
query
.
ToUri
(),
desired_class
=
ContactsFeed
)
update_timestamp
=
feed
.
updated
.
text
if
feed
else
None
while
feed
:
updated_contacts
=
[]
deleted_contacts
=
set
(
entry
.
id
.
text
for
entry
in
feed
.
entry
if
getattr
(
entry
,
'deleted'
,
False
))
self
.
remove_contacts
(
deleted_contacts
)
for
entry
in
(
entry
for
entry
in
feed
.
entry
if
entry
.
id
.
text
not
in
deleted_contacts
):
name
=
(
getattr
(
entry
,
'title'
,
None
)
or
Null
)
.
text
or
None
company
=
((
getattr
(
entry
,
'organization'
,
None
)
or
Null
)
.
name
or
Null
)
.
text
or
None
numbers
=
set
((
re
.
sub
(
r'^00'
,
'+'
,
number
.
text
),
number
.
label
or
number
.
rel
)
for
number
in
getattr
(
entry
,
'phone_number'
,
()))
numbers
.
update
(
set
((
re
.
sub
(
'^(sip:|sips:)'
,
''
,
email
.
address
),
email
.
label
or
email
.
rel
)
for
email
in
getattr
(
entry
,
'email'
,
())
if
re
.
search
(
'^(sip:|sips:)'
,
email
.
address
)))
numbers
.
update
(
set
((
re
.
sub
(
'^(sip:|sips:)'
,
''
,
web
.
href
),
web
.
label
or
web
.
rel
)
for
web
in
getattr
(
entry
,
'website'
,
())
if
re
.
search
(
'^(sip:|sips:)'
,
web
.
href
)))
numbers
.
difference_update
(
set
((
number
,
label
)
for
number
,
label
in
numbers
if
label
.
lower
()
.
find
(
'fax'
)
!=
-
1
))
if
not
numbers
:
continue
image_data
=
None
image_url
,
image_etag
=
entry
.
get_entry_photo_data
()
if
image_url
and
image_etag
and
self
.
entries_map
.
get
(
entry
.
id
.
text
,
Null
)[
0
]
.
image_etag
!=
image_etag
:
try
:
image_data
=
self
.
client
.
Get
(
image_url
)
.
read
()
except
Exception
:
pass
updated_contacts
.
append
((
entry
.
id
.
text
,
name
,
company
,
numbers
,
image_data
,
image_etag
))
self
.
update_contacts
(
updated_contacts
)
feed
=
self
.
client
.
get_next
(
feed
)
if
feed
.
find_next_link
()
is
not
None
else
None
except
Unauthorized
:
settings
.
google_contacts
.
authorization_token
=
AuthorizationToken
(
InvalidToken
)
settings
.
save
()
except
(
ConnectionLost
,
RequestError
,
socket
.
error
):
self
.
_load_timer
=
reactor
.
callLater
(
60
,
self
.
load_contacts
)
else
:
if
update_timestamp
:
self
.
update_group_timestamp
(
update_timestamp
)
self
.
_load_timer
=
reactor
.
callLater
(
60
,
self
.
load_contacts
)
@
run_in_gui_thread
@
updates_contacts_db
def
update_contacts
(
self
,
contacts
):
if
self
.
stop_adding_contacts
:
return
icon_cache
=
IconCache
()
for
id
,
name
,
company
,
numbers
,
image_data
,
image_etag
in
contacts
:
entries
=
self
.
entries_map
.
setdefault
(
id
,
[])
existing_numbers
=
dict
((
entry
.
uri
,
entry
)
for
entry
in
entries
)
# Save GoogleContact instances that can be reused to hold new contact information
reusable_entries
=
deque
(
entry
for
entry
in
entries
if
entry
.
uri
not
in
(
number
for
number
,
label
in
numbers
))
image
=
icon_cache
.
store_image
(
image_data
)
if
image_etag
and
not
image_data
:
try
:
image
=
entries
[
0
]
.
image
image_etag
=
entries
[
0
]
.
image_etag
except
IndexError
:
image
,
image_etag
=
None
,
None
for
number
,
label
in
numbers
:
if
number
in
existing_numbers
:
entry
=
existing_numbers
[
number
]
self
.
contact_model
.
updateContact
(
entry
,
dict
(
name
=
name
,
company
=
company
,
group
=
self
.
group
,
uri_type
=
self
.
normalize_uri_label
(
label
),
image
=
image
,
image_etag
=
image_etag
))
elif
reusable_entries
:
entry
=
reusable_entries
.
popleft
()
self
.
contact_model
.
updateContact
(
entry
,
dict
(
name
=
name
,
company
=
company
,
group
=
self
.
group
,
uri
=
number
,
uri_type
=
self
.
normalize_uri_label
(
label
),
image
=
image
,
image_etag
=
image_etag
))
else
:
try
:
image
=
entries
[
0
]
.
image
except
IndexError
:
pass
entry
=
GoogleContact
(
id
,
self
.
group
,
name
,
number
,
company
=
company
,
uri_type
=
self
.
normalize_uri_label
(
label
),
image
=
image
,
image_etag
=
image_etag
)
entries
.
append
(
entry
)
self
.
contact_model
.
addContact
(
entry
)
for
entry
in
reusable_entries
:
entries
.
remove
(
entry
)
self
.
contact_model
.
removeContact
(
entry
)
@
run_in_gui_thread
@
updates_contacts_db
def
remove_contacts
(
self
,
contact_ids
):
deleted_contacts
=
[]
for
id
in
contact_ids
:
deleted_contacts
.
extend
(
self
.
entries_map
.
pop
(
id
,
()))
for
contact
in
deleted_contacts
:
self
.
contact_model
.
removeContact
(
contact
)
@
run_in_gui_thread
@
updates_contacts_db
def
remove_group
(
self
):
self
.
contact_model
.
removeGroup
(
self
.
contact_model
.
google_contacts_group
)
self
.
group
.
id
=
None
self
.
group
.
update_timestamp
=
None
self
.
entries_map
.
clear
()
@
run_in_gui_thread
def
update_group_timestamp
(
self
,
timestamp
):
if
not
self
.
stop_adding_contacts
:
self
.
group
.
update_timestamp
=
timestamp
ui_class
,
base_class
=
uic
.
loadUiType
(
Resources
.
get
(
'google_contacts_dialog.ui'
))
class
GoogleContactsDialog
(
base_class
,
ui_class
):
__metaclass__
=
QSingleton
def
__init__
(
self
,
parent
=
None
):
super
(
GoogleContactsDialog
,
self
)
.
__init__
(
parent
)
with
Resources
.
directory
:
self
.
setupUi
(
self
)
self
.
authorize_button
.
clicked
.
connect
(
self
.
_SH_AuthorizeButtonClicked
)
self
.
captcha_editor
.
statusChanged
.
connect
(
self
.
_SH_ValidityStatusChanged
)
self
.
username_editor
.
statusChanged
.
connect
(
self
.
_SH_ValidityStatusChanged
)
self
.
password_editor
.
statusChanged
.
connect
(
self
.
_SH_ValidityStatusChanged
)
self
.
rejected
.
connect
(
self
.
_SH_DialogRejected
)
self
.
captcha_editor
.
regexp
=
re
.
compile
(
'^.+$'
)
self
.
username_editor
.
regexp
=
re
.
compile
(
'^.+$'
)
self
.
password_editor
.
regexp
=
re
.
compile
(
'^.+$'
)
self
.
captcha_token
=
None
self
.
enable_captcha
(
False
)
def
enable_captcha
(
self
,
visible
):
self
.
captcha_label
.
setVisible
(
visible
)
self
.
captcha_editor
.
setVisible
(
visible
)
self
.
captcha_image_label
.
setVisible
(
visible
)
inputs
=
[
self
.
username_editor
,
self
.
password_editor
]
if
visible
:
inputs
.
append
(
self
.
captcha_editor
)
self
.
captcha_editor
.
setText
(
u''
)
call_later
(
0
,
self
.
captcha_editor
.
setFocus
)
self
.
authorize_button
.
setEnabled
(
all
(
input
.
text_valid
for
input
in
inputs
))
def
open
(
self
):
settings
=
SIPSimpleSettings
()
self
.
username_editor
.
setEnabled
(
True
)
self
.
username_editor
.
setText
(
settings
.
google_contacts
.
username
or
u''
)
self
.
password_editor
.
setText
(
u''
)
super
(
GoogleContactsDialog
,
self
)
.
show
()
def
open_for_incorrect_password
(
self
):
red
=
'#cc0000'
settings
=
SIPSimpleSettings
()
self
.
username_editor
.
setEnabled
(
False
)
self
.
username_editor
.
setText
(
settings
.
google_contacts
.
username
)
self
.
status_label
.
value
=
Status
(
'Error authenticating with Google. Please enter your password:'
,
color
=
red
)
super
(
GoogleContactsDialog
,
self
)
.
show
()
@
run_in_green_thread
def
_authorize_google_account
(
self
):
red
=
'#cc0000'
captcha_response
=
unicode
(
self
.
captcha_editor
.
text
())
if
self
.
captcha_token
else
None
username
=
unicode
(
self
.
username_editor
.
text
())
password
=
unicode
(
self
.
password_editor
.
text
())
client
=
ContactsClient
()
try
:
client
.
client_login
(
email
=
username
,
password
=
password
,
source
=
'Blink'
,
captcha_token
=
self
.
captcha_token
,
captcha_response
=
captcha_response
)
except
CaptchaChallenge
,
e
:
call_in_gui_thread
(
self
.
username_editor
.
setEnabled
,
False
)
call_in_gui_thread
(
setattr
,
self
.
status_label
,
'value'
,
Status
(
'Error authenticating with Google'
,
color
=
red
))
try
:
captcha_data
=
urllib2
.
urlopen
(
e
.
captcha_url
)
.
read
()
except
(
urllib2
.
HTTPError
,
urllib2
.
URLError
):
pass
else
:
self
.
captcha_token
=
e
.
captcha_token
call_in_gui_thread
(
self
.
_set_captcha_image
,
captcha_data
)
call_in_gui_thread
(
self
.
enable_captcha
,
True
)
except
(
BadAuthentication
,
RequestError
):
self
.
captcha_token
=
None
call_in_gui_thread
(
self
.
username_editor
.
setEnabled
,
True
)
call_in_gui_thread
(
setattr
,
self
.
status_label
,
'value'
,
Status
(
'Error authenticating with Google'
,
color
=
red
))
except
Exception
:
self
.
captcha_token
=
None
call_in_gui_thread
(
self
.
username_editor
.
setEnabled
,
True
)
call_in_gui_thread
(
setattr
,
self
.
status_label
,
'value'
,
Status
(
'Error connecting with Google'
,
color
=
red
))
else
:
self
.
captcha_token
=
None
settings
=
SIPSimpleSettings
()
settings
.
google_contacts
.
authorization_token
=
AuthorizationToken
(
client
.
auth_token
.
token_string
)
settings
.
google_contacts
.
username
=
username
settings
.
save
()
call_in_gui_thread
(
self
.
enable_captcha
,
False
)
call_in_gui_thread
(
self
.
accept
)
finally
:
call_in_gui_thread
(
self
.
setEnabled
,
True
)
def
_set_captcha_image
(
self
,
data
):
pixmap
=
QPixmap
()
if
pixmap
.
loadFromData
(
data
):
pixmap
=
pixmap
.
scaled
(
200
,
70
,
Qt
.
KeepAspectRatio
,
Qt
.
SmoothTransformation
)
self
.
captcha_image_label
.
setPixmap
(
pixmap
)
def
_SH_AuthorizeButtonClicked
(
self
):
self
.
status_label
.
value
=
Status
(
'Contacting Google server...'
)
self
.
setEnabled
(
False
)
self
.
_authorize_google_account
()
@
run_in_twisted_thread
def
_SH_DialogRejected
(
self
):
settings
=
SIPSimpleSettings
()
settings
.
google_contacts
.
authorization_token
=
None
settings
.
save
()
self
.
captcha_token
=
None
call_in_gui_thread
(
self
.
enable_captcha
,
False
)
def
_SH_ValidityStatusChanged
(
self
):
red
=
'#cc0000'
if
not
self
.
username_editor
.
text_valid
:
self
.
status_label
.
value
=
Status
(
'Please specify your Google account username'
,
color
=
red
)
elif
not
self
.
password_editor
.
text_valid
:
self
.
status_label
.
value
=
Status
(
'Please specify your Google account password'
,
color
=
red
)
elif
self
.
captcha_editor
.
isVisible
()
and
not
self
.
captcha_editor
.
text_valid
:
self
.
status_label
.
value
=
Status
(
'Please insert the text in the image below'
,
color
=
red
)
else
:
self
.
status_label
.
value
=
None
self
.
authorize_button
.
setEnabled
(
self
.
username_editor
.
text_valid
and
self
.
password_editor
.
text_valid
and
(
True
if
not
self
.
captcha_editor
.
isVisible
()
else
self
.
captcha_editor
.
text_valid
))
del
ui_class
,
base_class
ui_class
,
base_class
=
uic
.
loadUiType
(
Resources
.
get
(
'contact.ui'
))
ui_class
,
base_class
=
uic
.
loadUiType
(
Resources
.
get
(
'contact.ui'
))
class
ContactWidget
(
base_class
,
ui_class
):
class
ContactWidget
(
base_class
,
ui_class
):
...
@@ -389,7 +767,7 @@ del ui_class, base_class
...
@@ -389,7 +767,7 @@ del ui_class, base_class
class
ContactDelegate
(
QStyledItemDelegate
):
class
ContactDelegate
(
QStyledItemDelegate
):
item_size_hints
=
{
Contact
:
QSize
(
200
,
36
),
ContactGroup
:
QSize
(
200
,
18
),
BonjourNeighbour
:
QSize
(
200
,
36
),
BonjourGroup
:
QSize
(
200
,
18
)}
item_size_hints
=
{
Contact
:
QSize
(
200
,
36
),
ContactGroup
:
QSize
(
200
,
18
),
BonjourNeighbour
:
QSize
(
200
,
36
),
BonjourGroup
:
QSize
(
200
,
18
)
,
GoogleContact
:
QSize
(
200
,
36
),
GoogleContactsGroup
:
QSize
(
200
,
18
)
}
def
__init__
(
self
,
parent
=
None
):
def
__init__
(
self
,
parent
=
None
):
super
(
ContactDelegate
,
self
)
.
__init__
(
parent
)
super
(
ContactDelegate
,
self
)
.
__init__
(
parent
)
...
@@ -488,6 +866,8 @@ class ContactDelegate(QStyledItemDelegate):
...
@@ -488,6 +866,8 @@ class ContactDelegate(QStyledItemDelegate):
paintBonjourNeighbour
=
paintContact
paintBonjourNeighbour
=
paintContact
paintBonjourGroup
=
paintContactGroup
paintBonjourGroup
=
paintContactGroup
paintGoogleContact
=
paintContact
paintGoogleContactsGroup
=
paintContactGroup
def
paint
(
self
,
painter
,
option
,
index
):
def
paint
(
self
,
painter
,
option
,
index
):
item
=
index
.
model
()
.
data
(
index
,
Qt
.
DisplayRole
)
item
=
index
.
model
()
.
data
(
index
,
Qt
.
DisplayRole
)
...
@@ -519,6 +899,9 @@ class ContactModel(QAbstractListModel):
...
@@ -519,6 +899,9 @@ class ContactModel(QAbstractListModel):
self
.
endResetModel
=
self
.
reset
self
.
endResetModel
=
self
.
reset
self
.
bonjour_group
=
None
self
.
bonjour_group
=
None
self
.
google_contacts_group
=
None
self
.
google_contacts_manager
=
GoogleContactsManager
(
self
)
notification_center
=
NotificationCenter
()
notification_center
=
NotificationCenter
()
notification_center
.
add_observer
(
self
,
name
=
'BonjourAccountDidAddNeighbour'
)
notification_center
.
add_observer
(
self
,
name
=
'BonjourAccountDidAddNeighbour'
)
notification_center
.
add_observer
(
self
,
name
=
'BonjourAccountDidRemoveNeighbour'
)
notification_center
.
add_observer
(
self
,
name
=
'BonjourAccountDidRemoveNeighbour'
)
...
@@ -912,8 +1295,13 @@ class ContactModel(QAbstractListModel):
...
@@ -912,8 +1295,13 @@ class ContactModel(QAbstractListModel):
self
.
contact_list
.
setRowHidden
(
position
,
item
.
group
.
collapsed
)
self
.
contact_list
.
setRowHidden
(
position
,
item
.
group
.
collapsed
)
if
type
(
item
)
is
BonjourGroup
:
if
type
(
item
)
is
BonjourGroup
:
self
.
bonjour_group
=
item
self
.
bonjour_group
=
item
if
type
(
item
)
is
GoogleContactsGroup
:
self
.
google_contacts_group
=
item
if
self
.
bonjour_group
is
None
:
if
self
.
bonjour_group
is
None
:
self
.
bonjour_group
=
BonjourGroup
(
'Bonjour Neighbours'
)
self
.
bonjour_group
=
BonjourGroup
(
'Bonjour Neighbours'
)
if
self
.
google_contacts_group
is
None
:
self
.
google_contacts_group
=
GoogleContactsGroup
(
'Google Contacts'
)
self
.
google_contacts_manager
.
initialize
()
if
file
is
None
:
if
file
is
None
:
self
.
save
()
self
.
save
()
...
@@ -926,6 +1314,8 @@ class ContactModel(QAbstractListModel):
...
@@ -926,6 +1314,8 @@ class ContactModel(QAbstractListModel):
items
.
remove
(
self
.
bonjour_group
)
items
.
remove
(
self
.
bonjour_group
)
position
=
items
.
index
(
reference
)
if
reference
in
contact_groups
else
len
(
self
.
items
)
position
=
items
.
index
(
reference
)
if
reference
in
contact_groups
else
len
(
self
.
items
)
items
.
insert
(
position
,
group
)
items
.
insert
(
position
,
group
)
if
self
.
google_contacts_group
not
in
contact_groups
:
items
.
append
(
self
.
google_contacts_group
)
self
.
_store_contacts
(
pickle
.
dumps
(
items
))
self
.
_store_contacts
(
pickle
.
dumps
(
items
))
...
...
blink/google/gdata/contacts/data.py
View file @
8e38a1ae
...
@@ -410,6 +410,12 @@ class ContactEntry(PersonEntry):
...
@@ -410,6 +410,12 @@ class ContactEntry(PersonEntry):
return
a_link
return
a_link
return
None
return
None
def
get_entry_photo_data
(
self
):
photo
=
self
.
GetPhotoLink
()
if
photo
.
_other_attributes
.
get
(
'{http://schemas.google.com/g/2005}etag'
):
return
(
photo
.
href
,
photo
.
_other_attributes
.
get
(
'{http://schemas.google.com/g/2005}etag'
)
.
strip
(
'"'
))
return
(
None
,
None
)
class
ContactsFeed
(
gdata_data
.
BatchFeed
):
class
ContactsFeed
(
gdata_data
.
BatchFeed
):
"""A collection of Contacts."""
"""A collection of Contacts."""
...
...
blink/mainwindow.py
View file @
8e38a1ae
...
@@ -22,7 +22,7 @@ from sipsimple.configuration.settings import SIPSimpleSettings
...
@@ -22,7 +22,7 @@ from sipsimple.configuration.settings import SIPSimpleSettings
from
blink.aboutpanel
import
AboutPanel
from
blink.aboutpanel
import
AboutPanel
from
blink.accounts
import
AccountModel
,
ActiveAccountModel
,
AddAccountDialog
,
ServerToolsAccountModel
,
ServerToolsWindow
from
blink.accounts
import
AccountModel
,
ActiveAccountModel
,
AddAccountDialog
,
ServerToolsAccountModel
,
ServerToolsWindow
from
blink.contacts
import
BonjourNeighbour
,
Contact
,
ContactGroup
,
ContactEditorDialog
,
ContactModel
,
ContactSearchModel
from
blink.contacts
import
BonjourNeighbour
,
Contact
,
ContactGroup
,
ContactEditorDialog
,
ContactModel
,
ContactSearchModel
,
GoogleContactsDialog
from
blink.sessions
import
SessionManager
,
SessionModel
from
blink.sessions
import
SessionManager
,
SessionModel
from
blink.resources
import
Resources
from
blink.resources
import
Resources
from
blink.util
import
call_in_auxiliary_thread
,
run_in_gui_thread
from
blink.util
import
call_in_auxiliary_thread
,
run_in_gui_thread
...
@@ -81,6 +81,7 @@ class MainWindow(base_class, ui_class):
...
@@ -81,6 +81,7 @@ class MainWindow(base_class, ui_class):
self
.
about_panel
=
AboutPanel
(
self
)
self
.
about_panel
=
AboutPanel
(
self
)
self
.
add_account_dialog
=
AddAccountDialog
(
self
)
self
.
add_account_dialog
=
AddAccountDialog
(
self
)
self
.
contact_editor_dialog
=
ContactEditorDialog
(
self
.
contact_model
,
self
)
self
.
contact_editor_dialog
=
ContactEditorDialog
(
self
.
contact_model
,
self
)
self
.
google_contacts_dialog
=
GoogleContactsDialog
(
self
)
self
.
server_tools_window
=
ServerToolsWindow
(
self
.
server_tools_account_model
,
None
)
self
.
server_tools_window
=
ServerToolsWindow
(
self
.
server_tools_account_model
,
None
)
# Signals
# Signals
...
@@ -195,6 +196,7 @@ class MainWindow(base_class, ui_class):
...
@@ -195,6 +196,7 @@ class MainWindow(base_class, ui_class):
self
.
about_panel
.
close
()
self
.
about_panel
.
close
()
self
.
add_account_dialog
.
close
()
self
.
add_account_dialog
.
close
()
self
.
contact_editor_dialog
.
close
()
self
.
contact_editor_dialog
.
close
()
self
.
google_contacts_dialog
.
close
()
self
.
server_tools_window
.
close
()
self
.
server_tools_window
.
close
()
def
set_user_icon
(
self
,
image_file_name
):
def
set_user_icon
(
self
,
image_file_name
):
...
@@ -290,6 +292,15 @@ class MainWindow(base_class, ui_class):
...
@@ -290,6 +292,15 @@ class MainWindow(base_class, ui_class):
settings
.
audio
.
output_device
=
action
.
data
()
.
toPyObject
()
settings
.
audio
.
output_device
=
action
.
data
()
.
toPyObject
()
call_in_auxiliary_thread
(
settings
.
save
)
call_in_auxiliary_thread
(
settings
.
save
)
def
_AH_GoogleContactsActionTriggered
(
self
):
settings
=
SIPSimpleSettings
()
if
settings
.
google_contacts
.
authorization_token
:
settings
=
SIPSimpleSettings
()
settings
.
google_contacts
.
authorization_token
=
None
settings
.
save
()
else
:
self
.
google_contacts_dialog
.
open
()
def
_AH_RedialActionTriggered
(
self
):
def
_AH_RedialActionTriggered
(
self
):
session_manager
=
SessionManager
()
session_manager
=
SessionManager
()
if
session_manager
.
last_dialed_uri
is
not
None
:
if
session_manager
.
last_dialed_uri
is
not
None
:
...
@@ -493,6 +504,16 @@ class MainWindow(base_class, ui_class):
...
@@ -493,6 +504,16 @@ class MainWindow(base_class, ui_class):
notification_center
.
add_observer
(
self
,
sender
=
account_manager
)
notification_center
.
add_observer
(
self
,
sender
=
account_manager
)
self
.
silent_action
.
setChecked
(
settings
.
audio
.
silent
)
self
.
silent_action
.
setChecked
(
settings
.
audio
.
silent
)
self
.
silent_button
.
setChecked
(
settings
.
audio
.
silent
)
self
.
silent_button
.
setChecked
(
settings
.
audio
.
silent
)
if
settings
.
google_contacts
.
authorization_token
:
self
.
google_contacts_action
.
setText
(
u'Disable Google Contacts'
)
elif
settings
.
google_contacts
.
authorization_token
is
not
None
:
# Token is invalid
self
.
google_contacts_action
.
setText
(
u'Disable Google Contacts'
)
# Maybe this should be moved to DidStart so that the dialog is shown *after* the MainWindow. -Saul
self
.
google_contacts_dialog
.
open_for_incorrect_password
()
else
:
self
.
google_contacts_action
.
setText
(
u'Enable Google Contacts'
)
self
.
google_contacts_action
.
triggered
.
connect
(
self
.
_AH_GoogleContactsActionTriggered
)
if
all
(
not
account
.
enabled
for
account
in
account_manager
.
iter_accounts
()):
if
all
(
not
account
.
enabled
for
account
in
account_manager
.
iter_accounts
()):
self
.
display_name
.
setEnabled
(
False
)
self
.
display_name
.
setEnabled
(
False
)
self
.
activity_note
.
setEnabled
(
False
)
self
.
activity_note
.
setEnabled
(
False
)
...
@@ -539,6 +560,15 @@ class MainWindow(base_class, ui_class):
...
@@ -539,6 +560,15 @@ class MainWindow(base_class, ui_class):
if
'audio.alert_device'
in
notification
.
data
.
modified
:
if
'audio.alert_device'
in
notification
.
data
.
modified
:
action
=
(
action
for
action
in
self
.
alert_devices_group
.
actions
()
if
action
.
data
()
.
toPyObject
()
==
settings
.
audio
.
alert_device
)
.
next
()
action
=
(
action
for
action
in
self
.
alert_devices_group
.
actions
()
if
action
.
data
()
.
toPyObject
()
==
settings
.
audio
.
alert_device
)
.
next
()
action
.
setChecked
(
True
)
action
.
setChecked
(
True
)
if
'google_contacts.authorization_token'
in
notification
.
data
.
modified
:
authorization_token
=
notification
.
sender
.
google_contacts
.
authorization_token
if
authorization_token
:
self
.
google_contacts_action
.
setText
(
u'Disable Google Contacts'
)
elif
authorization_token
is
not
None
:
# Token is invalid
self
.
google_contacts_dialog
.
open_for_incorrect_password
()
else
:
self
.
google_contacts_action
.
setText
(
u'Enable Google Contacts'
)
elif
isinstance
(
notification
.
sender
,
(
Account
,
BonjourAccount
)):
elif
isinstance
(
notification
.
sender
,
(
Account
,
BonjourAccount
)):
if
'enabled'
in
notification
.
data
.
modified
:
if
'enabled'
in
notification
.
data
.
modified
:
account
=
notification
.
sender
account
=
notification
.
sender
...
...
blink/resources.py
View file @
8e38a1ae
...
@@ -16,6 +16,7 @@ from application import log
...
@@ -16,6 +16,7 @@ from application import log
from
application.python.util
import
Singleton
from
application.python.util
import
Singleton
from
application.system
import
unlink
from
application.system
import
unlink
from
collections
import
deque
from
collections
import
deque
from
hashlib
import
sha512
from
sipsimple.util
import
classproperty
,
makedirs
from
sipsimple.util
import
classproperty
,
makedirs
...
@@ -165,4 +166,39 @@ class IconCache(object):
...
@@ -165,4 +166,39 @@ class IconCache(object):
self
.
available_names
.
appendleft
(
os
.
path
.
basename
(
destination_name
))
self
.
available_names
.
appendleft
(
os
.
path
.
basename
(
destination_name
))
return
filename
return
filename
def
store_image
(
self
,
data
):
if
data
is
None
:
return
None
data_hash
=
sha512
(
data
)
.
hexdigest
()
try
:
return
self
.
filemap
[
data_hash
]
.
destination
except
KeyError
:
pass
try
:
destination_name
=
os
.
path
.
join
(
'images'
,
self
.
available_names
.
popleft
())
except
IndexError
:
# No more available file names.
return
None
pixmap
=
QPixmap
()
if
pixmap
.
loadFromData
(
data
):
pixmap
=
pixmap
.
scaled
(
32
,
32
,
Qt
.
KeepAspectRatio
,
Qt
.
SmoothTransformation
)
makedirs
(
ApplicationData
.
get
(
'images'
))
if
pixmap
.
save
(
ApplicationData
.
get
(
destination_name
)):
file_mapping
=
FileMapping
(
data_hash
,
destination_name
)
self
.
filemap
[
data_hash
]
=
file_mapping
map_filename
=
ApplicationData
.
get
(
os
.
path
.
join
(
'images'
,
'.cached_icons.map'
))
map_tempname
=
map_filename
+
'.tmp'
try
:
file
=
open
(
map_tempname
,
'wb'
)
pickle
.
dump
(
self
.
filemap
,
file
)
file
.
close
()
if
sys
.
platform
==
'win32'
:
unlink
(
map_filename
)
os
.
rename
(
map_tempname
,
map_filename
)
except
Exception
,
e
:
log
.
error
(
"could not save icon cache file mappings:
%
s"
%
e
)
return
destination_name
else
:
self
.
available_names
.
appendleft
(
os
.
path
.
basename
(
destination_name
))
return
None
resources/blink.ui
View file @
8e38a1ae
...
@@ -1032,6 +1032,8 @@ padding: 2px;</string>
...
@@ -1032,6 +1032,8 @@ padding: 2px;</string>
<addaction
name=
"separator"
/>
<addaction
name=
"separator"
/>
<addaction
name=
"file_transfers_action"
/>
<addaction
name=
"file_transfers_action"
/>
<addaction
name=
"logs_action"
/>
<addaction
name=
"logs_action"
/>
<addaction
name=
"separator"
/>
<addaction
name=
"google_contacts_action"
/>
</widget>
</widget>
<addaction
name=
"blink_menu"
/>
<addaction
name=
"blink_menu"
/>
<addaction
name=
"audio_menu"
/>
<addaction
name=
"audio_menu"
/>
...
@@ -1249,6 +1251,11 @@ padding: 2px;</string>
...
@@ -1249,6 +1251,11 @@ padding: 2px;</string>
<string>
Donate if you like Blink
</string>
<string>
Donate if you like Blink
</string>
</property>
</property>
</action>
</action>
<action
name=
"google_contacts_action"
>
<property
name=
"text"
>
<string>
Enable Google Contacts
</string>
</property>
</action>
<action
name=
"history_on_server_action"
>
<action
name=
"history_on_server_action"
>
<property
name=
"text"
>
<property
name=
"text"
>
<string>
Call history on server...
</string>
<string>
Call history on server...
</string>
...
...
resources/google_contacts_dialog.ui
0 → 100644
View file @
8e38a1ae
<?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>
400
</width>
<height>
290
</height>
</rect>
</property>
<property
name=
"minimumSize"
>
<size>
<width>
0
</width>
<height>
0
</height>
</size>
</property>
<property
name=
"windowTitle"
>
<string>
Google Contacts
</string>
</property>
<layout
class=
"QVBoxLayout"
name=
"dialog_layout"
>
<property
name=
"spacing"
>
<number>
0
</number>
</property>
<property
name=
"sizeConstraint"
>
<enum>
QLayout::SetFixedSize
</enum>
</property>
<property
name=
"leftMargin"
>
<number>
8
</number>
</property>
<property
name=
"rightMargin"
>
<number>
8
</number>
</property>
<item>
<widget
class=
"QFrame"
name=
"background_frame"
>
<property
name=
"sizePolicy"
>
<sizepolicy
hsizetype=
"Preferred"
vsizetype=
"Fixed"
>
<horstretch>
0
</horstretch>
<verstretch>
0
</verstretch>
</sizepolicy>
</property>
<property
name=
"minimumSize"
>
<size>
<width>
0
</width>
<height>
245
</height>
</size>
</property>
<property
name=
"styleSheet"
>
<string
notr=
"true"
>
QFrame#background_frame {
border: 2px;
border-radius: 4px;
border-style: solid;
border-color: #545454;
background-color: rgba(244, 244, 244, 228); /* 244, 244, 244, 228 or 248, 248, 248, 224 */
}
</string>
</property>
<property
name=
"frameShape"
>
<enum>
QFrame::StyledPanel
</enum>
</property>
<property
name=
"frameShadow"
>
<enum>
QFrame::Raised
</enum>
</property>
<layout
class=
"QVBoxLayout"
name=
"frame_layout"
>
<property
name=
"leftMargin"
>
<number>
15
</number>
</property>
<property
name=
"topMargin"
>
<number>
10
</number>
</property>
<item>
<widget
class=
"QLabel"
name=
"note_label"
>
<property
name=
"sizePolicy"
>
<sizepolicy
hsizetype=
"Preferred"
vsizetype=
"Preferred"
>
<horstretch>
0
</horstretch>
<verstretch>
0
</verstretch>
</sizepolicy>
</property>
<property
name=
"minimumSize"
>
<size>
<width>
0
</width>
<height>
0
</height>
</size>
</property>
<property
name=
"text"
>
<string>
Your credentials will be used once to authorize Blink to access your Google Contacts and will not be stored.
</string>
</property>
<property
name=
"textFormat"
>
<enum>
Qt::AutoText
</enum>
</property>
<property
name=
"scaledContents"
>
<bool>
false
</bool>
</property>
<property
name=
"alignment"
>
<set>
Qt::AlignJustify|Qt::AlignVCenter
</set>
</property>
<property
name=
"wordWrap"
>
<bool>
true
</bool>
</property>
<property
name=
"buddy"
>
<cstring></cstring>
</property>
</widget>
</item>
<item>
<spacer
name=
"before_form_spacer"
>
<property
name=
"orientation"
>
<enum>
Qt::Vertical
</enum>
</property>
<property
name=
"sizeType"
>
<enum>
QSizePolicy::Fixed
</enum>
</property>
<property
name=
"sizeHint"
stdset=
"0"
>
<size>
<width>
10
</width>
<height>
10
</height>
</size>
</property>
</spacer>
</item>
<item>
<layout
class=
"QGridLayout"
name=
"grid_layout"
>
<item
row=
"0"
column=
"0"
>
<widget
class=
"QLabel"
name=
"username_label"
>
<property
name=
"text"
>
<string>
Google account:
</string>
</property>
<property
name=
"alignment"
>
<set>
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
</set>
</property>
</widget>
</item>
<item
row=
"0"
column=
"1"
>
<widget
class=
"ValidatingLineEdit"
name=
"username_editor"
>
<property
name=
"minimumSize"
>
<size>
<width>
0
</width>
<height>
22
</height>
</size>
</property>
<property
name=
"inactiveText"
stdset=
"0"
>
<string>
user@domain
</string>
</property>
<property
name=
"widgetSpacing"
stdset=
"0"
>
<number>
0
</number>
</property>
</widget>
</item>
<item
row=
"1"
column=
"0"
>
<widget
class=
"QLabel"
name=
"password_label"
>
<property
name=
"text"
>
<string>
Password:
</string>
</property>
<property
name=
"alignment"
>
<set>
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
</set>
</property>
</widget>
</item>
<item
row=
"1"
column=
"1"
>
<widget
class=
"ValidatingLineEdit"
name=
"password_editor"
>
<property
name=
"minimumSize"
>
<size>
<width>
0
</width>
<height>
22
</height>
</size>
</property>
<property
name=
"echoMode"
>
<enum>
QLineEdit::Password
</enum>
</property>
<property
name=
"inactiveText"
stdset=
"0"
>
<string/>
</property>
<property
name=
"widgetSpacing"
stdset=
"0"
>
<number>
0
</number>
</property>
</widget>
</item>
<item
row=
"2"
column=
"0"
>
<widget
class=
"QLabel"
name=
"captcha_label"
>
<property
name=
"sizePolicy"
>
<sizepolicy
hsizetype=
"Preferred"
vsizetype=
"Preferred"
>
<horstretch>
0
</horstretch>
<verstretch>
0
</verstretch>
</sizepolicy>
</property>
<property
name=
"minimumSize"
>
<size>
<width>
0
</width>
<height>
0
</height>
</size>
</property>
<property
name=
"text"
>
<string>
Captcha:
</string>
</property>
<property
name=
"alignment"
>
<set>
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
</set>
</property>
</widget>
</item>
<item
row=
"2"
column=
"1"
>
<widget
class=
"ValidatingLineEdit"
name=
"captcha_editor"
>
<property
name=
"minimumSize"
>
<size>
<width>
0
</width>
<height>
22
</height>
</size>
</property>
</widget>
</item>
<item
row=
"3"
column=
"1"
>
<widget
class=
"QLabel"
name=
"captcha_image_label"
>
<property
name=
"enabled"
>
<bool>
true
</bool>
</property>
<property
name=
"sizePolicy"
>
<sizepolicy
hsizetype=
"Preferred"
vsizetype=
"Preferred"
>
<horstretch>
0
</horstretch>
<verstretch>
0
</verstretch>
</sizepolicy>
</property>
<property
name=
"minimumSize"
>
<size>
<width>
0
</width>
<height>
70
</height>
</size>
</property>
<property
name=
"text"
>
<string/>
</property>
<property
name=
"alignment"
>
<set>
Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer
name=
"after_form_spacer"
>
<property
name=
"orientation"
>
<enum>
Qt::Vertical
</enum>
</property>
<property
name=
"sizeHint"
stdset=
"0"
>
<size>
<width>
20
</width>
<height>
40
</height>
</size>
</property>
</spacer>
</item>
<item>
<widget
class=
"StatusLabel"
name=
"status_label"
>
<property
name=
"alignment"
>
<set>
Qt::AlignJustify|Qt::AlignVCenter
</set>
</property>
<property
name=
"wordWrap"
>
<bool>
true
</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer
name=
"frame_spacer"
>
<property
name=
"orientation"
>
<enum>
Qt::Vertical
</enum>
</property>
<property
name=
"sizeType"
>
<enum>
QSizePolicy::Fixed
</enum>
</property>
<property
name=
"sizeHint"
stdset=
"0"
>
<size>
<width>
10
</width>
<height>
10
</height>
</size>
</property>
</spacer>
</item>
<item>
<layout
class=
"QHBoxLayout"
name=
"horizontal_layout"
>
<property
name=
"spacing"
>
<number>
6
</number>
</property>
<item>
<spacer
name=
"button_box_spacer"
>
<property
name=
"orientation"
>
<enum>
Qt::Horizontal
</enum>
</property>
<property
name=
"sizeType"
>
<enum>
QSizePolicy::Fixed
</enum>
</property>
<property
name=
"sizeHint"
stdset=
"0"
>
<size>
<width>
200
</width>
<height>
20
</height>
</size>
</property>
</spacer>
</item>
<item>
<widget
class=
"QPushButton"
name=
"reject_button"
>
<property
name=
"sizePolicy"
>
<sizepolicy
hsizetype=
"Fixed"
vsizetype=
"Fixed"
>
<horstretch>
0
</horstretch>
<verstretch>
0
</verstretch>
</sizepolicy>
</property>
<property
name=
"minimumSize"
>
<size>
<width>
85
</width>
<height>
25
</height>
</size>
</property>
<property
name=
"text"
>
<string>
Cancel
</string>
</property>
</widget>
</item>
<item>
<widget
class=
"QPushButton"
name=
"authorize_button"
>
<property
name=
"sizePolicy"
>
<sizepolicy
hsizetype=
"Fixed"
vsizetype=
"Fixed"
>
<horstretch>
0
</horstretch>
<verstretch>
0
</verstretch>
</sizepolicy>
</property>
<property
name=
"minimumSize"
>
<size>
<width>
85
</width>
<height>
25
</height>
</size>
</property>
<property
name=
"text"
>
<string>
Authorize
</string>
</property>
<property
name=
"default"
>
<bool>
true
</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>
ValidatingLineEdit
</class>
<extends>
QLineEdit
</extends>
<header>
blink.widgets.lineedit
</header>
</customwidget>
<customwidget>
<class>
StatusLabel
</class>
<extends>
QLabel
</extends>
<header>
blink.widgets.labels
</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>
reject_button
</sender>
<signal>
clicked()
</signal>
<receiver>
Dialog
</receiver>
<slot>
reject()
</slot>
<hints>
<hint
type=
"sourcelabel"
>
<x>
257
</x>
<y>
202
</y>
</hint>
<hint
type=
"destinationlabel"
>
<x>
199
</x>
<y>
109
</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