Compare commits
92 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 | |||
02507ab8b4 | |||
c27c35fcc0 | |||
a214ab12c7 | |||
b2fa484bb3 | |||
be4a592599 | |||
cfdf145160 | |||
948776c31c | |||
b2210c21cf | |||
3a835ceb7e | |||
21d5eed719 | |||
0fa7ca7b5c | |||
010c15a498 | |||
284311a91c | |||
a78f18cce5 | |||
d0b767c779 | |||
f0875b0415 | |||
c5ec90a8a4 | |||
1734ea02f9 | |||
b0e1aeb0f0 |
27
README.md
@ -1,11 +1,11 @@
|
||||
# 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/issues)
|
||||
[](https://raw.githubusercontent.com/xveduk/toxygen/master/LICENSE.md)
|
||||
|
||||
### [Install](/docs/install.md) - [Contribute](/docs/contributing.md)
|
||||
### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md)
|
||||
|
||||
### Supported OS:
|
||||
- Windows
|
||||
@ -15,9 +15,16 @@ Toxygen is cross-platform [Tox](https://tox.chat/) client written on Python
|
||||
- [x] 1v1 messages
|
||||
- [x] File transfers
|
||||
- [x] Audio
|
||||
- [x] Plugins support
|
||||
- [x] Chat history
|
||||
- [x] Name lookups (TOX DNS 4 support)
|
||||
- [x] Profile import/export
|
||||
- [x] Emoticons
|
||||
- [x] Stickers
|
||||
- [x] Screenshots
|
||||
- [x] Name lookups (toxme.io support)
|
||||
- [x] Save file encryption
|
||||
- [x] Profile import and export
|
||||
- [x] Faux offline messaging
|
||||
- [x] Faux offline file transfers
|
||||
- [x] Inline images
|
||||
- [x] Message splitting
|
||||
- [x] Proxy support
|
||||
@ -30,26 +37,24 @@ Toxygen is cross-platform [Tox](https://tox.chat/) client written on Python
|
||||
- [x] Typing notifications
|
||||
- [x] Changing nospam
|
||||
- [x] File resuming
|
||||
- [x] Read receipts
|
||||
- [ ] Video
|
||||
- [ ] Save file encryption
|
||||
- [ ] Plugins support
|
||||
- [ ] Group chats
|
||||
- [ ] Read receipts
|
||||
- [ ] Faux offline messaging
|
||||
- [ ] Desktop sharing
|
||||
- [ ] Group chats
|
||||
|
||||
###Downloads
|
||||
[Releases](https://github.com/xveduk/toxygen/releases)
|
||||
|
||||
[Download last stable version](https://github.com/xveduk/toxygen/archive/master.zip)
|
||||
|
||||
[Download develop version](https://github.com/xveduk/toxygen/archive/develop.zip)
|
||||
|
||||
[Releases](https://github.com/xveduk/toxygen/releases)
|
||||
|
||||
###Screenshots
|
||||
*Toxygen on Ubuntu and Windows*
|
||||

|
||||

|
||||
|
||||
|
||||
###Docs
|
||||
[Check /docs/ for more info](/docs/)
|
||||
|
||||
|
@ -3,14 +3,8 @@
|
||||
You can compile Toxygen using [PyInstaller](http://www.pyinstaller.org/)
|
||||
|
||||
Install PyInstaller:
|
||||
``pip install pyinstaller``
|
||||
|
||||
On Linux:
|
||||
|
||||
``pyinstaller --windowed main.py``
|
||||
|
||||
On Windows:
|
||||
``pip3 install pyinstaller``
|
||||
|
||||
``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
|
||||
|
||||
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
|
||||
- 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
|
||||
|
||||
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.
|
||||
You can find our TODO's in code and [here](/README.md)
|
||||
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, issues list and [here](/README.md). Also you can implement [plugins](/docs/plugins.md) for Toxygen.
|
||||
|
||||
#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
|
||||
|
||||
## Use precompiled binary:
|
||||
[Check our releases page](https://github.com/xveduk/toxygen/releases)
|
||||
|
||||
## From source code (recommended for developers)
|
||||
|
||||
### Windows
|
||||
|
||||
1. [Download and install latest Python 2.7](https://www.python.org/downloads/windows/)
|
||||
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4) *(PyQt4 support will be added later)*
|
||||
3. Install PyAudio: ``python -m pip install pyaudio``
|
||||
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) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
|
||||
3. Install PyAudio: ``pip3 install pyaudio``
|
||||
4. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
|
||||
5. Unpack archive
|
||||
6. Download latest libtox.dll build, download latest libsodium.a build, put it into \src\libs\
|
||||
@ -23,22 +26,23 @@
|
||||
|
||||
### Linux
|
||||
|
||||
- Install Python2.7:
|
||||
``sudo apt-get install python2.7``
|
||||
- [Install PySide](https://wiki.qt.io/PySide_Binaries_Linux) *(PyQt4 support will be added later)*
|
||||
- Install PyAudio:
|
||||
Dependencies:
|
||||
|
||||
1. Install latest Python3.4:
|
||||
``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
|
||||
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)
|
||||
- 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``
|
||||
Toxygen:
|
||||
|
||||
## Use precompiled binary:
|
||||
[Check our releases page](https://github.com/xveduk/toxygen/releases)
|
||||
1. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
|
||||
2. Unpack archive
|
||||
3. Run app:
|
||||
``python3 main.py``
|
||||
|
||||
## Compile Toxygen
|
||||
Check [compile.md](/docs/compile.md) for more info
|
||||
|
53
docs/plugin_api.md
Normal file
@ -0,0 +1,53 @@
|
||||
#Plugins API
|
||||
|
||||
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 plugin has it's own full name and unique short name (1-5 symbols). Main app can get it using special methods.
|
||||
|
||||
All plugin's data should be stored in following structure:
|
||||
|
||||
```
|
||||
/plugins/
|
||||
|---plugin_short_name.py
|
||||
|---/plugin_short_name/
|
||||
|---settings.json
|
||||
|---logs.txt
|
||||
|---other_files
|
||||
```
|
||||
|
||||
Plugin can override following methods:
|
||||
- get_description - this method should return plugin description.
|
||||
- get_menu - plugins allowed to add items in friend menu. You can open this menu making right click on friend in friends list. This method should return list of QAction's. Plugin must connect to QAction's triggered() signal.
|
||||
- get_window - plugins can have GUI, this method should return window instance or None for plugins without GUI.
|
||||
- start - plugin was started.
|
||||
- stop - plugin was stopped.
|
||||
- close - app is closing, stop plugin.
|
||||
- command - new command to plugin. Command can be entered in message field in format '/plugin <plugin_short_name> <command>'. Command 'help' should show user list of supported commands.
|
||||
- lossless_packet - callback - incoming lossless packet from friend.
|
||||
- lossy_packet - callback - incoming lossy packet from friend.
|
||||
- friend_connected - callback - friend became online.
|
||||
|
||||
Other methods:
|
||||
- send_lossless - this method send custom lossless packet. Plugins MUST send lossless packets using this method.
|
||||
- send_lossy - this method send custom lossy packet. Plugins MUST send lossy packets using this method.
|
||||
- load_settings - loads settings stored in default location.
|
||||
- save_settings - saves settings to default location.
|
||||
- load_translator - loads translations. Translations must be stored in directory with plugin's data. Files with translations must have the same name as in main app.
|
||||
|
||||
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 dynamic import instead.
|
||||
|
||||
About GUI:
|
||||
|
||||
It's strictly recommended to support both PySide and PyQt4 in GUI. Plugin can not have GUI at all.
|
||||
|
||||
Exceptions:
|
||||
|
||||
Plugin's methods should not raise exceptions.
|
||||
|
||||
#Examples
|
||||
|
||||
You can find examples in [official repo](https://github.com/ingvar1995/toxygen_plugins)
|
||||
|
22
docs/plugins.md
Normal file
@ -0,0 +1,22 @@
|
||||
#Plugins
|
||||
|
||||
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
|
||||
|
||||
Check [Plugin API](/docs/plugin_api.md) for more info
|
||||
|
||||
#How to install plugin
|
||||
|
||||
Toxygen comes without preinstalled plugins.
|
||||
|
||||
1. Put plugin and directory with its data into /src/plugins/
|
||||
2. Restart Toxygen
|
||||
|
||||
##Note: /src/plugins/ should contain plugin_super_class.py and __init__.py
|
||||
|
||||
#Plugins list
|
||||
|
||||
WARNING: It is unsecure to install plugin not from this list!
|
||||
|
||||
[Main repo](https://github.com/ingvar1995/toxygen_plugins)
|
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 |
@ -1,4 +1,7 @@
|
||||
from PySide import QtCore, QtGui
|
||||
try:
|
||||
from PySide import QtCore, QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
import widgets
|
||||
import profile
|
||||
import util
|
||||
@ -62,25 +65,27 @@ class IncomingCallWidget(widgets.CenteredWidget):
|
||||
QtCore.QThread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
class AudioFile(object):
|
||||
class AudioFile:
|
||||
chunk = 1024
|
||||
|
||||
def __init__(self, fl):
|
||||
self.stop = False
|
||||
self.wf = wave.open(fl, 'rb')
|
||||
self.fl = fl
|
||||
self.wf = wave.open(self.fl, 'rb')
|
||||
self.p = pyaudio.PyAudio()
|
||||
self.stream = self.p.open(
|
||||
format=self.p.get_format_from_width(self.wf.getsampwidth()),
|
||||
channels=self.wf.getnchannels(),
|
||||
rate=self.wf.getframerate(),
|
||||
output=True
|
||||
)
|
||||
output=True)
|
||||
|
||||
def play(self):
|
||||
data = self.wf.readframes(self.chunk)
|
||||
while data and not self.stop:
|
||||
self.stream.write(data)
|
||||
while not self.stop:
|
||||
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):
|
||||
self.stream.close()
|
||||
@ -104,3 +109,31 @@ class IncomingCallWidget(widgets.CenteredWidget):
|
||||
|
||||
def set_pixmap(self, 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
|
||||
|
||||
|
||||
class Node(object):
|
||||
class Node:
|
||||
def __init__(self, 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",
|
||||
"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)))
|
||||
arr = sorted(nodes, key=lambda x: x.rand)[:4]
|
||||
for elem in arr:
|
||||
yield elem.get_data()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
for elem in node_generator():
|
||||
print str(elem)
|
||||
|
104
src/callbacks.py
@ -1,11 +1,14 @@
|
||||
from PySide import QtCore
|
||||
try:
|
||||
from PySide import QtCore
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore
|
||||
from notifications import *
|
||||
from settings import Settings
|
||||
from profile import Profile
|
||||
from toxcore_enums_and_consts import *
|
||||
from toxav_enums import *
|
||||
from tox import bin_to_string
|
||||
from ctypes import c_char_p, cast, pointer
|
||||
from plugin_support import PluginLoader
|
||||
|
||||
|
||||
class InvokeEvent(QtCore.QEvent):
|
||||
@ -40,7 +43,7 @@ def self_connection_status(tox_link):
|
||||
Current user changed connection status (offline, UDP, TCP)
|
||||
"""
|
||||
def wrapped(tox, connection, user_data):
|
||||
print 'Connection status: ', str(connection)
|
||||
print('Connection status: ', str(connection))
|
||||
profile = Profile.get_instance()
|
||||
if profile.status is None:
|
||||
status = tox_link.self_get_status()
|
||||
@ -59,12 +62,13 @@ def friend_status(tox, friend_num, new_status, user_data):
|
||||
"""
|
||||
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()
|
||||
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']:
|
||||
sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_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)
|
||||
|
||||
|
||||
@ -72,7 +76,7 @@ def friend_connection_status(tox, friend_num, new_status, user_data):
|
||||
"""
|
||||
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()
|
||||
friend = profile.get_friend_by_number(friend_num)
|
||||
if new_status == TOX_CONNECTION['NONE']:
|
||||
@ -82,6 +86,7 @@ def friend_connection_status(tox, friend_num, new_status, user_data):
|
||||
sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
|
||||
elif friend.status is None:
|
||||
invoke_in_main_thread(profile.send_avatar, friend_num)
|
||||
invoke_in_main_thread(PluginLoader.get_instance().friend_online, friend_num)
|
||||
|
||||
|
||||
def friend_name(tox, friend_num, name, size, user_data):
|
||||
@ -89,11 +94,8 @@ def friend_name(tox, friend_num, name, size, user_data):
|
||||
Friend changed his name
|
||||
"""
|
||||
profile = Profile.get_instance()
|
||||
friend = profile.get_friend_by_number(friend_num)
|
||||
print 'New name: ', str(friend_num), str(name)
|
||||
invoke_in_main_thread(friend.set_name, name)
|
||||
if profile.get_active_number() == friend_num:
|
||||
invoke_in_main_thread(profile.set_active)
|
||||
print('New name: ', friend_num, name)
|
||||
invoke_in_main_thread(profile.new_name, friend_num, name)
|
||||
|
||||
|
||||
def friend_status_message(tox, friend_num, status_message, size, user_data):
|
||||
@ -104,7 +106,8 @@ def friend_status_message(tox, friend_num, status_message, size, user_data):
|
||||
profile = Profile.get_instance()
|
||||
friend = profile.get_friend_by_number(friend_num)
|
||||
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:
|
||||
invoke_in_main_thread(profile.set_active)
|
||||
|
||||
@ -116,13 +119,15 @@ def friend_message(window, tray):
|
||||
def wrapped(tox, friend_number, message_type, message, size, user_data):
|
||||
profile = Profile.get_instance()
|
||||
settings = Settings.get_instance()
|
||||
message = str(message, 'utf-8')
|
||||
invoke_in_main_thread(profile.new_message, friend_number, message_type, message)
|
||||
if not window.isActiveWindow():
|
||||
friend = profile.get_friend_by_number(friend_number)
|
||||
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']:
|
||||
sound_notification(SOUND_NOTIFICATION['MESSAGE'])
|
||||
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png'))
|
||||
return wrapped
|
||||
|
||||
|
||||
@ -130,17 +135,24 @@ def friend_request(tox, public_key, message, message_size, user_data):
|
||||
"""
|
||||
Called when user get new friend request
|
||||
"""
|
||||
print 'Friend request'
|
||||
print('Friend request')
|
||||
profile = Profile.get_instance()
|
||||
key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE])
|
||||
tox_id = bin_to_string(key, TOX_PUBLIC_KEY_SIZE)
|
||||
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):
|
||||
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
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
@ -154,11 +166,11 @@ def tox_file_recv(window, tray):
|
||||
profile = Profile.get_instance()
|
||||
settings = Settings.get_instance()
|
||||
if file_type == TOX_FILE_KIND['DATA']:
|
||||
print 'file'
|
||||
print('File')
|
||||
try:
|
||||
file_name = unicode(file_name[:file_name_size].decode('utf-8'))
|
||||
file_name = str(file_name[:file_name_size], 'utf-8')
|
||||
except:
|
||||
file_name = u'toxygen_file'
|
||||
file_name = 'toxygen_file'
|
||||
invoke_in_main_thread(profile.incoming_file_transfer,
|
||||
friend_number,
|
||||
file_number,
|
||||
@ -167,11 +179,13 @@ def tox_file_recv(window, tray):
|
||||
if not window.isActiveWindow():
|
||||
friend = profile.get_friend_by_number(friend_number)
|
||||
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']:
|
||||
sound_notification(SOUND_NOTIFICATION['FILE_TRANSFER'])
|
||||
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png'))
|
||||
else: # AVATAR
|
||||
print 'Avatar'
|
||||
print ('Avatar')
|
||||
invoke_in_main_thread(profile.incoming_avatar,
|
||||
friend_number,
|
||||
file_number,
|
||||
@ -190,7 +204,7 @@ def file_recv_chunk(tox, friend_number, file_number, position, chunk, length, us
|
||||
position,
|
||||
None)
|
||||
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):
|
||||
@ -212,11 +226,31 @@ def file_recv_control(tox, friend_number, file_number, file_control, user_data):
|
||||
Friend cancelled, paused or resumed file transfer
|
||||
"""
|
||||
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']:
|
||||
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']:
|
||||
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
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
def lossless_packet(tox, friend_number, data, length, user_data):
|
||||
"""
|
||||
Incoming lossless packet
|
||||
"""
|
||||
plugin = PluginLoader.get_instance()
|
||||
invoke_in_main_thread(plugin.callback_lossless, friend_number, data, length)
|
||||
|
||||
|
||||
def lossy_packet(tox, friend_number, data, length, user_data):
|
||||
"""
|
||||
Incoming lossy packet
|
||||
"""
|
||||
plugin = PluginLoader.get_instance()
|
||||
invoke_in_main_thread(plugin.callback_lossy, friend_number, data, length)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
@ -224,8 +258,10 @@ def file_recv_control(tox, friend_number, file_number, file_control, user_data):
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def call_state(toxav, friend_number, mask, user_data):
|
||||
"""New call state"""
|
||||
print friend_number, mask
|
||||
"""
|
||||
New call state
|
||||
"""
|
||||
print(friend_number, mask)
|
||||
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)
|
||||
else:
|
||||
@ -233,16 +269,20 @@ def call_state(toxav, friend_number, mask, user_data):
|
||||
|
||||
|
||||
def call(toxav, friend_number, audio, video, user_data):
|
||||
"""Incoming call from friend"""
|
||||
print friend_number, audio, video
|
||||
"""
|
||||
Incoming call from friend
|
||||
"""
|
||||
print(friend_number, audio, video)
|
||||
invoke_in_main_thread(Profile.get_instance().incoming_call, audio, video, friend_number)
|
||||
|
||||
|
||||
def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, audio_channels_count, rate, user_data):
|
||||
"""New audio chunk"""
|
||||
print audio_samples_per_channel, audio_channels_count, rate
|
||||
"""
|
||||
New audio chunk
|
||||
"""
|
||||
# print(audio_samples_per_channel, audio_channels_count, rate)
|
||||
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,
|
||||
rate)
|
||||
|
||||
@ -268,6 +308,7 @@ def init_callbacks(tox, window, tray):
|
||||
tox.callback_friend_status_message(friend_status_message, 0)
|
||||
tox.callback_friend_request(friend_request, 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_chunk(file_recv_chunk, 0)
|
||||
@ -279,3 +320,6 @@ def init_callbacks(tox, window, tray):
|
||||
toxav.callback_call(call, 0)
|
||||
toxav.callback_audio_receive_frame(callback_audio, 0)
|
||||
|
||||
tox.callback_friend_lossless_packet(lossless_packet, 0)
|
||||
tox.callback_friend_lossy_packet(lossy_packet, 0)
|
||||
|
||||
|
@ -4,6 +4,7 @@ import threading
|
||||
import settings
|
||||
from toxav_enums import *
|
||||
# TODO: play sound until outgoing call will be started or cancelled and add timeout
|
||||
# TODO: add widget for call
|
||||
|
||||
CALL_TYPE = {
|
||||
'NONE': 0,
|
||||
@ -12,7 +13,7 @@ CALL_TYPE = {
|
||||
}
|
||||
|
||||
|
||||
class AV(object):
|
||||
class AV:
|
||||
|
||||
def __init__(self, toxav):
|
||||
self._toxav = toxav
|
||||
@ -29,7 +30,7 @@ class AV(object):
|
||||
self._audio_rate = 8000
|
||||
self._audio_channels = 1
|
||||
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):
|
||||
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,23 +1,43 @@
|
||||
from toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL
|
||||
from os.path import basename, getsize, exists
|
||||
from os import remove, rename
|
||||
from os.path import basename, getsize, exists, dirname
|
||||
from os import remove, rename, chdir
|
||||
from time import time, sleep
|
||||
from tox import Tox
|
||||
import settings
|
||||
from PySide import QtCore
|
||||
try:
|
||||
from PySide import QtCore
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore
|
||||
|
||||
# TODO: threads!
|
||||
|
||||
|
||||
TOX_FILE_TRANSFER_STATE = {
|
||||
'RUNNING': 0,
|
||||
'PAUSED': 1,
|
||||
'CANCELED': 2,
|
||||
'PAUSED_BY_USER': 1,
|
||||
'CANCELLED': 2,
|
||||
'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):
|
||||
signal = QtCore.Signal(int, float) # state and progress
|
||||
try:
|
||||
signal = QtCore.Signal(int, float, int) # state and progress
|
||||
except:
|
||||
signal = QtCore.pyqtSignal(int, float, int) # state and progress - pyqt4
|
||||
|
||||
|
||||
class FileTransfer(QtCore.QObject):
|
||||
@ -32,7 +52,7 @@ class FileTransfer(QtCore.QObject):
|
||||
self._friend_number = friend_number
|
||||
self.state = TOX_FILE_TRANSFER_STATE['RUNNING']
|
||||
self._file_number = file_number
|
||||
self._creation_time = time()
|
||||
self._creation_time = None
|
||||
self._size = float(size)
|
||||
self._done = 0
|
||||
self._state_changed = StateSignal()
|
||||
@ -44,7 +64,12 @@ class FileTransfer(QtCore.QObject):
|
||||
self._state_changed.signal.connect(handler)
|
||||
|
||||
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):
|
||||
return self._file_number
|
||||
@ -56,14 +81,14 @@ class FileTransfer(QtCore.QObject):
|
||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
||||
if hasattr(self, '_file'):
|
||||
self._file.close()
|
||||
self._state_changed.signal.emit(self.state, 1)
|
||||
self.signal()
|
||||
|
||||
def cancelled(self):
|
||||
if hasattr(self, '_file'):
|
||||
sleep(0.1)
|
||||
self._file.close()
|
||||
self.state = TOX_FILE_TRANSFER_STATE['CANCELED']
|
||||
self._state_changed.signal.emit(self.state, 1)
|
||||
self.state = TOX_FILE_TRANSFER_STATE['CANCELLED']
|
||||
self.signal()
|
||||
|
||||
def pause(self, by_friend):
|
||||
if not by_friend:
|
||||
@ -94,8 +119,9 @@ class SendTransfer(FileTransfer):
|
||||
else:
|
||||
size = 0
|
||||
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,
|
||||
basename(path).encode('utf-8') if path else '')
|
||||
bytes(basename(path), 'utf-8') if path else b'')
|
||||
|
||||
def send_chunk(self, position, size):
|
||||
"""
|
||||
@ -103,6 +129,8 @@ class SendTransfer(FileTransfer):
|
||||
:param position: start position in file
|
||||
:param size: chunk max size
|
||||
"""
|
||||
if self._creation_time is None:
|
||||
self._creation_time = time()
|
||||
if size:
|
||||
self._file.seek(position)
|
||||
data = self._file.read(size)
|
||||
@ -113,7 +141,7 @@ class SendTransfer(FileTransfer):
|
||||
if hasattr(self, '_file'):
|
||||
self._file.close()
|
||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||
self._state_changed.signal.emit(self.state, 1)
|
||||
self.signal()
|
||||
|
||||
|
||||
class SendAvatar(SendTransfer):
|
||||
@ -137,21 +165,37 @@ class SendFromBuffer(FileTransfer):
|
||||
|
||||
def __init__(self, tox, friend_number, data, file_name):
|
||||
super(SendFromBuffer, self).__init__(None, tox, friend_number, len(data))
|
||||
self.state = TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
|
||||
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):
|
||||
return self._data
|
||||
|
||||
def send_chunk(self, position, size):
|
||||
if self._creation_time is None:
|
||||
self._creation_time = time()
|
||||
if size:
|
||||
data = self._data[position:position + size]
|
||||
self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
|
||||
self._done += size
|
||||
self._state_changed.signal.emit(self.state, self._done / self._size)
|
||||
self.signal()
|
||||
else:
|
||||
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
|
||||
@ -176,23 +220,24 @@ class ReceiveTransfer(FileTransfer):
|
||||
:param position: position in file to save data
|
||||
:param data: raw data (string)
|
||||
"""
|
||||
if self._creation_time is None:
|
||||
self._creation_time = time()
|
||||
if data is None:
|
||||
self._file.close()
|
||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||
self._state_changed.signal.emit(self.state, 1)
|
||||
self.signal()
|
||||
else:
|
||||
data = ''.join(chr(x) for x in data)
|
||||
data = bytearray(data)
|
||||
if self._file_size < position:
|
||||
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.write(data)
|
||||
self._file.flush()
|
||||
l = len(data)
|
||||
if position + l > self._file_size:
|
||||
self._file_size = position + l
|
||||
self._done += l
|
||||
self._state_changed.signal.emit(self.state, self._done / self._size)
|
||||
self.signal()
|
||||
|
||||
|
||||
class ReceiveToBuffer(FileTransfer):
|
||||
@ -202,26 +247,27 @@ class ReceiveToBuffer(FileTransfer):
|
||||
|
||||
def __init__(self, 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
|
||||
|
||||
def get_data(self):
|
||||
return self._data
|
||||
|
||||
def write_chunk(self, position, data):
|
||||
if self._creation_time is None:
|
||||
self._creation_time = time()
|
||||
if data is None:
|
||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||
self._state_changed.signal.emit(self.state, 1)
|
||||
else:
|
||||
data = ''.join(chr(x) for x in data)
|
||||
data = bytes(data)
|
||||
l = len(data)
|
||||
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:]
|
||||
if position + l > self._data_size:
|
||||
self._data_size = position + l
|
||||
self._done += l
|
||||
self.signal()
|
||||
self.signal()
|
||||
|
||||
|
||||
class ReceiveAvatar(ReceiveTransfer):
|
||||
@ -235,23 +281,26 @@ class ReceiveAvatar(ReceiveTransfer):
|
||||
super(ReceiveAvatar, self).__init__(path + '.tmp', tox, friend_number, size, file_number)
|
||||
if size > self.MAX_AVATAR_SIZE:
|
||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
||||
self._file.close()
|
||||
remove(path + '.tmp')
|
||||
elif not size:
|
||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
||||
self._file.close()
|
||||
if exists(path):
|
||||
remove(path)
|
||||
self._file.close()
|
||||
remove(path + '.tmp')
|
||||
elif exists(path):
|
||||
if not size:
|
||||
hash = self.get_file_id()
|
||||
with open(path, 'rb') as fl:
|
||||
data = fl.read()
|
||||
existing_hash = Tox.hash(data)
|
||||
if hash == existing_hash:
|
||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
||||
self._file.close()
|
||||
remove(path)
|
||||
remove(path + '.tmp')
|
||||
else:
|
||||
hash = self.get_file_id()
|
||||
with open(path) as fl:
|
||||
data = fl.read()
|
||||
existing_hash = Tox.hash(data)
|
||||
if hash == existing_hash:
|
||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
||||
remove(path + '.tmp')
|
||||
else:
|
||||
self.send_control(TOX_FILE_CONTROL['RESUME'])
|
||||
self.send_control(TOX_FILE_CONTROL['RESUME'])
|
||||
else:
|
||||
self.send_control(TOX_FILE_CONTROL['RESUME'])
|
||||
|
||||
@ -260,7 +309,6 @@ class ReceiveAvatar(ReceiveTransfer):
|
||||
if self.state:
|
||||
avatar_path = self._path[:-4]
|
||||
if exists(avatar_path):
|
||||
chdir(dirname(avatar_path))
|
||||
remove(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)
|
@ -2,21 +2,36 @@
|
||||
from sqlite3 import connect
|
||||
import settings
|
||||
from os import chdir
|
||||
import os.path
|
||||
from toxencryptsave import LibToxEncryptSave
|
||||
|
||||
|
||||
PAGE_SIZE = 42
|
||||
|
||||
MESSAGE_OWNER = {
|
||||
'ME': 0,
|
||||
'FRIEND': 1
|
||||
'FRIEND': 1,
|
||||
'NOT_SENT': 2
|
||||
}
|
||||
|
||||
|
||||
class History(object):
|
||||
class History:
|
||||
|
||||
def __init__(self, name):
|
||||
self._name = name
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
path = settings.ProfileHelper.get_path() + self._name + '.hstr'
|
||||
if os.path.exists(path):
|
||||
decr = LibToxEncryptSave.get_instance()
|
||||
try:
|
||||
with open(path, 'rb') as fin:
|
||||
data = fin.read()
|
||||
if decr.is_data_encrypted(data):
|
||||
data = decr.pass_decrypt(data)
|
||||
with open(path, 'wb') as fout:
|
||||
fout.write(data)
|
||||
except:
|
||||
os.remove(path)
|
||||
db = connect(name + '.hstr')
|
||||
cursor = db.cursor()
|
||||
cursor.execute('CREATE TABLE IF NOT EXISTS friends('
|
||||
@ -24,14 +39,26 @@ class History(object):
|
||||
')')
|
||||
db.close()
|
||||
|
||||
def save(self):
|
||||
encr = LibToxEncryptSave.get_instance()
|
||||
if encr.has_password():
|
||||
path = settings.ProfileHelper.get_path() + self._name + '.hstr'
|
||||
with open(path, 'rb') as fin:
|
||||
data = fin.read()
|
||||
data = encr.pass_encrypt(bytes(data))
|
||||
with open(path, 'wb') as fout:
|
||||
fout.write(data)
|
||||
|
||||
def export(self, directory):
|
||||
path = settings.ProfileHelper.get_path() + self._name + '.hstr'
|
||||
new_path = directory + self._name + '.hstr'
|
||||
with open(path, 'rb') as fin:
|
||||
data = fin.read()
|
||||
encr = LibToxEncryptSave.get_instance()
|
||||
if encr.has_password():
|
||||
data = encr.pass_encrypt(data)
|
||||
with open(new_path, 'wb') as fout:
|
||||
fout.write(data)
|
||||
print 'History exported to: {}'.format(new_path)
|
||||
|
||||
def add_friend_to_db(self, tox_id):
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
@ -90,6 +117,34 @@ class History(object):
|
||||
finally:
|
||||
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):
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
db = connect(self._name + '.hstr')
|
||||
@ -106,7 +161,7 @@ class History(object):
|
||||
def messages_getter(self, tox_id):
|
||||
return History.MessageGetter(self._name, tox_id)
|
||||
|
||||
class MessageGetter(object):
|
||||
class MessageGetter:
|
||||
def __init__(self, name, tox_id):
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
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 |
Before Width: | Height: | Size: 885 B After Width: | Height: | Size: 3.4 KiB |
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
Normal file
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 ctypes import CDLL
|
||||
import util
|
||||
|
||||
|
||||
class LibToxCore(object):
|
||||
class LibToxCore:
|
||||
|
||||
def __init__(self):
|
||||
if system() == 'Linux':
|
||||
# be sure that libtoxcore and libsodium are installed in your os
|
||||
# libtoxcore and libsodium must be installed in your os
|
||||
self._libtoxcore = CDLL('libtoxcore.so')
|
||||
elif system() == 'Windows':
|
||||
self._libtoxcore = CDLL('libs/libtox.dll')
|
||||
self._libtoxcore = CDLL(util.curr_directory() + '/libs/libtox.dll')
|
||||
else:
|
||||
raise OSError('Unknown system.')
|
||||
|
||||
@ -17,17 +18,33 @@ class LibToxCore(object):
|
||||
return self._libtoxcore.__getattr__(item)
|
||||
|
||||
|
||||
class LibToxAV(object):
|
||||
class LibToxAV:
|
||||
|
||||
def __init__(self):
|
||||
if system() == 'Linux':
|
||||
# be sure that /usr/lib/libtoxav.so exists
|
||||
# that /usr/lib/libtoxav.so must exists
|
||||
self._libtoxav = CDLL('libtoxav.so')
|
||||
elif system() == 'Windows':
|
||||
# on Windows av api is in libtox.dll
|
||||
self._libtoxav = CDLL('libs/libtox.dll')
|
||||
self._libtoxav = CDLL(util.curr_directory() + '/libs/libtox.dll')
|
||||
else:
|
||||
raise OSError('Unknown system.')
|
||||
|
||||
def __getattr__(self, item):
|
||||
return self._libtoxav.__getattr__(item)
|
||||
|
||||
|
||||
class LibToxEncryptSave:
|
||||
|
||||
def __init__(self):
|
||||
if system() == 'Linux':
|
||||
# /usr/lib/libtoxencryptsave.so must exists
|
||||
self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.so')
|
||||
elif system() == 'Windows':
|
||||
# on Windows profile encryption api is in libtox.dll
|
||||
self._lib_tox_encrypt_save = CDLL(util.curr_directory() + '/libs/libtox.dll')
|
||||
else:
|
||||
raise OSError('Unknown system.')
|
||||
|
||||
def __getattr__(self, item):
|
||||
return self._lib_tox_encrypt_save.__getattr__(item)
|
||||
|
@ -1,75 +1,170 @@
|
||||
from toxcore_enums_and_consts import *
|
||||
from PySide import QtGui, QtCore
|
||||
try:
|
||||
from PySide import QtCore, QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
import profile
|
||||
from file_transfers import TOX_FILE_TRANSFER_STATE
|
||||
from util import curr_directory, convert_time
|
||||
from messages import FILE_TRANSFER_MESSAGE_STATUS
|
||||
from widgets import DataLabel
|
||||
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, curr_time
|
||||
from widgets import DataLabel, create_menu
|
||||
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)
|
||||
self.urls = {}
|
||||
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.setWordWrapMode(QtGui.QTextOption.WrapAtWordBoundaryOrAnywhere)
|
||||
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.setFamily("Times New Roman")
|
||||
font.setPixelSize(14)
|
||||
font.setPixelSize(settings.Settings.get_instance()['message_font_size'])
|
||||
font.setBold(False)
|
||||
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.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):
|
||||
"""
|
||||
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)
|
||||
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)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setPointSize(11)
|
||||
font.setBold(True)
|
||||
self.name.setFont(font)
|
||||
self.name.setObjectName("name")
|
||||
self.name.setText(user)
|
||||
|
||||
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.setFamily("Times New Roman")
|
||||
font.setPointSize(10)
|
||||
font.setBold(False)
|
||||
self.time.setFont(font)
|
||||
self.time.setObjectName("time")
|
||||
self.time.setText(time)
|
||||
self._time = 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.setFixedHeight(self.message.height())
|
||||
|
||||
if message_type == TOX_MESSAGE_TYPE['ACTION']:
|
||||
self.name.setStyleSheet("QLabel { color: #4169E1; }")
|
||||
self.message.setStyleSheet("QTextEdit { color: #4169E1; }")
|
||||
else:
|
||||
if text[0] == '>':
|
||||
self.message.setStyleSheet("QTextEdit { color: green; }")
|
||||
if text[-1] == '<':
|
||||
self.message.setStyleSheet("QTextEdit { color: red; }")
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.button() == QtCore.Qt.RightButton and event.x() > self.time.x():
|
||||
self.listMenu = QtGui.QMenu()
|
||||
delete_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Delete message', None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.connect(delete_item, QtCore.SIGNAL("triggered()"), self.delete)
|
||||
parent_position = self.time.mapToGlobal(QtCore.QPoint(0, 0))
|
||||
self.listMenu.move(parent_position)
|
||||
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):
|
||||
"""
|
||||
Contact in friends list
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QtGui.QWidget.__init__(self, parent)
|
||||
self.setBaseSize(QtCore.QSize(250, 70))
|
||||
@ -77,22 +172,21 @@ class ContactItem(QtGui.QWidget):
|
||||
self.avatar_label.setGeometry(QtCore.QRect(3, 3, 64, 64))
|
||||
self.avatar_label.setScaledContents(True)
|
||||
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.setFamily("Times New Roman")
|
||||
font.setPointSize(12)
|
||||
font.setBold(True)
|
||||
self.name.setFont(font)
|
||||
self.name.setObjectName("name")
|
||||
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.setBold(False)
|
||||
self.status_message.setFont(font)
|
||||
self.status_message.setObjectName("status_message")
|
||||
self.connection_status = StatusCircle(self)
|
||||
self.connection_status.setGeometry(QtCore.QRect(220, 5, 32, 32))
|
||||
self.connection_status.setObjectName("connection_status")
|
||||
self.connection_status.setGeometry(QtCore.QRect(230, 5, 32, 32))
|
||||
self.messages = UnreadMessagesCount(self)
|
||||
self.messages.setGeometry(QtCore.QRect(52, 50, 30, 20))
|
||||
|
||||
|
||||
class StatusCircle(QtGui.QWidget):
|
||||
@ -102,36 +196,57 @@ class StatusCircle(QtGui.QWidget):
|
||||
def __init__(self, parent):
|
||||
QtGui.QWidget.__init__(self, parent)
|
||||
self.setGeometry(0, 0, 32, 32)
|
||||
self.data = None
|
||||
self.messages = False
|
||||
self.label = QtGui.QLabel(self)
|
||||
self.label.setGeometry(QtCore.QRect(0, 0, 32, 32))
|
||||
self.unread = False
|
||||
|
||||
def paintEvent(self, event):
|
||||
paint = QtGui.QPainter()
|
||||
paint.begin(self)
|
||||
paint.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
k = 16
|
||||
rad_x = rad_y = 5
|
||||
if self.data is None:
|
||||
color = QtCore.Qt.transparent
|
||||
def update(self, status, unread_messages=None):
|
||||
if unread_messages is None:
|
||||
unread_messages = self.unread
|
||||
else:
|
||||
if self.data == TOX_USER_STATUS['NONE']:
|
||||
color = QtGui.QColor(50, 205, 50)
|
||||
elif self.data == TOX_USER_STATUS['AWAY']:
|
||||
color = QtGui.QColor(255, 200, 50)
|
||||
else: # self.data == TOX_USER_STATUS['BUSY']:
|
||||
color = QtGui.QColor(255, 50, 0)
|
||||
self.unread = unread_messages
|
||||
if status == TOX_USER_STATUS['NONE']:
|
||||
name = 'online'
|
||||
elif status == TOX_USER_STATUS['AWAY']:
|
||||
name = 'idle'
|
||||
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)
|
||||
paint.setBrush(color)
|
||||
paint.drawEllipse(center, rad_x, rad_y)
|
||||
if self.messages:
|
||||
if color == QtCore.Qt.transparent:
|
||||
color = QtCore.Qt.darkRed
|
||||
paint.setBrush(QtCore.Qt.transparent)
|
||||
paint.setPen(color)
|
||||
paint.drawEllipse(center, rad_x + 3, rad_y + 3)
|
||||
paint.end()
|
||||
|
||||
class UnreadMessagesCount(QtGui.QWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(UnreadMessagesCount, self).__init__(parent)
|
||||
self.resize(30, 20)
|
||||
self.label = QtGui.QLabel(self)
|
||||
self.label.setGeometry(QtCore.QRect(0, 0, 30, 20))
|
||||
self.label.setVisible(False)
|
||||
font = QtGui.QFont()
|
||||
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):
|
||||
@ -140,13 +255,12 @@ class FileTransferItem(QtGui.QListWidget):
|
||||
|
||||
QtGui.QListWidget.__init__(self, parent)
|
||||
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; }')
|
||||
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; }')
|
||||
else:
|
||||
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
||||
|
||||
self.state = state
|
||||
|
||||
self.name = DataLabel(self)
|
||||
@ -172,18 +286,18 @@ class FileTransferItem(QtGui.QListWidget):
|
||||
icon = QtGui.QIcon(pixmap)
|
||||
self.cancel.setIcon(icon)
|
||||
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.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}')
|
||||
|
||||
self.accept_or_pause = QtGui.QPushButton(self)
|
||||
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.button_update('accept')
|
||||
elif state in (0, 1, 5):
|
||||
elif state in DO_NOT_SHOW_ACCEPT_BUTTON:
|
||||
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.button_update('resume')
|
||||
else: # pause
|
||||
@ -197,23 +311,28 @@ class FileTransferItem(QtGui.QListWidget):
|
||||
self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20))
|
||||
self.pb.setValue(0)
|
||||
self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }')
|
||||
if state < 2:
|
||||
self.pb.setVisible(False)
|
||||
self.pb.setVisible(state in SHOW_PROGRESS_BAR)
|
||||
|
||||
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)
|
||||
self.file_name.setFont(font)
|
||||
file_size = size / 1024
|
||||
file_size = size // 1024
|
||||
if not file_size:
|
||||
file_size = '{}B'.format(size)
|
||||
elif file_size >= 1024:
|
||||
file_size = '{}MB'.format(file_size / 1024)
|
||||
file_size = '{}MB'.format(file_size // 1024)
|
||||
else:
|
||||
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.setToolTip(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.paused = False
|
||||
|
||||
@ -226,23 +345,24 @@ class FileTransferItem(QtGui.QListWidget):
|
||||
self.pb.setVisible(False)
|
||||
|
||||
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,
|
||||
QtGui.QApplication.translate("MainWindow", 'Choose folder', None, QtGui.QApplication.UnicodeUTF8),
|
||||
curr_directory(),
|
||||
QtGui.QFileDialog.ShowDirsOnly)
|
||||
self.pb.setVisible(True)
|
||||
if directory:
|
||||
pr = profile.Profile.get_instance()
|
||||
pr.accept_transfer(self, directory + '/' + self.saved_name, friend_number, file_number, size)
|
||||
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
|
||||
profile.Profile.get_instance().resume_transfer(friend_number, file_number)
|
||||
self.button_update('pause')
|
||||
self.state = FILE_TRANSFER_MESSAGE_STATUS['OUTGOING']
|
||||
self.state = TOX_FILE_TRANSFER_STATE['RUNNING']
|
||||
else: # pause
|
||||
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)
|
||||
self.button_update('resume')
|
||||
self.accept_or_pause.clearFocus()
|
||||
@ -253,63 +373,125 @@ class FileTransferItem(QtGui.QListWidget):
|
||||
self.accept_or_pause.setIcon(icon)
|
||||
self.accept_or_pause.setIconSize(QtCore.QSize(30, 30))
|
||||
|
||||
def convert(self, state):
|
||||
# convert TOX_FILE_TRANSFER_STATE to FILE_TRANSFER_MESSAGE_STATUS
|
||||
d = {0: 2, 1: 6, 2: 1, 3: 0, 4: 5}
|
||||
return d[state]
|
||||
|
||||
@QtCore.Slot(int, float)
|
||||
def update(self, state, progress):
|
||||
@QtCore.Slot(int, float, int)
|
||||
def update(self, state, progress, time):
|
||||
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 state == FILE_TRANSFER_MESSAGE_STATUS['CANCELLED']:
|
||||
if state == TOX_FILE_TRANSFER_STATE['CANCELLED']:
|
||||
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
|
||||
self.cancel.setVisible(False)
|
||||
self.accept_or_pause.setVisible(False)
|
||||
self.pb.setVisible(False)
|
||||
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.pb.setVisible(False)
|
||||
self.cancel.setVisible(False)
|
||||
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
||||
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.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
|
||||
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.setStyleSheet('QListWidget { border: 1px solid green; }')
|
||||
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
|
||||
self.pb.setVisible(True)
|
||||
self.accept_or_pause.setVisible(True) # setup to pause
|
||||
self.button_update('pause')
|
||||
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
||||
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)
|
||||
self.resize(QtCore.QSize(width, 500))
|
||||
def cancel_transfer(self, *args):
|
||||
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.raise_()
|
||||
self._image_label.setAutoFillBackground(True)
|
||||
self.setWidget(self._image_label)
|
||||
self._image_label.setScaledContents(False)
|
||||
self.pixmap = QtGui.QPixmap()
|
||||
self.pixmap.loadFromData(QtCore.QByteArray(data), "PNG")
|
||||
max_size = width - 40
|
||||
if self.pixmap.width() <= max_size:
|
||||
self._image_label.setPixmap(self.pixmap)
|
||||
self.resize(QtCore.QSize(max_size, self.pixmap.height()))
|
||||
self._pixmap = QtGui.QPixmap()
|
||||
self._pixmap.loadFromData(data, 'PNG')
|
||||
self._max_size = width - 30
|
||||
self._resize_needed = not (self._pixmap.width() <= self._max_size)
|
||||
self._full_size = not self._resize_needed
|
||||
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:
|
||||
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.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
|
||||
|
||||
|
||||
|
||||
|
@ -1,10 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from PySide import QtCore, QtGui
|
||||
try:
|
||||
from PySide import QtCore, QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from widgets import *
|
||||
|
||||
|
||||
class NickEdit(QtGui.QPlainTextEdit):
|
||||
class NickEdit(LineEdit):
|
||||
|
||||
def __init__(self, parent):
|
||||
super(NickEdit, self).__init__(parent)
|
||||
@ -22,6 +25,7 @@ class LoginScreen(CenteredWidget):
|
||||
def __init__(self):
|
||||
super(LoginScreen, self).__init__()
|
||||
self.initUI()
|
||||
self.center()
|
||||
|
||||
def initUI(self):
|
||||
self.resize(400, 200)
|
||||
@ -67,6 +71,7 @@ class LoginScreen(CenteredWidget):
|
||||
QtCore.QMetaObject.connectSlotsByName(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.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))
|
||||
@ -78,7 +83,7 @@ class LoginScreen(CenteredWidget):
|
||||
|
||||
def create_profile(self):
|
||||
self.type = 1
|
||||
self.name = self.new_name.toPlainText()
|
||||
self.name = self.new_name.text()
|
||||
self.close()
|
||||
|
||||
def load_ex_profile(self):
|
||||
@ -91,7 +96,7 @@ class LoginScreen(CenteredWidget):
|
||||
def update_select(self, data):
|
||||
list_of_profiles = []
|
||||
for elem in data:
|
||||
list_of_profiles.append(self.tr(elem))
|
||||
list_of_profiles.append(elem)
|
||||
self.comboBox.addItems(list_of_profiles)
|
||||
self.create_only = not list_of_profiles
|
||||
|
||||
|
146
src/main.py
@ -1,22 +1,49 @@
|
||||
import sys
|
||||
from loginscreen import LoginScreen
|
||||
from settings import *
|
||||
from PySide import QtCore, QtGui
|
||||
try:
|
||||
from PySide import QtCore, QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from bootstrap import node_generator
|
||||
from mainscreen import MainWindow
|
||||
from profile import tox_factory
|
||||
from callbacks import init_callbacks
|
||||
from util import curr_directory, get_style
|
||||
from util import curr_directory
|
||||
import styles.style
|
||||
import locale
|
||||
import toxencryptsave
|
||||
from passwordscreen import PasswordScreen
|
||||
import profile
|
||||
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__()
|
||||
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):
|
||||
"""
|
||||
Show password screen
|
||||
"""
|
||||
tmp = [data]
|
||||
p = PasswordScreen(toxencryptsave.LibToxEncryptSave.get_instance(), tmp)
|
||||
p.show()
|
||||
self.app.connect(self.app, QtCore.SIGNAL("lastWindowClosed()"), self.app, QtCore.SLOT("quit()"))
|
||||
self.app.exec_()
|
||||
if tmp[0] == data:
|
||||
raise SystemExit()
|
||||
else:
|
||||
return tmp[0]
|
||||
|
||||
def main(self):
|
||||
"""
|
||||
@ -24,26 +51,32 @@ class Toxygen(object):
|
||||
"""
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
app.setWindowIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
|
||||
self.app = app
|
||||
|
||||
# application color scheme
|
||||
with open(curr_directory() + '/styles/style.qss') as fl:
|
||||
dark_style = fl.read()
|
||||
app.setStyleSheet(dark_style)
|
||||
|
||||
encrypt_save = toxencryptsave.LibToxEncryptSave()
|
||||
|
||||
if self.path is not None:
|
||||
path = os.path.dirname(self.path.encode(locale.getpreferredencoding())) + '/'
|
||||
name = os.path.basename(self.path.encode(locale.getpreferredencoding()))[:-4]
|
||||
data = ProfileHelper.open_profile(path, name)
|
||||
path = os.path.dirname(self.path) + '/'
|
||||
name = os.path.basename(self.path)[:-4]
|
||||
data = ProfileHelper(path, name).open_profile()
|
||||
if encrypt_save.is_data_encrypted(data):
|
||||
data = self.enter_pass(data)
|
||||
settings = Settings(name)
|
||||
self.tox = tox_factory(data, settings)
|
||||
else:
|
||||
auto_profile = Settings.get_auto_profile()
|
||||
if not auto_profile:
|
||||
if not auto_profile[0]:
|
||||
# show login screen if default profile not found
|
||||
current_locale = QtCore.QLocale()
|
||||
curr_lang = current_locale.languageToString(current_locale.language())
|
||||
langs = Settings.supported_languages()
|
||||
if curr_lang in map(lambda x: x[0], langs):
|
||||
lang_path = filter(lambda x: x[0] == curr_lang, langs)[0][1]
|
||||
if curr_lang in langs:
|
||||
lang_path = langs[curr_lang]
|
||||
translator = QtCore.QTranslator()
|
||||
translator.load(curr_directory() + '/translations/' + lang_path)
|
||||
app.installTranslator(translator)
|
||||
@ -60,28 +93,32 @@ class Toxygen(object):
|
||||
if not _login.t:
|
||||
return
|
||||
elif _login.t == 1: # create new profile
|
||||
_login.name = _login.name.strip()
|
||||
name = _login.name if _login.name else 'toxygen_user'
|
||||
self.tox = tox_factory()
|
||||
self.tox.self_set_name(_login.name if _login.name else 'Toxygen User')
|
||||
self.tox.self_set_status_message('Toxing on Toxygen')
|
||||
ProfileHelper.save_profile(self.tox.get_savedata(), name)
|
||||
self.tox.self_set_name(bytes(_login.name, 'utf-8') if _login.name else b'Toxygen User')
|
||||
self.tox.self_set_status_message(b'Toxing on Toxygen')
|
||||
ProfileHelper(Settings.get_default_path(), name).save_profile(self.tox.get_savedata())
|
||||
path = Settings.get_default_path()
|
||||
settings = Settings(name)
|
||||
else: # load existing profile
|
||||
path, name = _login.get_data()
|
||||
if _login.default:
|
||||
Settings.set_auto_profile(path, name)
|
||||
data = ProfileHelper.open_profile(path, name)
|
||||
data = ProfileHelper(path, name).open_profile()
|
||||
if encrypt_save.is_data_encrypted(data):
|
||||
data = self.enter_pass(data)
|
||||
settings = Settings(name)
|
||||
self.tox = tox_factory(data, settings)
|
||||
else:
|
||||
path, name = auto_profile
|
||||
path = path.encode(locale.getpreferredencoding())
|
||||
data = ProfileHelper.open_profile(path, name)
|
||||
data = ProfileHelper(path, name).open_profile()
|
||||
if encrypt_save.is_data_encrypted(data):
|
||||
data = self.enter_pass(data)
|
||||
settings = Settings(name)
|
||||
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,
|
||||
'Profile {}'.format(name),
|
||||
QtGui.QApplication.translate("login", 'Looks like other instance of Toxygen uses this profile! Continue?', None, QtGui.QApplication.UnicodeUTF8),
|
||||
@ -92,41 +129,77 @@ class Toxygen(object):
|
||||
else:
|
||||
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.load(curr_directory() + '/translations/' + lang[1])
|
||||
translator.load(curr_directory() + '/translations/' + lang)
|
||||
app.installTranslator(translator)
|
||||
app.translator = translator
|
||||
|
||||
self.ms = MainWindow(self.tox, self.reset)
|
||||
|
||||
# tray icon
|
||||
self.tray = QtGui.QSystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
|
||||
self.tray.setObjectName('tray')
|
||||
|
||||
self.ms = MainWindow(self.tox, self.reset, self.tray)
|
||||
|
||||
class Menu(QtGui.QMenu):
|
||||
|
||||
def newStatus(self, status):
|
||||
profile.Profile.get_instance().set_status(status)
|
||||
self.aboutToShow()
|
||||
self.hide()
|
||||
|
||||
def aboutToShow(self):
|
||||
status = profile.Profile.get_instance().status
|
||||
act = self.act
|
||||
if status is None:
|
||||
self.actions()[1].setVisible(False)
|
||||
else:
|
||||
self.actions()[1].setVisible(True)
|
||||
act.actions()[0].setChecked(False)
|
||||
act.actions()[1].setChecked(False)
|
||||
act.actions()[2].setChecked(False)
|
||||
act.actions()[status].setChecked(True)
|
||||
|
||||
def languageChange(self, *args, **kwargs):
|
||||
self.actions()[0].setText(QtGui.QApplication.translate('tray', 'Open Toxygen', None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.actions()[1].setText(QtGui.QApplication.translate('tray', 'Exit', None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.actions()[1].setText(QtGui.QApplication.translate('tray', 'Set status', None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.actions()[2].setText(QtGui.QApplication.translate('tray', 'Exit', None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.act.actions()[0].setText(QtGui.QApplication.translate('tray', 'Online', None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.act.actions()[1].setText(QtGui.QApplication.translate('tray', 'Away', None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.act.actions()[2].setText(QtGui.QApplication.translate('tray', 'Busy', None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
m = Menu()
|
||||
show = m.addAction(QtGui.QApplication.translate('tray', 'Open Toxygen', None, QtGui.QApplication.UnicodeUTF8))
|
||||
sub = m.addMenu(QtGui.QApplication.translate('tray', 'Set status', None, QtGui.QApplication.UnicodeUTF8))
|
||||
onl = sub.addAction(QtGui.QApplication.translate('tray', 'Online', None, QtGui.QApplication.UnicodeUTF8))
|
||||
away = sub.addAction(QtGui.QApplication.translate('tray', 'Away', None, QtGui.QApplication.UnicodeUTF8))
|
||||
busy = sub.addAction(QtGui.QApplication.translate('tray', 'Busy', None, QtGui.QApplication.UnicodeUTF8))
|
||||
onl.setCheckable(True)
|
||||
away.setCheckable(True)
|
||||
busy.setCheckable(True)
|
||||
m.act = sub
|
||||
exit = m.addAction(QtGui.QApplication.translate('tray', 'Exit', None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
def show_window():
|
||||
if not self.ms.isActiveWindow():
|
||||
self.ms.setWindowState(self.ms.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
|
||||
self.ms.activateWindow()
|
||||
if self.ms.isHidden():
|
||||
self.ms.show()
|
||||
self.ms.show()
|
||||
|
||||
m.connect(show, QtCore.SIGNAL("triggered()"), show_window)
|
||||
m.connect(exit, QtCore.SIGNAL("triggered()"), lambda: app.exit())
|
||||
m.connect(m, QtCore.SIGNAL("aboutToShow()"), lambda: m.aboutToShow())
|
||||
sub.connect(onl, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(0))
|
||||
sub.connect(away, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(1))
|
||||
sub.connect(busy, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(2))
|
||||
|
||||
self.tray.setContextMenu(m)
|
||||
self.tray.show()
|
||||
|
||||
self.ms.show()
|
||||
QtGui.QApplication.setStyle(get_style(settings['theme'])) # set application style
|
||||
|
||||
plugin_helper = PluginLoader(self.tox, settings) # plugin support
|
||||
plugin_helper.load()
|
||||
|
||||
# init thread
|
||||
self.init = self.InitThread(self.tox, self.ms, self.tray)
|
||||
@ -137,16 +210,21 @@ class Toxygen(object):
|
||||
self.mainloop.start()
|
||||
self.avloop = self.ToxAVIterateThread(self.tox.AV)
|
||||
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.exec_()
|
||||
self.init.stop = True
|
||||
self.mainloop.stop = True
|
||||
self.avloop.stop = True
|
||||
plugin_helper.stop()
|
||||
self.mainloop.wait()
|
||||
self.init.wait()
|
||||
self.avloop.wait()
|
||||
data = self.tox.get_savedata()
|
||||
ProfileHelper.save_profile(data)
|
||||
ProfileHelper.get_instance().save_profile(data)
|
||||
settings.close()
|
||||
del self.tox
|
||||
|
||||
@ -162,7 +240,7 @@ class Toxygen(object):
|
||||
self.init.wait()
|
||||
self.avloop.wait()
|
||||
data = self.tox.get_savedata()
|
||||
ProfileHelper.save_profile(data)
|
||||
ProfileHelper.get_instance().save_profile(data)
|
||||
del self.tox
|
||||
# create new tox instance
|
||||
self.tox = tox_factory(data, Settings.get_instance())
|
||||
@ -176,6 +254,10 @@ class Toxygen(object):
|
||||
|
||||
self.avloop = self.ToxAVIterateThread(self.tox.AV)
|
||||
self.avloop.start()
|
||||
|
||||
plugin_helper = PluginLoader.get_instance()
|
||||
plugin_helper.set_tox(self.tox)
|
||||
|
||||
return self.tox
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
@ -200,7 +282,7 @@ class Toxygen(object):
|
||||
self.tox.bootstrap(*data)
|
||||
except:
|
||||
pass
|
||||
for _ in xrange(10):
|
||||
for _ in range(10):
|
||||
if self.stop:
|
||||
return
|
||||
self.msleep(1000)
|
||||
@ -239,7 +321,7 @@ class Toxygen(object):
|
||||
self.toxav.iterate()
|
||||
self.msleep(self.toxav.iteration_interval())
|
||||
|
||||
class Login(object):
|
||||
class Login:
|
||||
|
||||
def __init__(self, arr):
|
||||
self.arr = arr
|
||||
@ -263,6 +345,6 @@ class Toxygen(object):
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) == 1:
|
||||
toxygen = Toxygen()
|
||||
else: # path to profile
|
||||
else: # path to profile or tox: uri
|
||||
toxygen = Toxygen(sys.argv[1])
|
||||
toxygen.main()
|
||||
|
@ -3,41 +3,18 @@
|
||||
from menu import *
|
||||
from profile import *
|
||||
from list_items 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)
|
||||
from widgets import MultilineEdit, LineEdit
|
||||
import plugin_support
|
||||
from mainscreen_widgets import *
|
||||
|
||||
|
||||
class MainWindow(QtGui.QMainWindow):
|
||||
|
||||
def __init__(self, tox, reset):
|
||||
def __init__(self, tox, reset, tray):
|
||||
super(MainWindow, self).__init__()
|
||||
self.reset = reset
|
||||
self.tray = tray
|
||||
self.setAcceptDrops(True)
|
||||
self.initUI(tox)
|
||||
|
||||
def setup_menu(self, MainWindow):
|
||||
@ -47,12 +24,16 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.menubar.setMinimumSize(self.width(), 25)
|
||||
self.menubar.setMaximumSize(self.width(), 25)
|
||||
self.menubar.setBaseSize(self.width(), 25)
|
||||
|
||||
self.menuProfile = QtGui.QMenu(self.menubar)
|
||||
self.menuProfile.setObjectName("menuProfile")
|
||||
self.menuSettings = QtGui.QMenu(self.menubar)
|
||||
self.menuSettings.setObjectName("menuSettings")
|
||||
self.menuPlugins = QtGui.QMenu(self.menubar)
|
||||
self.menuPlugins.setObjectName("menuPlugins")
|
||||
self.menuAbout = QtGui.QMenu(self.menubar)
|
||||
self.menuAbout.setObjectName("menuAbout")
|
||||
|
||||
self.actionAdd_friend = QtGui.QAction(MainWindow)
|
||||
self.actionAdd_friend.setObjectName("actionAdd_friend")
|
||||
self.actionProfile_settings = QtGui.QAction(MainWindow)
|
||||
@ -70,6 +51,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.actionSettings = QtGui.QAction(MainWindow)
|
||||
self.actionSettings.setObjectName("actionSettings")
|
||||
self.audioSettings = QtGui.QAction(MainWindow)
|
||||
self.pluginData = QtGui.QAction(MainWindow)
|
||||
self.menuProfile.addAction(self.actionAdd_friend)
|
||||
self.menuProfile.addAction(self.actionSettings)
|
||||
self.menuSettings.addAction(self.actionPrivacy_settings)
|
||||
@ -77,9 +59,11 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.menuSettings.addAction(self.actionNotifications)
|
||||
self.menuSettings.addAction(self.actionNetwork)
|
||||
self.menuSettings.addAction(self.audioSettings)
|
||||
self.menuPlugins.addAction(self.pluginData)
|
||||
self.menuAbout.addAction(self.actionAbout_program)
|
||||
self.menubar.addAction(self.menuProfile.menuAction())
|
||||
self.menubar.addAction(self.menuSettings.menuAction())
|
||||
self.menubar.addAction(self.menuPlugins.menuAction())
|
||||
self.menubar.addAction(self.menuAbout.menuAction())
|
||||
|
||||
self.actionAbout_program.triggered.connect(self.about_program)
|
||||
@ -90,13 +74,20 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.actionInterface_settings.triggered.connect(self.interface_settings)
|
||||
self.actionNotifications.triggered.connect(self.notification_settings)
|
||||
self.audioSettings.triggered.connect(self.audio_settings)
|
||||
self.pluginData.triggered.connect(self.plugins_menu)
|
||||
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
||||
|
||||
def languageChange(self, *args, **kwargs):
|
||||
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):
|
||||
self.online_contacts.setText(QtGui.QApplication.translate("Form", "Online contacts", 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.menuProfile.setTitle(QtGui.QApplication.translate("MainWindow", "Profile", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.menuSettings.setTitle(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.menuAbout.setTitle(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8))
|
||||
@ -109,86 +100,75 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.actionAbout_program.setText(QtGui.QApplication.translate("MainWindow", "About program", 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.contact_name.setPlaceholderText(QtGui.QApplication.translate("MainWindow", "Find contact", 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.contact_name.setPlaceholderText(QtGui.QApplication.translate("MainWindow", "Search", 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.online_contacts.clear()
|
||||
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "All", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.online_contacts.setCurrentIndex(int(Settings.get_instance()['show_online_friends']))
|
||||
|
||||
def setup_right_bottom(self, Form):
|
||||
Form.setObjectName("right_bottom")
|
||||
Form.resize(650, 75)
|
||||
Form.resize(650, 60)
|
||||
self.messageEdit = MessageArea(Form, self)
|
||||
self.messageEdit.setGeometry(QtCore.QRect(0, 5, 450, 70))
|
||||
self.messageEdit.setGeometry(QtCore.QRect(0, 3, 450, 55))
|
||||
self.messageEdit.setObjectName("messageEdit")
|
||||
|
||||
class QRightClickButton(QtGui.QPushButton):
|
||||
def __init__(self, parent):
|
||||
super(QRightClickButton, self).__init__(parent)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if event.button() == QtCore.Qt.RightButton:
|
||||
self.emit(QtCore.SIGNAL("rightClicked()"))
|
||||
else:
|
||||
super(QRightClickButton, self).mousePressEvent(event)
|
||||
|
||||
self.screenshotButton = QRightClickButton(Form)
|
||||
self.screenshotButton.setGeometry(QtCore.QRect(455, 5, 55, 70))
|
||||
self.screenshotButton.setObjectName("screenshotButton")
|
||||
|
||||
self.fileTransferButton = QtGui.QPushButton(Form)
|
||||
self.fileTransferButton.setGeometry(QtCore.QRect(510, 5, 55, 70))
|
||||
self.fileTransferButton.setObjectName("fileTransferButton")
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(10)
|
||||
self.messageEdit.setFont(font)
|
||||
|
||||
self.sendMessageButton = QtGui.QPushButton(Form)
|
||||
self.sendMessageButton.setGeometry(QtCore.QRect(565, 5, 55, 70))
|
||||
self.sendMessageButton.setGeometry(QtCore.QRect(565, 3, 60, 55))
|
||||
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)
|
||||
self.sendMessageButton.setIcon(icon)
|
||||
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(30, 45))
|
||||
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)
|
||||
self.screenshotButton.clicked.connect(self.send_screenshot)
|
||||
pixmap = QtGui.QPixmap('menu.png')
|
||||
icon = QtGui.QIcon(pixmap)
|
||||
self.menuButton.setIcon(icon)
|
||||
self.menuButton.setIconSize(QtCore.QSize(40, 40))
|
||||
|
||||
self.sendMessageButton.clicked.connect(self.send_message)
|
||||
self.connect(self.screenshotButton, QtCore.SIGNAL("rightClicked()"), lambda: self.send_screenshot(True))
|
||||
|
||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||
|
||||
def setup_left_bottom(self, Form):
|
||||
Form.setObjectName("left_bottom")
|
||||
Form.resize(270, 80)
|
||||
self.online_contacts = QtGui.QCheckBox(Form)
|
||||
self.online_contacts.setGeometry(QtCore.QRect(0, 0, 250, 20))
|
||||
self.online_contacts.setObjectName("online_contacts")
|
||||
self.online_contacts.clicked.connect(self.filtering)
|
||||
self.contact_name = QtGui.QLineEdit(Form)
|
||||
self.contact_name.setGeometry(QtCore.QRect(0, 23, 270, 26))
|
||||
def setup_left_center_menu(self, Form):
|
||||
Form.resize(270, 25)
|
||||
self.search_label = QtGui.QLabel(Form)
|
||||
self.search_label.setGeometry(QtCore.QRect(3, 2, 20, 20))
|
||||
pixmap = QtGui.QPixmap()
|
||||
pixmap.load(curr_directory() + '/images/search.png')
|
||||
self.search_label.setScaledContents(False)
|
||||
self.search_label.setPixmap(pixmap)
|
||||
|
||||
self.contact_name = LineEdit(Form)
|
||||
self.contact_name.setGeometry(QtCore.QRect(0, 0, 150, 25))
|
||||
self.contact_name.setObjectName("contact_name")
|
||||
self.contact_name.textChanged.connect(self.filtering)
|
||||
|
||||
self.online_contacts = QtGui.QComboBox(Form)
|
||||
self.online_contacts.setGeometry(QtCore.QRect(150, 0, 120, 25))
|
||||
self.online_contacts.activated[int].connect(lambda x: self.filtering())
|
||||
self.search_label.raise_()
|
||||
|
||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||
|
||||
def setup_left_top(self, Form):
|
||||
Form.setObjectName("left_top")
|
||||
Form.resize(500, 300)
|
||||
Form.setCursor(QtCore.Qt.PointingHandCursor)
|
||||
Form.setMinimumSize(QtCore.QSize(250, 100))
|
||||
Form.setMaximumSize(QtCore.QSize(250, 100))
|
||||
Form.setBaseSize(QtCore.QSize(250, 100))
|
||||
Form.setMinimumSize(QtCore.QSize(270, 100))
|
||||
Form.setMaximumSize(QtCore.QSize(270, 100))
|
||||
Form.setBaseSize(QtCore.QSize(270, 100))
|
||||
self.avatar_label = Form.avatar_label = QtGui.QLabel(Form)
|
||||
self.avatar_label.setGeometry(QtCore.QRect(10, 20, 64, 64))
|
||||
self.avatar_label.setGeometry(QtCore.QRect(5, 30, 64, 64))
|
||||
self.avatar_label.setScaledContents(True)
|
||||
self.name = Form.name = DataLabel(Form)
|
||||
Form.name.setGeometry(QtCore.QRect(80, 30, 150, 25))
|
||||
Form.name.setGeometry(QtCore.QRect(75, 40, 150, 25))
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setPointSize(14)
|
||||
@ -196,16 +176,13 @@ class MainWindow(QtGui.QMainWindow):
|
||||
Form.name.setFont(font)
|
||||
Form.name.setObjectName("name")
|
||||
self.status_message = Form.status_message = DataLabel(Form)
|
||||
Form.status_message.setGeometry(QtCore.QRect(80, 60, 170, 20))
|
||||
Form.status_message.setGeometry(QtCore.QRect(75, 60, 170, 25))
|
||||
font.setPointSize(12)
|
||||
font.setBold(False)
|
||||
Form.status_message.setFont(font)
|
||||
Form.status_message.setObjectName("status_message")
|
||||
self.connection_status = Form.connection_status = StatusCircle(self)
|
||||
Form.connection_status.setGeometry(QtCore.QRect(230, 34, 64, 64))
|
||||
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.connection_status = Form.connection_status = StatusCircle(Form)
|
||||
Form.connection_status.setGeometry(QtCore.QRect(230, 35, 32, 32))
|
||||
self.avatar_label.mouseReleaseEvent = self.profile_settings
|
||||
self.status_message.mouseReleaseEvent = self.profile_settings
|
||||
self.name.mouseReleaseEvent = self.profile_settings
|
||||
@ -213,13 +190,12 @@ class MainWindow(QtGui.QMainWindow):
|
||||
Form.connection_status.setObjectName("connection_status")
|
||||
|
||||
def setup_right_top(self, Form):
|
||||
Form.setObjectName("Form")
|
||||
Form.resize(650, 300)
|
||||
Form.resize(650, 100)
|
||||
self.account_avatar = QtGui.QLabel(Form)
|
||||
self.account_avatar.setGeometry(QtCore.QRect(10, 20, 64, 64))
|
||||
self.account_avatar.setGeometry(QtCore.QRect(10, 30, 64, 64))
|
||||
self.account_avatar.setScaledContents(True)
|
||||
self.account_name = DataLabel(Form)
|
||||
self.account_name.setGeometry(QtCore.QRect(100, 30, 400, 25))
|
||||
self.account_name.setGeometry(QtCore.QRect(100, 25, 400, 25))
|
||||
self.account_name.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
@ -228,7 +204,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.account_name.setFont(font)
|
||||
self.account_name.setObjectName("account_name")
|
||||
self.account_status = DataLabel(Form)
|
||||
self.account_status.setGeometry(QtCore.QRect(100, 50, 400, 25))
|
||||
self.account_status.setGeometry(QtCore.QRect(100, 45, 400, 25))
|
||||
self.account_status.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse)
|
||||
font.setPointSize(12)
|
||||
font.setBold(False)
|
||||
@ -237,10 +213,14 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.callButton = QtGui.QPushButton(Form)
|
||||
self.callButton.setGeometry(QtCore.QRect(550, 30, 50, 50))
|
||||
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.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.load(curr_directory() + '/images/typing.png')
|
||||
self.typing.setScaledContents(False)
|
||||
@ -262,6 +242,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.messages = QtGui.QListWidget(widget)
|
||||
self.messages.setGeometry(0, 0, 620, 310)
|
||||
self.messages.setObjectName("messages")
|
||||
self.messages.setSpacing(1)
|
||||
self.messages.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||
self.messages.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.messages.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
@ -275,60 +256,85 @@ class MainWindow(QtGui.QMainWindow):
|
||||
|
||||
def initUI(self, tox):
|
||||
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')
|
||||
os.chdir(curr_directory() + '/images/')
|
||||
main = QtGui.QWidget()
|
||||
grid = QtGui.QGridLayout()
|
||||
search = QtGui.QWidget()
|
||||
self.setup_left_bottom(search)
|
||||
grid.addWidget(search, 3, 0)
|
||||
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)
|
||||
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, 2, 1, 2, 1)
|
||||
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)
|
||||
grid.addWidget(main_list, 1, 0, 2, 1)
|
||||
grid.setColumnMinimumWidth(1, 500)
|
||||
grid.setColumnMinimumWidth(0, 270)
|
||||
grid.setRowMinimumHeight(1, 400)
|
||||
grid.setRowMinimumHeight(2, 20)
|
||||
grid.setRowMinimumHeight(3, 50)
|
||||
if not Settings.get_instance()['mirror_mode']:
|
||||
grid.addWidget(search, 1, 0)
|
||||
grid.addWidget(name, 0, 0)
|
||||
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(2, 320)
|
||||
grid.setRowMinimumHeight(3, 55)
|
||||
grid.setColumnStretch(1, 1)
|
||||
grid.setRowStretch(1, 1)
|
||||
grid.setRowStretch(2, 1)
|
||||
main.setLayout(grid)
|
||||
self.setCentralWidget(main)
|
||||
self.setup_menu(self)
|
||||
self.messageEdit.setFocus()
|
||||
self.user_info = name
|
||||
self.friend_info = info
|
||||
self.profile = Profile(tox, self)
|
||||
self.retranslateUi()
|
||||
self.profile = Profile(tox, self)
|
||||
|
||||
def closeEvent(self, *args, **kwargs):
|
||||
self.profile.save_history()
|
||||
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()
|
||||
|
||||
def resizeEvent(self, *args, **kwargs):
|
||||
self.messages.setGeometry(0, 0, self.width() - 300, self.height() - 205)
|
||||
self.friends_list.setGeometry(0, 0, 270, self.height() - 180)
|
||||
self.callButton.setGeometry(QtCore.QRect(self.width() - 370, 30, 50, 50))
|
||||
self.typing.setGeometry(QtCore.QRect(self.width() - 420, 40, 50, 30))
|
||||
self.messageEdit.setGeometry(QtCore.QRect(0, 5, self.width() - 470, 70))
|
||||
self.screenshotButton.setGeometry(QtCore.QRect(self.width() - 465, 5, 55, 70))
|
||||
self.fileTransferButton.setGeometry(QtCore.QRect(self.width() - 410, 5, 55, 70))
|
||||
self.sendMessageButton.setGeometry(QtCore.QRect(self.width() - 355, 5, 55, 70))
|
||||
self.account_name.setGeometry(QtCore.QRect(100, 30, self.width() - 520, 25))
|
||||
self.account_status.setGeometry(QtCore.QRect(100, 50, self.width() - 520, 25))
|
||||
self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155)
|
||||
self.friends_list.setGeometry(0, 0, 270, self.height() - 125)
|
||||
|
||||
self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 40, 50, 50))
|
||||
self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 40, 50, 50))
|
||||
self.typing.setGeometry(QtCore.QRect(self.width() - 450, 50, 50, 30))
|
||||
|
||||
self.messageEdit.setGeometry(QtCore.QRect(55, 0, self.width() - 395, 55))
|
||||
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()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
@ -353,8 +359,12 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.n_s = NetworkSettings(self.reset)
|
||||
self.n_s.show()
|
||||
|
||||
def add_contact(self):
|
||||
self.a_c = AddContact()
|
||||
def plugins_menu(self):
|
||||
self.p_s = PluginsSettings()
|
||||
self.p_s.show()
|
||||
|
||||
def add_contact(self, link=''):
|
||||
self.a_c = AddContact(link)
|
||||
self.a_c.show()
|
||||
|
||||
def profile_settings(self, *args):
|
||||
@ -377,6 +387,15 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.audio_s = AudioSettings()
|
||||
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
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
@ -386,23 +405,40 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.profile.send_message(text)
|
||||
|
||||
def send_file(self):
|
||||
if self.profile.is_active_online(): # active friend exists and online
|
||||
choose_file = QtGui.QApplication.translate("MainWindow", 'Choose file', None, QtGui.QApplication.UnicodeUTF8)
|
||||
choose = QtGui.QApplication.translate("MainWindow", choose_file, None, QtGui.QApplication.UnicodeUTF8)
|
||||
self.menu.hide()
|
||||
if self.profile.active_friend + 1:
|
||||
choose = QtGui.QApplication.translate("MainWindow", 'Choose file', None, QtGui.QApplication.UnicodeUTF8)
|
||||
name = QtGui.QFileDialog.getOpenFileName(self, choose)
|
||||
if name[0]:
|
||||
self.profile.send_file(name[0])
|
||||
|
||||
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.show()
|
||||
if hide:
|
||||
self.hide()
|
||||
|
||||
def call(self):
|
||||
if self.profile.is_active_online(): # active friend exists and online
|
||||
self.profile.call_click(True)
|
||||
def send_smiley(self):
|
||||
self.menu.hide()
|
||||
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):
|
||||
self.update_call_state('finish_call')
|
||||
@ -414,10 +450,16 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.update_call_state('call')
|
||||
|
||||
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))
|
||||
icon = QtGui.QIcon(pixmap)
|
||||
self.callButton.setIcon(icon)
|
||||
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
|
||||
@ -426,7 +468,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||
def friend_right_click(self, pos):
|
||||
item = self.friends_list.itemAt(pos)
|
||||
num = self.friends_list.indexFromItem(item).row()
|
||||
friend = Profile.get_instance().get_friend_by_number(num)
|
||||
friend = Profile.get_instance().get_friend(num)
|
||||
settings = Settings.get_instance()
|
||||
allowed = friend.tox_id in settings['auto_accept_from_friends']
|
||||
auto = QtGui.QApplication.translate("MainWindow", 'Disallow auto accept', None, QtGui.QApplication.UnicodeUTF8) if allowed else QtGui.QApplication.translate("MainWindow", 'Allow auto accept', None, QtGui.QApplication.UnicodeUTF8)
|
||||
@ -437,15 +479,37 @@ class MainWindow(QtGui.QMainWindow):
|
||||
copy_key_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Copy public key', None, QtGui.QApplication.UnicodeUTF8))
|
||||
auto_accept_item = self.listMenu.addAction(auto)
|
||||
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)
|
||||
if len(submenu):
|
||||
plug = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8))
|
||||
plug.addActions(submenu)
|
||||
self.connect(set_alias_item, QtCore.SIGNAL("triggered()"), lambda: self.set_alias(num))
|
||||
self.connect(remove_item, QtCore.SIGNAL("triggered()"), lambda: self.remove_friend(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(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))
|
||||
self.listMenu.move(parent_position + pos)
|
||||
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):
|
||||
self.profile.set_alias(num)
|
||||
|
||||
@ -478,71 +542,13 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.profile.set_active(num)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
x, y = event.x(), event.y()
|
||||
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()
|
||||
else:
|
||||
super(self.__class__, self).mouseReleaseEvent(event)
|
||||
super(MainWindow, self).mouseReleaseEvent(event)
|
||||
|
||||
def filtering(self):
|
||||
self.profile.filtration(self.online_contacts.isChecked(), 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)
|
||||
|
||||
|
||||
self.profile.filtration(self.online_contacts.currentIndex() == 1, self.contact_name.text())
|
||||
|
||||
|
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()
|
||||
|
||||
|
459
src/menu.py
@ -1,19 +1,24 @@
|
||||
from PySide import QtCore, QtGui
|
||||
try:
|
||||
from PySide import QtCore, QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from settings import *
|
||||
from profile import Profile
|
||||
from util import get_style, curr_directory
|
||||
from widgets import CenteredWidget, DataLabel
|
||||
from util import curr_directory
|
||||
from widgets import CenteredWidget, DataLabel, LineEdit
|
||||
import pyaudio
|
||||
import toxencryptsave
|
||||
import plugin_support
|
||||
|
||||
|
||||
class AddContact(CenteredWidget):
|
||||
"""Add contact form"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, tox_id=''):
|
||||
super(AddContact, self).__init__()
|
||||
self.initUI()
|
||||
self.initUI(tox_id)
|
||||
|
||||
def initUI(self):
|
||||
def initUI(self, tox_id):
|
||||
self.setObjectName('AddContact')
|
||||
self.resize(568, 306)
|
||||
self.sendRequestButton = QtGui.QPushButton(self)
|
||||
@ -22,24 +27,25 @@ class AddContact(CenteredWidget):
|
||||
self.sendRequestButton.setBaseSize(QtCore.QSize(0, 0))
|
||||
self.sendRequestButton.setObjectName("sendRequestButton")
|
||||
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.setObjectName("lineEdit")
|
||||
self.tox_id.setText(tox_id)
|
||||
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.setGeometry(QtCore.QRect(120, 10, 420, 20))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(10)
|
||||
font.setWeight(30)
|
||||
self.error_label.setFont(font)
|
||||
self.error_label.setStyleSheet("QLabel { color: red; }")
|
||||
self.error_label.setStyleSheet("QLabel { color: #BC1C1C; }")
|
||||
self.label.setObjectName("label")
|
||||
self.message_edit = QtGui.QTextEdit(self)
|
||||
self.message_edit.setGeometry(QtCore.QRect(50, 110, 471, 151))
|
||||
self.message_edit.setObjectName("textEdit")
|
||||
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.setObjectName("label_2")
|
||||
self.retranslateUi()
|
||||
@ -65,6 +71,7 @@ class AddContact(CenteredWidget):
|
||||
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.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):
|
||||
@ -72,20 +79,21 @@ class ProfileSettings(CenteredWidget):
|
||||
def __init__(self):
|
||||
super(ProfileSettings, self).__init__()
|
||||
self.initUI()
|
||||
self.center()
|
||||
|
||||
def initUI(self):
|
||||
self.setObjectName("ProfileSettingsForm")
|
||||
self.setMinimumSize(QtCore.QSize(650, 320))
|
||||
self.setMaximumSize(QtCore.QSize(650, 320))
|
||||
self.nick = QtGui.QLineEdit(self)
|
||||
self.setMinimumSize(QtCore.QSize(700, 600))
|
||||
self.setMaximumSize(QtCore.QSize(700, 600))
|
||||
self.nick = LineEdit(self)
|
||||
self.nick.setGeometry(QtCore.QRect(30, 60, 350, 27))
|
||||
self.nick.setObjectName("nick")
|
||||
profile = Profile.get_instance()
|
||||
self.nick.setText(profile.name)
|
||||
self.status = QtGui.QLineEdit(self)
|
||||
self.status.setGeometry(QtCore.QRect(30, 130, 350, 27))
|
||||
self.status.setObjectName("status")
|
||||
self.status.setText(profile.status_message)
|
||||
self.status = QtGui.QComboBox(self)
|
||||
self.status.setGeometry(QtCore.QRect(400, 60, 200, 27))
|
||||
self.status_message = LineEdit(self)
|
||||
self.status_message.setGeometry(QtCore.QRect(30, 130, 350, 27))
|
||||
self.status_message.setText(profile.status_message)
|
||||
self.label = QtGui.QLabel(self)
|
||||
self.label.setGeometry(QtCore.QRect(40, 30, 91, 25))
|
||||
font = QtGui.QFont()
|
||||
@ -93,40 +101,66 @@ class ProfileSettings(CenteredWidget):
|
||||
font.setWeight(75)
|
||||
font.setBold(True)
|
||||
self.label.setFont(font)
|
||||
self.label.setObjectName("label")
|
||||
self.label_2 = QtGui.QLabel(self)
|
||||
self.label_2.setGeometry(QtCore.QRect(40, 100, 100, 25))
|
||||
self.label_2.setFont(font)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.label_3 = QtGui.QLabel(self)
|
||||
self.label_3.setGeometry(QtCore.QRect(40, 180, 100, 25))
|
||||
self.label_3.setFont(font)
|
||||
self.label_3.setObjectName("label_3")
|
||||
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)
|
||||
self.tox_id.setFont(font)
|
||||
self.tox_id.setObjectName("tox_id")
|
||||
s = profile.tox_id
|
||||
self.tox_id.setText(s)
|
||||
self.copyId = QtGui.QPushButton(self)
|
||||
self.copyId.setGeometry(QtCore.QRect(40, 250, 160, 30))
|
||||
self.copyId.setObjectName("copyId")
|
||||
self.copyId.setGeometry(QtCore.QRect(40, 250, 180, 30))
|
||||
self.copyId.clicked.connect(self.copy)
|
||||
self.export = QtGui.QPushButton(self)
|
||||
self.export.setGeometry(QtCore.QRect(210, 250, 160, 30))
|
||||
self.export.setObjectName("export")
|
||||
self.export.setGeometry(QtCore.QRect(230, 250, 180, 30))
|
||||
self.export.clicked.connect(self.export_profile)
|
||||
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_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.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.new_avatar.clicked.connect(self.set_avatar)
|
||||
self.profile_pass = QtGui.QLabel(self)
|
||||
self.profile_pass.setGeometry(QtCore.QRect(40, 340, 300, 30))
|
||||
font.setPointSize(18)
|
||||
self.profile_pass.setFont(font)
|
||||
self.password = LineEdit(self)
|
||||
self.password.setGeometry(QtCore.QRect(40, 380, 300, 30))
|
||||
self.password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
|
||||
self.leave_blank = QtGui.QLabel(self)
|
||||
self.leave_blank.setGeometry(QtCore.QRect(350, 380, 300, 30))
|
||||
self.confirm_password = LineEdit(self)
|
||||
self.confirm_password.setGeometry(QtCore.QRect(40, 420, 300, 30))
|
||||
self.confirm_password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
|
||||
self.set_password = QtGui.QPushButton(self)
|
||||
self.set_password.setGeometry(QtCore.QRect(40, 470, 300, 30))
|
||||
self.set_password.clicked.connect(self.new_password)
|
||||
self.not_match = QtGui.QLabel(self)
|
||||
self.not_match.setGeometry(QtCore.QRect(350, 420, 300, 30))
|
||||
self.not_match.setVisible(False)
|
||||
self.not_match.setStyleSheet('QLabel { color: #BC1C1C; }')
|
||||
self.warning = QtGui.QLabel(self)
|
||||
self.warning.setGeometry(QtCore.QRect(40, 510, 500, 30))
|
||||
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()
|
||||
if profile.status is not None:
|
||||
self.status.setCurrentIndex(profile.status)
|
||||
else:
|
||||
self.status.setVisible(False)
|
||||
QtCore.QMetaObject.connectSlotsByName(self)
|
||||
|
||||
def retranslateUi(self):
|
||||
@ -139,6 +173,50 @@ class ProfileSettings(CenteredWidget):
|
||||
self.new_avatar.setText(QtGui.QApplication.translate("ProfileSettingsForm", "New avatar", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.delete_avatar.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Reset avatar", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.new_nospam.setText(QtGui.QApplication.translate("ProfileSettingsForm", "New NoSpam", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.profile_pass.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Profile password", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.password.setPlaceholderText(QtGui.QApplication.translate("ProfileSettingsForm", "Password (at least 8 symbols)", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.confirm_password.setPlaceholderText(QtGui.QApplication.translate("ProfileSettingsForm", "Confirm password", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.set_password.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Set password", 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.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):
|
||||
if self.password.text() == self.confirm_password.text():
|
||||
if not len(self.password.text()) or len(self.password.text()) >= 8:
|
||||
e = toxencryptsave.LibToxEncryptSave.get_instance()
|
||||
e.set_password(self.password.text())
|
||||
self.close()
|
||||
else:
|
||||
self.not_match.setText(
|
||||
QtGui.QApplication.translate("ProfileSettingsForm", "Password must be at least 8 symbols", None,
|
||||
QtGui.QApplication.UnicodeUTF8))
|
||||
self.not_match.setVisible(True)
|
||||
else:
|
||||
self.not_match.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Passwords do not match", None,
|
||||
QtGui.QApplication.UnicodeUTF8))
|
||||
self.not_match.setVisible(True)
|
||||
|
||||
def copy(self):
|
||||
clipboard = QtGui.QApplication.clipboard()
|
||||
@ -156,16 +234,23 @@ class ProfileSettings(CenteredWidget):
|
||||
Profile.get_instance().reset_avatar()
|
||||
|
||||
def set_avatar(self):
|
||||
name = QtGui.QFileDialog.getOpenFileName(self, 'Open file', None, 'Image Files (*.png)')
|
||||
choose = QtGui.QApplication.translate("ProfileSettingsForm", "Choose avatar", None, QtGui.QApplication.UnicodeUTF8)
|
||||
name = QtGui.QFileDialog.getOpenFileName(self, choose, None, 'Images (*.png)')
|
||||
if name[0]:
|
||||
with open(name[0], 'rb') as f:
|
||||
data = f.read()
|
||||
Profile.get_instance().set_avatar(data)
|
||||
bitmap = QtGui.QPixmap(name[0])
|
||||
bitmap.scaled(QtCore.QSize(128, 128), aspectMode=QtCore.Qt.KeepAspectRatio,
|
||||
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):
|
||||
directory = QtGui.QFileDialog.getExistingDirectory() + '/'
|
||||
if directory != '/':
|
||||
ProfileHelper.export_profile(directory)
|
||||
ProfileHelper.get_instance().export_profile(directory)
|
||||
settings = Settings.get_instance()
|
||||
settings.export(directory)
|
||||
profile = Profile.get_instance()
|
||||
@ -173,8 +258,9 @@ class ProfileSettings(CenteredWidget):
|
||||
|
||||
def closeEvent(self, event):
|
||||
profile = Profile.get_instance()
|
||||
profile.set_name(self.nick.text().encode('utf-8'))
|
||||
profile.set_status_message(self.status.text().encode('utf-8'))
|
||||
profile.set_name(self.nick.text())
|
||||
profile.set_status_message(self.status_message.text().encode('utf-8'))
|
||||
profile.set_status(self.status.currentIndex())
|
||||
|
||||
|
||||
class NetworkSettings(CenteredWidget):
|
||||
@ -183,13 +269,14 @@ class NetworkSettings(CenteredWidget):
|
||||
super(NetworkSettings, self).__init__()
|
||||
self.reset = reset
|
||||
self.initUI()
|
||||
self.center()
|
||||
|
||||
def initUI(self):
|
||||
self.setObjectName("NetworkSettings")
|
||||
self.resize(300, 300)
|
||||
self.setMinimumSize(QtCore.QSize(300, 300))
|
||||
self.setMaximumSize(QtCore.QSize(300, 300))
|
||||
self.setBaseSize(QtCore.QSize(300, 300))
|
||||
self.resize(300, 330)
|
||||
self.setMinimumSize(QtCore.QSize(300, 330))
|
||||
self.setMaximumSize(QtCore.QSize(300, 330))
|
||||
self.setBaseSize(QtCore.QSize(300, 330))
|
||||
self.ipv = QtGui.QCheckBox(self)
|
||||
self.ipv.setGeometry(QtCore.QRect(20, 10, 97, 22))
|
||||
self.ipv.setObjectName("ipv")
|
||||
@ -201,10 +288,10 @@ class NetworkSettings(CenteredWidget):
|
||||
self.http = QtGui.QCheckBox(self)
|
||||
self.http.setGeometry(QtCore.QRect(20, 70, 97, 22))
|
||||
self.proxy.setObjectName("proxy")
|
||||
self.proxyip = QtGui.QLineEdit(self)
|
||||
self.proxyip = LineEdit(self)
|
||||
self.proxyip.setGeometry(QtCore.QRect(40, 130, 231, 27))
|
||||
self.proxyip.setObjectName("proxyip")
|
||||
self.proxyport = QtGui.QLineEdit(self)
|
||||
self.proxyport = LineEdit(self)
|
||||
self.proxyport.setGeometry(QtCore.QRect(40, 190, 231, 27))
|
||||
self.proxyport.setObjectName("proxyport")
|
||||
self.label = QtGui.QLabel(self)
|
||||
@ -219,8 +306,11 @@ class NetworkSettings(CenteredWidget):
|
||||
self.udp.setChecked(settings['udp_enabled'])
|
||||
self.proxy.setChecked(settings['proxy_type'])
|
||||
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.warning = QtGui.QLabel(self)
|
||||
self.warning.setGeometry(QtCore.QRect(5, 270, 290, 60))
|
||||
self.warning.setStyleSheet('QLabel { color: #BC1C1C; }')
|
||||
self.retranslateUi()
|
||||
self.proxy.stateChanged.connect(lambda x: self.activate())
|
||||
self.activate()
|
||||
@ -235,6 +325,8 @@ class NetworkSettings(CenteredWidget):
|
||||
self.label_2.setText(QtGui.QApplication.translate("Form", "Port:", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.reconnect.setText(QtGui.QApplication.translate("NetworkSettings", "Restart TOX core", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.http.setText(QtGui.QApplication.translate("Form", "HTTP", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.warning.setText(QtGui.QApplication.translate("Form", "WARNING:\nusing proxy with enabled UDP\ncan produce IP leak",
|
||||
None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
def activate(self):
|
||||
bl = self.proxy.isChecked()
|
||||
@ -254,8 +346,8 @@ class NetworkSettings(CenteredWidget):
|
||||
# recreate tox instance
|
||||
Profile.get_instance().reset(self.reset)
|
||||
self.close()
|
||||
except:
|
||||
pass
|
||||
except Exception as ex:
|
||||
log('Exception in restart: ' + str(ex))
|
||||
|
||||
|
||||
class PrivacySettings(CenteredWidget):
|
||||
@ -264,53 +356,55 @@ class PrivacySettings(CenteredWidget):
|
||||
def __init__(self):
|
||||
super(PrivacySettings, self).__init__()
|
||||
self.initUI()
|
||||
self.center()
|
||||
|
||||
def initUI(self):
|
||||
self.setObjectName("privacySettings")
|
||||
self.resize(350, 550)
|
||||
self.setMinimumSize(QtCore.QSize(350, 550))
|
||||
self.setMaximumSize(QtCore.QSize(350, 550))
|
||||
self.resize(370, 600)
|
||||
self.setMinimumSize(QtCore.QSize(370, 600))
|
||||
self.setMaximumSize(QtCore.QSize(370, 600))
|
||||
self.saveHistory = QtGui.QCheckBox(self)
|
||||
self.saveHistory.setGeometry(QtCore.QRect(10, 20, 291, 22))
|
||||
self.saveHistory.setObjectName("saveHistory")
|
||||
self.saveHistory.setGeometry(QtCore.QRect(10, 20, 350, 22))
|
||||
self.saveUnsentOnly = QtGui.QCheckBox(self)
|
||||
self.saveUnsentOnly.setGeometry(QtCore.QRect(10, 60, 350, 22))
|
||||
|
||||
self.fileautoaccept = QtGui.QCheckBox(self)
|
||||
self.fileautoaccept.setGeometry(QtCore.QRect(10, 60, 271, 22))
|
||||
self.fileautoaccept.setObjectName("fileautoaccept")
|
||||
self.fileautoaccept.setGeometry(QtCore.QRect(10, 100, 350, 22))
|
||||
|
||||
self.typingNotifications = QtGui.QCheckBox(self)
|
||||
self.typingNotifications.setGeometry(QtCore.QRect(10, 100, 350, 30))
|
||||
self.typingNotifications.setObjectName("typingNotifications")
|
||||
self.typingNotifications.setGeometry(QtCore.QRect(10, 140, 350, 30))
|
||||
self.inlines = QtGui.QCheckBox(self)
|
||||
self.inlines.setGeometry(QtCore.QRect(10, 140, 350, 30))
|
||||
self.inlines.setObjectName("inlines")
|
||||
|
||||
|
||||
self.inlines.setGeometry(QtCore.QRect(10, 180, 350, 30))
|
||||
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.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.setGeometry(QtCore.QRect(10, 280, 330, 30))
|
||||
self.change_path.setGeometry(QtCore.QRect(10, 320, 350, 30))
|
||||
settings = Settings.get_instance()
|
||||
self.typingNotifications.setChecked(settings['typing_notifications'])
|
||||
self.fileautoaccept.setChecked(settings['allow_auto_accept'])
|
||||
self.saveHistory.setChecked(settings['save_history'])
|
||||
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.change_path.clicked.connect(self.new_path)
|
||||
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.setGeometry(QtCore.QRect(10, 350, 330, 30))
|
||||
self.block_id.setGeometry(QtCore.QRect(10, 390, 350, 30))
|
||||
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.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.setGeometry(QtCore.QRect(10, 460, 330, 30))
|
||||
self.comboBox.setGeometry(QtCore.QRect(10, 500, 350, 30))
|
||||
self.comboBox.addItems(settings['blocked'])
|
||||
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.retranslateUi()
|
||||
QtCore.QMetaObject.connectSlotsByName(self)
|
||||
@ -323,10 +417,16 @@ class PrivacySettings(CenteredWidget):
|
||||
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.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.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.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):
|
||||
if not self.comboBox.count():
|
||||
@ -341,6 +441,7 @@ class PrivacySettings(CenteredWidget):
|
||||
settings = Settings.get_instance()
|
||||
settings['typing_notifications'] = self.typingNotifications.isChecked()
|
||||
settings['allow_auto_accept'] = self.fileautoaccept.isChecked()
|
||||
|
||||
if settings['save_history'] and not self.saveHistory.isChecked(): # clear history
|
||||
reply = QtGui.QMessageBox.question(None,
|
||||
QtGui.QApplication.translate("privacySettings",
|
||||
@ -356,6 +457,21 @@ class PrivacySettings(CenteredWidget):
|
||||
settings['save_history'] = self.saveHistory.isChecked()
|
||||
else:
|
||||
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['allow_inline'] = self.inlines.isChecked()
|
||||
settings.save()
|
||||
@ -372,12 +488,13 @@ class NotificationsSettings(CenteredWidget):
|
||||
def __init__(self):
|
||||
super(NotificationsSettings, self).__init__()
|
||||
self.initUI()
|
||||
self.center()
|
||||
|
||||
def initUI(self):
|
||||
self.setObjectName("notificationsForm")
|
||||
self.resize(350, 200)
|
||||
self.setMinimumSize(QtCore.QSize(350, 200))
|
||||
self.setMaximumSize(QtCore.QSize(350, 200))
|
||||
self.resize(350, 180)
|
||||
self.setMinimumSize(QtCore.QSize(350, 180))
|
||||
self.setMaximumSize(QtCore.QSize(350, 180))
|
||||
self.enableNotifications = QtGui.QCheckBox(self)
|
||||
self.enableNotifications.setGeometry(QtCore.QRect(10, 20, 340, 18))
|
||||
self.callsSound = QtGui.QCheckBox(self)
|
||||
@ -386,7 +503,6 @@ class NotificationsSettings(CenteredWidget):
|
||||
self.soundNotifications.setGeometry(QtCore.QRect(10, 70, 340, 18))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
font.setBold(True)
|
||||
self.callsSound.setFont(font)
|
||||
self.soundNotifications.setFont(font)
|
||||
self.enableNotifications.setFont(font)
|
||||
@ -413,46 +529,72 @@ class NotificationsSettings(CenteredWidget):
|
||||
|
||||
class InterfaceSettings(CenteredWidget):
|
||||
"""Interface settings form"""
|
||||
|
||||
def __init__(self):
|
||||
super(InterfaceSettings, self).__init__()
|
||||
self.initUI()
|
||||
self.center()
|
||||
|
||||
def initUI(self):
|
||||
self.setObjectName("interfaceForm")
|
||||
self.resize(300, 300)
|
||||
self.setMinimumSize(QtCore.QSize(300, 300))
|
||||
self.setMaximumSize(QtCore.QSize(300, 300))
|
||||
self.setBaseSize(QtCore.QSize(300, 300))
|
||||
self.setMinimumSize(QtCore.QSize(400, 450))
|
||||
self.setMaximumSize(QtCore.QSize(400, 450))
|
||||
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.setPointSize(16)
|
||||
font.setWeight(75)
|
||||
font.setPointSize(14)
|
||||
font.setBold(True)
|
||||
self.label.setFont(font)
|
||||
self.label.setObjectName("label")
|
||||
self.themeSelect = QtGui.QComboBox(self)
|
||||
self.themeSelect.setGeometry(QtCore.QRect(30, 60, 160, 30))
|
||||
self.themeSelect.setObjectName("themeSelect")
|
||||
list_of_themes = ['default', 'windows', 'gtk', 'cde', 'plastique', 'motif']
|
||||
self.themeSelect.setGeometry(QtCore.QRect(30, 40, 120, 30))
|
||||
list_of_themes = ['dark']
|
||||
self.themeSelect.addItems(list_of_themes)
|
||||
settings = Settings.get_instance()
|
||||
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.lang_choose = QtGui.QComboBox(self)
|
||||
self.lang_choose.setGeometry(QtCore.QRect(30, 150, 160, 30))
|
||||
self.lang_choose.setObjectName("comboBox")
|
||||
self.lang_choose.setGeometry(QtCore.QRect(30, 110, 120, 30))
|
||||
supported = Settings.supported_languages()
|
||||
for elem in supported:
|
||||
self.lang_choose.addItem(elem[0])
|
||||
lang = settings['language']
|
||||
index = map(lambda x: x[0], supported).index(lang)
|
||||
self.lang_choose.setCurrentIndex(index)
|
||||
for key in supported:
|
||||
self.lang_choose.insertItem(0, key)
|
||||
if settings['language'] == key:
|
||||
self.lang_choose.setCurrentIndex(0)
|
||||
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.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()
|
||||
QtCore.QMetaObject.connectSlotsByName(self)
|
||||
|
||||
@ -460,30 +602,60 @@ class InterfaceSettings(CenteredWidget):
|
||||
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.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):
|
||||
settings = Settings.get_instance()
|
||||
style = str(self.themeSelect.currentText())
|
||||
settings['theme'] = style
|
||||
QtGui.QApplication.setStyle(get_style(style))
|
||||
settings['theme'] = str(self.themeSelect.currentText())
|
||||
settings['smileys'] = self.smileys.isChecked()
|
||||
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()
|
||||
if settings['language'] != language:
|
||||
settings['language'] = language
|
||||
index = self.lang_choose.currentIndex()
|
||||
path = Settings.supported_languages()[index][1]
|
||||
text = self.lang_choose.currentText()
|
||||
path = Settings.supported_languages()[text]
|
||||
app = QtGui.QApplication.instance()
|
||||
app.removeTranslator(app.translator)
|
||||
app.translator.load(curr_directory() + '/translations/' + path)
|
||||
app.installTranslator(app.translator)
|
||||
settings['message_font_size'] = self.messages_font_size.currentIndex() + 10
|
||||
Profile.get_instance().update()
|
||||
settings.save()
|
||||
|
||||
|
||||
class AudioSettings(CenteredWidget):
|
||||
"""
|
||||
Audio calls settings form
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(AudioSettings, self).__init__()
|
||||
self.initUI()
|
||||
self.retranslateUi()
|
||||
self.center()
|
||||
|
||||
def initUI(self):
|
||||
self.setObjectName("audioSettingsForm")
|
||||
@ -506,13 +678,13 @@ class AudioSettings(CenteredWidget):
|
||||
p = pyaudio.PyAudio()
|
||||
settings = Settings.get_instance()
|
||||
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)
|
||||
if device["maxInputChannels"]:
|
||||
self.input.addItem(unicode(device["name"]))
|
||||
self.input.addItem(str(device["name"]))
|
||||
self.in_indexes.append(i)
|
||||
if device["maxOutputChannels"]:
|
||||
self.output.addItem(unicode(device["name"]))
|
||||
self.output.addItem(str(device["name"]))
|
||||
self.out_indexes.append(i)
|
||||
self.input.setCurrentIndex(self.in_indexes.index(settings.audio['input']))
|
||||
self.output.setCurrentIndex(self.out_indexes.index(settings.audio['output']))
|
||||
@ -528,3 +700,86 @@ class AudioSettings(CenteredWidget):
|
||||
settings.audio['input'] = self.in_indexes[self.input.currentIndex()]
|
||||
settings.audio['output'] = self.out_indexes[self.output.currentIndex()]
|
||||
settings.save()
|
||||
|
||||
|
||||
class PluginsSettings(CenteredWidget):
|
||||
"""
|
||||
Plugins settings form
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(PluginsSettings, self).__init__()
|
||||
self.initUI()
|
||||
self.center()
|
||||
self.retranslateUi()
|
||||
|
||||
def initUI(self):
|
||||
self.resize(400, 210)
|
||||
self.setMinimumSize(QtCore.QSize(400, 210))
|
||||
self.setMaximumSize(QtCore.QSize(400, 210))
|
||||
self.comboBox = QtGui.QComboBox(self)
|
||||
self.comboBox.setGeometry(QtCore.QRect(30, 10, 340, 30))
|
||||
self.label = QtGui.QLabel(self)
|
||||
self.label.setGeometry(QtCore.QRect(30, 40, 340, 90))
|
||||
self.label.setWordWrap(True)
|
||||
self.button = QtGui.QPushButton(self)
|
||||
self.button.setGeometry(QtCore.QRect(30, 130, 340, 30))
|
||||
self.button.clicked.connect(self.button_click)
|
||||
self.open = QtGui.QPushButton(self)
|
||||
self.open.setGeometry(QtCore.QRect(30, 170, 340, 30))
|
||||
self.open.clicked.connect(self.open_plugin)
|
||||
self.pl_loader = plugin_support.PluginLoader.get_instance()
|
||||
self.update_list()
|
||||
self.comboBox.currentIndexChanged.connect(self.show_data)
|
||||
self.show_data()
|
||||
|
||||
def retranslateUi(self):
|
||||
self.setWindowTitle(QtGui.QApplication.translate('PluginsForm', "Plugins", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.open.setText(QtGui.QApplication.translate('PluginsForm', "Open selected plugin", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
def open_plugin(self):
|
||||
ind = self.comboBox.currentIndex()
|
||||
plugin = self.data[ind]
|
||||
window = self.pl_loader.plugin_window(plugin[-1])
|
||||
if window is not None:
|
||||
self.window = window
|
||||
self.window.show()
|
||||
else:
|
||||
msgBox = QtGui.QMessageBox()
|
||||
text = QtGui.QApplication.translate("PluginsForm", 'No GUI found for this plugin', None,
|
||||
QtGui.QApplication.UnicodeUTF8)
|
||||
msgBox.setWindowTitle(QtGui.QApplication.translate("PluginsForm", 'Error', None,
|
||||
QtGui.QApplication.UnicodeUTF8))
|
||||
msgBox.setText(text)
|
||||
msgBox.exec_()
|
||||
|
||||
def update_list(self):
|
||||
self.comboBox.clear()
|
||||
data = self.pl_loader.get_plugins_list()
|
||||
self.comboBox.addItems(list(map(lambda x: x[0], data)))
|
||||
self.data = data
|
||||
|
||||
def show_data(self):
|
||||
ind = self.comboBox.currentIndex()
|
||||
if len(self.data):
|
||||
plugin = self.data[ind]
|
||||
descr = plugin[2] or QtGui.QApplication.translate("PluginsForm", "No description available", None, QtGui.QApplication.UnicodeUTF8)
|
||||
self.label.setText(descr)
|
||||
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:
|
||||
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):
|
||||
ind = self.comboBox.currentIndex()
|
||||
plugin = self.data[ind]
|
||||
self.pl_loader.toggle_plugin(plugin[-1])
|
||||
plugin[1] = not plugin[1]
|
||||
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))
|
||||
|
@ -4,21 +4,12 @@ MESSAGE_TYPE = {
|
||||
'TEXT': 0,
|
||||
'ACTION': 1,
|
||||
'FILE_TRANSFER': 2,
|
||||
'INLINE': 3
|
||||
}
|
||||
|
||||
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
|
||||
'INLINE': 3,
|
||||
'INFO_MESSAGE': 4
|
||||
}
|
||||
|
||||
|
||||
class Message(object):
|
||||
class Message:
|
||||
|
||||
def __init__(self, message_type, owner, time):
|
||||
self._time = time
|
||||
@ -31,6 +22,9 @@ class Message(object):
|
||||
def get_owner(self):
|
||||
return self._owner
|
||||
|
||||
def mark_as_sent(self):
|
||||
self._owner = 0
|
||||
|
||||
|
||||
class TextMessage(Message):
|
||||
"""
|
||||
@ -58,7 +52,7 @@ class TransferMessage(Message):
|
||||
self._friend_number, self._file_number = friend_number, 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):
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
Inline image
|
||||
@ -87,3 +93,9 @@ class InlineImage(Message):
|
||||
|
||||
def get_data(self):
|
||||
return self._data
|
||||
|
||||
|
||||
class InfoMessage(TextMessage):
|
||||
|
||||
def __init__(self, message, time):
|
||||
super(InfoMessage, self).__init__(message, None, time, MESSAGE_TYPE['INFO_MESSAGE'])
|
||||
|
@ -1,4 +1,7 @@
|
||||
from PySide import QtGui, QtCore
|
||||
try:
|
||||
from PySide import QtCore, QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from util import curr_directory
|
||||
import wave
|
||||
import pyaudio
|
||||
@ -32,7 +35,7 @@ def tray_notification(title, text, tray, window):
|
||||
tray.connect(tray, QtCore.SIGNAL("messageClicked()"), message_clicked)
|
||||
|
||||
|
||||
class AudioFile(object):
|
||||
class AudioFile:
|
||||
chunk = 1024
|
||||
|
||||
def __init__(self, fl):
|
||||
|
77
src/passwordscreen.py
Normal file
@ -0,0 +1,77 @@
|
||||
from widgets import CenteredWidget, LineEdit
|
||||
try:
|
||||
from PySide import QtCore, QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
|
||||
class PasswordArea(LineEdit):
|
||||
|
||||
def __init__(self, parent):
|
||||
super(PasswordArea, self).__init__(parent)
|
||||
self.parent = parent
|
||||
self.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == QtCore.Qt.Key_Return:
|
||||
self.parent.button_click()
|
||||
else:
|
||||
super(PasswordArea, self).keyPressEvent(event)
|
||||
|
||||
|
||||
class PasswordScreen(CenteredWidget):
|
||||
|
||||
def __init__(self, encrypt, data):
|
||||
super(PasswordScreen, self).__init__()
|
||||
self._encrypt = encrypt
|
||||
self._data = data
|
||||
self.initUI()
|
||||
|
||||
def initUI(self):
|
||||
self.resize(360, 170)
|
||||
self.setMinimumSize(QtCore.QSize(360, 170))
|
||||
self.setMaximumSize(QtCore.QSize(360, 170))
|
||||
|
||||
self.enter_pass = QtGui.QLabel(self)
|
||||
self.enter_pass.setGeometry(QtCore.QRect(30, 10, 300, 30))
|
||||
|
||||
self.password = PasswordArea(self)
|
||||
self.password.setGeometry(QtCore.QRect(30, 50, 300, 30))
|
||||
|
||||
self.button = QtGui.QPushButton(self)
|
||||
self.button.setGeometry(QtCore.QRect(30, 90, 300, 30))
|
||||
self.button.setText('OK')
|
||||
self.button.clicked.connect(self.button_click)
|
||||
|
||||
self.warning = QtGui.QLabel(self)
|
||||
self.warning.setGeometry(QtCore.QRect(30, 130, 300, 30))
|
||||
self.warning.setStyleSheet('QLabel { color: #F70D1A; }')
|
||||
self.warning.setVisible(False)
|
||||
|
||||
self.retranslateUi()
|
||||
self.center()
|
||||
QtCore.QMetaObject.connectSlotsByName(self)
|
||||
|
||||
def button_click(self):
|
||||
if self.password.text():
|
||||
try:
|
||||
self._encrypt.set_password(self.password.text())
|
||||
new_data = self._encrypt.pass_decrypt(self._data[0])
|
||||
except Exception as ex:
|
||||
self.warning.setVisible(True)
|
||||
print('Decryption error:', ex)
|
||||
else:
|
||||
self._data[0] = new_data
|
||||
self.close()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == QtCore.Qt.Key_Enter:
|
||||
self.button_click()
|
||||
else:
|
||||
super(PasswordScreen, self).keyPressEvent(event)
|
||||
|
||||
def retranslateUi(self):
|
||||
self.setWindowTitle(QtGui.QApplication.translate("pass", "Enter password", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.enter_pass.setText(QtGui.QApplication.translate("pass", "Password:", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.warning.setText(QtGui.QApplication.translate("pass", "Incorrect password", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
156
src/plugin_support.py
Normal file
@ -0,0 +1,156 @@
|
||||
import util
|
||||
import profile
|
||||
import os
|
||||
import importlib
|
||||
import inspect
|
||||
import plugins.plugin_super_class as pl
|
||||
import toxencryptsave
|
||||
import sys
|
||||
|
||||
|
||||
class PluginLoader(util.Singleton):
|
||||
|
||||
def __init__(self, tox, settings):
|
||||
super().__init__()
|
||||
self._profile = profile.Profile.get_instance()
|
||||
self._settings = settings
|
||||
self._plugins = {} # dict. key - plugin unique short name, value - tuple (plugin instance, is active)
|
||||
self._tox = tox
|
||||
self._encr = toxencryptsave.LibToxEncryptSave.get_instance()
|
||||
|
||||
def set_tox(self, tox):
|
||||
"""
|
||||
New tox instance
|
||||
"""
|
||||
self._tox = tox
|
||||
for value in self._plugins.values():
|
||||
value[0].set_tox(tox)
|
||||
|
||||
def load(self):
|
||||
"""
|
||||
Load all plugins in plugins folder
|
||||
"""
|
||||
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))]
|
||||
for fl in files:
|
||||
if fl in ('plugin_super_class.py', '__init__.py') or not fl.endswith('.py'):
|
||||
continue
|
||||
name = fl[:-3] # module name without .py
|
||||
try:
|
||||
module = importlib.import_module(name) # import plugin
|
||||
except ImportError:
|
||||
util.log('Import error in module ' + name)
|
||||
continue
|
||||
except Exception as ex:
|
||||
util.log('Exception in module ' + name + ' Exception: ' + str(ex))
|
||||
continue
|
||||
for elem in dir(module):
|
||||
obj = getattr(module, elem)
|
||||
if inspect.isclass(obj) and hasattr(obj, 'is_plugin') and obj.is_plugin: # looking for plugin class in module
|
||||
print('Plugin', elem)
|
||||
try: # create instance of plugin class
|
||||
inst = obj(self._tox, self._profile, self._settings, self._encr)
|
||||
autostart = inst.get_short_name() in self._settings['plugins']
|
||||
if autostart:
|
||||
inst.start()
|
||||
except Exception as ex:
|
||||
util.log('Exception in module ' + name + ' Exception: ' + str(ex))
|
||||
continue
|
||||
self._plugins[inst.get_short_name()] = [inst, autostart] # (inst, is active)
|
||||
break
|
||||
|
||||
def callback_lossless(self, friend_number, data, length):
|
||||
"""
|
||||
New incoming custom lossless packet (callback)
|
||||
"""
|
||||
l = data[0] - pl.LOSSLESS_FIRST_BYTE
|
||||
name = ''.join(chr(x) for x in data[1:l + 1])
|
||||
if name in self._plugins and self._plugins[name][1]:
|
||||
self._plugins[name][0].lossless_packet(''.join(chr(x) for x in data[l + 1:length]), friend_number)
|
||||
|
||||
def callback_lossy(self, friend_number, data, length):
|
||||
"""
|
||||
New incoming custom lossy packet (callback)
|
||||
"""
|
||||
l = data[0] - pl.LOSSY_FIRST_BYTE
|
||||
name = ''.join(chr(x) for x in data[1:l + 1])
|
||||
if name in self._plugins and self._plugins[name][1]:
|
||||
self._plugins[name][0].lossy_packet(''.join(chr(x) for x in data[l + 1:length]), friend_number)
|
||||
|
||||
def friend_online(self, friend_number):
|
||||
"""
|
||||
Friend with specified number is online
|
||||
"""
|
||||
for elem in self._plugins.values():
|
||||
if elem[1]:
|
||||
elem[0].friend_connected(friend_number)
|
||||
|
||||
def get_plugins_list(self):
|
||||
"""
|
||||
Returns list of all plugins
|
||||
"""
|
||||
result = []
|
||||
for data in self._plugins.values():
|
||||
result.append([data[0].get_name(), # plugin full name
|
||||
data[1], # is enabled
|
||||
data[0].get_description(), # plugin description
|
||||
data[0].get_short_name()]) # key - short unique name
|
||||
return result
|
||||
|
||||
def plugin_window(self, key):
|
||||
"""
|
||||
Return window or None for specified plugin
|
||||
"""
|
||||
return self._plugins[key][0].get_window()
|
||||
|
||||
def toggle_plugin(self, key):
|
||||
"""
|
||||
Enable/disable plugin
|
||||
:param key: plugin short name
|
||||
"""
|
||||
plugin = self._plugins[key]
|
||||
if plugin[1]:
|
||||
plugin[0].stop()
|
||||
else:
|
||||
plugin[0].start()
|
||||
plugin[1] = not plugin[1]
|
||||
if plugin[1]:
|
||||
self._settings['plugins'].append(key)
|
||||
else:
|
||||
self._settings['plugins'].remove(key)
|
||||
self._settings.save()
|
||||
|
||||
def command(self, text):
|
||||
"""
|
||||
New command for plugin
|
||||
"""
|
||||
text = text.strip()
|
||||
name = text.split()[0]
|
||||
if name in self._plugins and self._plugins[name][1]:
|
||||
self._plugins[name][0].command(text[len(name) + 1:])
|
||||
|
||||
def get_menu(self, menu, num):
|
||||
"""
|
||||
Return list of items for menu
|
||||
"""
|
||||
result = []
|
||||
for elem in self._plugins.values():
|
||||
if elem[1]:
|
||||
try:
|
||||
result.extend(elem[0].get_menu(menu, num))
|
||||
except:
|
||||
continue
|
||||
return result
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
App is closing, stop all plugins
|
||||
"""
|
||||
for key in list(self._plugins.keys()):
|
||||
self._plugins[key][0].close()
|
||||
del self._plugins[key]
|
0
src/plugins/__init__.py
Normal file
239
src/plugins/plugin_super_class.py
Normal file
@ -0,0 +1,239 @@
|
||||
import os
|
||||
try:
|
||||
from PySide import QtCore, QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
|
||||
MAX_SHORT_NAME_LENGTH = 5
|
||||
|
||||
LOSSY_FIRST_BYTE = 200
|
||||
|
||||
LOSSLESS_FIRST_BYTE = 160
|
||||
|
||||
|
||||
def path_to_data(name):
|
||||
"""
|
||||
:param name: plugin unique name
|
||||
:return path do plugin's directory
|
||||
"""
|
||||
return os.path.dirname(os.path.realpath(__file__)) + '/' + name + '/'
|
||||
|
||||
|
||||
def log(name, data):
|
||||
"""
|
||||
:param name: plugin unique name
|
||||
:param data: data for saving in log
|
||||
"""
|
||||
with open(path_to_data(name) + 'logs.txt', 'a') as fl:
|
||||
fl.write(bytes(data, 'utf-8') + b'\n')
|
||||
|
||||
|
||||
class 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):
|
||||
"""
|
||||
:param name: plugin full name
|
||||
:param short_name: plugin unique short name (length of short name should not exceed MAX_SHORT_NAME_LENGTH)
|
||||
:param tox: tox instance
|
||||
:param profile: profile instance
|
||||
:param settings: profile settings
|
||||
:param encrypt_save: LibToxEncryptSave instance.
|
||||
"""
|
||||
self._settings = settings
|
||||
self._profile = profile
|
||||
self._tox = tox
|
||||
name = name.strip()
|
||||
short_name = short_name.strip()
|
||||
if not name or not short_name:
|
||||
raise NameError('Wrong name')
|
||||
self._name = name
|
||||
self._short_name = short_name[:MAX_SHORT_NAME_LENGTH]
|
||||
self._translator = None # translator for plugin's GUI
|
||||
self._encrypt_save = encrypt_save
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Get methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_name(self):
|
||||
"""
|
||||
:return plugin full name
|
||||
"""
|
||||
return self._name
|
||||
|
||||
def get_short_name(self):
|
||||
"""
|
||||
:return plugin unique (short) name
|
||||
"""
|
||||
return self._short_name
|
||||
|
||||
def get_description(self):
|
||||
"""
|
||||
Should return plugin description
|
||||
"""
|
||||
return self.__doc__
|
||||
|
||||
def get_menu(self, menu, row_number):
|
||||
"""
|
||||
This method creates items for menu which called on right click in list of friends
|
||||
:param menu: menu instance
|
||||
:param row_number: number of selected row in list of contacts
|
||||
:return list of QAction's
|
||||
"""
|
||||
return []
|
||||
|
||||
def get_window(self):
|
||||
"""
|
||||
This method should return window for plugins with GUI or None
|
||||
"""
|
||||
return None
|
||||
|
||||
def set_tox(self, tox):
|
||||
"""
|
||||
New tox instance
|
||||
"""
|
||||
self._tox = tox
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Plugin was stopped, started or new command received
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
This method called when plugin was started
|
||||
"""
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
This method called when plugin was stopped
|
||||
"""
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
App is closing
|
||||
"""
|
||||
pass
|
||||
|
||||
def command(self, command):
|
||||
"""
|
||||
New command. On 'help' this method should provide user list of available commands
|
||||
:param command: string with command
|
||||
"""
|
||||
if command == 'help':
|
||||
msgbox = QtGui.QMessageBox()
|
||||
title = QtGui.QApplication.translate("PluginWindow", "List of commands for plugin {}", None, QtGui.QApplication.UnicodeUTF8)
|
||||
msgbox.setWindowTitle(title.format(self._name))
|
||||
msgbox.setText(QtGui.QApplication.translate("PluginWindow", "No commands available", None, QtGui.QApplication.UnicodeUTF8))
|
||||
msgbox.exec_()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Translations support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def load_translator(self):
|
||||
"""
|
||||
This method loads translations for GUI
|
||||
"""
|
||||
app = QtGui.QApplication.instance()
|
||||
langs = self._settings.supported_languages()
|
||||
curr_lang = self._settings['language']
|
||||
if curr_lang in langs:
|
||||
if self._translator is not None:
|
||||
app.removeTranslator(self._translator)
|
||||
self._translator = QtCore.QTranslator()
|
||||
lang_path = langs[curr_lang]
|
||||
self._translator.load(path_to_data(self._short_name) + lang_path)
|
||||
app.installTranslator(self._translator)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Settings loading and saving
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def load_settings(self):
|
||||
"""
|
||||
This method loads settings of plugin and returns raw data
|
||||
"""
|
||||
with open(path_to_data(self._short_name) + 'settings.json', 'rb') as fl:
|
||||
data = fl.read()
|
||||
return str(data, 'utf-8')
|
||||
|
||||
def save_settings(self, data):
|
||||
"""
|
||||
This method saves plugin's settings to file
|
||||
:param data: string with data
|
||||
"""
|
||||
with open(path_to_data(self._short_name) + 'settings.json', 'wb') as fl:
|
||||
fl.write(bytes(data, 'utf-8'))
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Callbacks
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def lossless_packet(self, data, friend_number):
|
||||
"""
|
||||
Incoming lossless packet
|
||||
:param data: string with data
|
||||
:param friend_number: number of friend who sent packet
|
||||
"""
|
||||
pass
|
||||
|
||||
def lossy_packet(self, data, friend_number):
|
||||
"""
|
||||
Incoming lossy packet
|
||||
:param data: string with data
|
||||
:param friend_number: number of friend who sent packet
|
||||
"""
|
||||
pass
|
||||
|
||||
def friend_connected(self, friend_number):
|
||||
"""
|
||||
Friend with specified number is online now
|
||||
"""
|
||||
pass
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Custom packets sending
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def send_lossless(self, data, friend_number):
|
||||
"""
|
||||
This method sends lossless packet to friend
|
||||
Wrapper for self._tox.friend_send_lossless_packet
|
||||
Use it instead of direct using self._tox.friend_send_lossless_packet
|
||||
:return True on success
|
||||
"""
|
||||
if data is None:
|
||||
data = ''
|
||||
try:
|
||||
return self._tox.friend_send_lossless_packet(friend_number,
|
||||
bytes([ord(x) for x in
|
||||
chr(len(self._short_name) + LOSSLESS_FIRST_BYTE) +
|
||||
self._short_name + str(data)
|
||||
]))
|
||||
except:
|
||||
return False
|
||||
|
||||
def send_lossy(self, data, friend_number):
|
||||
"""
|
||||
This method sends lossy packet to friend
|
||||
Wrapper for self._tox.friend_send_lossy_packet
|
||||
Use it instead of direct using self._tox.friend_send_lossy_packet
|
||||
:return True on success
|
||||
"""
|
||||
if data is None:
|
||||
data = ''
|
||||
try:
|
||||
return self._tox.friend_send_lossy_packet(friend_number,
|
||||
bytes([ord(x) for x in
|
||||
chr(len(self._short_name) + LOSSY_FIRST_BYTE) +
|
||||
self._short_name + str(data)
|
||||
]))
|
||||
except:
|
||||
return False
|
749
src/profile.py
189
src/settings.py
@ -4,26 +4,36 @@ import os
|
||||
import locale
|
||||
from util import Singleton, curr_directory, log
|
||||
import pyaudio
|
||||
from toxencryptsave import LibToxEncryptSave
|
||||
import smileys
|
||||
|
||||
|
||||
class Settings(Singleton, dict):
|
||||
class Settings(dict, Singleton):
|
||||
"""
|
||||
Settings of current profile + global app settings
|
||||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
Singleton.__init__(self)
|
||||
self.path = ProfileHelper.get_path() + str(name) + '.json'
|
||||
self.name = name
|
||||
if os.path.isfile(self.path):
|
||||
with open(self.path) as fl:
|
||||
with open(self.path, 'rb') as fl:
|
||||
data = fl.read()
|
||||
inst = LibToxEncryptSave.get_instance()
|
||||
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:
|
||||
info = Settings.get_default_settings()
|
||||
log('Parsing settings error: ' + str(ex))
|
||||
super(self.__class__, self).__init__(info)
|
||||
super(Settings, self).__init__(info)
|
||||
self.upgrade()
|
||||
else:
|
||||
super(self.__class__, self).__init__(Settings.get_default_settings())
|
||||
super(Settings, self).__init__(Settings.get_default_settings())
|
||||
self.save()
|
||||
smileys.SmileyLoader(self)
|
||||
p = pyaudio.PyAudio()
|
||||
self.audio = {'input': p.get_default_input_device_info()['index'],
|
||||
'output': p.get_default_output_device_info()['index']}
|
||||
@ -36,17 +46,49 @@ class Settings(Singleton, dict):
|
||||
data = fl.read()
|
||||
auto = json.loads(data)
|
||||
if 'path' in auto and 'name' in auto:
|
||||
return unicode(auto['path']), unicode(auto['name'])
|
||||
return str(auto['path']), str(auto['name'])
|
||||
return '', ''
|
||||
|
||||
@staticmethod
|
||||
def set_auto_profile(path, name):
|
||||
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:
|
||||
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
|
||||
def get_default_settings():
|
||||
"""
|
||||
Default profile settings
|
||||
"""
|
||||
return {
|
||||
'theme': 'default',
|
||||
'ipv6_enabled': True,
|
||||
@ -69,28 +111,45 @@ class Settings(Singleton, dict):
|
||||
'friends_aliases': [],
|
||||
'typing_notifications': False,
|
||||
'calls_sound': True,
|
||||
'blocked': []
|
||||
'blocked': [],
|
||||
'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
|
||||
def supported_languages():
|
||||
return [
|
||||
('English', 'en_EN'),
|
||||
('Russian', 'ru_RU'),
|
||||
('French', 'fr_FR')
|
||||
]
|
||||
return {
|
||||
'English': 'en_EN',
|
||||
'Russian': 'ru_RU',
|
||||
'French': 'fr_FR'
|
||||
}
|
||||
|
||||
def upgrade(self):
|
||||
default = Settings.get_default_settings()
|
||||
for key in default:
|
||||
if key not in self:
|
||||
print key
|
||||
print(key)
|
||||
self[key] = default[key]
|
||||
self.save()
|
||||
|
||||
def save(self):
|
||||
text = json.dumps(self)
|
||||
with open(self.path, 'w') as fl:
|
||||
inst = LibToxEncryptSave.get_instance()
|
||||
if inst.has_password():
|
||||
text = bytes(inst.pass_encrypt(bytes(text, 'utf-8')))
|
||||
else:
|
||||
text = bytes(text, 'utf-8')
|
||||
with open(self.path, 'wb') as fl:
|
||||
fl.write(text)
|
||||
|
||||
def close(self):
|
||||
@ -100,7 +159,7 @@ class Settings(Singleton, dict):
|
||||
data = fl.read()
|
||||
app_settings = json.loads(data)
|
||||
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:
|
||||
pass
|
||||
data = json.dumps(app_settings)
|
||||
@ -108,6 +167,9 @@ class Settings(Singleton, dict):
|
||||
fl.write(data)
|
||||
|
||||
def set_active_profile(self):
|
||||
"""
|
||||
Mark current profile as active
|
||||
"""
|
||||
path = Settings.get_default_path() + 'toxygen.json'
|
||||
if os.path.isfile(path):
|
||||
with open(path) as fl:
|
||||
@ -118,7 +180,7 @@ class Settings(Singleton, dict):
|
||||
if 'active_profile' not in app_settings:
|
||||
app_settings['active_profile'] = []
|
||||
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)
|
||||
with open(path, 'w') as fl:
|
||||
fl.write(data)
|
||||
@ -136,12 +198,51 @@ class Settings(Singleton, dict):
|
||||
return os.getenv('APPDATA') + '/Tox/'
|
||||
|
||||
|
||||
class ProfileHelper(object):
|
||||
class ProfileHelper(Singleton):
|
||||
"""
|
||||
Class with static methods for search, load and save profiles
|
||||
Class with methods for search, load and save profiles
|
||||
"""
|
||||
def __init__(self, path, name):
|
||||
Singleton.__init__(self)
|
||||
self._path = path + name + '.tox'
|
||||
self._directory = path
|
||||
# create /avatars if not exists:
|
||||
directory = path + 'avatars'
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
|
||||
def open_profile(self):
|
||||
with open(self._path, 'rb') as fl:
|
||||
data = fl.read()
|
||||
if data:
|
||||
return data
|
||||
else:
|
||||
raise IOError('Save file has zero size!')
|
||||
|
||||
def get_dir(self):
|
||||
return self._directory
|
||||
|
||||
def save_profile(self, data):
|
||||
inst = LibToxEncryptSave.get_instance()
|
||||
if inst.has_password():
|
||||
data = inst.pass_encrypt(data)
|
||||
with open(self._path, 'wb') as fl:
|
||||
fl.write(data)
|
||||
print('Profile saved successfully')
|
||||
|
||||
def export_profile(self, new_path):
|
||||
new_path += os.path.basename(self._path)
|
||||
with open(self._path, 'rb') as fin:
|
||||
data = fin.read()
|
||||
with open(new_path, 'wb') as fout:
|
||||
fout.write(data)
|
||||
print('Profile exported successfully')
|
||||
|
||||
@staticmethod
|
||||
def find_profiles():
|
||||
"""
|
||||
Find available tox profiles
|
||||
"""
|
||||
path = Settings.get_default_path()
|
||||
result = []
|
||||
# check default path
|
||||
@ -159,52 +260,6 @@ class ProfileHelper(object):
|
||||
result.append((path + '/', name))
|
||||
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']
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def open_profile(path, name):
|
||||
path = path.decode(locale.getpreferredencoding())
|
||||
ProfileHelper._path = path + name + '.tox'
|
||||
ProfileHelper._directory = path
|
||||
# create /avatars if not exists:
|
||||
directory = path + 'avatars'
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
with open(ProfileHelper._path, 'rb') as fl:
|
||||
data = fl.read()
|
||||
if data:
|
||||
return data
|
||||
else:
|
||||
raise IOError('Save file has zero size!')
|
||||
|
||||
@staticmethod
|
||||
def save_profile(data, name=None):
|
||||
if name is not None:
|
||||
ProfileHelper._path = Settings.get_default_path() + name + '.tox'
|
||||
ProfileHelper._directory = Settings.get_default_path()
|
||||
with open(ProfileHelper._path, 'wb') as fl:
|
||||
fl.write(data)
|
||||
|
||||
@staticmethod
|
||||
def export_profile(new_path):
|
||||
new_path += os.path.basename(ProfileHelper._path)
|
||||
with open(ProfileHelper._path, 'rb') as fin:
|
||||
data = fin.read()
|
||||
with open(new_path, 'wb') as fout:
|
||||
fout.write(data)
|
||||
print 'Data exported to: {}'.format(new_path)
|
||||
|
||||
@staticmethod
|
||||
def get_path():
|
||||
return ProfileHelper._directory
|
||||
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 |