Compare commits
73 Commits
Author | SHA1 | Date | |
---|---|---|---|
2ad78bc56d | |||
ff196d8d92 | |||
7a77b56560 | |||
b982df70ea | |||
a56693547c | |||
699fe59706 | |||
e78bc846b8 | |||
fc2b53d4a5 | |||
82184b3df1 | |||
5cfa484a67 | |||
27a5735dc1 | |||
a714c1045d | |||
a4da3a7afa | |||
190877f5b9 | |||
55f13cbfd1 | |||
b9cbf809b5 | |||
5a0843d98b | |||
fb74ea4455 | |||
42aa102d1d | |||
02aa2981b5 | |||
bde1160562 | |||
dd53c6a842 | |||
3936f652e9 | |||
fa5b77fd2d | |||
cbeb25e0be | |||
3cb961957c | |||
65930716f9 | |||
03e34cc43f | |||
48360ea54c | |||
def6e10b93 | |||
24d27f9f47 | |||
7bdc506ff2 | |||
23b045070a | |||
6a7a38ef02 | |||
0e86fd66df | |||
e375dca9cc | |||
fd9bfa52f3 | |||
5fd5a9bd85 | |||
e75190f767 | |||
96705ed1cc | |||
5c4bfceee7 | |||
85061454a6 | |||
fe4af18196 | |||
183dfadc77 | |||
d3c8c99048 | |||
63774ba5df | |||
53a381222f | |||
7e63d9634a | |||
f6affc14ef | |||
9bf072e122 | |||
3ed4e8f511 | |||
94eb292b16 | |||
a0b23b0faa | |||
04f9852050 | |||
01854ff2d0 | |||
298dba4d6c | |||
bc248a9e30 | |||
7901aad3e7 | |||
6a244b30c1 | |||
17a8d81486 | |||
c114e9b6c3 | |||
672dafc701 | |||
d02ee92508 | |||
42617c8816 | |||
453b5e25be | |||
8fb263dadc | |||
4b4075606e | |||
b02e063302 | |||
cc8c5f7e99 | |||
51ec1af369 | |||
74d7c67d8c | |||
b40c0a868b | |||
b6d13166bc |
20
README.md
@ -1,5 +1,5 @@
|
|||||||
# Toxygen
|
# Toxygen
|
||||||
Toxygen is cross-platform [Tox](https://tox.chat/) client written on Python
|
Toxygen is cross-platform [Tox](https://tox.chat/) client written in Python3
|
||||||
|
|
||||||
[](https://github.com/xveduk/toxygen/releases/latest)
|
[](https://github.com/xveduk/toxygen/releases/latest)
|
||||||
[](https://github.com/xveduk/toxygen/issues)
|
[](https://github.com/xveduk/toxygen/issues)
|
||||||
@ -15,10 +15,16 @@ Toxygen is cross-platform [Tox](https://tox.chat/) client written on Python
|
|||||||
- [x] 1v1 messages
|
- [x] 1v1 messages
|
||||||
- [x] File transfers
|
- [x] File transfers
|
||||||
- [x] Audio
|
- [x] Audio
|
||||||
|
- [x] Plugins support
|
||||||
- [x] Chat history
|
- [x] Chat history
|
||||||
|
- [x] Emoticons
|
||||||
|
- [x] Stickers
|
||||||
- [x] Screenshots
|
- [x] Screenshots
|
||||||
- [x] Name lookups (toxme.io support)
|
- [x] Name lookups (toxme.io support)
|
||||||
|
- [x] Save file encryption
|
||||||
- [x] Profile import and export
|
- [x] Profile import and export
|
||||||
|
- [x] Faux offline messaging
|
||||||
|
- [x] Faux offline file transfers
|
||||||
- [x] Inline images
|
- [x] Inline images
|
||||||
- [x] Message splitting
|
- [x] Message splitting
|
||||||
- [x] Proxy support
|
- [x] Proxy support
|
||||||
@ -31,26 +37,24 @@ Toxygen is cross-platform [Tox](https://tox.chat/) client written on Python
|
|||||||
- [x] Typing notifications
|
- [x] Typing notifications
|
||||||
- [x] Changing nospam
|
- [x] Changing nospam
|
||||||
- [x] File resuming
|
- [x] File resuming
|
||||||
- [x] Save file encryption
|
- [x] Read receipts
|
||||||
- [x] Plugins support
|
|
||||||
- [ ] Video
|
- [ ] Video
|
||||||
- [ ] Group chats
|
|
||||||
- [ ] Read receipts
|
|
||||||
- [ ] Faux offline messaging
|
|
||||||
- [ ] Desktop sharing
|
- [ ] Desktop sharing
|
||||||
|
- [ ] Group chats
|
||||||
|
|
||||||
###Downloads
|
###Downloads
|
||||||
|
[Releases](https://github.com/xveduk/toxygen/releases)
|
||||||
|
|
||||||
[Download last stable version](https://github.com/xveduk/toxygen/archive/master.zip)
|
[Download last stable version](https://github.com/xveduk/toxygen/archive/master.zip)
|
||||||
|
|
||||||
[Download develop version](https://github.com/xveduk/toxygen/archive/develop.zip)
|
[Download develop version](https://github.com/xveduk/toxygen/archive/develop.zip)
|
||||||
|
|
||||||
[Releases](https://github.com/xveduk/toxygen/releases)
|
|
||||||
|
|
||||||
###Screenshots
|
###Screenshots
|
||||||
*Toxygen on Ubuntu and Windows*
|
*Toxygen on Ubuntu and Windows*
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
###Docs
|
###Docs
|
||||||
[Check /docs/ for more info](/docs/)
|
[Check /docs/ for more info](/docs/)
|
||||||
|
|
||||||
|
@ -3,14 +3,8 @@
|
|||||||
You can compile Toxygen using [PyInstaller](http://www.pyinstaller.org/)
|
You can compile Toxygen using [PyInstaller](http://www.pyinstaller.org/)
|
||||||
|
|
||||||
Install PyInstaller:
|
Install PyInstaller:
|
||||||
``pip install pyinstaller``
|
``pip3 install pyinstaller``
|
||||||
|
|
||||||
On Linux:
|
|
||||||
|
|
||||||
``pyinstaller --windowed main.py``
|
|
||||||
|
|
||||||
On Windows:
|
|
||||||
|
|
||||||
``pyinstaller --windowed --icon images/icon.ico main.py``
|
``pyinstaller --windowed --icon images/icon.ico main.py``
|
||||||
|
|
||||||
Don't forget to copy /images/, /sounds/, /translations/, /styles/, to /dist/main/
|
Don't forget to copy /images/, /sounds/, /translations/, /styles/, /smileys/, /stickers/ (and /libs/libtox.dll on Windows) to /dist/main/
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
#Issues
|
#Issues
|
||||||
|
|
||||||
Help us find all bugs in Toxygen! Please provide following info:
|
Help us find all bugs in Toxygen! Please provide following info:
|
||||||
@ -9,14 +7,14 @@ Help us find all bugs in Toxygen! Please provide following info:
|
|||||||
- Toxygen executable info - .py or precompiled binary
|
- Toxygen executable info - .py or precompiled binary
|
||||||
- Steps to reproduce the bug
|
- Steps to reproduce the bug
|
||||||
|
|
||||||
Want to see new feature in Toxygen? [Open issue!](https://github.com/xveduk/toxygen/issues)
|
Want to see new feature in Toxygen? [Ask for it!](https://github.com/xveduk/toxygen/issues)
|
||||||
|
|
||||||
#Pull requests
|
#Pull requests
|
||||||
|
|
||||||
Developer? Feel free to open pull request. Our dev team is small so we glad to get help.
|
Developer? Feel free to open pull request. Our dev team is small so we glad to get help.
|
||||||
Don't know what to do? Impove UI, fix [issues](https://github.com/xveduk/toxygen/issues) or implement features from our TODO list.
|
Don't know what to do? Improve UI, fix [issues](https://github.com/xveduk/toxygen/issues) or implement features from our TODO list.
|
||||||
You can find our TODO's in code and [here](/README.md)
|
You can find our TODO's in code, issues list and [here](/README.md). Also you can implement [plugins](/docs/plugins.md) for Toxygen.
|
||||||
|
|
||||||
#Translations
|
#Translations
|
||||||
|
|
||||||
Help us translate Toxygen! Translate can be created using pyside-lupdate and QT Linguist.
|
Help us translate Toxygen! Translation can be created using pyside-lupdate (``pyside-lupdate toxygen.pro``) and QT Linguist.
|
@ -1,12 +1,15 @@
|
|||||||
# How to install Toxygen
|
# How to install Toxygen
|
||||||
|
|
||||||
|
## Use precompiled binary:
|
||||||
|
[Check our releases page](https://github.com/xveduk/toxygen/releases)
|
||||||
|
|
||||||
## From source code (recommended for developers)
|
## From source code (recommended for developers)
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
1. [Download and install latest Python 2.7](https://www.python.org/downloads/windows/)
|
1. [Download and install latest Python 3.4](https://www.python.org/downloads/windows/)
|
||||||
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4) *(PyQt4 support will be added later)*
|
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
|
||||||
3. Install PyAudio: ``python -m pip install pyaudio``
|
3. Install PyAudio: ``pip3 install pyaudio``
|
||||||
4. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
|
4. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
|
||||||
5. Unpack archive
|
5. Unpack archive
|
||||||
6. Download latest libtox.dll build, download latest libsodium.a build, put it into \src\libs\
|
6. Download latest libtox.dll build, download latest libsodium.a build, put it into \src\libs\
|
||||||
@ -23,22 +26,23 @@
|
|||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
- Install Python2.7:
|
Dependencies:
|
||||||
``sudo apt-get install python2.7``
|
|
||||||
- [Install PySide](https://wiki.qt.io/PySide_Binaries_Linux) *(PyQt4 support will be added later)*
|
1. Install latest Python3.4:
|
||||||
- Install PyAudio:
|
``sudo apt-get install python3``
|
||||||
|
2. [Install PySide](https://wiki.qt.io/PySide_Binaries_Linux) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
|
||||||
|
3. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system (install in /usr/lib/)
|
||||||
|
4. Install PyAudio:
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install portaudio19-dev
|
sudo apt-get install portaudio19-dev
|
||||||
sudo pip install pyaudio
|
sudo apt-get install python3-pyaudio
|
||||||
```
|
```
|
||||||
- [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
|
Toxygen:
|
||||||
- Unpack archive
|
|
||||||
- Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) in your system (install in /usr/lib/)
|
|
||||||
- Run app:
|
|
||||||
``python main.py``
|
|
||||||
|
|
||||||
## Use precompiled binary:
|
1. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
|
||||||
[Check our releases page](https://github.com/xveduk/toxygen/releases)
|
2. Unpack archive
|
||||||
|
3. Run app:
|
||||||
|
``python3 main.py``
|
||||||
|
|
||||||
## Compile Toxygen
|
## Compile Toxygen
|
||||||
Check [compile.md](/docs/compile.md) for more info
|
Check [compile.md](/docs/compile.md) for more info
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#Plugins API
|
#Plugins API
|
||||||
|
|
||||||
In Toxygen plugin is single python module (.py file) and directory with data associated with it.
|
In Toxygen plugin is single python (supported Python 3.0 - 3.4) module (.py file) and directory with data associated with it.
|
||||||
Every module must contain one class derived from PluginSuperClass defined in [plugin_super_class.py](/src/plugins/plugin_super_class.py). Instance of this class will be created by PluginLoader class (defined in [plugin_support.py](/src/plugin_support.py) ). This class can enable/disable plugins and send data to it.
|
Every module must contain one class derived from PluginSuperClass defined in [plugin_super_class.py](/src/plugins/plugin_super_class.py). Instance of this class will be created by PluginLoader class (defined in [plugin_support.py](/src/plugin_support.py) ). This class can enable/disable plugins and send data to it.
|
||||||
|
|
||||||
Every plugin has it's own full name and unique short name (1-5 symbols). Main app can get it using special methods.
|
Every plugin has it's own full name and unique short name (1-5 symbols). Main app can get it using special methods.
|
||||||
@ -37,11 +37,11 @@ Other methods:
|
|||||||
|
|
||||||
About import:
|
About import:
|
||||||
|
|
||||||
import statement will not work in case you import module that wasn't previously imported by main program and user use precompiled binary. It's recommended to use imp module and dynamic import instead.
|
import statement will not work in case you import module that wasn't previously imported by main program and user use precompiled binary. It's recommended to use dynamic import instead.
|
||||||
|
|
||||||
About GUI:
|
About GUI:
|
||||||
|
|
||||||
It's strictly recommended to support both PySide and PyQt4 in GUI.
|
It's strictly recommended to support both PySide and PyQt4 in GUI. Plugin can not have GUI at all.
|
||||||
|
|
||||||
Exceptions:
|
Exceptions:
|
||||||
|
|
||||||
@ -49,5 +49,5 @@ Plugin's methods should not raise exceptions.
|
|||||||
|
|
||||||
#Examples
|
#Examples
|
||||||
|
|
||||||
You can find example of a plugin in [official repo](https://github.com/ingvar1995/toxygen_plugins)
|
You can find examples in [official repo](https://github.com/ingvar1995/toxygen_plugins)
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#Plugins
|
#Plugins
|
||||||
|
|
||||||
Toxygen is the first [Tox](https://tox.chat/) client with plugins support. Plugin is Python module and directory with plugin's data which provide some additional functionality.
|
Toxygen is the first [Tox](https://tox.chat/) client with plugins support. Plugin is Python 3.4 module (.py file) and directory with plugin's data which provide some additional functionality.
|
||||||
|
|
||||||
#How to write plugin
|
#How to write plugin
|
||||||
|
|
||||||
@ -10,9 +10,11 @@ Check [Plugin API](/docs/plugin_api.md) for more info
|
|||||||
|
|
||||||
Toxygen comes without preinstalled plugins.
|
Toxygen comes without preinstalled plugins.
|
||||||
|
|
||||||
1. Put plugin and directory with it's data into /src/plugins/
|
1. Put plugin and directory with its data into /src/plugins/
|
||||||
2. Restart Toxygen
|
2. Restart Toxygen
|
||||||
|
|
||||||
|
##Note: /src/plugins/ should contain plugin_super_class.py and __init__.py
|
||||||
|
|
||||||
#Plugins list
|
#Plugins list
|
||||||
|
|
||||||
WARNING: It is unsecure to install plugin not from this list!
|
WARNING: It is unsecure to install plugin not from this list!
|
||||||
|
11
docs/smileys_and_stickers.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#Smileys
|
||||||
|
|
||||||
|
Toxygen support smileys. Smiley is small picture which replaces some symbol or combination of symbols. If you want to create your own smiley pack, create directory in src/smileys/. This directory must contain images with smileys and config.json. Example of config.json:
|
||||||
|
|
||||||
|
{":)": "one.png", ":(": "two.png"}
|
||||||
|
|
||||||
|
Animated smileys (.gif) are supported too.
|
||||||
|
|
||||||
|
#Stickers
|
||||||
|
|
||||||
|
Sticker is inline image. If you want to create your own smiley pack, create directory in src/stickers/ and place your stickers there.
|
BIN
docs/ubuntu.png
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 109 KiB |
BIN
docs/windows.png
Before Width: | Height: | Size: 317 KiB After Width: | Height: | Size: 81 KiB |
@ -65,25 +65,27 @@ class IncomingCallWidget(widgets.CenteredWidget):
|
|||||||
QtCore.QThread.__init__(self)
|
QtCore.QThread.__init__(self)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
class AudioFile(object):
|
class AudioFile:
|
||||||
chunk = 1024
|
chunk = 1024
|
||||||
|
|
||||||
def __init__(self, fl):
|
def __init__(self, fl):
|
||||||
self.stop = False
|
self.stop = False
|
||||||
self.wf = wave.open(fl, 'rb')
|
self.fl = fl
|
||||||
|
self.wf = wave.open(self.fl, 'rb')
|
||||||
self.p = pyaudio.PyAudio()
|
self.p = pyaudio.PyAudio()
|
||||||
self.stream = self.p.open(
|
self.stream = self.p.open(
|
||||||
format=self.p.get_format_from_width(self.wf.getsampwidth()),
|
format=self.p.get_format_from_width(self.wf.getsampwidth()),
|
||||||
channels=self.wf.getnchannels(),
|
channels=self.wf.getnchannels(),
|
||||||
rate=self.wf.getframerate(),
|
rate=self.wf.getframerate(),
|
||||||
output=True
|
output=True)
|
||||||
)
|
|
||||||
|
|
||||||
def play(self):
|
def play(self):
|
||||||
data = self.wf.readframes(self.chunk)
|
while not self.stop:
|
||||||
while data and not self.stop:
|
|
||||||
self.stream.write(data)
|
|
||||||
data = self.wf.readframes(self.chunk)
|
data = self.wf.readframes(self.chunk)
|
||||||
|
while data and not self.stop:
|
||||||
|
self.stream.write(data)
|
||||||
|
data = self.wf.readframes(self.chunk)
|
||||||
|
self.wf = wave.open(self.fl, 'rb')
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.stream.close()
|
self.stream.close()
|
||||||
@ -107,3 +109,31 @@ class IncomingCallWidget(widgets.CenteredWidget):
|
|||||||
|
|
||||||
def set_pixmap(self, pixmap):
|
def set_pixmap(self, pixmap):
|
||||||
self.avatar_label.setPixmap(pixmap)
|
self.avatar_label.setPixmap(pixmap)
|
||||||
|
|
||||||
|
|
||||||
|
class AudioMessageRecorder(widgets.CenteredWidget):
|
||||||
|
|
||||||
|
def __init__(self, friend_number, name):
|
||||||
|
super(AudioMessageRecorder, self).__init__()
|
||||||
|
self.label = QtGui.QLabel(self)
|
||||||
|
self.label.setGeometry(QtCore.QRect(10, 20, 250, 20))
|
||||||
|
text = QtGui.QApplication.translate("MenuWindow", "Send audio message to friend {}", None, QtGui.QApplication.UnicodeUTF8)
|
||||||
|
self.label.setText(text.format(name))
|
||||||
|
self.record = QtGui.QPushButton(self)
|
||||||
|
self.record.setGeometry(QtCore.QRect(20, 100, 150, 150))
|
||||||
|
|
||||||
|
self.record.setText(QtGui.QApplication.translate("MenuWindow", "Start recording", None,
|
||||||
|
QtGui.QApplication.UnicodeUTF8))
|
||||||
|
self.record.clicked.connect(self.start_or_stop_recording)
|
||||||
|
self.recording = False
|
||||||
|
self.friend_num = friend_number
|
||||||
|
|
||||||
|
def start_or_stop_recording(self):
|
||||||
|
if not self.recording:
|
||||||
|
self.recording = True
|
||||||
|
self.record.setText(QtGui.QApplication.translate("MenuWindow", "Stop recording", None,
|
||||||
|
QtGui.QApplication.UnicodeUTF8))
|
||||||
|
else:
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
|
|
||||||
class Node(object):
|
class Node:
|
||||||
def __init__(self, ip, port, tox_key, rand):
|
def __init__(self, ip, port, tox_key, rand):
|
||||||
self._ip, self._port, self._tox_key, self.rand = ip, port, tox_key, rand
|
self._ip, self._port, self._tox_key, self.rand = ip, port, tox_key, rand
|
||||||
|
|
||||||
@ -75,13 +75,9 @@ def node_generator():
|
|||||||
"5625A62618CB4FCA70E147A71B29695F38CC65FF0CBD68AD46254585BE564802",
|
"5625A62618CB4FCA70E147A71B29695F38CC65FF0CBD68AD46254585BE564802",
|
||||||
"31910C0497D347FF160D6F3A6C0E317BAFA71E8E03BC4CBB2A185C9D4FB8B31E"
|
"31910C0497D347FF160D6F3A6C0E317BAFA71E8E03BC4CBB2A185C9D4FB8B31E"
|
||||||
]
|
]
|
||||||
for i in xrange(len(ips)):
|
for i in range(len(ips)):
|
||||||
nodes.append(Node(ips[i], ports[i], ids[i], random.randint(0, 1000000)))
|
nodes.append(Node(ips[i], ports[i], ids[i], random.randint(0, 1000000)))
|
||||||
arr = sorted(nodes, key=lambda x: x.rand)[:4]
|
arr = sorted(nodes, key=lambda x: x.rand)[:4]
|
||||||
for elem in arr:
|
for elem in arr:
|
||||||
yield elem.get_data()
|
yield elem.get_data()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
for elem in node_generator():
|
|
||||||
print str(elem)
|
|
||||||
|
@ -43,7 +43,7 @@ def self_connection_status(tox_link):
|
|||||||
Current user changed connection status (offline, UDP, TCP)
|
Current user changed connection status (offline, UDP, TCP)
|
||||||
"""
|
"""
|
||||||
def wrapped(tox, connection, user_data):
|
def wrapped(tox, connection, user_data):
|
||||||
print 'Connection status: ', str(connection)
|
print('Connection status: ', str(connection))
|
||||||
profile = Profile.get_instance()
|
profile = Profile.get_instance()
|
||||||
if profile.status is None:
|
if profile.status is None:
|
||||||
status = tox_link.self_get_status()
|
status = tox_link.self_get_status()
|
||||||
@ -62,12 +62,13 @@ def friend_status(tox, friend_num, new_status, user_data):
|
|||||||
"""
|
"""
|
||||||
Check friend's status (none, busy, away)
|
Check friend's status (none, busy, away)
|
||||||
"""
|
"""
|
||||||
print "Friend's #{} status changed! New status: {}".format(friend_num, new_status)
|
print("Friend's #{} status changed! New status: {}".format(friend_num, new_status))
|
||||||
profile = Profile.get_instance()
|
profile = Profile.get_instance()
|
||||||
friend = profile.get_friend_by_number(friend_num)
|
friend = profile.get_friend_by_number(friend_num)
|
||||||
if friend.status is None and Settings.get_instance()['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
if friend.status is None and Settings.get_instance()['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
||||||
sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
|
sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
|
||||||
invoke_in_main_thread(friend.set_status, new_status)
|
invoke_in_main_thread(friend.set_status, new_status)
|
||||||
|
invoke_in_main_thread(profile.send_files, friend_num)
|
||||||
invoke_in_main_thread(profile.update_filtration)
|
invoke_in_main_thread(profile.update_filtration)
|
||||||
|
|
||||||
|
|
||||||
@ -75,7 +76,7 @@ def friend_connection_status(tox, friend_num, new_status, user_data):
|
|||||||
"""
|
"""
|
||||||
Check friend's connection status (offline, udp, tcp)
|
Check friend's connection status (offline, udp, tcp)
|
||||||
"""
|
"""
|
||||||
print "Friend #{} connected! Friend's status: {}".format(friend_num, new_status)
|
print("Friend #{} connection status: {}".format(friend_num, new_status))
|
||||||
profile = Profile.get_instance()
|
profile = Profile.get_instance()
|
||||||
friend = profile.get_friend_by_number(friend_num)
|
friend = profile.get_friend_by_number(friend_num)
|
||||||
if new_status == TOX_CONNECTION['NONE']:
|
if new_status == TOX_CONNECTION['NONE']:
|
||||||
@ -85,7 +86,7 @@ def friend_connection_status(tox, friend_num, new_status, user_data):
|
|||||||
sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
|
sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
|
||||||
elif friend.status is None:
|
elif friend.status is None:
|
||||||
invoke_in_main_thread(profile.send_avatar, friend_num)
|
invoke_in_main_thread(profile.send_avatar, friend_num)
|
||||||
PluginLoader.get_instance().friend_online(friend_num)
|
invoke_in_main_thread(PluginLoader.get_instance().friend_online, friend_num)
|
||||||
|
|
||||||
|
|
||||||
def friend_name(tox, friend_num, name, size, user_data):
|
def friend_name(tox, friend_num, name, size, user_data):
|
||||||
@ -93,11 +94,8 @@ def friend_name(tox, friend_num, name, size, user_data):
|
|||||||
Friend changed his name
|
Friend changed his name
|
||||||
"""
|
"""
|
||||||
profile = Profile.get_instance()
|
profile = Profile.get_instance()
|
||||||
friend = profile.get_friend_by_number(friend_num)
|
print('New name: ', friend_num, name)
|
||||||
print 'New name: ', str(friend_num), str(name)
|
invoke_in_main_thread(profile.new_name, friend_num, name)
|
||||||
invoke_in_main_thread(friend.set_name, name)
|
|
||||||
if profile.get_active_number() == friend_num:
|
|
||||||
invoke_in_main_thread(profile.set_active)
|
|
||||||
|
|
||||||
|
|
||||||
def friend_status_message(tox, friend_num, status_message, size, user_data):
|
def friend_status_message(tox, friend_num, status_message, size, user_data):
|
||||||
@ -108,7 +106,8 @@ def friend_status_message(tox, friend_num, status_message, size, user_data):
|
|||||||
profile = Profile.get_instance()
|
profile = Profile.get_instance()
|
||||||
friend = profile.get_friend_by_number(friend_num)
|
friend = profile.get_friend_by_number(friend_num)
|
||||||
invoke_in_main_thread(friend.set_status_message, status_message)
|
invoke_in_main_thread(friend.set_status_message, status_message)
|
||||||
print 'User #{} has new status: {}'.format(friend_num, status_message)
|
print('User #{} has new status: {}'.format(friend_num, status_message))
|
||||||
|
invoke_in_main_thread(profile.send_messages, friend_num)
|
||||||
if profile.get_active_number() == friend_num:
|
if profile.get_active_number() == friend_num:
|
||||||
invoke_in_main_thread(profile.set_active)
|
invoke_in_main_thread(profile.set_active)
|
||||||
|
|
||||||
@ -120,13 +119,15 @@ def friend_message(window, tray):
|
|||||||
def wrapped(tox, friend_number, message_type, message, size, user_data):
|
def wrapped(tox, friend_number, message_type, message, size, user_data):
|
||||||
profile = Profile.get_instance()
|
profile = Profile.get_instance()
|
||||||
settings = Settings.get_instance()
|
settings = Settings.get_instance()
|
||||||
|
message = str(message, 'utf-8')
|
||||||
invoke_in_main_thread(profile.new_message, friend_number, message_type, message)
|
invoke_in_main_thread(profile.new_message, friend_number, message_type, message)
|
||||||
if not window.isActiveWindow():
|
if not window.isActiveWindow():
|
||||||
friend = profile.get_friend_by_number(friend_number)
|
friend = profile.get_friend_by_number(friend_number)
|
||||||
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
||||||
invoke_in_main_thread(tray_notification, friend.name, message.decode('utf8'), tray, window)
|
invoke_in_main_thread(tray_notification, friend.name, message, tray, window)
|
||||||
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
||||||
sound_notification(SOUND_NOTIFICATION['MESSAGE'])
|
sound_notification(SOUND_NOTIFICATION['MESSAGE'])
|
||||||
|
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png'))
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
@ -134,17 +135,24 @@ def friend_request(tox, public_key, message, message_size, user_data):
|
|||||||
"""
|
"""
|
||||||
Called when user get new friend request
|
Called when user get new friend request
|
||||||
"""
|
"""
|
||||||
print 'Friend request'
|
print('Friend request')
|
||||||
profile = Profile.get_instance()
|
profile = Profile.get_instance()
|
||||||
key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE])
|
key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE])
|
||||||
tox_id = bin_to_string(key, TOX_PUBLIC_KEY_SIZE)
|
tox_id = bin_to_string(key, TOX_PUBLIC_KEY_SIZE)
|
||||||
if tox_id not in Settings.get_instance()['blocked']:
|
if tox_id not in Settings.get_instance()['blocked']:
|
||||||
invoke_in_main_thread(profile.process_friend_request, tox_id, message.decode('utf-8'))
|
invoke_in_main_thread(profile.process_friend_request, tox_id, str(message, 'utf-8'))
|
||||||
|
|
||||||
|
|
||||||
def friend_typing(tox, friend_number, typing, user_data):
|
def friend_typing(tox, friend_number, typing, user_data):
|
||||||
invoke_in_main_thread(Profile.get_instance().friend_typing, friend_number, typing)
|
invoke_in_main_thread(Profile.get_instance().friend_typing, friend_number, typing)
|
||||||
|
|
||||||
|
|
||||||
|
def friend_read_receipt(tox, friend_number, message_id, user_data):
|
||||||
|
profile = Profile.get_instance()
|
||||||
|
profile.get_friend_by_number(friend_number).dec_receipt()
|
||||||
|
if friend_number == profile.get_active_number():
|
||||||
|
invoke_in_main_thread(profile.receipt)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Callbacks - file transfers
|
# Callbacks - file transfers
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
@ -158,11 +166,11 @@ def tox_file_recv(window, tray):
|
|||||||
profile = Profile.get_instance()
|
profile = Profile.get_instance()
|
||||||
settings = Settings.get_instance()
|
settings = Settings.get_instance()
|
||||||
if file_type == TOX_FILE_KIND['DATA']:
|
if file_type == TOX_FILE_KIND['DATA']:
|
||||||
print 'file'
|
print('File')
|
||||||
try:
|
try:
|
||||||
file_name = unicode(file_name[:file_name_size].decode('utf-8'))
|
file_name = str(file_name[:file_name_size], 'utf-8')
|
||||||
except:
|
except:
|
||||||
file_name = u'toxygen_file'
|
file_name = 'toxygen_file'
|
||||||
invoke_in_main_thread(profile.incoming_file_transfer,
|
invoke_in_main_thread(profile.incoming_file_transfer,
|
||||||
friend_number,
|
friend_number,
|
||||||
file_number,
|
file_number,
|
||||||
@ -171,11 +179,13 @@ def tox_file_recv(window, tray):
|
|||||||
if not window.isActiveWindow():
|
if not window.isActiveWindow():
|
||||||
friend = profile.get_friend_by_number(friend_number)
|
friend = profile.get_friend_by_number(friend_number)
|
||||||
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
||||||
invoke_in_main_thread(tray_notification, 'File from ' + friend.name, file_name, tray, window)
|
file_from = QtGui.QApplication.translate("Callback", "File from", None, QtGui.QApplication.UnicodeUTF8)
|
||||||
|
invoke_in_main_thread(tray_notification, file_from + ' ' + friend.name, file_name, tray, window)
|
||||||
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
||||||
sound_notification(SOUND_NOTIFICATION['FILE_TRANSFER'])
|
sound_notification(SOUND_NOTIFICATION['FILE_TRANSFER'])
|
||||||
|
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png'))
|
||||||
else: # AVATAR
|
else: # AVATAR
|
||||||
print 'Avatar'
|
print ('Avatar')
|
||||||
invoke_in_main_thread(profile.incoming_avatar,
|
invoke_in_main_thread(profile.incoming_avatar,
|
||||||
friend_number,
|
friend_number,
|
||||||
file_number,
|
file_number,
|
||||||
@ -194,7 +204,7 @@ def file_recv_chunk(tox, friend_number, file_number, position, chunk, length, us
|
|||||||
position,
|
position,
|
||||||
None)
|
None)
|
||||||
else:
|
else:
|
||||||
Profile.get_instance().incoming_chunk(friend_number, file_number, position,chunk[:length])
|
Profile.get_instance().incoming_chunk(friend_number, file_number, position, chunk[:length])
|
||||||
|
|
||||||
|
|
||||||
def file_chunk_request(tox, friend_number, file_number, position, size, user_data):
|
def file_chunk_request(tox, friend_number, file_number, position, size, user_data):
|
||||||
@ -216,11 +226,11 @@ def file_recv_control(tox, friend_number, file_number, file_control, user_data):
|
|||||||
Friend cancelled, paused or resumed file transfer
|
Friend cancelled, paused or resumed file transfer
|
||||||
"""
|
"""
|
||||||
if file_control == TOX_FILE_CONTROL['CANCEL']:
|
if file_control == TOX_FILE_CONTROL['CANCEL']:
|
||||||
Profile.get_instance().cancel_transfer(friend_number, file_number, True)
|
invoke_in_main_thread(Profile.get_instance().cancel_transfer, friend_number, file_number, True)
|
||||||
elif file_control == TOX_FILE_CONTROL['PAUSE']:
|
elif file_control == TOX_FILE_CONTROL['PAUSE']:
|
||||||
Profile.get_instance().pause_transfer(friend_number, file_number, True)
|
invoke_in_main_thread(Profile.get_instance().pause_transfer, friend_number, file_number, True)
|
||||||
elif file_control == TOX_FILE_CONTROL['RESUME']:
|
elif file_control == TOX_FILE_CONTROL['RESUME']:
|
||||||
Profile.get_instance().resume_transfer(friend_number, file_number, True)
|
invoke_in_main_thread(Profile.get_instance().resume_transfer, friend_number, file_number, True)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Callbacks - custom packets
|
# Callbacks - custom packets
|
||||||
@ -251,7 +261,7 @@ def call_state(toxav, friend_number, mask, user_data):
|
|||||||
"""
|
"""
|
||||||
New call state
|
New call state
|
||||||
"""
|
"""
|
||||||
print friend_number, mask
|
print(friend_number, mask)
|
||||||
if mask == TOXAV_FRIEND_CALL_STATE['FINISHED'] or mask == TOXAV_FRIEND_CALL_STATE['ERROR']:
|
if mask == TOXAV_FRIEND_CALL_STATE['FINISHED'] or mask == TOXAV_FRIEND_CALL_STATE['ERROR']:
|
||||||
invoke_in_main_thread(Profile.get_instance().stop_call, friend_number, True)
|
invoke_in_main_thread(Profile.get_instance().stop_call, friend_number, True)
|
||||||
else:
|
else:
|
||||||
@ -262,7 +272,7 @@ def call(toxav, friend_number, audio, video, user_data):
|
|||||||
"""
|
"""
|
||||||
Incoming call from friend
|
Incoming call from friend
|
||||||
"""
|
"""
|
||||||
print friend_number, audio, video
|
print(friend_number, audio, video)
|
||||||
invoke_in_main_thread(Profile.get_instance().incoming_call, audio, video, friend_number)
|
invoke_in_main_thread(Profile.get_instance().incoming_call, audio, video, friend_number)
|
||||||
|
|
||||||
|
|
||||||
@ -270,9 +280,9 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud
|
|||||||
"""
|
"""
|
||||||
New audio chunk
|
New audio chunk
|
||||||
"""
|
"""
|
||||||
print audio_samples_per_channel, audio_channels_count, rate
|
# print(audio_samples_per_channel, audio_channels_count, rate)
|
||||||
Profile.get_instance().call.chunk(
|
Profile.get_instance().call.chunk(
|
||||||
''.join(chr(x) for x in samples[:audio_samples_per_channel * 2 * audio_channels_count]),
|
bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]),
|
||||||
audio_channels_count,
|
audio_channels_count,
|
||||||
rate)
|
rate)
|
||||||
|
|
||||||
@ -298,6 +308,7 @@ def init_callbacks(tox, window, tray):
|
|||||||
tox.callback_friend_status_message(friend_status_message, 0)
|
tox.callback_friend_status_message(friend_status_message, 0)
|
||||||
tox.callback_friend_request(friend_request, 0)
|
tox.callback_friend_request(friend_request, 0)
|
||||||
tox.callback_friend_typing(friend_typing, 0)
|
tox.callback_friend_typing(friend_typing, 0)
|
||||||
|
tox.callback_friend_read_receipt(friend_read_receipt, 0)
|
||||||
|
|
||||||
tox.callback_file_recv(tox_file_recv(window, tray), 0)
|
tox.callback_file_recv(tox_file_recv(window, tray), 0)
|
||||||
tox.callback_file_recv_chunk(file_recv_chunk, 0)
|
tox.callback_file_recv_chunk(file_recv_chunk, 0)
|
||||||
|
@ -4,6 +4,7 @@ import threading
|
|||||||
import settings
|
import settings
|
||||||
from toxav_enums import *
|
from toxav_enums import *
|
||||||
# TODO: play sound until outgoing call will be started or cancelled and add timeout
|
# TODO: play sound until outgoing call will be started or cancelled and add timeout
|
||||||
|
# TODO: add widget for call
|
||||||
|
|
||||||
CALL_TYPE = {
|
CALL_TYPE = {
|
||||||
'NONE': 0,
|
'NONE': 0,
|
||||||
@ -12,7 +13,7 @@ CALL_TYPE = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class AV(object):
|
class AV:
|
||||||
|
|
||||||
def __init__(self, toxav):
|
def __init__(self, toxav):
|
||||||
self._toxav = toxav
|
self._toxav = toxav
|
||||||
@ -29,7 +30,7 @@ class AV(object):
|
|||||||
self._audio_rate = 8000
|
self._audio_rate = 8000
|
||||||
self._audio_channels = 1
|
self._audio_channels = 1
|
||||||
self._audio_duration = 60
|
self._audio_duration = 60
|
||||||
self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration / 1000
|
self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration // 1000
|
||||||
|
|
||||||
def __contains__(self, friend_number):
|
def __contains__(self, friend_number):
|
||||||
return friend_number in self._calls
|
return friend_number in self._calls
|
||||||
|
113
src/contact.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import os
|
||||||
|
from settings import *
|
||||||
|
try:
|
||||||
|
from PySide import QtCore, QtGui
|
||||||
|
except ImportError:
|
||||||
|
from PyQt4 import QtCore, QtGui
|
||||||
|
from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
|
||||||
|
|
||||||
|
|
||||||
|
class Contact:
|
||||||
|
"""
|
||||||
|
Class encapsulating TOX contact
|
||||||
|
Properties: name (alias of contact or name), status_message, status (connection status)
|
||||||
|
widget - widget for update
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name, status_message, widget, tox_id):
|
||||||
|
"""
|
||||||
|
:param name: name, example: 'Toxygen user'
|
||||||
|
:param status_message: status message, example: 'Toxing on Toxygen'
|
||||||
|
:param widget: ContactItem instance
|
||||||
|
:param tox_id: tox id of contact
|
||||||
|
"""
|
||||||
|
self._name, self._status_message = name, status_message
|
||||||
|
self._status, self._widget = None, widget
|
||||||
|
self._widget.name.setText(name)
|
||||||
|
self._widget.status_message.setText(status_message)
|
||||||
|
self._tox_id = tox_id
|
||||||
|
self.load_avatar()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# name - current name or alias of user
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_name(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
def set_name(self, value):
|
||||||
|
self._name = str(value, 'utf-8')
|
||||||
|
self._widget.name.setText(self._name)
|
||||||
|
self._widget.name.repaint()
|
||||||
|
|
||||||
|
name = property(get_name, set_name)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Status message
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_status_message(self):
|
||||||
|
return self._status_message
|
||||||
|
|
||||||
|
def set_status_message(self, value):
|
||||||
|
self._status_message = str(value, 'utf-8')
|
||||||
|
self._widget.status_message.setText(self._status_message)
|
||||||
|
self._widget.status_message.repaint()
|
||||||
|
|
||||||
|
status_message = property(get_status_message, set_status_message)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Status
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_status(self):
|
||||||
|
return self._status
|
||||||
|
|
||||||
|
def set_status(self, value):
|
||||||
|
self._status = value
|
||||||
|
self._widget.connection_status.update(value)
|
||||||
|
|
||||||
|
status = property(get_status, set_status)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# TOX ID. WARNING: for friend it will return public key, for profile - full address
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_tox_id(self):
|
||||||
|
return self._tox_id
|
||||||
|
|
||||||
|
tox_id = property(get_tox_id)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Avatars
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def load_avatar(self):
|
||||||
|
"""
|
||||||
|
Tries to load avatar of contact or uses default avatar
|
||||||
|
"""
|
||||||
|
avatar_path = '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||||
|
os.chdir(ProfileHelper.get_path() + 'avatars/')
|
||||||
|
if not os.path.isfile(avatar_path): # load default image
|
||||||
|
avatar_path = 'avatar.png'
|
||||||
|
os.chdir(curr_directory() + '/images/')
|
||||||
|
pixmap = QtGui.QPixmap(QtCore.QSize(64, 64))
|
||||||
|
pixmap.load(avatar_path)
|
||||||
|
self._widget.avatar_label.setScaledContents(False)
|
||||||
|
self._widget.avatar_label.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio))
|
||||||
|
self._widget.avatar_label.repaint()
|
||||||
|
|
||||||
|
def reset_avatar(self):
|
||||||
|
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||||
|
if os.path.isfile(avatar_path):
|
||||||
|
os.remove(avatar_path)
|
||||||
|
self.load_avatar()
|
||||||
|
|
||||||
|
def set_avatar(self, avatar):
|
||||||
|
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||||
|
with open(avatar_path, 'wb') as f:
|
||||||
|
f.write(avatar)
|
||||||
|
self.load_avatar()
|
||||||
|
|
||||||
|
def get_pixmap(self):
|
||||||
|
return self._widget.avatar_label.pixmap()
|
@ -1,6 +1,6 @@
|
|||||||
from toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL
|
from toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL
|
||||||
from os.path import basename, getsize, exists
|
from os.path import basename, getsize, exists, dirname
|
||||||
from os import remove, rename
|
from os import remove, rename, chdir
|
||||||
from time import time, sleep
|
from time import time, sleep
|
||||||
from tox import Tox
|
from tox import Tox
|
||||||
import settings
|
import settings
|
||||||
@ -9,21 +9,35 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
from PyQt4 import QtCore
|
from PyQt4 import QtCore
|
||||||
|
|
||||||
|
# TODO: threads!
|
||||||
|
|
||||||
|
|
||||||
TOX_FILE_TRANSFER_STATE = {
|
TOX_FILE_TRANSFER_STATE = {
|
||||||
'RUNNING': 0,
|
'RUNNING': 0,
|
||||||
'PAUSED': 1,
|
'PAUSED_BY_USER': 1,
|
||||||
'CANCELED': 2,
|
'CANCELLED': 2,
|
||||||
'FINISHED': 3,
|
'FINISHED': 3,
|
||||||
'PAUSED_BY_FRIEND': 4
|
'PAUSED_BY_FRIEND': 4,
|
||||||
|
'INCOMING_NOT_STARTED': 5,
|
||||||
|
'OUTGOING_NOT_STARTED': 6
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ACTIVE_FILE_TRANSFERS = (0, 1, 4, 5, 6)
|
||||||
|
|
||||||
|
PAUSED_FILE_TRANSFERS = (1, 4, 5, 6)
|
||||||
|
|
||||||
|
DO_NOT_SHOW_ACCEPT_BUTTON = (2, 3, 4, 6)
|
||||||
|
|
||||||
|
SHOW_PROGRESS_BAR = (0, 1, 4)
|
||||||
|
|
||||||
|
ALLOWED_FILES = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png')
|
||||||
|
|
||||||
|
|
||||||
class StateSignal(QtCore.QObject):
|
class StateSignal(QtCore.QObject):
|
||||||
try:
|
try:
|
||||||
signal = QtCore.Signal(int, float) # state and progress
|
signal = QtCore.Signal(int, float, int) # state and progress
|
||||||
except:
|
except:
|
||||||
signal = QtCore.pyqtSignal(int, float) # state and progress - pyqt4
|
signal = QtCore.pyqtSignal(int, float, int) # state and progress - pyqt4
|
||||||
|
|
||||||
|
|
||||||
class FileTransfer(QtCore.QObject):
|
class FileTransfer(QtCore.QObject):
|
||||||
@ -38,7 +52,7 @@ class FileTransfer(QtCore.QObject):
|
|||||||
self._friend_number = friend_number
|
self._friend_number = friend_number
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['RUNNING']
|
self.state = TOX_FILE_TRANSFER_STATE['RUNNING']
|
||||||
self._file_number = file_number
|
self._file_number = file_number
|
||||||
self._creation_time = time()
|
self._creation_time = None
|
||||||
self._size = float(size)
|
self._size = float(size)
|
||||||
self._done = 0
|
self._done = 0
|
||||||
self._state_changed = StateSignal()
|
self._state_changed = StateSignal()
|
||||||
@ -50,7 +64,12 @@ class FileTransfer(QtCore.QObject):
|
|||||||
self._state_changed.signal.connect(handler)
|
self._state_changed.signal.connect(handler)
|
||||||
|
|
||||||
def signal(self):
|
def signal(self):
|
||||||
self._state_changed.signal.emit(self.state, self._done / self._size if self._size else 0)
|
percentage = self._done / self._size if self._size else 0
|
||||||
|
if self._creation_time is None or not percentage:
|
||||||
|
t = -1
|
||||||
|
else:
|
||||||
|
t = ((time() - self._creation_time) / percentage) * (1 - percentage)
|
||||||
|
self._state_changed.signal.emit(self.state, percentage, int(t))
|
||||||
|
|
||||||
def get_file_number(self):
|
def get_file_number(self):
|
||||||
return self._file_number
|
return self._file_number
|
||||||
@ -62,14 +81,14 @@ class FileTransfer(QtCore.QObject):
|
|||||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
||||||
if hasattr(self, '_file'):
|
if hasattr(self, '_file'):
|
||||||
self._file.close()
|
self._file.close()
|
||||||
self._state_changed.signal.emit(self.state, 1)
|
self.signal()
|
||||||
|
|
||||||
def cancelled(self):
|
def cancelled(self):
|
||||||
if hasattr(self, '_file'):
|
if hasattr(self, '_file'):
|
||||||
sleep(0.1)
|
sleep(0.1)
|
||||||
self._file.close()
|
self._file.close()
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['CANCELED']
|
self.state = TOX_FILE_TRANSFER_STATE['CANCELLED']
|
||||||
self._state_changed.signal.emit(self.state, 1)
|
self.signal()
|
||||||
|
|
||||||
def pause(self, by_friend):
|
def pause(self, by_friend):
|
||||||
if not by_friend:
|
if not by_friend:
|
||||||
@ -100,8 +119,9 @@ class SendTransfer(FileTransfer):
|
|||||||
else:
|
else:
|
||||||
size = 0
|
size = 0
|
||||||
super(SendTransfer, self).__init__(path, tox, friend_number, size)
|
super(SendTransfer, self).__init__(path, tox, friend_number, size)
|
||||||
|
self.state = TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
|
||||||
self._file_number = tox.file_send(friend_number, kind, size, file_id,
|
self._file_number = tox.file_send(friend_number, kind, size, file_id,
|
||||||
basename(path).encode('utf-8') if path else '')
|
bytes(basename(path), 'utf-8') if path else b'')
|
||||||
|
|
||||||
def send_chunk(self, position, size):
|
def send_chunk(self, position, size):
|
||||||
"""
|
"""
|
||||||
@ -109,6 +129,8 @@ class SendTransfer(FileTransfer):
|
|||||||
:param position: start position in file
|
:param position: start position in file
|
||||||
:param size: chunk max size
|
:param size: chunk max size
|
||||||
"""
|
"""
|
||||||
|
if self._creation_time is None:
|
||||||
|
self._creation_time = time()
|
||||||
if size:
|
if size:
|
||||||
self._file.seek(position)
|
self._file.seek(position)
|
||||||
data = self._file.read(size)
|
data = self._file.read(size)
|
||||||
@ -119,7 +141,7 @@ class SendTransfer(FileTransfer):
|
|||||||
if hasattr(self, '_file'):
|
if hasattr(self, '_file'):
|
||||||
self._file.close()
|
self._file.close()
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||||
self._state_changed.signal.emit(self.state, 1)
|
self.signal()
|
||||||
|
|
||||||
|
|
||||||
class SendAvatar(SendTransfer):
|
class SendAvatar(SendTransfer):
|
||||||
@ -143,21 +165,37 @@ class SendFromBuffer(FileTransfer):
|
|||||||
|
|
||||||
def __init__(self, tox, friend_number, data, file_name):
|
def __init__(self, tox, friend_number, data, file_name):
|
||||||
super(SendFromBuffer, self).__init__(None, tox, friend_number, len(data))
|
super(SendFromBuffer, self).__init__(None, tox, friend_number, len(data))
|
||||||
|
self.state = TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
|
||||||
self._data = data
|
self._data = data
|
||||||
self._file_number = tox.file_send(friend_number, TOX_FILE_KIND['DATA'], len(data), None, file_name)
|
self._file_number = tox.file_send(friend_number, TOX_FILE_KIND['DATA'],
|
||||||
|
len(data), None, bytes(file_name, 'utf-8'))
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
return self._data
|
return self._data
|
||||||
|
|
||||||
def send_chunk(self, position, size):
|
def send_chunk(self, position, size):
|
||||||
|
if self._creation_time is None:
|
||||||
|
self._creation_time = time()
|
||||||
if size:
|
if size:
|
||||||
data = self._data[position:position + size]
|
data = self._data[position:position + size]
|
||||||
self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
|
self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
|
||||||
self._done += size
|
self._done += size
|
||||||
self._state_changed.signal.emit(self.state, self._done / self._size)
|
self.signal()
|
||||||
else:
|
else:
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||||
self._state_changed.signal.emit(self.state, 1)
|
self.signal()
|
||||||
|
|
||||||
|
|
||||||
|
class SendFromFileBuffer(SendTransfer):
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
super(SendFromFileBuffer, self).__init__(*args)
|
||||||
|
|
||||||
|
def send_chunk(self, position, size):
|
||||||
|
super(SendFromFileBuffer, self).send_chunk(position, size)
|
||||||
|
if not size:
|
||||||
|
chdir(dirname(self._path))
|
||||||
|
remove(self._path)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Receive file
|
# Receive file
|
||||||
@ -182,23 +220,24 @@ class ReceiveTransfer(FileTransfer):
|
|||||||
:param position: position in file to save data
|
:param position: position in file to save data
|
||||||
:param data: raw data (string)
|
:param data: raw data (string)
|
||||||
"""
|
"""
|
||||||
|
if self._creation_time is None:
|
||||||
|
self._creation_time = time()
|
||||||
if data is None:
|
if data is None:
|
||||||
self._file.close()
|
self._file.close()
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||||
self._state_changed.signal.emit(self.state, 1)
|
self.signal()
|
||||||
else:
|
else:
|
||||||
data = ''.join(chr(x) for x in data)
|
data = bytearray(data)
|
||||||
if self._file_size < position:
|
if self._file_size < position:
|
||||||
self._file.seek(0, 2)
|
self._file.seek(0, 2)
|
||||||
self._file.write('\0' * (position - self._file_size))
|
self._file.write(b'\0' * (position - self._file_size))
|
||||||
self._file.seek(position)
|
self._file.seek(position)
|
||||||
self._file.write(data)
|
self._file.write(data)
|
||||||
self._file.flush()
|
|
||||||
l = len(data)
|
l = len(data)
|
||||||
if position + l > self._file_size:
|
if position + l > self._file_size:
|
||||||
self._file_size = position + l
|
self._file_size = position + l
|
||||||
self._done += l
|
self._done += l
|
||||||
self._state_changed.signal.emit(self.state, self._done / self._size)
|
self.signal()
|
||||||
|
|
||||||
|
|
||||||
class ReceiveToBuffer(FileTransfer):
|
class ReceiveToBuffer(FileTransfer):
|
||||||
@ -208,26 +247,27 @@ class ReceiveToBuffer(FileTransfer):
|
|||||||
|
|
||||||
def __init__(self, tox, friend_number, size, file_number):
|
def __init__(self, tox, friend_number, size, file_number):
|
||||||
super(ReceiveToBuffer, self).__init__(None, tox, friend_number, size, file_number)
|
super(ReceiveToBuffer, self).__init__(None, tox, friend_number, size, file_number)
|
||||||
self._data = ''
|
self._data = bytes()
|
||||||
self._data_size = 0
|
self._data_size = 0
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
return self._data
|
return self._data
|
||||||
|
|
||||||
def write_chunk(self, position, data):
|
def write_chunk(self, position, data):
|
||||||
|
if self._creation_time is None:
|
||||||
|
self._creation_time = time()
|
||||||
if data is None:
|
if data is None:
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||||
self._state_changed.signal.emit(self.state, 1)
|
|
||||||
else:
|
else:
|
||||||
data = ''.join(chr(x) for x in data)
|
data = bytes(data)
|
||||||
l = len(data)
|
l = len(data)
|
||||||
if self._data_size < position:
|
if self._data_size < position:
|
||||||
self._data += ('\0' * (position - self._data_size))
|
self._data += (b'\0' * (position - self._data_size))
|
||||||
self._data = self._data[:position] + data + self._data[position + l:]
|
self._data = self._data[:position] + data + self._data[position + l:]
|
||||||
if position + l > self._data_size:
|
if position + l > self._data_size:
|
||||||
self._data_size = position + l
|
self._data_size = position + l
|
||||||
self._done += l
|
self._done += l
|
||||||
self.signal()
|
self.signal()
|
||||||
|
|
||||||
|
|
||||||
class ReceiveAvatar(ReceiveTransfer):
|
class ReceiveAvatar(ReceiveTransfer):
|
||||||
@ -241,20 +281,23 @@ class ReceiveAvatar(ReceiveTransfer):
|
|||||||
super(ReceiveAvatar, self).__init__(path + '.tmp', tox, friend_number, size, file_number)
|
super(ReceiveAvatar, self).__init__(path + '.tmp', tox, friend_number, size, file_number)
|
||||||
if size > self.MAX_AVATAR_SIZE:
|
if size > self.MAX_AVATAR_SIZE:
|
||||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
||||||
|
self._file.close()
|
||||||
remove(path + '.tmp')
|
remove(path + '.tmp')
|
||||||
elif not size:
|
elif not size:
|
||||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
||||||
self._file.close()
|
self._file.close()
|
||||||
if exists(path):
|
if exists(path):
|
||||||
remove(path)
|
remove(path)
|
||||||
|
self._file.close()
|
||||||
remove(path + '.tmp')
|
remove(path + '.tmp')
|
||||||
elif exists(path):
|
elif exists(path):
|
||||||
hash = self.get_file_id()
|
hash = self.get_file_id()
|
||||||
with open(path) as fl:
|
with open(path, 'rb') as fl:
|
||||||
data = fl.read()
|
data = fl.read()
|
||||||
existing_hash = Tox.hash(data)
|
existing_hash = Tox.hash(data)
|
||||||
if hash == existing_hash:
|
if hash == existing_hash:
|
||||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
||||||
|
self._file.close()
|
||||||
remove(path + '.tmp')
|
remove(path + '.tmp')
|
||||||
else:
|
else:
|
||||||
self.send_control(TOX_FILE_CONTROL['RESUME'])
|
self.send_control(TOX_FILE_CONTROL['RESUME'])
|
||||||
@ -266,7 +309,6 @@ class ReceiveAvatar(ReceiveTransfer):
|
|||||||
if self.state:
|
if self.state:
|
||||||
avatar_path = self._path[:-4]
|
avatar_path = self._path[:-4]
|
||||||
if exists(avatar_path):
|
if exists(avatar_path):
|
||||||
|
chdir(dirname(avatar_path))
|
||||||
remove(avatar_path)
|
remove(avatar_path)
|
||||||
rename(self._path, avatar_path)
|
rename(self._path, avatar_path)
|
||||||
|
|
||||||
|
|
||||||
|
243
src/friend.py
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
import contact
|
||||||
|
from messages import *
|
||||||
|
from history import *
|
||||||
|
import util
|
||||||
|
import file_transfers as ft
|
||||||
|
|
||||||
|
|
||||||
|
class Friend(contact.Contact):
|
||||||
|
"""
|
||||||
|
Friend in list of friends. Can be hidden, properties 'has unread messages' and 'has alias' added
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, message_getter, number, *args):
|
||||||
|
"""
|
||||||
|
:param message_getter: gets messages from db
|
||||||
|
:param number: number of friend.
|
||||||
|
"""
|
||||||
|
super(Friend, self).__init__(*args)
|
||||||
|
self._number = number
|
||||||
|
self._new_messages = False
|
||||||
|
self._visible = True
|
||||||
|
self._alias = False
|
||||||
|
self._message_getter = message_getter
|
||||||
|
self._corr = []
|
||||||
|
self._unsaved_messages = 0
|
||||||
|
self._history_loaded = self._new_actions = False
|
||||||
|
self._receipts = 0
|
||||||
|
self._curr_text = ''
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.set_visibility(False)
|
||||||
|
del self._widget
|
||||||
|
if hasattr(self, '_message_getter'):
|
||||||
|
del self._message_getter
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# History support
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_receipts(self):
|
||||||
|
return self._receipts
|
||||||
|
|
||||||
|
receipts = property(get_receipts) # read receipts
|
||||||
|
|
||||||
|
def inc_receipts(self):
|
||||||
|
self._receipts += 1
|
||||||
|
|
||||||
|
def dec_receipt(self):
|
||||||
|
if self._receipts:
|
||||||
|
self._receipts -= 1
|
||||||
|
self.mark_as_sent()
|
||||||
|
|
||||||
|
def load_corr(self, first_time=True):
|
||||||
|
"""
|
||||||
|
:param first_time: friend became active, load first part of messages
|
||||||
|
"""
|
||||||
|
if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')):
|
||||||
|
return
|
||||||
|
data = list(self._message_getter.get(PAGE_SIZE))
|
||||||
|
if data is not None and len(data):
|
||||||
|
data.reverse()
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
data = list(map(lambda tupl: TextMessage(*tupl), data))
|
||||||
|
self._corr = data + self._corr
|
||||||
|
self._history_loaded = True
|
||||||
|
|
||||||
|
def get_corr_for_saving(self):
|
||||||
|
"""
|
||||||
|
Get data to save in db
|
||||||
|
:return: list of unsaved messages or []
|
||||||
|
"""
|
||||||
|
messages = list(filter(lambda x: x.get_type() <= 1, self._corr))
|
||||||
|
return list(map(lambda x: x.get_data(), messages[-self._unsaved_messages:])) if self._unsaved_messages else []
|
||||||
|
|
||||||
|
def get_corr(self):
|
||||||
|
return self._corr[:]
|
||||||
|
|
||||||
|
def append_message(self, message):
|
||||||
|
"""
|
||||||
|
:param message: text or file transfer message
|
||||||
|
"""
|
||||||
|
self._corr.append(message)
|
||||||
|
if message.get_type() <= 1:
|
||||||
|
self._unsaved_messages += 1
|
||||||
|
|
||||||
|
def get_last_message_text(self):
|
||||||
|
messages = list(filter(lambda x: x.get_type() <= 1 and x.get_owner() != MESSAGE_OWNER['FRIEND'], self._corr))
|
||||||
|
if messages:
|
||||||
|
return messages[-1].get_data()[0]
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def get_unsent_messages(self):
|
||||||
|
"""
|
||||||
|
:return list of unsent messages
|
||||||
|
"""
|
||||||
|
messages = filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
|
||||||
|
return list(messages)
|
||||||
|
|
||||||
|
def get_unsent_messages_for_saving(self):
|
||||||
|
"""
|
||||||
|
:return list of unsent messages for saving
|
||||||
|
"""
|
||||||
|
messages = filter(lambda x: x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
|
||||||
|
return list(map(lambda x: x.get_data(), messages))
|
||||||
|
|
||||||
|
def delete_message(self, time):
|
||||||
|
elem = list(filter(lambda x: type(x) is TextMessage and x.get_data()[2] == time, self._corr))[0]
|
||||||
|
tmp = list(filter(lambda x: x.get_type() <= 1, self._corr))
|
||||||
|
if elem in tmp[-self._unsaved_messages:]:
|
||||||
|
self._unsaved_messages -= 1
|
||||||
|
self._corr.remove(elem)
|
||||||
|
|
||||||
|
def mark_as_sent(self):
|
||||||
|
try:
|
||||||
|
message = list(filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr))[0]
|
||||||
|
message.mark_as_sent()
|
||||||
|
except Exception as ex:
|
||||||
|
util.log('Mark as sent ex: ' + str(ex))
|
||||||
|
|
||||||
|
def clear_corr(self, save_unsent=False):
|
||||||
|
"""
|
||||||
|
Clear messages list
|
||||||
|
"""
|
||||||
|
if hasattr(self, '_message_getter'):
|
||||||
|
del self._message_getter
|
||||||
|
# don't delete data about active file transfer
|
||||||
|
if not save_unsent:
|
||||||
|
self._corr = list(filter(lambda x: x.get_type() in (2, 3) and
|
||||||
|
x.get_status() in ft.ACTIVE_FILE_TRANSFERS, self._corr))
|
||||||
|
self._unsaved_messages = 0
|
||||||
|
else:
|
||||||
|
self._corr = list(filter(lambda x: (x.get_type() in (2, 3) and x.get_status() in ft.ACTIVE_FILE_TRANSFERS)
|
||||||
|
or (x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT']),
|
||||||
|
self._corr))
|
||||||
|
self._unsaved_messages = len(self.get_unsent_messages())
|
||||||
|
|
||||||
|
def get_curr_text(self):
|
||||||
|
return self._curr_text
|
||||||
|
|
||||||
|
def set_curr_text(self, value):
|
||||||
|
self._curr_text = value
|
||||||
|
|
||||||
|
curr_text = property(get_curr_text, set_curr_text)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# File transfers support
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def update_transfer_data(self, file_number, status, inline=None):
|
||||||
|
"""
|
||||||
|
Update status of active transfer and load inline if needed
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
tr = list(filter(lambda x: x.get_type() == MESSAGE_TYPE['FILE_TRANSFER'] and x.is_active(file_number),
|
||||||
|
self._corr))[0]
|
||||||
|
tr.set_status(status)
|
||||||
|
i = self._corr.index(tr)
|
||||||
|
if inline: # inline was loaded
|
||||||
|
self._corr.insert(i, inline)
|
||||||
|
return i - len(self._corr)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_unsent_files(self):
|
||||||
|
messages = filter(lambda x: type(x) is UnsentFile, self._corr)
|
||||||
|
return messages
|
||||||
|
|
||||||
|
def clear_unsent_files(self):
|
||||||
|
self._corr = list(filter(lambda x: type(x) is not UnsentFile, self._corr))
|
||||||
|
|
||||||
|
def delete_one_unsent_file(self, time):
|
||||||
|
self._corr = list(filter(lambda x: not (type(x) is UnsentFile and x.get_data()[2] == time), self._corr))
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Alias support
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def set_name(self, value):
|
||||||
|
"""
|
||||||
|
Set new name or ignore if alias exists
|
||||||
|
:param value: new name
|
||||||
|
"""
|
||||||
|
if not self._alias:
|
||||||
|
super(Friend, self).set_name(value)
|
||||||
|
|
||||||
|
def set_alias(self, alias):
|
||||||
|
self._alias = bool(alias)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Visibility in friends' list
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_visibility(self):
|
||||||
|
return self._visible
|
||||||
|
|
||||||
|
def set_visibility(self, value):
|
||||||
|
self._visible = value
|
||||||
|
|
||||||
|
visibility = property(get_visibility, set_visibility)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Unread messages from friend
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_actions(self):
|
||||||
|
return self._new_actions
|
||||||
|
|
||||||
|
def set_actions(self, value):
|
||||||
|
self._new_actions = value
|
||||||
|
self._widget.connection_status.update(self.status, value)
|
||||||
|
|
||||||
|
actions = property(get_actions, set_actions) # unread messages, incoming files, av calls
|
||||||
|
|
||||||
|
def get_messages(self):
|
||||||
|
return self._new_messages
|
||||||
|
|
||||||
|
def inc_messages(self):
|
||||||
|
self._new_messages += 1
|
||||||
|
self._new_actions = True
|
||||||
|
self._widget.connection_status.update(self.status, True)
|
||||||
|
self._widget.messages.update(self._new_messages)
|
||||||
|
|
||||||
|
def reset_messages(self):
|
||||||
|
self._new_actions = False
|
||||||
|
self._new_messages = 0
|
||||||
|
self._widget.messages.update(self._new_messages)
|
||||||
|
self._widget.connection_status.update(self.status, False)
|
||||||
|
|
||||||
|
messages = property(get_messages)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Friend's number (can be used in toxcore)
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_number(self):
|
||||||
|
return self._number
|
||||||
|
|
||||||
|
def set_number(self, value):
|
||||||
|
self._number = value
|
||||||
|
|
||||||
|
number = property(get_number, set_number)
|
@ -10,11 +10,12 @@ PAGE_SIZE = 42
|
|||||||
|
|
||||||
MESSAGE_OWNER = {
|
MESSAGE_OWNER = {
|
||||||
'ME': 0,
|
'ME': 0,
|
||||||
'FRIEND': 1
|
'FRIEND': 1,
|
||||||
|
'NOT_SENT': 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class History(object):
|
class History:
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self._name = name
|
self._name = name
|
||||||
@ -22,12 +23,15 @@ class History(object):
|
|||||||
path = settings.ProfileHelper.get_path() + self._name + '.hstr'
|
path = settings.ProfileHelper.get_path() + self._name + '.hstr'
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
decr = LibToxEncryptSave.get_instance()
|
decr = LibToxEncryptSave.get_instance()
|
||||||
with open(path, 'rb') as fin:
|
try:
|
||||||
data = fin.read()
|
with open(path, 'rb') as fin:
|
||||||
if decr.is_data_encrypted(data):
|
data = fin.read()
|
||||||
data = decr.pass_decrypt(data)
|
if decr.is_data_encrypted(data):
|
||||||
with open(path, 'wb') as fout:
|
data = decr.pass_decrypt(data)
|
||||||
fout.write(data)
|
with open(path, 'wb') as fout:
|
||||||
|
fout.write(data)
|
||||||
|
except:
|
||||||
|
os.remove(path)
|
||||||
db = connect(name + '.hstr')
|
db = connect(name + '.hstr')
|
||||||
cursor = db.cursor()
|
cursor = db.cursor()
|
||||||
cursor.execute('CREATE TABLE IF NOT EXISTS friends('
|
cursor.execute('CREATE TABLE IF NOT EXISTS friends('
|
||||||
@ -41,7 +45,7 @@ class History(object):
|
|||||||
path = settings.ProfileHelper.get_path() + self._name + '.hstr'
|
path = settings.ProfileHelper.get_path() + self._name + '.hstr'
|
||||||
with open(path, 'rb') as fin:
|
with open(path, 'rb') as fin:
|
||||||
data = fin.read()
|
data = fin.read()
|
||||||
data = encr.pass_encrypt(data)
|
data = encr.pass_encrypt(bytes(data))
|
||||||
with open(path, 'wb') as fout:
|
with open(path, 'wb') as fout:
|
||||||
fout.write(data)
|
fout.write(data)
|
||||||
|
|
||||||
@ -50,6 +54,9 @@ class History(object):
|
|||||||
new_path = directory + self._name + '.hstr'
|
new_path = directory + self._name + '.hstr'
|
||||||
with open(path, 'rb') as fin:
|
with open(path, 'rb') as fin:
|
||||||
data = fin.read()
|
data = fin.read()
|
||||||
|
encr = LibToxEncryptSave.get_instance()
|
||||||
|
if encr.has_password():
|
||||||
|
data = encr.pass_encrypt(data)
|
||||||
with open(new_path, 'wb') as fout:
|
with open(new_path, 'wb') as fout:
|
||||||
fout.write(data)
|
fout.write(data)
|
||||||
|
|
||||||
@ -110,6 +117,34 @@ class History(object):
|
|||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
def update_messages(self, tox_id, unsent_time):
|
||||||
|
chdir(settings.ProfileHelper.get_path())
|
||||||
|
db = connect(self._name + '.hstr')
|
||||||
|
try:
|
||||||
|
cursor = db.cursor()
|
||||||
|
cursor.execute('UPDATE id' + tox_id + ' SET owner = 0 '
|
||||||
|
'WHERE unix_time < ' + str(unsent_time) + ' AND owner = 2;')
|
||||||
|
db.commit()
|
||||||
|
except:
|
||||||
|
db.rollback()
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delete_message(self, tox_id, time):
|
||||||
|
chdir(settings.ProfileHelper.get_path())
|
||||||
|
db = connect(self._name + '.hstr')
|
||||||
|
try:
|
||||||
|
cursor = db.cursor()
|
||||||
|
cursor.execute('DELETE FROM id' + tox_id + ' WHERE unix_time = ' + str(time) + ';')
|
||||||
|
db.commit()
|
||||||
|
except:
|
||||||
|
db.rollback()
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
def delete_messages(self, tox_id):
|
def delete_messages(self, tox_id):
|
||||||
chdir(settings.ProfileHelper.get_path())
|
chdir(settings.ProfileHelper.get_path())
|
||||||
db = connect(self._name + '.hstr')
|
db = connect(self._name + '.hstr')
|
||||||
@ -126,7 +161,7 @@ class History(object):
|
|||||||
def messages_getter(self, tox_id):
|
def messages_getter(self, tox_id):
|
||||||
return History.MessageGetter(self._name, tox_id)
|
return History.MessageGetter(self._name, tox_id)
|
||||||
|
|
||||||
class MessageGetter(object):
|
class MessageGetter:
|
||||||
def __init__(self, name, tox_id):
|
def __init__(self, name, tox_id):
|
||||||
chdir(settings.ProfileHelper.get_path())
|
chdir(settings.ProfileHelper.get_path())
|
||||||
self._db = connect(name + '.hstr')
|
self._db = connect(name + '.hstr')
|
||||||
|
BIN
src/images/audio_message.png
Executable file
After Width: | Height: | Size: 4.2 KiB |
BIN
src/images/busy.png
Normal file
After Width: | Height: | Size: 329 B |
BIN
src/images/busy_notification.png
Normal file
After Width: | Height: | Size: 609 B |
BIN
src/images/icon_new_messages.png
Executable file
After Width: | Height: | Size: 3.8 KiB |
BIN
src/images/idle.png
Normal file
After Width: | Height: | Size: 231 B |
BIN
src/images/idle_notification.png
Normal file
After Width: | Height: | Size: 405 B |
BIN
src/images/menu.png
Executable file
After Width: | Height: | Size: 3.5 KiB |
BIN
src/images/offline.png
Normal file
After Width: | Height: | Size: 159 B |
BIN
src/images/offline_notification.png
Normal file
After Width: | Height: | Size: 445 B |
BIN
src/images/online.png
Normal file
After Width: | Height: | Size: 201 B |
BIN
src/images/online_notification.png
Normal file
After Width: | Height: | Size: 351 B |
BIN
src/images/search.png
Executable file → Normal file
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.7 KiB |
BIN
src/images/smiley.png
Executable file
After Width: | Height: | Size: 5.6 KiB |
BIN
src/images/spinner.gif
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
src/images/sticker.png
Executable file
After Width: | Height: | Size: 94 KiB |
BIN
src/images/video_message.png
Executable file
After Width: | Height: | Size: 3.6 KiB |
BIN
src/images/videocall.png
Executable file
After Width: | Height: | Size: 3.5 KiB |
@ -1,15 +1,16 @@
|
|||||||
from platform import system
|
from platform import system
|
||||||
from ctypes import CDLL
|
from ctypes import CDLL
|
||||||
|
import util
|
||||||
|
|
||||||
|
|
||||||
class LibToxCore(object):
|
class LibToxCore:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
if system() == 'Linux':
|
if system() == 'Linux':
|
||||||
# libtoxcore and libsodium must be installed in your os
|
# libtoxcore and libsodium must be installed in your os
|
||||||
self._libtoxcore = CDLL('libtoxcore.so')
|
self._libtoxcore = CDLL('libtoxcore.so')
|
||||||
elif system() == 'Windows':
|
elif system() == 'Windows':
|
||||||
self._libtoxcore = CDLL('libs/libtox.dll')
|
self._libtoxcore = CDLL(util.curr_directory() + '/libs/libtox.dll')
|
||||||
else:
|
else:
|
||||||
raise OSError('Unknown system.')
|
raise OSError('Unknown system.')
|
||||||
|
|
||||||
@ -17,7 +18,7 @@ class LibToxCore(object):
|
|||||||
return self._libtoxcore.__getattr__(item)
|
return self._libtoxcore.__getattr__(item)
|
||||||
|
|
||||||
|
|
||||||
class LibToxAV(object):
|
class LibToxAV:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
if system() == 'Linux':
|
if system() == 'Linux':
|
||||||
@ -25,7 +26,7 @@ class LibToxAV(object):
|
|||||||
self._libtoxav = CDLL('libtoxav.so')
|
self._libtoxav = CDLL('libtoxav.so')
|
||||||
elif system() == 'Windows':
|
elif system() == 'Windows':
|
||||||
# on Windows av api is in libtox.dll
|
# on Windows av api is in libtox.dll
|
||||||
self._libtoxav = CDLL('libs/libtox.dll')
|
self._libtoxav = CDLL(util.curr_directory() + '/libs/libtox.dll')
|
||||||
else:
|
else:
|
||||||
raise OSError('Unknown system.')
|
raise OSError('Unknown system.')
|
||||||
|
|
||||||
@ -33,7 +34,7 @@ class LibToxAV(object):
|
|||||||
return self._libtoxav.__getattr__(item)
|
return self._libtoxav.__getattr__(item)
|
||||||
|
|
||||||
|
|
||||||
class LibToxEncryptSave(object):
|
class LibToxEncryptSave:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
if system() == 'Linux':
|
if system() == 'Linux':
|
||||||
@ -41,7 +42,7 @@ class LibToxEncryptSave(object):
|
|||||||
self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.so')
|
self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.so')
|
||||||
elif system() == 'Windows':
|
elif system() == 'Windows':
|
||||||
# on Windows profile encryption api is in libtox.dll
|
# on Windows profile encryption api is in libtox.dll
|
||||||
self._lib_tox_encrypt_save = CDLL('libs/libtox.dll')
|
self._lib_tox_encrypt_save = CDLL(util.curr_directory() + '/libs/libtox.dll')
|
||||||
else:
|
else:
|
||||||
raise OSError('Unknown system.')
|
raise OSError('Unknown system.')
|
||||||
|
|
||||||
|
@ -4,75 +4,167 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
import profile
|
import profile
|
||||||
from file_transfers import TOX_FILE_TRANSFER_STATE
|
from file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR
|
||||||
from util import curr_directory, convert_time
|
from util import curr_directory, convert_time, curr_time
|
||||||
from messages import FILE_TRANSFER_MESSAGE_STATUS
|
from widgets import DataLabel, create_menu
|
||||||
from widgets import DataLabel
|
import html as h
|
||||||
|
import smileys
|
||||||
|
import settings
|
||||||
|
|
||||||
|
|
||||||
class MessageEdit(QtGui.QTextEdit):
|
class MessageEdit(QtGui.QTextBrowser):
|
||||||
|
|
||||||
def __init__(self, text, width, parent=None):
|
def __init__(self, text, width, message_type, parent=None):
|
||||||
super(MessageEdit, self).__init__(parent)
|
super(MessageEdit, self).__init__(parent)
|
||||||
|
self.urls = {}
|
||||||
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||||
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||||
self.setWordWrapMode(QtGui.QTextOption.WrapAtWordBoundaryOrAnywhere)
|
self.setWordWrapMode(QtGui.QTextOption.WrapAtWordBoundaryOrAnywhere)
|
||||||
self.document().setTextWidth(width)
|
self.document().setTextWidth(width)
|
||||||
self.setPlainText(text)
|
self.setOpenExternalLinks(True)
|
||||||
|
self.setAcceptRichText(True)
|
||||||
|
self.setOpenLinks(False)
|
||||||
|
self.setSearchPaths([smileys.SmileyLoader.get_instance().get_smileys_path()])
|
||||||
|
self.document().setDefaultStyleSheet('a { color: #306EFF; }')
|
||||||
|
text = self.decoratedText(text)
|
||||||
|
if message_type != TOX_MESSAGE_TYPE['NORMAL']:
|
||||||
|
self.setHtml('<p style="color: #5CB3FF; font: italic; font-size: 20px;" >' + text + '</p>')
|
||||||
|
else:
|
||||||
|
self.setHtml(text)
|
||||||
font = QtGui.QFont()
|
font = QtGui.QFont()
|
||||||
font.setFamily("Times New Roman")
|
font.setFamily("Times New Roman")
|
||||||
font.setPixelSize(14)
|
font.setPixelSize(settings.Settings.get_instance()['message_font_size'])
|
||||||
font.setBold(False)
|
font.setBold(False)
|
||||||
self.setFont(font)
|
self.setFont(font)
|
||||||
self.setFixedHeight(self.document().size().height())
|
self.resize(width, self.document().size().height())
|
||||||
self.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse | QtCore.Qt.LinksAccessibleByMouse)
|
self.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse | QtCore.Qt.LinksAccessibleByMouse)
|
||||||
|
self.anchorClicked.connect(self.on_anchor_clicked)
|
||||||
|
|
||||||
|
def contextMenuEvent(self, event):
|
||||||
|
menu = create_menu(self.createStandardContextMenu(event.pos()))
|
||||||
|
menu.popup(event.globalPos())
|
||||||
|
menu.exec_(event.globalPos())
|
||||||
|
del menu
|
||||||
|
|
||||||
|
def on_anchor_clicked(self, url):
|
||||||
|
text = str(url.toString())
|
||||||
|
if text.startswith('tox:'):
|
||||||
|
import menu
|
||||||
|
self.add_contact = menu.AddContact(text[4:])
|
||||||
|
self.add_contact.show()
|
||||||
|
else:
|
||||||
|
QtGui.QDesktopServices.openUrl(url)
|
||||||
|
self.clearFocus()
|
||||||
|
|
||||||
|
def addAnimation(self, url, fileName):
|
||||||
|
movie = QtGui.QMovie(self)
|
||||||
|
movie.setFileName(fileName)
|
||||||
|
self.urls[movie] = url
|
||||||
|
movie.frameChanged[int].connect(lambda x: self.animate(movie))
|
||||||
|
movie.start()
|
||||||
|
|
||||||
|
def animate(self, movie):
|
||||||
|
self.document().addResource(QtGui.QTextDocument.ImageResource,
|
||||||
|
self.urls[movie],
|
||||||
|
movie.currentPixmap())
|
||||||
|
self.setLineWrapColumnOrWidth(self.lineWrapColumnOrWidth())
|
||||||
|
|
||||||
|
def decoratedText(self, text):
|
||||||
|
text = h.escape(text) # replace < and >
|
||||||
|
exp = QtCore.QRegExp(
|
||||||
|
'('
|
||||||
|
'(?:\\b)((www\\.)|(http[s]?|ftp)://)'
|
||||||
|
'\\w+\\S+)'
|
||||||
|
'|(?:\\b)(file:///)([\\S| ]*)'
|
||||||
|
'|(?:\\b)(tox:[a-zA-Z\\d]{76}$)'
|
||||||
|
'|(?:\\b)(mailto:\\S+@\\S+\\.\\S+)'
|
||||||
|
'|(?:\\b)(tox:\\S+@\\S+)')
|
||||||
|
offset = exp.indexIn(text, 0)
|
||||||
|
while offset != -1: # add links
|
||||||
|
url = exp.cap()
|
||||||
|
if exp.cap(2) == 'www.':
|
||||||
|
html = '<a href="http://{0}">{0}</a>'.format(url)
|
||||||
|
else:
|
||||||
|
html = '<a href="{0}">{0}</a>'.format(url)
|
||||||
|
text = text[:offset] + html + text[offset + len(exp.cap()):]
|
||||||
|
offset += len(html)
|
||||||
|
offset = exp.indexIn(text, offset)
|
||||||
|
arr = text.split('\n')
|
||||||
|
for i in range(len(arr)): # quotes
|
||||||
|
if arr[i].startswith('>'):
|
||||||
|
arr[i] = '<font color="green"><b>' + arr[i][4:] + '</b></font>'
|
||||||
|
text = '<br>'.join(arr)
|
||||||
|
text = smileys.SmileyLoader.get_instance().add_smileys_to_text(text, self) # smileys
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
class MessageItem(QtGui.QWidget):
|
class MessageItem(QtGui.QWidget):
|
||||||
"""
|
"""
|
||||||
Message in messages list
|
Message in messages list
|
||||||
"""
|
"""
|
||||||
def __init__(self, text, time, user='', message_type=TOX_MESSAGE_TYPE['NORMAL'], parent=None):
|
def __init__(self, text, time, user='', sent=True, message_type=TOX_MESSAGE_TYPE['NORMAL'], parent=None):
|
||||||
QtGui.QWidget.__init__(self, parent)
|
QtGui.QWidget.__init__(self, parent)
|
||||||
self.name = DataLabel(self)
|
self.name = DataLabel(self)
|
||||||
self.name.setGeometry(QtCore.QRect(0, 2, 95, 20))
|
self.name.setGeometry(QtCore.QRect(2, 2, 95, 20))
|
||||||
self.name.setTextFormat(QtCore.Qt.PlainText)
|
self.name.setTextFormat(QtCore.Qt.PlainText)
|
||||||
font = QtGui.QFont()
|
font = QtGui.QFont()
|
||||||
font.setFamily("Times New Roman")
|
font.setFamily("Times New Roman")
|
||||||
font.setPointSize(11)
|
font.setPointSize(11)
|
||||||
font.setBold(True)
|
font.setBold(True)
|
||||||
self.name.setFont(font)
|
self.name.setFont(font)
|
||||||
self.name.setObjectName("name")
|
|
||||||
self.name.setText(user)
|
self.name.setText(user)
|
||||||
|
|
||||||
self.time = QtGui.QLabel(self)
|
self.time = QtGui.QLabel(self)
|
||||||
self.time.setGeometry(QtCore.QRect(parent.width() - 50, 0, 50, 25))
|
self.time.setGeometry(QtCore.QRect(parent.width() - 50, 0, 50, 20))
|
||||||
font = QtGui.QFont()
|
font = QtGui.QFont()
|
||||||
font.setFamily("Times New Roman")
|
font.setFamily("Times New Roman")
|
||||||
font.setPointSize(10)
|
font.setPointSize(10)
|
||||||
font.setBold(False)
|
font.setBold(False)
|
||||||
self.time.setFont(font)
|
self.time.setFont(font)
|
||||||
self.time.setObjectName("time")
|
self._time = time
|
||||||
self.time.setText(time)
|
if not sent:
|
||||||
|
movie = QtGui.QMovie(curr_directory() + '/images/spinner.gif')
|
||||||
|
self.time.setMovie(movie)
|
||||||
|
movie.start()
|
||||||
|
self.t = True
|
||||||
|
else:
|
||||||
|
self.time.setText(convert_time(time))
|
||||||
|
self.t = False
|
||||||
|
|
||||||
self.message = MessageEdit(text, parent.width() - 150, self)
|
self.message = MessageEdit(text, parent.width() - 150, message_type, self)
|
||||||
|
if message_type != TOX_MESSAGE_TYPE['NORMAL']:
|
||||||
|
self.name.setStyleSheet("QLabel { color: #5CB3FF; }")
|
||||||
|
self.message.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
|
self.time.setStyleSheet("QLabel { color: #5CB3FF; }")
|
||||||
self.message.setGeometry(QtCore.QRect(100, 0, parent.width() - 150, self.message.height()))
|
self.message.setGeometry(QtCore.QRect(100, 0, parent.width() - 150, self.message.height()))
|
||||||
self.setFixedHeight(self.message.height())
|
self.setFixedHeight(self.message.height())
|
||||||
|
|
||||||
if message_type == TOX_MESSAGE_TYPE['ACTION']:
|
def mouseReleaseEvent(self, event):
|
||||||
self.name.setStyleSheet("QLabel { color: #4169E1; }")
|
if event.button() == QtCore.Qt.RightButton and event.x() > self.time.x():
|
||||||
self.message.setStyleSheet("QTextEdit { color: #4169E1; }")
|
self.listMenu = QtGui.QMenu()
|
||||||
else:
|
delete_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Delete message', None, QtGui.QApplication.UnicodeUTF8))
|
||||||
if text[0] == '>':
|
self.connect(delete_item, QtCore.SIGNAL("triggered()"), self.delete)
|
||||||
self.message.setStyleSheet("QTextEdit { color: green; }")
|
parent_position = self.time.mapToGlobal(QtCore.QPoint(0, 0))
|
||||||
if text[-1] == '<':
|
self.listMenu.move(parent_position)
|
||||||
self.message.setStyleSheet("QTextEdit { color: red; }")
|
self.listMenu.show()
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
pr = profile.Profile.get_instance()
|
||||||
|
pr.delete_message(self._time)
|
||||||
|
|
||||||
|
def mark_as_sent(self):
|
||||||
|
if self.t:
|
||||||
|
self.time.setText(convert_time(self._time))
|
||||||
|
self.t = False
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class ContactItem(QtGui.QWidget):
|
class ContactItem(QtGui.QWidget):
|
||||||
"""
|
"""
|
||||||
Contact in friends list
|
Contact in friends list
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QtGui.QWidget.__init__(self, parent)
|
QtGui.QWidget.__init__(self, parent)
|
||||||
self.setBaseSize(QtCore.QSize(250, 70))
|
self.setBaseSize(QtCore.QSize(250, 70))
|
||||||
@ -80,22 +172,21 @@ class ContactItem(QtGui.QWidget):
|
|||||||
self.avatar_label.setGeometry(QtCore.QRect(3, 3, 64, 64))
|
self.avatar_label.setGeometry(QtCore.QRect(3, 3, 64, 64))
|
||||||
self.avatar_label.setScaledContents(True)
|
self.avatar_label.setScaledContents(True)
|
||||||
self.name = DataLabel(self)
|
self.name = DataLabel(self)
|
||||||
self.name.setGeometry(QtCore.QRect(70, 10, 160, 25))
|
self.name.setGeometry(QtCore.QRect(75, 10, 150, 25))
|
||||||
font = QtGui.QFont()
|
font = QtGui.QFont()
|
||||||
font.setFamily("Times New Roman")
|
font.setFamily("Times New Roman")
|
||||||
font.setPointSize(12)
|
font.setPointSize(12)
|
||||||
font.setBold(True)
|
font.setBold(True)
|
||||||
self.name.setFont(font)
|
self.name.setFont(font)
|
||||||
self.name.setObjectName("name")
|
|
||||||
self.status_message = DataLabel(self)
|
self.status_message = DataLabel(self)
|
||||||
self.status_message.setGeometry(QtCore.QRect(70, 30, 180, 20))
|
self.status_message.setGeometry(QtCore.QRect(75, 30, 170, 20))
|
||||||
font.setPointSize(10)
|
font.setPointSize(10)
|
||||||
font.setBold(False)
|
font.setBold(False)
|
||||||
self.status_message.setFont(font)
|
self.status_message.setFont(font)
|
||||||
self.status_message.setObjectName("status_message")
|
|
||||||
self.connection_status = StatusCircle(self)
|
self.connection_status = StatusCircle(self)
|
||||||
self.connection_status.setGeometry(QtCore.QRect(220, 5, 32, 32))
|
self.connection_status.setGeometry(QtCore.QRect(230, 5, 32, 32))
|
||||||
self.connection_status.setObjectName("connection_status")
|
self.messages = UnreadMessagesCount(self)
|
||||||
|
self.messages.setGeometry(QtCore.QRect(52, 50, 30, 20))
|
||||||
|
|
||||||
|
|
||||||
class StatusCircle(QtGui.QWidget):
|
class StatusCircle(QtGui.QWidget):
|
||||||
@ -105,36 +196,57 @@ class StatusCircle(QtGui.QWidget):
|
|||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
QtGui.QWidget.__init__(self, parent)
|
QtGui.QWidget.__init__(self, parent)
|
||||||
self.setGeometry(0, 0, 32, 32)
|
self.setGeometry(0, 0, 32, 32)
|
||||||
self.data = None
|
self.label = QtGui.QLabel(self)
|
||||||
self.messages = False
|
self.label.setGeometry(QtCore.QRect(0, 0, 32, 32))
|
||||||
|
self.unread = False
|
||||||
|
|
||||||
def paintEvent(self, event):
|
def update(self, status, unread_messages=None):
|
||||||
paint = QtGui.QPainter()
|
if unread_messages is None:
|
||||||
paint.begin(self)
|
unread_messages = self.unread
|
||||||
paint.setRenderHint(QtGui.QPainter.Antialiasing)
|
|
||||||
k = 16
|
|
||||||
rad_x = rad_y = 5
|
|
||||||
if self.data is None:
|
|
||||||
color = QtCore.Qt.transparent
|
|
||||||
else:
|
else:
|
||||||
if self.data == TOX_USER_STATUS['NONE']:
|
self.unread = unread_messages
|
||||||
color = QtGui.QColor(50, 205, 50)
|
if status == TOX_USER_STATUS['NONE']:
|
||||||
elif self.data == TOX_USER_STATUS['AWAY']:
|
name = 'online'
|
||||||
color = QtGui.QColor(255, 200, 50)
|
elif status == TOX_USER_STATUS['AWAY']:
|
||||||
else: # self.data == TOX_USER_STATUS['BUSY']:
|
name = 'idle'
|
||||||
color = QtGui.QColor(255, 50, 0)
|
elif status == TOX_USER_STATUS['BUSY']:
|
||||||
|
name = 'busy'
|
||||||
|
else:
|
||||||
|
name = 'offline'
|
||||||
|
if unread_messages:
|
||||||
|
name += '_notification'
|
||||||
|
self.label.setGeometry(QtCore.QRect(0, 0, 32, 32))
|
||||||
|
else:
|
||||||
|
self.label.setGeometry(QtCore.QRect(2, 0, 32, 32))
|
||||||
|
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(name))
|
||||||
|
self.label.setPixmap(pixmap)
|
||||||
|
|
||||||
paint.setPen(color)
|
|
||||||
center = QtCore.QPoint(k, k)
|
class UnreadMessagesCount(QtGui.QWidget):
|
||||||
paint.setBrush(color)
|
|
||||||
paint.drawEllipse(center, rad_x, rad_y)
|
def __init__(self, parent=None):
|
||||||
if self.messages:
|
super(UnreadMessagesCount, self).__init__(parent)
|
||||||
if color == QtCore.Qt.transparent:
|
self.resize(30, 20)
|
||||||
color = QtCore.Qt.darkRed
|
self.label = QtGui.QLabel(self)
|
||||||
paint.setBrush(QtCore.Qt.transparent)
|
self.label.setGeometry(QtCore.QRect(0, 0, 30, 20))
|
||||||
paint.setPen(color)
|
self.label.setVisible(False)
|
||||||
paint.drawEllipse(center, rad_x + 3, rad_y + 3)
|
font = QtGui.QFont()
|
||||||
paint.end()
|
font.setFamily("Times New Roman")
|
||||||
|
font.setPointSize(12)
|
||||||
|
font.setBold(True)
|
||||||
|
self.label.setFont(font)
|
||||||
|
self.label.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignCenter)
|
||||||
|
color = settings.Settings.get_instance()['unread_color']
|
||||||
|
self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }')
|
||||||
|
|
||||||
|
def update(self, messages_count):
|
||||||
|
color = settings.Settings.get_instance()['unread_color']
|
||||||
|
self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }')
|
||||||
|
if messages_count:
|
||||||
|
self.label.setVisible(True)
|
||||||
|
self.label.setText(str(messages_count))
|
||||||
|
else:
|
||||||
|
self.label.setVisible(False)
|
||||||
|
|
||||||
|
|
||||||
class FileTransferItem(QtGui.QListWidget):
|
class FileTransferItem(QtGui.QListWidget):
|
||||||
@ -143,13 +255,12 @@ class FileTransferItem(QtGui.QListWidget):
|
|||||||
|
|
||||||
QtGui.QListWidget.__init__(self, parent)
|
QtGui.QListWidget.__init__(self, parent)
|
||||||
self.resize(QtCore.QSize(width, 34))
|
self.resize(QtCore.QSize(width, 34))
|
||||||
if state == FILE_TRANSFER_MESSAGE_STATUS['CANCELLED']:
|
if state == TOX_FILE_TRANSFER_STATE['CANCELLED']:
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
|
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
|
||||||
elif state in (FILE_TRANSFER_MESSAGE_STATUS['INCOMING_NOT_STARTED'], FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_FRIEND']):
|
elif state in PAUSED_FILE_TRANSFERS:
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
|
self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
|
||||||
else:
|
else:
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
||||||
|
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
self.name = DataLabel(self)
|
self.name = DataLabel(self)
|
||||||
@ -175,18 +286,18 @@ class FileTransferItem(QtGui.QListWidget):
|
|||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
self.cancel.setIcon(icon)
|
self.cancel.setIcon(icon)
|
||||||
self.cancel.setIconSize(QtCore.QSize(30, 30))
|
self.cancel.setIconSize(QtCore.QSize(30, 30))
|
||||||
self.cancel.setVisible(state > 1)
|
self.cancel.setVisible(state in ACTIVE_FILE_TRANSFERS)
|
||||||
self.cancel.clicked.connect(lambda: self.cancel_transfer(friend_number, file_number))
|
self.cancel.clicked.connect(lambda: self.cancel_transfer(friend_number, file_number))
|
||||||
self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}')
|
self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}')
|
||||||
|
|
||||||
self.accept_or_pause = QtGui.QPushButton(self)
|
self.accept_or_pause = QtGui.QPushButton(self)
|
||||||
self.accept_or_pause.setGeometry(QtCore.QRect(width - 170, 2, 30, 30))
|
self.accept_or_pause.setGeometry(QtCore.QRect(width - 170, 2, 30, 30))
|
||||||
if state == FILE_TRANSFER_MESSAGE_STATUS['INCOMING_NOT_STARTED']:
|
if state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
|
||||||
self.accept_or_pause.setVisible(True)
|
self.accept_or_pause.setVisible(True)
|
||||||
self.button_update('accept')
|
self.button_update('accept')
|
||||||
elif state in (0, 1, 5):
|
elif state in DO_NOT_SHOW_ACCEPT_BUTTON:
|
||||||
self.accept_or_pause.setVisible(False)
|
self.accept_or_pause.setVisible(False)
|
||||||
elif state == FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_USER']: # setup for continue
|
elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: # setup for continue
|
||||||
self.accept_or_pause.setVisible(True)
|
self.accept_or_pause.setVisible(True)
|
||||||
self.button_update('resume')
|
self.button_update('resume')
|
||||||
else: # pause
|
else: # pause
|
||||||
@ -200,23 +311,28 @@ class FileTransferItem(QtGui.QListWidget):
|
|||||||
self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20))
|
self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20))
|
||||||
self.pb.setValue(0)
|
self.pb.setValue(0)
|
||||||
self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }')
|
self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }')
|
||||||
if state < 2:
|
self.pb.setVisible(state in SHOW_PROGRESS_BAR)
|
||||||
self.pb.setVisible(False)
|
|
||||||
|
|
||||||
self.file_name = DataLabel(self)
|
self.file_name = DataLabel(self)
|
||||||
self.file_name.setGeometry(QtCore.QRect(210, 7, width - 400, 20))
|
self.file_name.setGeometry(QtCore.QRect(210, 7, width - 420, 20))
|
||||||
font.setPointSize(12)
|
font.setPointSize(12)
|
||||||
self.file_name.setFont(font)
|
self.file_name.setFont(font)
|
||||||
file_size = size / 1024
|
file_size = size // 1024
|
||||||
if not file_size:
|
if not file_size:
|
||||||
file_size = '{}B'.format(size)
|
file_size = '{}B'.format(size)
|
||||||
elif file_size >= 1024:
|
elif file_size >= 1024:
|
||||||
file_size = '{}MB'.format(file_size / 1024)
|
file_size = '{}MB'.format(file_size // 1024)
|
||||||
else:
|
else:
|
||||||
file_size = '{}KB'.format(file_size)
|
file_size = '{}KB'.format(file_size)
|
||||||
file_data = u'{} {}'.format(file_size, file_name)
|
file_data = '{} {}'.format(file_size, file_name)
|
||||||
self.file_name.setText(file_data)
|
self.file_name.setText(file_data)
|
||||||
|
self.file_name.setToolTip(file_name)
|
||||||
self.saved_name = file_name
|
self.saved_name = file_name
|
||||||
|
self.time_left = QtGui.QLabel(self)
|
||||||
|
self.time_left.setGeometry(QtCore.QRect(width - 87, 7, 30, 20))
|
||||||
|
font.setPointSize(10)
|
||||||
|
self.time_left.setFont(font)
|
||||||
|
self.time_left.setVisible(state == TOX_FILE_TRANSFER_STATE['RUNNING'])
|
||||||
self.setFocusPolicy(QtCore.Qt.NoFocus)
|
self.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||||
self.paused = False
|
self.paused = False
|
||||||
|
|
||||||
@ -229,23 +345,24 @@ class FileTransferItem(QtGui.QListWidget):
|
|||||||
self.pb.setVisible(False)
|
self.pb.setVisible(False)
|
||||||
|
|
||||||
def accept_or_pause_transfer(self, friend_number, file_number, size):
|
def accept_or_pause_transfer(self, friend_number, file_number, size):
|
||||||
if self.state == FILE_TRANSFER_MESSAGE_STATUS['INCOMING_NOT_STARTED']:
|
if self.state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
|
||||||
directory = QtGui.QFileDialog.getExistingDirectory(self,
|
directory = QtGui.QFileDialog.getExistingDirectory(self,
|
||||||
QtGui.QApplication.translate("MainWindow", 'Choose folder', None, QtGui.QApplication.UnicodeUTF8),
|
QtGui.QApplication.translate("MainWindow", 'Choose folder', None, QtGui.QApplication.UnicodeUTF8),
|
||||||
curr_directory(),
|
curr_directory(),
|
||||||
QtGui.QFileDialog.ShowDirsOnly)
|
QtGui.QFileDialog.ShowDirsOnly)
|
||||||
|
self.pb.setVisible(True)
|
||||||
if directory:
|
if directory:
|
||||||
pr = profile.Profile.get_instance()
|
pr = profile.Profile.get_instance()
|
||||||
pr.accept_transfer(self, directory + '/' + self.saved_name, friend_number, file_number, size)
|
pr.accept_transfer(self, directory + '/' + self.saved_name, friend_number, file_number, size)
|
||||||
self.button_update('pause')
|
self.button_update('pause')
|
||||||
elif self.state == FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_USER']: # resume
|
elif self.state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: # resume
|
||||||
self.paused = False
|
self.paused = False
|
||||||
profile.Profile.get_instance().resume_transfer(friend_number, file_number)
|
profile.Profile.get_instance().resume_transfer(friend_number, file_number)
|
||||||
self.button_update('pause')
|
self.button_update('pause')
|
||||||
self.state = FILE_TRANSFER_MESSAGE_STATUS['OUTGOING']
|
self.state = TOX_FILE_TRANSFER_STATE['RUNNING']
|
||||||
else: # pause
|
else: # pause
|
||||||
self.paused = True
|
self.paused = True
|
||||||
self.state = FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_USER']
|
self.state = TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']
|
||||||
profile.Profile.get_instance().pause_transfer(friend_number, file_number)
|
profile.Profile.get_instance().pause_transfer(friend_number, file_number)
|
||||||
self.button_update('resume')
|
self.button_update('resume')
|
||||||
self.accept_or_pause.clearFocus()
|
self.accept_or_pause.clearFocus()
|
||||||
@ -256,63 +373,125 @@ class FileTransferItem(QtGui.QListWidget):
|
|||||||
self.accept_or_pause.setIcon(icon)
|
self.accept_or_pause.setIcon(icon)
|
||||||
self.accept_or_pause.setIconSize(QtCore.QSize(30, 30))
|
self.accept_or_pause.setIconSize(QtCore.QSize(30, 30))
|
||||||
|
|
||||||
def convert(self, state):
|
@QtCore.Slot(int, float, int)
|
||||||
# convert TOX_FILE_TRANSFER_STATE to FILE_TRANSFER_MESSAGE_STATUS
|
def update(self, state, progress, time):
|
||||||
d = {0: 2, 1: 6, 2: 1, 3: 0, 4: 5}
|
|
||||||
return d[state]
|
|
||||||
|
|
||||||
@QtCore.Slot(int, float)
|
|
||||||
def update(self, state, progress):
|
|
||||||
self.pb.setValue(int(progress * 100))
|
self.pb.setValue(int(progress * 100))
|
||||||
state = self.convert(state)
|
if time + 1:
|
||||||
|
m, s = divmod(time, 60)
|
||||||
|
self.time_left.setText('{0:02d}:{1:02d}'.format(m, s))
|
||||||
if self.state != state:
|
if self.state != state:
|
||||||
if state == FILE_TRANSFER_MESSAGE_STATUS['CANCELLED']:
|
if state == TOX_FILE_TRANSFER_STATE['CANCELLED']:
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
|
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
|
||||||
self.cancel.setVisible(False)
|
self.cancel.setVisible(False)
|
||||||
self.accept_or_pause.setVisible(False)
|
self.accept_or_pause.setVisible(False)
|
||||||
self.pb.setVisible(False)
|
self.pb.setVisible(False)
|
||||||
self.state = state
|
self.state = state
|
||||||
elif state == FILE_TRANSFER_MESSAGE_STATUS['FINISHED']:
|
self.time_left.setVisible(False)
|
||||||
|
elif state == TOX_FILE_TRANSFER_STATE['FINISHED']:
|
||||||
self.accept_or_pause.setVisible(False)
|
self.accept_or_pause.setVisible(False)
|
||||||
self.pb.setVisible(False)
|
self.pb.setVisible(False)
|
||||||
self.cancel.setVisible(False)
|
self.cancel.setVisible(False)
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
||||||
self.state = state
|
self.state = state
|
||||||
elif state == FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_FRIEND']:
|
self.time_left.setVisible(False)
|
||||||
|
elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND']:
|
||||||
self.accept_or_pause.setVisible(False)
|
self.accept_or_pause.setVisible(False)
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
|
self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
|
||||||
self.state = state
|
self.state = state
|
||||||
elif state == FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_USER']:
|
self.time_left.setVisible(False)
|
||||||
|
elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']:
|
||||||
self.button_update('resume') # setup button continue
|
self.button_update('resume') # setup button continue
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
||||||
self.state = state
|
self.state = state
|
||||||
|
self.time_left.setVisible(False)
|
||||||
|
elif state == TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']:
|
||||||
|
self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
|
||||||
|
self.accept_or_pause.setVisible(False)
|
||||||
|
self.time_left.setVisible(False)
|
||||||
|
self.pb.setVisible(False)
|
||||||
elif not self.paused: # active
|
elif not self.paused: # active
|
||||||
|
self.pb.setVisible(True)
|
||||||
self.accept_or_pause.setVisible(True) # setup to pause
|
self.accept_or_pause.setVisible(True) # setup to pause
|
||||||
self.button_update('pause')
|
self.button_update('pause')
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
||||||
self.state = state
|
self.state = state
|
||||||
|
self.time_left.setVisible(True)
|
||||||
|
|
||||||
|
def mark_as_sent(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class InlineImageItem(QtGui.QWidget):
|
class UnsentFileItem(FileTransferItem):
|
||||||
|
|
||||||
def __init__(self, data, width, parent=None):
|
def __init__(self, file_name, size, user, time, width, parent=None):
|
||||||
|
super(UnsentFileItem, self).__init__(file_name, size, time, user, -1, -1,
|
||||||
|
TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'], width, parent)
|
||||||
|
self._time = time
|
||||||
|
self.pb.setVisible(False)
|
||||||
|
movie = QtGui.QMovie(curr_directory() + '/images/spinner.gif')
|
||||||
|
self.time.setMovie(movie)
|
||||||
|
movie.start()
|
||||||
|
|
||||||
QtGui.QWidget.__init__(self, parent)
|
def cancel_transfer(self, *args):
|
||||||
self.resize(QtCore.QSize(width, 500))
|
pr = profile.Profile.get_instance()
|
||||||
|
pr.cancel_not_started_transfer(self._time)
|
||||||
|
|
||||||
|
|
||||||
|
class InlineImageItem(QtGui.QScrollArea):
|
||||||
|
|
||||||
|
def __init__(self, data, width, elem):
|
||||||
|
|
||||||
|
QtGui.QScrollArea.__init__(self)
|
||||||
|
self.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||||
|
self._elem = elem
|
||||||
self._image_label = QtGui.QLabel(self)
|
self._image_label = QtGui.QLabel(self)
|
||||||
self._image_label.raise_()
|
self._image_label.raise_()
|
||||||
self._image_label.setAutoFillBackground(True)
|
self.setWidget(self._image_label)
|
||||||
self._image_label.setScaledContents(False)
|
self._image_label.setScaledContents(False)
|
||||||
self.pixmap = QtGui.QPixmap()
|
self._pixmap = QtGui.QPixmap()
|
||||||
self.pixmap.loadFromData(QtCore.QByteArray(data), "PNG")
|
self._pixmap.loadFromData(data, 'PNG')
|
||||||
max_size = width - 40
|
self._max_size = width - 30
|
||||||
if self.pixmap.width() <= max_size:
|
self._resize_needed = not (self._pixmap.width() <= self._max_size)
|
||||||
self._image_label.setPixmap(self.pixmap)
|
self._full_size = not self._resize_needed
|
||||||
self.resize(QtCore.QSize(max_size, self.pixmap.height()))
|
if not self._resize_needed:
|
||||||
|
self._image_label.setPixmap(self._pixmap)
|
||||||
|
self.resize(QtCore.QSize(self._max_size + 5, self._pixmap.height() + 5))
|
||||||
|
self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height())
|
||||||
else:
|
else:
|
||||||
pixmap = self.pixmap.scaled(max_size, max_size, QtCore.Qt.KeepAspectRatio)
|
pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio)
|
||||||
self._image_label.setPixmap(pixmap)
|
self._image_label.setPixmap(pixmap)
|
||||||
self.resize(QtCore.QSize(max_size, pixmap.height()))
|
self.resize(QtCore.QSize(self._max_size + 5, pixmap.height()))
|
||||||
|
self._image_label.setGeometry(5, 0, self._max_size + 5, pixmap.height())
|
||||||
|
self._elem.setSizeHint(QtCore.QSize(self.width(), self.height()))
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, event):
|
||||||
|
if event.button() == QtCore.Qt.LeftButton and self._resize_needed: # scale inline
|
||||||
|
if self._full_size:
|
||||||
|
pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio)
|
||||||
|
self._image_label.setPixmap(pixmap)
|
||||||
|
self.resize(QtCore.QSize(self._max_size, pixmap.height()))
|
||||||
|
self._image_label.setGeometry(5, 0, pixmap.width(), pixmap.height())
|
||||||
|
else:
|
||||||
|
self._image_label.setPixmap(self._pixmap)
|
||||||
|
self.resize(QtCore.QSize(self._max_size, self._pixmap.height() + 17))
|
||||||
|
self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height())
|
||||||
|
self._full_size = not self._full_size
|
||||||
|
self._elem.setSizeHint(QtCore.QSize(self.width(), self.height()))
|
||||||
|
elif event.button() == QtCore.Qt.RightButton: # save inline
|
||||||
|
directory = QtGui.QFileDialog.getExistingDirectory(self,
|
||||||
|
QtGui.QApplication.translate("MainWindow",
|
||||||
|
'Choose folder', None,
|
||||||
|
QtGui.QApplication.UnicodeUTF8),
|
||||||
|
curr_directory(),
|
||||||
|
QtGui.QFileDialog.ShowDirsOnly)
|
||||||
|
if directory:
|
||||||
|
fl = QtCore.QFile(directory + '/toxygen_inline_' + curr_time().replace(':', '_') + '.png')
|
||||||
|
self._pixmap.save(fl, 'PNG')
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def mark_as_sent(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ except ImportError:
|
|||||||
from widgets import *
|
from widgets import *
|
||||||
|
|
||||||
|
|
||||||
class NickEdit(QtGui.QPlainTextEdit):
|
class NickEdit(LineEdit):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super(NickEdit, self).__init__(parent)
|
super(NickEdit, self).__init__(parent)
|
||||||
@ -71,6 +71,7 @@ class LoginScreen(CenteredWidget):
|
|||||||
QtCore.QMetaObject.connectSlotsByName(self)
|
QtCore.QMetaObject.connectSlotsByName(self)
|
||||||
|
|
||||||
def retranslateUi(self):
|
def retranslateUi(self):
|
||||||
|
self.new_name.setPlaceholderText(QtGui.QApplication.translate("login", "Profile name", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.setWindowTitle(QtGui.QApplication.translate("login", "Log in", None, QtGui.QApplication.UnicodeUTF8))
|
self.setWindowTitle(QtGui.QApplication.translate("login", "Log in", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.new_profile.setText(QtGui.QApplication.translate("login", "Create", None, QtGui.QApplication.UnicodeUTF8))
|
self.new_profile.setText(QtGui.QApplication.translate("login", "Create", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.label.setText(QtGui.QApplication.translate("login", "Profile name:", None, QtGui.QApplication.UnicodeUTF8))
|
self.label.setText(QtGui.QApplication.translate("login", "Profile name:", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
@ -82,7 +83,7 @@ class LoginScreen(CenteredWidget):
|
|||||||
|
|
||||||
def create_profile(self):
|
def create_profile(self):
|
||||||
self.type = 1
|
self.type = 1
|
||||||
self.name = self.new_name.toPlainText()
|
self.name = self.new_name.text()
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def load_ex_profile(self):
|
def load_ex_profile(self):
|
||||||
|
53
src/main.py
@ -9,21 +9,27 @@ from bootstrap import node_generator
|
|||||||
from mainscreen import MainWindow
|
from mainscreen import MainWindow
|
||||||
from profile import tox_factory
|
from profile import tox_factory
|
||||||
from callbacks import init_callbacks
|
from callbacks import init_callbacks
|
||||||
from util import curr_directory, get_style
|
from util import curr_directory
|
||||||
import styles.style
|
import styles.style
|
||||||
import locale
|
|
||||||
import toxencryptsave
|
import toxencryptsave
|
||||||
from passwordscreen import PasswordScreen
|
from passwordscreen import PasswordScreen
|
||||||
import profile
|
import profile
|
||||||
from plugin_support import PluginLoader
|
from plugin_support import PluginLoader
|
||||||
|
|
||||||
|
|
||||||
class Toxygen(object):
|
class Toxygen:
|
||||||
|
|
||||||
def __init__(self, path=None):
|
def __init__(self, path_or_uri=None):
|
||||||
super(Toxygen, self).__init__()
|
super(Toxygen, self).__init__()
|
||||||
self.tox = self.ms = self.init = self.mainloop = self.avloop = None
|
self.tox = self.ms = self.init = self.mainloop = self.avloop = None
|
||||||
self.path = path
|
if path_or_uri is None:
|
||||||
|
self.uri = self.path = None
|
||||||
|
elif path_or_uri.startswith('tox:'):
|
||||||
|
self.path = None
|
||||||
|
self.uri = path_or_uri[4:]
|
||||||
|
else:
|
||||||
|
self.path = path_or_uri
|
||||||
|
self.uri = None
|
||||||
|
|
||||||
def enter_pass(self, data):
|
def enter_pass(self, data):
|
||||||
"""
|
"""
|
||||||
@ -55,8 +61,8 @@ class Toxygen(object):
|
|||||||
encrypt_save = toxencryptsave.LibToxEncryptSave()
|
encrypt_save = toxencryptsave.LibToxEncryptSave()
|
||||||
|
|
||||||
if self.path is not None:
|
if self.path is not None:
|
||||||
path = os.path.dirname(self.path.encode(locale.getpreferredencoding())) + '/'
|
path = os.path.dirname(self.path) + '/'
|
||||||
name = os.path.basename(self.path.encode(locale.getpreferredencoding()))[:-4]
|
name = os.path.basename(self.path)[:-4]
|
||||||
data = ProfileHelper(path, name).open_profile()
|
data = ProfileHelper(path, name).open_profile()
|
||||||
if encrypt_save.is_data_encrypted(data):
|
if encrypt_save.is_data_encrypted(data):
|
||||||
data = self.enter_pass(data)
|
data = self.enter_pass(data)
|
||||||
@ -64,13 +70,13 @@ class Toxygen(object):
|
|||||||
self.tox = tox_factory(data, settings)
|
self.tox = tox_factory(data, settings)
|
||||||
else:
|
else:
|
||||||
auto_profile = Settings.get_auto_profile()
|
auto_profile = Settings.get_auto_profile()
|
||||||
if not auto_profile:
|
if not auto_profile[0]:
|
||||||
# show login screen if default profile not found
|
# show login screen if default profile not found
|
||||||
current_locale = QtCore.QLocale()
|
current_locale = QtCore.QLocale()
|
||||||
curr_lang = current_locale.languageToString(current_locale.language())
|
curr_lang = current_locale.languageToString(current_locale.language())
|
||||||
langs = Settings.supported_languages()
|
langs = Settings.supported_languages()
|
||||||
if curr_lang in map(lambda x: x[0], langs):
|
if curr_lang in langs:
|
||||||
lang_path = filter(lambda x: x[0] == curr_lang, langs)[0][1]
|
lang_path = langs[curr_lang]
|
||||||
translator = QtCore.QTranslator()
|
translator = QtCore.QTranslator()
|
||||||
translator.load(curr_directory() + '/translations/' + lang_path)
|
translator.load(curr_directory() + '/translations/' + lang_path)
|
||||||
app.installTranslator(translator)
|
app.installTranslator(translator)
|
||||||
@ -87,10 +93,11 @@ class Toxygen(object):
|
|||||||
if not _login.t:
|
if not _login.t:
|
||||||
return
|
return
|
||||||
elif _login.t == 1: # create new profile
|
elif _login.t == 1: # create new profile
|
||||||
|
_login.name = _login.name.strip()
|
||||||
name = _login.name if _login.name else 'toxygen_user'
|
name = _login.name if _login.name else 'toxygen_user'
|
||||||
self.tox = tox_factory()
|
self.tox = tox_factory()
|
||||||
self.tox.self_set_name(_login.name if _login.name else 'Toxygen User')
|
self.tox.self_set_name(bytes(_login.name, 'utf-8') if _login.name else b'Toxygen User')
|
||||||
self.tox.self_set_status_message('Toxing on Toxygen')
|
self.tox.self_set_status_message(b'Toxing on Toxygen')
|
||||||
ProfileHelper(Settings.get_default_path(), name).save_profile(self.tox.get_savedata())
|
ProfileHelper(Settings.get_default_path(), name).save_profile(self.tox.get_savedata())
|
||||||
path = Settings.get_default_path()
|
path = Settings.get_default_path()
|
||||||
settings = Settings(name)
|
settings = Settings(name)
|
||||||
@ -105,14 +112,13 @@ class Toxygen(object):
|
|||||||
self.tox = tox_factory(data, settings)
|
self.tox = tox_factory(data, settings)
|
||||||
else:
|
else:
|
||||||
path, name = auto_profile
|
path, name = auto_profile
|
||||||
path = path.encode(locale.getpreferredencoding())
|
|
||||||
data = ProfileHelper(path, name).open_profile()
|
data = ProfileHelper(path, name).open_profile()
|
||||||
if encrypt_save.is_data_encrypted(data):
|
if encrypt_save.is_data_encrypted(data):
|
||||||
data = self.enter_pass(data)
|
data = self.enter_pass(data)
|
||||||
settings = Settings(name)
|
settings = Settings(name)
|
||||||
self.tox = tox_factory(data, settings)
|
self.tox = tox_factory(data, settings)
|
||||||
|
|
||||||
if ProfileHelper.is_active_profile(path, name): # profile is in use
|
if Settings.is_active_profile(path, name): # profile is in use
|
||||||
reply = QtGui.QMessageBox.question(None,
|
reply = QtGui.QMessageBox.question(None,
|
||||||
'Profile {}'.format(name),
|
'Profile {}'.format(name),
|
||||||
QtGui.QApplication.translate("login", 'Looks like other instance of Toxygen uses this profile! Continue?', None, QtGui.QApplication.UnicodeUTF8),
|
QtGui.QApplication.translate("login", 'Looks like other instance of Toxygen uses this profile! Continue?', None, QtGui.QApplication.UnicodeUTF8),
|
||||||
@ -123,18 +129,18 @@ class Toxygen(object):
|
|||||||
else:
|
else:
|
||||||
settings.set_active_profile()
|
settings.set_active_profile()
|
||||||
|
|
||||||
lang = filter(lambda x: x[0] == settings['language'], Settings.supported_languages())[0]
|
lang = Settings.supported_languages()[settings['language']]
|
||||||
translator = QtCore.QTranslator()
|
translator = QtCore.QTranslator()
|
||||||
translator.load(curr_directory() + '/translations/' + lang[1])
|
translator.load(curr_directory() + '/translations/' + lang)
|
||||||
app.installTranslator(translator)
|
app.installTranslator(translator)
|
||||||
app.translator = translator
|
app.translator = translator
|
||||||
|
|
||||||
self.ms = MainWindow(self.tox, self.reset)
|
|
||||||
|
|
||||||
# tray icon
|
# tray icon
|
||||||
self.tray = QtGui.QSystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
|
self.tray = QtGui.QSystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
|
||||||
self.tray.setObjectName('tray')
|
self.tray.setObjectName('tray')
|
||||||
|
|
||||||
|
self.ms = MainWindow(self.tox, self.reset, self.tray)
|
||||||
|
|
||||||
class Menu(QtGui.QMenu):
|
class Menu(QtGui.QMenu):
|
||||||
|
|
||||||
def newStatus(self, status):
|
def newStatus(self, status):
|
||||||
@ -186,11 +192,11 @@ class Toxygen(object):
|
|||||||
sub.connect(onl, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(0))
|
sub.connect(onl, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(0))
|
||||||
sub.connect(away, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(1))
|
sub.connect(away, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(1))
|
||||||
sub.connect(busy, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(2))
|
sub.connect(busy, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(2))
|
||||||
|
|
||||||
self.tray.setContextMenu(m)
|
self.tray.setContextMenu(m)
|
||||||
self.tray.show()
|
self.tray.show()
|
||||||
|
|
||||||
self.ms.show()
|
self.ms.show()
|
||||||
QtGui.QApplication.setStyle(get_style(settings['theme'])) # set application style
|
|
||||||
|
|
||||||
plugin_helper = PluginLoader(self.tox, settings) # plugin support
|
plugin_helper = PluginLoader(self.tox, settings) # plugin support
|
||||||
plugin_helper.load()
|
plugin_helper.load()
|
||||||
@ -205,6 +211,9 @@ class Toxygen(object):
|
|||||||
self.avloop = self.ToxAVIterateThread(self.tox.AV)
|
self.avloop = self.ToxAVIterateThread(self.tox.AV)
|
||||||
self.avloop.start()
|
self.avloop.start()
|
||||||
|
|
||||||
|
if self.uri is not None:
|
||||||
|
self.ms.add_contact(self.uri)
|
||||||
|
|
||||||
app.connect(app, QtCore.SIGNAL("lastWindowClosed()"), app, QtCore.SLOT("quit()"))
|
app.connect(app, QtCore.SIGNAL("lastWindowClosed()"), app, QtCore.SLOT("quit()"))
|
||||||
app.exec_()
|
app.exec_()
|
||||||
self.init.stop = True
|
self.init.stop = True
|
||||||
@ -273,7 +282,7 @@ class Toxygen(object):
|
|||||||
self.tox.bootstrap(*data)
|
self.tox.bootstrap(*data)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
for _ in xrange(10):
|
for _ in range(10):
|
||||||
if self.stop:
|
if self.stop:
|
||||||
return
|
return
|
||||||
self.msleep(1000)
|
self.msleep(1000)
|
||||||
@ -312,7 +321,7 @@ class Toxygen(object):
|
|||||||
self.toxav.iterate()
|
self.toxav.iterate()
|
||||||
self.msleep(self.toxav.iteration_interval())
|
self.msleep(self.toxav.iteration_interval())
|
||||||
|
|
||||||
class Login(object):
|
class Login:
|
||||||
|
|
||||||
def __init__(self, arr):
|
def __init__(self, arr):
|
||||||
self.arr = arr
|
self.arr = arr
|
||||||
@ -336,6 +345,6 @@ class Toxygen(object):
|
|||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if len(sys.argv) == 1:
|
if len(sys.argv) == 1:
|
||||||
toxygen = Toxygen()
|
toxygen = Toxygen()
|
||||||
else: # path to profile
|
else: # path to profile or tox: uri
|
||||||
toxygen = Toxygen(sys.argv[1])
|
toxygen = Toxygen(sys.argv[1])
|
||||||
toxygen.main()
|
toxygen.main()
|
||||||
|
@ -3,43 +3,18 @@
|
|||||||
from menu import *
|
from menu import *
|
||||||
from profile import *
|
from profile import *
|
||||||
from list_items import *
|
from list_items import *
|
||||||
from widgets import QRightClickButton
|
from widgets import MultilineEdit, LineEdit
|
||||||
import plugin_support
|
import plugin_support
|
||||||
|
from mainscreen_widgets import *
|
||||||
|
|
||||||
class MessageArea(QtGui.QPlainTextEdit):
|
|
||||||
|
|
||||||
def __init__(self, parent, form):
|
|
||||||
super(MessageArea, self).__init__(parent)
|
|
||||||
self.parent = form
|
|
||||||
self.timer = QtCore.QTimer(self)
|
|
||||||
self.timer.timeout.connect(lambda: self.parent.profile.send_typing(False))
|
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
|
||||||
if event.key() == QtCore.Qt.Key_Return:
|
|
||||||
modifiers = event.modifiers()
|
|
||||||
if modifiers & QtCore.Qt.ControlModifier or modifiers & QtCore.Qt.ShiftModifier:
|
|
||||||
self.appendPlainText('')
|
|
||||||
else:
|
|
||||||
if self.timer.isActive():
|
|
||||||
self.timer.stop()
|
|
||||||
self.parent.profile.send_typing(False)
|
|
||||||
self.parent.send_message()
|
|
||||||
elif event.key() == QtCore.Qt.Key_Up and not self.toPlainText():
|
|
||||||
self.appendPlainText(Profile.get_instance().get_last_message())
|
|
||||||
else:
|
|
||||||
self.parent.profile.send_typing(True)
|
|
||||||
if self.timer.isActive():
|
|
||||||
self.timer.stop()
|
|
||||||
self.timer.start(5000)
|
|
||||||
super(MessageArea, self).keyPressEvent(event)
|
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QtGui.QMainWindow):
|
class MainWindow(QtGui.QMainWindow):
|
||||||
|
|
||||||
def __init__(self, tox, reset):
|
def __init__(self, tox, reset, tray):
|
||||||
super(MainWindow, self).__init__()
|
super(MainWindow, self).__init__()
|
||||||
self.reset = reset
|
self.reset = reset
|
||||||
|
self.tray = tray
|
||||||
|
self.setAcceptDrops(True)
|
||||||
self.initUI(tox)
|
self.initUI(tox)
|
||||||
|
|
||||||
def setup_menu(self, MainWindow):
|
def setup_menu(self, MainWindow):
|
||||||
@ -105,6 +80,11 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
def languageChange(self, *args, **kwargs):
|
def languageChange(self, *args, **kwargs):
|
||||||
self.retranslateUi()
|
self.retranslateUi()
|
||||||
|
|
||||||
|
def event(self, event):
|
||||||
|
if event.type() == QtCore.QEvent.WindowActivate:
|
||||||
|
self.tray.setIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
|
||||||
|
return super(MainWindow, self).event(event)
|
||||||
|
|
||||||
def retranslateUi(self):
|
def retranslateUi(self):
|
||||||
self.menuPlugins.setTitle(QtGui.QApplication.translate("MainWindow", "Plugins", None, QtGui.QApplication.UnicodeUTF8))
|
self.menuPlugins.setTitle(QtGui.QApplication.translate("MainWindow", "Plugins", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.pluginData.setText(QtGui.QApplication.translate("MainWindow", "List of plugins", None, QtGui.QApplication.UnicodeUTF8))
|
self.pluginData.setText(QtGui.QApplication.translate("MainWindow", "List of plugins", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
@ -121,8 +101,6 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
self.actionSettings.setText(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8))
|
self.actionSettings.setText(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.audioSettings.setText(QtGui.QApplication.translate("MainWindow", "Audio", None, QtGui.QApplication.UnicodeUTF8))
|
self.audioSettings.setText(QtGui.QApplication.translate("MainWindow", "Audio", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.contact_name.setPlaceholderText(QtGui.QApplication.translate("MainWindow", "Search", None, QtGui.QApplication.UnicodeUTF8))
|
self.contact_name.setPlaceholderText(QtGui.QApplication.translate("MainWindow", "Search", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.screenshotButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Send screenshot", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.fileTransferButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Send file", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.sendMessageButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Send message", None, QtGui.QApplication.UnicodeUTF8))
|
self.sendMessageButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Send message", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.callButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Start audio call with friend", None, QtGui.QApplication.UnicodeUTF8))
|
self.callButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Start audio call with friend", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.online_contacts.clear()
|
self.online_contacts.clear()
|
||||||
@ -131,7 +109,6 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
self.online_contacts.setCurrentIndex(int(Settings.get_instance()['show_online_friends']))
|
self.online_contacts.setCurrentIndex(int(Settings.get_instance()['show_online_friends']))
|
||||||
|
|
||||||
def setup_right_bottom(self, Form):
|
def setup_right_bottom(self, Form):
|
||||||
Form.setObjectName("right_bottom")
|
|
||||||
Form.resize(650, 60)
|
Form.resize(650, 60)
|
||||||
self.messageEdit = MessageArea(Form, self)
|
self.messageEdit = MessageArea(Form, self)
|
||||||
self.messageEdit.setGeometry(QtCore.QRect(0, 3, 450, 55))
|
self.messageEdit.setGeometry(QtCore.QRect(0, 3, 450, 55))
|
||||||
@ -140,68 +117,58 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
font.setPointSize(10)
|
font.setPointSize(10)
|
||||||
self.messageEdit.setFont(font)
|
self.messageEdit.setFont(font)
|
||||||
|
|
||||||
self.screenshotButton = QRightClickButton(Form)
|
|
||||||
self.screenshotButton.setGeometry(QtCore.QRect(455, 3, 55, 55))
|
|
||||||
self.screenshotButton.setObjectName("screenshotButton")
|
|
||||||
|
|
||||||
self.fileTransferButton = QtGui.QPushButton(Form)
|
|
||||||
self.fileTransferButton.setGeometry(QtCore.QRect(510, 3, 55, 55))
|
|
||||||
self.fileTransferButton.setObjectName("fileTransferButton")
|
|
||||||
|
|
||||||
self.sendMessageButton = QtGui.QPushButton(Form)
|
self.sendMessageButton = QtGui.QPushButton(Form)
|
||||||
self.sendMessageButton.setGeometry(QtCore.QRect(565, 3, 60, 55))
|
self.sendMessageButton.setGeometry(QtCore.QRect(565, 3, 60, 55))
|
||||||
self.sendMessageButton.setObjectName("sendMessageButton")
|
self.sendMessageButton.setObjectName("sendMessageButton")
|
||||||
|
|
||||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/send.png')
|
self.menuButton = MenuButton(Form, self.show_menu)
|
||||||
|
self.menuButton.setGeometry(QtCore.QRect(QtCore.QRect(455, 3, 55, 55)))
|
||||||
|
|
||||||
|
pixmap = QtGui.QPixmap('send.png')
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
self.sendMessageButton.setIcon(icon)
|
self.sendMessageButton.setIcon(icon)
|
||||||
self.sendMessageButton.setIconSize(QtCore.QSize(45, 60))
|
self.sendMessageButton.setIconSize(QtCore.QSize(45, 60))
|
||||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/file.png')
|
|
||||||
icon = QtGui.QIcon(pixmap)
|
|
||||||
self.fileTransferButton.setIcon(icon)
|
|
||||||
self.fileTransferButton.setIconSize(QtCore.QSize(40, 40))
|
|
||||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/screenshot.png')
|
|
||||||
icon = QtGui.QIcon(pixmap)
|
|
||||||
self.screenshotButton.setIcon(icon)
|
|
||||||
self.screenshotButton.setIconSize(QtCore.QSize(40, 60))
|
|
||||||
|
|
||||||
self.fileTransferButton.clicked.connect(self.send_file)
|
pixmap = QtGui.QPixmap('menu.png')
|
||||||
self.screenshotButton.clicked.connect(self.send_screenshot)
|
icon = QtGui.QIcon(pixmap)
|
||||||
|
self.menuButton.setIcon(icon)
|
||||||
|
self.menuButton.setIconSize(QtCore.QSize(40, 40))
|
||||||
|
|
||||||
self.sendMessageButton.clicked.connect(self.send_message)
|
self.sendMessageButton.clicked.connect(self.send_message)
|
||||||
self.connect(self.screenshotButton, QtCore.SIGNAL("rightClicked()"), lambda: self.send_screenshot(True))
|
|
||||||
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||||
|
|
||||||
def setup_left_center_menu(self, Form):
|
def setup_left_center_menu(self, Form):
|
||||||
Form.resize(270, 25)
|
Form.resize(270, 25)
|
||||||
self.search_label = QtGui.QLabel(Form)
|
self.search_label = QtGui.QLabel(Form)
|
||||||
self.search_label.setGeometry(QtCore.QRect(3, 0, 25, 25))
|
self.search_label.setGeometry(QtCore.QRect(3, 2, 20, 20))
|
||||||
pixmap = QtGui.QPixmap(QtCore.QSize(25, 25))
|
pixmap = QtGui.QPixmap()
|
||||||
pixmap.load(curr_directory() + '/images/search.png')
|
pixmap.load(curr_directory() + '/images/search.png')
|
||||||
self.search_label.setScaledContents(False)
|
self.search_label.setScaledContents(False)
|
||||||
self.search_label.setPixmap(pixmap.scaled(25, 25, QtCore.Qt.KeepAspectRatio))
|
self.search_label.setPixmap(pixmap)
|
||||||
|
|
||||||
self.contact_name = QtGui.QLineEdit(Form)
|
self.contact_name = LineEdit(Form)
|
||||||
self.contact_name.setGeometry(QtCore.QRect(30, 0, 120, 25))
|
self.contact_name.setGeometry(QtCore.QRect(0, 0, 150, 25))
|
||||||
self.contact_name.setObjectName("contact_name")
|
self.contact_name.setObjectName("contact_name")
|
||||||
self.contact_name.textChanged.connect(self.filtering)
|
self.contact_name.textChanged.connect(self.filtering)
|
||||||
|
|
||||||
self.online_contacts = QtGui.QComboBox(Form)
|
self.online_contacts = QtGui.QComboBox(Form)
|
||||||
self.online_contacts.setGeometry(QtCore.QRect(150, 0, 120, 25))
|
self.online_contacts.setGeometry(QtCore.QRect(150, 0, 120, 25))
|
||||||
self.online_contacts.activated[int].connect(lambda x: self.filtering())
|
self.online_contacts.activated[int].connect(lambda x: self.filtering())
|
||||||
|
self.search_label.raise_()
|
||||||
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||||
|
|
||||||
def setup_left_top(self, Form):
|
def setup_left_top(self, Form):
|
||||||
Form.setObjectName("left_top")
|
|
||||||
Form.setCursor(QtCore.Qt.PointingHandCursor)
|
Form.setCursor(QtCore.Qt.PointingHandCursor)
|
||||||
Form.setMinimumSize(QtCore.QSize(250, 80))
|
Form.setMinimumSize(QtCore.QSize(270, 100))
|
||||||
Form.setMaximumSize(QtCore.QSize(250, 80))
|
Form.setMaximumSize(QtCore.QSize(270, 100))
|
||||||
Form.setBaseSize(QtCore.QSize(250, 80))
|
Form.setBaseSize(QtCore.QSize(270, 100))
|
||||||
self.avatar_label = Form.avatar_label = QtGui.QLabel(Form)
|
self.avatar_label = Form.avatar_label = QtGui.QLabel(Form)
|
||||||
self.avatar_label.setGeometry(QtCore.QRect(5, 15, 64, 64))
|
self.avatar_label.setGeometry(QtCore.QRect(5, 30, 64, 64))
|
||||||
self.avatar_label.setScaledContents(True)
|
self.avatar_label.setScaledContents(True)
|
||||||
self.name = Form.name = DataLabel(Form)
|
self.name = Form.name = DataLabel(Form)
|
||||||
Form.name.setGeometry(QtCore.QRect(80, 25, 150, 25))
|
Form.name.setGeometry(QtCore.QRect(75, 40, 150, 25))
|
||||||
font = QtGui.QFont()
|
font = QtGui.QFont()
|
||||||
font.setFamily("Times New Roman")
|
font.setFamily("Times New Roman")
|
||||||
font.setPointSize(14)
|
font.setPointSize(14)
|
||||||
@ -209,16 +176,13 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
Form.name.setFont(font)
|
Form.name.setFont(font)
|
||||||
Form.name.setObjectName("name")
|
Form.name.setObjectName("name")
|
||||||
self.status_message = Form.status_message = DataLabel(Form)
|
self.status_message = Form.status_message = DataLabel(Form)
|
||||||
Form.status_message.setGeometry(QtCore.QRect(80, 55, 170, 20))
|
Form.status_message.setGeometry(QtCore.QRect(75, 60, 170, 25))
|
||||||
font.setPointSize(12)
|
font.setPointSize(12)
|
||||||
font.setBold(False)
|
font.setBold(False)
|
||||||
Form.status_message.setFont(font)
|
Form.status_message.setFont(font)
|
||||||
Form.status_message.setObjectName("status_message")
|
Form.status_message.setObjectName("status_message")
|
||||||
self.connection_status = Form.connection_status = StatusCircle(self)
|
self.connection_status = Form.connection_status = StatusCircle(Form)
|
||||||
Form.connection_status.setGeometry(QtCore.QRect(230, 29, 64, 64))
|
Form.connection_status.setGeometry(QtCore.QRect(230, 35, 32, 32))
|
||||||
Form.connection_status.setMinimumSize(QtCore.QSize(32, 32))
|
|
||||||
Form.connection_status.setMaximumSize(QtCore.QSize(32, 32))
|
|
||||||
Form.connection_status.setBaseSize(QtCore.QSize(32, 32))
|
|
||||||
self.avatar_label.mouseReleaseEvent = self.profile_settings
|
self.avatar_label.mouseReleaseEvent = self.profile_settings
|
||||||
self.status_message.mouseReleaseEvent = self.profile_settings
|
self.status_message.mouseReleaseEvent = self.profile_settings
|
||||||
self.name.mouseReleaseEvent = self.profile_settings
|
self.name.mouseReleaseEvent = self.profile_settings
|
||||||
@ -226,10 +190,9 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
Form.connection_status.setObjectName("connection_status")
|
Form.connection_status.setObjectName("connection_status")
|
||||||
|
|
||||||
def setup_right_top(self, Form):
|
def setup_right_top(self, Form):
|
||||||
Form.setObjectName("Form")
|
Form.resize(650, 100)
|
||||||
Form.resize(650, 80)
|
|
||||||
self.account_avatar = QtGui.QLabel(Form)
|
self.account_avatar = QtGui.QLabel(Form)
|
||||||
self.account_avatar.setGeometry(QtCore.QRect(10, 17, 64, 64))
|
self.account_avatar.setGeometry(QtCore.QRect(10, 30, 64, 64))
|
||||||
self.account_avatar.setScaledContents(True)
|
self.account_avatar.setScaledContents(True)
|
||||||
self.account_name = DataLabel(Form)
|
self.account_name = DataLabel(Form)
|
||||||
self.account_name.setGeometry(QtCore.QRect(100, 25, 400, 25))
|
self.account_name.setGeometry(QtCore.QRect(100, 25, 400, 25))
|
||||||
@ -250,10 +213,14 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
self.callButton = QtGui.QPushButton(Form)
|
self.callButton = QtGui.QPushButton(Form)
|
||||||
self.callButton.setGeometry(QtCore.QRect(550, 30, 50, 50))
|
self.callButton.setGeometry(QtCore.QRect(550, 30, 50, 50))
|
||||||
self.callButton.setObjectName("callButton")
|
self.callButton.setObjectName("callButton")
|
||||||
self.callButton.clicked.connect(self.call)
|
self.callButton.clicked.connect(lambda: self.profile.call_click(True))
|
||||||
|
self.videocallButton = QtGui.QPushButton(Form)
|
||||||
|
self.videocallButton.setGeometry(QtCore.QRect(550, 30, 50, 50))
|
||||||
|
self.videocallButton.setObjectName("videocallButton")
|
||||||
|
self.videocallButton.clicked.connect(lambda: self.profile.call_click(True, True))
|
||||||
self.update_call_state('call')
|
self.update_call_state('call')
|
||||||
self.typing = QtGui.QLabel(Form)
|
self.typing = QtGui.QLabel(Form)
|
||||||
self.typing.setGeometry(QtCore.QRect(500, 40, 50, 30))
|
self.typing.setGeometry(QtCore.QRect(500, 50, 50, 30))
|
||||||
pixmap = QtGui.QPixmap(QtCore.QSize(50, 30))
|
pixmap = QtGui.QPixmap(QtCore.QSize(50, 30))
|
||||||
pixmap.load(curr_directory() + '/images/typing.png')
|
pixmap.load(curr_directory() + '/images/typing.png')
|
||||||
self.typing.setScaledContents(False)
|
self.typing.setScaledContents(False)
|
||||||
@ -275,6 +242,7 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
self.messages = QtGui.QListWidget(widget)
|
self.messages = QtGui.QListWidget(widget)
|
||||||
self.messages.setGeometry(0, 0, 620, 310)
|
self.messages.setGeometry(0, 0, 620, 310)
|
||||||
self.messages.setObjectName("messages")
|
self.messages.setObjectName("messages")
|
||||||
|
self.messages.setSpacing(1)
|
||||||
self.messages.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
self.messages.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||||
self.messages.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
self.messages.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||||
self.messages.setFocusPolicy(QtCore.Qt.NoFocus)
|
self.messages.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||||
@ -288,37 +256,50 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
|
|
||||||
def initUI(self, tox):
|
def initUI(self, tox):
|
||||||
self.setMinimumSize(920, 500)
|
self.setMinimumSize(920, 500)
|
||||||
self.setGeometry(400, 400, 920, 500)
|
s = Settings.get_instance()
|
||||||
|
self.setGeometry(s['x'], s['y'], s['width'], s['height'])
|
||||||
self.setWindowTitle('Toxygen')
|
self.setWindowTitle('Toxygen')
|
||||||
os.chdir(curr_directory() + '/images/')
|
os.chdir(curr_directory() + '/images/')
|
||||||
main = QtGui.QWidget()
|
main = QtGui.QWidget()
|
||||||
grid = QtGui.QGridLayout()
|
grid = QtGui.QGridLayout()
|
||||||
search = QtGui.QWidget()
|
search = QtGui.QWidget()
|
||||||
self.setup_left_center_menu(search)
|
|
||||||
grid.addWidget(search, 1, 0)
|
|
||||||
name = QtGui.QWidget()
|
name = QtGui.QWidget()
|
||||||
self.setup_left_top(name)
|
|
||||||
grid.addWidget(name, 0, 0)
|
|
||||||
messages = QtGui.QWidget()
|
|
||||||
self.setup_right_center(messages)
|
|
||||||
grid.addWidget(messages, 1, 1, 2, 1)
|
|
||||||
info = QtGui.QWidget()
|
info = QtGui.QWidget()
|
||||||
self.setup_right_top(info)
|
|
||||||
grid.addWidget(info, 0, 1)
|
|
||||||
message_buttons = QtGui.QWidget()
|
|
||||||
self.setup_right_bottom(message_buttons)
|
|
||||||
grid.addWidget(message_buttons, 3, 1)
|
|
||||||
main_list = QtGui.QWidget()
|
main_list = QtGui.QWidget()
|
||||||
|
messages = QtGui.QWidget()
|
||||||
|
message_buttons = QtGui.QWidget()
|
||||||
|
self.setup_left_center_menu(search)
|
||||||
|
self.setup_left_top(name)
|
||||||
|
self.setup_right_center(messages)
|
||||||
|
self.setup_right_top(info)
|
||||||
|
self.setup_right_bottom(message_buttons)
|
||||||
self.setup_left_center(main_list)
|
self.setup_left_center(main_list)
|
||||||
grid.addWidget(main_list, 2, 0, 2, 1)
|
if not Settings.get_instance()['mirror_mode']:
|
||||||
grid.setColumnMinimumWidth(1, 500)
|
grid.addWidget(search, 1, 0)
|
||||||
grid.setColumnMinimumWidth(0, 270)
|
grid.addWidget(name, 0, 0)
|
||||||
grid.setRowMinimumHeight(0, 82)
|
grid.addWidget(messages, 1, 1, 2, 1)
|
||||||
|
grid.addWidget(info, 0, 1)
|
||||||
|
grid.addWidget(message_buttons, 3, 1)
|
||||||
|
grid.addWidget(main_list, 2, 0, 2, 1)
|
||||||
|
grid.setColumnMinimumWidth(1, 500)
|
||||||
|
grid.setColumnMinimumWidth(0, 270)
|
||||||
|
else:
|
||||||
|
grid.addWidget(search, 1, 1)
|
||||||
|
grid.addWidget(name, 0, 1)
|
||||||
|
grid.addWidget(messages, 1, 0, 2, 1)
|
||||||
|
grid.addWidget(info, 0, 0)
|
||||||
|
grid.addWidget(message_buttons, 3, 0)
|
||||||
|
grid.addWidget(main_list, 2, 1, 2, 1)
|
||||||
|
grid.setColumnMinimumWidth(0, 500)
|
||||||
|
grid.setColumnMinimumWidth(1, 270)
|
||||||
|
grid.setSpacing(0)
|
||||||
|
grid.setContentsMargins(0, 0, 0, 0)
|
||||||
|
grid.setRowMinimumHeight(0, 100)
|
||||||
grid.setRowMinimumHeight(1, 25)
|
grid.setRowMinimumHeight(1, 25)
|
||||||
grid.setRowMinimumHeight(2, 410)
|
grid.setRowMinimumHeight(2, 320)
|
||||||
grid.setRowMinimumHeight(3, 60)
|
grid.setRowMinimumHeight(3, 55)
|
||||||
grid.setColumnStretch(1, 1)
|
grid.setColumnStretch(1, 1)
|
||||||
grid.setRowStretch(1, 1)
|
grid.setRowStretch(2, 1)
|
||||||
main.setLayout(grid)
|
main.setLayout(grid)
|
||||||
self.setCentralWidget(main)
|
self.setCentralWidget(main)
|
||||||
self.setup_menu(self)
|
self.setup_menu(self)
|
||||||
@ -331,21 +312,29 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
def closeEvent(self, *args, **kwargs):
|
def closeEvent(self, *args, **kwargs):
|
||||||
self.profile.save_history()
|
self.profile.save_history()
|
||||||
self.profile.close()
|
self.profile.close()
|
||||||
|
s = Settings.get_instance()
|
||||||
|
s['x'] = self.pos().x()
|
||||||
|
s['y'] = self.pos().y()
|
||||||
|
s['width'] = self.width()
|
||||||
|
s['height'] = self.height()
|
||||||
|
s.save()
|
||||||
QtGui.QApplication.closeAllWindows()
|
QtGui.QApplication.closeAllWindows()
|
||||||
|
|
||||||
def resizeEvent(self, *args, **kwargs):
|
def resizeEvent(self, *args, **kwargs):
|
||||||
self.messages.setGeometry(0, 0, self.width() - 300, self.height() - 172)
|
self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155)
|
||||||
self.friends_list.setGeometry(0, 0, 270, self.height() - 142)
|
self.friends_list.setGeometry(0, 0, 270, self.height() - 125)
|
||||||
self.callButton.setGeometry(QtCore.QRect(self.width() - 370, 20, 50, 50))
|
|
||||||
self.typing.setGeometry(QtCore.QRect(self.width() - 420, 30, 50, 30))
|
|
||||||
|
|
||||||
self.messageEdit.setGeometry(QtCore.QRect(120, 2, self.width() - 490, 55))
|
self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 40, 50, 50))
|
||||||
self.screenshotButton.setGeometry(QtCore.QRect(0, 2, 55, 55))
|
self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 40, 50, 50))
|
||||||
self.fileTransferButton.setGeometry(QtCore.QRect(60, 2, 55, 55))
|
self.typing.setGeometry(QtCore.QRect(self.width() - 450, 50, 50, 30))
|
||||||
self.sendMessageButton.setGeometry(QtCore.QRect(self.width() - 360, 2, 60, 55))
|
|
||||||
|
|
||||||
self.account_name.setGeometry(QtCore.QRect(100, 30, self.width() - 520, 25))
|
self.messageEdit.setGeometry(QtCore.QRect(55, 0, self.width() - 395, 55))
|
||||||
self.account_status.setGeometry(QtCore.QRect(100, 50, self.width() - 520, 25))
|
self.menuButton.setGeometry(QtCore.QRect(0, 0, 55, 55))
|
||||||
|
self.sendMessageButton.setGeometry(QtCore.QRect(self.width() - 340, 0, 70, 55))
|
||||||
|
|
||||||
|
self.account_name.setGeometry(QtCore.QRect(100, 40, self.width() - 560, 25))
|
||||||
|
self.account_status.setGeometry(QtCore.QRect(100, 60, self.width() - 560, 25))
|
||||||
|
self.messageEdit.setFocus()
|
||||||
self.profile.update()
|
self.profile.update()
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
@ -374,8 +363,8 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
self.p_s = PluginsSettings()
|
self.p_s = PluginsSettings()
|
||||||
self.p_s.show()
|
self.p_s.show()
|
||||||
|
|
||||||
def add_contact(self):
|
def add_contact(self, link=''):
|
||||||
self.a_c = AddContact()
|
self.a_c = AddContact(link)
|
||||||
self.a_c.show()
|
self.a_c.show()
|
||||||
|
|
||||||
def profile_settings(self, *args):
|
def profile_settings(self, *args):
|
||||||
@ -398,6 +387,15 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
self.audio_s = AudioSettings()
|
self.audio_s = AudioSettings()
|
||||||
self.audio_s.show()
|
self.audio_s.show()
|
||||||
|
|
||||||
|
def show_menu(self):
|
||||||
|
if not hasattr(self, 'menu'):
|
||||||
|
self.menu = DropdownMenu(self)
|
||||||
|
self.menu.setGeometry(QtCore.QRect(0 if Settings.get_instance()['mirror_mode'] else 270,
|
||||||
|
self.height() - 100,
|
||||||
|
150,
|
||||||
|
100))
|
||||||
|
self.menu.show()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Messages, calls and file transfers
|
# Messages, calls and file transfers
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
@ -407,23 +405,40 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
self.profile.send_message(text)
|
self.profile.send_message(text)
|
||||||
|
|
||||||
def send_file(self):
|
def send_file(self):
|
||||||
if self.profile.is_active_online(): # active friend exists and online
|
self.menu.hide()
|
||||||
choose_file = QtGui.QApplication.translate("MainWindow", 'Choose file', None, QtGui.QApplication.UnicodeUTF8)
|
if self.profile.active_friend + 1:
|
||||||
choose = QtGui.QApplication.translate("MainWindow", choose_file, None, QtGui.QApplication.UnicodeUTF8)
|
choose = QtGui.QApplication.translate("MainWindow", 'Choose file', None, QtGui.QApplication.UnicodeUTF8)
|
||||||
name = QtGui.QFileDialog.getOpenFileName(self, choose)
|
name = QtGui.QFileDialog.getOpenFileName(self, choose)
|
||||||
if name[0]:
|
if name[0]:
|
||||||
self.profile.send_file(name[0])
|
self.profile.send_file(name[0])
|
||||||
|
|
||||||
def send_screenshot(self, hide=False):
|
def send_screenshot(self, hide=False):
|
||||||
if self.profile.is_active_online(): # active friend exists and online
|
self.menu.hide()
|
||||||
|
if self.profile.active_friend + 1:
|
||||||
self.sw = ScreenShotWindow(self)
|
self.sw = ScreenShotWindow(self)
|
||||||
self.sw.show()
|
self.sw.show()
|
||||||
if hide:
|
if hide:
|
||||||
self.hide()
|
self.hide()
|
||||||
|
|
||||||
def call(self):
|
def send_smiley(self):
|
||||||
if self.profile.is_active_online(): # active friend exists and online
|
self.menu.hide()
|
||||||
self.profile.call_click(True)
|
if self.profile.active_friend + 1:
|
||||||
|
self.smiley = SmileyWindow(self)
|
||||||
|
self.smiley.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(),
|
||||||
|
self.y() + self.height() - 200,
|
||||||
|
self.smiley.width(),
|
||||||
|
self.smiley.height()))
|
||||||
|
self.smiley.show()
|
||||||
|
|
||||||
|
def send_sticker(self):
|
||||||
|
self.menu.hide()
|
||||||
|
if self.profile.active_friend + 1:
|
||||||
|
self.sticker = StickerWindow(self)
|
||||||
|
self.sticker.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(),
|
||||||
|
self.y() + self.height() - 200,
|
||||||
|
self.sticker.width(),
|
||||||
|
self.sticker.height()))
|
||||||
|
self.sticker.show()
|
||||||
|
|
||||||
def active_call(self):
|
def active_call(self):
|
||||||
self.update_call_state('finish_call')
|
self.update_call_state('finish_call')
|
||||||
@ -435,10 +450,16 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
self.update_call_state('call')
|
self.update_call_state('call')
|
||||||
|
|
||||||
def update_call_state(self, fl):
|
def update_call_state(self, fl):
|
||||||
|
# TODO: do smth with video call button
|
||||||
|
os.chdir(curr_directory() + '/images/')
|
||||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(fl))
|
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(fl))
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
self.callButton.setIcon(icon)
|
self.callButton.setIcon(icon)
|
||||||
self.callButton.setIconSize(QtCore.QSize(50, 50))
|
self.callButton.setIconSize(QtCore.QSize(50, 50))
|
||||||
|
pixmap = QtGui.QPixmap(curr_directory() + '/images/videocall.png')
|
||||||
|
icon = QtGui.QIcon(pixmap)
|
||||||
|
self.videocallButton.setIcon(icon)
|
||||||
|
self.videocallButton.setIconSize(QtCore.QSize(35, 35))
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Functions which called when user open context menu in friends list
|
# Functions which called when user open context menu in friends list
|
||||||
@ -458,6 +479,8 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
copy_key_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Copy public key', None, QtGui.QApplication.UnicodeUTF8))
|
copy_key_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Copy public key', None, QtGui.QApplication.UnicodeUTF8))
|
||||||
auto_accept_item = self.listMenu.addAction(auto)
|
auto_accept_item = self.listMenu.addAction(auto)
|
||||||
remove_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Remove friend', None, QtGui.QApplication.UnicodeUTF8))
|
remove_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Remove friend', None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
notes_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Notes', None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
|
||||||
submenu = plugin_support.PluginLoader.get_instance().get_menu(self.listMenu, num)
|
submenu = plugin_support.PluginLoader.get_instance().get_menu(self.listMenu, num)
|
||||||
if len(submenu):
|
if len(submenu):
|
||||||
plug = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8))
|
plug = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8))
|
||||||
@ -467,10 +490,26 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
self.connect(copy_key_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_friend_key(num))
|
self.connect(copy_key_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_friend_key(num))
|
||||||
self.connect(clear_history_item, QtCore.SIGNAL("triggered()"), lambda: self.clear_history(num))
|
self.connect(clear_history_item, QtCore.SIGNAL("triggered()"), lambda: self.clear_history(num))
|
||||||
self.connect(auto_accept_item, QtCore.SIGNAL("triggered()"), lambda: self.auto_accept(num, not allowed))
|
self.connect(auto_accept_item, QtCore.SIGNAL("triggered()"), lambda: self.auto_accept(num, not allowed))
|
||||||
|
self.connect(notes_item, QtCore.SIGNAL("triggered()"), lambda: self.show_note(friend))
|
||||||
parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0))
|
parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0))
|
||||||
self.listMenu.move(parent_position + pos)
|
self.listMenu.move(parent_position + pos)
|
||||||
self.listMenu.show()
|
self.listMenu.show()
|
||||||
|
|
||||||
|
def show_note(self, friend):
|
||||||
|
s = Settings.get_instance()
|
||||||
|
note = s['notes'][friend.tox_id] if friend.tox_id in s['notes'] else ''
|
||||||
|
user = QtGui.QApplication.translate("MainWindow", 'Notes about user', None, QtGui.QApplication.UnicodeUTF8)
|
||||||
|
user = '{} {}'.format(user, friend.name)
|
||||||
|
|
||||||
|
def save_note(text):
|
||||||
|
if friend.tox_id in s['notes']:
|
||||||
|
del s['notes'][friend.tox_id]
|
||||||
|
if text:
|
||||||
|
s['notes'][friend.tox_id] = text
|
||||||
|
s.save()
|
||||||
|
self.note = MultilineEdit(user, note, save_note)
|
||||||
|
self.note.show()
|
||||||
|
|
||||||
def set_alias(self, num):
|
def set_alias(self, num):
|
||||||
self.profile.set_alias(num)
|
self.profile.set_alias(num)
|
||||||
|
|
||||||
@ -503,71 +542,13 @@ class MainWindow(QtGui.QMainWindow):
|
|||||||
self.profile.set_active(num)
|
self.profile.set_active(num)
|
||||||
|
|
||||||
def mouseReleaseEvent(self, event):
|
def mouseReleaseEvent(self, event):
|
||||||
x, y = event.x(), event.y()
|
|
||||||
pos = self.connection_status.pos()
|
pos = self.connection_status.pos()
|
||||||
if (pos.x() < x < pos.x() + 32) and (pos.y() < y < pos.y() + 32):
|
x, y = pos.x() + self.user_info.pos().x(), pos.y() + self.user_info.pos().y()
|
||||||
|
if (x < event.x() < x + 32) and (y < event.y() < y + 32):
|
||||||
self.profile.change_status()
|
self.profile.change_status()
|
||||||
else:
|
else:
|
||||||
super(self.__class__, self).mouseReleaseEvent(event)
|
super(MainWindow, self).mouseReleaseEvent(event)
|
||||||
|
|
||||||
def filtering(self):
|
def filtering(self):
|
||||||
self.profile.filtration(self.online_contacts.currentIndex() == 1, self.contact_name.text())
|
self.profile.filtration(self.online_contacts.currentIndex() == 1, self.contact_name.text())
|
||||||
|
|
||||||
|
|
||||||
class ScreenShotWindow(QtGui.QWidget):
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
|
||||||
super(ScreenShotWindow, self).__init__()
|
|
||||||
self.parent = parent
|
|
||||||
self.setMouseTracking(True)
|
|
||||||
self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
|
|
||||||
self.showFullScreen()
|
|
||||||
self.setWindowOpacity(0.5)
|
|
||||||
self.rubberband = QtGui.QRubberBand(QtGui.QRubberBand.Rectangle, None)
|
|
||||||
|
|
||||||
def closeEvent(self, *args):
|
|
||||||
if self.parent.isHidden():
|
|
||||||
self.parent.show()
|
|
||||||
|
|
||||||
def mousePressEvent(self, event):
|
|
||||||
self.origin = event.pos()
|
|
||||||
self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize()))
|
|
||||||
self.rubberband.show()
|
|
||||||
QtGui.QWidget.mousePressEvent(self, event)
|
|
||||||
|
|
||||||
def mouseMoveEvent(self, event):
|
|
||||||
if self.rubberband.isVisible():
|
|
||||||
self.rubberband.setGeometry(QtCore.QRect(self.origin, event.pos()).normalized())
|
|
||||||
left = QtGui.QRegion(QtCore.QRect(0, 0, self.rubberband.x(), self.height()))
|
|
||||||
right = QtGui.QRegion(QtCore.QRect(self.rubberband.x() + self.rubberband.width(), 0, self.width(), self.height()))
|
|
||||||
top = QtGui.QRegion(0, 0, self.width(), self.rubberband.y())
|
|
||||||
bottom = QtGui.QRegion(0, self.rubberband.y() + self.rubberband.height(), self.width(), self.height())
|
|
||||||
self.setMask(left + right + top + bottom)
|
|
||||||
|
|
||||||
def mouseReleaseEvent(self, event):
|
|
||||||
if self.rubberband.isVisible():
|
|
||||||
self.rubberband.hide()
|
|
||||||
rect = self.rubberband.geometry()
|
|
||||||
print rect
|
|
||||||
if rect.width() and rect.height():
|
|
||||||
p = QtGui.QPixmap.grabWindow(QtGui.QApplication.desktop().winId(),
|
|
||||||
rect.x() + 4,
|
|
||||||
rect.y() + 4,
|
|
||||||
rect.width() - 8,
|
|
||||||
rect.height() - 8)
|
|
||||||
byte_array = QtCore.QByteArray()
|
|
||||||
buffer = QtCore.QBuffer(byte_array)
|
|
||||||
buffer.open(QtCore.QIODevice.WriteOnly)
|
|
||||||
p.save(buffer, 'PNG')
|
|
||||||
Profile.get_instance().send_screenshot(str(byte_array.data()))
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
|
||||||
if event.key() == QtCore.Qt.Key_Escape:
|
|
||||||
self.rubberband.setHidden(True)
|
|
||||||
self.close()
|
|
||||||
else:
|
|
||||||
super(ScreenShotWindow, self).keyPressEvent(event)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
314
src/mainscreen_widgets.py
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
try:
|
||||||
|
from PySide import QtCore, QtGui
|
||||||
|
except ImportError:
|
||||||
|
from PyQt4 import QtCore, QtGui
|
||||||
|
from widgets import RubberBand, create_menu, QRightClickButton
|
||||||
|
from profile import Profile
|
||||||
|
import smileys
|
||||||
|
import util
|
||||||
|
|
||||||
|
|
||||||
|
class MessageArea(QtGui.QPlainTextEdit):
|
||||||
|
"""User types messages here"""
|
||||||
|
|
||||||
|
def __init__(self, parent, form):
|
||||||
|
super(MessageArea, self).__init__(parent)
|
||||||
|
self.parent = form
|
||||||
|
self.setAcceptDrops(True)
|
||||||
|
self.timer = QtCore.QTimer(self)
|
||||||
|
self.timer.timeout.connect(lambda: self.parent.profile.send_typing(False))
|
||||||
|
|
||||||
|
def keyPressEvent(self, event):
|
||||||
|
if event.matches(QtGui.QKeySequence.Paste):
|
||||||
|
self.pasteEvent()
|
||||||
|
elif event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
|
||||||
|
modifiers = event.modifiers()
|
||||||
|
if modifiers & QtCore.Qt.ControlModifier or modifiers & QtCore.Qt.ShiftModifier:
|
||||||
|
self.insertPlainText('\n')
|
||||||
|
else:
|
||||||
|
if self.timer.isActive():
|
||||||
|
self.timer.stop()
|
||||||
|
self.parent.profile.send_typing(False)
|
||||||
|
self.parent.send_message()
|
||||||
|
elif event.key() == QtCore.Qt.Key_Up and not self.toPlainText():
|
||||||
|
self.appendPlainText(Profile.get_instance().get_last_message())
|
||||||
|
else:
|
||||||
|
self.parent.profile.send_typing(True)
|
||||||
|
if self.timer.isActive():
|
||||||
|
self.timer.stop()
|
||||||
|
self.timer.start(5000)
|
||||||
|
super(MessageArea, self).keyPressEvent(event)
|
||||||
|
|
||||||
|
def contextMenuEvent(self, event):
|
||||||
|
menu = create_menu(self.createStandardContextMenu())
|
||||||
|
menu.exec_(event.globalPos())
|
||||||
|
del menu
|
||||||
|
|
||||||
|
def dragEnterEvent(self, e):
|
||||||
|
e.accept()
|
||||||
|
|
||||||
|
def dragMoveEvent(self, e):
|
||||||
|
e.accept()
|
||||||
|
|
||||||
|
def dropEvent(self, e):
|
||||||
|
if e.mimeData().hasFormat('text/plain'):
|
||||||
|
e.accept()
|
||||||
|
self.pasteEvent(e.mimeData().text())
|
||||||
|
else:
|
||||||
|
e.ignore()
|
||||||
|
|
||||||
|
def pasteEvent(self, text=None):
|
||||||
|
text = text or QtGui.QApplication.clipboard().text()
|
||||||
|
if text.startswith('file://'):
|
||||||
|
self.parent.profile.send_file(text[7:])
|
||||||
|
else:
|
||||||
|
self.insertPlainText(text)
|
||||||
|
|
||||||
|
|
||||||
|
class ScreenShotWindow(QtGui.QWidget):
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
super(ScreenShotWindow, self).__init__()
|
||||||
|
self.parent = parent
|
||||||
|
self.setMouseTracking(True)
|
||||||
|
self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
|
||||||
|
self.showFullScreen()
|
||||||
|
self.setWindowOpacity(0.5)
|
||||||
|
self.rubberband = RubberBand()
|
||||||
|
|
||||||
|
def closeEvent(self, *args):
|
||||||
|
if self.parent.isHidden():
|
||||||
|
self.parent.show()
|
||||||
|
|
||||||
|
def mousePressEvent(self, event):
|
||||||
|
self.origin = event.pos()
|
||||||
|
self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize()))
|
||||||
|
self.rubberband.show()
|
||||||
|
QtGui.QWidget.mousePressEvent(self, event)
|
||||||
|
|
||||||
|
def mouseMoveEvent(self, event):
|
||||||
|
if self.rubberband.isVisible():
|
||||||
|
self.rubberband.setGeometry(QtCore.QRect(self.origin, event.pos()).normalized())
|
||||||
|
left = QtGui.QRegion(QtCore.QRect(0, 0, self.rubberband.x(), self.height()))
|
||||||
|
right = QtGui.QRegion(QtCore.QRect(self.rubberband.x() + self.rubberband.width(), 0, self.width(), self.height()))
|
||||||
|
top = QtGui.QRegion(0, 0, self.width(), self.rubberband.y())
|
||||||
|
bottom = QtGui.QRegion(0, self.rubberband.y() + self.rubberband.height(), self.width(), self.height())
|
||||||
|
self.setMask(left + right + top + bottom)
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, event):
|
||||||
|
if self.rubberband.isVisible():
|
||||||
|
self.rubberband.hide()
|
||||||
|
rect = self.rubberband.geometry()
|
||||||
|
if rect.width() and rect.height():
|
||||||
|
p = QtGui.QPixmap.grabWindow(QtGui.QApplication.desktop().winId(),
|
||||||
|
rect.x() + 4,
|
||||||
|
rect.y() + 4,
|
||||||
|
rect.width() - 8,
|
||||||
|
rect.height() - 8)
|
||||||
|
byte_array = QtCore.QByteArray()
|
||||||
|
buffer = QtCore.QBuffer(byte_array)
|
||||||
|
buffer.open(QtCore.QIODevice.WriteOnly)
|
||||||
|
p.save(buffer, 'PNG')
|
||||||
|
Profile.get_instance().send_screenshot(bytes(byte_array.data()))
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def keyPressEvent(self, event):
|
||||||
|
if event.key() == QtCore.Qt.Key_Escape:
|
||||||
|
self.rubberband.setHidden(True)
|
||||||
|
self.close()
|
||||||
|
else:
|
||||||
|
super(ScreenShotWindow, self).keyPressEvent(event)
|
||||||
|
|
||||||
|
|
||||||
|
class SmileyWindow(QtGui.QWidget):
|
||||||
|
"""
|
||||||
|
Smiley selection window
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
super(SmileyWindow, self).__init__()
|
||||||
|
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
|
||||||
|
inst = smileys.SmileyLoader.get_instance()
|
||||||
|
self.data = inst.get_smileys()
|
||||||
|
count = len(self.data)
|
||||||
|
if not count:
|
||||||
|
self.close()
|
||||||
|
self.page_size = int(pow(count / 8, 0.5) + 1) * 8 # smileys per page
|
||||||
|
if count % self.page_size == 0:
|
||||||
|
self.page_count = count // self.page_size
|
||||||
|
else:
|
||||||
|
self.page_count = round(count / self.page_size + 0.5)
|
||||||
|
self.page = -1
|
||||||
|
self.radio = []
|
||||||
|
self.parent = parent
|
||||||
|
for i in range(self.page_count): # buttons with smileys
|
||||||
|
elem = QtGui.QRadioButton(self)
|
||||||
|
elem.setGeometry(QtCore.QRect(i * 20 + 5, 180, 20, 20))
|
||||||
|
elem.clicked.connect(lambda i=i: self.checked(i))
|
||||||
|
self.radio.append(elem)
|
||||||
|
width = max(self.page_count * 20 + 30, (self.page_size + 5) * 8 // 10)
|
||||||
|
self.setMaximumSize(width, 200)
|
||||||
|
self.setMinimumSize(width, 200)
|
||||||
|
self.buttons = []
|
||||||
|
for i in range(self.page_size): # pages - radio buttons
|
||||||
|
b = QtGui.QPushButton(self)
|
||||||
|
b.setGeometry(QtCore.QRect((i // 8) * 20 + 5, (i % 8) * 20, 20, 20))
|
||||||
|
b.clicked.connect(lambda i=i: self.clicked(i))
|
||||||
|
self.buttons.append(b)
|
||||||
|
self.checked(0)
|
||||||
|
|
||||||
|
def checked(self, pos): # new page opened
|
||||||
|
self.radio[self.page].setChecked(False)
|
||||||
|
self.radio[pos].setChecked(True)
|
||||||
|
self.page = pos
|
||||||
|
start = self.page * self.page_size
|
||||||
|
for i in range(self.page_size):
|
||||||
|
try:
|
||||||
|
self.buttons[i].setVisible(True)
|
||||||
|
pixmap = QtGui.QPixmap(self.data[start + i][1])
|
||||||
|
icon = QtGui.QIcon(pixmap)
|
||||||
|
self.buttons[i].setIcon(icon)
|
||||||
|
except:
|
||||||
|
self.buttons[i].setVisible(False)
|
||||||
|
|
||||||
|
def clicked(self, pos): # smiley selected
|
||||||
|
pos += self.page * self.page_size
|
||||||
|
smiley = self.data[pos][0]
|
||||||
|
self.parent.messageEdit.insertPlainText(smiley)
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def leaveEvent(self, event):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
|
class MenuButton(QtGui.QPushButton):
|
||||||
|
|
||||||
|
def __init__(self, parent, enter):
|
||||||
|
super(MenuButton, self).__init__(parent)
|
||||||
|
self.enter = enter
|
||||||
|
|
||||||
|
def enterEvent(self, event):
|
||||||
|
self.enter()
|
||||||
|
super(MenuButton, self).enterEvent(event)
|
||||||
|
|
||||||
|
|
||||||
|
class DropdownMenu(QtGui.QWidget):
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
super(DropdownMenu, self).__init__(parent)
|
||||||
|
self.installEventFilter(self)
|
||||||
|
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
|
||||||
|
self.setMaximumSize(150, 100)
|
||||||
|
self.setMinimumSize(150, 100)
|
||||||
|
self.screenshotButton = QRightClickButton(self)
|
||||||
|
self.screenshotButton.setGeometry(QtCore.QRect(0, 50, 50, 50))
|
||||||
|
self.screenshotButton.setObjectName("screenshotButton")
|
||||||
|
|
||||||
|
self.fileTransferButton = QtGui.QPushButton(self)
|
||||||
|
self.fileTransferButton.setGeometry(QtCore.QRect(50, 50, 50, 50))
|
||||||
|
self.fileTransferButton.setObjectName("fileTransferButton")
|
||||||
|
|
||||||
|
self.audioMessageButton = QtGui.QPushButton(self)
|
||||||
|
self.audioMessageButton.setGeometry(QtCore.QRect(100, 50, 50, 50))
|
||||||
|
|
||||||
|
self.smileyButton = QtGui.QPushButton(self)
|
||||||
|
self.smileyButton.setGeometry(QtCore.QRect(0, 0, 50, 50))
|
||||||
|
|
||||||
|
self.videoMessageButton = QtGui.QPushButton(self)
|
||||||
|
self.videoMessageButton.setGeometry(QtCore.QRect(100, 0, 50, 50))
|
||||||
|
|
||||||
|
self.stickerButton = QtGui.QPushButton(self)
|
||||||
|
self.stickerButton.setGeometry(QtCore.QRect(50, 0, 50, 50))
|
||||||
|
|
||||||
|
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/file.png')
|
||||||
|
icon = QtGui.QIcon(pixmap)
|
||||||
|
self.fileTransferButton.setIcon(icon)
|
||||||
|
self.fileTransferButton.setIconSize(QtCore.QSize(40, 40))
|
||||||
|
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/screenshot.png')
|
||||||
|
icon = QtGui.QIcon(pixmap)
|
||||||
|
self.screenshotButton.setIcon(icon)
|
||||||
|
self.screenshotButton.setIconSize(QtCore.QSize(40, 50))
|
||||||
|
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/audio_message.png')
|
||||||
|
icon = QtGui.QIcon(pixmap)
|
||||||
|
self.audioMessageButton.setIcon(icon)
|
||||||
|
self.audioMessageButton.setIconSize(QtCore.QSize(40, 40))
|
||||||
|
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/smiley.png')
|
||||||
|
icon = QtGui.QIcon(pixmap)
|
||||||
|
self.smileyButton.setIcon(icon)
|
||||||
|
self.smileyButton.setIconSize(QtCore.QSize(40, 40))
|
||||||
|
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/video_message.png')
|
||||||
|
icon = QtGui.QIcon(pixmap)
|
||||||
|
self.videoMessageButton.setIcon(icon)
|
||||||
|
self.videoMessageButton.setIconSize(QtCore.QSize(45, 45))
|
||||||
|
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/sticker.png')
|
||||||
|
icon = QtGui.QIcon(pixmap)
|
||||||
|
self.stickerButton.setIcon(icon)
|
||||||
|
self.stickerButton.setIconSize(QtCore.QSize(45, 45))
|
||||||
|
|
||||||
|
self.screenshotButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send screenshot", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
self.fileTransferButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send file", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
self.audioMessageButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send audio message", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
self.videoMessageButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send video message", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
self.smileyButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Add smiley", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
self.stickerButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send sticker", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
|
||||||
|
self.fileTransferButton.clicked.connect(parent.send_file)
|
||||||
|
self.screenshotButton.clicked.connect(parent.send_screenshot)
|
||||||
|
self.connect(self.screenshotButton, QtCore.SIGNAL("rightClicked()"), lambda: parent.send_screenshot(True))
|
||||||
|
self.smileyButton.clicked.connect(parent.send_smiley)
|
||||||
|
self.stickerButton.clicked.connect(parent.send_sticker)
|
||||||
|
|
||||||
|
def leaveEvent(self, event):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def eventFilter(self, object, event):
|
||||||
|
if event.type() == QtCore.QEvent.WindowDeactivate:
|
||||||
|
self.close()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class StickerItem(QtGui.QWidget):
|
||||||
|
|
||||||
|
def __init__(self, fl):
|
||||||
|
super(StickerItem, self).__init__()
|
||||||
|
self._image_label = QtGui.QLabel(self)
|
||||||
|
self.path = fl
|
||||||
|
self.pixmap = QtGui.QPixmap()
|
||||||
|
self.pixmap.load(fl)
|
||||||
|
if self.pixmap.width() > 150:
|
||||||
|
self.pixmap = self.pixmap.scaled(150, 200, QtCore.Qt.KeepAspectRatio)
|
||||||
|
self.setFixedSize(150, self.pixmap.height())
|
||||||
|
self._image_label.setPixmap(self.pixmap)
|
||||||
|
|
||||||
|
|
||||||
|
class StickerWindow(QtGui.QWidget):
|
||||||
|
"""Sticker selection window"""
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
super(StickerWindow, self).__init__()
|
||||||
|
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
|
||||||
|
self.setMaximumSize(250, 200)
|
||||||
|
self.setMinimumSize(250, 200)
|
||||||
|
self.list = QtGui.QListWidget(self)
|
||||||
|
self.list.setGeometry(QtCore.QRect(0, 0, 250, 200))
|
||||||
|
self.arr = smileys.sticker_loader()
|
||||||
|
for sticker in self.arr:
|
||||||
|
item = StickerItem(sticker)
|
||||||
|
elem = QtGui.QListWidgetItem()
|
||||||
|
elem.setSizeHint(QtCore.QSize(250, item.height()))
|
||||||
|
self.list.addItem(elem)
|
||||||
|
self.list.setItemWidget(elem, item)
|
||||||
|
self.list.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
|
||||||
|
self.list.setSpacing(3)
|
||||||
|
self.list.clicked.connect(self.click)
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
def click(self, index):
|
||||||
|
num = index.row()
|
||||||
|
self.parent.profile.send_sticker(self.arr[num])
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def leaveEvent(self, event):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
|
350
src/menu.py
@ -4,8 +4,8 @@ except ImportError:
|
|||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
from settings import *
|
from settings import *
|
||||||
from profile import Profile
|
from profile import Profile
|
||||||
from util import get_style, curr_directory
|
from util import curr_directory
|
||||||
from widgets import CenteredWidget, DataLabel
|
from widgets import CenteredWidget, DataLabel, LineEdit
|
||||||
import pyaudio
|
import pyaudio
|
||||||
import toxencryptsave
|
import toxencryptsave
|
||||||
import plugin_support
|
import plugin_support
|
||||||
@ -14,11 +14,11 @@ import plugin_support
|
|||||||
class AddContact(CenteredWidget):
|
class AddContact(CenteredWidget):
|
||||||
"""Add contact form"""
|
"""Add contact form"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, tox_id=''):
|
||||||
super(AddContact, self).__init__()
|
super(AddContact, self).__init__()
|
||||||
self.initUI()
|
self.initUI(tox_id)
|
||||||
|
|
||||||
def initUI(self):
|
def initUI(self, tox_id):
|
||||||
self.setObjectName('AddContact')
|
self.setObjectName('AddContact')
|
||||||
self.resize(568, 306)
|
self.resize(568, 306)
|
||||||
self.sendRequestButton = QtGui.QPushButton(self)
|
self.sendRequestButton = QtGui.QPushButton(self)
|
||||||
@ -27,24 +27,25 @@ class AddContact(CenteredWidget):
|
|||||||
self.sendRequestButton.setBaseSize(QtCore.QSize(0, 0))
|
self.sendRequestButton.setBaseSize(QtCore.QSize(0, 0))
|
||||||
self.sendRequestButton.setObjectName("sendRequestButton")
|
self.sendRequestButton.setObjectName("sendRequestButton")
|
||||||
self.sendRequestButton.clicked.connect(self.add_friend)
|
self.sendRequestButton.clicked.connect(self.add_friend)
|
||||||
self.tox_id = QtGui.QLineEdit(self)
|
self.tox_id = LineEdit(self)
|
||||||
self.tox_id.setGeometry(QtCore.QRect(50, 40, 471, 27))
|
self.tox_id.setGeometry(QtCore.QRect(50, 40, 471, 27))
|
||||||
self.tox_id.setObjectName("lineEdit")
|
self.tox_id.setObjectName("lineEdit")
|
||||||
|
self.tox_id.setText(tox_id)
|
||||||
self.label = QtGui.QLabel(self)
|
self.label = QtGui.QLabel(self)
|
||||||
self.label.setGeometry(QtCore.QRect(60, 10, 80, 20))
|
self.label.setGeometry(QtCore.QRect(50, 10, 80, 20))
|
||||||
self.error_label = DataLabel(self)
|
self.error_label = DataLabel(self)
|
||||||
self.error_label.setGeometry(QtCore.QRect(120, 10, 420, 20))
|
self.error_label.setGeometry(QtCore.QRect(120, 10, 420, 20))
|
||||||
font = QtGui.QFont()
|
font = QtGui.QFont()
|
||||||
font.setPointSize(10)
|
font.setPointSize(10)
|
||||||
font.setWeight(30)
|
font.setWeight(30)
|
||||||
self.error_label.setFont(font)
|
self.error_label.setFont(font)
|
||||||
self.error_label.setStyleSheet("QLabel { color: red; }")
|
self.error_label.setStyleSheet("QLabel { color: #BC1C1C; }")
|
||||||
self.label.setObjectName("label")
|
self.label.setObjectName("label")
|
||||||
self.message_edit = QtGui.QTextEdit(self)
|
self.message_edit = QtGui.QTextEdit(self)
|
||||||
self.message_edit.setGeometry(QtCore.QRect(50, 110, 471, 151))
|
self.message_edit.setGeometry(QtCore.QRect(50, 110, 471, 151))
|
||||||
self.message_edit.setObjectName("textEdit")
|
self.message_edit.setObjectName("textEdit")
|
||||||
self.message = QtGui.QLabel(self)
|
self.message = QtGui.QLabel(self)
|
||||||
self.message.setGeometry(QtCore.QRect(60, 70, 101, 31))
|
self.message.setGeometry(QtCore.QRect(50, 70, 101, 31))
|
||||||
self.message.setFont(font)
|
self.message.setFont(font)
|
||||||
self.message.setObjectName("label_2")
|
self.message.setObjectName("label_2")
|
||||||
self.retranslateUi()
|
self.retranslateUi()
|
||||||
@ -70,6 +71,7 @@ class AddContact(CenteredWidget):
|
|||||||
self.sendRequestButton.setText(QtGui.QApplication.translate("Form", "Send request", None, QtGui.QApplication.UnicodeUTF8))
|
self.sendRequestButton.setText(QtGui.QApplication.translate("Form", "Send request", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.label.setText(QtGui.QApplication.translate('AddContact', "TOX ID:", None, QtGui.QApplication.UnicodeUTF8))
|
self.label.setText(QtGui.QApplication.translate('AddContact', "TOX ID:", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.message.setText(QtGui.QApplication.translate('AddContact', "Message:", None, QtGui.QApplication.UnicodeUTF8))
|
self.message.setText(QtGui.QApplication.translate('AddContact', "Message:", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
self.tox_id.setPlaceholderText(QtGui.QApplication.translate('AddContact', "TOX ID or public key of contact", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
|
||||||
|
|
||||||
class ProfileSettings(CenteredWidget):
|
class ProfileSettings(CenteredWidget):
|
||||||
@ -81,17 +83,17 @@ class ProfileSettings(CenteredWidget):
|
|||||||
|
|
||||||
def initUI(self):
|
def initUI(self):
|
||||||
self.setObjectName("ProfileSettingsForm")
|
self.setObjectName("ProfileSettingsForm")
|
||||||
self.setMinimumSize(QtCore.QSize(650, 520))
|
self.setMinimumSize(QtCore.QSize(700, 600))
|
||||||
self.setMaximumSize(QtCore.QSize(650, 520))
|
self.setMaximumSize(QtCore.QSize(700, 600))
|
||||||
self.nick = QtGui.QLineEdit(self)
|
self.nick = LineEdit(self)
|
||||||
self.nick.setGeometry(QtCore.QRect(30, 60, 350, 27))
|
self.nick.setGeometry(QtCore.QRect(30, 60, 350, 27))
|
||||||
self.nick.setObjectName("nick")
|
|
||||||
profile = Profile.get_instance()
|
profile = Profile.get_instance()
|
||||||
self.nick.setText(profile.name)
|
self.nick.setText(profile.name)
|
||||||
self.status = QtGui.QLineEdit(self)
|
self.status = QtGui.QComboBox(self)
|
||||||
self.status.setGeometry(QtCore.QRect(30, 130, 350, 27))
|
self.status.setGeometry(QtCore.QRect(400, 60, 200, 27))
|
||||||
self.status.setObjectName("status")
|
self.status_message = LineEdit(self)
|
||||||
self.status.setText(profile.status_message)
|
self.status_message.setGeometry(QtCore.QRect(30, 130, 350, 27))
|
||||||
|
self.status_message.setText(profile.status_message)
|
||||||
self.label = QtGui.QLabel(self)
|
self.label = QtGui.QLabel(self)
|
||||||
self.label.setGeometry(QtCore.QRect(40, 30, 91, 25))
|
self.label.setGeometry(QtCore.QRect(40, 30, 91, 25))
|
||||||
font = QtGui.QFont()
|
font = QtGui.QFont()
|
||||||
@ -99,62 +101,66 @@ class ProfileSettings(CenteredWidget):
|
|||||||
font.setWeight(75)
|
font.setWeight(75)
|
||||||
font.setBold(True)
|
font.setBold(True)
|
||||||
self.label.setFont(font)
|
self.label.setFont(font)
|
||||||
self.label.setObjectName("label")
|
|
||||||
self.label_2 = QtGui.QLabel(self)
|
self.label_2 = QtGui.QLabel(self)
|
||||||
self.label_2.setGeometry(QtCore.QRect(40, 100, 100, 25))
|
self.label_2.setGeometry(QtCore.QRect(40, 100, 100, 25))
|
||||||
self.label_2.setFont(font)
|
self.label_2.setFont(font)
|
||||||
self.label_2.setObjectName("label_2")
|
|
||||||
self.label_3 = QtGui.QLabel(self)
|
self.label_3 = QtGui.QLabel(self)
|
||||||
self.label_3.setGeometry(QtCore.QRect(40, 180, 100, 25))
|
self.label_3.setGeometry(QtCore.QRect(40, 180, 100, 25))
|
||||||
self.label_3.setFont(font)
|
self.label_3.setFont(font)
|
||||||
self.label_3.setObjectName("label_3")
|
|
||||||
self.tox_id = QtGui.QLabel(self)
|
self.tox_id = QtGui.QLabel(self)
|
||||||
self.tox_id.setGeometry(QtCore.QRect(10, 210, self.width(), 21))
|
self.tox_id.setGeometry(QtCore.QRect(15, 210, 685, 21))
|
||||||
font.setPointSize(10)
|
font.setPointSize(10)
|
||||||
self.tox_id.setFont(font)
|
self.tox_id.setFont(font)
|
||||||
self.tox_id.setObjectName("tox_id")
|
|
||||||
s = profile.tox_id
|
s = profile.tox_id
|
||||||
self.tox_id.setText(s)
|
self.tox_id.setText(s)
|
||||||
self.copyId = QtGui.QPushButton(self)
|
self.copyId = QtGui.QPushButton(self)
|
||||||
self.copyId.setGeometry(QtCore.QRect(40, 250, 160, 30))
|
self.copyId.setGeometry(QtCore.QRect(40, 250, 180, 30))
|
||||||
self.copyId.setObjectName("copyId")
|
|
||||||
self.copyId.clicked.connect(self.copy)
|
self.copyId.clicked.connect(self.copy)
|
||||||
self.export = QtGui.QPushButton(self)
|
self.export = QtGui.QPushButton(self)
|
||||||
self.export.setGeometry(QtCore.QRect(210, 250, 160, 30))
|
self.export.setGeometry(QtCore.QRect(230, 250, 180, 30))
|
||||||
self.export.setObjectName("export")
|
|
||||||
self.export.clicked.connect(self.export_profile)
|
self.export.clicked.connect(self.export_profile)
|
||||||
self.new_nospam = QtGui.QPushButton(self)
|
self.new_nospam = QtGui.QPushButton(self)
|
||||||
self.new_nospam.setGeometry(QtCore.QRect(380, 250, 160, 30))
|
self.new_nospam.setGeometry(QtCore.QRect(420, 250, 180, 30))
|
||||||
self.new_nospam.clicked.connect(self.new_no_spam)
|
self.new_nospam.clicked.connect(self.new_no_spam)
|
||||||
|
|
||||||
self.new_avatar = QtGui.QPushButton(self)
|
self.new_avatar = QtGui.QPushButton(self)
|
||||||
self.new_avatar.setGeometry(QtCore.QRect(400, 50, 200, 50))
|
self.new_avatar.setGeometry(QtCore.QRect(40, 300, 180, 30))
|
||||||
self.delete_avatar = QtGui.QPushButton(self)
|
self.delete_avatar = QtGui.QPushButton(self)
|
||||||
self.delete_avatar.setGeometry(QtCore.QRect(400, 120, 200, 50))
|
self.delete_avatar.setGeometry(QtCore.QRect(230, 300, 180, 30))
|
||||||
self.delete_avatar.clicked.connect(self.reset_avatar)
|
self.delete_avatar.clicked.connect(self.reset_avatar)
|
||||||
self.new_avatar.clicked.connect(self.set_avatar)
|
self.new_avatar.clicked.connect(self.set_avatar)
|
||||||
self.profile_pass = QtGui.QLabel(self)
|
self.profile_pass = QtGui.QLabel(self)
|
||||||
self.profile_pass.setGeometry(QtCore.QRect(40, 300, 300, 50))
|
self.profile_pass.setGeometry(QtCore.QRect(40, 340, 300, 30))
|
||||||
font.setPointSize(18)
|
font.setPointSize(18)
|
||||||
self.profile_pass.setFont(font)
|
self.profile_pass.setFont(font)
|
||||||
self.password = QtGui.QLineEdit(self)
|
self.password = LineEdit(self)
|
||||||
self.password.setGeometry(QtCore.QRect(30, 350, 300, 30))
|
self.password.setGeometry(QtCore.QRect(40, 380, 300, 30))
|
||||||
self.password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
|
self.password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
|
||||||
self.leave_blank = QtGui.QLabel(self)
|
self.leave_blank = QtGui.QLabel(self)
|
||||||
self.leave_blank.setGeometry(QtCore.QRect(340, 350, 300, 30))
|
self.leave_blank.setGeometry(QtCore.QRect(350, 380, 300, 30))
|
||||||
self.confirm_password = QtGui.QLineEdit(self)
|
self.confirm_password = LineEdit(self)
|
||||||
self.confirm_password.setGeometry(QtCore.QRect(30, 400, 300, 30))
|
self.confirm_password.setGeometry(QtCore.QRect(40, 420, 300, 30))
|
||||||
self.confirm_password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
|
self.confirm_password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
|
||||||
self.set_password = QtGui.QPushButton(self)
|
self.set_password = QtGui.QPushButton(self)
|
||||||
self.set_password.setGeometry(QtCore.QRect(30, 450, 300, 30))
|
self.set_password.setGeometry(QtCore.QRect(40, 470, 300, 30))
|
||||||
self.set_password.clicked.connect(self.new_password)
|
self.set_password.clicked.connect(self.new_password)
|
||||||
self.not_match = QtGui.QLabel(self)
|
self.not_match = QtGui.QLabel(self)
|
||||||
self.not_match.setGeometry(QtCore.QRect(340, 400, 300, 30))
|
self.not_match.setGeometry(QtCore.QRect(350, 420, 300, 30))
|
||||||
self.not_match.setVisible(False)
|
self.not_match.setVisible(False)
|
||||||
self.not_match.setStyleSheet('QLabel { color: #F70D1A; }')
|
self.not_match.setStyleSheet('QLabel { color: #BC1C1C; }')
|
||||||
self.warning = QtGui.QLabel(self)
|
self.warning = QtGui.QLabel(self)
|
||||||
self.warning.setGeometry(QtCore.QRect(30, 490, 500, 30))
|
self.warning.setGeometry(QtCore.QRect(40, 510, 500, 30))
|
||||||
self.warning.setStyleSheet('QLabel { color: #F70D1A; }')
|
self.warning.setStyleSheet('QLabel { color: #BC1C1C; }')
|
||||||
|
self.default = QtGui.QPushButton(self)
|
||||||
|
self.default.setGeometry(QtCore.QRect(40, 550, 620, 30))
|
||||||
|
path, name = Settings.get_auto_profile()
|
||||||
|
self.auto = path + name == ProfileHelper.get_path() + Settings.get_instance().name
|
||||||
|
self.default.clicked.connect(self.auto_profile)
|
||||||
self.retranslateUi()
|
self.retranslateUi()
|
||||||
|
if profile.status is not None:
|
||||||
|
self.status.setCurrentIndex(profile.status)
|
||||||
|
else:
|
||||||
|
self.status.setVisible(False)
|
||||||
QtCore.QMetaObject.connectSlotsByName(self)
|
QtCore.QMetaObject.connectSlotsByName(self)
|
||||||
|
|
||||||
def retranslateUi(self):
|
def retranslateUi(self):
|
||||||
@ -174,6 +180,27 @@ class ProfileSettings(CenteredWidget):
|
|||||||
self.not_match.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Passwords do not match", None, QtGui.QApplication.UnicodeUTF8))
|
self.not_match.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Passwords do not match", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.leave_blank.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Leaving blank will reset current password", None, QtGui.QApplication.UnicodeUTF8))
|
self.leave_blank.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Leaving blank will reset current password", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.warning.setText(QtGui.QApplication.translate("ProfileSettingsForm", "There is no way to recover lost passwords", None, QtGui.QApplication.UnicodeUTF8))
|
self.warning.setText(QtGui.QApplication.translate("ProfileSettingsForm", "There is no way to recover lost passwords", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
self.status.addItem(QtGui.QApplication.translate("ProfileSettingsForm", "Online", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
self.status.addItem(QtGui.QApplication.translate("ProfileSettingsForm", "Away", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
self.status.addItem(QtGui.QApplication.translate("ProfileSettingsForm", "Busy", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
if self.auto:
|
||||||
|
self.default.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Mark as not default profile", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
else:
|
||||||
|
self.default.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Mark as default profile", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
|
||||||
|
def auto_profile(self):
|
||||||
|
if self.auto:
|
||||||
|
Settings.reset_auto_profile()
|
||||||
|
else:
|
||||||
|
Settings.set_auto_profile(ProfileHelper.get_path(), Settings.get_instance().name)
|
||||||
|
self.auto = not self.auto
|
||||||
|
if self.auto:
|
||||||
|
self.default.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Mark as not default profile", None,
|
||||||
|
QtGui.QApplication.UnicodeUTF8))
|
||||||
|
else:
|
||||||
|
self.default.setText(
|
||||||
|
QtGui.QApplication.translate("ProfileSettingsForm", "Mark as default profile", None,
|
||||||
|
QtGui.QApplication.UnicodeUTF8))
|
||||||
|
|
||||||
def new_password(self):
|
def new_password(self):
|
||||||
if self.password.text() == self.confirm_password.text():
|
if self.password.text() == self.confirm_password.text():
|
||||||
@ -208,11 +235,17 @@ class ProfileSettings(CenteredWidget):
|
|||||||
|
|
||||||
def set_avatar(self):
|
def set_avatar(self):
|
||||||
choose = QtGui.QApplication.translate("ProfileSettingsForm", "Choose avatar", None, QtGui.QApplication.UnicodeUTF8)
|
choose = QtGui.QApplication.translate("ProfileSettingsForm", "Choose avatar", None, QtGui.QApplication.UnicodeUTF8)
|
||||||
name = QtGui.QFileDialog.getOpenFileName(self, choose, None, 'Image Files (*.png)')
|
name = QtGui.QFileDialog.getOpenFileName(self, choose, None, 'Images (*.png)')
|
||||||
if name[0]:
|
if name[0]:
|
||||||
with open(name[0], 'rb') as f:
|
bitmap = QtGui.QPixmap(name[0])
|
||||||
data = f.read()
|
bitmap.scaled(QtCore.QSize(128, 128), aspectMode=QtCore.Qt.KeepAspectRatio,
|
||||||
Profile.get_instance().set_avatar(data)
|
mode=QtCore.Qt.SmoothTransformation)
|
||||||
|
|
||||||
|
byte_array = QtCore.QByteArray()
|
||||||
|
buffer = QtCore.QBuffer(byte_array)
|
||||||
|
buffer.open(QtCore.QIODevice.WriteOnly)
|
||||||
|
bitmap.save(buffer, 'PNG')
|
||||||
|
Profile.get_instance().set_avatar(str(byte_array.data()))
|
||||||
|
|
||||||
def export_profile(self):
|
def export_profile(self):
|
||||||
directory = QtGui.QFileDialog.getExistingDirectory() + '/'
|
directory = QtGui.QFileDialog.getExistingDirectory() + '/'
|
||||||
@ -225,8 +258,9 @@ class ProfileSettings(CenteredWidget):
|
|||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
profile = Profile.get_instance()
|
profile = Profile.get_instance()
|
||||||
profile.set_name(self.nick.text().encode('utf-8'))
|
profile.set_name(self.nick.text())
|
||||||
profile.set_status_message(self.status.text().encode('utf-8'))
|
profile.set_status_message(self.status_message.text().encode('utf-8'))
|
||||||
|
profile.set_status(self.status.currentIndex())
|
||||||
|
|
||||||
|
|
||||||
class NetworkSettings(CenteredWidget):
|
class NetworkSettings(CenteredWidget):
|
||||||
@ -254,10 +288,10 @@ class NetworkSettings(CenteredWidget):
|
|||||||
self.http = QtGui.QCheckBox(self)
|
self.http = QtGui.QCheckBox(self)
|
||||||
self.http.setGeometry(QtCore.QRect(20, 70, 97, 22))
|
self.http.setGeometry(QtCore.QRect(20, 70, 97, 22))
|
||||||
self.proxy.setObjectName("proxy")
|
self.proxy.setObjectName("proxy")
|
||||||
self.proxyip = QtGui.QLineEdit(self)
|
self.proxyip = LineEdit(self)
|
||||||
self.proxyip.setGeometry(QtCore.QRect(40, 130, 231, 27))
|
self.proxyip.setGeometry(QtCore.QRect(40, 130, 231, 27))
|
||||||
self.proxyip.setObjectName("proxyip")
|
self.proxyip.setObjectName("proxyip")
|
||||||
self.proxyport = QtGui.QLineEdit(self)
|
self.proxyport = LineEdit(self)
|
||||||
self.proxyport.setGeometry(QtCore.QRect(40, 190, 231, 27))
|
self.proxyport.setGeometry(QtCore.QRect(40, 190, 231, 27))
|
||||||
self.proxyport.setObjectName("proxyport")
|
self.proxyport.setObjectName("proxyport")
|
||||||
self.label = QtGui.QLabel(self)
|
self.label = QtGui.QLabel(self)
|
||||||
@ -272,11 +306,11 @@ class NetworkSettings(CenteredWidget):
|
|||||||
self.udp.setChecked(settings['udp_enabled'])
|
self.udp.setChecked(settings['udp_enabled'])
|
||||||
self.proxy.setChecked(settings['proxy_type'])
|
self.proxy.setChecked(settings['proxy_type'])
|
||||||
self.proxyip.setText(settings['proxy_host'])
|
self.proxyip.setText(settings['proxy_host'])
|
||||||
self.proxyport.setText(unicode(settings['proxy_port']))
|
self.proxyport.setText(str(settings['proxy_port']))
|
||||||
self.http.setChecked(settings['proxy_type'] == 1)
|
self.http.setChecked(settings['proxy_type'] == 1)
|
||||||
self.warning = QtGui.QLabel(self)
|
self.warning = QtGui.QLabel(self)
|
||||||
self.warning.setGeometry(QtCore.QRect(5, 270, 290, 60))
|
self.warning.setGeometry(QtCore.QRect(5, 270, 290, 60))
|
||||||
self.warning.setStyleSheet('QLabel { color: #F70D1A; }')
|
self.warning.setStyleSheet('QLabel { color: #BC1C1C; }')
|
||||||
self.retranslateUi()
|
self.retranslateUi()
|
||||||
self.proxy.stateChanged.connect(lambda x: self.activate())
|
self.proxy.stateChanged.connect(lambda x: self.activate())
|
||||||
self.activate()
|
self.activate()
|
||||||
@ -326,50 +360,51 @@ class PrivacySettings(CenteredWidget):
|
|||||||
|
|
||||||
def initUI(self):
|
def initUI(self):
|
||||||
self.setObjectName("privacySettings")
|
self.setObjectName("privacySettings")
|
||||||
self.resize(350, 550)
|
self.resize(370, 600)
|
||||||
self.setMinimumSize(QtCore.QSize(350, 550))
|
self.setMinimumSize(QtCore.QSize(370, 600))
|
||||||
self.setMaximumSize(QtCore.QSize(350, 550))
|
self.setMaximumSize(QtCore.QSize(370, 600))
|
||||||
self.saveHistory = QtGui.QCheckBox(self)
|
self.saveHistory = QtGui.QCheckBox(self)
|
||||||
self.saveHistory.setGeometry(QtCore.QRect(10, 20, 291, 22))
|
self.saveHistory.setGeometry(QtCore.QRect(10, 20, 350, 22))
|
||||||
self.saveHistory.setObjectName("saveHistory")
|
self.saveUnsentOnly = QtGui.QCheckBox(self)
|
||||||
|
self.saveUnsentOnly.setGeometry(QtCore.QRect(10, 60, 350, 22))
|
||||||
|
|
||||||
self.fileautoaccept = QtGui.QCheckBox(self)
|
self.fileautoaccept = QtGui.QCheckBox(self)
|
||||||
self.fileautoaccept.setGeometry(QtCore.QRect(10, 60, 271, 22))
|
self.fileautoaccept.setGeometry(QtCore.QRect(10, 100, 350, 22))
|
||||||
self.fileautoaccept.setObjectName("fileautoaccept")
|
|
||||||
self.typingNotifications = QtGui.QCheckBox(self)
|
self.typingNotifications = QtGui.QCheckBox(self)
|
||||||
self.typingNotifications.setGeometry(QtCore.QRect(10, 100, 350, 30))
|
self.typingNotifications.setGeometry(QtCore.QRect(10, 140, 350, 30))
|
||||||
self.typingNotifications.setObjectName("typingNotifications")
|
|
||||||
self.inlines = QtGui.QCheckBox(self)
|
self.inlines = QtGui.QCheckBox(self)
|
||||||
self.inlines.setGeometry(QtCore.QRect(10, 140, 350, 30))
|
self.inlines.setGeometry(QtCore.QRect(10, 180, 350, 30))
|
||||||
self.inlines.setObjectName("inlines")
|
|
||||||
|
|
||||||
|
|
||||||
self.auto_path = QtGui.QLabel(self)
|
self.auto_path = QtGui.QLabel(self)
|
||||||
self.auto_path.setGeometry(QtCore.QRect(10, 190, 350, 30))
|
self.auto_path.setGeometry(QtCore.QRect(10, 230, 350, 30))
|
||||||
self.path = QtGui.QPlainTextEdit(self)
|
self.path = QtGui.QPlainTextEdit(self)
|
||||||
self.path.setGeometry(QtCore.QRect(10, 225, 330, 45))
|
self.path.setGeometry(QtCore.QRect(10, 265, 330, 45))
|
||||||
self.change_path = QtGui.QPushButton(self)
|
self.change_path = QtGui.QPushButton(self)
|
||||||
self.change_path.setGeometry(QtCore.QRect(10, 280, 330, 30))
|
self.change_path.setGeometry(QtCore.QRect(10, 320, 350, 30))
|
||||||
settings = Settings.get_instance()
|
settings = Settings.get_instance()
|
||||||
self.typingNotifications.setChecked(settings['typing_notifications'])
|
self.typingNotifications.setChecked(settings['typing_notifications'])
|
||||||
self.fileautoaccept.setChecked(settings['allow_auto_accept'])
|
self.fileautoaccept.setChecked(settings['allow_auto_accept'])
|
||||||
self.saveHistory.setChecked(settings['save_history'])
|
self.saveHistory.setChecked(settings['save_history'])
|
||||||
self.inlines.setChecked(settings['allow_inline'])
|
self.inlines.setChecked(settings['allow_inline'])
|
||||||
|
self.saveUnsentOnly.setChecked(settings['save_unsent_only'])
|
||||||
|
self.saveUnsentOnly.setEnabled(settings['save_history'])
|
||||||
|
self.saveHistory.stateChanged.connect(self.update)
|
||||||
self.path.setPlainText(settings['auto_accept_path'] or curr_directory())
|
self.path.setPlainText(settings['auto_accept_path'] or curr_directory())
|
||||||
self.change_path.clicked.connect(self.new_path)
|
self.change_path.clicked.connect(self.new_path)
|
||||||
self.block_user_label = QtGui.QLabel(self)
|
self.block_user_label = QtGui.QLabel(self)
|
||||||
self.block_user_label.setGeometry(QtCore.QRect(10, 320, 330, 30))
|
self.block_user_label.setGeometry(QtCore.QRect(10, 360, 350, 30))
|
||||||
self.block_id = QtGui.QPlainTextEdit(self)
|
self.block_id = QtGui.QPlainTextEdit(self)
|
||||||
self.block_id.setGeometry(QtCore.QRect(10, 350, 330, 30))
|
self.block_id.setGeometry(QtCore.QRect(10, 390, 350, 30))
|
||||||
self.block = QtGui.QPushButton(self)
|
self.block = QtGui.QPushButton(self)
|
||||||
self.block.setGeometry(QtCore.QRect(10, 390, 330, 30))
|
self.block.setGeometry(QtCore.QRect(10, 430, 350, 30))
|
||||||
self.block.clicked.connect(lambda: Profile.get_instance().block_user(self.block_id.toPlainText()) or self.close())
|
self.block.clicked.connect(lambda: Profile.get_instance().block_user(self.block_id.toPlainText()) or self.close())
|
||||||
self.blocked_users_label = QtGui.QLabel(self)
|
self.blocked_users_label = QtGui.QLabel(self)
|
||||||
self.blocked_users_label.setGeometry(QtCore.QRect(10, 430, 330, 30))
|
self.blocked_users_label.setGeometry(QtCore.QRect(10, 470, 350, 30))
|
||||||
self.comboBox = QtGui.QComboBox(self)
|
self.comboBox = QtGui.QComboBox(self)
|
||||||
self.comboBox.setGeometry(QtCore.QRect(10, 460, 330, 30))
|
self.comboBox.setGeometry(QtCore.QRect(10, 500, 350, 30))
|
||||||
self.comboBox.addItems(settings['blocked'])
|
self.comboBox.addItems(settings['blocked'])
|
||||||
self.unblock = QtGui.QPushButton(self)
|
self.unblock = QtGui.QPushButton(self)
|
||||||
self.unblock.setGeometry(QtCore.QRect(10, 500, 330, 30))
|
self.unblock.setGeometry(QtCore.QRect(10, 540, 350, 30))
|
||||||
self.unblock.clicked.connect(lambda: self.unblock_user())
|
self.unblock.clicked.connect(lambda: self.unblock_user())
|
||||||
self.retranslateUi()
|
self.retranslateUi()
|
||||||
QtCore.QMetaObject.connectSlotsByName(self)
|
QtCore.QMetaObject.connectSlotsByName(self)
|
||||||
@ -382,10 +417,16 @@ class PrivacySettings(CenteredWidget):
|
|||||||
self.auto_path.setText(QtGui.QApplication.translate("privacySettings", "Auto accept default path:", None, QtGui.QApplication.UnicodeUTF8))
|
self.auto_path.setText(QtGui.QApplication.translate("privacySettings", "Auto accept default path:", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.change_path.setText(QtGui.QApplication.translate("privacySettings", "Change", None, QtGui.QApplication.UnicodeUTF8))
|
self.change_path.setText(QtGui.QApplication.translate("privacySettings", "Change", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.inlines.setText(QtGui.QApplication.translate("privacySettings", "Allow inlines", None, QtGui.QApplication.UnicodeUTF8))
|
self.inlines.setText(QtGui.QApplication.translate("privacySettings", "Allow inlines", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.block_user_label.setText(QtGui.QApplication.translate("privacySettings", "Block by TOX ID:", None, QtGui.QApplication.UnicodeUTF8))
|
self.block_user_label.setText(QtGui.QApplication.translate("privacySettings", "Block by public key:", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.blocked_users_label.setText(QtGui.QApplication.translate("privacySettings", "Blocked users:", None, QtGui.QApplication.UnicodeUTF8))
|
self.blocked_users_label.setText(QtGui.QApplication.translate("privacySettings", "Blocked users:", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.unblock.setText(QtGui.QApplication.translate("privacySettings", "Unblock", None, QtGui.QApplication.UnicodeUTF8))
|
self.unblock.setText(QtGui.QApplication.translate("privacySettings", "Unblock", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.block.setText(QtGui.QApplication.translate("privacySettings", "Block user", None, QtGui.QApplication.UnicodeUTF8))
|
self.block.setText(QtGui.QApplication.translate("privacySettings", "Block user", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
self.saveUnsentOnly.setText(QtGui.QApplication.translate("privacySettings", "Save unsent messages only", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
|
||||||
|
def update(self, new_state):
|
||||||
|
self.saveUnsentOnly.setEnabled(new_state)
|
||||||
|
if not new_state:
|
||||||
|
self.saveUnsentOnly.setChecked(False)
|
||||||
|
|
||||||
def unblock_user(self):
|
def unblock_user(self):
|
||||||
if not self.comboBox.count():
|
if not self.comboBox.count():
|
||||||
@ -400,6 +441,7 @@ class PrivacySettings(CenteredWidget):
|
|||||||
settings = Settings.get_instance()
|
settings = Settings.get_instance()
|
||||||
settings['typing_notifications'] = self.typingNotifications.isChecked()
|
settings['typing_notifications'] = self.typingNotifications.isChecked()
|
||||||
settings['allow_auto_accept'] = self.fileautoaccept.isChecked()
|
settings['allow_auto_accept'] = self.fileautoaccept.isChecked()
|
||||||
|
|
||||||
if settings['save_history'] and not self.saveHistory.isChecked(): # clear history
|
if settings['save_history'] and not self.saveHistory.isChecked(): # clear history
|
||||||
reply = QtGui.QMessageBox.question(None,
|
reply = QtGui.QMessageBox.question(None,
|
||||||
QtGui.QApplication.translate("privacySettings",
|
QtGui.QApplication.translate("privacySettings",
|
||||||
@ -415,6 +457,21 @@ class PrivacySettings(CenteredWidget):
|
|||||||
settings['save_history'] = self.saveHistory.isChecked()
|
settings['save_history'] = self.saveHistory.isChecked()
|
||||||
else:
|
else:
|
||||||
settings['save_history'] = self.saveHistory.isChecked()
|
settings['save_history'] = self.saveHistory.isChecked()
|
||||||
|
if self.saveUnsentOnly.isChecked() and not settings['save_unsent_only']:
|
||||||
|
reply = QtGui.QMessageBox.question(None,
|
||||||
|
QtGui.QApplication.translate("privacySettings",
|
||||||
|
'Chat history',
|
||||||
|
None, QtGui.QApplication.UnicodeUTF8),
|
||||||
|
QtGui.QApplication.translate("privacySettings",
|
||||||
|
'History will be cleaned! Continue?',
|
||||||
|
None, QtGui.QApplication.UnicodeUTF8),
|
||||||
|
QtGui.QMessageBox.Yes,
|
||||||
|
QtGui.QMessageBox.No)
|
||||||
|
if reply == QtGui.QMessageBox.Yes:
|
||||||
|
Profile.get_instance().clear_history(None, True)
|
||||||
|
settings['save_unsent_only'] = self.saveUnsentOnly.isChecked()
|
||||||
|
else:
|
||||||
|
settings['save_unsent_only'] = self.saveUnsentOnly.isChecked()
|
||||||
settings['auto_accept_path'] = self.path.toPlainText()
|
settings['auto_accept_path'] = self.path.toPlainText()
|
||||||
settings['allow_inline'] = self.inlines.isChecked()
|
settings['allow_inline'] = self.inlines.isChecked()
|
||||||
settings.save()
|
settings.save()
|
||||||
@ -435,9 +492,9 @@ class NotificationsSettings(CenteredWidget):
|
|||||||
|
|
||||||
def initUI(self):
|
def initUI(self):
|
||||||
self.setObjectName("notificationsForm")
|
self.setObjectName("notificationsForm")
|
||||||
self.resize(350, 200)
|
self.resize(350, 180)
|
||||||
self.setMinimumSize(QtCore.QSize(350, 200))
|
self.setMinimumSize(QtCore.QSize(350, 180))
|
||||||
self.setMaximumSize(QtCore.QSize(350, 200))
|
self.setMaximumSize(QtCore.QSize(350, 180))
|
||||||
self.enableNotifications = QtGui.QCheckBox(self)
|
self.enableNotifications = QtGui.QCheckBox(self)
|
||||||
self.enableNotifications.setGeometry(QtCore.QRect(10, 20, 340, 18))
|
self.enableNotifications.setGeometry(QtCore.QRect(10, 20, 340, 18))
|
||||||
self.callsSound = QtGui.QCheckBox(self)
|
self.callsSound = QtGui.QCheckBox(self)
|
||||||
@ -446,7 +503,6 @@ class NotificationsSettings(CenteredWidget):
|
|||||||
self.soundNotifications.setGeometry(QtCore.QRect(10, 70, 340, 18))
|
self.soundNotifications.setGeometry(QtCore.QRect(10, 70, 340, 18))
|
||||||
font = QtGui.QFont()
|
font = QtGui.QFont()
|
||||||
font.setPointSize(12)
|
font.setPointSize(12)
|
||||||
font.setBold(True)
|
|
||||||
self.callsSound.setFont(font)
|
self.callsSound.setFont(font)
|
||||||
self.soundNotifications.setFont(font)
|
self.soundNotifications.setFont(font)
|
||||||
self.enableNotifications.setFont(font)
|
self.enableNotifications.setFont(font)
|
||||||
@ -473,7 +529,6 @@ class NotificationsSettings(CenteredWidget):
|
|||||||
|
|
||||||
class InterfaceSettings(CenteredWidget):
|
class InterfaceSettings(CenteredWidget):
|
||||||
"""Interface settings form"""
|
"""Interface settings form"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(InterfaceSettings, self).__init__()
|
super(InterfaceSettings, self).__init__()
|
||||||
self.initUI()
|
self.initUI()
|
||||||
@ -481,39 +536,65 @@ class InterfaceSettings(CenteredWidget):
|
|||||||
|
|
||||||
def initUI(self):
|
def initUI(self):
|
||||||
self.setObjectName("interfaceForm")
|
self.setObjectName("interfaceForm")
|
||||||
self.resize(300, 300)
|
self.setMinimumSize(QtCore.QSize(400, 450))
|
||||||
self.setMinimumSize(QtCore.QSize(300, 300))
|
self.setMaximumSize(QtCore.QSize(400, 450))
|
||||||
self.setMaximumSize(QtCore.QSize(300, 300))
|
|
||||||
self.setBaseSize(QtCore.QSize(300, 300))
|
|
||||||
self.label = QtGui.QLabel(self)
|
self.label = QtGui.QLabel(self)
|
||||||
self.label.setGeometry(QtCore.QRect(30, 20, 91, 21))
|
self.label.setGeometry(QtCore.QRect(30, 10, 370, 20))
|
||||||
font = QtGui.QFont()
|
font = QtGui.QFont()
|
||||||
font.setPointSize(16)
|
font.setPointSize(14)
|
||||||
font.setWeight(75)
|
|
||||||
font.setBold(True)
|
font.setBold(True)
|
||||||
self.label.setFont(font)
|
self.label.setFont(font)
|
||||||
self.label.setObjectName("label")
|
|
||||||
self.themeSelect = QtGui.QComboBox(self)
|
self.themeSelect = QtGui.QComboBox(self)
|
||||||
self.themeSelect.setGeometry(QtCore.QRect(30, 60, 160, 30))
|
self.themeSelect.setGeometry(QtCore.QRect(30, 40, 120, 30))
|
||||||
self.themeSelect.setObjectName("themeSelect")
|
list_of_themes = ['dark']
|
||||||
list_of_themes = ['default', 'windows', 'gtk', 'cde', 'plastique', 'motif']
|
|
||||||
self.themeSelect.addItems(list_of_themes)
|
self.themeSelect.addItems(list_of_themes)
|
||||||
settings = Settings.get_instance()
|
settings = Settings.get_instance()
|
||||||
theme = settings['theme']
|
theme = settings['theme']
|
||||||
index = list_of_themes.index(theme)
|
if theme in list_of_themes:
|
||||||
|
index = list_of_themes.index(theme)
|
||||||
|
else:
|
||||||
|
index = 0
|
||||||
self.themeSelect.setCurrentIndex(index)
|
self.themeSelect.setCurrentIndex(index)
|
||||||
self.lang_choose = QtGui.QComboBox(self)
|
self.lang_choose = QtGui.QComboBox(self)
|
||||||
self.lang_choose.setGeometry(QtCore.QRect(30, 150, 160, 30))
|
self.lang_choose.setGeometry(QtCore.QRect(30, 110, 120, 30))
|
||||||
self.lang_choose.setObjectName("comboBox")
|
|
||||||
supported = Settings.supported_languages()
|
supported = Settings.supported_languages()
|
||||||
for elem in supported:
|
for key in supported:
|
||||||
self.lang_choose.addItem(elem[0])
|
self.lang_choose.insertItem(0, key)
|
||||||
lang = settings['language']
|
if settings['language'] == key:
|
||||||
index = map(lambda x: x[0], supported).index(lang)
|
self.lang_choose.setCurrentIndex(0)
|
||||||
self.lang_choose.setCurrentIndex(index)
|
|
||||||
self.lang = QtGui.QLabel(self)
|
self.lang = QtGui.QLabel(self)
|
||||||
self.lang.setGeometry(QtCore.QRect(30, 110, 121, 31))
|
self.lang.setGeometry(QtCore.QRect(30, 80, 370, 20))
|
||||||
self.lang.setFont(font)
|
self.lang.setFont(font)
|
||||||
|
self.mirror_mode = QtGui.QCheckBox(self)
|
||||||
|
self.mirror_mode.setGeometry(QtCore.QRect(30, 160, 370, 20))
|
||||||
|
self.mirror_mode.setChecked(settings['mirror_mode'])
|
||||||
|
self.smileys = QtGui.QCheckBox(self)
|
||||||
|
self.smileys.setGeometry(QtCore.QRect(30, 190, 120, 20))
|
||||||
|
self.smileys.setChecked(settings['smileys'])
|
||||||
|
self.smiley_pack_label = QtGui.QLabel(self)
|
||||||
|
self.smiley_pack_label.setGeometry(QtCore.QRect(30, 230, 370, 20))
|
||||||
|
self.smiley_pack_label.setFont(font)
|
||||||
|
self.smiley_pack = QtGui.QComboBox(self)
|
||||||
|
self.smiley_pack.setGeometry(QtCore.QRect(30, 260, 160, 30))
|
||||||
|
sm = smileys.SmileyLoader.get_instance()
|
||||||
|
self.smiley_pack.addItems(sm.get_packs_list())
|
||||||
|
try:
|
||||||
|
ind = sm.get_packs_list().index(settings['smiley_pack'])
|
||||||
|
except:
|
||||||
|
ind = sm.get_packs_list().index('default')
|
||||||
|
self.smiley_pack.setCurrentIndex(ind)
|
||||||
|
self.messages_font_size_label = QtGui.QLabel(self)
|
||||||
|
self.messages_font_size_label.setGeometry(QtCore.QRect(30, 300, 370, 20))
|
||||||
|
self.messages_font_size_label.setFont(font)
|
||||||
|
self.messages_font_size = QtGui.QComboBox(self)
|
||||||
|
self.messages_font_size.setGeometry(QtCore.QRect(30, 330, 160, 30))
|
||||||
|
self.messages_font_size.addItems([str(x) for x in range(10, 19)])
|
||||||
|
self.messages_font_size.setCurrentIndex(settings['message_font_size'] - 10)
|
||||||
|
|
||||||
|
self.unread = QtGui.QPushButton(self)
|
||||||
|
self.unread.setGeometry(QtCore.QRect(30, 380, 340, 40))
|
||||||
|
self.unread.clicked.connect(self.select_color)
|
||||||
|
|
||||||
self.retranslateUi()
|
self.retranslateUi()
|
||||||
QtCore.QMetaObject.connectSlotsByName(self)
|
QtCore.QMetaObject.connectSlotsByName(self)
|
||||||
|
|
||||||
@ -521,25 +602,54 @@ class InterfaceSettings(CenteredWidget):
|
|||||||
self.setWindowTitle(QtGui.QApplication.translate("interfaceForm", "Interface settings", None, QtGui.QApplication.UnicodeUTF8))
|
self.setWindowTitle(QtGui.QApplication.translate("interfaceForm", "Interface settings", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.label.setText(QtGui.QApplication.translate("interfaceForm", "Theme:", None, QtGui.QApplication.UnicodeUTF8))
|
self.label.setText(QtGui.QApplication.translate("interfaceForm", "Theme:", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.lang.setText(QtGui.QApplication.translate("interfaceForm", "Language:", None, QtGui.QApplication.UnicodeUTF8))
|
self.lang.setText(QtGui.QApplication.translate("interfaceForm", "Language:", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
self.smileys.setText(QtGui.QApplication.translate("interfaceForm", "Smileys", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
self.smiley_pack_label.setText(QtGui.QApplication.translate("interfaceForm", "Smiley pack:", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
self.mirror_mode.setText(QtGui.QApplication.translate("interfaceForm", "Mirror mode", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
self.messages_font_size_label.setText(QtGui.QApplication.translate("interfaceForm", "Messages font size:", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
self.unread.setText(QtGui.QApplication.translate("interfaceForm", "Select unread messages notification color", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
|
||||||
|
def select_color(self):
|
||||||
|
col = QtGui.QColorDialog.getColor()
|
||||||
|
|
||||||
|
if col.isValid():
|
||||||
|
settings = Settings.get_instance()
|
||||||
|
name = col.name()
|
||||||
|
settings['unread_color'] = name
|
||||||
|
settings.save()
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
settings = Settings.get_instance()
|
settings = Settings.get_instance()
|
||||||
style = str(self.themeSelect.currentText())
|
settings['theme'] = str(self.themeSelect.currentText())
|
||||||
settings['theme'] = style
|
settings['smileys'] = self.smileys.isChecked()
|
||||||
QtGui.QApplication.setStyle(get_style(style))
|
if settings['mirror_mode'] != self.mirror_mode.isChecked():
|
||||||
|
settings['mirror_mode'] = self.mirror_mode.isChecked()
|
||||||
|
msgBox = QtGui.QMessageBox()
|
||||||
|
text = QtGui.QApplication.translate("interfaceForm", 'Restart app to apply settings', None,
|
||||||
|
QtGui.QApplication.UnicodeUTF8)
|
||||||
|
msgBox.setWindowTitle(QtGui.QApplication.translate("interfaceForm", 'Restart required', None,
|
||||||
|
QtGui.QApplication.UnicodeUTF8))
|
||||||
|
msgBox.setText(text)
|
||||||
|
msgBox.exec_()
|
||||||
|
settings['smiley_pack'] = self.smiley_pack.currentText()
|
||||||
|
smileys.SmileyLoader.get_instance().load_pack()
|
||||||
language = self.lang_choose.currentText()
|
language = self.lang_choose.currentText()
|
||||||
if settings['language'] != language:
|
if settings['language'] != language:
|
||||||
settings['language'] = language
|
settings['language'] = language
|
||||||
index = self.lang_choose.currentIndex()
|
text = self.lang_choose.currentText()
|
||||||
path = Settings.supported_languages()[index][1]
|
path = Settings.supported_languages()[text]
|
||||||
app = QtGui.QApplication.instance()
|
app = QtGui.QApplication.instance()
|
||||||
app.removeTranslator(app.translator)
|
app.removeTranslator(app.translator)
|
||||||
app.translator.load(curr_directory() + '/translations/' + path)
|
app.translator.load(curr_directory() + '/translations/' + path)
|
||||||
app.installTranslator(app.translator)
|
app.installTranslator(app.translator)
|
||||||
|
settings['message_font_size'] = self.messages_font_size.currentIndex() + 10
|
||||||
|
Profile.get_instance().update()
|
||||||
settings.save()
|
settings.save()
|
||||||
|
|
||||||
|
|
||||||
class AudioSettings(CenteredWidget):
|
class AudioSettings(CenteredWidget):
|
||||||
|
"""
|
||||||
|
Audio calls settings form
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(AudioSettings, self).__init__()
|
super(AudioSettings, self).__init__()
|
||||||
@ -568,13 +678,13 @@ class AudioSettings(CenteredWidget):
|
|||||||
p = pyaudio.PyAudio()
|
p = pyaudio.PyAudio()
|
||||||
settings = Settings.get_instance()
|
settings = Settings.get_instance()
|
||||||
self.in_indexes, self.out_indexes = [], []
|
self.in_indexes, self.out_indexes = [], []
|
||||||
for i in xrange(p.get_device_count()):
|
for i in range(p.get_device_count()):
|
||||||
device = p.get_device_info_by_index(i)
|
device = p.get_device_info_by_index(i)
|
||||||
if device["maxInputChannels"]:
|
if device["maxInputChannels"]:
|
||||||
self.input.addItem(unicode(device["name"]))
|
self.input.addItem(str(device["name"]))
|
||||||
self.in_indexes.append(i)
|
self.in_indexes.append(i)
|
||||||
if device["maxOutputChannels"]:
|
if device["maxOutputChannels"]:
|
||||||
self.output.addItem(unicode(device["name"]))
|
self.output.addItem(str(device["name"]))
|
||||||
self.out_indexes.append(i)
|
self.out_indexes.append(i)
|
||||||
self.input.setCurrentIndex(self.in_indexes.index(settings.audio['input']))
|
self.input.setCurrentIndex(self.in_indexes.index(settings.audio['input']))
|
||||||
self.output.setCurrentIndex(self.out_indexes.index(settings.audio['output']))
|
self.output.setCurrentIndex(self.out_indexes.index(settings.audio['output']))
|
||||||
@ -593,6 +703,9 @@ class AudioSettings(CenteredWidget):
|
|||||||
|
|
||||||
|
|
||||||
class PluginsSettings(CenteredWidget):
|
class PluginsSettings(CenteredWidget):
|
||||||
|
"""
|
||||||
|
Plugins settings form
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(PluginsSettings, self).__init__()
|
super(PluginsSettings, self).__init__()
|
||||||
@ -633,26 +746,33 @@ class PluginsSettings(CenteredWidget):
|
|||||||
self.window.show()
|
self.window.show()
|
||||||
else:
|
else:
|
||||||
msgBox = QtGui.QMessageBox()
|
msgBox = QtGui.QMessageBox()
|
||||||
text = (QtGui.QApplication.translate("PluginsForm", 'No GUI found for this plugin', None,
|
text = QtGui.QApplication.translate("PluginsForm", 'No GUI found for this plugin', None,
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
QtGui.QApplication.UnicodeUTF8)
|
||||||
|
msgBox.setWindowTitle(QtGui.QApplication.translate("PluginsForm", 'Error', None,
|
||||||
|
QtGui.QApplication.UnicodeUTF8))
|
||||||
msgBox.setText(text)
|
msgBox.setText(text)
|
||||||
msgBox.exec_()
|
msgBox.exec_()
|
||||||
|
|
||||||
def update_list(self):
|
def update_list(self):
|
||||||
self.comboBox.clear()
|
self.comboBox.clear()
|
||||||
data = self.pl_loader.get_plugins_list()
|
data = self.pl_loader.get_plugins_list()
|
||||||
self.comboBox.addItems(map(lambda x: x[0], data))
|
self.comboBox.addItems(list(map(lambda x: x[0], data)))
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
def show_data(self):
|
def show_data(self):
|
||||||
ind = self.comboBox.currentIndex()
|
ind = self.comboBox.currentIndex()
|
||||||
plugin = self.data[ind]
|
if len(self.data):
|
||||||
descr = plugin[2] or QtGui.QApplication.translate("PluginsForm", "No description available", None, QtGui.QApplication.UnicodeUTF8)
|
plugin = self.data[ind]
|
||||||
self.label.setText(descr)
|
descr = plugin[2] or QtGui.QApplication.translate("PluginsForm", "No description available", None, QtGui.QApplication.UnicodeUTF8)
|
||||||
if plugin[1]:
|
self.label.setText(descr)
|
||||||
self.button.setText(QtGui.QApplication.translate("PluginsForm", "Disable plugin", None, QtGui.QApplication.UnicodeUTF8))
|
if plugin[1]:
|
||||||
|
self.button.setText(QtGui.QApplication.translate("PluginsForm", "Disable plugin", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
else:
|
||||||
|
self.button.setText(QtGui.QApplication.translate("PluginsForm", "Enable plugin", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
else:
|
else:
|
||||||
self.button.setText(QtGui.QApplication.translate("PluginsForm", "Enable plugin", None, QtGui.QApplication.UnicodeUTF8))
|
self.open.setVisible(False)
|
||||||
|
self.button.setVisible(False)
|
||||||
|
self.label.setText(QtGui.QApplication.translate("PluginsForm", "No plugins found", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
|
||||||
def button_click(self):
|
def button_click(self):
|
||||||
ind = self.comboBox.currentIndex()
|
ind = self.comboBox.currentIndex()
|
||||||
|
@ -4,21 +4,12 @@ MESSAGE_TYPE = {
|
|||||||
'TEXT': 0,
|
'TEXT': 0,
|
||||||
'ACTION': 1,
|
'ACTION': 1,
|
||||||
'FILE_TRANSFER': 2,
|
'FILE_TRANSFER': 2,
|
||||||
'INLINE': 3
|
'INLINE': 3,
|
||||||
}
|
'INFO_MESSAGE': 4
|
||||||
|
|
||||||
FILE_TRANSFER_MESSAGE_STATUS = {
|
|
||||||
'FINISHED': 0,
|
|
||||||
'CANCELLED': 1,
|
|
||||||
'OUTGOING': 2,
|
|
||||||
'INCOMING_NOT_STARTED': 3,
|
|
||||||
'INCOMING_STARTED': 4,
|
|
||||||
'PAUSED_BY_FRIEND': 5,
|
|
||||||
'PAUSED_BY_USER': 6
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Message(object):
|
class Message:
|
||||||
|
|
||||||
def __init__(self, message_type, owner, time):
|
def __init__(self, message_type, owner, time):
|
||||||
self._time = time
|
self._time = time
|
||||||
@ -31,6 +22,9 @@ class Message(object):
|
|||||||
def get_owner(self):
|
def get_owner(self):
|
||||||
return self._owner
|
return self._owner
|
||||||
|
|
||||||
|
def mark_as_sent(self):
|
||||||
|
self._owner = 0
|
||||||
|
|
||||||
|
|
||||||
class TextMessage(Message):
|
class TextMessage(Message):
|
||||||
"""
|
"""
|
||||||
@ -58,7 +52,7 @@ class TransferMessage(Message):
|
|||||||
self._friend_number, self._file_number = friend_number, file_number
|
self._friend_number, self._file_number = friend_number, file_number
|
||||||
|
|
||||||
def is_active(self, file_number):
|
def is_active(self, file_number):
|
||||||
return self._file_number == file_number and self._status > 1
|
return self._file_number == file_number and self._status not in (2, 3)
|
||||||
|
|
||||||
def get_friend_number(self):
|
def get_friend_number(self):
|
||||||
return self._friend_number
|
return self._friend_number
|
||||||
@ -76,6 +70,18 @@ class TransferMessage(Message):
|
|||||||
return self._file_name, self._size, self._time, self._owner, self._friend_number, self._file_number, self._status
|
return self._file_name, self._size, self._time, self._owner, self._friend_number, self._file_number, self._status
|
||||||
|
|
||||||
|
|
||||||
|
class UnsentFile(Message):
|
||||||
|
def __init__(self, path, data, time):
|
||||||
|
super(UnsentFile, self).__init__(MESSAGE_TYPE['FILE_TRANSFER'], 0, time)
|
||||||
|
self._data, self._path = data, path
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
return self._path, self._data, self._time
|
||||||
|
|
||||||
|
def get_status(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class InlineImage(Message):
|
class InlineImage(Message):
|
||||||
"""
|
"""
|
||||||
Inline image
|
Inline image
|
||||||
@ -87,3 +93,9 @@ class InlineImage(Message):
|
|||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
return self._data
|
return self._data
|
||||||
|
|
||||||
|
|
||||||
|
class InfoMessage(TextMessage):
|
||||||
|
|
||||||
|
def __init__(self, message, time):
|
||||||
|
super(InfoMessage, self).__init__(message, None, time, MESSAGE_TYPE['INFO_MESSAGE'])
|
||||||
|
@ -35,7 +35,7 @@ def tray_notification(title, text, tray, window):
|
|||||||
tray.connect(tray, QtCore.SIGNAL("messageClicked()"), message_clicked)
|
tray.connect(tray, QtCore.SIGNAL("messageClicked()"), message_clicked)
|
||||||
|
|
||||||
|
|
||||||
class AudioFile(object):
|
class AudioFile:
|
||||||
chunk = 1024
|
chunk = 1024
|
||||||
|
|
||||||
def __init__(self, fl):
|
def __init__(self, fl):
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
from widgets import CenteredWidget
|
from widgets import CenteredWidget, LineEdit
|
||||||
try:
|
try:
|
||||||
from PySide import QtCore, QtGui
|
from PySide import QtCore, QtGui
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
|
|
||||||
class PasswordArea(QtGui.QLineEdit):
|
class PasswordArea(LineEdit):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super(PasswordArea, self).__init__(parent)
|
super(PasswordArea, self).__init__(parent)
|
||||||
@ -57,8 +57,9 @@ class PasswordScreen(CenteredWidget):
|
|||||||
try:
|
try:
|
||||||
self._encrypt.set_password(self.password.text())
|
self._encrypt.set_password(self.password.text())
|
||||||
new_data = self._encrypt.pass_decrypt(self._data[0])
|
new_data = self._encrypt.pass_decrypt(self._data[0])
|
||||||
except Exception:
|
except Exception as ex:
|
||||||
self.warning.setVisible(True)
|
self.warning.setVisible(True)
|
||||||
|
print('Decryption error:', ex)
|
||||||
else:
|
else:
|
||||||
self._data[0] = new_data
|
self._data[0] = new_data
|
||||||
self.close()
|
self.close()
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
import util
|
import util
|
||||||
import profile
|
import profile
|
||||||
import os
|
import os
|
||||||
import imp
|
import importlib
|
||||||
import inspect
|
import inspect
|
||||||
import plugins.plugin_super_class as pl
|
import plugins.plugin_super_class as pl
|
||||||
import toxencryptsave
|
import toxencryptsave
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
class PluginLoader(util.Singleton):
|
class PluginLoader(util.Singleton):
|
||||||
|
|
||||||
def __init__(self, tox, settings):
|
def __init__(self, tox, settings):
|
||||||
|
super().__init__()
|
||||||
self._profile = profile.Profile.get_instance()
|
self._profile = profile.Profile.get_instance()
|
||||||
self._settings = settings
|
self._settings = settings
|
||||||
self._plugins = {} # dict. key - plugin unique short name, value - tuple (plugin instance, is active)
|
self._plugins = {} # dict. key - plugin unique short name, value - tuple (plugin instance, is active)
|
||||||
@ -29,13 +31,18 @@ class PluginLoader(util.Singleton):
|
|||||||
Load all plugins in plugins folder
|
Load all plugins in plugins folder
|
||||||
"""
|
"""
|
||||||
path = util.curr_directory() + '/plugins/'
|
path = util.curr_directory() + '/plugins/'
|
||||||
|
if not os.path.exists(path):
|
||||||
|
util.log('Plugin dir not found')
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
sys.path.append(path)
|
||||||
files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))]
|
files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))]
|
||||||
for fl in files:
|
for fl in files:
|
||||||
if fl in ('plugin_super_class.py', '__init__.py') or not fl.endswith('.py'):
|
if fl in ('plugin_super_class.py', '__init__.py') or not fl.endswith('.py'):
|
||||||
continue
|
continue
|
||||||
name = fl[:-3] # module name without .py
|
name = fl[:-3] # module name without .py
|
||||||
try:
|
try:
|
||||||
module = imp.load_source('plugins.' + name, path + fl) # import plugin
|
module = importlib.import_module(name) # import plugin
|
||||||
except ImportError:
|
except ImportError:
|
||||||
util.log('Import error in module ' + name)
|
util.log('Import error in module ' + name)
|
||||||
continue
|
continue
|
||||||
@ -44,8 +51,8 @@ class PluginLoader(util.Singleton):
|
|||||||
continue
|
continue
|
||||||
for elem in dir(module):
|
for elem in dir(module):
|
||||||
obj = getattr(module, elem)
|
obj = getattr(module, elem)
|
||||||
if inspect.isclass(obj) and issubclass(obj, pl.PluginSuperClass): # looking for plugin class in module
|
if inspect.isclass(obj) and hasattr(obj, 'is_plugin') and obj.is_plugin: # looking for plugin class in module
|
||||||
print 'Plugin', elem
|
print('Plugin', elem)
|
||||||
try: # create instance of plugin class
|
try: # create instance of plugin class
|
||||||
inst = obj(self._tox, self._profile, self._settings, self._encr)
|
inst = obj(self._tox, self._profile, self._settings, self._encr)
|
||||||
autostart = inst.get_short_name() in self._settings['plugins']
|
autostart = inst.get_short_name() in self._settings['plugins']
|
||||||
@ -76,6 +83,9 @@ class PluginLoader(util.Singleton):
|
|||||||
self._plugins[name][0].lossy_packet(''.join(chr(x) for x in data[l + 1:length]), friend_number)
|
self._plugins[name][0].lossy_packet(''.join(chr(x) for x in data[l + 1:length]), friend_number)
|
||||||
|
|
||||||
def friend_online(self, friend_number):
|
def friend_online(self, friend_number):
|
||||||
|
"""
|
||||||
|
Friend with specified number is online
|
||||||
|
"""
|
||||||
for elem in self._plugins.values():
|
for elem in self._plugins.values():
|
||||||
if elem[1]:
|
if elem[1]:
|
||||||
elem[0].friend_connected(friend_number)
|
elem[0].friend_connected(friend_number)
|
||||||
@ -141,6 +151,6 @@ class PluginLoader(util.Singleton):
|
|||||||
"""
|
"""
|
||||||
App is closing, stop all plugins
|
App is closing, stop all plugins
|
||||||
"""
|
"""
|
||||||
for key in self._plugins.keys():
|
for key in list(self._plugins.keys()):
|
||||||
self._plugins[key][0].close()
|
self._plugins[key][0].close()
|
||||||
del self._plugins[key]
|
del self._plugins[key]
|
||||||
|
@ -26,13 +26,14 @@ def log(name, data):
|
|||||||
:param data: data for saving in log
|
:param data: data for saving in log
|
||||||
"""
|
"""
|
||||||
with open(path_to_data(name) + 'logs.txt', 'a') as fl:
|
with open(path_to_data(name) + 'logs.txt', 'a') as fl:
|
||||||
fl.write(str(data) + '\n')
|
fl.write(bytes(data, 'utf-8') + b'\n')
|
||||||
|
|
||||||
|
|
||||||
class PluginSuperClass(object):
|
class PluginSuperClass:
|
||||||
"""
|
"""
|
||||||
Superclass for all plugins. Plugin is python module with at least one class derived from PluginSuperClass.
|
Superclass for all plugins. Plugin is python module with at least one class derived from PluginSuperClass.
|
||||||
"""
|
"""
|
||||||
|
is_plugin = True
|
||||||
|
|
||||||
def __init__(self, name, short_name, tox=None, profile=None, settings=None, encrypt_save=None):
|
def __init__(self, name, short_name, tox=None, profile=None, settings=None, encrypt_save=None):
|
||||||
"""
|
"""
|
||||||
@ -125,11 +126,12 @@ class PluginSuperClass(object):
|
|||||||
New command. On 'help' this method should provide user list of available commands
|
New command. On 'help' this method should provide user list of available commands
|
||||||
:param command: string with command
|
:param command: string with command
|
||||||
"""
|
"""
|
||||||
msgbox = QtGui.QMessageBox()
|
if command == 'help':
|
||||||
title = QtGui.QApplication.translate("PluginWindow", "List of commands for plugin {}", None, QtGui.QApplication.UnicodeUTF8)
|
msgbox = QtGui.QMessageBox()
|
||||||
msgbox.setWindowTitle(title.format(self._name))
|
title = QtGui.QApplication.translate("PluginWindow", "List of commands for plugin {}", None, QtGui.QApplication.UnicodeUTF8)
|
||||||
msgbox.setText(QtGui.QApplication.translate("PluginWindow", "No commands available", None, QtGui.QApplication.UnicodeUTF8))
|
msgbox.setWindowTitle(title.format(self._name))
|
||||||
msgbox.exec_()
|
msgbox.setText(QtGui.QApplication.translate("PluginWindow", "No commands available", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
msgbox.exec_()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Translations support
|
# Translations support
|
||||||
@ -142,11 +144,11 @@ class PluginSuperClass(object):
|
|||||||
app = QtGui.QApplication.instance()
|
app = QtGui.QApplication.instance()
|
||||||
langs = self._settings.supported_languages()
|
langs = self._settings.supported_languages()
|
||||||
curr_lang = self._settings['language']
|
curr_lang = self._settings['language']
|
||||||
if curr_lang in map(lambda x: x[0], langs):
|
if curr_lang in langs:
|
||||||
if self._translator is not None:
|
if self._translator is not None:
|
||||||
app.removeTranslator(self._translator)
|
app.removeTranslator(self._translator)
|
||||||
self._translator = QtCore.QTranslator()
|
self._translator = QtCore.QTranslator()
|
||||||
lang_path = filter(lambda x: x[0] == curr_lang, langs)[0][1]
|
lang_path = langs[curr_lang]
|
||||||
self._translator.load(path_to_data(self._short_name) + lang_path)
|
self._translator.load(path_to_data(self._short_name) + lang_path)
|
||||||
app.installTranslator(self._translator)
|
app.installTranslator(self._translator)
|
||||||
|
|
||||||
@ -158,9 +160,9 @@ class PluginSuperClass(object):
|
|||||||
"""
|
"""
|
||||||
This method loads settings of plugin and returns raw data
|
This method loads settings of plugin and returns raw data
|
||||||
"""
|
"""
|
||||||
with open(path_to_data(self._short_name) + 'settings.json') as fl:
|
with open(path_to_data(self._short_name) + 'settings.json', 'rb') as fl:
|
||||||
data = fl.read()
|
data = fl.read()
|
||||||
return data
|
return str(data, 'utf-8')
|
||||||
|
|
||||||
def save_settings(self, data):
|
def save_settings(self, data):
|
||||||
"""
|
"""
|
||||||
@ -168,7 +170,7 @@ class PluginSuperClass(object):
|
|||||||
:param data: string with data
|
:param data: string with data
|
||||||
"""
|
"""
|
||||||
with open(path_to_data(self._short_name) + 'settings.json', 'wb') as fl:
|
with open(path_to_data(self._short_name) + 'settings.json', 'wb') as fl:
|
||||||
fl.write(data)
|
fl.write(bytes(data, 'utf-8'))
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Callbacks
|
# Callbacks
|
||||||
@ -211,8 +213,10 @@ class PluginSuperClass(object):
|
|||||||
data = ''
|
data = ''
|
||||||
try:
|
try:
|
||||||
return self._tox.friend_send_lossless_packet(friend_number,
|
return self._tox.friend_send_lossless_packet(friend_number,
|
||||||
chr(len(self._short_name) + LOSSLESS_FIRST_BYTE) +
|
bytes([ord(x) for x in
|
||||||
self._short_name + str(data))
|
chr(len(self._short_name) + LOSSLESS_FIRST_BYTE) +
|
||||||
|
self._short_name + str(data)
|
||||||
|
]))
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -227,7 +231,9 @@ class PluginSuperClass(object):
|
|||||||
data = ''
|
data = ''
|
||||||
try:
|
try:
|
||||||
return self._tox.friend_send_lossy_packet(friend_number,
|
return self._tox.friend_send_lossy_packet(friend_number,
|
||||||
chr(len(self._short_name) + LOSSY_FIRST_BYTE) +
|
bytes([ord(x) for x in
|
||||||
self._short_name + str(data))
|
chr(len(self._short_name) + LOSSY_FIRST_BYTE) +
|
||||||
|
self._short_name + str(data)
|
||||||
|
]))
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
714
src/profile.py
105
src/settings.py
@ -5,32 +5,35 @@ import locale
|
|||||||
from util import Singleton, curr_directory, log
|
from util import Singleton, curr_directory, log
|
||||||
import pyaudio
|
import pyaudio
|
||||||
from toxencryptsave import LibToxEncryptSave
|
from toxencryptsave import LibToxEncryptSave
|
||||||
|
import smileys
|
||||||
|
|
||||||
|
|
||||||
class Settings(Singleton, dict):
|
class Settings(dict, Singleton):
|
||||||
"""
|
"""
|
||||||
Settings of current profile + global app settings
|
Settings of current profile + global app settings
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
|
Singleton.__init__(self)
|
||||||
self.path = ProfileHelper.get_path() + str(name) + '.json'
|
self.path = ProfileHelper.get_path() + str(name) + '.json'
|
||||||
self.name = name
|
self.name = name
|
||||||
if os.path.isfile(self.path):
|
if os.path.isfile(self.path):
|
||||||
with open(self.path) as fl:
|
with open(self.path, 'rb') as fl:
|
||||||
data = fl.read()
|
data = fl.read()
|
||||||
inst = LibToxEncryptSave.get_instance()
|
inst = LibToxEncryptSave.get_instance()
|
||||||
if inst.has_password():
|
|
||||||
data = inst.pass_decrypt(data)
|
|
||||||
try:
|
try:
|
||||||
info = json.loads(data)
|
if inst.is_data_encrypted(data):
|
||||||
|
data = inst.pass_decrypt(data)
|
||||||
|
info = json.loads(str(data, 'utf-8'))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
info = Settings.get_default_settings()
|
info = Settings.get_default_settings()
|
||||||
log('Parsing settings error: ' + str(ex))
|
log('Parsing settings error: ' + str(ex))
|
||||||
super(self.__class__, self).__init__(info)
|
super(Settings, self).__init__(info)
|
||||||
self.upgrade()
|
self.upgrade()
|
||||||
else:
|
else:
|
||||||
super(self.__class__, self).__init__(Settings.get_default_settings())
|
super(Settings, self).__init__(Settings.get_default_settings())
|
||||||
self.save()
|
self.save()
|
||||||
|
smileys.SmileyLoader(self)
|
||||||
p = pyaudio.PyAudio()
|
p = pyaudio.PyAudio()
|
||||||
self.audio = {'input': p.get_default_input_device_info()['index'],
|
self.audio = {'input': p.get_default_input_device_info()['index'],
|
||||||
'output': p.get_default_output_device_info()['index']}
|
'output': p.get_default_output_device_info()['index']}
|
||||||
@ -43,14 +46,43 @@ class Settings(Singleton, dict):
|
|||||||
data = fl.read()
|
data = fl.read()
|
||||||
auto = json.loads(data)
|
auto = json.loads(data)
|
||||||
if 'path' in auto and 'name' in auto:
|
if 'path' in auto and 'name' in auto:
|
||||||
return unicode(auto['path']), unicode(auto['name'])
|
return str(auto['path']), str(auto['name'])
|
||||||
|
return '', ''
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_auto_profile(path, name):
|
def set_auto_profile(path, name):
|
||||||
p = Settings.get_default_path() + 'toxygen.json'
|
p = Settings.get_default_path() + 'toxygen.json'
|
||||||
data = json.dumps({'path': unicode(path.decode(locale.getpreferredencoding())), 'name': unicode(name)})
|
with open(p) as fl:
|
||||||
|
data = fl.read()
|
||||||
|
data = json.loads(data)
|
||||||
|
data['path'] = str(path)
|
||||||
|
data['name'] = str(name)
|
||||||
with open(p, 'w') as fl:
|
with open(p, 'w') as fl:
|
||||||
fl.write(data)
|
fl.write(json.dumps(data))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def reset_auto_profile():
|
||||||
|
p = Settings.get_default_path() + 'toxygen.json'
|
||||||
|
with open(p) as fl:
|
||||||
|
data = fl.read()
|
||||||
|
data = json.loads(data)
|
||||||
|
if 'path' in data:
|
||||||
|
del data['path']
|
||||||
|
del data['name']
|
||||||
|
with open(p, 'w') as fl:
|
||||||
|
fl.write(json.dumps(data))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_active_profile(path, name):
|
||||||
|
path = path + name + '.tox'
|
||||||
|
settings = Settings.get_default_path() + 'toxygen.json'
|
||||||
|
if os.path.isfile(settings):
|
||||||
|
with open(settings) as fl:
|
||||||
|
data = fl.read()
|
||||||
|
data = json.loads(data)
|
||||||
|
if 'active_profile' in data:
|
||||||
|
return path in data['active_profile']
|
||||||
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_default_settings():
|
def get_default_settings():
|
||||||
@ -80,22 +112,33 @@ class Settings(Singleton, dict):
|
|||||||
'typing_notifications': False,
|
'typing_notifications': False,
|
||||||
'calls_sound': True,
|
'calls_sound': True,
|
||||||
'blocked': [],
|
'blocked': [],
|
||||||
'plugins': []
|
'plugins': [],
|
||||||
|
'notes': {},
|
||||||
|
'smileys': True,
|
||||||
|
'smiley_pack': 'default',
|
||||||
|
'mirror_mode': False,
|
||||||
|
'width': 920,
|
||||||
|
'height': 500,
|
||||||
|
'x': 400,
|
||||||
|
'y': 400,
|
||||||
|
'message_font_size': 14,
|
||||||
|
'unread_color': 'red',
|
||||||
|
'save_unsent_only': False
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def supported_languages():
|
def supported_languages():
|
||||||
return [
|
return {
|
||||||
('English', 'en_EN'),
|
'English': 'en_EN',
|
||||||
('Russian', 'ru_RU'),
|
'Russian': 'ru_RU',
|
||||||
('French', 'fr_FR')
|
'French': 'fr_FR'
|
||||||
]
|
}
|
||||||
|
|
||||||
def upgrade(self):
|
def upgrade(self):
|
||||||
default = Settings.get_default_settings()
|
default = Settings.get_default_settings()
|
||||||
for key in default:
|
for key in default:
|
||||||
if key not in self:
|
if key not in self:
|
||||||
print key
|
print(key)
|
||||||
self[key] = default[key]
|
self[key] = default[key]
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
@ -103,8 +146,10 @@ class Settings(Singleton, dict):
|
|||||||
text = json.dumps(self)
|
text = json.dumps(self)
|
||||||
inst = LibToxEncryptSave.get_instance()
|
inst = LibToxEncryptSave.get_instance()
|
||||||
if inst.has_password():
|
if inst.has_password():
|
||||||
text = inst.pass_encrypt(text)
|
text = bytes(inst.pass_encrypt(bytes(text, 'utf-8')))
|
||||||
with open(self.path, 'w') as fl:
|
else:
|
||||||
|
text = bytes(text, 'utf-8')
|
||||||
|
with open(self.path, 'wb') as fl:
|
||||||
fl.write(text)
|
fl.write(text)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
@ -114,7 +159,7 @@ class Settings(Singleton, dict):
|
|||||||
data = fl.read()
|
data = fl.read()
|
||||||
app_settings = json.loads(data)
|
app_settings = json.loads(data)
|
||||||
try:
|
try:
|
||||||
app_settings['active_profile'].remove(unicode(ProfileHelper.get_path() + self.name + '.tox'))
|
app_settings['active_profile'].remove(str(ProfileHelper.get_path() + self.name + '.tox'))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
data = json.dumps(app_settings)
|
data = json.dumps(app_settings)
|
||||||
@ -135,7 +180,7 @@ class Settings(Singleton, dict):
|
|||||||
if 'active_profile' not in app_settings:
|
if 'active_profile' not in app_settings:
|
||||||
app_settings['active_profile'] = []
|
app_settings['active_profile'] = []
|
||||||
profile_path = ProfileHelper.get_path()
|
profile_path = ProfileHelper.get_path()
|
||||||
app_settings['active_profile'].append(unicode(profile_path + str(self.name) + '.tox'))
|
app_settings['active_profile'].append(str(profile_path + str(self.name) + '.tox'))
|
||||||
data = json.dumps(app_settings)
|
data = json.dumps(app_settings)
|
||||||
with open(path, 'w') as fl:
|
with open(path, 'w') as fl:
|
||||||
fl.write(data)
|
fl.write(data)
|
||||||
@ -158,7 +203,7 @@ class ProfileHelper(Singleton):
|
|||||||
Class with methods for search, load and save profiles
|
Class with methods for search, load and save profiles
|
||||||
"""
|
"""
|
||||||
def __init__(self, path, name):
|
def __init__(self, path, name):
|
||||||
path = path.decode(locale.getpreferredencoding())
|
Singleton.__init__(self)
|
||||||
self._path = path + name + '.tox'
|
self._path = path + name + '.tox'
|
||||||
self._directory = path
|
self._directory = path
|
||||||
# create /avatars if not exists:
|
# create /avatars if not exists:
|
||||||
@ -183,7 +228,7 @@ class ProfileHelper(Singleton):
|
|||||||
data = inst.pass_encrypt(data)
|
data = inst.pass_encrypt(data)
|
||||||
with open(self._path, 'wb') as fl:
|
with open(self._path, 'wb') as fl:
|
||||||
fl.write(data)
|
fl.write(data)
|
||||||
print 'Profile saved successfully'
|
print('Profile saved successfully')
|
||||||
|
|
||||||
def export_profile(self, new_path):
|
def export_profile(self, new_path):
|
||||||
new_path += os.path.basename(self._path)
|
new_path += os.path.basename(self._path)
|
||||||
@ -191,7 +236,7 @@ class ProfileHelper(Singleton):
|
|||||||
data = fin.read()
|
data = fin.read()
|
||||||
with open(new_path, 'wb') as fout:
|
with open(new_path, 'wb') as fout:
|
||||||
fout.write(data)
|
fout.write(data)
|
||||||
print 'Profile exported successfully'
|
print('Profile exported successfully')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def find_profiles():
|
def find_profiles():
|
||||||
@ -215,18 +260,6 @@ class ProfileHelper(Singleton):
|
|||||||
result.append((path + '/', name))
|
result.append((path + '/', name))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def is_active_profile(path, name):
|
|
||||||
path = path.decode(locale.getpreferredencoding()) + name + '.tox'
|
|
||||||
settings = Settings.get_default_path() + 'toxygen.json'
|
|
||||||
if os.path.isfile(settings):
|
|
||||||
with open(settings) as fl:
|
|
||||||
data = fl.read()
|
|
||||||
data = json.loads(data)
|
|
||||||
if 'active_profile' in data:
|
|
||||||
return path in data['active_profile']
|
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_path():
|
def get_path():
|
||||||
return ProfileHelper.get_instance().get_dir()
|
return ProfileHelper.get_instance().get_dir()
|
||||||
|
91
src/smileys.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import util
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from collections import OrderedDict
|
||||||
|
try:
|
||||||
|
from PySide import QtCore
|
||||||
|
except ImportError:
|
||||||
|
from PyQt4 import QtCore
|
||||||
|
|
||||||
|
|
||||||
|
class SmileyLoader(util.Singleton):
|
||||||
|
"""
|
||||||
|
Class which loads smileys packs and insert smileys into messages
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, settings):
|
||||||
|
super().__init__()
|
||||||
|
self._settings = settings
|
||||||
|
self._curr_pack = None # current pack name
|
||||||
|
self._smileys = {} # smileys dict. key - smiley (str), value - path to image (str)
|
||||||
|
self._list = [] # smileys list without duplicates
|
||||||
|
self.load_pack()
|
||||||
|
|
||||||
|
def load_pack(self):
|
||||||
|
"""
|
||||||
|
Loads smiley pack
|
||||||
|
"""
|
||||||
|
pack_name = self._settings['smiley_pack']
|
||||||
|
if self._settings['smileys'] and self._curr_pack != pack_name:
|
||||||
|
self._curr_pack = pack_name
|
||||||
|
path = self.get_smileys_path() + 'config.json'
|
||||||
|
try:
|
||||||
|
with open(path, encoding='utf8') as fl:
|
||||||
|
self._smileys = json.loads(fl.read())
|
||||||
|
fl.seek(0)
|
||||||
|
tmp = json.loads(fl.read(), object_pairs_hook=OrderedDict)
|
||||||
|
print('Smiley pack {} loaded'.format(pack_name))
|
||||||
|
keys, values, self._list = [], [], []
|
||||||
|
for key, value in tmp.items():
|
||||||
|
value = self.get_smileys_path() + value
|
||||||
|
if value not in values:
|
||||||
|
keys.append(key)
|
||||||
|
values.append(value)
|
||||||
|
self._list = list(zip(keys, values))
|
||||||
|
except Exception as ex:
|
||||||
|
self._smileys = {}
|
||||||
|
self._list = []
|
||||||
|
print('Smiley pack {} was not loaded. Error: {}'.format(pack_name, ex))
|
||||||
|
|
||||||
|
def get_smileys_path(self):
|
||||||
|
return util.curr_directory() + '/smileys/' + self._curr_pack + '/'
|
||||||
|
|
||||||
|
def get_packs_list(self):
|
||||||
|
d = util.curr_directory() + '/smileys/'
|
||||||
|
return [x[1] for x in os.walk(d)][0]
|
||||||
|
|
||||||
|
def get_smileys(self):
|
||||||
|
return list(self._list)
|
||||||
|
|
||||||
|
def add_smileys_to_text(self, text, edit):
|
||||||
|
"""
|
||||||
|
Adds smileys to text
|
||||||
|
:param text: message
|
||||||
|
:param edit: MessageEdit instance
|
||||||
|
:return text with smileys
|
||||||
|
"""
|
||||||
|
if not self._settings['smileys'] or not len(self._smileys):
|
||||||
|
return text
|
||||||
|
arr = text.split(' ')
|
||||||
|
for i in range(len(arr)):
|
||||||
|
if arr[i] in self._smileys:
|
||||||
|
file_name = self._smileys[arr[i]] # image name
|
||||||
|
arr[i] = '<img title=\"{}\" src=\"{}\" />'.format(arr[i], file_name)
|
||||||
|
if file_name.endswith('.gif'): # animated smiley
|
||||||
|
edit.addAnimation(QtCore.QUrl(file_name), self.get_smileys_path() + file_name)
|
||||||
|
return ' '.join(arr)
|
||||||
|
|
||||||
|
|
||||||
|
def sticker_loader():
|
||||||
|
"""
|
||||||
|
:return list of stickers
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
d = util.curr_directory() + '/stickers/'
|
||||||
|
keys = [x[1] for x in os.walk(d)][0]
|
||||||
|
for key in keys:
|
||||||
|
path = d + key + '/'
|
||||||
|
files = filter(lambda f: f.endswith('.png'), os.listdir(path))
|
||||||
|
files = map(lambda f: str(path + f), files)
|
||||||
|
result.extend(files)
|
||||||
|
return result
|
BIN
src/smileys/animated/_ao.gif
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
src/smileys/animated/aa.gif
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
src/smileys/animated/ab.gif
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
src/smileys/animated/ac.gif
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
src/smileys/animated/ad.gif
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
src/smileys/animated/ae.gif
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
src/smileys/animated/af.gif
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
src/smileys/animated/ag.gif
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
src/smileys/animated/ah.gif
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
src/smileys/animated/ai.gif
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
src/smileys/animated/aj.gif
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
src/smileys/animated/ak.gif
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
src/smileys/animated/al.gif
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
src/smileys/animated/am.gif
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src/smileys/animated/an.gif
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
src/smileys/animated/ao.gif
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
src/smileys/animated/ap.gif
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
src/smileys/animated/aq.gif
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
src/smileys/animated/ar.gif
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
src/smileys/animated/as.gif
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
src/smileys/animated/at.gif
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
src/smileys/animated/au.gif
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src/smileys/animated/av.gif
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
src/smileys/animated/aw.gif
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
src/smileys/animated/ax.gif
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
src/smileys/animated/ay.gif
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
src/smileys/animated/az.gif
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
src/smileys/animated/ba.gif
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
src/smileys/animated/bb.gif
Normal file
After Width: | Height: | Size: 868 B |
BIN
src/smileys/animated/bc.gif
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/smileys/animated/bd.gif
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
src/smileys/animated/be.gif
Normal file
After Width: | Height: | Size: 1010 B |
BIN
src/smileys/animated/bf.gif
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
src/smileys/animated/bg.gif
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
src/smileys/animated/bh.gif
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src/smileys/animated/bi.gif
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
src/smileys/animated/bj.gif
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
src/smileys/animated/bk.gif
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
src/smileys/animated/bl.gif
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
src/smileys/animated/bm.gif
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
src/smileys/animated/bn.gif
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
src/smileys/animated/bo.gif
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
src/smileys/animated/bp.gif
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
src/smileys/animated/bq.gif
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
src/smileys/animated/br.gif
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
src/smileys/animated/bs.gif
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
src/smileys/animated/bt.gif
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
src/smileys/animated/bu.gif
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
src/smileys/animated/bv.gif
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
src/smileys/animated/bw.gif
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
src/smileys/animated/bx.gif
Normal file
After Width: | Height: | Size: 5.0 KiB |