Compare commits
116 Commits
Author | SHA1 | Date | |
---|---|---|---|
cc37485fc0 | |||
028de8176b | |||
2e285a8400 | |||
cf912b137d | |||
5019535c0d | |||
1554d9e53a | |||
a984b624b5 | |||
2aea5df33c | |||
1fa13db4e4 | |||
3582722faa | |||
74396834cf | |||
ce84cc526b | |||
98cc288bcd | |||
9b5d768819 | |||
762eb89a46 | |||
b428bd54c4 | |||
f76a1c0fbe | |||
bb2a857ecf | |||
62c5df751d | |||
55a127a820 | |||
32055050ee | |||
a6633f1e77 | |||
23b55522ba | |||
5a5b0e9069 | |||
24c8b18f7e | |||
3ddb7470fc | |||
80b0ea4f0e | |||
6efb1790bb | |||
d5d1e616ba | |||
1ea919bdc2 | |||
65167de1fe | |||
db519e2608 | |||
19893c5c28 | |||
8e6d37e23c | |||
aae71d081f | |||
9c129e925b | |||
87392ea95a | |||
1bbd9a629c | |||
f4d806f5fc | |||
4854b6151d | |||
c755b4a52a | |||
7505b06ddf | |||
ace663804e | |||
2ff41313f8 | |||
1e1772e306 | |||
300b28bdfa | |||
1f4e81af35 | |||
335d646c42 | |||
b6f5123495 | |||
fbe0b1f819 | |||
000a4c7920 | |||
262714d3ee | |||
d06982b38a | |||
c21e39b158 | |||
8d0426f775 | |||
2c031fce3f | |||
6e1b8a9f17 | |||
4b85401adf | |||
5932d8cb84 | |||
adf6cefd1f | |||
142255ccc8 | |||
1b6b8e043a | |||
1b4c211c1d | |||
43c71ec1a5 | |||
4d4fd21fe9 | |||
6cbacef95b | |||
7a817eb82a | |||
49fc253c19 | |||
df5a1a901a | |||
54a2da4670 | |||
361f1f0e29 | |||
9031a4a3e3 | |||
0a378c1682 | |||
8bc4613407 | |||
ec6c04a7df | |||
769119c795 | |||
d1e90c6aef | |||
6d705deb55 | |||
464fba23c5 | |||
c60808a7da | |||
a2273e8c27 | |||
a20a00130d | |||
d0e2f61d03 | |||
8ea1a77186 | |||
bf1bea1e93 | |||
124decc34a | |||
89caef6905 | |||
9118e01775 | |||
138135b9e9 | |||
2863eb790d | |||
06e8c79b3f | |||
81695737cd | |||
ac07cb529f | |||
4f77e2c105 | |||
47ce9252b7 | |||
9153836ead | |||
f8a7087779 | |||
01546f0047 | |||
d8dd16e865 | |||
19fb905554 | |||
3ef581bc5d | |||
f897c7ce8d | |||
ba390eda91 | |||
5fea3e918d | |||
7cc404ce52 | |||
8a502b4082 | |||
b83ea6be18 | |||
85554eacd1 | |||
8bbefff6c7 | |||
019165aeac | |||
0cfb8efefa | |||
b227ed627a | |||
f41b5e5c97 | |||
bc9ec04171 | |||
05e4184c5d | |||
3194099f59 |
3
.gitignore
vendored
@ -6,6 +6,7 @@ tests/tests
|
|||||||
tests/libs
|
tests/libs
|
||||||
tests/.cache
|
tests/.cache
|
||||||
tests/__pycache__
|
tests/__pycache__
|
||||||
|
tests/avatars
|
||||||
toxygen/libs
|
toxygen/libs
|
||||||
.idea
|
.idea
|
||||||
*~
|
*~
|
||||||
@ -23,4 +24,4 @@ toxygen/__pycache__
|
|||||||
html
|
html
|
||||||
Toxygen.egg-info
|
Toxygen.egg-info
|
||||||
*.tox
|
*.tox
|
||||||
|
.cache
|
||||||
|
20
.travis.yml
@ -1,16 +1,26 @@
|
|||||||
language: python
|
language: python
|
||||||
python:
|
python:
|
||||||
- "3.4"
|
- "3.5"
|
||||||
|
- "3.6"
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
dist: trusty
|
||||||
|
notifications:
|
||||||
|
email: false
|
||||||
before_install:
|
before_install:
|
||||||
|
- sudo apt-get update
|
||||||
- sudo apt-get install -y checkinstall build-essential
|
- sudo apt-get install -y checkinstall build-essential
|
||||||
- sudo apt-get install portaudio19-dev
|
- sudo apt-get install portaudio19-dev
|
||||||
|
- sudo apt-get install libsecret-1-dev
|
||||||
|
|
||||||
- sudo apt-get install libconfig-dev libvpx-dev check -qq
|
- sudo apt-get install libconfig-dev libvpx-dev check -qq
|
||||||
install:
|
install:
|
||||||
- pip install PySide --no-index --find-links https://parkin.github.io/python-wheelhouse/;
|
- pip3 install sip
|
||||||
- python ~/virtualenv/python${TRAVIS_PYTHON_VERSION}/bin/pyside_postinstall.py -install
|
- pip3 install pyaudio
|
||||||
- pip install pyaudio
|
- pip3 install pyqt5==5.14
|
||||||
|
- pip3 install opencv-python
|
||||||
before_script:
|
before_script:
|
||||||
# OPUS
|
# Opus
|
||||||
- wget http://downloads.xiph.org/releases/opus/opus-1.0.3.tar.gz
|
- wget http://downloads.xiph.org/releases/opus/opus-1.0.3.tar.gz
|
||||||
- tar xzf opus-1.0.3.tar.gz
|
- tar xzf opus-1.0.3.tar.gz
|
||||||
- cd opus-1.0.3
|
- cd opus-1.0.3
|
||||||
|
@ -12,9 +12,8 @@ include toxygen/smileys/starwars/*.png
|
|||||||
include toxygen/smileys/starwars/config.json
|
include toxygen/smileys/starwars/config.json
|
||||||
include toxygen/smileys/ksk/*.png
|
include toxygen/smileys/ksk/*.png
|
||||||
include toxygen/smileys/ksk/config.json
|
include toxygen/smileys/ksk/config.json
|
||||||
include toxygen/styles/style.qss
|
include toxygen/styles/*.qss
|
||||||
include toxygen/translations/*.qm
|
include toxygen/translations/*.qm
|
||||||
include toxygen/libs/libtox.dll
|
include toxygen/libs/libtox.dll
|
||||||
include toxygen/libs/libsodium.a
|
include toxygen/libs/libsodium.a
|
||||||
include toxygen/libs/libtox64.dll
|
include toxygen/nodes.json
|
||||||
include toxygen/libs/libsodium64.a
|
|
||||||
|
70
README.md
@ -2,49 +2,47 @@
|
|||||||
|
|
||||||
Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pure Python3.
|
Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pure Python3.
|
||||||
|
|
||||||
[](https://github.com/toxygen-project/toxygen/releases/latest)
|
[](https://github.com/toxygen-project/toxygen/releases/latest)
|
||||||
[](https://github.com/toxygen-project/toxygen/stargazers)
|
[](https://github.com/toxygen-project/toxygen/stargazers)
|
||||||
[](https://github.com/toxygen-project/toxygen/issues)
|
[](https://github.com/toxygen-project/toxygen/issues)
|
||||||
[](https://raw.githubusercontent.com/toxygen-project/toxygen/master/LICENSE.md)
|
[](https://raw.githubusercontent.com/toxygen-project/toxygen/master/LICENSE.md)
|
||||||
[](https://travis-ci.org/toxygen-project/toxygen)
|
[](https://travis-ci.org/toxygen-project/toxygen)
|
||||||
|
|
||||||
### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md) - [Compile](/docs/compile.md) - [Contact](/docs/contact.md)
|
### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md) - [Compile](/docs/compile.md) - [Contact](/docs/contact.md) - [Updater](https://github.com/toxygen-project/toxygen_updater)
|
||||||
|
|
||||||
### Supported OS:
|
### Supported OS: Linux and Windows
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Features:
|
### Features:
|
||||||
|
|
||||||
- [x] 1v1 messages
|
- 1v1 messages
|
||||||
- [x] File transfers
|
- File transfers
|
||||||
- [x] Audio
|
- Audio calls
|
||||||
- [x] Plugins support
|
- Video calls
|
||||||
- [x] Chat history
|
- Group chats
|
||||||
- [x] Emoticons
|
- Plugins support
|
||||||
- [x] Stickers
|
- Desktop sharing
|
||||||
- [x] Screenshots
|
- Chat history
|
||||||
- [x] Name lookups (toxme.io support)
|
- Emoticons
|
||||||
- [x] Save file encryption
|
- Stickers
|
||||||
- [x] Profile import and export
|
- Screenshots
|
||||||
- [x] Faux offline messaging
|
- Name lookups (toxme.io support)
|
||||||
- [x] Faux offline file transfers
|
- Save file encryption
|
||||||
- [x] Inline images
|
- Profile import and export
|
||||||
- [x] Message splitting
|
- Faux offline messaging
|
||||||
- [x] Proxy support
|
- Faux offline file transfers
|
||||||
- [x] Avatars
|
- Inline images
|
||||||
- [x] Multiprofile
|
- Message splitting
|
||||||
- [x] Multilingual
|
- Proxy support
|
||||||
- [x] Sound notifications
|
- Avatars
|
||||||
- [x] Contact aliases
|
- Multiprofile
|
||||||
- [x] Contact blocking
|
- Multilingual
|
||||||
- [x] Typing notifications
|
- Sound notifications
|
||||||
- [x] Changing nospam
|
- Contact aliases
|
||||||
- [x] File resuming
|
- Contact blocking
|
||||||
- [x] Read receipts
|
- Typing notifications
|
||||||
- [ ] Video
|
- Changing nospam
|
||||||
- [ ] Desktop sharing
|
- File resuming
|
||||||
- [ ] Group chats
|
- Read receipts
|
||||||
|
|
||||||
### Downloads
|
### Downloads
|
||||||
[Releases](https://github.com/toxygen-project/toxygen/releases)
|
[Releases](https://github.com/toxygen-project/toxygen/releases)
|
||||||
|
@ -5,6 +5,7 @@ You can compile Toxygen using [PyInstaller](http://www.pyinstaller.org/)
|
|||||||
Install PyInstaller:
|
Install PyInstaller:
|
||||||
``pip3 install pyinstaller``
|
``pip3 install pyinstaller``
|
||||||
|
|
||||||
|
Compile Toxygen:
|
||||||
``pyinstaller --windowed --icon images/icon.ico main.py``
|
``pyinstaller --windowed --icon images/icon.ico main.py``
|
||||||
|
|
||||||
Don't forget to copy /images/, /sounds/, /translations/, /styles/, /smileys/, /stickers/, /plugins/ (and /libs/libtox.dll, /libs/libsodium.a on Windows) to /dist/main/
|
Don't forget to copy /images/, /sounds/, /translations/, /styles/, /smileys/, /stickers/, /plugins/ (and /libs/libtox.dll, /libs/libsodium.a on Windows) to /dist/main/
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
#Issues
|
# Issues
|
||||||
|
|
||||||
Help us find all bugs in Toxygen! Please provide following info:
|
Help us find all bugs in Toxygen! Please provide following info:
|
||||||
|
|
||||||
- OS
|
- OS
|
||||||
- Toxygen version
|
- Toxygen version
|
||||||
- Toxygen executable info - .py or precompiled binary
|
- Toxygen executable info - python executable (.py), precompiled binary, from package etc.
|
||||||
- Steps to reproduce the bug
|
- Steps to reproduce the bug
|
||||||
|
|
||||||
Want to see new feature in Toxygen? [Ask for it!](https://github.com/xveduk/toxygen/issues)
|
Want to see new feature in Toxygen? [Ask for it!](https://github.com/toxygen-project/toxygen/issues)
|
||||||
|
|
||||||
#Pull requests
|
# Pull requests
|
||||||
|
|
||||||
Developer? Feel free to open pull request. Our dev team is small so we glad to get help.
|
Developer? Feel free to open pull request. Our dev team is small so we glad to get help.
|
||||||
Don't know what to do? Improve UI, fix [issues](https://github.com/xveduk/toxygen/issues) or implement features from our TODO list.
|
Don't know what to do? Improve UI, fix [issues](https://github.com/toxygen-project/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.
|
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
|
Note that we have a lot of branches for different purposes. Master branch is for stable versions (releases) only, so I recommend to open PR's to develop branch. Development of next Toxygen version usually goes there. Other branches used for implementing different tasks such as file transfers improvements or audio calls implementation etc.
|
||||||
|
|
||||||
Help us translate Toxygen! Translation can be created using pyside-lupdate (``pyside-lupdate toxygen.pro``) and QT Linguist.
|
# Translations
|
||||||
|
|
||||||
|
Help us translate Toxygen! Translation can be created using pylupdate (``pylupdate5 toxygen.pro``) and QT Linguist.
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
# How to install Toxygen
|
# How to install Toxygen
|
||||||
|
|
||||||
## Use precompiled binary:
|
## Use precompiled binary (recommended for users):
|
||||||
[Check our releases page](https://github.com/xveduk/toxygen/releases)
|
[Check our releases page](https://github.com/toxygen-project/toxygen/releases)
|
||||||
|
|
||||||
## Using pip3
|
## Using pip3
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
``pip3.4 install toxygen``
|
``pip install toxygen``
|
||||||
|
|
||||||
Run app using ``toxygen`` command.
|
Run app using ``toxygen`` command.
|
||||||
|
|
||||||
@ -16,19 +16,11 @@ Run app using ``toxygen`` command.
|
|||||||
1. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system (install in /usr/lib/)
|
1. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system (install in /usr/lib/)
|
||||||
2. Install PortAudio:
|
2. Install PortAudio:
|
||||||
``sudo apt-get install portaudio19-dev``
|
``sudo apt-get install portaudio19-dev``
|
||||||
3. Install PySide: ``sudo apt-get install python3-pyside``
|
3. For 32-bit Linux install PyQt5: ``sudo apt-get install python3-pyqt5``
|
||||||
4. Install toxygen:
|
4. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo pip3 install opencv-python``
|
||||||
``sudo pip3.4 install toxygen``
|
5. Install toxygen:
|
||||||
5. Run toxygen using ``toxygen`` command.
|
``sudo pip3 install toxygen``
|
||||||
|
6. Run toxygen using ``toxygen`` command.
|
||||||
### OS X
|
|
||||||
|
|
||||||
1. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system
|
|
||||||
2. Install PortAudio:
|
|
||||||
``brew install portaudio``
|
|
||||||
3. Install toxygen:
|
|
||||||
``pip3.4 install toxygen``
|
|
||||||
4. Run toxygen using ``toxygen`` command.
|
|
||||||
|
|
||||||
## Packages
|
## Packages
|
||||||
|
|
||||||
@ -40,15 +32,19 @@ Debian/Ubuntu: [tox.chat](https://tox.chat/download.html#gnulinux)
|
|||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
1. [Download and install latest Python 3.4](https://www.python.org/downloads/windows/)
|
Note: 32-bit Python isn't supported due to bug with videocalls. It is strictly recommended to use 64-bit Python.
|
||||||
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4#installing-pyside-on-a-windows-system) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
|
|
||||||
3. Install PyAudio: ``pip3.4 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\
|
|
||||||
7. Run \toxygen\main.py.
|
|
||||||
|
|
||||||
Optional: install toxygen using setup.py: ``python3.4 setup.py install``
|
1. [Download and install latest Python 3 64-bit](https://www.python.org/downloads/windows/)
|
||||||
|
2. Install PyQt5: ``pip install pyqt5``
|
||||||
|
3. Install PyAudio: ``pip install pyaudio``
|
||||||
|
4. Install numpy: ``pip install numpy``
|
||||||
|
5. Install OpenCV: ``pip install opencv-python``
|
||||||
|
6. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip)
|
||||||
|
7. Unpack archive
|
||||||
|
8. Download latest libtox.dll build, download latest libsodium.a build, put it into \toxygen\libs\
|
||||||
|
9. Run \toxygen\main.py.
|
||||||
|
|
||||||
|
Optional: install toxygen using setup.py: ``python setup.py install``
|
||||||
|
|
||||||
[libtox.dll for 32-bit Python](https://build.tox.chat/view/libtoxcore/job/libtoxcore_build_windows_x86_shared_release/lastSuccessfulBuild/artifact/libtoxcore_build_windows_x86_shared_release.zip)
|
[libtox.dll for 32-bit Python](https://build.tox.chat/view/libtoxcore/job/libtoxcore_build_windows_x86_shared_release/lastSuccessfulBuild/artifact/libtoxcore_build_windows_x86_shared_release.zip)
|
||||||
|
|
||||||
@ -62,27 +58,15 @@ Optional: install toxygen using setup.py: ``python3.4 setup.py install``
|
|||||||
|
|
||||||
1. Install latest Python3:
|
1. Install latest Python3:
|
||||||
``sudo apt-get install python3``
|
``sudo apt-get install python3``
|
||||||
2. Install PySide: ``sudo apt-get install python3-pyside`` or install [PyQt4](https://riverbankcomputing.com/software/pyqt/download) (``sudo apt-get install python3-pyqt4``).
|
2. Install PyQt5: ``sudo apt-get install python3-pyqt5`` or ``sudo pip3 install pyqt5``
|
||||||
3. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system (install in /usr/lib/)
|
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:
|
4. Install PyAudio:
|
||||||
``sudo apt-get install portaudio19-dev`` and ``sudo apt-get install python3-pyaudio`` (or ``pip3 install pyaudio``)
|
``sudo apt-get install portaudio19-dev`` and ``sudo apt-get install python3-pyaudio`` (or ``sudo pip3 install pyaudio``)
|
||||||
5. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
|
5. Install NumPy: ``sudo pip3 install numpy``
|
||||||
6. Unpack archive
|
6. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo pip3 install opencv-python``
|
||||||
7. Run app:
|
7. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip)
|
||||||
``python3.4 main.py``
|
8. Unpack archive
|
||||||
|
9. Run app:
|
||||||
Optional: install toxygen using setup.py: ``python3.4 setup.py install``
|
``python3 main.py``
|
||||||
|
|
||||||
### OS X
|
|
||||||
|
|
||||||
1. [Download and install latest Python 3.4](https://www.python.org/downloads/mac-osx/)
|
|
||||||
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4#installing-pyside-on-a-mac-os-x-system) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
|
|
||||||
3. Install PortAudio:
|
|
||||||
``brew install portaudio``
|
|
||||||
4. Install PyAudio: ``pip3 install pyaudio``
|
|
||||||
5. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system
|
|
||||||
6. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
|
|
||||||
7. Unpack archive
|
|
||||||
8. Run \toxygen\main.py.
|
|
||||||
|
|
||||||
Optional: install toxygen using setup.py: ``python3 setup.py install``
|
Optional: install toxygen using setup.py: ``python3 setup.py install``
|
||||||
|
BIN
docs/os.png
Before Width: | Height: | Size: 28 KiB |
@ -1,6 +1,6 @@
|
|||||||
#Plugins API
|
# Plugins API
|
||||||
|
|
||||||
In Toxygen plugin is single python (supported Python 3.0 - 3.4) module (.py file) and directory with data associated with it.
|
In Toxygen plugin is single python (supported Python 3.4 - 3.6) module (.py file) and directory with data associated with it.
|
||||||
Every module must contain one class derived from PluginSuperClass defined in [plugin_super_class.py](/src/plugins/plugin_super_class.py). Instance of this class will be created by PluginLoader class (defined in [plugin_support.py](/src/plugin_support.py) ). This class can enable/disable plugins and send data to it.
|
Every module must contain one class derived from PluginSuperClass defined in [plugin_super_class.py](/src/plugins/plugin_super_class.py). Instance of this class will be created by PluginLoader class (defined in [plugin_support.py](/src/plugin_support.py) ). This class can enable/disable plugins and send data to it.
|
||||||
|
|
||||||
Every plugin has its own full name and unique short name (1-5 symbols). Main app can get it using special methods.
|
Every plugin has its own full name and unique short name (1-5 symbols). Main app can get it using special methods.
|
||||||
@ -18,7 +18,7 @@ All plugin's data should be stored in following structure:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Plugin MUST override:
|
Plugin MUST override:
|
||||||
- __init__ with params: tox (Tox instance), profile (Profile instance), settings (Settings instance), encrypt_save (ToxEncryptSave instance). Call super().__init__ with params plugin_full_name, plugin_short_name, tox, profile, settings, encrypt_save.
|
- __init__ with params: tox (Tox instance), profile (Profile instance), settings (Settings instance), encrypt_save (ToxES instance). Call super().__init__ with params plugin_full_name, plugin_short_name, tox, profile, settings, encrypt_save.
|
||||||
|
|
||||||
Plugin can override following methods:
|
Plugin can override following methods:
|
||||||
- get_description - this method should return plugin description.
|
- get_description - this method should return plugin description.
|
||||||
@ -45,13 +45,13 @@ Import statement will not work in case you import module that wasn't previously
|
|||||||
|
|
||||||
About GUI:
|
About GUI:
|
||||||
|
|
||||||
It's strictly recommended to support both PySide and PyQt4 in GUI. Plugin can have no GUI at all.
|
GUI is available via PyQt5. Plugin can have no GUI at all.
|
||||||
|
|
||||||
Exceptions:
|
Exceptions:
|
||||||
|
|
||||||
Plugin's methods MUST NOT raise exceptions.
|
Plugin's methods MUST NOT raise exceptions.
|
||||||
|
|
||||||
#Examples
|
# Examples
|
||||||
|
|
||||||
You can find examples in [official repo](https://github.com/ingvar1995/toxygen_plugins)
|
You can find examples in [official repo](https://github.com/toxygen-project/toxygen_plugins)
|
||||||
|
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
#Plugins
|
# 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.
|
Toxygen is the first [Tox](https://tox.chat/) client with plugins support. Plugin is Python 3.5 - 3.6 module (.py file) and directory with plugin's data which provide some additional functionality.
|
||||||
|
|
||||||
#How to write plugin
|
# How to write plugin
|
||||||
|
|
||||||
Check [Plugin API](/docs/plugin_api.md) for more info
|
Check [Plugin API](/docs/plugin_api.md) for more info
|
||||||
|
|
||||||
#How to install plugin
|
# How to install plugin
|
||||||
|
|
||||||
Toxygen comes without preinstalled plugins.
|
Toxygen comes without preinstalled plugins.
|
||||||
|
|
||||||
1. Put plugin and directory with its data into /src/plugins/ or import it via GUI (In menu: Plugins -> Import plugin)
|
1. Put plugin and directory with its data into /src/plugins/ or import it via GUI (In menu: Plugins => Import plugin)
|
||||||
2. Restart Toxygen
|
2. Restart Toxygen or choose Plugins => Reload plugins in menu.
|
||||||
|
|
||||||
##Note: /src/plugins/ should contain plugin_super_class.py and __init__.py
|
## Note: /src/plugins/ should contain plugin_super_class.py and __init__.py
|
||||||
|
|
||||||
#Plugins list
|
# Plugins list
|
||||||
|
|
||||||
WARNING: It is unsecure to install plugin not from this list!
|
WARNING: It is unsecure to install plugin not from this list!
|
||||||
|
|
||||||
[Main repo](https://github.com/ingvar1995/toxygen_plugins)
|
[Main repo](https://github.com/toxygen-project/toxygen_plugins)
|
@ -1,4 +1,4 @@
|
|||||||
#Smileys
|
# 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:
|
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:
|
||||||
|
|
||||||
@ -6,8 +6,8 @@ Toxygen support smileys. Smiley is small picture which replaces some symbol or c
|
|||||||
|
|
||||||
Animated smileys (.gif) are supported too.
|
Animated smileys (.gif) are supported too.
|
||||||
|
|
||||||
#Stickers
|
# Stickers
|
||||||
|
|
||||||
Sticker is inline image. If you want to create your own smiley pack, create directory in src/stickers/ and place your stickers there.
|
Sticker is inline image. If you want to create your own sticker pack, create directory in src/stickers/ and place your stickers there.
|
||||||
|
|
||||||
Users can import plugins and stickers packs using menu: Settings -> Interface
|
Users can import smileys and stickers using menu: Settings -> Interface
|
||||||
|
35
setup.py
@ -8,15 +8,27 @@ import sys
|
|||||||
|
|
||||||
version = program_version + '.0'
|
version = program_version + '.0'
|
||||||
|
|
||||||
MODULES = []
|
|
||||||
|
|
||||||
if system() in ('Windows', 'Darwin'):
|
if system() == 'Windows':
|
||||||
MODULES = ['PyAudio', 'PySide']
|
MODULES = ['PyQt5', 'PyAudio', 'numpy', 'opencv-python']
|
||||||
else:
|
else:
|
||||||
|
MODULES = []
|
||||||
try:
|
try:
|
||||||
import pyaudio
|
import pyaudio
|
||||||
except ImportError:
|
except ImportError:
|
||||||
MODULES = ['PyAudio']
|
MODULES.append('PyAudio')
|
||||||
|
try:
|
||||||
|
import PyQt5
|
||||||
|
except ImportError:
|
||||||
|
MODULES.append('PyQt5')
|
||||||
|
try:
|
||||||
|
import numpy
|
||||||
|
except ImportError:
|
||||||
|
MODULES.append('numpy')
|
||||||
|
try:
|
||||||
|
import cv2
|
||||||
|
except ImportError:
|
||||||
|
MODULES.append('opencv-python')
|
||||||
|
|
||||||
|
|
||||||
class InstallScript(install):
|
class InstallScript(install):
|
||||||
@ -25,9 +37,7 @@ class InstallScript(install):
|
|||||||
def run(self):
|
def run(self):
|
||||||
install.run(self)
|
install.run(self)
|
||||||
try:
|
try:
|
||||||
if system() == 'Windows':
|
if system() != 'Windows':
|
||||||
call(["toxygen", "--configure"])
|
|
||||||
else:
|
|
||||||
call(["toxygen", "--clean"])
|
call(["toxygen", "--clean"])
|
||||||
except:
|
except:
|
||||||
try:
|
try:
|
||||||
@ -37,13 +47,12 @@ class InstallScript(install):
|
|||||||
if path[-1] not in ('/', '\\'):
|
if path[-1] not in ('/', '\\'):
|
||||||
path += '/'
|
path += '/'
|
||||||
path += 'bin/toxygen'
|
path += 'bin/toxygen'
|
||||||
if system() == 'Windows':
|
if system() != 'Windows':
|
||||||
call([path, "--configure"])
|
|
||||||
else:
|
|
||||||
call([path, "--clean"])
|
call([path, "--clean"])
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
setup(name='Toxygen',
|
setup(name='Toxygen',
|
||||||
version=version,
|
version=version,
|
||||||
description='Toxygen - Tox client',
|
description='Toxygen - Tox client',
|
||||||
@ -58,12 +67,12 @@ setup(name='Toxygen',
|
|||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Programming Language :: Python :: 3 :: Only',
|
'Programming Language :: Python :: 3 :: Only',
|
||||||
'Programming Language :: Python :: 3.4',
|
'Programming Language :: Python :: 3.5',
|
||||||
|
'Programming Language :: Python :: 3.6',
|
||||||
],
|
],
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': ['toxygen=toxygen.main:main'],
|
'console_scripts': ['toxygen=toxygen.main:main'],
|
||||||
},
|
},
|
||||||
cmdclass={
|
cmdclass={
|
||||||
'install': InstallScript,
|
'install': InstallScript,
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
117
tests/tests.py
@ -1,7 +1,10 @@
|
|||||||
from toxygen.profile import *
|
from toxygen.profile import *
|
||||||
from toxygen.tox_dns import tox_dns
|
from toxygen.tox_dns import tox_dns
|
||||||
|
from toxygen.history import History
|
||||||
|
from toxygen.smileys import SmileyLoader
|
||||||
|
from toxygen.messages import *
|
||||||
import toxygen.toxes as encr
|
import toxygen.toxes as encr
|
||||||
import toxygen.messages as m
|
import toxygen.util as util
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
@ -33,21 +36,6 @@ class TestProfileHelper:
|
|||||||
assert os.path.exists(path + 'avatars/')
|
assert os.path.exists(path + 'avatars/')
|
||||||
|
|
||||||
|
|
||||||
class TestDNS:
|
|
||||||
|
|
||||||
def test_dns(self):
|
|
||||||
Settings._instance = Settings.get_default_settings()
|
|
||||||
bot_id = '56A1ADE4B65B86BCD51CC73E2CD4E542179F47959FE3E0E21B4B0ACDADE51855D34D34D37CB5'
|
|
||||||
tox_id = tox_dns('groupbot@toxme.io')
|
|
||||||
assert tox_id == bot_id
|
|
||||||
|
|
||||||
def test_dns2(self):
|
|
||||||
Settings._instance = Settings.get_default_settings()
|
|
||||||
bot_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6'
|
|
||||||
tox_id = tox_dns('echobot@toxme.io')
|
|
||||||
assert tox_id == bot_id
|
|
||||||
|
|
||||||
|
|
||||||
class TestEncryption:
|
class TestEncryption:
|
||||||
|
|
||||||
def test_encr_decr(self):
|
def test_encr_decr(self):
|
||||||
@ -58,47 +46,68 @@ class TestEncryption:
|
|||||||
lib.set_password(password)
|
lib.set_password(password)
|
||||||
copy_data = data[:]
|
copy_data = data[:]
|
||||||
new_data = lib.pass_encrypt(data)
|
new_data = lib.pass_encrypt(data)
|
||||||
|
assert lib.is_data_encrypted(new_data)
|
||||||
new_data = lib.pass_decrypt(new_data)
|
new_data = lib.pass_decrypt(new_data)
|
||||||
assert copy_data == new_data
|
assert copy_data == new_data
|
||||||
|
|
||||||
|
|
||||||
class TestFriend:
|
class TestSmileys:
|
||||||
|
|
||||||
def create_singletons(self):
|
def test_loading(self):
|
||||||
|
settings = {'smiley_pack': 'default', 'smileys': True}
|
||||||
|
sm = SmileyLoader(settings)
|
||||||
|
assert sm.get_smileys_path() is not None
|
||||||
|
l = sm.get_packs_list()
|
||||||
|
assert len(l) == 4
|
||||||
|
|
||||||
|
|
||||||
|
def create_singletons():
|
||||||
|
folder = util.curr_directory() + '/abc'
|
||||||
Settings._instance = Settings.get_default_settings()
|
Settings._instance = Settings.get_default_settings()
|
||||||
ProfileHelper('abc', 'test')
|
if not os.path.exists(folder):
|
||||||
|
os.makedirs(folder)
|
||||||
|
ProfileHelper(folder, 'test')
|
||||||
|
|
||||||
def create_friend(self, name, status_message, number, tox_id):
|
|
||||||
|
def create_friend(name, status_message, number, tox_id):
|
||||||
friend = Friend(None, number, name, status_message, None, tox_id)
|
friend = Friend(None, number, name, status_message, None, tox_id)
|
||||||
return friend
|
return friend
|
||||||
|
|
||||||
def test_friend_creation(self):
|
|
||||||
self.create_singletons()
|
def create_random_friend():
|
||||||
name, status_message, number = 'Friend', 'I am friend!', 0
|
name, status_message, number = 'Friend', 'I am friend!', 0
|
||||||
tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6'
|
tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6'
|
||||||
friend = self.create_friend(name, status_message, number, tox_id)
|
friend = create_friend(name, status_message, number, tox_id)
|
||||||
|
return friend
|
||||||
|
|
||||||
|
|
||||||
|
class TestFriend:
|
||||||
|
|
||||||
|
def test_friend_creation(self):
|
||||||
|
create_singletons()
|
||||||
|
name, status_message, number = 'Friend', 'I am friend!', 0
|
||||||
|
tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6'
|
||||||
|
friend = create_friend(name, status_message, number, tox_id)
|
||||||
assert friend.name == name
|
assert friend.name == name
|
||||||
assert friend.tox_id == tox_id
|
assert friend.tox_id == tox_id
|
||||||
assert friend.status_message == status_message
|
assert friend.status_message == status_message
|
||||||
assert friend.number == number
|
assert friend.number == number
|
||||||
|
|
||||||
def test_friend_corr(self):
|
def test_friend_corr(self):
|
||||||
self.create_singletons()
|
create_singletons()
|
||||||
name, status_message, number = 'Friend', 'I am friend!', 0
|
friend = create_random_friend()
|
||||||
tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6'
|
|
||||||
friend = self.create_friend(name, status_message, number, tox_id)
|
|
||||||
t = time.time()
|
t = time.time()
|
||||||
friend.append_message(m.InfoMessage('Info message', t))
|
friend.append_message(InfoMessage('Info message', t))
|
||||||
friend.append_message(m.TextMessage('Hello! It is test!', MESSAGE_OWNER['ME'], t + 0.001, 0))
|
friend.append_message(TextMessage('Hello! It is test!', MESSAGE_OWNER['ME'], t + 0.001, 0))
|
||||||
friend.append_message(m.TextMessage('Hello!', MESSAGE_OWNER['FRIEND'], t + 0.002, 0))
|
friend.append_message(TextMessage('Hello!', MESSAGE_OWNER['FRIEND'], t + 0.002, 0))
|
||||||
assert friend.get_last_message_text() == 'Hello! It is test!'
|
assert friend.get_last_message_text() == 'Hello! It is test!'
|
||||||
assert len(friend.get_corr()) == 3
|
assert len(friend.get_corr()) == 3
|
||||||
assert len(friend.get_corr_for_saving()) == 2
|
assert len(friend.get_corr_for_saving()) == 2
|
||||||
friend.append_message(m.TextMessage('Not sent', MESSAGE_OWNER['NOT_SENT'], t + 0.002, 0))
|
friend.append_message(TextMessage('Not sent', MESSAGE_OWNER['NOT_SENT'], t + 0.002, 0))
|
||||||
arr = friend.get_unsent_messages_for_saving()
|
arr = friend.get_unsent_messages_for_saving()
|
||||||
assert len(arr) == 1
|
assert len(arr) == 1
|
||||||
assert arr[0][0] == 'Not sent'
|
assert arr[0][0] == 'Not sent'
|
||||||
tm = m.TransferMessage(MESSAGE_OWNER['FRIEND'],
|
tm = TransferMessage(MESSAGE_OWNER['FRIEND'],
|
||||||
time.time(),
|
time.time(),
|
||||||
TOX_FILE_TRANSFER_STATE['RUNNING'],
|
TOX_FILE_TRANSFER_STATE['RUNNING'],
|
||||||
100, 'file_name', friend.number, 0)
|
100, 'file_name', friend.number, 0)
|
||||||
@ -106,8 +115,48 @@ class TestFriend:
|
|||||||
friend.clear_corr()
|
friend.clear_corr()
|
||||||
assert len(friend.get_corr()) == 1
|
assert len(friend.get_corr()) == 1
|
||||||
assert len(friend.get_corr_for_saving()) == 0
|
assert len(friend.get_corr_for_saving()) == 0
|
||||||
friend.append_message(m.TextMessage('Hello! It is test!', MESSAGE_OWNER['ME'], t, 0))
|
friend.append_message(TextMessage('Hello! It is test!', MESSAGE_OWNER['ME'], t, 0))
|
||||||
assert len(friend.get_corr()) == 2
|
assert len(friend.get_corr()) == 2
|
||||||
assert len(friend.get_corr_for_saving()) == 1
|
assert len(friend.get_corr_for_saving()) == 1
|
||||||
|
|
||||||
# TODO: more friend tests and history test
|
def test_history_search(self):
|
||||||
|
create_singletons()
|
||||||
|
friend = create_random_friend()
|
||||||
|
message = 'Hello! It is test!'
|
||||||
|
friend.append_message(TextMessage(message, MESSAGE_OWNER['ME'], time.time(), 0))
|
||||||
|
last_message = friend.get_last_message_text()
|
||||||
|
assert last_message == message
|
||||||
|
result = friend.search_string('e[m|s]')
|
||||||
|
assert result is not None
|
||||||
|
result = friend.search_string('tox')
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
|
||||||
|
class TestHistory:
|
||||||
|
|
||||||
|
def test_history(self):
|
||||||
|
create_singletons()
|
||||||
|
db_name = 'my_name'
|
||||||
|
name, status_message, number = 'Friend', 'I am friend!', 0
|
||||||
|
tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6'
|
||||||
|
friend = create_friend(name, status_message, number, tox_id)
|
||||||
|
history = History(db_name)
|
||||||
|
history.add_friend_to_db(friend.tox_id)
|
||||||
|
assert history.friend_exists_in_db(friend.tox_id)
|
||||||
|
text_message = 'Test!'
|
||||||
|
t = time.time()
|
||||||
|
friend.append_message(TextMessage(text_message, MESSAGE_OWNER['ME'], t, 0))
|
||||||
|
messages = friend.get_corr_for_saving()
|
||||||
|
history.save_messages_to_db(friend.tox_id, messages)
|
||||||
|
getter = history.messages_getter(friend.tox_id)
|
||||||
|
messages = getter.get_all()
|
||||||
|
assert len(messages) == 1
|
||||||
|
assert messages[0][0] == text_message
|
||||||
|
assert messages[0][1] == MESSAGE_OWNER['ME']
|
||||||
|
assert messages[0][-1] == 0
|
||||||
|
history.delete_message(friend.tox_id, t)
|
||||||
|
getter = history.messages_getter(friend.tox_id)
|
||||||
|
messages = getter.get_all()
|
||||||
|
assert len(messages) == 0
|
||||||
|
history.delete_friend_from_db(friend.tox_id)
|
||||||
|
assert not history.friend_exists_in_db(friend.tox_id)
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
try:
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
from PySide import QtCore, QtGui
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
import widgets
|
import widgets
|
||||||
import profile
|
import profile
|
||||||
import util
|
import util
|
||||||
@ -17,11 +14,12 @@ class IncomingCallWidget(widgets.CenteredWidget):
|
|||||||
super(IncomingCallWidget, self).__init__()
|
super(IncomingCallWidget, self).__init__()
|
||||||
self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowStaysOnTopHint)
|
self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowStaysOnTopHint)
|
||||||
self.resize(QtCore.QSize(500, 270))
|
self.resize(QtCore.QSize(500, 270))
|
||||||
self.avatar_label = QtGui.QLabel(self)
|
self.avatar_label = QtWidgets.QLabel(self)
|
||||||
self.avatar_label.setGeometry(QtCore.QRect(10, 20, 64, 64))
|
self.avatar_label.setGeometry(QtCore.QRect(10, 20, 64, 64))
|
||||||
self.avatar_label.setScaledContents(False)
|
self.avatar_label.setScaledContents(False)
|
||||||
self.name = widgets.DataLabel(self)
|
self.name = widgets.DataLabel(self)
|
||||||
self.name.setGeometry(QtCore.QRect(90, 20, 300, 25))
|
self.name.setGeometry(QtCore.QRect(90, 20, 300, 25))
|
||||||
|
self._friend_number = friend_number
|
||||||
font = QtGui.QFont()
|
font = QtGui.QFont()
|
||||||
font.setFamily(settings.Settings.get_instance()['font'])
|
font.setFamily(settings.Settings.get_instance()['font'])
|
||||||
font.setPointSize(16)
|
font.setPointSize(16)
|
||||||
@ -30,11 +28,11 @@ class IncomingCallWidget(widgets.CenteredWidget):
|
|||||||
self.call_type = widgets.DataLabel(self)
|
self.call_type = widgets.DataLabel(self)
|
||||||
self.call_type.setGeometry(QtCore.QRect(90, 55, 300, 25))
|
self.call_type.setGeometry(QtCore.QRect(90, 55, 300, 25))
|
||||||
self.call_type.setFont(font)
|
self.call_type.setFont(font)
|
||||||
self.accept_audio = QtGui.QPushButton(self)
|
self.accept_audio = QtWidgets.QPushButton(self)
|
||||||
self.accept_audio.setGeometry(QtCore.QRect(20, 100, 150, 150))
|
self.accept_audio.setGeometry(QtCore.QRect(20, 100, 150, 150))
|
||||||
self.accept_video = QtGui.QPushButton(self)
|
self.accept_video = QtWidgets.QPushButton(self)
|
||||||
self.accept_video.setGeometry(QtCore.QRect(170, 100, 150, 150))
|
self.accept_video.setGeometry(QtCore.QRect(170, 100, 150, 150))
|
||||||
self.decline = QtGui.QPushButton(self)
|
self.decline = QtWidgets.QPushButton(self)
|
||||||
self.decline.setGeometry(QtCore.QRect(320, 100, 150, 150))
|
self.decline.setGeometry(QtCore.QRect(320, 100, 150, 150))
|
||||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/accept_audio.png')
|
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/accept_audio.png')
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
@ -54,15 +52,16 @@ class IncomingCallWidget(widgets.CenteredWidget):
|
|||||||
self.setWindowTitle(text)
|
self.setWindowTitle(text)
|
||||||
self.name.setText(name)
|
self.name.setText(name)
|
||||||
self.call_type.setText(text)
|
self.call_type.setText(text)
|
||||||
pr = profile.Profile.get_instance()
|
self._processing = False
|
||||||
self.accept_audio.clicked.connect(lambda: pr.accept_call(friend_number, True, False) or self.stop())
|
self.accept_audio.clicked.connect(self.accept_call_with_audio)
|
||||||
# self.accept_video.clicked.connect(lambda: pr.start_call(friend_number, True, True))
|
self.accept_video.clicked.connect(self.accept_call_with_video)
|
||||||
self.decline.clicked.connect(lambda: pr.stop_call(friend_number, False) or self.stop())
|
self.decline.clicked.connect(self.decline_call)
|
||||||
|
|
||||||
class SoundPlay(QtCore.QThread):
|
class SoundPlay(QtCore.QThread):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
QtCore.QThread.__init__(self)
|
QtCore.QThread.__init__(self)
|
||||||
|
self.a = None
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
class AudioFile:
|
class AudioFile:
|
||||||
@ -107,33 +106,29 @@ class IncomingCallWidget(widgets.CenteredWidget):
|
|||||||
self.thread.wait()
|
self.thread.wait()
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
def accept_call_with_audio(self):
|
||||||
|
if self._processing:
|
||||||
|
return
|
||||||
|
self._processing = True
|
||||||
|
pr = profile.Profile.get_instance()
|
||||||
|
pr.accept_call(self._friend_number, True, False)
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
def accept_call_with_video(self):
|
||||||
|
if self._processing:
|
||||||
|
return
|
||||||
|
self._processing = True
|
||||||
|
pr = profile.Profile.get_instance()
|
||||||
|
pr.accept_call(self._friend_number, True, True)
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
def decline_call(self):
|
||||||
|
if self._processing:
|
||||||
|
return
|
||||||
|
self._processing = True
|
||||||
|
pr = profile.Profile.get_instance()
|
||||||
|
pr.stop_call(self._friend_number, False)
|
||||||
|
self.stop()
|
||||||
|
|
||||||
def set_pixmap(self, pixmap):
|
def set_pixmap(self, pixmap):
|
||||||
self.avatar_label.setPixmap(pixmap)
|
self.avatar_label.setPixmap(pixmap)
|
||||||
|
|
||||||
|
|
||||||
class AudioMessageRecorder(widgets.CenteredWidget):
|
|
||||||
|
|
||||||
def __init__(self, friend_number, name):
|
|
||||||
super(AudioMessageRecorder, self).__init__()
|
|
||||||
self.label = QtGui.QLabel(self)
|
|
||||||
self.label.setGeometry(QtCore.QRect(10, 20, 250, 20))
|
|
||||||
text = QtGui.QApplication.translate("MenuWindow", "Send audio message to friend {}", None, QtGui.QApplication.UnicodeUTF8)
|
|
||||||
self.label.setText(text.format(name))
|
|
||||||
self.record = QtGui.QPushButton(self)
|
|
||||||
self.record.setGeometry(QtCore.QRect(20, 100, 150, 150))
|
|
||||||
|
|
||||||
self.record.setText(QtGui.QApplication.translate("MenuWindow", "Start recording", None,
|
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.record.clicked.connect(self.start_or_stop_recording)
|
|
||||||
self.recording = False
|
|
||||||
self.friend_num = friend_number
|
|
||||||
|
|
||||||
def start_or_stop_recording(self):
|
|
||||||
if not self.recording:
|
|
||||||
self.recording = True
|
|
||||||
self.record.setText(QtGui.QApplication.translate("MenuWindow", "Stop recording", None,
|
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
else:
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
from settings import *
|
from settings import *
|
||||||
try:
|
from PyQt5 import QtCore, QtGui
|
||||||
from PySide import QtCore, QtGui
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
|
from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
|
||||||
|
|
||||||
|
|
||||||
@ -109,6 +106,10 @@ class BaseContact:
|
|||||||
def get_pixmap(self):
|
def get_pixmap(self):
|
||||||
return self._widget.avatar_label.pixmap()
|
return self._widget.avatar_label.pixmap()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Widgets
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
def init_widget(self):
|
def init_widget(self):
|
||||||
if self._widget is not None:
|
if self._widget is not None:
|
||||||
self._widget.name.setText(self._name)
|
self._widget.name.setText(self._name)
|
||||||
|
@ -1,84 +1,75 @@
|
|||||||
import random
|
import random
|
||||||
|
import urllib.request
|
||||||
|
from util import log, curr_directory
|
||||||
|
import settings
|
||||||
|
from PyQt5 import QtNetwork, QtCore
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
class Node:
|
class Node:
|
||||||
|
|
||||||
def __init__(self, ip, port, tox_key, rand):
|
def __init__(self, node):
|
||||||
self._ip, self._port, self._tox_key, self.rand = ip, port, tox_key, rand
|
self._ip, self._port, self._tox_key = node['ipv4'], node['port'], node['public_key']
|
||||||
|
self._priority = random.randint(1, 1000000) if node['status_tcp'] and node['status_udp'] else 0
|
||||||
|
|
||||||
|
def get_priority(self):
|
||||||
|
return self._priority
|
||||||
|
|
||||||
|
priority = property(get_priority)
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
return bytes(self._ip, 'utf-8'), self._port, self._tox_key
|
return bytes(self._ip, 'utf-8'), self._port, self._tox_key
|
||||||
|
|
||||||
|
|
||||||
def node_generator():
|
def generate_nodes():
|
||||||
nodes = []
|
with open(curr_directory() + '/nodes.json', 'rt') as fl:
|
||||||
ips = [
|
json_nodes = json.loads(fl.read())['nodes']
|
||||||
"144.76.60.215", "23.226.230.47", "195.154.119.113", "biribiri.org",
|
nodes = map(lambda json_node: Node(json_node), json_nodes)
|
||||||
"46.38.239.179", "178.62.250.138", "130.133.110.14", "104.167.101.29",
|
sorted_nodes = sorted(nodes, key=lambda x: x.priority)[-4:]
|
||||||
"205.185.116.116", "198.98.51.198", "80.232.246.79", "108.61.165.198",
|
for node in sorted_nodes:
|
||||||
"212.71.252.109", "194.249.212.109", "185.25.116.107", "192.99.168.140",
|
yield node.get_data()
|
||||||
"46.101.197.175", "95.215.46.114", "5.189.176.217", "148.251.23.146",
|
|
||||||
"104.223.122.15", "78.47.114.252", "d4rk4.ru", "81.4.110.149",
|
|
||||||
"95.31.20.151", "104.233.104.126", "51.254.84.212", "home.vikingmakt.com.br",
|
|
||||||
"5.135.59.163", "185.58.206.164", "188.244.38.183", "mrflibble.c4.ee",
|
|
||||||
"82.211.31.116", "128.199.199.197", "103.230.156.174", "91.121.66.124",
|
|
||||||
"92.54.84.70", "tox1.privacydragon.me"
|
|
||||||
]
|
|
||||||
ports = [
|
|
||||||
33445, 33445, 33445, 33445,
|
|
||||||
33445, 33445, 33445, 33445,
|
|
||||||
33445, 33445, 33445, 33445,
|
|
||||||
33445, 33445, 33445, 33445,
|
|
||||||
443, 33445, 5190, 2306,
|
|
||||||
33445, 33445, 1813, 33445,
|
|
||||||
33445, 33445, 33445, 33445,
|
|
||||||
33445, 33445, 33445, 33445,
|
|
||||||
33445, 33445, 33445, 33445,
|
|
||||||
33445, 33445
|
|
||||||
]
|
|
||||||
ids = [
|
|
||||||
"04119E835DF3E78BACF0F84235B300546AF8B936F035185E2A8E9E0A67C8924F",
|
|
||||||
"A09162D68618E742FFBCA1C2C70385E6679604B2D80EA6E84AD0996A1AC8A074",
|
|
||||||
"E398A69646B8CEACA9F0B84F553726C1C49270558C57DF5F3C368F05A7D71354",
|
|
||||||
"F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67",
|
|
||||||
"F5A1A38EFB6BD3C2C8AF8B10D85F0F89E931704D349F1D0720C3C4059AF2440A",
|
|
||||||
"788236D34978D1D5BD822F0A5BEBD2C53C64CC31CD3149350EE27D4D9A2F9B6B",
|
|
||||||
"461FA3776EF0FA655F1A05477DF1B3B614F7D6B124F7DB1DD4FE3C08B03B640F",
|
|
||||||
"5918AC3C06955962A75AD7DF4F80A5D7C34F7DB9E1498D2E0495DE35B3FE8A57",
|
|
||||||
"A179B09749AC826FF01F37A9613F6B57118AE014D4196A0E1105A98F93A54702",
|
|
||||||
"1D5A5F2F5D6233058BF0259B09622FB40B482E4FA0931EB8FD3AB8E7BF7DAF6F",
|
|
||||||
"CF6CECA0A14A31717CC8501DA51BE27742B70746956E6676FF423A529F91ED5D",
|
|
||||||
"8E7D0B859922EF569298B4D261A8CCB5FEA14FB91ED412A7603A585A25698832",
|
|
||||||
"C4CEB8C7AC607C6B374E2E782B3C00EA3A63B80D4910B8649CCACDD19F260819",
|
|
||||||
"3CEE1F054081E7A011234883BC4FC39F661A55B73637A5AC293DDF1251D9432B",
|
|
||||||
"DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43",
|
|
||||||
"6A4D0607A296838434A6A7DDF99F50EF9D60A2C510BBF31FE538A25CB6B4652F",
|
|
||||||
"CD133B521159541FB1D326DE9850F5E56A6C724B5B8E5EB5CD8D950408E95707",
|
|
||||||
"5823FB947FF24CF83DDFAC3F3BAA18F96EA2018B16CC08429CB97FA502F40C23",
|
|
||||||
"2B2137E094F743AC8BD44652C55F41DFACC502F125E99E4FE24D40537489E32F",
|
|
||||||
"7AED21F94D82B05774F697B209628CD5A9AD17E0C073D9329076A4C28ED28147",
|
|
||||||
"0FB96EEBFB1650DDB52E70CF773DDFCABE25A95CC3BB50FC251082E4B63EF82A",
|
|
||||||
"1C5293AEF2114717547B39DA8EA6F1E331E5E358B35F9B6B5F19317911C5F976",
|
|
||||||
"53737F6D47FA6BD2808F378E339AF45BF86F39B64E79D6D491C53A1D522E7039",
|
|
||||||
"9E7BD4793FFECA7F32238FA2361040C09025ED3333744483CA6F3039BFF0211E",
|
|
||||||
"9CA69BB74DE7C056D1CC6B16AB8A0A38725C0349D187D8996766958584D39340",
|
|
||||||
"EDEE8F2E839A57820DE3DA4156D88350E53D4161447068A3457EE8F59F362414",
|
|
||||||
"AEC204B9A4501412D5F0BB67D9C81B5DB3EE6ADA64122D32A3E9B093D544327D",
|
|
||||||
"188E072676404ED833A4E947DC1D223DF8EFEBE5F5258573A236573688FB9761",
|
|
||||||
"2D320F971EF2CA18004416C2AAE7BA52BF7949DB34EA8E2E21AF67BD367BE211",
|
|
||||||
"24156472041E5F220D1FA11D9DF32F7AD697D59845701CDD7BE7D1785EB9DB39",
|
|
||||||
"15A0F9684E2423F9F46CFA5A50B562AE42525580D840CC50E518192BF333EE38",
|
|
||||||
"FAAB17014F42F7F20949F61E55F66A73C230876812A9737F5F6D2DCE4D9E4207",
|
|
||||||
"AF97B76392A6474AF2FD269220FDCF4127D86A42EF3A242DF53A40A268A2CD7C",
|
|
||||||
"B05C8869DBB4EDDD308F43C1A974A20A725A36EACCA123862FDE9945BF9D3E09",
|
|
||||||
"5C4C7A60183D668E5BD8B3780D1288203E2F1BAE4EEF03278019E21F86174C1D",
|
|
||||||
"4E3F7D37295664BBD0741B6DBCB6431D6CD77FC4105338C2FC31567BF5C8224A",
|
|
||||||
"5625A62618CB4FCA70E147A71B29695F38CC65FF0CBD68AD46254585BE564802",
|
|
||||||
"31910C0497D347FF160D6F3A6C0E317BAFA71E8E03BC4CBB2A185C9D4FB8B31E"
|
|
||||||
]
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
|
def save_nodes(nodes):
|
||||||
|
if not nodes:
|
||||||
|
return
|
||||||
|
print('Saving nodes...')
|
||||||
|
with open(curr_directory() + '/nodes.json', 'wb') as fl:
|
||||||
|
fl.write(nodes)
|
||||||
|
|
||||||
|
|
||||||
|
def download_nodes_list():
|
||||||
|
url = 'https://nodes.tox.chat/json'
|
||||||
|
s = settings.Settings.get_instance()
|
||||||
|
if not s['download_nodes_list']:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not s['proxy_type']: # no proxy
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(url)
|
||||||
|
req.add_header('Content-Type', 'application/json')
|
||||||
|
response = urllib.request.urlopen(req)
|
||||||
|
result = response.read()
|
||||||
|
save_nodes(result)
|
||||||
|
except Exception as ex:
|
||||||
|
log('TOX nodes loading error: ' + str(ex))
|
||||||
|
else: # proxy
|
||||||
|
netman = QtNetwork.QNetworkAccessManager()
|
||||||
|
proxy = QtNetwork.QNetworkProxy()
|
||||||
|
proxy.setType(
|
||||||
|
QtNetwork.QNetworkProxy.Socks5Proxy if s['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy)
|
||||||
|
proxy.setHostName(s['proxy_host'])
|
||||||
|
proxy.setPort(s['proxy_port'])
|
||||||
|
netman.setProxy(proxy)
|
||||||
|
try:
|
||||||
|
request = QtNetwork.QNetworkRequest()
|
||||||
|
request.setUrl(QtCore.QUrl(url))
|
||||||
|
reply = netman.get(request)
|
||||||
|
|
||||||
|
while not reply.isFinished():
|
||||||
|
QtCore.QThread.msleep(1)
|
||||||
|
QtCore.QCoreApplication.processEvents()
|
||||||
|
data = bytes(reply.readAll().data())
|
||||||
|
save_nodes(data)
|
||||||
|
except Exception as ex:
|
||||||
|
log('TOX nodes loading error: ' + str(ex))
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
try:
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
from PySide import QtCore
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore
|
|
||||||
from notifications import *
|
from notifications import *
|
||||||
from settings import Settings
|
from settings import Settings
|
||||||
from profile import Profile
|
from profile import Profile
|
||||||
@ -12,7 +9,8 @@ from plugin_support import PluginLoader
|
|||||||
import queue
|
import queue
|
||||||
import threading
|
import threading
|
||||||
import util
|
import util
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Threads
|
# Threads
|
||||||
@ -35,6 +33,7 @@ class Invoker(QtCore.QObject):
|
|||||||
event.fn(*event.args, **event.kwargs)
|
event.fn(*event.args, **event.kwargs)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
_invoker = Invoker()
|
_invoker = Invoker()
|
||||||
|
|
||||||
|
|
||||||
@ -68,6 +67,7 @@ class FileTransfersThread(threading.Thread):
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
util.log('Exception in _thread: ' + str(ex))
|
util.log('Exception in _thread: ' + str(ex))
|
||||||
|
|
||||||
|
|
||||||
_thread = FileTransfersThread()
|
_thread = FileTransfersThread()
|
||||||
|
|
||||||
|
|
||||||
@ -225,7 +225,7 @@ def tox_file_recv(window, tray):
|
|||||||
if not window.isActiveWindow():
|
if not window.isActiveWindow():
|
||||||
friend = profile.get_friend_by_number(friend_number)
|
friend = profile.get_friend_by_number(friend_number)
|
||||||
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
|
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
|
||||||
file_from = QtGui.QApplication.translate("Callback", "File from", None, QtGui.QApplication.UnicodeUTF8)
|
file_from = QtWidgets.QApplication.translate("Callback", "File from")
|
||||||
invoke_in_main_thread(tray_notification, file_from + ' ' + friend.name, file_name, tray, window)
|
invoke_in_main_thread(tray_notification, file_from + ' ' + friend.name, file_name, tray, window)
|
||||||
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
||||||
sound_notification(SOUND_NOTIFICATION['FILE_TRANSFER'])
|
sound_notification(SOUND_NOTIFICATION['FILE_TRANSFER'])
|
||||||
@ -315,11 +315,115 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud
|
|||||||
"""
|
"""
|
||||||
New audio chunk
|
New audio chunk
|
||||||
"""
|
"""
|
||||||
Profile.get_instance().call.chunk(
|
Profile.get_instance().call.audio_chunk(
|
||||||
bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]),
|
bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]),
|
||||||
audio_channels_count,
|
audio_channels_count,
|
||||||
rate)
|
rate)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Callbacks - video
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, ustride, vstride, user_data):
|
||||||
|
"""
|
||||||
|
Creates yuv frame from y, u, v and shows it using OpenCV
|
||||||
|
For yuv => bgr we need this YUV420 frame:
|
||||||
|
|
||||||
|
width
|
||||||
|
-------------------------
|
||||||
|
| |
|
||||||
|
| Y | height
|
||||||
|
| |
|
||||||
|
-------------------------
|
||||||
|
| | |
|
||||||
|
| U even | U odd | height // 4
|
||||||
|
| | |
|
||||||
|
-------------------------
|
||||||
|
| | |
|
||||||
|
| V even | V odd | height // 4
|
||||||
|
| | |
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
width // 2 width // 2
|
||||||
|
|
||||||
|
It can be created from initial y, u, v using slices
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
y_size = abs(max(width, abs(ystride)))
|
||||||
|
u_size = abs(max(width // 2, abs(ustride)))
|
||||||
|
v_size = abs(max(width // 2, abs(vstride)))
|
||||||
|
|
||||||
|
y = np.asarray(y[:y_size * height], dtype=np.uint8).reshape(height, y_size)
|
||||||
|
u = np.asarray(u[:u_size * height // 2], dtype=np.uint8).reshape(height // 2, u_size)
|
||||||
|
v = np.asarray(v[:v_size * height // 2], dtype=np.uint8).reshape(height // 2, v_size)
|
||||||
|
|
||||||
|
width -= width % 4
|
||||||
|
height -= height % 4
|
||||||
|
|
||||||
|
frame = np.zeros((int(height * 1.5), width), dtype=np.uint8)
|
||||||
|
|
||||||
|
frame[:height, :] = y[:height, :width]
|
||||||
|
frame[height:height * 5 // 4, :width // 2] = u[:height // 2:2, :width // 2]
|
||||||
|
frame[height:height * 5 // 4, width // 2:] = u[1:height // 2:2, :width // 2]
|
||||||
|
|
||||||
|
frame[height * 5 // 4:, :width // 2] = v[:height // 2:2, :width // 2]
|
||||||
|
frame[height * 5 // 4:, width // 2:] = v[1:height // 2:2, :width // 2]
|
||||||
|
|
||||||
|
frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420)
|
||||||
|
|
||||||
|
invoke_in_main_thread(cv2.imshow, str(friend_number), frame)
|
||||||
|
except Exception as ex:
|
||||||
|
print(ex)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Callbacks - groups
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def group_invite(tox, friend_number, gc_type, data, length, user_data):
|
||||||
|
invoke_in_main_thread(Profile.get_instance().group_invite, friend_number, gc_type,
|
||||||
|
bytes(data[:length]))
|
||||||
|
|
||||||
|
|
||||||
|
def show_gc_notification(window, tray, message, group_number, peer_number):
|
||||||
|
profile = Profile.get_instance()
|
||||||
|
settings = Settings.get_instance()
|
||||||
|
chat = profile.get_group_by_number(group_number)
|
||||||
|
peer_name = chat.get_peer_name(peer_number)
|
||||||
|
if not window.isActiveWindow() and (profile.name in message or settings['group_notifications']):
|
||||||
|
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
|
||||||
|
invoke_in_main_thread(tray_notification, chat.name + ' ' + peer_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'))
|
||||||
|
|
||||||
|
|
||||||
|
def group_message(window, tray):
|
||||||
|
def wrapped(tox, group_number, peer_number, message, length, user_data):
|
||||||
|
message = str(message[:length], 'utf-8')
|
||||||
|
invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number,
|
||||||
|
peer_number, TOX_MESSAGE_TYPE['NORMAL'], message)
|
||||||
|
show_gc_notification(window, tray, message, group_number, peer_number)
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def group_action(window, tray):
|
||||||
|
def wrapped(tox, group_number, peer_number, message, length, user_data):
|
||||||
|
message = str(message[:length], 'utf-8')
|
||||||
|
invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number,
|
||||||
|
peer_number, TOX_MESSAGE_TYPE['ACTION'], message)
|
||||||
|
show_gc_notification(window, tray, message, group_number, peer_number)
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def group_title(tox, group_number, peer_number, title, length, user_data):
|
||||||
|
invoke_in_main_thread(Profile.get_instance().new_gc_title, group_number,
|
||||||
|
title[:length])
|
||||||
|
|
||||||
|
|
||||||
|
def group_namelist_change(tox, group_number, peer_number, change, user_data):
|
||||||
|
invoke_in_main_thread(Profile.get_instance().update_gc, group_number)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Callbacks - initialization
|
# Callbacks - initialization
|
||||||
@ -353,7 +457,13 @@ def init_callbacks(tox, window, tray):
|
|||||||
toxav.callback_call_state(call_state, 0)
|
toxav.callback_call_state(call_state, 0)
|
||||||
toxav.callback_call(call, 0)
|
toxav.callback_call(call, 0)
|
||||||
toxav.callback_audio_receive_frame(callback_audio, 0)
|
toxav.callback_audio_receive_frame(callback_audio, 0)
|
||||||
|
toxav.callback_video_receive_frame(video_receive_frame, 0)
|
||||||
|
|
||||||
tox.callback_friend_lossless_packet(lossless_packet, 0)
|
tox.callback_friend_lossless_packet(lossless_packet, 0)
|
||||||
tox.callback_friend_lossy_packet(lossy_packet, 0)
|
tox.callback_friend_lossy_packet(lossy_packet, 0)
|
||||||
|
|
||||||
|
tox.callback_group_invite(group_invite)
|
||||||
|
tox.callback_group_message(group_message(window, tray))
|
||||||
|
tox.callback_group_action(group_action(window, tray))
|
||||||
|
tox.callback_group_title(group_title)
|
||||||
|
tox.callback_group_namelist_change(group_namelist_change)
|
||||||
|
275
toxygen/calls.py
@ -3,14 +3,69 @@ import time
|
|||||||
import threading
|
import threading
|
||||||
import settings
|
import settings
|
||||||
from toxav_enums import *
|
from toxav_enums import *
|
||||||
# TODO: play sound until outgoing call will be started or cancelled and add timeout
|
import cv2
|
||||||
# TODO: add widget for call
|
import itertools
|
||||||
|
import numpy as np
|
||||||
|
import screen_sharing
|
||||||
|
# TODO: play sound until outgoing call will be started or cancelled
|
||||||
|
|
||||||
CALL_TYPE = {
|
|
||||||
'NONE': 0,
|
class Call:
|
||||||
'AUDIO': 1,
|
|
||||||
'VIDEO': 2
|
def __init__(self, out_audio, out_video, in_audio=False, in_video=False):
|
||||||
}
|
self._in_audio = in_audio
|
||||||
|
self._in_video = in_video
|
||||||
|
self._out_audio = out_audio
|
||||||
|
self._out_video = out_video
|
||||||
|
self._is_active = False
|
||||||
|
|
||||||
|
def get_is_active(self):
|
||||||
|
return self._is_active
|
||||||
|
|
||||||
|
def set_is_active(self, value):
|
||||||
|
self._is_active = value
|
||||||
|
|
||||||
|
is_active = property(get_is_active, set_is_active)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Audio
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_in_audio(self):
|
||||||
|
return self._in_audio
|
||||||
|
|
||||||
|
def set_in_audio(self, value):
|
||||||
|
self._in_audio = value
|
||||||
|
|
||||||
|
in_audio = property(get_in_audio, set_in_audio)
|
||||||
|
|
||||||
|
def get_out_audio(self):
|
||||||
|
return self._out_audio
|
||||||
|
|
||||||
|
def set_out_audio(self, value):
|
||||||
|
self._out_audio = value
|
||||||
|
|
||||||
|
out_audio = property(get_out_audio, set_out_audio)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Video
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_in_video(self):
|
||||||
|
return self._in_video
|
||||||
|
|
||||||
|
def set_in_video(self, value):
|
||||||
|
self._in_video = value
|
||||||
|
|
||||||
|
in_video = property(get_in_video, set_in_video)
|
||||||
|
|
||||||
|
def get_out_video(self):
|
||||||
|
return self._out_video
|
||||||
|
|
||||||
|
def set_out_video(self, value):
|
||||||
|
self._out_video = value
|
||||||
|
|
||||||
|
out_video = property(get_out_video, set_out_video)
|
||||||
|
|
||||||
|
|
||||||
class AV:
|
class AV:
|
||||||
@ -19,7 +74,7 @@ class AV:
|
|||||||
self._toxav = toxav
|
self._toxav = toxav
|
||||||
self._running = True
|
self._running = True
|
||||||
|
|
||||||
self._calls = {} # dict: key - friend number, value - call type
|
self._calls = {} # dict: key - friend number, value - Call instance
|
||||||
|
|
||||||
self._audio = None
|
self._audio = None
|
||||||
self._audio_stream = None
|
self._audio_stream = None
|
||||||
@ -32,27 +87,78 @@ class AV:
|
|||||||
self._audio_duration = 60
|
self._audio_duration = 60
|
||||||
self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration // 1000
|
self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration // 1000
|
||||||
|
|
||||||
def __contains__(self, friend_number):
|
self._video = None
|
||||||
return friend_number in self._calls
|
self._video_thread = None
|
||||||
|
self._video_running = False
|
||||||
|
|
||||||
def __call__(self, friend_number, audio, video):
|
self._video_width = 640
|
||||||
"""Call friend with specified number"""
|
self._video_height = 480
|
||||||
self._toxav.call(friend_number, 32 if audio else 0, 5000 if video else 0)
|
|
||||||
self._calls[friend_number] = CALL_TYPE['AUDIO']
|
|
||||||
self.start_audio_thread()
|
|
||||||
|
|
||||||
def finish_call(self, friend_number, by_friend=False):
|
|
||||||
|
|
||||||
if not by_friend:
|
|
||||||
self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL'])
|
|
||||||
if friend_number in self._calls:
|
|
||||||
del self._calls[friend_number]
|
|
||||||
if not len(self._calls):
|
|
||||||
self.stop_audio_thread()
|
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self._running = False
|
self._running = False
|
||||||
self.stop_audio_thread()
|
self.stop_audio_thread()
|
||||||
|
self.stop_video_thread()
|
||||||
|
|
||||||
|
def __contains__(self, friend_number):
|
||||||
|
return friend_number in self._calls
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Calls
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def __call__(self, friend_number, audio, video):
|
||||||
|
"""Call friend with specified number"""
|
||||||
|
self._toxav.call(friend_number, 32 if audio else 0, 5000 if video else 0)
|
||||||
|
self._calls[friend_number] = Call(audio, video)
|
||||||
|
threading.Timer(30.0, lambda: self.finish_not_started_call(friend_number)).start()
|
||||||
|
|
||||||
|
def accept_call(self, friend_number, audio_enabled, video_enabled):
|
||||||
|
if self._running:
|
||||||
|
self._calls[friend_number] = Call(audio_enabled, video_enabled)
|
||||||
|
self._toxav.answer(friend_number, 32 if audio_enabled else 0, 5000 if video_enabled else 0)
|
||||||
|
if audio_enabled:
|
||||||
|
self.start_audio_thread()
|
||||||
|
if video_enabled:
|
||||||
|
self.start_video_thread()
|
||||||
|
|
||||||
|
def finish_call(self, friend_number, by_friend=False):
|
||||||
|
if not by_friend:
|
||||||
|
self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL'])
|
||||||
|
if friend_number in self._calls:
|
||||||
|
del self._calls[friend_number]
|
||||||
|
if not len(list(filter(lambda c: c.out_audio, self._calls))):
|
||||||
|
self.stop_audio_thread()
|
||||||
|
if not len(list(filter(lambda c: c.out_video, self._calls))):
|
||||||
|
self.stop_video_thread()
|
||||||
|
|
||||||
|
def finish_not_started_call(self, friend_number):
|
||||||
|
if friend_number in self:
|
||||||
|
call = self._calls[friend_number]
|
||||||
|
if not call.is_active:
|
||||||
|
self.finish_call(friend_number)
|
||||||
|
|
||||||
|
def toxav_call_state_cb(self, friend_number, state):
|
||||||
|
"""
|
||||||
|
New call state
|
||||||
|
"""
|
||||||
|
call = self._calls[friend_number]
|
||||||
|
call.is_active = True
|
||||||
|
|
||||||
|
call.in_audio = state | TOXAV_FRIEND_CALL_STATE['SENDING_A'] > 0
|
||||||
|
call.in_video = state | TOXAV_FRIEND_CALL_STATE['SENDING_V'] > 0
|
||||||
|
|
||||||
|
if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_A'] and call.out_audio:
|
||||||
|
self.start_audio_thread()
|
||||||
|
|
||||||
|
if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_V'] and call.out_video:
|
||||||
|
self.start_video_thread()
|
||||||
|
|
||||||
|
def is_video_call(self, number):
|
||||||
|
return number in self and self._calls[number].in_video
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Threads
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
def start_audio_thread(self):
|
def start_audio_thread(self):
|
||||||
"""
|
"""
|
||||||
@ -92,7 +198,41 @@ class AV:
|
|||||||
self._out_stream.close()
|
self._out_stream.close()
|
||||||
self._out_stream = None
|
self._out_stream = None
|
||||||
|
|
||||||
def chunk(self, samples, channels_count, rate):
|
def start_video_thread(self):
|
||||||
|
if self._video_thread is not None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._video_running = True
|
||||||
|
s = settings.Settings.get_instance()
|
||||||
|
self._video_width = s.video['width']
|
||||||
|
self._video_height = s.video['height']
|
||||||
|
|
||||||
|
if s.video['device'] == -1:
|
||||||
|
self._video = screen_sharing.DesktopGrabber(s.video['x'], s.video['y'],
|
||||||
|
s.video['width'], s.video['height'])
|
||||||
|
else:
|
||||||
|
self._video = cv2.VideoCapture(s.video['device'])
|
||||||
|
self._video.set(cv2.CAP_PROP_FPS, 25)
|
||||||
|
self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width)
|
||||||
|
self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._video_height)
|
||||||
|
|
||||||
|
self._video_thread = threading.Thread(target=self.send_video)
|
||||||
|
self._video_thread.start()
|
||||||
|
|
||||||
|
def stop_video_thread(self):
|
||||||
|
if self._video_thread is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._video_running = False
|
||||||
|
self._video_thread.join()
|
||||||
|
self._video_thread = None
|
||||||
|
self._video = None
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Incoming chunks
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def audio_chunk(self, samples, channels_count, rate):
|
||||||
"""
|
"""
|
||||||
Incoming chunk
|
Incoming chunk
|
||||||
"""
|
"""
|
||||||
@ -105,6 +245,10 @@ class AV:
|
|||||||
output=True)
|
output=True)
|
||||||
self._out_stream.write(samples)
|
self._out_stream.write(samples)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# AV sending
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
def send_audio(self):
|
def send_audio(self):
|
||||||
"""
|
"""
|
||||||
This method sends audio to friends
|
This method sends audio to friends
|
||||||
@ -114,10 +258,10 @@ class AV:
|
|||||||
try:
|
try:
|
||||||
pcm = self._audio_stream.read(self._audio_sample_count)
|
pcm = self._audio_stream.read(self._audio_sample_count)
|
||||||
if pcm:
|
if pcm:
|
||||||
for friend in self._calls:
|
for friend_num in self._calls:
|
||||||
if self._calls[friend] & 1:
|
if self._calls[friend_num].out_audio:
|
||||||
try:
|
try:
|
||||||
self._toxav.audio_send_frame(friend, pcm, self._audio_sample_count,
|
self._toxav.audio_send_frame(friend_num, pcm, self._audio_sample_count,
|
||||||
self._audio_channels, self._audio_rate)
|
self._audio_channels, self._audio_rate)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@ -126,19 +270,70 @@ class AV:
|
|||||||
|
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
|
|
||||||
def accept_call(self, friend_number, audio_enabled, video_enabled):
|
def send_video(self):
|
||||||
|
|
||||||
if self._running:
|
|
||||||
self._calls[friend_number] = int(video_enabled) * 2 + int(audio_enabled)
|
|
||||||
self._toxav.answer(friend_number, 32 if audio_enabled else 0, 5000 if video_enabled else 0)
|
|
||||||
self.start_audio_thread()
|
|
||||||
|
|
||||||
def toxav_call_state_cb(self, friend_number, state):
|
|
||||||
"""
|
"""
|
||||||
New call state
|
This method sends video to friends
|
||||||
"""
|
"""
|
||||||
if self._running:
|
while self._video_running:
|
||||||
|
try:
|
||||||
|
result, frame = self._video.read()
|
||||||
|
if result:
|
||||||
|
height, width, channels = frame.shape
|
||||||
|
for friend_num in self._calls:
|
||||||
|
if self._calls[friend_num].out_video:
|
||||||
|
try:
|
||||||
|
y, u, v = self.convert_bgr_to_yuv(frame)
|
||||||
|
self._toxav.video_send_frame(friend_num, width, height, y, u, v)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
if state & TOXAV_FRIEND_CALL_STATE['ACCEPTING_A']:
|
time.sleep(0.01)
|
||||||
self._calls[friend_number] |= 1
|
|
||||||
|
|
||||||
|
def convert_bgr_to_yuv(self, frame):
|
||||||
|
"""
|
||||||
|
:param frame: input bgr frame
|
||||||
|
:return y, u, v: y, u, v values of frame
|
||||||
|
|
||||||
|
How this function works:
|
||||||
|
OpenCV creates YUV420 frame from BGR
|
||||||
|
This frame has following structure and size:
|
||||||
|
width, height - dim of input frame
|
||||||
|
width, height * 1.5 - dim of output frame
|
||||||
|
|
||||||
|
width
|
||||||
|
-------------------------
|
||||||
|
| |
|
||||||
|
| Y | height
|
||||||
|
| |
|
||||||
|
-------------------------
|
||||||
|
| | |
|
||||||
|
| U even | U odd | height // 4
|
||||||
|
| | |
|
||||||
|
-------------------------
|
||||||
|
| | |
|
||||||
|
| V even | V odd | height // 4
|
||||||
|
| | |
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
width // 2 width // 2
|
||||||
|
|
||||||
|
Y, U, V can be extracted using slices and joined in one list using itertools.chain.from_iterable()
|
||||||
|
Function returns bytes(y), bytes(u), bytes(v), because it is required for ctypes
|
||||||
|
"""
|
||||||
|
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2YUV_I420)
|
||||||
|
|
||||||
|
y = frame[:self._video_height, :]
|
||||||
|
y = list(itertools.chain.from_iterable(y))
|
||||||
|
|
||||||
|
u = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int)
|
||||||
|
u[::2, :] = frame[self._video_height:self._video_height * 5 // 4, :self._video_width // 2]
|
||||||
|
u[1::2, :] = frame[self._video_height:self._video_height * 5 // 4, self._video_width // 2:]
|
||||||
|
u = list(itertools.chain.from_iterable(u))
|
||||||
|
v = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int)
|
||||||
|
v[::2, :] = frame[self._video_height * 5 // 4:, :self._video_width // 2]
|
||||||
|
v[1::2, :] = frame[self._video_height * 5 // 4:, self._video_width // 2:]
|
||||||
|
v = list(itertools.chain.from_iterable(v))
|
||||||
|
|
||||||
|
return bytes(y), bytes(u), bytes(v)
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
try:
|
from PyQt5 import QtCore, QtGui
|
||||||
from PySide import QtCore, QtGui
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
from history import *
|
from history import *
|
||||||
import basecontact
|
import basecontact
|
||||||
import util
|
import util
|
||||||
@ -30,7 +27,6 @@ class Contact(basecontact.BaseContact):
|
|||||||
self._corr = []
|
self._corr = []
|
||||||
self._unsaved_messages = 0
|
self._unsaved_messages = 0
|
||||||
self._history_loaded = self._new_actions = False
|
self._history_loaded = self._new_actions = False
|
||||||
self._receipts = 0
|
|
||||||
self._curr_text = self._search_string = ''
|
self._curr_text = self._search_string = ''
|
||||||
self._search_index = 0
|
self._search_index = 0
|
||||||
|
|
||||||
@ -50,6 +46,8 @@ class Contact(basecontact.BaseContact):
|
|||||||
"""
|
"""
|
||||||
if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')):
|
if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')):
|
||||||
return
|
return
|
||||||
|
if self._message_getter is None:
|
||||||
|
return
|
||||||
data = list(self._message_getter.get(PAGE_SIZE))
|
data = list(self._message_getter.get(PAGE_SIZE))
|
||||||
if data is not None and len(data):
|
if data is not None and len(data):
|
||||||
data.reverse()
|
data.reverse()
|
||||||
@ -63,6 +61,8 @@ class Contact(basecontact.BaseContact):
|
|||||||
"""
|
"""
|
||||||
Get all chat history from db for current friend
|
Get all chat history from db for current friend
|
||||||
"""
|
"""
|
||||||
|
if self._message_getter is None:
|
||||||
|
return
|
||||||
data = list(self._message_getter.get_all())
|
data = list(self._message_getter.get_all())
|
||||||
if data is not None and len(data):
|
if data is not None and len(data):
|
||||||
data.reverse()
|
data.reverse()
|
||||||
@ -126,7 +126,7 @@ class Contact(basecontact.BaseContact):
|
|||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
def delete_message(self, time):
|
def delete_message(self, time):
|
||||||
elem = list(filter(lambda x: type(x) is TextMessage and x.get_data()[2] == time, self._corr))[0]
|
elem = list(filter(lambda x: type(x) in (TextMessage, GroupChatMessage) and x.get_data()[2] == time, self._corr))[0]
|
||||||
tmp = list(filter(lambda x: x.get_type() <= 1, self._corr))
|
tmp = list(filter(lambda x: x.get_type() <= 1, self._corr))
|
||||||
if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages:
|
if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages:
|
||||||
self._unsaved_messages -= 1
|
self._unsaved_messages -= 1
|
||||||
@ -138,12 +138,15 @@ class Contact(basecontact.BaseContact):
|
|||||||
"""
|
"""
|
||||||
Delete old messages (reduces RAM usage if messages saving is not enabled)
|
Delete old messages (reduces RAM usage if messages saving is not enabled)
|
||||||
"""
|
"""
|
||||||
old = filter(lambda x: x.get_type() == 2 and (x.get_status() >= 2 or x.get_status() is None),
|
def save_message(x):
|
||||||
self._corr[:-SAVE_MESSAGES])
|
if x.get_type() == 2 and (x.get_status() >= 2 or x.get_status() is None):
|
||||||
old = list(old)
|
return True
|
||||||
l = max(len(self._corr) - SAVE_MESSAGES, 0) - len(old)
|
return x.get_owner() == MESSAGE_OWNER['NOT_SENT']
|
||||||
self._unsaved_messages -= l
|
|
||||||
self._corr = old + self._corr[-SAVE_MESSAGES:]
|
old = filter(save_message, self._corr[:-SAVE_MESSAGES])
|
||||||
|
self._corr = list(old) + self._corr[-SAVE_MESSAGES:]
|
||||||
|
text_messages = filter(lambda x: x.get_type() <= 1, self._corr)
|
||||||
|
self._unsaved_messages = min(self._unsaved_messages, len(list(text_messages)))
|
||||||
self._search_index = 0
|
self._search_index = 0
|
||||||
|
|
||||||
def clear_corr(self, save_unsent=False):
|
def clear_corr(self, save_unsent=False):
|
||||||
@ -176,7 +179,7 @@ class Contact(basecontact.BaseContact):
|
|||||||
while True:
|
while True:
|
||||||
l = len(self._corr)
|
l = len(self._corr)
|
||||||
for i in range(self._search_index - 1, -l - 1, -1):
|
for i in range(self._search_index - 1, -l - 1, -1):
|
||||||
if type(self._corr[i]) is not TextMessage:
|
if self._corr[i].get_type() > 1:
|
||||||
continue
|
continue
|
||||||
message = self._corr[i].get_data()[0]
|
message = self._corr[i].get_data()[0]
|
||||||
if re.search(self._search_string, message, re.IGNORECASE) is not None:
|
if re.search(self._search_string, message, re.IGNORECASE) is not None:
|
||||||
@ -191,7 +194,7 @@ class Contact(basecontact.BaseContact):
|
|||||||
if not self._search_index:
|
if not self._search_index:
|
||||||
return None
|
return None
|
||||||
for i in range(self._search_index + 1, 0):
|
for i in range(self._search_index + 1, 0):
|
||||||
if type(self._corr[i]) is not TextMessage:
|
if self._corr[i].get_type() > 1:
|
||||||
continue
|
continue
|
||||||
message = self._corr[i].get_data()[0]
|
message = self._corr[i].get_data()[0]
|
||||||
if re.search(self._search_string, message, re.IGNORECASE) is not None:
|
if re.search(self._search_string, message, re.IGNORECASE) is not None:
|
||||||
|
@ -4,11 +4,7 @@ from os import remove, rename, chdir
|
|||||||
from time import time, sleep
|
from time import time, sleep
|
||||||
from tox import Tox
|
from tox import Tox
|
||||||
import settings
|
import settings
|
||||||
try:
|
from PyQt5 import QtCore
|
||||||
from PySide import QtCore
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore
|
|
||||||
QtCore.Signal = QtCore.pyqtSignal
|
|
||||||
|
|
||||||
|
|
||||||
TOX_FILE_TRANSFER_STATE = {
|
TOX_FILE_TRANSFER_STATE = {
|
||||||
@ -33,17 +29,17 @@ ALLOWED_FILES = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png')
|
|||||||
|
|
||||||
|
|
||||||
def is_inline(file_name):
|
def is_inline(file_name):
|
||||||
return file_name in ALLOWED_FILES or file_name.startswith('qTox_Screenshot_')
|
return file_name in ALLOWED_FILES or file_name.startswith('qTox_Screenshot_') or file_name.startswith('qTox_Image_')
|
||||||
|
|
||||||
|
|
||||||
class StateSignal(QtCore.QObject):
|
class StateSignal(QtCore.QObject):
|
||||||
|
|
||||||
signal = QtCore.Signal(int, float, int) # state, progress, time in sec
|
signal = QtCore.pyqtSignal(int, float, int) # state, progress, time in sec
|
||||||
|
|
||||||
|
|
||||||
class TransferFinishedSignal(QtCore.QObject):
|
class TransferFinishedSignal(QtCore.QObject):
|
||||||
|
|
||||||
signal = QtCore.Signal(int, int) # friend number, file number
|
signal = QtCore.pyqtSignal(int, int) # friend number, file number
|
||||||
|
|
||||||
|
|
||||||
class FileTransfer(QtCore.QObject):
|
class FileTransfer(QtCore.QObject):
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import contact
|
import contact
|
||||||
from messages import *
|
from messages import *
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
class Friend(contact.Contact):
|
class Friend(contact.Contact):
|
||||||
@ -37,6 +38,15 @@ class Friend(contact.Contact):
|
|||||||
def clear_unsent_files(self):
|
def clear_unsent_files(self):
|
||||||
self._corr = list(filter(lambda x: type(x) is not UnsentFile, self._corr))
|
self._corr = list(filter(lambda x: type(x) is not UnsentFile, self._corr))
|
||||||
|
|
||||||
|
def remove_invalid_unsent_files(self):
|
||||||
|
def is_valid(message):
|
||||||
|
if type(message) is not UnsentFile:
|
||||||
|
return True
|
||||||
|
if message.get_data()[1] is not None:
|
||||||
|
return True
|
||||||
|
return os.path.exists(message.get_data()[0])
|
||||||
|
self._corr = list(filter(is_valid, self._corr))
|
||||||
|
|
||||||
def delete_one_unsent_file(self, time):
|
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))
|
self._corr = list(filter(lambda x: not (type(x) is UnsentFile and x.get_data()[2] == time), self._corr))
|
||||||
|
|
||||||
@ -56,3 +66,10 @@ class Friend(contact.Contact):
|
|||||||
if self._receipts:
|
if self._receipts:
|
||||||
self._receipts -= 1
|
self._receipts -= 1
|
||||||
self.mark_as_sent()
|
self.mark_as_sent()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Full status
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_full_status(self):
|
||||||
|
return self._status_message
|
||||||
|
49
toxygen/group_chat.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import contact
|
||||||
|
import util
|
||||||
|
from PyQt5 import QtGui, QtCore
|
||||||
|
import toxcore_enums_and_consts as constants
|
||||||
|
|
||||||
|
|
||||||
|
class GroupChat(contact.Contact):
|
||||||
|
|
||||||
|
def __init__(self, name, status_message, widget, tox, group_number):
|
||||||
|
super().__init__(None, group_number, name, status_message, widget, None)
|
||||||
|
self._tox = tox
|
||||||
|
self.set_status(constants.TOX_USER_STATUS['NONE'])
|
||||||
|
|
||||||
|
def set_name(self, name):
|
||||||
|
self._tox.group_set_title(self._number, name)
|
||||||
|
super().set_name(name)
|
||||||
|
|
||||||
|
def send_message(self, message):
|
||||||
|
self._tox.group_message_send(self._number, message.encode('utf-8'))
|
||||||
|
|
||||||
|
def new_title(self, title):
|
||||||
|
super().set_name(title)
|
||||||
|
|
||||||
|
def load_avatar(self):
|
||||||
|
path = util.curr_directory() + '/images/group.png'
|
||||||
|
width = self._widget.avatar_label.width()
|
||||||
|
pixmap = QtGui.QPixmap(path)
|
||||||
|
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
|
||||||
|
QtCore.Qt.SmoothTransformation))
|
||||||
|
self._widget.avatar_label.repaint()
|
||||||
|
|
||||||
|
def remove_invalid_unsent_files(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_names(self):
|
||||||
|
peers_count = self._tox.group_number_peers(self._number)
|
||||||
|
names = []
|
||||||
|
for i in range(peers_count):
|
||||||
|
name = self._tox.group_peername(self._number, i)
|
||||||
|
names.append(name)
|
||||||
|
names = sorted(names, key=lambda n: n.lower())
|
||||||
|
return names
|
||||||
|
|
||||||
|
def get_full_status(self):
|
||||||
|
names = self.get_names()
|
||||||
|
return '\n'.join(names)
|
||||||
|
|
||||||
|
def get_peer_name(self, peer_number):
|
||||||
|
return self._tox.group_peername(self._number, peer_number)
|
@ -9,7 +9,7 @@ PAGE_SIZE = 42
|
|||||||
|
|
||||||
TIMEOUT = 11
|
TIMEOUT = 11
|
||||||
|
|
||||||
SAVE_MESSAGES = 150
|
SAVE_MESSAGES = 250
|
||||||
|
|
||||||
MESSAGE_OWNER = {
|
MESSAGE_OWNER = {
|
||||||
'ME': 0,
|
'ME': 0,
|
||||||
@ -133,14 +133,15 @@ class History:
|
|||||||
db.rollback()
|
db.rollback()
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
pass
|
|
||||||
|
|
||||||
def delete_message(self, tox_id, time):
|
def delete_message(self, tox_id, time):
|
||||||
|
start, end = str(time - 0.01), str(time + 0.01)
|
||||||
chdir(settings.ProfileHelper.get_path())
|
chdir(settings.ProfileHelper.get_path())
|
||||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
||||||
try:
|
try:
|
||||||
cursor = db.cursor()
|
cursor = db.cursor()
|
||||||
cursor.execute('DELETE FROM id' + tox_id + ' WHERE unix_time = ' + str(time) + ';')
|
cursor.execute('DELETE FROM id' + tox_id + ' WHERE unix_time < ' + end + ' AND unix_time > ' +
|
||||||
|
start + ';')
|
||||||
db.commit()
|
db.commit()
|
||||||
except:
|
except:
|
||||||
print('Database is locked!')
|
print('Database is locked!')
|
||||||
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.0 KiB |
BIN
toxygen/images/group.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.0 KiB |
@ -1,7 +1,4 @@
|
|||||||
try:
|
from PyQt5 import QtWidgets, QtCore
|
||||||
from PySide import QtCore, QtGui
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
from list_items import *
|
from list_items import *
|
||||||
|
|
||||||
|
|
||||||
@ -13,7 +10,7 @@ class ItemsFactory:
|
|||||||
|
|
||||||
def friend_item(self):
|
def friend_item(self):
|
||||||
item = ContactItem()
|
item = ContactItem()
|
||||||
elem = QtGui.QListWidgetItem(self._friends)
|
elem = QtWidgets.QListWidgetItem(self._friends)
|
||||||
elem.setSizeHint(QtCore.QSize(250, item.height()))
|
elem.setSizeHint(QtCore.QSize(250, item.height()))
|
||||||
self._friends.addItem(elem)
|
self._friends.addItem(elem)
|
||||||
self._friends.setItemWidget(elem, item)
|
self._friends.setItemWidget(elem, item)
|
||||||
@ -23,7 +20,7 @@ class ItemsFactory:
|
|||||||
item = MessageItem(text, time, name, sent, message_type, self._messages)
|
item = MessageItem(text, time, name, sent, message_type, self._messages)
|
||||||
if pixmap is not None:
|
if pixmap is not None:
|
||||||
item.set_avatar(pixmap)
|
item.set_avatar(pixmap)
|
||||||
elem = QtGui.QListWidgetItem()
|
elem = QtWidgets.QListWidgetItem()
|
||||||
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
|
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
|
||||||
if append:
|
if append:
|
||||||
self._messages.addItem(elem)
|
self._messages.addItem(elem)
|
||||||
@ -33,7 +30,7 @@ class ItemsFactory:
|
|||||||
return item
|
return item
|
||||||
|
|
||||||
def inline_item(self, data, append):
|
def inline_item(self, data, append):
|
||||||
elem = QtGui.QListWidgetItem()
|
elem = QtWidgets.QListWidgetItem()
|
||||||
item = InlineImageItem(data, self._messages.width(), elem)
|
item = InlineImageItem(data, self._messages.width(), elem)
|
||||||
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
|
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
|
||||||
if append:
|
if append:
|
||||||
@ -49,7 +46,7 @@ class ItemsFactory:
|
|||||||
name,
|
name,
|
||||||
time,
|
time,
|
||||||
self._messages.width())
|
self._messages.width())
|
||||||
elem = QtGui.QListWidgetItem()
|
elem = QtWidgets.QListWidgetItem()
|
||||||
elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34))
|
elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34))
|
||||||
if append:
|
if append:
|
||||||
self._messages.addItem(elem)
|
self._messages.addItem(elem)
|
||||||
@ -61,7 +58,7 @@ class ItemsFactory:
|
|||||||
def file_transfer_item(self, data, append):
|
def file_transfer_item(self, data, append):
|
||||||
data.append(self._messages.width())
|
data.append(self._messages.width())
|
||||||
item = FileTransferItem(*data)
|
item = FileTransferItem(*data)
|
||||||
elem = QtGui.QListWidgetItem()
|
elem = QtWidgets.QListWidgetItem()
|
||||||
elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34))
|
elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34))
|
||||||
if append:
|
if append:
|
||||||
self._messages.addItem(elem)
|
self._messages.addItem(elem)
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
from toxcore_enums_and_consts import *
|
from toxcore_enums_and_consts import *
|
||||||
try:
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
from PySide import QtCore, QtGui
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
QtCore.Slot = QtCore.pyqtSlot
|
|
||||||
import profile
|
import profile
|
||||||
from file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR
|
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 util import curr_directory, convert_time, curr_time
|
||||||
@ -14,7 +10,7 @@ import settings
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
class MessageEdit(QtGui.QTextBrowser):
|
class MessageEdit(QtWidgets.QTextBrowser):
|
||||||
|
|
||||||
def __init__(self, text, width, message_type, parent=None):
|
def __init__(self, text, width, message_type, parent=None):
|
||||||
super(MessageEdit, self).__init__(parent)
|
super(MessageEdit, self).__init__(parent)
|
||||||
@ -46,7 +42,7 @@ class MessageEdit(QtGui.QTextBrowser):
|
|||||||
|
|
||||||
def contextMenuEvent(self, event):
|
def contextMenuEvent(self, event):
|
||||||
menu = create_menu(self.createStandardContextMenu(event.pos()))
|
menu = create_menu(self.createStandardContextMenu(event.pos()))
|
||||||
quote = menu.addAction(QtGui.QApplication.translate("MainWindow", 'Quote selected text', None, QtGui.QApplication.UnicodeUTF8))
|
quote = menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Quote selected text'))
|
||||||
quote.triggered.connect(self.quote_text)
|
quote.triggered.connect(self.quote_text)
|
||||||
text = self.textCursor().selection().toPlainText()
|
text = self.textCursor().selection().toPlainText()
|
||||||
if not text:
|
if not text:
|
||||||
@ -55,7 +51,7 @@ class MessageEdit(QtGui.QTextBrowser):
|
|||||||
import plugin_support
|
import plugin_support
|
||||||
submenu = plugin_support.PluginLoader.get_instance().get_message_menu(menu, text)
|
submenu = plugin_support.PluginLoader.get_instance().get_message_menu(menu, text)
|
||||||
if len(submenu):
|
if len(submenu):
|
||||||
plug = menu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8))
|
plug = menu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins'))
|
||||||
plug.addActions(submenu)
|
plug.addActions(submenu)
|
||||||
menu.popup(event.globalPos())
|
menu.popup(event.globalPos())
|
||||||
menu.exec_(event.globalPos())
|
menu.exec_(event.globalPos())
|
||||||
@ -123,12 +119,12 @@ class MessageEdit(QtGui.QTextBrowser):
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
class MessageItem(QtGui.QWidget):
|
class MessageItem(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
Message in messages list
|
Message in messages list
|
||||||
"""
|
"""
|
||||||
def __init__(self, text, time, user='', sent=True, 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)
|
QtWidgets.QWidget.__init__(self, parent)
|
||||||
self.name = DataLabel(self)
|
self.name = DataLabel(self)
|
||||||
self.name.setGeometry(QtCore.QRect(2, 2, 95, 23))
|
self.name.setGeometry(QtCore.QRect(2, 2, 95, 23))
|
||||||
self.name.setTextFormat(QtCore.Qt.PlainText)
|
self.name.setTextFormat(QtCore.Qt.PlainText)
|
||||||
@ -139,7 +135,7 @@ class MessageItem(QtGui.QWidget):
|
|||||||
self.name.setFont(font)
|
self.name.setFont(font)
|
||||||
self.name.setText(user)
|
self.name.setText(user)
|
||||||
|
|
||||||
self.time = QtGui.QLabel(self)
|
self.time = QtWidgets.QLabel(self)
|
||||||
self.time.setGeometry(QtCore.QRect(parent.width() - 60, 0, 50, 25))
|
self.time.setGeometry(QtCore.QRect(parent.width() - 60, 0, 50, 25))
|
||||||
font.setPointSize(10)
|
font.setPointSize(10)
|
||||||
font.setBold(False)
|
font.setBold(False)
|
||||||
@ -164,9 +160,9 @@ class MessageItem(QtGui.QWidget):
|
|||||||
|
|
||||||
def mouseReleaseEvent(self, event):
|
def mouseReleaseEvent(self, event):
|
||||||
if event.button() == QtCore.Qt.RightButton and event.x() > self.time.x():
|
if event.button() == QtCore.Qt.RightButton and event.x() > self.time.x():
|
||||||
self.listMenu = QtGui.QMenu()
|
self.listMenu = QtWidgets.QMenu()
|
||||||
delete_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Delete message', None, QtGui.QApplication.UnicodeUTF8))
|
delete_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Delete message'))
|
||||||
self.connect(delete_item, QtCore.SIGNAL("triggered()"), self.delete)
|
delete_item.triggered.connect(self.delete)
|
||||||
parent_position = self.time.mapToGlobal(QtCore.QPoint(0, 0))
|
parent_position = self.time.mapToGlobal(QtCore.QPoint(0, 0))
|
||||||
self.listMenu.move(parent_position)
|
self.listMenu.move(parent_position)
|
||||||
self.listMenu.show()
|
self.listMenu.show()
|
||||||
@ -216,16 +212,16 @@ class MessageItem(QtGui.QWidget):
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
class ContactItem(QtGui.QWidget):
|
class ContactItem(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
Contact in friends list
|
Contact in friends list
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QtGui.QWidget.__init__(self, parent)
|
QtWidgets.QWidget.__init__(self, parent)
|
||||||
mode = settings.Settings.get_instance()['compact_mode']
|
mode = settings.Settings.get_instance()['compact_mode']
|
||||||
self.setBaseSize(QtCore.QSize(250, 40 if mode else 70))
|
self.setBaseSize(QtCore.QSize(250, 40 if mode else 70))
|
||||||
self.avatar_label = QtGui.QLabel(self)
|
self.avatar_label = QtWidgets.QLabel(self)
|
||||||
size = 32 if mode else 64
|
size = 32 if mode else 64
|
||||||
self.avatar_label.setGeometry(QtCore.QRect(3, 4, size, size))
|
self.avatar_label.setGeometry(QtCore.QRect(3, 4, size, size))
|
||||||
self.avatar_label.setScaledContents(False)
|
self.avatar_label.setScaledContents(False)
|
||||||
@ -248,14 +244,14 @@ class ContactItem(QtGui.QWidget):
|
|||||||
self.messages.setGeometry(QtCore.QRect(20 if mode else 52, 20 if mode else 50, 30, 20))
|
self.messages.setGeometry(QtCore.QRect(20 if mode else 52, 20 if mode else 50, 30, 20))
|
||||||
|
|
||||||
|
|
||||||
class StatusCircle(QtGui.QWidget):
|
class StatusCircle(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
Connection status
|
Connection status
|
||||||
"""
|
"""
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
QtGui.QWidget.__init__(self, parent)
|
QtWidgets.QWidget.__init__(self, parent)
|
||||||
self.setGeometry(0, 0, 32, 32)
|
self.setGeometry(0, 0, 32, 32)
|
||||||
self.label = QtGui.QLabel(self)
|
self.label = QtWidgets.QLabel(self)
|
||||||
self.label.setGeometry(QtCore.QRect(0, 0, 32, 32))
|
self.label.setGeometry(QtCore.QRect(0, 0, 32, 32))
|
||||||
self.unread = False
|
self.unread = False
|
||||||
|
|
||||||
@ -281,12 +277,12 @@ class StatusCircle(QtGui.QWidget):
|
|||||||
self.label.setPixmap(pixmap)
|
self.label.setPixmap(pixmap)
|
||||||
|
|
||||||
|
|
||||||
class UnreadMessagesCount(QtGui.QWidget):
|
class UnreadMessagesCount(QtWidgets.QWidget):
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super(UnreadMessagesCount, self).__init__(parent)
|
super(UnreadMessagesCount, self).__init__(parent)
|
||||||
self.resize(30, 20)
|
self.resize(30, 20)
|
||||||
self.label = QtGui.QLabel(self)
|
self.label = QtWidgets.QLabel(self)
|
||||||
self.label.setGeometry(QtCore.QRect(0, 0, 30, 20))
|
self.label.setGeometry(QtCore.QRect(0, 0, 30, 20))
|
||||||
self.label.setVisible(False)
|
self.label.setVisible(False)
|
||||||
font = QtGui.QFont()
|
font = QtGui.QFont()
|
||||||
@ -308,11 +304,11 @@ class UnreadMessagesCount(QtGui.QWidget):
|
|||||||
self.label.setVisible(False)
|
self.label.setVisible(False)
|
||||||
|
|
||||||
|
|
||||||
class FileTransferItem(QtGui.QListWidget):
|
class FileTransferItem(QtWidgets.QListWidget):
|
||||||
|
|
||||||
def __init__(self, file_name, size, time, user, friend_number, file_number, state, width, parent=None):
|
def __init__(self, file_name, size, time, user, friend_number, file_number, state, width, parent=None):
|
||||||
|
|
||||||
QtGui.QListWidget.__init__(self, parent)
|
QtWidgets.QListWidget.__init__(self, parent)
|
||||||
self.resize(QtCore.QSize(width, 34))
|
self.resize(QtCore.QSize(width, 34))
|
||||||
if state == TOX_FILE_TRANSFER_STATE['CANCELLED']:
|
if state == TOX_FILE_TRANSFER_STATE['CANCELLED']:
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
|
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
|
||||||
@ -332,14 +328,14 @@ class FileTransferItem(QtGui.QListWidget):
|
|||||||
self.name.setFont(font)
|
self.name.setFont(font)
|
||||||
self.name.setText(user)
|
self.name.setText(user)
|
||||||
|
|
||||||
self.time = QtGui.QLabel(self)
|
self.time = QtWidgets.QLabel(self)
|
||||||
self.time.setGeometry(QtCore.QRect(width - 60, 7, 50, 25))
|
self.time.setGeometry(QtCore.QRect(width - 60, 7, 50, 25))
|
||||||
font.setPointSize(10)
|
font.setPointSize(10)
|
||||||
font.setBold(False)
|
font.setBold(False)
|
||||||
self.time.setFont(font)
|
self.time.setFont(font)
|
||||||
self.time.setText(convert_time(time))
|
self.time.setText(convert_time(time))
|
||||||
|
|
||||||
self.cancel = QtGui.QPushButton(self)
|
self.cancel = QtWidgets.QPushButton(self)
|
||||||
self.cancel.setGeometry(QtCore.QRect(width - 125, 2, 30, 30))
|
self.cancel.setGeometry(QtCore.QRect(width - 125, 2, 30, 30))
|
||||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/decline.png')
|
pixmap = QtGui.QPixmap(curr_directory() + '/images/decline.png')
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
@ -349,7 +345,7 @@ class FileTransferItem(QtGui.QListWidget):
|
|||||||
self.cancel.clicked.connect(lambda: self.cancel_transfer(friend_number, file_number))
|
self.cancel.clicked.connect(lambda: self.cancel_transfer(friend_number, file_number))
|
||||||
self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}')
|
self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}')
|
||||||
|
|
||||||
self.accept_or_pause = QtGui.QPushButton(self)
|
self.accept_or_pause = QtWidgets.QPushButton(self)
|
||||||
self.accept_or_pause.setGeometry(QtCore.QRect(width - 170, 2, 30, 30))
|
self.accept_or_pause.setGeometry(QtCore.QRect(width - 170, 2, 30, 30))
|
||||||
if state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
|
if state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
|
||||||
self.accept_or_pause.setVisible(True)
|
self.accept_or_pause.setVisible(True)
|
||||||
@ -366,7 +362,7 @@ class FileTransferItem(QtGui.QListWidget):
|
|||||||
|
|
||||||
self.accept_or_pause.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none}')
|
self.accept_or_pause.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none}')
|
||||||
|
|
||||||
self.pb = QtGui.QProgressBar(self)
|
self.pb = QtWidgets.QProgressBar(self)
|
||||||
self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20))
|
self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20))
|
||||||
self.pb.setValue(0)
|
self.pb.setValue(0)
|
||||||
self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }')
|
self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }')
|
||||||
@ -387,7 +383,7 @@ class FileTransferItem(QtGui.QListWidget):
|
|||||||
self.file_name.setText(file_data)
|
self.file_name.setText(file_data)
|
||||||
self.file_name.setToolTip(file_name)
|
self.file_name.setToolTip(file_name)
|
||||||
self.saved_name = file_name
|
self.saved_name = file_name
|
||||||
self.time_left = QtGui.QLabel(self)
|
self.time_left = QtWidgets.QLabel(self)
|
||||||
self.time_left.setGeometry(QtCore.QRect(width - 92, 7, 30, 20))
|
self.time_left.setGeometry(QtCore.QRect(width - 92, 7, 30, 20))
|
||||||
font.setPointSize(10)
|
font.setPointSize(10)
|
||||||
self.time_left.setFont(font)
|
self.time_left.setFont(font)
|
||||||
@ -405,10 +401,10 @@ class FileTransferItem(QtGui.QListWidget):
|
|||||||
|
|
||||||
def accept_or_pause_transfer(self, friend_number, file_number, size):
|
def accept_or_pause_transfer(self, friend_number, file_number, size):
|
||||||
if self.state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
|
if self.state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
|
||||||
directory = QtGui.QFileDialog.getExistingDirectory(self,
|
directory = QtWidgets.QFileDialog.getExistingDirectory(self,
|
||||||
QtGui.QApplication.translate("MainWindow", 'Choose folder', None, QtGui.QApplication.UnicodeUTF8),
|
QtWidgets.QApplication.translate("MainWindow", 'Choose folder'),
|
||||||
curr_directory(),
|
curr_directory(),
|
||||||
QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog)
|
QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
|
||||||
self.pb.setVisible(True)
|
self.pb.setVisible(True)
|
||||||
if directory:
|
if directory:
|
||||||
pr = profile.Profile.get_instance()
|
pr = profile.Profile.get_instance()
|
||||||
@ -432,8 +428,7 @@ class FileTransferItem(QtGui.QListWidget):
|
|||||||
self.accept_or_pause.setIcon(icon)
|
self.accept_or_pause.setIcon(icon)
|
||||||
self.accept_or_pause.setIconSize(QtCore.QSize(30, 30))
|
self.accept_or_pause.setIconSize(QtCore.QSize(30, 30))
|
||||||
|
|
||||||
@QtCore.Slot(int, float, int)
|
def update_transfer_state(self, state, progress, time):
|
||||||
def update(self, state, progress, time):
|
|
||||||
self.pb.setValue(int(progress * 100))
|
self.pb.setValue(int(progress * 100))
|
||||||
if time + 1:
|
if time + 1:
|
||||||
m, s = divmod(time, 60)
|
m, s = divmod(time, 60)
|
||||||
@ -496,14 +491,14 @@ class UnsentFileItem(FileTransferItem):
|
|||||||
pr.cancel_not_started_transfer(self._time)
|
pr.cancel_not_started_transfer(self._time)
|
||||||
|
|
||||||
|
|
||||||
class InlineImageItem(QtGui.QScrollArea):
|
class InlineImageItem(QtWidgets.QScrollArea):
|
||||||
|
|
||||||
def __init__(self, data, width, elem):
|
def __init__(self, data, width, elem):
|
||||||
|
|
||||||
QtGui.QScrollArea.__init__(self)
|
QtWidgets.QScrollArea.__init__(self)
|
||||||
self.setFocusPolicy(QtCore.Qt.NoFocus)
|
self.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||||
self._elem = elem
|
self._elem = elem
|
||||||
self._image_label = QtGui.QLabel(self)
|
self._image_label = QtWidgets.QLabel(self)
|
||||||
self._image_label.raise_()
|
self._image_label.raise_()
|
||||||
self.setWidget(self._image_label)
|
self.setWidget(self._image_label)
|
||||||
self._image_label.setScaledContents(False)
|
self._image_label.setScaledContents(False)
|
||||||
@ -537,21 +532,14 @@ class InlineImageItem(QtGui.QScrollArea):
|
|||||||
self._full_size = not self._full_size
|
self._full_size = not self._full_size
|
||||||
self._elem.setSizeHint(QtCore.QSize(self.width(), self.height()))
|
self._elem.setSizeHint(QtCore.QSize(self.width(), self.height()))
|
||||||
elif event.button() == QtCore.Qt.RightButton: # save inline
|
elif event.button() == QtCore.Qt.RightButton: # save inline
|
||||||
directory = QtGui.QFileDialog.getExistingDirectory(self,
|
directory = QtWidgets.QFileDialog.getExistingDirectory(self,
|
||||||
QtGui.QApplication.translate("MainWindow",
|
QtWidgets.QApplication.translate("MainWindow",
|
||||||
'Choose folder', None,
|
'Choose folder'),
|
||||||
QtGui.QApplication.UnicodeUTF8),
|
|
||||||
curr_directory(),
|
curr_directory(),
|
||||||
QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog)
|
QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
|
||||||
if directory:
|
if directory:
|
||||||
fl = QtCore.QFile(directory + '/toxygen_inline_' + curr_time().replace(':', '_') + '.png')
|
fl = QtCore.QFile(directory + '/toxygen_inline_' + curr_time().replace(':', '_') + '.png')
|
||||||
self._pixmap.save(fl, 'PNG')
|
self._pixmap.save(fl, 'PNG')
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def mark_as_sent(self):
|
def mark_as_sent(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
from PyQt5 import QtWidgets, QtCore
|
||||||
|
|
||||||
try:
|
|
||||||
from PySide import QtCore, QtGui
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
from widgets import *
|
from widgets import *
|
||||||
|
|
||||||
|
|
||||||
@ -31,25 +26,25 @@ class LoginScreen(CenteredWidget):
|
|||||||
self.resize(400, 200)
|
self.resize(400, 200)
|
||||||
self.setMinimumSize(QtCore.QSize(400, 200))
|
self.setMinimumSize(QtCore.QSize(400, 200))
|
||||||
self.setMaximumSize(QtCore.QSize(400, 200))
|
self.setMaximumSize(QtCore.QSize(400, 200))
|
||||||
self.new_profile = QtGui.QPushButton(self)
|
self.new_profile = QtWidgets.QPushButton(self)
|
||||||
self.new_profile.setGeometry(QtCore.QRect(20, 150, 171, 27))
|
self.new_profile.setGeometry(QtCore.QRect(20, 150, 171, 27))
|
||||||
self.new_profile.clicked.connect(self.create_profile)
|
self.new_profile.clicked.connect(self.create_profile)
|
||||||
self.label = QtGui.QLabel(self)
|
self.label = QtWidgets.QLabel(self)
|
||||||
self.label.setGeometry(QtCore.QRect(20, 70, 101, 17))
|
self.label.setGeometry(QtCore.QRect(20, 70, 101, 17))
|
||||||
self.new_name = NickEdit(self)
|
self.new_name = NickEdit(self)
|
||||||
self.new_name.setGeometry(QtCore.QRect(20, 100, 171, 31))
|
self.new_name.setGeometry(QtCore.QRect(20, 100, 171, 31))
|
||||||
self.load_profile = QtGui.QPushButton(self)
|
self.load_profile = QtWidgets.QPushButton(self)
|
||||||
self.load_profile.setGeometry(QtCore.QRect(220, 150, 161, 27))
|
self.load_profile.setGeometry(QtCore.QRect(220, 150, 161, 27))
|
||||||
self.load_profile.clicked.connect(self.load_ex_profile)
|
self.load_profile.clicked.connect(self.load_ex_profile)
|
||||||
self.default = QtGui.QCheckBox(self)
|
self.default = QtWidgets.QCheckBox(self)
|
||||||
self.default.setGeometry(QtCore.QRect(220, 110, 131, 22))
|
self.default.setGeometry(QtCore.QRect(220, 110, 131, 22))
|
||||||
self.groupBox = QtGui.QGroupBox(self)
|
self.groupBox = QtWidgets.QGroupBox(self)
|
||||||
self.groupBox.setGeometry(QtCore.QRect(210, 40, 181, 151))
|
self.groupBox.setGeometry(QtCore.QRect(210, 40, 181, 151))
|
||||||
self.comboBox = QtGui.QComboBox(self.groupBox)
|
self.comboBox = QtWidgets.QComboBox(self.groupBox)
|
||||||
self.comboBox.setGeometry(QtCore.QRect(10, 30, 161, 27))
|
self.comboBox.setGeometry(QtCore.QRect(10, 30, 161, 27))
|
||||||
self.groupBox_2 = QtGui.QGroupBox(self)
|
self.groupBox_2 = QtWidgets.QGroupBox(self)
|
||||||
self.groupBox_2.setGeometry(QtCore.QRect(10, 40, 191, 151))
|
self.groupBox_2.setGeometry(QtCore.QRect(10, 40, 191, 151))
|
||||||
self.toxygen = QtGui.QLabel(self)
|
self.toxygen = QtWidgets.QLabel(self)
|
||||||
self.groupBox.raise_()
|
self.groupBox.raise_()
|
||||||
self.groupBox_2.raise_()
|
self.groupBox_2.raise_()
|
||||||
self.comboBox.raise_()
|
self.comboBox.raise_()
|
||||||
@ -71,15 +66,15 @@ class LoginScreen(CenteredWidget):
|
|||||||
QtCore.QMetaObject.connectSlotsByName(self)
|
QtCore.QMetaObject.connectSlotsByName(self)
|
||||||
|
|
||||||
def retranslateUi(self):
|
def retranslateUi(self):
|
||||||
self.new_name.setPlaceholderText(QtGui.QApplication.translate("login", "Profile name", None, QtGui.QApplication.UnicodeUTF8))
|
self.new_name.setPlaceholderText(QtWidgets.QApplication.translate("login", "Profile name"))
|
||||||
self.setWindowTitle(QtGui.QApplication.translate("login", "Log in", None, QtGui.QApplication.UnicodeUTF8))
|
self.setWindowTitle(QtWidgets.QApplication.translate("login", "Log in"))
|
||||||
self.new_profile.setText(QtGui.QApplication.translate("login", "Create", None, QtGui.QApplication.UnicodeUTF8))
|
self.new_profile.setText(QtWidgets.QApplication.translate("login", "Create"))
|
||||||
self.label.setText(QtGui.QApplication.translate("login", "Profile name:", None, QtGui.QApplication.UnicodeUTF8))
|
self.label.setText(QtWidgets.QApplication.translate("login", "Profile name:"))
|
||||||
self.load_profile.setText(QtGui.QApplication.translate("login", "Load profile", None, QtGui.QApplication.UnicodeUTF8))
|
self.load_profile.setText(QtWidgets.QApplication.translate("login", "Load profile"))
|
||||||
self.default.setText(QtGui.QApplication.translate("login", "Use as default", None, QtGui.QApplication.UnicodeUTF8))
|
self.default.setText(QtWidgets.QApplication.translate("login", "Use as default"))
|
||||||
self.groupBox.setTitle(QtGui.QApplication.translate("login", "Load existing profile", None, QtGui.QApplication.UnicodeUTF8))
|
self.groupBox.setTitle(QtWidgets.QApplication.translate("login", "Load existing profile"))
|
||||||
self.groupBox_2.setTitle(QtGui.QApplication.translate("login", "Create new profile", None, QtGui.QApplication.UnicodeUTF8))
|
self.groupBox_2.setTitle(QtWidgets.QApplication.translate("login", "Create new profile"))
|
||||||
self.toxygen.setText(QtGui.QApplication.translate("login", "toxygen", None, QtGui.QApplication.UnicodeUTF8))
|
self.toxygen.setText(QtWidgets.QApplication.translate("login", "toxygen"))
|
||||||
|
|
||||||
def create_profile(self):
|
def create_profile(self):
|
||||||
self.type = 1
|
self.type = 1
|
||||||
|
189
toxygen/main.py
@ -2,15 +2,12 @@ import sys
|
|||||||
from loginscreen import LoginScreen
|
from loginscreen import LoginScreen
|
||||||
import profile
|
import profile
|
||||||
from settings import *
|
from settings import *
|
||||||
try:
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
from PySide import QtCore, QtGui
|
from bootstrap import generate_nodes, download_nodes_list
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
from bootstrap import node_generator
|
|
||||||
from mainscreen import MainWindow
|
from mainscreen import MainWindow
|
||||||
from callbacks import init_callbacks, stop, start
|
from callbacks import init_callbacks, stop, start
|
||||||
from util import curr_directory, program_version, remove, is_64_bit
|
from util import curr_directory, program_version, remove
|
||||||
import styles.style
|
import styles.style # reqired for styles loading
|
||||||
import platform
|
import platform
|
||||||
import toxes
|
import toxes
|
||||||
from passwordscreen import PasswordScreen, UnlockAppScreen, SetProfilePasswordScreen
|
from passwordscreen import PasswordScreen, UnlockAppScreen, SetProfilePasswordScreen
|
||||||
@ -39,7 +36,7 @@ class Toxygen:
|
|||||||
tmp = [data]
|
tmp = [data]
|
||||||
p = PasswordScreen(toxes.ToxES.get_instance(), tmp)
|
p = PasswordScreen(toxes.ToxES.get_instance(), tmp)
|
||||||
p.show()
|
p.show()
|
||||||
self.app.connect(self.app, QtCore.SIGNAL("lastWindowClosed()"), self.app, QtCore.SLOT("quit()"))
|
self.app.lastWindowClosed.connect(self.app.quit)
|
||||||
self.app.exec_()
|
self.app.exec_()
|
||||||
if tmp[0] == data:
|
if tmp[0] == data:
|
||||||
raise SystemExit()
|
raise SystemExit()
|
||||||
@ -50,17 +47,16 @@ class Toxygen:
|
|||||||
"""
|
"""
|
||||||
Main function of app. loads login screen if needed and starts main screen
|
Main function of app. loads login screen if needed and starts main screen
|
||||||
"""
|
"""
|
||||||
app = QtGui.QApplication(sys.argv)
|
app = QtWidgets.QApplication(sys.argv)
|
||||||
app.setWindowIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
|
app.setWindowIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
if platform.system() == 'Linux':
|
if platform.system() == 'Linux':
|
||||||
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
|
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
|
||||||
|
|
||||||
# application color scheme
|
with open(curr_directory() + '/styles/dark_style.qss') as fl:
|
||||||
with open(curr_directory() + '/styles/style.qss') as fl:
|
style = fl.read()
|
||||||
dark_style = fl.read()
|
app.setStyleSheet(style)
|
||||||
app.setStyleSheet(dark_style)
|
|
||||||
|
|
||||||
encrypt_save = toxes.ToxES()
|
encrypt_save = toxes.ToxES()
|
||||||
|
|
||||||
@ -92,7 +88,6 @@ class Toxygen:
|
|||||||
_login = self.Login(profiles)
|
_login = self.Login(profiles)
|
||||||
ls.update_on_close(_login.login_screen_close)
|
ls.update_on_close(_login.login_screen_close)
|
||||||
ls.show()
|
ls.show()
|
||||||
app.connect(app, QtCore.SIGNAL("lastWindowClosed()"), app, QtCore.SLOT("quit()"))
|
|
||||||
app.exec_()
|
app.exec_()
|
||||||
if not _login.t:
|
if not _login.t:
|
||||||
return
|
return
|
||||||
@ -101,40 +96,35 @@ class Toxygen:
|
|||||||
name = _login.name if _login.name else 'toxygen_user'
|
name = _login.name if _login.name else 'toxygen_user'
|
||||||
pr = map(lambda x: x[1], ProfileHelper.find_profiles())
|
pr = map(lambda x: x[1], ProfileHelper.find_profiles())
|
||||||
if name in list(pr):
|
if name in list(pr):
|
||||||
msgBox = QtGui.QMessageBox()
|
msgBox = QtWidgets.QMessageBox()
|
||||||
msgBox.setWindowTitle(
|
msgBox.setWindowTitle(
|
||||||
QtGui.QApplication.translate("MainWindow", "Error", None, QtGui.QApplication.UnicodeUTF8))
|
QtWidgets.QApplication.translate("MainWindow", "Error"))
|
||||||
text = (QtGui.QApplication.translate("MainWindow",
|
text = (QtWidgets.QApplication.translate("MainWindow",
|
||||||
'Profile with this name already exists',
|
'Profile with this name already exists'))
|
||||||
None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
msgBox.setText(text)
|
msgBox.setText(text)
|
||||||
msgBox.exec_()
|
msgBox.exec_()
|
||||||
return
|
return
|
||||||
self.tox = profile.tox_factory()
|
self.tox = profile.tox_factory()
|
||||||
self.tox.self_set_name(bytes(_login.name, 'utf-8') if _login.name else b'Toxygen User')
|
self.tox.self_set_name(bytes(_login.name, 'utf-8') if _login.name else b'Toxygen User')
|
||||||
self.tox.self_set_status_message(b'Toxing on Toxygen')
|
self.tox.self_set_status_message(b'Toxing on Toxygen')
|
||||||
reply = QtGui.QMessageBox.question(None,
|
reply = QtWidgets.QMessageBox.question(None,
|
||||||
'Profile {}'.format(name),
|
'Profile {}'.format(name),
|
||||||
QtGui.QApplication.translate("login",
|
QtWidgets.QApplication.translate("login",
|
||||||
'Do you want to set profile password?',
|
'Do you want to set profile password?'),
|
||||||
None,
|
QtWidgets.QMessageBox.Yes,
|
||||||
QtGui.QApplication.UnicodeUTF8),
|
QtWidgets.QMessageBox.No)
|
||||||
QtGui.QMessageBox.Yes,
|
if reply == QtWidgets.QMessageBox.Yes:
|
||||||
QtGui.QMessageBox.No)
|
|
||||||
if reply == QtGui.QMessageBox.Yes:
|
|
||||||
set_pass = SetProfilePasswordScreen(encrypt_save)
|
set_pass = SetProfilePasswordScreen(encrypt_save)
|
||||||
set_pass.show()
|
set_pass.show()
|
||||||
self.app.connect(self.app, QtCore.SIGNAL("lastWindowClosed()"), self.app, QtCore.SLOT("quit()"))
|
self.app.lastWindowClosed.connect(self.app.quit)
|
||||||
self.app.exec_()
|
self.app.exec_()
|
||||||
reply = QtGui.QMessageBox.question(None,
|
reply = QtWidgets.QMessageBox.question(None,
|
||||||
'Profile {}'.format(name),
|
'Profile {}'.format(name),
|
||||||
QtGui.QApplication.translate("login",
|
QtWidgets.QApplication.translate("login",
|
||||||
'Do you want to save profile in default folder? If no, profile will be saved in program folder',
|
'Do you want to save profile in default folder? If no, profile will be saved in program folder'),
|
||||||
None,
|
QtWidgets.QMessageBox.Yes,
|
||||||
QtGui.QApplication.UnicodeUTF8),
|
QtWidgets.QMessageBox.No)
|
||||||
QtGui.QMessageBox.Yes,
|
if reply == QtWidgets.QMessageBox.Yes:
|
||||||
QtGui.QMessageBox.No)
|
|
||||||
if reply == QtGui.QMessageBox.Yes:
|
|
||||||
path = Settings.get_default_path()
|
path = Settings.get_default_path()
|
||||||
else:
|
else:
|
||||||
path = curr_directory() + '/'
|
path = curr_directory() + '/'
|
||||||
@ -143,11 +133,9 @@ class Toxygen:
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print(str(ex))
|
print(str(ex))
|
||||||
log('Profile creation exception: ' + str(ex))
|
log('Profile creation exception: ' + str(ex))
|
||||||
msgBox = QtGui.QMessageBox()
|
msgBox = QtWidgets.QMessageBox()
|
||||||
msgBox.setText(QtGui.QApplication.translate("login",
|
msgBox.setText(QtWidgets.QApplication.translate("login",
|
||||||
'Profile saving error! Does Toxygen have permission to write to this directory?',
|
'Profile saving error! Does Toxygen have permission to write to this directory?'))
|
||||||
None,
|
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
msgBox.exec_()
|
msgBox.exec_()
|
||||||
return
|
return
|
||||||
path = Settings.get_default_path()
|
path = Settings.get_default_path()
|
||||||
@ -173,16 +161,23 @@ class Toxygen:
|
|||||||
self.tox = profile.tox_factory(data, settings)
|
self.tox = profile.tox_factory(data, settings)
|
||||||
|
|
||||||
if Settings.is_active_profile(path, name): # profile is in use
|
if Settings.is_active_profile(path, name): # profile is in use
|
||||||
reply = QtGui.QMessageBox.question(None,
|
reply = QtWidgets.QMessageBox.question(None,
|
||||||
'Profile {}'.format(name),
|
'Profile {}'.format(name),
|
||||||
QtGui.QApplication.translate("login", 'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?', None, QtGui.QApplication.UnicodeUTF8),
|
QtWidgets.QApplication.translate("login", 'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?'),
|
||||||
QtGui.QMessageBox.Yes,
|
QtWidgets.QMessageBox.Yes,
|
||||||
QtGui.QMessageBox.No)
|
QtWidgets.QMessageBox.No)
|
||||||
if reply != QtGui.QMessageBox.Yes:
|
if reply != QtWidgets.QMessageBox.Yes:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
settings.set_active_profile()
|
settings.set_active_profile()
|
||||||
|
|
||||||
|
# application color scheme
|
||||||
|
for theme in settings.built_in_themes().keys():
|
||||||
|
if settings['theme'] == theme:
|
||||||
|
with open(curr_directory() + settings.built_in_themes()[theme]) as fl:
|
||||||
|
style = fl.read()
|
||||||
|
app.setStyleSheet(style)
|
||||||
|
|
||||||
lang = Settings.supported_languages()[settings['language']]
|
lang = Settings.supported_languages()[settings['language']]
|
||||||
translator = QtCore.QTranslator()
|
translator = QtCore.QTranslator()
|
||||||
translator.load(curr_directory() + '/translations/' + lang)
|
translator.load(curr_directory() + '/translations/' + lang)
|
||||||
@ -190,21 +185,21 @@ class Toxygen:
|
|||||||
app.translator = translator
|
app.translator = translator
|
||||||
|
|
||||||
# tray icon
|
# tray icon
|
||||||
self.tray = QtGui.QSystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
|
self.tray = QtWidgets.QSystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
|
||||||
self.tray.setObjectName('tray')
|
self.tray.setObjectName('tray')
|
||||||
|
|
||||||
self.ms = MainWindow(self.tox, self.reset, self.tray)
|
self.ms = MainWindow(self.tox, self.reset, self.tray)
|
||||||
app.aboutToQuit.connect(self.ms.close_window)
|
app.aboutToQuit.connect(self.ms.close_window)
|
||||||
|
|
||||||
class Menu(QtGui.QMenu):
|
class Menu(QtWidgets.QMenu):
|
||||||
|
|
||||||
def newStatus(self, status):
|
def newStatus(self, status):
|
||||||
if not Settings.get_instance().locked:
|
if not Settings.get_instance().locked:
|
||||||
profile.Profile.get_instance().set_status(status)
|
profile.Profile.get_instance().set_status(status)
|
||||||
self.aboutToShow()
|
self.aboutToShowHandler()
|
||||||
self.hide()
|
self.hide()
|
||||||
|
|
||||||
def aboutToShow(self):
|
def aboutToShowHandler(self):
|
||||||
status = profile.Profile.get_instance().status
|
status = profile.Profile.get_instance().status
|
||||||
act = self.act
|
act = self.act
|
||||||
if status is None or Settings.get_instance().locked:
|
if status is None or Settings.get_instance().locked:
|
||||||
@ -218,42 +213,47 @@ class Toxygen:
|
|||||||
self.actions()[2].setVisible(not Settings.get_instance().locked)
|
self.actions()[2].setVisible(not Settings.get_instance().locked)
|
||||||
|
|
||||||
def languageChange(self, *args, **kwargs):
|
def languageChange(self, *args, **kwargs):
|
||||||
self.actions()[0].setText(QtGui.QApplication.translate('tray', 'Open Toxygen', None, QtGui.QApplication.UnicodeUTF8))
|
self.actions()[0].setText(QtWidgets.QApplication.translate('tray', 'Open Toxygen'))
|
||||||
self.actions()[1].setText(QtGui.QApplication.translate('tray', 'Set status', None, QtGui.QApplication.UnicodeUTF8))
|
self.actions()[1].setText(QtWidgets.QApplication.translate('tray', 'Set status'))
|
||||||
self.actions()[2].setText(QtGui.QApplication.translate('tray', 'Exit', None, QtGui.QApplication.UnicodeUTF8))
|
self.actions()[2].setText(QtWidgets.QApplication.translate('tray', 'Exit'))
|
||||||
self.act.actions()[0].setText(QtGui.QApplication.translate('tray', 'Online', None, QtGui.QApplication.UnicodeUTF8))
|
self.act.actions()[0].setText(QtWidgets.QApplication.translate('tray', 'Online'))
|
||||||
self.act.actions()[1].setText(QtGui.QApplication.translate('tray', 'Away', None, QtGui.QApplication.UnicodeUTF8))
|
self.act.actions()[1].setText(QtWidgets.QApplication.translate('tray', 'Away'))
|
||||||
self.act.actions()[2].setText(QtGui.QApplication.translate('tray', 'Busy', None, QtGui.QApplication.UnicodeUTF8))
|
self.act.actions()[2].setText(QtWidgets.QApplication.translate('tray', 'Busy'))
|
||||||
|
|
||||||
m = Menu()
|
m = Menu()
|
||||||
show = m.addAction(QtGui.QApplication.translate('tray', 'Open Toxygen', None, QtGui.QApplication.UnicodeUTF8))
|
show = m.addAction(QtWidgets.QApplication.translate('tray', 'Open Toxygen'))
|
||||||
sub = m.addMenu(QtGui.QApplication.translate('tray', 'Set status', None, QtGui.QApplication.UnicodeUTF8))
|
sub = m.addMenu(QtWidgets.QApplication.translate('tray', 'Set status'))
|
||||||
onl = sub.addAction(QtGui.QApplication.translate('tray', 'Online', None, QtGui.QApplication.UnicodeUTF8))
|
onl = sub.addAction(QtWidgets.QApplication.translate('tray', 'Online'))
|
||||||
away = sub.addAction(QtGui.QApplication.translate('tray', 'Away', None, QtGui.QApplication.UnicodeUTF8))
|
away = sub.addAction(QtWidgets.QApplication.translate('tray', 'Away'))
|
||||||
busy = sub.addAction(QtGui.QApplication.translate('tray', 'Busy', None, QtGui.QApplication.UnicodeUTF8))
|
busy = sub.addAction(QtWidgets.QApplication.translate('tray', 'Busy'))
|
||||||
onl.setCheckable(True)
|
onl.setCheckable(True)
|
||||||
away.setCheckable(True)
|
away.setCheckable(True)
|
||||||
busy.setCheckable(True)
|
busy.setCheckable(True)
|
||||||
m.act = sub
|
m.act = sub
|
||||||
exit = m.addAction(QtGui.QApplication.translate('tray', 'Exit', None, QtGui.QApplication.UnicodeUTF8))
|
exit = m.addAction(QtWidgets.QApplication.translate('tray', 'Exit'))
|
||||||
|
|
||||||
def show_window():
|
def show_window():
|
||||||
|
s = Settings.get_instance()
|
||||||
|
|
||||||
def show():
|
def show():
|
||||||
if not self.ms.isActiveWindow():
|
if not self.ms.isActiveWindow():
|
||||||
self.ms.setWindowState(self.ms.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
|
self.ms.setWindowState(self.ms.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
|
||||||
self.ms.activateWindow()
|
self.ms.activateWindow()
|
||||||
self.ms.show()
|
self.ms.show()
|
||||||
if not Settings.get_instance().locked:
|
if not s.locked:
|
||||||
show()
|
show()
|
||||||
else:
|
else:
|
||||||
def correct_pass():
|
def correct_pass():
|
||||||
show()
|
show()
|
||||||
Settings.get_instance().locked = False
|
s.locked = False
|
||||||
|
s.unlockScreen = False
|
||||||
|
if not s.unlockScreen:
|
||||||
|
s.unlockScreen = True
|
||||||
self.p = UnlockAppScreen(toxes.ToxES.get_instance(), correct_pass)
|
self.p = UnlockAppScreen(toxes.ToxES.get_instance(), correct_pass)
|
||||||
self.p.show()
|
self.p.show()
|
||||||
|
|
||||||
def tray_activated(reason):
|
def tray_activated(reason):
|
||||||
if reason == QtGui.QSystemTrayIcon.DoubleClick:
|
if reason == QtWidgets.QSystemTrayIcon.DoubleClick:
|
||||||
show_window()
|
show_window()
|
||||||
|
|
||||||
def close_app():
|
def close_app():
|
||||||
@ -261,12 +261,12 @@ class Toxygen:
|
|||||||
settings.closing = True
|
settings.closing = True
|
||||||
self.ms.close()
|
self.ms.close()
|
||||||
|
|
||||||
m.connect(show, QtCore.SIGNAL("triggered()"), show_window)
|
show.triggered.connect(show_window)
|
||||||
m.connect(exit, QtCore.SIGNAL("triggered()"), close_app)
|
exit.triggered.connect(close_app)
|
||||||
m.connect(m, QtCore.SIGNAL("aboutToShow()"), lambda: m.aboutToShow())
|
m.aboutToShow.connect(lambda: m.aboutToShowHandler())
|
||||||
sub.connect(onl, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(0))
|
onl.triggered.connect(lambda: m.newStatus(0))
|
||||||
sub.connect(away, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(1))
|
away.triggered.connect(lambda: m.newStatus(1))
|
||||||
sub.connect(busy, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(2))
|
busy.triggered.connect(lambda: m.newStatus(2))
|
||||||
|
|
||||||
self.tray.setContextMenu(m)
|
self.tray.setContextMenu(m)
|
||||||
self.tray.show()
|
self.tray.show()
|
||||||
@ -282,15 +282,13 @@ class Toxygen:
|
|||||||
updater.download(version)
|
updater.download(version)
|
||||||
updating = True
|
updating = True
|
||||||
else:
|
else:
|
||||||
reply = QtGui.QMessageBox.question(None,
|
reply = QtWidgets.QMessageBox.question(None,
|
||||||
'Toxygen',
|
'Toxygen',
|
||||||
QtGui.QApplication.translate("login",
|
QtWidgets.QApplication.translate("login",
|
||||||
'Update for Toxygen was found. Download and install it?',
|
'Update for Toxygen was found. Download and install it?'),
|
||||||
None,
|
QtWidgets.QMessageBox.Yes,
|
||||||
QtGui.QApplication.UnicodeUTF8),
|
QtWidgets.QMessageBox.No)
|
||||||
QtGui.QMessageBox.Yes,
|
if reply == QtWidgets.QMessageBox.Yes:
|
||||||
QtGui.QMessageBox.No)
|
|
||||||
if reply == QtGui.QMessageBox.Yes:
|
|
||||||
updater.download(version)
|
updater.download(version)
|
||||||
updating = True
|
updating = True
|
||||||
|
|
||||||
@ -318,7 +316,7 @@ class Toxygen:
|
|||||||
if self.uri is not None:
|
if self.uri is not None:
|
||||||
self.ms.add_contact(self.uri)
|
self.ms.add_contact(self.uri)
|
||||||
|
|
||||||
app.connect(app, QtCore.SIGNAL("lastWindowClosed()"), app, QtCore.SLOT("quit()"))
|
app.lastWindowClosed.connect(app.quit)
|
||||||
app.exec_()
|
app.exec_()
|
||||||
|
|
||||||
self.init.stop = True
|
self.init.stop = True
|
||||||
@ -329,6 +327,7 @@ class Toxygen:
|
|||||||
self.mainloop.wait()
|
self.mainloop.wait()
|
||||||
self.init.wait()
|
self.init.wait()
|
||||||
self.avloop.wait()
|
self.avloop.wait()
|
||||||
|
self.tray.hide()
|
||||||
data = self.tox.get_savedata()
|
data = self.tox.get_savedata()
|
||||||
ProfileHelper.get_instance().save_profile(data)
|
ProfileHelper.get_instance().save_profile(data)
|
||||||
settings.close()
|
settings.close()
|
||||||
@ -380,9 +379,11 @@ class Toxygen:
|
|||||||
def run(self):
|
def run(self):
|
||||||
# initializing callbacks
|
# initializing callbacks
|
||||||
init_callbacks(self.tox, self.ms, self.tray)
|
init_callbacks(self.tox, self.ms, self.tray)
|
||||||
|
# download list of nodes if needed
|
||||||
|
download_nodes_list()
|
||||||
# bootstrap
|
# bootstrap
|
||||||
try:
|
try:
|
||||||
for data in node_generator():
|
for data in generate_nodes():
|
||||||
if self.stop:
|
if self.stop:
|
||||||
return
|
return
|
||||||
self.tox.bootstrap(*data)
|
self.tox.bootstrap(*data)
|
||||||
@ -395,7 +396,7 @@ class Toxygen:
|
|||||||
self.msleep(1000)
|
self.msleep(1000)
|
||||||
while not self.tox.self_get_connection_status():
|
while not self.tox.self_get_connection_status():
|
||||||
try:
|
try:
|
||||||
for data in node_generator():
|
for data in generate_nodes():
|
||||||
if self.stop:
|
if self.stop:
|
||||||
return
|
return
|
||||||
self.tox.bootstrap(*data)
|
self.tox.bootstrap(*data)
|
||||||
@ -456,27 +457,6 @@ def clean():
|
|||||||
remove(d)
|
remove(d)
|
||||||
|
|
||||||
|
|
||||||
def configure():
|
|
||||||
"""Removes unused libs"""
|
|
||||||
d = curr_directory() + '/libs/'
|
|
||||||
is_64bits = is_64_bit()
|
|
||||||
if not is_64bits:
|
|
||||||
if os.path.exists(d + 'libtox64.dll'):
|
|
||||||
os.remove(d + 'libtox64.dll')
|
|
||||||
if os.path.exists(d + 'libsodium64.a'):
|
|
||||||
os.remove(d + 'libsodium64.a')
|
|
||||||
else:
|
|
||||||
if os.path.exists(d + 'libtox.dll'):
|
|
||||||
os.remove(d + 'libtox.dll')
|
|
||||||
if os.path.exists(d + 'libsodium.a'):
|
|
||||||
os.remove(d + 'libsodium.a')
|
|
||||||
try:
|
|
||||||
os.rename(d + 'libtox64.dll', d + 'libtox.dll')
|
|
||||||
os.rename(d + 'libsodium64.a', d + 'libsodium.a')
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def reset():
|
def reset():
|
||||||
Settings.reset_auto_profile()
|
Settings.reset_auto_profile()
|
||||||
|
|
||||||
@ -492,9 +472,6 @@ def main():
|
|||||||
elif arg == '--help':
|
elif arg == '--help':
|
||||||
print('Usage:\ntoxygen path_to_profile\ntoxygen tox_id\ntoxygen --version\ntoxygen --reset')
|
print('Usage:\ntoxygen path_to_profile\ntoxygen tox_id\ntoxygen --version\ntoxygen --reset')
|
||||||
return
|
return
|
||||||
elif arg == '--configure':
|
|
||||||
configure()
|
|
||||||
return
|
|
||||||
elif arg == '--clean':
|
elif arg == '--clean':
|
||||||
clean()
|
clean()
|
||||||
return
|
return
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
from menu import *
|
from menu import *
|
||||||
from profile import *
|
from profile import *
|
||||||
from list_items import *
|
from list_items import *
|
||||||
from widgets import MultilineEdit, LineEdit, ComboBox
|
from widgets import MultilineEdit, ComboBox
|
||||||
import plugin_support
|
import plugin_support
|
||||||
from mainscreen_widgets import *
|
from mainscreen_widgets import *
|
||||||
import settings
|
import settings
|
||||||
import platform
|
|
||||||
import toxes
|
import toxes
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QtGui.QMainWindow, Singleton):
|
class MainWindow(QtWidgets.QMainWindow, Singleton):
|
||||||
|
|
||||||
def __init__(self, tox, reset, tray):
|
def __init__(self, tox, reset, tray):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -22,51 +21,50 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
if settings.Settings.get_instance()['show_welcome_screen']:
|
if settings.Settings.get_instance()['show_welcome_screen']:
|
||||||
self.ws = WelcomeScreen()
|
self.ws = WelcomeScreen()
|
||||||
|
|
||||||
def setup_menu(self, Form):
|
def setup_menu(self, window):
|
||||||
box = QtGui.QHBoxLayout()
|
self.menubar = QtWidgets.QMenuBar(window)
|
||||||
box.setContentsMargins(0, 0, 0, 0)
|
self.menubar.setObjectName("menubar")
|
||||||
box.setAlignment(QtCore.Qt.AlignLeft)
|
self.menubar.setNativeMenuBar(False)
|
||||||
self.profile_button = MainMenuButton(Form)
|
self.menubar.setMinimumSize(self.width(), 25)
|
||||||
box.addWidget(self.profile_button)
|
self.menubar.setMaximumSize(self.width(), 25)
|
||||||
self.settings_button = MainMenuButton(Form)
|
self.menubar.setBaseSize(self.width(), 25)
|
||||||
box.addWidget(self.settings_button)
|
self.menuProfile = QtWidgets.QMenu(self.menubar)
|
||||||
self.plugins_button = MainMenuButton(Form)
|
|
||||||
box.addWidget(self.plugins_button)
|
|
||||||
self.about_button = MainMenuButton(Form)
|
|
||||||
box.addWidget(self.about_button)
|
|
||||||
box.setSpacing(0)
|
|
||||||
|
|
||||||
self.menuProfile = QtGui.QMenu()
|
self.menuProfile = QtWidgets.QMenu(self.menubar)
|
||||||
self.menuProfile.setObjectName("menuProfile")
|
self.menuProfile.setObjectName("menuProfile")
|
||||||
self.menuSettings = QtGui.QMenu()
|
self.menuSettings = QtWidgets.QMenu(self.menubar)
|
||||||
self.menuSettings.setObjectName("menuSettings")
|
self.menuSettings.setObjectName("menuSettings")
|
||||||
self.menuPlugins = QtGui.QMenu()
|
self.menuPlugins = QtWidgets.QMenu(self.menubar)
|
||||||
self.menuPlugins.setObjectName("menuPlugins")
|
self.menuPlugins.setObjectName("menuPlugins")
|
||||||
self.menuAbout = QtGui.QMenu()
|
self.menuAbout = QtWidgets.QMenu(self.menubar)
|
||||||
self.menuAbout.setObjectName("menuAbout")
|
self.menuAbout.setObjectName("menuAbout")
|
||||||
|
|
||||||
self.actionAdd_friend = QtGui.QAction(Form)
|
self.actionAdd_friend = QtWidgets.QAction(window)
|
||||||
|
self.actionAdd_gc = QtWidgets.QAction(window)
|
||||||
self.actionAdd_friend.setObjectName("actionAdd_friend")
|
self.actionAdd_friend.setObjectName("actionAdd_friend")
|
||||||
self.actionprofilesettings = QtGui.QAction(Form)
|
self.actionprofilesettings = QtWidgets.QAction(window)
|
||||||
self.actionprofilesettings.setObjectName("actionprofilesettings")
|
self.actionprofilesettings.setObjectName("actionprofilesettings")
|
||||||
self.actionPrivacy_settings = QtGui.QAction(Form)
|
self.actionPrivacy_settings = QtWidgets.QAction(window)
|
||||||
self.actionPrivacy_settings.setObjectName("actionPrivacy_settings")
|
self.actionPrivacy_settings.setObjectName("actionPrivacy_settings")
|
||||||
self.actionInterface_settings = QtGui.QAction(Form)
|
self.actionInterface_settings = QtWidgets.QAction(window)
|
||||||
self.actionInterface_settings.setObjectName("actionInterface_settings")
|
self.actionInterface_settings.setObjectName("actionInterface_settings")
|
||||||
self.actionNotifications = QtGui.QAction(Form)
|
self.actionNotifications = QtWidgets.QAction(window)
|
||||||
self.actionNotifications.setObjectName("actionNotifications")
|
self.actionNotifications.setObjectName("actionNotifications")
|
||||||
self.actionNetwork = QtGui.QAction(Form)
|
self.actionNetwork = QtWidgets.QAction(window)
|
||||||
self.actionNetwork.setObjectName("actionNetwork")
|
self.actionNetwork.setObjectName("actionNetwork")
|
||||||
self.actionAbout_program = QtGui.QAction(Form)
|
self.actionAbout_program = QtWidgets.QAction(window)
|
||||||
self.actionAbout_program.setObjectName("actionAbout_program")
|
self.actionAbout_program.setObjectName("actionAbout_program")
|
||||||
self.updateSettings = QtGui.QAction(Form)
|
self.updateSettings = QtWidgets.QAction(window)
|
||||||
self.actionSettings = QtGui.QAction(Form)
|
self.actionSettings = QtWidgets.QAction(window)
|
||||||
self.actionSettings.setObjectName("actionSettings")
|
self.actionSettings.setObjectName("actionSettings")
|
||||||
self.audioSettings = QtGui.QAction(Form)
|
self.audioSettings = QtWidgets.QAction(window)
|
||||||
self.pluginData = QtGui.QAction(Form)
|
self.videoSettings = QtWidgets.QAction(window)
|
||||||
self.importPlugin = QtGui.QAction(Form)
|
self.pluginData = QtWidgets.QAction(window)
|
||||||
self.lockApp = QtGui.QAction(Form)
|
self.importPlugin = QtWidgets.QAction(window)
|
||||||
|
self.reloadPlugins = QtWidgets.QAction(window)
|
||||||
|
self.lockApp = QtWidgets.QAction(window)
|
||||||
self.menuProfile.addAction(self.actionAdd_friend)
|
self.menuProfile.addAction(self.actionAdd_friend)
|
||||||
|
self.menuProfile.addAction(self.actionAdd_gc)
|
||||||
self.menuProfile.addAction(self.actionSettings)
|
self.menuProfile.addAction(self.actionSettings)
|
||||||
self.menuProfile.addAction(self.lockApp)
|
self.menuProfile.addAction(self.lockApp)
|
||||||
self.menuSettings.addAction(self.actionPrivacy_settings)
|
self.menuSettings.addAction(self.actionPrivacy_settings)
|
||||||
@ -74,31 +72,33 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
self.menuSettings.addAction(self.actionNotifications)
|
self.menuSettings.addAction(self.actionNotifications)
|
||||||
self.menuSettings.addAction(self.actionNetwork)
|
self.menuSettings.addAction(self.actionNetwork)
|
||||||
self.menuSettings.addAction(self.audioSettings)
|
self.menuSettings.addAction(self.audioSettings)
|
||||||
|
self.menuSettings.addAction(self.videoSettings)
|
||||||
self.menuSettings.addAction(self.updateSettings)
|
self.menuSettings.addAction(self.updateSettings)
|
||||||
self.menuPlugins.addAction(self.pluginData)
|
self.menuPlugins.addAction(self.pluginData)
|
||||||
self.menuPlugins.addAction(self.importPlugin)
|
self.menuPlugins.addAction(self.importPlugin)
|
||||||
|
self.menuPlugins.addAction(self.reloadPlugins)
|
||||||
self.menuAbout.addAction(self.actionAbout_program)
|
self.menuAbout.addAction(self.actionAbout_program)
|
||||||
|
|
||||||
self.profile_button.setMenu(self.menuProfile)
|
self.menubar.addAction(self.menuProfile.menuAction())
|
||||||
self.settings_button.setMenu(self.menuSettings)
|
self.menubar.addAction(self.menuSettings.menuAction())
|
||||||
self.plugins_button.setMenu(self.menuPlugins)
|
self.menubar.addAction(self.menuPlugins.menuAction())
|
||||||
self.about_button.setMenu(self.menuAbout)
|
self.menubar.addAction(self.menuAbout.menuAction())
|
||||||
|
|
||||||
self.actionAbout_program.triggered.connect(self.about_program)
|
self.actionAbout_program.triggered.connect(self.about_program)
|
||||||
self.actionNetwork.triggered.connect(self.network_settings)
|
self.actionNetwork.triggered.connect(self.network_settings)
|
||||||
self.actionAdd_friend.triggered.connect(self.add_contact)
|
self.actionAdd_friend.triggered.connect(self.add_contact)
|
||||||
|
self.actionAdd_gc.triggered.connect(self.create_gc)
|
||||||
self.actionSettings.triggered.connect(self.profile_settings)
|
self.actionSettings.triggered.connect(self.profile_settings)
|
||||||
self.actionPrivacy_settings.triggered.connect(self.privacy_settings)
|
self.actionPrivacy_settings.triggered.connect(self.privacy_settings)
|
||||||
self.actionInterface_settings.triggered.connect(self.interface_settings)
|
self.actionInterface_settings.triggered.connect(self.interface_settings)
|
||||||
self.actionNotifications.triggered.connect(self.notification_settings)
|
self.actionNotifications.triggered.connect(self.notification_settings)
|
||||||
self.audioSettings.triggered.connect(self.audio_settings)
|
self.audioSettings.triggered.connect(self.audio_settings)
|
||||||
|
self.videoSettings.triggered.connect(self.video_settings)
|
||||||
self.updateSettings.triggered.connect(self.update_settings)
|
self.updateSettings.triggered.connect(self.update_settings)
|
||||||
self.pluginData.triggered.connect(self.plugins_menu)
|
self.pluginData.triggered.connect(self.plugins_menu)
|
||||||
self.lockApp.triggered.connect(self.lock_app)
|
self.lockApp.triggered.connect(self.lock_app)
|
||||||
self.importPlugin.triggered.connect(self.import_plugin)
|
self.importPlugin.triggered.connect(self.import_plugin)
|
||||||
|
self.reloadPlugins.triggered.connect(self.reload_plugins)
|
||||||
Form.setLayout(box)
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
|
||||||
|
|
||||||
def languageChange(self, *args, **kwargs):
|
def languageChange(self, *args, **kwargs):
|
||||||
self.retranslateUi()
|
self.retranslateUi()
|
||||||
@ -110,36 +110,39 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
return super(MainWindow, self).event(event)
|
return super(MainWindow, self).event(event)
|
||||||
|
|
||||||
def retranslateUi(self):
|
def retranslateUi(self):
|
||||||
self.lockApp.setText(QtGui.QApplication.translate("MainWindow", "Lock", None, QtGui.QApplication.UnicodeUTF8))
|
self.lockApp.setText(QtWidgets.QApplication.translate("MainWindow", "Lock"))
|
||||||
self.plugins_button.setText(QtGui.QApplication.translate("MainWindow", "Plugins", None, QtGui.QApplication.UnicodeUTF8))
|
self.menuPlugins.setTitle(QtWidgets.QApplication.translate("MainWindow", "Plugins"))
|
||||||
self.pluginData.setText(QtGui.QApplication.translate("MainWindow", "List of plugins", None, QtGui.QApplication.UnicodeUTF8))
|
self.pluginData.setText(QtWidgets.QApplication.translate("MainWindow", "List of plugins"))
|
||||||
self.profile_button.setText(QtGui.QApplication.translate("MainWindow", "Profile", None, QtGui.QApplication.UnicodeUTF8))
|
self.menuProfile.setTitle(QtWidgets.QApplication.translate("MainWindow", "Profile"))
|
||||||
self.settings_button.setText(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8))
|
self.menuSettings.setTitle(QtWidgets.QApplication.translate("MainWindow", "Settings"))
|
||||||
self.about_button.setText(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8))
|
self.menuAbout.setTitle(QtWidgets.QApplication.translate("MainWindow", "About"))
|
||||||
self.actionAdd_friend.setText(QtGui.QApplication.translate("MainWindow", "Add contact", None, QtGui.QApplication.UnicodeUTF8))
|
self.actionAdd_friend.setText(QtWidgets.QApplication.translate("MainWindow", "Add contact"))
|
||||||
self.actionprofilesettings.setText(QtGui.QApplication.translate("MainWindow", "Profile", None, QtGui.QApplication.UnicodeUTF8))
|
self.actionAdd_gc.setText(QtWidgets.QApplication.translate("MainWindow", "Create group chat"))
|
||||||
self.actionPrivacy_settings.setText(QtGui.QApplication.translate("MainWindow", "Privacy", None, QtGui.QApplication.UnicodeUTF8))
|
self.actionprofilesettings.setText(QtWidgets.QApplication.translate("MainWindow", "Profile"))
|
||||||
self.actionInterface_settings.setText(QtGui.QApplication.translate("MainWindow", "Interface", None, QtGui.QApplication.UnicodeUTF8))
|
self.actionPrivacy_settings.setText(QtWidgets.QApplication.translate("MainWindow", "Privacy"))
|
||||||
self.actionNotifications.setText(QtGui.QApplication.translate("MainWindow", "Notifications", None, QtGui.QApplication.UnicodeUTF8))
|
self.actionInterface_settings.setText(QtWidgets.QApplication.translate("MainWindow", "Interface"))
|
||||||
self.actionNetwork.setText(QtGui.QApplication.translate("MainWindow", "Network", None, QtGui.QApplication.UnicodeUTF8))
|
self.actionNotifications.setText(QtWidgets.QApplication.translate("MainWindow", "Notifications"))
|
||||||
self.actionAbout_program.setText(QtGui.QApplication.translate("MainWindow", "About program", None, QtGui.QApplication.UnicodeUTF8))
|
self.actionNetwork.setText(QtWidgets.QApplication.translate("MainWindow", "Network"))
|
||||||
self.actionSettings.setText(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8))
|
self.actionAbout_program.setText(QtWidgets.QApplication.translate("MainWindow", "About program"))
|
||||||
self.audioSettings.setText(QtGui.QApplication.translate("MainWindow", "Audio", None, QtGui.QApplication.UnicodeUTF8))
|
self.actionSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Settings"))
|
||||||
self.updateSettings.setText(QtGui.QApplication.translate("MainWindow", "Updates", None, QtGui.QApplication.UnicodeUTF8))
|
self.audioSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Audio"))
|
||||||
self.contact_name.setPlaceholderText(QtGui.QApplication.translate("MainWindow", "Search", None, QtGui.QApplication.UnicodeUTF8))
|
self.videoSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Video"))
|
||||||
self.sendMessageButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Send message", None, QtGui.QApplication.UnicodeUTF8))
|
self.updateSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Updates"))
|
||||||
self.callButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Start audio call with friend", None, QtGui.QApplication.UnicodeUTF8))
|
self.contact_name.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "Search"))
|
||||||
|
self.sendMessageButton.setToolTip(QtWidgets.QApplication.translate("MainWindow", "Send message"))
|
||||||
|
self.callButton.setToolTip(QtWidgets.QApplication.translate("MainWindow", "Start audio call with friend"))
|
||||||
self.online_contacts.clear()
|
self.online_contacts.clear()
|
||||||
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "All", None, QtGui.QApplication.UnicodeUTF8))
|
self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "All"))
|
||||||
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online", None, QtGui.QApplication.UnicodeUTF8))
|
self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online"))
|
||||||
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online first", None, QtGui.QApplication.UnicodeUTF8))
|
self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online first"))
|
||||||
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Name", None, QtGui.QApplication.UnicodeUTF8))
|
self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Name"))
|
||||||
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online and by name", None, QtGui.QApplication.UnicodeUTF8))
|
self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online and by name"))
|
||||||
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online first and by name", None, QtGui.QApplication.UnicodeUTF8))
|
self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online first and by name"))
|
||||||
ind = Settings.get_instance()['sorting']
|
ind = Settings.get_instance()['sorting']
|
||||||
d = {0: 0, 1: 1, 2: 2, 3: 4, 1 | 4: 4, 2 | 4: 5}
|
d = {0: 0, 1: 1, 2: 2, 3: 4, 1 | 4: 4, 2 | 4: 5}
|
||||||
self.online_contacts.setCurrentIndex(d[ind])
|
self.online_contacts.setCurrentIndex(d[ind])
|
||||||
self.importPlugin.setText(QtGui.QApplication.translate("MainWindow", "Import plugin", None, QtGui.QApplication.UnicodeUTF8))
|
self.importPlugin.setText(QtWidgets.QApplication.translate("MainWindow", "Import plugin"))
|
||||||
|
self.reloadPlugins.setText(QtWidgets.QApplication.translate("MainWindow", "Reload plugins"))
|
||||||
|
|
||||||
def setup_right_bottom(self, Form):
|
def setup_right_bottom(self, Form):
|
||||||
Form.resize(650, 60)
|
Form.resize(650, 60)
|
||||||
@ -151,7 +154,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
font.setFamily(settings.Settings.get_instance()['font'])
|
font.setFamily(settings.Settings.get_instance()['font'])
|
||||||
self.messageEdit.setFont(font)
|
self.messageEdit.setFont(font)
|
||||||
|
|
||||||
self.sendMessageButton = QtGui.QPushButton(Form)
|
self.sendMessageButton = QtWidgets.QPushButton(Form)
|
||||||
self.sendMessageButton.setGeometry(QtCore.QRect(565, 3, 60, 55))
|
self.sendMessageButton.setGeometry(QtCore.QRect(565, 3, 60, 55))
|
||||||
self.sendMessageButton.setObjectName("sendMessageButton")
|
self.sendMessageButton.setObjectName("sendMessageButton")
|
||||||
|
|
||||||
@ -174,7 +177,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
|
|
||||||
def setup_left_center_menu(self, Form):
|
def setup_left_center_menu(self, Form):
|
||||||
Form.resize(270, 25)
|
Form.resize(270, 25)
|
||||||
self.search_label = QtGui.QLabel(Form)
|
self.search_label = QtWidgets.QLabel(Form)
|
||||||
self.search_label.setGeometry(QtCore.QRect(3, 2, 20, 20))
|
self.search_label.setGeometry(QtCore.QRect(3, 2, 20, 20))
|
||||||
pixmap = QtGui.QPixmap()
|
pixmap = QtGui.QPixmap()
|
||||||
pixmap.load(curr_directory() + '/images/search.png')
|
pixmap.load(curr_directory() + '/images/search.png')
|
||||||
@ -198,7 +201,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
Form.setMinimumSize(QtCore.QSize(270, 75))
|
Form.setMinimumSize(QtCore.QSize(270, 75))
|
||||||
Form.setMaximumSize(QtCore.QSize(270, 75))
|
Form.setMaximumSize(QtCore.QSize(270, 75))
|
||||||
Form.setBaseSize(QtCore.QSize(270, 75))
|
Form.setBaseSize(QtCore.QSize(270, 75))
|
||||||
self.avatar_label = Form.avatar_label = QtGui.QLabel(Form)
|
self.avatar_label = Form.avatar_label = QtWidgets.QLabel(Form)
|
||||||
self.avatar_label.setGeometry(QtCore.QRect(5, 5, 64, 64))
|
self.avatar_label.setGeometry(QtCore.QRect(5, 5, 64, 64))
|
||||||
self.avatar_label.setScaledContents(False)
|
self.avatar_label.setScaledContents(False)
|
||||||
self.avatar_label.setAlignment(QtCore.Qt.AlignCenter)
|
self.avatar_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
@ -226,7 +229,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
|
|
||||||
def setup_right_top(self, Form):
|
def setup_right_top(self, Form):
|
||||||
Form.resize(650, 75)
|
Form.resize(650, 75)
|
||||||
self.account_avatar = QtGui.QLabel(Form)
|
self.account_avatar = QtWidgets.QLabel(Form)
|
||||||
self.account_avatar.setGeometry(QtCore.QRect(10, 5, 64, 64))
|
self.account_avatar.setGeometry(QtCore.QRect(10, 5, 64, 64))
|
||||||
self.account_avatar.setScaledContents(False)
|
self.account_avatar.setScaledContents(False)
|
||||||
self.account_name = DataLabel(Form)
|
self.account_name = DataLabel(Form)
|
||||||
@ -245,16 +248,16 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
font.setBold(False)
|
font.setBold(False)
|
||||||
self.account_status.setFont(font)
|
self.account_status.setFont(font)
|
||||||
self.account_status.setObjectName("account_status")
|
self.account_status.setObjectName("account_status")
|
||||||
self.callButton = QtGui.QPushButton(Form)
|
self.callButton = QtWidgets.QPushButton(Form)
|
||||||
self.callButton.setGeometry(QtCore.QRect(550, 5, 50, 50))
|
self.callButton.setGeometry(QtCore.QRect(550, 5, 50, 50))
|
||||||
self.callButton.setObjectName("callButton")
|
self.callButton.setObjectName("callButton")
|
||||||
self.callButton.clicked.connect(lambda: self.profile.call_click(True))
|
self.callButton.clicked.connect(lambda: self.profile.call_click(True))
|
||||||
self.videocallButton = QtGui.QPushButton(Form)
|
self.videocallButton = QtWidgets.QPushButton(Form)
|
||||||
self.videocallButton.setGeometry(QtCore.QRect(550, 5, 50, 50))
|
self.videocallButton.setGeometry(QtCore.QRect(550, 5, 50, 50))
|
||||||
self.videocallButton.setObjectName("videocallButton")
|
self.videocallButton.setObjectName("videocallButton")
|
||||||
self.videocallButton.clicked.connect(lambda: self.profile.call_click(True, True))
|
self.videocallButton.clicked.connect(lambda: self.profile.call_click(True, True))
|
||||||
self.update_call_state('call')
|
self.update_call_state('call')
|
||||||
self.typing = QtGui.QLabel(Form)
|
self.typing = QtWidgets.QLabel(Form)
|
||||||
self.typing.setGeometry(QtCore.QRect(500, 25, 50, 30))
|
self.typing.setGeometry(QtCore.QRect(500, 25, 50, 30))
|
||||||
pixmap = QtGui.QPixmap(QtCore.QSize(50, 30))
|
pixmap = QtGui.QPixmap(QtCore.QSize(50, 30))
|
||||||
pixmap.load(curr_directory() + '/images/typing.png')
|
pixmap.load(curr_directory() + '/images/typing.png')
|
||||||
@ -264,20 +267,19 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||||
|
|
||||||
def setup_left_center(self, widget):
|
def setup_left_center(self, widget):
|
||||||
self.friends_list = QtGui.QListWidget(widget)
|
self.friends_list = QtWidgets.QListWidget(widget)
|
||||||
self.friends_list.setObjectName("friends_list")
|
self.friends_list.setObjectName("friends_list")
|
||||||
self.friends_list.setGeometry(0, 0, 270, 310)
|
self.friends_list.setGeometry(0, 0, 270, 310)
|
||||||
self.friends_list.clicked.connect(self.friend_click)
|
self.friends_list.clicked.connect(self.friend_click)
|
||||||
self.friends_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
self.friends_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||||
self.friends_list.connect(self.friends_list, QtCore.SIGNAL("customContextMenuRequested(QPoint)"),
|
self.friends_list.customContextMenuRequested.connect(self.friend_right_click)
|
||||||
self.friend_right_click)
|
self.friends_list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||||
self.friends_list.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
|
|
||||||
self.friends_list.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
self.friends_list.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||||
self.friends_list.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
self.friends_list.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||||
self.friends_list.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu)
|
self.friends_list.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu)
|
||||||
|
|
||||||
def setup_right_center(self, widget):
|
def setup_right_center(self, widget):
|
||||||
self.messages = QtGui.QListWidget(widget)
|
self.messages = QtWidgets.QListWidget(widget)
|
||||||
self.messages.setGeometry(0, 0, 620, 310)
|
self.messages.setGeometry(0, 0, 620, 310)
|
||||||
self.messages.setObjectName("messages")
|
self.messages.setObjectName("messages")
|
||||||
self.messages.setSpacing(1)
|
self.messages.setSpacing(1)
|
||||||
@ -291,8 +293,8 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
self.profile.load_history()
|
self.profile.load_history()
|
||||||
self.messages.verticalScrollBar().setValue(1)
|
self.messages.verticalScrollBar().setValue(1)
|
||||||
self.messages.verticalScrollBar().valueChanged.connect(load)
|
self.messages.verticalScrollBar().valueChanged.connect(load)
|
||||||
self.messages.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
|
self.messages.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||||
self.messages.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
|
self.messages.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||||
|
|
||||||
def initUI(self, tox):
|
def initUI(self, tox):
|
||||||
self.setMinimumSize(920, 500)
|
self.setMinimumSize(920, 500)
|
||||||
@ -300,15 +302,15 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
self.setGeometry(s['x'], s['y'], s['width'], s['height'])
|
self.setGeometry(s['x'], s['y'], s['width'], s['height'])
|
||||||
self.setWindowTitle('Toxygen')
|
self.setWindowTitle('Toxygen')
|
||||||
os.chdir(curr_directory() + '/images/')
|
os.chdir(curr_directory() + '/images/')
|
||||||
menu = QtGui.QWidget()
|
menu = QtWidgets.QWidget()
|
||||||
main = QtGui.QWidget()
|
main = QtWidgets.QWidget()
|
||||||
grid = QtGui.QGridLayout()
|
grid = QtWidgets.QGridLayout()
|
||||||
search = QtGui.QWidget()
|
search = QtWidgets.QWidget()
|
||||||
name = QtGui.QWidget()
|
name = QtWidgets.QWidget()
|
||||||
info = QtGui.QWidget()
|
info = QtWidgets.QWidget()
|
||||||
main_list = QtGui.QWidget()
|
main_list = QtWidgets.QWidget()
|
||||||
messages = QtGui.QWidget()
|
messages = QtWidgets.QWidget()
|
||||||
message_buttons = QtGui.QWidget()
|
message_buttons = QtWidgets.QWidget()
|
||||||
self.setup_left_center_menu(search)
|
self.setup_left_center_menu(search)
|
||||||
self.setup_left_top(name)
|
self.setup_left_top(name)
|
||||||
self.setup_right_center(messages)
|
self.setup_right_center(messages)
|
||||||
@ -365,9 +367,9 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
s['width'] = self.width()
|
s['width'] = self.width()
|
||||||
s['height'] = self.height()
|
s['height'] = self.height()
|
||||||
s.save()
|
s.save()
|
||||||
QtGui.QApplication.closeAllWindows()
|
QtWidgets.QApplication.closeAllWindows()
|
||||||
event.accept()
|
event.accept()
|
||||||
elif QtGui.QSystemTrayIcon.isSystemTrayAvailable():
|
elif QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
|
||||||
event.ignore()
|
event.ignore()
|
||||||
self.hide()
|
self.hide()
|
||||||
|
|
||||||
@ -376,12 +378,8 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def resizeEvent(self, *args, **kwargs):
|
def resizeEvent(self, *args, **kwargs):
|
||||||
if platform.system() == 'Windows':
|
|
||||||
self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155)
|
self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155)
|
||||||
self.friends_list.setGeometry(0, 0, 270, self.height() - 125)
|
self.friends_list.setGeometry(0, 0, 270, self.height() - 125)
|
||||||
else:
|
|
||||||
self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 159)
|
|
||||||
self.friends_list.setGeometry(0, 0, 270, self.height() - 129)
|
|
||||||
|
|
||||||
self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 10, 50, 50))
|
self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 10, 50, 50))
|
||||||
self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 10, 50, 50))
|
self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 10, 50, 50))
|
||||||
@ -397,13 +395,13 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
self.profile.update()
|
self.profile.update()
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
if event.key() == QtCore.Qt.Key_Escape and QtGui.QSystemTrayIcon.isSystemTrayAvailable():
|
if event.key() == QtCore.Qt.Key_Escape and QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
|
||||||
self.hide()
|
self.hide()
|
||||||
elif event.key() == QtCore.Qt.Key_C and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
|
elif event.key() == QtCore.Qt.Key_C and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
|
||||||
rows = list(map(lambda x: self.messages.row(x), self.messages.selectedItems()))
|
rows = list(map(lambda x: self.messages.row(x), self.messages.selectedItems()))
|
||||||
indexes = (rows[0] - self.messages.count(), rows[-1] - self.messages.count())
|
indexes = (rows[0] - self.messages.count(), rows[-1] - self.messages.count())
|
||||||
s = self.profile.export_history(self.profile.active_friend, True, indexes)
|
s = self.profile.export_history(self.profile.active_friend, True, indexes)
|
||||||
clipboard = QtGui.QApplication.clipboard()
|
clipboard = QtWidgets.QApplication.clipboard()
|
||||||
clipboard.setText(s)
|
clipboard.setText(s)
|
||||||
elif event.key() == QtCore.Qt.Key_Z and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
|
elif event.key() == QtCore.Qt.Key_Z and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
|
||||||
self.messages.clearSelection()
|
self.messages.clearSelection()
|
||||||
@ -418,10 +416,12 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
|
|
||||||
def about_program(self):
|
def about_program(self):
|
||||||
import util
|
import util
|
||||||
msgBox = QtGui.QMessageBox()
|
msgBox = QtWidgets.QMessageBox()
|
||||||
msgBox.setWindowTitle(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8))
|
msgBox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "About"))
|
||||||
text = (QtGui.QApplication.translate("MainWindow", 'Toxygen is Tox client written on Python.\nVersion: ', None, QtGui.QApplication.UnicodeUTF8))
|
text = (QtWidgets.QApplication.translate("MainWindow", 'Toxygen is Tox client written on Python.<br>Version: '))
|
||||||
msgBox.setText(text + util.program_version + '\nGitHub: https://github.com/toxygen-project/toxygen/')
|
github = '<br><a href="https://github.com/toxygen-project/toxygen/">Github</a>'
|
||||||
|
submit_a_bug = '<br><a href="https://github.com/toxygen-project/toxygen/issues">Submit a bug</a>'
|
||||||
|
msgBox.setText(text + util.program_version + github + submit_a_bug)
|
||||||
msgBox.exec_()
|
msgBox.exec_()
|
||||||
|
|
||||||
def network_settings(self):
|
def network_settings(self):
|
||||||
@ -436,6 +436,9 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
self.a_c = AddContact(link or '')
|
self.a_c = AddContact(link or '')
|
||||||
self.a_c.show()
|
self.a_c.show()
|
||||||
|
|
||||||
|
def create_gc(self):
|
||||||
|
self.profile.create_group_chat()
|
||||||
|
|
||||||
def profile_settings(self, *args):
|
def profile_settings(self, *args):
|
||||||
self.p_s = ProfileSettings()
|
self.p_s = ProfileSettings()
|
||||||
self.p_s.show()
|
self.p_s.show()
|
||||||
@ -456,28 +459,34 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
self.audio_s = AudioSettings()
|
self.audio_s = AudioSettings()
|
||||||
self.audio_s.show()
|
self.audio_s.show()
|
||||||
|
|
||||||
|
def video_settings(self):
|
||||||
|
self.video_s = VideoSettings()
|
||||||
|
self.video_s.show()
|
||||||
|
|
||||||
def update_settings(self):
|
def update_settings(self):
|
||||||
self.update_s = UpdateSettings()
|
self.update_s = UpdateSettings()
|
||||||
self.update_s.show()
|
self.update_s.show()
|
||||||
|
|
||||||
|
def reload_plugins(self):
|
||||||
|
plugin_loader = plugin_support.PluginLoader.get_instance()
|
||||||
|
if plugin_loader is not None:
|
||||||
|
plugin_loader.reload()
|
||||||
|
|
||||||
def import_plugin(self):
|
def import_plugin(self):
|
||||||
import util
|
import util
|
||||||
directory = QtGui.QFileDialog.getExistingDirectory(self,
|
directory = QtWidgets.QFileDialog.getExistingDirectory(self,
|
||||||
QtGui.QApplication.translate("MainWindow", 'Choose folder with plugin',
|
QtWidgets.QApplication.translate("MainWindow", 'Choose folder with plugin'),
|
||||||
None,
|
|
||||||
QtGui.QApplication.UnicodeUTF8),
|
|
||||||
util.curr_directory(),
|
util.curr_directory(),
|
||||||
QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog)
|
QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
|
||||||
if directory:
|
if directory:
|
||||||
src = directory + '/'
|
src = directory + '/'
|
||||||
dest = curr_directory() + '/plugins/'
|
dest = curr_directory() + '/plugins/'
|
||||||
util.copy(src, dest)
|
util.copy(src, dest)
|
||||||
msgBox = QtGui.QMessageBox()
|
msgBox = QtWidgets.QMessageBox()
|
||||||
msgBox.setWindowTitle(
|
msgBox.setWindowTitle(
|
||||||
QtGui.QApplication.translate("MainWindow", "Restart Toxygen", None, QtGui.QApplication.UnicodeUTF8))
|
QtWidgets.QApplication.translate("MainWindow", "Restart Toxygen"))
|
||||||
msgBox.setText(
|
msgBox.setText(
|
||||||
QtGui.QApplication.translate("MainWindow", 'Plugin will be loaded after restart', None,
|
QtWidgets.QApplication.translate("MainWindow", 'Plugin will be loaded after restart'))
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
msgBox.exec_()
|
msgBox.exec_()
|
||||||
|
|
||||||
def lock_app(self):
|
def lock_app(self):
|
||||||
@ -485,12 +494,11 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
Settings.get_instance().locked = True
|
Settings.get_instance().locked = True
|
||||||
self.hide()
|
self.hide()
|
||||||
else:
|
else:
|
||||||
msgBox = QtGui.QMessageBox()
|
msgBox = QtWidgets.QMessageBox()
|
||||||
msgBox.setWindowTitle(
|
msgBox.setWindowTitle(
|
||||||
QtGui.QApplication.translate("MainWindow", "Cannot lock app", None, QtGui.QApplication.UnicodeUTF8))
|
QtWidgets.QApplication.translate("MainWindow", "Cannot lock app"))
|
||||||
msgBox.setText(
|
msgBox.setText(
|
||||||
QtGui.QApplication.translate("MainWindow", 'Error. Profile password is not set.', None,
|
QtWidgets.QApplication.translate("MainWindow", 'Error. Profile password is not set.'))
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
msgBox.exec_()
|
msgBox.exec_()
|
||||||
|
|
||||||
def show_menu(self):
|
def show_menu(self):
|
||||||
@ -512,15 +520,15 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
|
|
||||||
def send_file(self):
|
def send_file(self):
|
||||||
self.menu.hide()
|
self.menu.hide()
|
||||||
if self.profile.active_friend + 1:
|
if self.profile.active_friend + 1and self.profile.is_active_a_friend():
|
||||||
choose = QtGui.QApplication.translate("MainWindow", 'Choose file', None, QtGui.QApplication.UnicodeUTF8)
|
choose = QtWidgets.QApplication.translate("MainWindow", 'Choose file')
|
||||||
name = QtGui.QFileDialog.getOpenFileName(self, choose, options=QtGui.QFileDialog.DontUseNativeDialog)
|
name = QtWidgets.QFileDialog.getOpenFileName(self, choose, options=QtWidgets.QFileDialog.DontUseNativeDialog)
|
||||||
if name[0]:
|
if name[0]:
|
||||||
self.profile.send_file(name[0])
|
self.profile.send_file(name[0])
|
||||||
|
|
||||||
def send_screenshot(self, hide=False):
|
def send_screenshot(self, hide=False):
|
||||||
self.menu.hide()
|
self.menu.hide()
|
||||||
if self.profile.active_friend + 1:
|
if self.profile.active_friend + 1 and self.profile.is_active_a_friend():
|
||||||
self.sw = ScreenShotWindow(self)
|
self.sw = ScreenShotWindow(self)
|
||||||
self.sw.show()
|
self.sw.show()
|
||||||
if hide:
|
if hide:
|
||||||
@ -538,7 +546,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
|
|
||||||
def send_sticker(self):
|
def send_sticker(self):
|
||||||
self.menu.hide()
|
self.menu.hide()
|
||||||
if self.profile.active_friend + 1:
|
if self.profile.active_friend + 1 and self.profile.is_active_a_friend():
|
||||||
self.sticker = StickerWindow(self)
|
self.sticker = StickerWindow(self)
|
||||||
self.sticker.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(),
|
self.sticker.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(),
|
||||||
self.y() + self.height() - 200,
|
self.y() + self.height() - 200,
|
||||||
@ -555,14 +563,15 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
def call_finished(self):
|
def call_finished(self):
|
||||||
self.update_call_state('call')
|
self.update_call_state('call')
|
||||||
|
|
||||||
def update_call_state(self, fl):
|
def update_call_state(self, state):
|
||||||
# TODO: do smth with video call button
|
|
||||||
os.chdir(curr_directory() + '/images/')
|
os.chdir(curr_directory() + '/images/')
|
||||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(fl))
|
|
||||||
|
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(state))
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
self.callButton.setIcon(icon)
|
self.callButton.setIcon(icon)
|
||||||
self.callButton.setIconSize(QtCore.QSize(50, 50))
|
self.callButton.setIconSize(QtCore.QSize(50, 50))
|
||||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/videocall.png')
|
|
||||||
|
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}_video.png'.format(state))
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
self.videocallButton.setIcon(icon)
|
self.videocallButton.setIcon(icon)
|
||||||
self.videocallButton.setIconSize(QtCore.QSize(35, 35))
|
self.videocallButton.setIconSize(QtCore.QSize(35, 35))
|
||||||
@ -579,40 +588,59 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
return
|
return
|
||||||
settings = Settings.get_instance()
|
settings = Settings.get_instance()
|
||||||
allowed = friend.tox_id in settings['auto_accept_from_friends']
|
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)
|
auto = QtWidgets.QApplication.translate("MainWindow", 'Disallow auto accept') if allowed else QtWidgets.QApplication.translate("MainWindow", 'Allow auto accept')
|
||||||
if item is not None:
|
if item is not None:
|
||||||
self.listMenu = QtGui.QMenu()
|
self.listMenu = QtWidgets.QMenu()
|
||||||
set_alias_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Set alias', None, QtGui.QApplication.UnicodeUTF8))
|
is_friend = type(friend) is Friend
|
||||||
|
if is_friend:
|
||||||
|
set_alias_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Set alias'))
|
||||||
|
set_alias_item.triggered.connect(lambda: self.set_alias(num))
|
||||||
|
|
||||||
history_menu = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Chat history', None, QtGui.QApplication.UnicodeUTF8))
|
history_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Chat history'))
|
||||||
clear_history_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8))
|
clear_history_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Clear history'))
|
||||||
export_to_text_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Export as text', None, QtGui.QApplication.UnicodeUTF8))
|
export_to_text_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Export as text'))
|
||||||
export_to_html_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Export as HTML', None, QtGui.QApplication.UnicodeUTF8))
|
export_to_html_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Export as HTML'))
|
||||||
|
|
||||||
copy_menu = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Copy', None, QtGui.QApplication.UnicodeUTF8))
|
copy_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Copy'))
|
||||||
copy_name_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Name', None, QtGui.QApplication.UnicodeUTF8))
|
copy_name_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Name'))
|
||||||
copy_status_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Status message', None, QtGui.QApplication.UnicodeUTF8))
|
copy_status_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Status message'))
|
||||||
copy_key_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Public key', None, QtGui.QApplication.UnicodeUTF8))
|
if is_friend:
|
||||||
|
copy_key_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Public key'))
|
||||||
|
|
||||||
auto_accept_item = self.listMenu.addAction(auto)
|
auto_accept_item = self.listMenu.addAction(auto)
|
||||||
remove_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Remove friend', None, QtGui.QApplication.UnicodeUTF8))
|
remove_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Remove friend'))
|
||||||
notes_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Notes', None, QtGui.QApplication.UnicodeUTF8))
|
block_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Block friend'))
|
||||||
|
notes_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Notes'))
|
||||||
|
|
||||||
submenu = plugin_support.PluginLoader.get_instance().get_menu(self.listMenu, num)
|
chats = self.profile.get_group_chats()
|
||||||
|
if len(chats) and self.profile.is_active_online():
|
||||||
|
invite_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Invite to group chat'))
|
||||||
|
for i in range(len(chats)):
|
||||||
|
name, number = chats[i]
|
||||||
|
item = invite_menu.addAction(name)
|
||||||
|
item.triggered.connect(lambda number=number: self.invite_friend_to_gc(num, number))
|
||||||
|
|
||||||
|
plugins_loader = plugin_support.PluginLoader.get_instance()
|
||||||
|
if plugins_loader is not None:
|
||||||
|
submenu = plugins_loader.get_menu(self.listMenu, num)
|
||||||
if len(submenu):
|
if len(submenu):
|
||||||
plug = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8))
|
plug = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins'))
|
||||||
plug.addActions(submenu)
|
plug.addActions(submenu)
|
||||||
self.connect(set_alias_item, QtCore.SIGNAL("triggered()"), lambda: self.set_alias(num))
|
copy_key_item.triggered.connect(lambda: self.copy_friend_key(num))
|
||||||
self.connect(remove_item, QtCore.SIGNAL("triggered()"), lambda: self.remove_friend(num))
|
remove_item.triggered.connect(lambda: self.remove_friend(num))
|
||||||
self.connect(copy_key_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_friend_key(num))
|
block_item.triggered.connect(lambda: self.block_friend(num))
|
||||||
self.connect(clear_history_item, QtCore.SIGNAL("triggered()"), lambda: self.clear_history(num))
|
auto_accept_item.triggered.connect(lambda: self.auto_accept(num, not allowed))
|
||||||
self.connect(auto_accept_item, QtCore.SIGNAL("triggered()"), lambda: self.auto_accept(num, not allowed))
|
notes_item.triggered.connect(lambda: self.show_note(friend))
|
||||||
self.connect(notes_item, QtCore.SIGNAL("triggered()"), lambda: self.show_note(friend))
|
else:
|
||||||
self.connect(copy_name_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_name(friend))
|
leave_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Leave chat'))
|
||||||
self.connect(copy_status_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_status(friend))
|
set_title_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Set title'))
|
||||||
self.connect(export_to_text_item, QtCore.SIGNAL("triggered()"), lambda: self.export_history(num))
|
leave_item.triggered.connect(lambda: self.leave_gc(num))
|
||||||
self.connect(export_to_html_item, QtCore.SIGNAL("triggered()"),
|
set_title_item.triggered.connect(lambda: self.set_title(num))
|
||||||
lambda: self.export_history(num, False))
|
clear_history_item.triggered.connect(lambda: self.clear_history(num))
|
||||||
|
copy_name_item.triggered.connect(lambda: self.copy_name(friend))
|
||||||
|
copy_status_item.triggered.connect(lambda: self.copy_status(friend))
|
||||||
|
export_to_text_item.triggered.connect(lambda: self.export_history(num))
|
||||||
|
export_to_html_item.triggered.connect(lambda: self.export_history(num, False))
|
||||||
parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0))
|
parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0))
|
||||||
self.listMenu.move(parent_position + pos)
|
self.listMenu.move(parent_position + pos)
|
||||||
self.listMenu.show()
|
self.listMenu.show()
|
||||||
@ -620,7 +648,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
def show_note(self, friend):
|
def show_note(self, friend):
|
||||||
s = Settings.get_instance()
|
s = Settings.get_instance()
|
||||||
note = s['notes'][friend.tox_id] if friend.tox_id in s['notes'] else ''
|
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 = QtWidgets.QApplication.translate("MainWindow", 'Notes about user')
|
||||||
user = '{} {}'.format(user, friend.name)
|
user = '{} {}'.format(user, friend.name)
|
||||||
|
|
||||||
def save_note(text):
|
def save_note(text):
|
||||||
@ -634,16 +662,18 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
|
|
||||||
def export_history(self, num, as_text=True):
|
def export_history(self, num, as_text=True):
|
||||||
s = self.profile.export_history(num, as_text)
|
s = self.profile.export_history(num, as_text)
|
||||||
directory = QtGui.QFileDialog.getExistingDirectory(None,
|
extension = 'txt' if as_text else 'html'
|
||||||
QtGui.QApplication.translate("MainWindow", 'Choose folder',
|
file_name, _ = QtWidgets.QFileDialog.getSaveFileName(None,
|
||||||
None,
|
QtWidgets.QApplication.translate("MainWindow",
|
||||||
QtGui.QApplication.UnicodeUTF8),
|
'Choose file name'),
|
||||||
curr_directory(),
|
curr_directory(),
|
||||||
QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog)
|
filter=extension,
|
||||||
|
options=QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
|
||||||
|
|
||||||
if directory:
|
if file_name:
|
||||||
name = 'exported_history_{}.{}'.format(convert_time(time.time()), 'txt' if as_text else 'html')
|
if not file_name.endswith('.' + extension):
|
||||||
with open(directory + '/' + name, 'wt') as fl:
|
file_name += '.' + extension
|
||||||
|
with open(file_name, 'wt') as fl:
|
||||||
fl.write(s)
|
fl.write(s)
|
||||||
|
|
||||||
def set_alias(self, num):
|
def set_alias(self, num):
|
||||||
@ -652,22 +682,32 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
def remove_friend(self, num):
|
def remove_friend(self, num):
|
||||||
self.profile.delete_friend(num)
|
self.profile.delete_friend(num)
|
||||||
|
|
||||||
|
def block_friend(self, num):
|
||||||
|
friend = self.profile.get_friend(num)
|
||||||
|
self.profile.block_user(friend.tox_id)
|
||||||
|
|
||||||
def copy_friend_key(self, num):
|
def copy_friend_key(self, num):
|
||||||
tox_id = self.profile.friend_public_key(num)
|
tox_id = self.profile.friend_public_key(num)
|
||||||
clipboard = QtGui.QApplication.clipboard()
|
clipboard = QtWidgets.QApplication.clipboard()
|
||||||
clipboard.setText(tox_id)
|
clipboard.setText(tox_id)
|
||||||
|
|
||||||
def copy_name(self, friend):
|
def copy_name(self, friend):
|
||||||
clipboard = QtGui.QApplication.clipboard()
|
clipboard = QtWidgets.QApplication.clipboard()
|
||||||
clipboard.setText(friend.name)
|
clipboard.setText(friend.name)
|
||||||
|
|
||||||
def copy_status(self, friend):
|
def copy_status(self, friend):
|
||||||
clipboard = QtGui.QApplication.clipboard()
|
clipboard = QtWidgets.QApplication.clipboard()
|
||||||
clipboard.setText(friend.status_message)
|
clipboard.setText(friend.status_message)
|
||||||
|
|
||||||
def clear_history(self, num):
|
def clear_history(self, num):
|
||||||
self.profile.clear_history(num)
|
self.profile.clear_history(num)
|
||||||
|
|
||||||
|
def leave_gc(self, num):
|
||||||
|
self.profile.leave_gc(num)
|
||||||
|
|
||||||
|
def set_title(self, num):
|
||||||
|
self.profile.set_title(num)
|
||||||
|
|
||||||
def auto_accept(self, num, value):
|
def auto_accept(self, num, value):
|
||||||
settings = Settings.get_instance()
|
settings = Settings.get_instance()
|
||||||
tox_id = self.profile.friend_public_key(num)
|
tox_id = self.profile.friend_public_key(num)
|
||||||
@ -677,6 +717,9 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
|||||||
settings['auto_accept_from_friends'].remove(tox_id)
|
settings['auto_accept_from_friends'].remove(tox_id)
|
||||||
settings.save()
|
settings.save()
|
||||||
|
|
||||||
|
def invite_friend_to_gc(self, friend_number, group_number):
|
||||||
|
self.profile.invite_friend(friend_number, group_number)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Functions which called when user click somewhere else
|
# Functions which called when user click somewhere else
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
try:
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
from PySide import QtCore, QtGui
|
from widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
from widgets import RubberBand, create_menu, QRightClickButton, CenteredWidget, LineEdit
|
|
||||||
from profile import Profile
|
from profile import Profile
|
||||||
import smileys
|
import smileys
|
||||||
import util
|
import util
|
||||||
|
import platform
|
||||||
|
|
||||||
|
|
||||||
class MessageArea(QtGui.QPlainTextEdit):
|
class MessageArea(QtWidgets.QPlainTextEdit):
|
||||||
"""User types messages here"""
|
"""User types messages here"""
|
||||||
|
|
||||||
def __init__(self, parent, form):
|
def __init__(self, parent, form):
|
||||||
@ -20,7 +18,7 @@ class MessageArea(QtGui.QPlainTextEdit):
|
|||||||
|
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
if event.matches(QtGui.QKeySequence.Paste):
|
if event.matches(QtGui.QKeySequence.Paste):
|
||||||
mimeData = QtGui.QApplication.clipboard().mimeData()
|
mimeData = QtWidgets.QApplication.clipboard().mimeData()
|
||||||
if mimeData.hasUrls():
|
if mimeData.hasUrls():
|
||||||
for url in mimeData.urls():
|
for url in mimeData.urls():
|
||||||
self.pasteEvent(url.toString())
|
self.pasteEvent(url.toString())
|
||||||
@ -37,6 +35,10 @@ class MessageArea(QtGui.QPlainTextEdit):
|
|||||||
self.parent.send_message()
|
self.parent.send_message()
|
||||||
elif event.key() == QtCore.Qt.Key_Up and not self.toPlainText():
|
elif event.key() == QtCore.Qt.Key_Up and not self.toPlainText():
|
||||||
self.appendPlainText(Profile.get_instance().get_last_message())
|
self.appendPlainText(Profile.get_instance().get_last_message())
|
||||||
|
elif event.key() == QtCore.Qt.Key_Tab and not self.parent.profile.is_active_a_friend():
|
||||||
|
text = self.toPlainText()
|
||||||
|
pos = self.textCursor().position()
|
||||||
|
self.insertPlainText(Profile.get_instance().get_gc_peer_name(text[:pos]))
|
||||||
else:
|
else:
|
||||||
self.parent.profile.send_typing(True)
|
self.parent.profile.send_typing(True)
|
||||||
if self.timer.isActive():
|
if self.timer.isActive():
|
||||||
@ -67,49 +69,42 @@ class MessageArea(QtGui.QPlainTextEdit):
|
|||||||
e.ignore()
|
e.ignore()
|
||||||
|
|
||||||
def pasteEvent(self, text=None):
|
def pasteEvent(self, text=None):
|
||||||
text = text or QtGui.QApplication.clipboard().text()
|
text = text or QtWidgets.QApplication.clipboard().text()
|
||||||
if text.startswith('file://'):
|
if text.startswith('file://'):
|
||||||
self.parent.profile.send_file(text[7:])
|
file_name = self.parse_file_name(text)
|
||||||
else:
|
self.parent.profile.send_file(file_name)
|
||||||
|
elif text:
|
||||||
self.insertPlainText(text)
|
self.insertPlainText(text)
|
||||||
|
else:
|
||||||
|
image = QtWidgets.QApplication.clipboard().image()
|
||||||
|
if image is not None:
|
||||||
|
byte_array = QtCore.QByteArray()
|
||||||
|
buffer = QtCore.QBuffer(byte_array)
|
||||||
|
buffer.open(QtCore.QIODevice.WriteOnly)
|
||||||
|
image.save(buffer, 'PNG')
|
||||||
|
self.parent.profile.send_screenshot(bytes(byte_array.data()))
|
||||||
|
|
||||||
|
def parse_file_name(self, file_name):
|
||||||
|
import urllib
|
||||||
|
if file_name.endswith('\r\n'):
|
||||||
|
file_name = file_name[:-2]
|
||||||
|
file_name = urllib.parse.unquote(file_name)
|
||||||
|
return file_name[8 if platform.system() == 'Windows' else 7:]
|
||||||
|
|
||||||
|
|
||||||
class ScreenShotWindow(QtGui.QWidget):
|
class ScreenShotWindow(RubberBandWindow):
|
||||||
|
|
||||||
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):
|
def closeEvent(self, *args):
|
||||||
if self.parent.isHidden():
|
if self.parent.isHidden():
|
||||||
self.parent.show()
|
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):
|
def mouseReleaseEvent(self, event):
|
||||||
if self.rubberband.isVisible():
|
if self.rubberband.isVisible():
|
||||||
self.rubberband.hide()
|
self.rubberband.hide()
|
||||||
rect = self.rubberband.geometry()
|
rect = self.rubberband.geometry()
|
||||||
if rect.width() and rect.height():
|
if rect.width() and rect.height():
|
||||||
p = QtGui.QPixmap.grabWindow(QtGui.QApplication.desktop().winId(),
|
screen = QtWidgets.QApplication.primaryScreen()
|
||||||
|
p = screen.grabWindow(0,
|
||||||
rect.x() + 4,
|
rect.x() + 4,
|
||||||
rect.y() + 4,
|
rect.y() + 4,
|
||||||
rect.width() - 8,
|
rect.width() - 8,
|
||||||
@ -121,15 +116,8 @@ class ScreenShotWindow(QtGui.QWidget):
|
|||||||
Profile.get_instance().send_screenshot(bytes(byte_array.data()))
|
Profile.get_instance().send_screenshot(bytes(byte_array.data()))
|
||||||
self.close()
|
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(QtWidgets.QWidget):
|
||||||
class SmileyWindow(QtGui.QWidget):
|
|
||||||
"""
|
"""
|
||||||
Smiley selection window
|
Smiley selection window
|
||||||
"""
|
"""
|
||||||
@ -151,18 +139,18 @@ class SmileyWindow(QtGui.QWidget):
|
|||||||
self.radio = []
|
self.radio = []
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
for i in range(self.page_count): # buttons with smileys
|
for i in range(self.page_count): # buttons with smileys
|
||||||
elem = QtGui.QRadioButton(self)
|
elem = QtWidgets.QRadioButton(self)
|
||||||
elem.setGeometry(QtCore.QRect(i * 20 + 5, 180, 20, 20))
|
elem.setGeometry(QtCore.QRect(i * 20 + 5, 180, 20, 20))
|
||||||
elem.clicked.connect(lambda i=i: self.checked(i))
|
elem.clicked.connect(lambda c, t=i: self.checked(t))
|
||||||
self.radio.append(elem)
|
self.radio.append(elem)
|
||||||
width = max(self.page_count * 20 + 30, (self.page_size + 5) * 8 // 10)
|
width = max(self.page_count * 20 + 30, (self.page_size + 5) * 8 // 10)
|
||||||
self.setMaximumSize(width, 200)
|
self.setMaximumSize(width, 200)
|
||||||
self.setMinimumSize(width, 200)
|
self.setMinimumSize(width, 200)
|
||||||
self.buttons = []
|
self.buttons = []
|
||||||
for i in range(self.page_size): # pages - radio buttons
|
for i in range(self.page_size): # pages - radio buttons
|
||||||
b = QtGui.QPushButton(self)
|
b = QtWidgets.QPushButton(self)
|
||||||
b.setGeometry(QtCore.QRect((i // 8) * 20 + 5, (i % 8) * 20, 20, 20))
|
b.setGeometry(QtCore.QRect((i // 8) * 20 + 5, (i % 8) * 20, 20, 20))
|
||||||
b.clicked.connect(lambda i=i: self.clicked(i))
|
b.clicked.connect(lambda c, t=i: self.clicked(t))
|
||||||
self.buttons.append(b)
|
self.buttons.append(b)
|
||||||
self.checked(0)
|
self.checked(0)
|
||||||
|
|
||||||
@ -190,7 +178,7 @@ class SmileyWindow(QtGui.QWidget):
|
|||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
class MenuButton(QtGui.QPushButton):
|
class MenuButton(QtWidgets.QPushButton):
|
||||||
|
|
||||||
def __init__(self, parent, enter):
|
def __init__(self, parent, enter):
|
||||||
super(MenuButton, self).__init__(parent)
|
super(MenuButton, self).__init__(parent)
|
||||||
@ -201,86 +189,73 @@ class MenuButton(QtGui.QPushButton):
|
|||||||
super(MenuButton, self).enterEvent(event)
|
super(MenuButton, self).enterEvent(event)
|
||||||
|
|
||||||
|
|
||||||
class DropdownMenu(QtGui.QWidget):
|
class DropdownMenu(QtWidgets.QWidget):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super(DropdownMenu, self).__init__(parent)
|
super(DropdownMenu, self).__init__(parent)
|
||||||
self.installEventFilter(self)
|
self.installEventFilter(self)
|
||||||
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
|
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
|
||||||
self.setMaximumSize(180, 120)
|
self.setMaximumSize(120, 120)
|
||||||
self.setMinimumSize(180, 120)
|
self.setMinimumSize(120, 120)
|
||||||
self.screenshotButton = QRightClickButton(self)
|
self.screenshotButton = QRightClickButton(self)
|
||||||
self.screenshotButton.setGeometry(QtCore.QRect(0, 60, 60, 60))
|
self.screenshotButton.setGeometry(QtCore.QRect(0, 60, 60, 60))
|
||||||
self.screenshotButton.setObjectName("screenshotButton")
|
self.screenshotButton.setObjectName("screenshotButton")
|
||||||
|
|
||||||
self.fileTransferButton = QtGui.QPushButton(self)
|
self.fileTransferButton = QtWidgets.QPushButton(self)
|
||||||
self.fileTransferButton.setGeometry(QtCore.QRect(60, 60, 60, 60))
|
self.fileTransferButton.setGeometry(QtCore.QRect(60, 60, 60, 60))
|
||||||
self.fileTransferButton.setObjectName("fileTransferButton")
|
self.fileTransferButton.setObjectName("fileTransferButton")
|
||||||
|
|
||||||
self.audioMessageButton = QtGui.QPushButton(self)
|
self.smileyButton = QtWidgets.QPushButton(self)
|
||||||
self.audioMessageButton.setGeometry(QtCore.QRect(120, 60, 60, 60))
|
|
||||||
|
|
||||||
self.smileyButton = QtGui.QPushButton(self)
|
|
||||||
self.smileyButton.setGeometry(QtCore.QRect(0, 0, 60, 60))
|
self.smileyButton.setGeometry(QtCore.QRect(0, 0, 60, 60))
|
||||||
|
|
||||||
self.videoMessageButton = QtGui.QPushButton(self)
|
self.stickerButton = QtWidgets.QPushButton(self)
|
||||||
self.videoMessageButton.setGeometry(QtCore.QRect(120, 0, 60, 60))
|
|
||||||
|
|
||||||
self.stickerButton = QtGui.QPushButton(self)
|
|
||||||
self.stickerButton.setGeometry(QtCore.QRect(60, 0, 60, 60))
|
self.stickerButton.setGeometry(QtCore.QRect(60, 0, 60, 60))
|
||||||
|
|
||||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/file.png')
|
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/file.png')
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
self.fileTransferButton.setIcon(icon)
|
self.fileTransferButton.setIcon(icon)
|
||||||
self.fileTransferButton.setIconSize(QtCore.QSize(50, 50))
|
self.fileTransferButton.setIconSize(QtCore.QSize(50, 50))
|
||||||
|
|
||||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/screenshot.png')
|
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/screenshot.png')
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
self.screenshotButton.setIcon(icon)
|
self.screenshotButton.setIcon(icon)
|
||||||
self.screenshotButton.setIconSize(QtCore.QSize(50, 60))
|
self.screenshotButton.setIconSize(QtCore.QSize(50, 60))
|
||||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/audio_message.png')
|
|
||||||
icon = QtGui.QIcon(pixmap)
|
|
||||||
self.audioMessageButton.setIcon(icon)
|
|
||||||
self.audioMessageButton.setIconSize(QtCore.QSize(50, 50))
|
|
||||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/smiley.png')
|
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/smiley.png')
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
self.smileyButton.setIcon(icon)
|
self.smileyButton.setIcon(icon)
|
||||||
self.smileyButton.setIconSize(QtCore.QSize(50, 50))
|
self.smileyButton.setIconSize(QtCore.QSize(50, 50))
|
||||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/video_message.png')
|
|
||||||
icon = QtGui.QIcon(pixmap)
|
|
||||||
self.videoMessageButton.setIcon(icon)
|
|
||||||
self.videoMessageButton.setIconSize(QtCore.QSize(55, 55))
|
|
||||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/sticker.png')
|
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/sticker.png')
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
self.stickerButton.setIcon(icon)
|
self.stickerButton.setIcon(icon)
|
||||||
self.stickerButton.setIconSize(QtCore.QSize(55, 55))
|
self.stickerButton.setIconSize(QtCore.QSize(55, 55))
|
||||||
|
|
||||||
self.screenshotButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send screenshot", None, QtGui.QApplication.UnicodeUTF8))
|
self.screenshotButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send screenshot"))
|
||||||
self.fileTransferButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send file", None, QtGui.QApplication.UnicodeUTF8))
|
self.fileTransferButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send file"))
|
||||||
self.audioMessageButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send audio message", None, QtGui.QApplication.UnicodeUTF8))
|
self.smileyButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Add smiley"))
|
||||||
self.videoMessageButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send video message", None, QtGui.QApplication.UnicodeUTF8))
|
self.stickerButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send sticker"))
|
||||||
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.fileTransferButton.clicked.connect(parent.send_file)
|
||||||
self.screenshotButton.clicked.connect(parent.send_screenshot)
|
self.screenshotButton.clicked.connect(parent.send_screenshot)
|
||||||
self.connect(self.screenshotButton, QtCore.SIGNAL("rightClicked()"), lambda: parent.send_screenshot(True))
|
self.screenshotButton.rightClicked.connect(lambda: parent.send_screenshot(True))
|
||||||
self.smileyButton.clicked.connect(parent.send_smiley)
|
self.smileyButton.clicked.connect(parent.send_smiley)
|
||||||
self.stickerButton.clicked.connect(parent.send_sticker)
|
self.stickerButton.clicked.connect(parent.send_sticker)
|
||||||
|
|
||||||
def leaveEvent(self, event):
|
def leaveEvent(self, event):
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def eventFilter(self, object, event):
|
def eventFilter(self, obj, event):
|
||||||
if event.type() == QtCore.QEvent.WindowDeactivate:
|
if event.type() == QtCore.QEvent.WindowDeactivate:
|
||||||
self.close()
|
self.close()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class StickerItem(QtGui.QWidget):
|
class StickerItem(QtWidgets.QWidget):
|
||||||
|
|
||||||
def __init__(self, fl):
|
def __init__(self, fl):
|
||||||
super(StickerItem, self).__init__()
|
super(StickerItem, self).__init__()
|
||||||
self._image_label = QtGui.QLabel(self)
|
self._image_label = QtWidgets.QLabel(self)
|
||||||
self.path = fl
|
self.path = fl
|
||||||
self.pixmap = QtGui.QPixmap()
|
self.pixmap = QtGui.QPixmap()
|
||||||
self.pixmap.load(fl)
|
self.pixmap.load(fl)
|
||||||
@ -290,7 +265,7 @@ class StickerItem(QtGui.QWidget):
|
|||||||
self._image_label.setPixmap(self.pixmap)
|
self._image_label.setPixmap(self.pixmap)
|
||||||
|
|
||||||
|
|
||||||
class StickerWindow(QtGui.QWidget):
|
class StickerWindow(QtWidgets.QWidget):
|
||||||
"""Sticker selection window"""
|
"""Sticker selection window"""
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
@ -298,16 +273,16 @@ class StickerWindow(QtGui.QWidget):
|
|||||||
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
|
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
|
||||||
self.setMaximumSize(250, 200)
|
self.setMaximumSize(250, 200)
|
||||||
self.setMinimumSize(250, 200)
|
self.setMinimumSize(250, 200)
|
||||||
self.list = QtGui.QListWidget(self)
|
self.list = QtWidgets.QListWidget(self)
|
||||||
self.list.setGeometry(QtCore.QRect(0, 0, 250, 200))
|
self.list.setGeometry(QtCore.QRect(0, 0, 250, 200))
|
||||||
self.arr = smileys.sticker_loader()
|
self.arr = smileys.sticker_loader()
|
||||||
for sticker in self.arr:
|
for sticker in self.arr:
|
||||||
item = StickerItem(sticker)
|
item = StickerItem(sticker)
|
||||||
elem = QtGui.QListWidgetItem()
|
elem = QtWidgets.QListWidgetItem()
|
||||||
elem.setSizeHint(QtCore.QSize(250, item.height()))
|
elem.setSizeHint(QtCore.QSize(250, item.height()))
|
||||||
self.list.addItem(elem)
|
self.list.addItem(elem)
|
||||||
self.list.setItemWidget(elem, item)
|
self.list.setItemWidget(elem, item)
|
||||||
self.list.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
|
self.list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||||
self.list.setSpacing(3)
|
self.list.setSpacing(3)
|
||||||
self.list.clicked.connect(self.click)
|
self.list.clicked.connect(self.click)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
@ -329,60 +304,47 @@ class WelcomeScreen(CenteredWidget):
|
|||||||
self.setMinimumSize(250, 200)
|
self.setMinimumSize(250, 200)
|
||||||
self.center()
|
self.center()
|
||||||
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
||||||
self.text = QtGui.QTextBrowser(self)
|
self.text = QtWidgets.QTextBrowser(self)
|
||||||
self.text.setGeometry(QtCore.QRect(0, 0, 250, 170))
|
self.text.setGeometry(QtCore.QRect(0, 0, 250, 170))
|
||||||
self.text.setOpenExternalLinks(True)
|
self.text.setOpenExternalLinks(True)
|
||||||
self.checkbox = QtGui.QCheckBox(self)
|
self.checkbox = QtWidgets.QCheckBox(self)
|
||||||
self.checkbox.setGeometry(QtCore.QRect(5, 170, 240, 30))
|
self.checkbox.setGeometry(QtCore.QRect(5, 170, 240, 30))
|
||||||
self.checkbox.setText(QtGui.QApplication.translate('WelcomeScreen', "Don't show again",
|
self.checkbox.setText(QtWidgets.QApplication.translate('WelcomeScreen', "Don't show again"))
|
||||||
None, QtGui.QApplication.UnicodeUTF8))
|
self.setWindowTitle(QtWidgets.QApplication.translate('WelcomeScreen', 'Tip of the day'))
|
||||||
self.setWindowTitle(QtGui.QApplication.translate('WelcomeScreen', 'Tip of the day',
|
|
||||||
None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
import random
|
import random
|
||||||
num = random.randint(0, 10)
|
num = random.randint(0, 10)
|
||||||
if num == 0:
|
if num == 0:
|
||||||
text = QtGui.QApplication.translate('WelcomeScreen', 'Press Esc if you want hide app to tray.',
|
text = QtWidgets.QApplication.translate('WelcomeScreen', 'Press Esc if you want hide app to tray.')
|
||||||
None, QtGui.QApplication.UnicodeUTF8)
|
|
||||||
elif num == 1:
|
elif num == 1:
|
||||||
text = QtGui.QApplication.translate('WelcomeScreen',
|
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
||||||
'Right click on screenshot button hides app to tray during screenshot.',
|
'Right click on screenshot button hides app to tray during screenshot.')
|
||||||
None, QtGui.QApplication.UnicodeUTF8)
|
|
||||||
elif num == 2:
|
elif num == 2:
|
||||||
text = QtGui.QApplication.translate('WelcomeScreen',
|
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
||||||
'You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a>',
|
'You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a>')
|
||||||
None, QtGui.QApplication.UnicodeUTF8)
|
|
||||||
elif num == 3:
|
elif num == 3:
|
||||||
text = QtGui.QApplication.translate('WelcomeScreen',
|
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
||||||
'Use Settings -> Interface to customize interface.',
|
'Use Settings -> Interface to customize interface.')
|
||||||
None, QtGui.QApplication.UnicodeUTF8)
|
|
||||||
elif num == 4:
|
elif num == 4:
|
||||||
text = QtGui.QApplication.translate('WelcomeScreen',
|
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
||||||
'Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.',
|
'Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.')
|
||||||
None, QtGui.QApplication.UnicodeUTF8)
|
|
||||||
elif num == 5:
|
elif num == 5:
|
||||||
text = QtGui.QApplication.translate('WelcomeScreen',
|
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
||||||
'Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/xveduk/toxygen/blob/master/docs/plugins.md">Read more</a>',
|
'Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a>')
|
||||||
None, QtGui.QApplication.UnicodeUTF8)
|
|
||||||
elif num == 6:
|
elif num == 6:
|
||||||
text = QtGui.QApplication.translate('WelcomeScreen',
|
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
||||||
'New in Toxygen v0.2.6:<br>Updater<br>Better contact sorting<br>Plugins improvements',
|
'Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.')
|
||||||
None, QtGui.QApplication.UnicodeUTF8)
|
|
||||||
elif num == 7:
|
elif num == 7:
|
||||||
text = QtGui.QApplication.translate('WelcomeScreen',
|
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
||||||
'Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.',
|
'New in Toxygen 0.4.1:<br>Downloading nodes from tox.chat<br>Bug fixes')
|
||||||
None, QtGui.QApplication.UnicodeUTF8)
|
|
||||||
elif num == 8:
|
elif num == 8:
|
||||||
text = QtGui.QApplication.translate('WelcomeScreen',
|
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
||||||
'Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu',
|
'Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu')
|
||||||
None, QtGui.QApplication.UnicodeUTF8)
|
|
||||||
elif num == 9:
|
elif num == 9:
|
||||||
text = QtGui.QApplication.translate('WelcomeScreen',
|
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
||||||
'Use right click on inline image to save it',
|
'Use right click on inline image to save it')
|
||||||
None, QtGui.QApplication.UnicodeUTF8)
|
|
||||||
else:
|
else:
|
||||||
text = QtGui.QApplication.translate('WelcomeScreen',
|
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
||||||
'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.',
|
'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.')
|
||||||
None, QtGui.QApplication.UnicodeUTF8)
|
|
||||||
self.text.setHtml(text)
|
self.text.setHtml(text)
|
||||||
self.checkbox.stateChanged.connect(self.not_show)
|
self.checkbox.stateChanged.connect(self.not_show)
|
||||||
QtCore.QTimer.singleShot(1000, self.show)
|
QtCore.QTimer.singleShot(1000, self.show)
|
||||||
@ -394,7 +356,7 @@ class WelcomeScreen(CenteredWidget):
|
|||||||
s.save()
|
s.save()
|
||||||
|
|
||||||
|
|
||||||
class MainMenuButton(QtGui.QPushButton):
|
class MainMenuButton(QtWidgets.QPushButton):
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
@ -406,16 +368,18 @@ class MainMenuButton(QtGui.QPushButton):
|
|||||||
super().setText(text)
|
super().setText(text)
|
||||||
|
|
||||||
|
|
||||||
class ClickableLabel(QtGui.QLabel):
|
class ClickableLabel(QtWidgets.QLabel):
|
||||||
|
|
||||||
|
clicked = QtCore.pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
|
|
||||||
def mouseReleaseEvent(self, ev):
|
def mouseReleaseEvent(self, ev):
|
||||||
self.emit(QtCore.SIGNAL('clicked()'))
|
self.clicked.emit()
|
||||||
|
|
||||||
|
|
||||||
class SearchScreen(QtGui.QWidget):
|
class SearchScreen(QtWidgets.QWidget):
|
||||||
|
|
||||||
def __init__(self, messages, width, *args):
|
def __init__(self, messages, width, *args):
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
@ -433,23 +397,23 @@ class SearchScreen(QtGui.QWidget):
|
|||||||
self.search_button.setScaledContents(False)
|
self.search_button.setScaledContents(False)
|
||||||
self.search_button.setAlignment(QtCore.Qt.AlignCenter)
|
self.search_button.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
self.search_button.setPixmap(pixmap)
|
self.search_button.setPixmap(pixmap)
|
||||||
self.connect(self.search_button, QtCore.SIGNAL('clicked()'), self.search)
|
self.search_button.clicked.connect(self.search)
|
||||||
|
|
||||||
font = QtGui.QFont()
|
font = QtGui.QFont()
|
||||||
font.setPointSize(32)
|
font.setPointSize(32)
|
||||||
font.setBold(True)
|
font.setBold(True)
|
||||||
|
|
||||||
self.prev_button = QtGui.QPushButton(self)
|
self.prev_button = QtWidgets.QPushButton(self)
|
||||||
self.prev_button.setGeometry(width - 120, 0, 40, 40)
|
self.prev_button.setGeometry(width - 120, 0, 40, 40)
|
||||||
self.prev_button.clicked.connect(self.prev)
|
self.prev_button.clicked.connect(self.prev)
|
||||||
self.prev_button.setText('\u25B2')
|
self.prev_button.setText('\u25B2')
|
||||||
|
|
||||||
self.next_button = QtGui.QPushButton(self)
|
self.next_button = QtWidgets.QPushButton(self)
|
||||||
self.next_button.setGeometry(width - 80, 0, 40, 40)
|
self.next_button.setGeometry(width - 80, 0, 40, 40)
|
||||||
self.next_button.clicked.connect(self.next)
|
self.next_button.clicked.connect(self.next)
|
||||||
self.next_button.setText('\u25BC')
|
self.next_button.setText('\u25BC')
|
||||||
|
|
||||||
self.close_button = QtGui.QPushButton(self)
|
self.close_button = QtWidgets.QPushButton(self)
|
||||||
self.close_button.setGeometry(width - 40, 0, 40, 40)
|
self.close_button.setGeometry(width - 40, 0, 40, 40)
|
||||||
self.close_button.clicked.connect(self.close)
|
self.close_button.clicked.connect(self.close)
|
||||||
self.close_button.setText('×')
|
self.close_button.setText('×')
|
||||||
@ -462,8 +426,7 @@ class SearchScreen(QtGui.QWidget):
|
|||||||
self.retranslateUi()
|
self.retranslateUi()
|
||||||
|
|
||||||
def retranslateUi(self):
|
def retranslateUi(self):
|
||||||
self.search_text.setPlaceholderText(QtGui.QApplication.translate("MainWindow", "Search", None,
|
self.search_text.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "Search"))
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
|
|
||||||
def show(self):
|
def show(self):
|
||||||
super().show()
|
super().show()
|
||||||
@ -519,13 +482,11 @@ class SearchScreen(QtGui.QWidget):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def not_found(text):
|
def not_found(text):
|
||||||
mbox = QtGui.QMessageBox()
|
mbox = QtWidgets.QMessageBox()
|
||||||
mbox.setText(QtGui.QApplication.translate("MainWindow",
|
mbox_text = QtWidgets.QApplication.translate("MainWindow",
|
||||||
'Text "{}" was not found'.format(text),
|
'Text "{}" was not found')
|
||||||
None,
|
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
mbox.setText(mbox_text.format(text))
|
||||||
mbox.setWindowTitle(QtGui.QApplication.translate("MainWindow",
|
mbox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow",
|
||||||
'Not found',
|
'Not found'))
|
||||||
None,
|
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
mbox.exec_()
|
mbox.exec_()
|
||||||
|
665
toxygen/menu.py
@ -5,7 +5,9 @@ MESSAGE_TYPE = {
|
|||||||
'ACTION': 1,
|
'ACTION': 1,
|
||||||
'FILE_TRANSFER': 2,
|
'FILE_TRANSFER': 2,
|
||||||
'INLINE': 3,
|
'INLINE': 3,
|
||||||
'INFO_MESSAGE': 4
|
'INFO_MESSAGE': 4,
|
||||||
|
'GC_TEXT': 5,
|
||||||
|
'GC_ACTION': 6
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -39,6 +41,16 @@ class TextMessage(Message):
|
|||||||
return self._message, self._owner, self._time, self._type
|
return self._message, self._owner, self._time, self._type
|
||||||
|
|
||||||
|
|
||||||
|
class GroupChatMessage(TextMessage):
|
||||||
|
|
||||||
|
def __init__(self, message, owner, time, message_type, name):
|
||||||
|
super().__init__(message, owner, time, message_type)
|
||||||
|
self._user_name = name
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
return self._message, self._owner, self._time, self._type, self._user_name
|
||||||
|
|
||||||
|
|
||||||
class TransferMessage(Message):
|
class TransferMessage(Message):
|
||||||
"""
|
"""
|
||||||
Message with info about file transfer
|
Message with info about file transfer
|
||||||
|
1
toxygen/nodes.json
Normal file
@ -1,7 +1,4 @@
|
|||||||
try:
|
from PyQt5 import QtCore, QtWidgets
|
||||||
from PySide import QtCore, QtGui
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
from util import curr_directory
|
from util import curr_directory
|
||||||
import wave
|
import wave
|
||||||
import pyaudio
|
import pyaudio
|
||||||
@ -23,16 +20,16 @@ def tray_notification(title, text, tray, window):
|
|||||||
:param tray: ref to tray icon
|
:param tray: ref to tray icon
|
||||||
:param window: main window
|
:param window: main window
|
||||||
"""
|
"""
|
||||||
if QtGui.QSystemTrayIcon.isSystemTrayAvailable():
|
if QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
|
||||||
if len(text) > 30:
|
if len(text) > 30:
|
||||||
text = text[:27] + '...'
|
text = text[:27] + '...'
|
||||||
tray.showMessage(title, text, QtGui.QSystemTrayIcon.NoIcon, 3000)
|
tray.showMessage(title, text, QtWidgets.QSystemTrayIcon.NoIcon, 3000)
|
||||||
QtGui.QApplication.alert(window, 0)
|
QtWidgets.QApplication.alert(window, 0)
|
||||||
|
|
||||||
def message_clicked():
|
def message_clicked():
|
||||||
window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
|
window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
|
||||||
window.activateWindow()
|
window.activateWindow()
|
||||||
tray.connect(tray, QtCore.SIGNAL("messageClicked()"), message_clicked)
|
tray.messageClicked.connect(message_clicked)
|
||||||
|
|
||||||
|
|
||||||
class AudioFile:
|
class AudioFile:
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
from widgets import CenteredWidget, LineEdit
|
from widgets import CenteredWidget, LineEdit
|
||||||
try:
|
from PyQt5 import QtCore, QtWidgets
|
||||||
from PySide import QtCore, QtGui
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
|
|
||||||
|
|
||||||
class PasswordArea(LineEdit):
|
class PasswordArea(LineEdit):
|
||||||
@ -10,7 +7,7 @@ class PasswordArea(LineEdit):
|
|||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super(PasswordArea, self).__init__(parent)
|
super(PasswordArea, self).__init__(parent)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
|
self.setEchoMode(QtWidgets.QLineEdit.Password)
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
if event.key() == QtCore.Qt.Key_Return:
|
if event.key() == QtCore.Qt.Key_Return:
|
||||||
@ -31,18 +28,18 @@ class PasswordScreenBase(CenteredWidget):
|
|||||||
self.setMinimumSize(QtCore.QSize(360, 170))
|
self.setMinimumSize(QtCore.QSize(360, 170))
|
||||||
self.setMaximumSize(QtCore.QSize(360, 170))
|
self.setMaximumSize(QtCore.QSize(360, 170))
|
||||||
|
|
||||||
self.enter_pass = QtGui.QLabel(self)
|
self.enter_pass = QtWidgets.QLabel(self)
|
||||||
self.enter_pass.setGeometry(QtCore.QRect(30, 10, 300, 30))
|
self.enter_pass.setGeometry(QtCore.QRect(30, 10, 300, 30))
|
||||||
|
|
||||||
self.password = PasswordArea(self)
|
self.password = PasswordArea(self)
|
||||||
self.password.setGeometry(QtCore.QRect(30, 50, 300, 30))
|
self.password.setGeometry(QtCore.QRect(30, 50, 300, 30))
|
||||||
|
|
||||||
self.button = QtGui.QPushButton(self)
|
self.button = QtWidgets.QPushButton(self)
|
||||||
self.button.setGeometry(QtCore.QRect(30, 90, 300, 30))
|
self.button.setGeometry(QtCore.QRect(30, 90, 300, 30))
|
||||||
self.button.setText('OK')
|
self.button.setText('OK')
|
||||||
self.button.clicked.connect(self.button_click)
|
self.button.clicked.connect(self.button_click)
|
||||||
|
|
||||||
self.warning = QtGui.QLabel(self)
|
self.warning = QtWidgets.QLabel(self)
|
||||||
self.warning.setGeometry(QtCore.QRect(30, 130, 300, 30))
|
self.warning.setGeometry(QtCore.QRect(30, 130, 300, 30))
|
||||||
self.warning.setStyleSheet('QLabel { color: #F70D1A; }')
|
self.warning.setStyleSheet('QLabel { color: #F70D1A; }')
|
||||||
self.warning.setVisible(False)
|
self.warning.setVisible(False)
|
||||||
@ -61,9 +58,9 @@ class PasswordScreenBase(CenteredWidget):
|
|||||||
super(PasswordScreenBase, self).keyPressEvent(event)
|
super(PasswordScreenBase, self).keyPressEvent(event)
|
||||||
|
|
||||||
def retranslateUi(self):
|
def retranslateUi(self):
|
||||||
self.setWindowTitle(QtGui.QApplication.translate("pass", "Enter password", None, QtGui.QApplication.UnicodeUTF8))
|
self.setWindowTitle(QtWidgets.QApplication.translate("pass", "Enter password"))
|
||||||
self.enter_pass.setText(QtGui.QApplication.translate("pass", "Password:", None, QtGui.QApplication.UnicodeUTF8))
|
self.enter_pass.setText(QtWidgets.QApplication.translate("pass", "Password:"))
|
||||||
self.warning.setText(QtGui.QApplication.translate("pass", "Incorrect password", None, QtGui.QApplication.UnicodeUTF8))
|
self.warning.setText(QtWidgets.QApplication.translate("pass", "Incorrect password"))
|
||||||
|
|
||||||
|
|
||||||
class PasswordScreen(PasswordScreenBase):
|
class PasswordScreen(PasswordScreenBase):
|
||||||
@ -116,37 +113,32 @@ class SetProfilePasswordScreen(CenteredWidget):
|
|||||||
self.setMaximumSize(QtCore.QSize(700, 200))
|
self.setMaximumSize(QtCore.QSize(700, 200))
|
||||||
self.password = LineEdit(self)
|
self.password = LineEdit(self)
|
||||||
self.password.setGeometry(QtCore.QRect(40, 10, 300, 30))
|
self.password.setGeometry(QtCore.QRect(40, 10, 300, 30))
|
||||||
self.password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
|
self.password.setEchoMode(QtWidgets.QLineEdit.Password)
|
||||||
self.confirm_password = LineEdit(self)
|
self.confirm_password = LineEdit(self)
|
||||||
self.confirm_password.setGeometry(QtCore.QRect(40, 50, 300, 30))
|
self.confirm_password.setGeometry(QtCore.QRect(40, 50, 300, 30))
|
||||||
self.confirm_password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
|
self.confirm_password.setEchoMode(QtWidgets.QLineEdit.Password)
|
||||||
self.set_password = QtGui.QPushButton(self)
|
self.set_password = QtWidgets.QPushButton(self)
|
||||||
self.set_password.setGeometry(QtCore.QRect(40, 100, 300, 30))
|
self.set_password.setGeometry(QtCore.QRect(40, 100, 300, 30))
|
||||||
self.set_password.clicked.connect(self.new_password)
|
self.set_password.clicked.connect(self.new_password)
|
||||||
self.not_match = QtGui.QLabel(self)
|
self.not_match = QtWidgets.QLabel(self)
|
||||||
self.not_match.setGeometry(QtCore.QRect(350, 50, 300, 30))
|
self.not_match.setGeometry(QtCore.QRect(350, 50, 300, 30))
|
||||||
self.not_match.setVisible(False)
|
self.not_match.setVisible(False)
|
||||||
self.not_match.setStyleSheet('QLabel { color: #BC1C1C; }')
|
self.not_match.setStyleSheet('QLabel { color: #BC1C1C; }')
|
||||||
self.warning = QtGui.QLabel(self)
|
self.warning = QtWidgets.QLabel(self)
|
||||||
self.warning.setGeometry(QtCore.QRect(40, 160, 500, 30))
|
self.warning.setGeometry(QtCore.QRect(40, 160, 500, 30))
|
||||||
self.warning.setStyleSheet('QLabel { color: #BC1C1C; }')
|
self.warning.setStyleSheet('QLabel { color: #BC1C1C; }')
|
||||||
|
|
||||||
def retranslateUi(self):
|
def retranslateUi(self):
|
||||||
self.setWindowTitle(QtGui.QApplication.translate("PasswordScreen", "Profile password", None,
|
self.setWindowTitle(QtWidgets.QApplication.translate("PasswordScreen", "Profile password"))
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.password.setPlaceholderText(
|
self.password.setPlaceholderText(
|
||||||
QtGui.QApplication.translate("PasswordScreen", "Password (at least 8 symbols)", None,
|
QtWidgets.QApplication.translate("PasswordScreen", "Password (at least 8 symbols)"))
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.confirm_password.setPlaceholderText(
|
self.confirm_password.setPlaceholderText(
|
||||||
QtGui.QApplication.translate("PasswordScreen", "Confirm password", None,
|
QtWidgets.QApplication.translate("PasswordScreen", "Confirm password"))
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.set_password.setText(
|
self.set_password.setText(
|
||||||
QtGui.QApplication.translate("PasswordScreen", "Set password", None, QtGui.QApplication.UnicodeUTF8))
|
QtWidgets.QApplication.translate("PasswordScreen", "Set password"))
|
||||||
self.not_match.setText(QtGui.QApplication.translate("PasswordScreen", "Passwords do not match", None,
|
self.not_match.setText(QtWidgets.QApplication.translate("PasswordScreen", "Passwords do not match"))
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.warning.setText(
|
self.warning.setText(
|
||||||
QtGui.QApplication.translate("PasswordScreen", "There is no way to recover lost passwords", None,
|
QtWidgets.QApplication.translate("PasswordScreen", "There is no way to recover lost passwords"))
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
|
|
||||||
def new_password(self):
|
def new_password(self):
|
||||||
if self.password.text() == self.confirm_password.text():
|
if self.password.text() == self.confirm_password.text():
|
||||||
@ -155,10 +147,8 @@ class SetProfilePasswordScreen(CenteredWidget):
|
|||||||
self.close()
|
self.close()
|
||||||
else:
|
else:
|
||||||
self.not_match.setText(
|
self.not_match.setText(
|
||||||
QtGui.QApplication.translate("PasswordScreen", "Password must be at least 8 symbols", None,
|
QtWidgets.QApplication.translate("PasswordScreen", "Password must be at least 8 symbols"))
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.not_match.setVisible(True)
|
self.not_match.setVisible(True)
|
||||||
else:
|
else:
|
||||||
self.not_match.setText(QtGui.QApplication.translate("PasswordScreen", "Passwords do not match", None,
|
self.not_match.setText(QtWidgets.QApplication.translate("PasswordScreen", "Passwords do not match"))
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.not_match.setVisible(True)
|
self.not_match.setVisible(True)
|
||||||
|
@ -51,7 +51,8 @@ class PluginLoader(util.Singleton):
|
|||||||
continue
|
continue
|
||||||
for elem in dir(module):
|
for elem in dir(module):
|
||||||
obj = getattr(module, elem)
|
obj = getattr(module, elem)
|
||||||
if inspect.isclass(obj) and hasattr(obj, 'is_plugin') and obj.is_plugin: # looking for plugin class in module
|
# looking for plugin class in module
|
||||||
|
if inspect.isclass(obj) and hasattr(obj, 'is_plugin') and obj.is_plugin:
|
||||||
print('Plugin', elem)
|
print('Plugin', elem)
|
||||||
try: # create instance of plugin class
|
try: # create instance of plugin class
|
||||||
inst = obj(self._tox, self._profile, self._settings, self._encr)
|
inst = obj(self._tox, self._profile, self._settings, self._encr)
|
||||||
@ -96,10 +97,13 @@ class PluginLoader(util.Singleton):
|
|||||||
"""
|
"""
|
||||||
result = []
|
result = []
|
||||||
for data in self._plugins.values():
|
for data in self._plugins.values():
|
||||||
|
try:
|
||||||
result.append([data[0].get_name(), # plugin full name
|
result.append([data[0].get_name(), # plugin full name
|
||||||
data[1], # is enabled
|
data[1], # is enabled
|
||||||
data[0].get_description(), # plugin description
|
data[0].get_description(), # plugin description
|
||||||
data[0].get_short_name()]) # key - short unique name
|
data[0].get_short_name()]) # key - short unique name
|
||||||
|
except:
|
||||||
|
continue
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def plugin_window(self, key):
|
def plugin_window(self, key):
|
||||||
@ -165,3 +169,8 @@ class PluginLoader(util.Singleton):
|
|||||||
if self._plugins[key][1]:
|
if self._plugins[key][1]:
|
||||||
self._plugins[key][0].close()
|
self._plugins[key][0].close()
|
||||||
del self._plugins[key]
|
del self._plugins[key]
|
||||||
|
|
||||||
|
def reload(self):
|
||||||
|
print('Reloading plugins')
|
||||||
|
self.stop()
|
||||||
|
self.load()
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
try:
|
from PyQt5 import QtCore, QtWidgets
|
||||||
from PySide import QtCore, QtGui
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
|
|
||||||
|
|
||||||
MAX_SHORT_NAME_LENGTH = 5
|
MAX_SHORT_NAME_LENGTH = 5
|
||||||
@ -137,10 +134,10 @@ class PluginSuperClass:
|
|||||||
:param command: string with command
|
:param command: string with command
|
||||||
"""
|
"""
|
||||||
if command == 'help':
|
if command == 'help':
|
||||||
msgbox = QtGui.QMessageBox()
|
msgbox = QtWidgets.QMessageBox()
|
||||||
title = QtGui.QApplication.translate("PluginWindow", "List of commands for plugin {}", None, QtGui.QApplication.UnicodeUTF8)
|
title = QtWidgets.QApplication.translate("PluginWindow", "List of commands for plugin {}")
|
||||||
msgbox.setWindowTitle(title.format(self._name))
|
msgbox.setWindowTitle(title.format(self._name))
|
||||||
msgbox.setText(QtGui.QApplication.translate("PluginWindow", "No commands available", None, QtGui.QApplication.UnicodeUTF8))
|
msgbox.setText(QtWidgets.QApplication.translate("PluginWindow", "No commands available"))
|
||||||
msgbox.exec_()
|
msgbox.exec_()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
@ -151,7 +148,7 @@ class PluginSuperClass:
|
|||||||
"""
|
"""
|
||||||
This method loads translations for GUI
|
This method loads translations for GUI
|
||||||
"""
|
"""
|
||||||
app = QtGui.QApplication.instance()
|
app = QtWidgets.QApplication.instance()
|
||||||
langs = self._settings.supported_languages()
|
langs = self._settings.supported_languages()
|
||||||
curr_lang = self._settings['language']
|
curr_lang = self._settings['language']
|
||||||
if curr_lang in langs:
|
if curr_lang in langs:
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
from list_items import *
|
from list_items import *
|
||||||
try:
|
from PyQt5 import QtGui, QtWidgets
|
||||||
from PySide import QtCore, QtGui
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
from friend import *
|
from friend import *
|
||||||
from settings import *
|
from settings import *
|
||||||
from toxcore_enums_and_consts import *
|
from toxcore_enums_and_consts import *
|
||||||
@ -17,6 +14,10 @@ import avwidgets
|
|||||||
import plugin_support
|
import plugin_support
|
||||||
import basecontact
|
import basecontact
|
||||||
import items_factory
|
import items_factory
|
||||||
|
import cv2
|
||||||
|
import threading
|
||||||
|
from group_chat import *
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
class Profile(basecontact.BaseContact, Singleton):
|
class Profile(basecontact.BaseContact, Singleton):
|
||||||
@ -39,8 +40,10 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
self._tox = tox
|
self._tox = tox
|
||||||
self._file_transfers = {} # dict of file transfers. key - tuple (friend_number, file_number)
|
self._file_transfers = {} # dict of file transfers. key - tuple (friend_number, file_number)
|
||||||
self._call = calls.AV(tox.AV) # object with data about calls
|
self._call = calls.AV(tox.AV) # object with data about calls
|
||||||
|
self._call_widgets = {} # dict of incoming call widgets
|
||||||
self._incoming_calls = set()
|
self._incoming_calls = set()
|
||||||
self._load_history = True
|
self._load_history = True
|
||||||
|
self._waiting_for_reconnection = False
|
||||||
self._factory = items_factory.ItemsFactory(self._screen.friends_list, self._messages)
|
self._factory = items_factory.ItemsFactory(self._screen.friends_list, self._messages)
|
||||||
settings = Settings.get_instance()
|
settings = Settings.get_instance()
|
||||||
self._sorting = settings['sorting']
|
self._sorting = settings['sorting']
|
||||||
@ -69,6 +72,8 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
friend = Friend(message_getter, i, name, status_message, item, tox_id)
|
friend = Friend(message_getter, i, name, status_message, item, tox_id)
|
||||||
friend.set_alias(alias)
|
friend.set_alias(alias)
|
||||||
self._contacts.append(friend)
|
self._contacts.append(friend)
|
||||||
|
if len(self._contacts):
|
||||||
|
self.set_active(0)
|
||||||
self.filtration_and_sorting(self._sorting)
|
self.filtration_and_sorting(self._sorting)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
@ -86,8 +91,9 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
super(Profile, self).set_status(status)
|
super(Profile, self).set_status(status)
|
||||||
if status is not None:
|
if status is not None:
|
||||||
self._tox.self_set_status(status)
|
self._tox.self_set_status(status)
|
||||||
else:
|
elif not self._waiting_for_reconnection:
|
||||||
QtCore.QTimer.singleShot(45000, self.reconnect)
|
self._waiting_for_reconnection = True
|
||||||
|
QtCore.QTimer.singleShot(50000, self.reconnect)
|
||||||
|
|
||||||
def set_name(self, value):
|
def set_name(self, value):
|
||||||
if self.name == value:
|
if self.name == value:
|
||||||
@ -95,8 +101,7 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
tmp = self.name
|
tmp = self.name
|
||||||
super(Profile, self).set_name(value.encode('utf-8'))
|
super(Profile, self).set_name(value.encode('utf-8'))
|
||||||
self._tox.self_set_name(self._name.encode('utf-8'))
|
self._tox.self_set_name(self._name.encode('utf-8'))
|
||||||
message = QtGui.QApplication.translate("MainWindow", 'User {} is now known as {}', None,
|
message = QtWidgets.QApplication.translate("MainWindow", 'User {} is now known as {}')
|
||||||
QtGui.QApplication.UnicodeUTF8)
|
|
||||||
message = message.format(tmp, value)
|
message = message.format(tmp, value)
|
||||||
for friend in self._contacts:
|
for friend in self._contacts:
|
||||||
friend.append_message(InfoMessage(message, time.time()))
|
friend.append_message(InfoMessage(message, time.time()))
|
||||||
@ -128,6 +133,7 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
filter_str = filter_str.lower()
|
filter_str = filter_str.lower()
|
||||||
settings = Settings.get_instance()
|
settings = Settings.get_instance()
|
||||||
number = self.get_active_number()
|
number = self.get_active_number()
|
||||||
|
is_friend = self.is_active_a_friend()
|
||||||
if sorting > 1:
|
if sorting > 1:
|
||||||
if sorting & 2:
|
if sorting & 2:
|
||||||
self._contacts = sorted(self._contacts, key=lambda x: int(x.status is not None), reverse=True)
|
self._contacts = sorted(self._contacts, key=lambda x: int(x.status is not None), reverse=True)
|
||||||
@ -163,7 +169,7 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
self._sorting, self._filter_string = sorting, filter_str
|
self._sorting, self._filter_string = sorting, filter_str
|
||||||
settings['sorting'] = self._sorting
|
settings['sorting'] = self._sorting
|
||||||
settings.save()
|
settings.save()
|
||||||
self.set_active_by_number(number)
|
self.set_active_by_number_and_type(number, is_friend)
|
||||||
|
|
||||||
def update_filtration(self):
|
def update_filtration(self):
|
||||||
"""
|
"""
|
||||||
@ -176,7 +182,7 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
def get_friend_by_number(self, num):
|
def get_friend_by_number(self, num):
|
||||||
return list(filter(lambda x: x.number == num, self._contacts))[0]
|
return list(filter(lambda x: x.number == num and type(x) is Friend, self._contacts))[0]
|
||||||
|
|
||||||
def get_friend(self, num):
|
def get_friend(self, num):
|
||||||
if num < 0 or num >= len(self._contacts):
|
if num < 0 or num >= len(self._contacts):
|
||||||
@ -203,6 +209,7 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
if value == -1: # all friends were deleted
|
if value == -1: # all friends were deleted
|
||||||
self._screen.account_name.setText('')
|
self._screen.account_name.setText('')
|
||||||
self._screen.account_status.setText('')
|
self._screen.account_status.setText('')
|
||||||
|
self._screen.account_status.setToolTip('')
|
||||||
self._active_friend = -1
|
self._active_friend = -1
|
||||||
self._screen.account_avatar.setHidden(True)
|
self._screen.account_avatar.setHidden(True)
|
||||||
self._messages.clear()
|
self._messages.clear()
|
||||||
@ -218,6 +225,7 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
friend = self._contacts[value]
|
friend = self._contacts[value]
|
||||||
|
friend.remove_invalid_unsent_files()
|
||||||
if self._active_friend != value:
|
if self._active_friend != value:
|
||||||
self._screen.messageEdit.setPlainText(friend.curr_text)
|
self._screen.messageEdit.setPlainText(friend.curr_text)
|
||||||
self._active_friend = value
|
self._active_friend = value
|
||||||
@ -243,18 +251,21 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer
|
if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer
|
||||||
try:
|
try:
|
||||||
ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())]
|
ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())]
|
||||||
ft.set_state_changed_handler(item.update)
|
ft.set_state_changed_handler(item.update_transfer_state)
|
||||||
ft.signal()
|
ft.signal()
|
||||||
except:
|
except:
|
||||||
print('Incoming not started transfer - no info found')
|
print('Incoming not started transfer - no info found')
|
||||||
elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline
|
elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline
|
||||||
self.create_inline_item(message.get_data())
|
self.create_inline_item(message.get_data())
|
||||||
else: # info message
|
elif message.get_type() < 5: # info message
|
||||||
data = message.get_data()
|
data = message.get_data()
|
||||||
self.create_message_item(data[0],
|
self.create_message_item(data[0],
|
||||||
data[2],
|
data[2],
|
||||||
'',
|
'',
|
||||||
data[3])
|
data[3])
|
||||||
|
else:
|
||||||
|
data = message.get_data()
|
||||||
|
self.create_gc_message_item(data[0], data[2], data[1], data[4], data[3])
|
||||||
self._messages.scrollToBottom()
|
self._messages.scrollToBottom()
|
||||||
self._load_history = True
|
self._load_history = True
|
||||||
if value in self._call:
|
if value in self._call:
|
||||||
@ -268,6 +279,10 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
|
|
||||||
self._screen.account_name.setText(friend.name)
|
self._screen.account_name.setText(friend.name)
|
||||||
self._screen.account_status.setText(friend.status_message)
|
self._screen.account_status.setText(friend.status_message)
|
||||||
|
self._screen.account_status.setToolTip(friend.get_full_status())
|
||||||
|
if friend.tox_id is None:
|
||||||
|
avatar_path = curr_directory() + '/images/group.png'
|
||||||
|
else:
|
||||||
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(friend.tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(friend.tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||||
if not os.path.isfile(avatar_path): # load default image
|
if not os.path.isfile(avatar_path): # load default image
|
||||||
avatar_path = curr_directory() + '/images/avatar.png'
|
avatar_path = curr_directory() + '/images/avatar.png'
|
||||||
@ -280,9 +295,10 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
log('Error in set active: ' + str(ex))
|
log('Error in set active: ' + str(ex))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def set_active_by_number(self, number):
|
def set_active_by_number_and_type(self, number, is_friend):
|
||||||
for i in range(len(self._contacts)):
|
for i in range(len(self._contacts)):
|
||||||
if self._contacts[i].number == number:
|
c = self._contacts[i]
|
||||||
|
if c.number == number and (type(c) is Friend == is_friend):
|
||||||
self._active_friend = i
|
self._active_friend = i
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -309,7 +325,7 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
friend.set_name(name)
|
friend.set_name(name)
|
||||||
name = str(name, 'utf-8')
|
name = str(name, 'utf-8')
|
||||||
if friend.name == name and tmp != name:
|
if friend.name == name and tmp != name:
|
||||||
message = QtGui.QApplication.translate("MainWindow", 'User {} is now known as {}', None, QtGui.QApplication.UnicodeUTF8)
|
message = QtWidgets.QApplication.translate("MainWindow", 'User {} is now known as {}')
|
||||||
message = message.format(tmp, name)
|
message = message.format(tmp, name)
|
||||||
friend.append_message(InfoMessage(message, time.time()))
|
friend.append_message(InfoMessage(message, time.time()))
|
||||||
friend.actions = True
|
friend.actions = True
|
||||||
@ -328,6 +344,7 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
|
|
||||||
def send_files(self, friend_number):
|
def send_files(self, friend_number):
|
||||||
friend = self.get_friend_by_number(friend_number)
|
friend = self.get_friend_by_number(friend_number)
|
||||||
|
friend.remove_invalid_unsent_files()
|
||||||
files = friend.get_unsent_files()
|
files = friend.get_unsent_files()
|
||||||
try:
|
try:
|
||||||
for fl in files:
|
for fl in files:
|
||||||
@ -344,7 +361,7 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
elif data[1] == friend_number and not data[2]:
|
elif data[1] == friend_number and not data[2]:
|
||||||
self.send_file(data[0], friend_number, True, key)
|
self.send_file(data[0], friend_number, True, key)
|
||||||
del self._paused_file_transfers[key]
|
del self._paused_file_transfers[key]
|
||||||
if friend_number == self.get_active_number():
|
if friend_number == self.get_active_number() and self.is_active_a_friend():
|
||||||
self.update()
|
self.update()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print('Exception in file sending: ' + str(ex))
|
print('Exception in file sending: ' + str(ex))
|
||||||
@ -386,7 +403,7 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
"""
|
"""
|
||||||
Display incoming typing notification
|
Display incoming typing notification
|
||||||
"""
|
"""
|
||||||
if friend_number == self.get_active_number():
|
if friend_number == self.get_active_number() and self.is_active_a_friend():
|
||||||
self._screen.typing.setVisible(typing)
|
self._screen.typing.setVisible(typing)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
@ -409,25 +426,25 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
for message in messages:
|
for message in messages:
|
||||||
self.split_and_send(friend_number, message.get_data()[-1], message.get_data()[0].encode('utf-8'))
|
self.split_and_send(friend_number, message.get_data()[-1], message.get_data()[0].encode('utf-8'))
|
||||||
friend.inc_receipts()
|
friend.inc_receipts()
|
||||||
except:
|
except Exception as ex:
|
||||||
pass
|
log('Sending pending messages failed with ' + str(ex))
|
||||||
|
|
||||||
def split_and_send(self, number, message_type, message):
|
def split_and_send(self, number, message_type, message):
|
||||||
"""
|
"""
|
||||||
Message splitting
|
Message splitting. Message length cannot be > TOX_MAX_MESSAGE_LENGTH
|
||||||
:param number: friend's number
|
:param number: friend's number
|
||||||
:param message_type: type of message
|
:param message_type: type of message
|
||||||
:param message: message text
|
:param message: message text
|
||||||
"""
|
"""
|
||||||
while len(message) > TOX_MAX_MESSAGE_LENGTH:
|
while len(message) > TOX_MAX_MESSAGE_LENGTH:
|
||||||
size = TOX_MAX_MESSAGE_LENGTH * 4 / 5
|
size = TOX_MAX_MESSAGE_LENGTH * 4 // 5
|
||||||
last_part = message[size:TOX_MAX_MESSAGE_LENGTH]
|
last_part = message[size:TOX_MAX_MESSAGE_LENGTH]
|
||||||
if ' ' in last_part:
|
if b' ' in last_part:
|
||||||
index = last_part.index(' ')
|
index = last_part.index(b' ')
|
||||||
elif ',' in last_part:
|
elif b',' in last_part:
|
||||||
index = last_part.index(',')
|
index = last_part.index(b',')
|
||||||
elif '.' in last_part:
|
elif b'.' in last_part:
|
||||||
index = last_part.index('.')
|
index = last_part.index(b'.')
|
||||||
else:
|
else:
|
||||||
index = TOX_MAX_MESSAGE_LENGTH - size - 1
|
index = TOX_MAX_MESSAGE_LENGTH - size - 1
|
||||||
index += size + 1
|
index += size + 1
|
||||||
@ -442,7 +459,7 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
:param message_type: message type - plain text or action message (/me)
|
:param message_type: message type - plain text or action message (/me)
|
||||||
:param message: text of message
|
:param message: text of message
|
||||||
"""
|
"""
|
||||||
if friend_num == self.get_active_number(): # add message to list
|
if friend_num == self.get_active_number()and self.is_active_a_friend(): # add message to list
|
||||||
t = time.time()
|
t = time.time()
|
||||||
self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type)
|
self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type)
|
||||||
self._messages.scrollToBottom()
|
self._messages.scrollToBottom()
|
||||||
@ -462,6 +479,9 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
:param text: message text
|
:param text: message text
|
||||||
:param friend_num: num of friend
|
:param friend_num: num of friend
|
||||||
"""
|
"""
|
||||||
|
if not self.is_active_a_friend():
|
||||||
|
self.send_gc_message(text)
|
||||||
|
return
|
||||||
if friend_num is None:
|
if friend_num is None:
|
||||||
friend_num = self.get_active_number()
|
friend_num = self.get_active_number()
|
||||||
if text.startswith('/plugin '):
|
if text.startswith('/plugin '):
|
||||||
@ -477,8 +497,8 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
friend.inc_receipts()
|
friend.inc_receipts()
|
||||||
if friend.status is not None:
|
if friend.status is not None:
|
||||||
self.split_and_send(friend.number, message_type, text.encode('utf-8'))
|
self.split_and_send(friend.number, message_type, text.encode('utf-8'))
|
||||||
if friend.number == self.get_active_number():
|
|
||||||
t = time.time()
|
t = time.time()
|
||||||
|
if friend.number == self.get_active_number() and self.is_active_a_friend():
|
||||||
self.create_message_item(text, t, MESSAGE_OWNER['NOT_SENT'], message_type)
|
self.create_message_item(text, t, MESSAGE_OWNER['NOT_SENT'], message_type)
|
||||||
self._screen.messageEdit.clear()
|
self._screen.messageEdit.clear()
|
||||||
self._messages.scrollToBottom()
|
self._messages.scrollToBottom()
|
||||||
@ -501,7 +521,7 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
s = Settings.get_instance()
|
s = Settings.get_instance()
|
||||||
if hasattr(self, '_history'):
|
if hasattr(self, '_history'):
|
||||||
if s['save_history']:
|
if s['save_history']:
|
||||||
for friend in self._contacts:
|
for friend in filter(lambda x: type(x) is Friend, self._contacts):
|
||||||
if not self._history.friend_exists_in_db(friend.tox_id):
|
if not self._history.friend_exists_in_db(friend.tox_id):
|
||||||
self._history.add_friend_to_db(friend.tox_id)
|
self._history.add_friend_to_db(friend.tox_id)
|
||||||
if not s['save_unsent_only']:
|
if not s['save_unsent_only']:
|
||||||
@ -562,19 +582,22 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer
|
if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer
|
||||||
try:
|
try:
|
||||||
ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())]
|
ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())]
|
||||||
ft.set_state_changed_handler(item.update)
|
ft.set_state_changed_handler(item.update_transfer_state)
|
||||||
ft.signal()
|
ft.signal()
|
||||||
except:
|
except:
|
||||||
print('Incoming not started transfer - no info found')
|
print('Incoming not started transfer - no info found')
|
||||||
elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline image
|
elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline image
|
||||||
self.create_inline_item(message.get_data(), False)
|
self.create_inline_item(message.get_data(), False)
|
||||||
else: # info message
|
elif message.get_type() < 5: # info message
|
||||||
data = message.get_data()
|
data = message.get_data()
|
||||||
self.create_message_item(data[0],
|
self.create_message_item(data[0],
|
||||||
data[2],
|
data[2],
|
||||||
'',
|
'',
|
||||||
data[3],
|
data[3],
|
||||||
False)
|
False)
|
||||||
|
else:
|
||||||
|
data = message.get_data()
|
||||||
|
self.create_gc_message_item(data[0], data[2], data[1], data[4], data[3], False)
|
||||||
self._load_history = True
|
self._load_history = True
|
||||||
|
|
||||||
def export_db(self, directory):
|
def export_db(self, directory):
|
||||||
@ -633,6 +656,16 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
return self._factory.message_item(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'],
|
return self._factory.message_item(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'],
|
||||||
message_type, append, pixmap)
|
message_type, append, pixmap)
|
||||||
|
|
||||||
|
def create_gc_message_item(self, text, time, owner, name, message_type, append=True):
|
||||||
|
pixmap = None
|
||||||
|
if self._show_avatars:
|
||||||
|
if owner == MESSAGE_OWNER['FRIEND']:
|
||||||
|
pixmap = self.get_curr_friend().get_pixmap()
|
||||||
|
else:
|
||||||
|
pixmap = self.get_pixmap()
|
||||||
|
return self._factory.message_item(text, time, name, True,
|
||||||
|
message_type - 5, append, pixmap)
|
||||||
|
|
||||||
def create_file_transfer_item(self, tm, append=True):
|
def create_file_transfer_item(self, tm, append=True):
|
||||||
data = list(tm.get_data())
|
data = list(tm.get_data())
|
||||||
data[3] = self.get_friend_by_number(data[4]).name if data[3] else self._name
|
data[3] = self.get_friend_by_number(data[4]).name if data[3] else self._name
|
||||||
@ -659,17 +692,15 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
"""
|
"""
|
||||||
friend = self._contacts[num]
|
friend = self._contacts[num]
|
||||||
name = friend.name
|
name = friend.name
|
||||||
dialog = QtGui.QApplication.translate('MainWindow',
|
dialog = QtWidgets.QApplication.translate('MainWindow',
|
||||||
"Enter new alias for friend {} or leave empty to use friend's name:",
|
"Enter new alias for friend {} or leave empty to use friend's name:")
|
||||||
None, QtGui.QApplication.UnicodeUTF8)
|
|
||||||
dialog = dialog.format(name)
|
dialog = dialog.format(name)
|
||||||
title = QtGui.QApplication.translate('MainWindow',
|
title = QtWidgets.QApplication.translate('MainWindow',
|
||||||
'Set alias',
|
'Set alias')
|
||||||
None, QtGui.QApplication.UnicodeUTF8)
|
text, ok = QtWidgets.QInputDialog.getText(None,
|
||||||
text, ok = QtGui.QInputDialog.getText(None,
|
|
||||||
title,
|
title,
|
||||||
dialog,
|
dialog,
|
||||||
QtGui.QLineEdit.Normal,
|
QtWidgets.QLineEdit.Normal,
|
||||||
name)
|
name)
|
||||||
if ok:
|
if ok:
|
||||||
settings = Settings.get_instance()
|
settings = Settings.get_instance()
|
||||||
@ -691,7 +722,7 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
settings.save()
|
settings.save()
|
||||||
if num == self.get_active_number():
|
if num == self.get_active_number() and self.is_active_a_friend():
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def friend_public_key(self, num):
|
def friend_public_key(self, num):
|
||||||
@ -794,9 +825,9 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
raise Exception('TOX DNS lookup failed')
|
raise Exception('TOX DNS lookup failed')
|
||||||
if len(tox_id) == TOX_PUBLIC_KEY_SIZE * 2: # public key
|
if len(tox_id) == TOX_PUBLIC_KEY_SIZE * 2: # public key
|
||||||
self.add_friend(tox_id)
|
self.add_friend(tox_id)
|
||||||
msgBox = QtGui.QMessageBox()
|
msgBox = QtWidgets.QMessageBox()
|
||||||
msgBox.setWindowTitle(QtGui.QApplication.translate("MainWindow", "Friend added", None, QtGui.QApplication.UnicodeUTF8))
|
msgBox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "Friend added"))
|
||||||
text = (QtGui.QApplication.translate("MainWindow", 'Friend added without sending friend request', None, QtGui.QApplication.UnicodeUTF8))
|
text = (QtWidgets.QApplication.translate("MainWindow", 'Friend added without sending friend request'))
|
||||||
msgBox.setText(text)
|
msgBox.setText(text)
|
||||||
msgBox.exec_()
|
msgBox.exec_()
|
||||||
else:
|
else:
|
||||||
@ -822,11 +853,11 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
:param message: message
|
:param message: message
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
text = QtGui.QApplication.translate('MainWindow', 'User {} wants to add you to contact list. Message:\n{}', None, QtGui.QApplication.UnicodeUTF8)
|
text = QtWidgets.QApplication.translate('MainWindow', 'User {} wants to add you to contact list. Message:\n{}')
|
||||||
info = text.format(tox_id, message)
|
info = text.format(tox_id, message)
|
||||||
fr_req = QtGui.QApplication.translate('MainWindow', 'Friend request', None, QtGui.QApplication.UnicodeUTF8)
|
fr_req = QtWidgets.QApplication.translate('MainWindow', 'Friend request')
|
||||||
reply = QtGui.QMessageBox.question(None, fr_req, info, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
|
reply = QtWidgets.QMessageBox.question(None, fr_req, info, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
|
||||||
if reply == QtGui.QMessageBox.Yes: # accepted
|
if reply == QtWidgets.QMessageBox.Yes: # accepted
|
||||||
self.add_friend(tox_id)
|
self.add_friend(tox_id)
|
||||||
data = self._tox.get_savedata()
|
data = self._tox.get_savedata()
|
||||||
ProfileHelper.get_instance().save_profile(data)
|
ProfileHelper.get_instance().save_profile(data)
|
||||||
@ -842,8 +873,11 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
Recreate tox instance
|
Recreate tox instance
|
||||||
:param restart: method which calls restart and returns new tox instance
|
:param restart: method which calls restart and returns new tox instance
|
||||||
"""
|
"""
|
||||||
for friend in self._contacts:
|
for contact in self._contacts:
|
||||||
self.friend_exit(friend.number)
|
if type(contact) is Friend:
|
||||||
|
self.friend_exit(contact.number)
|
||||||
|
else:
|
||||||
|
self.leave_gc(contact.number)
|
||||||
self._call.stop()
|
self._call.stop()
|
||||||
del self._call
|
del self._call
|
||||||
del self._tox
|
del self._tox
|
||||||
@ -855,12 +889,14 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
self.update_filtration()
|
self.update_filtration()
|
||||||
|
|
||||||
def reconnect(self):
|
def reconnect(self):
|
||||||
|
self._waiting_for_reconnection = False
|
||||||
if self.status is None or all(list(map(lambda x: x.status is None, self._contacts))) and len(self._contacts):
|
if self.status is None or all(list(map(lambda x: x.status is None, self._contacts))) and len(self._contacts):
|
||||||
|
self._waiting_for_reconnection = True
|
||||||
self.reset(self._screen.reset)
|
self.reset(self._screen.reset)
|
||||||
QtCore.QTimer.singleShot(45000, self.reconnect)
|
QtCore.QTimer.singleShot(50000, self.reconnect)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
for friend in self._contacts:
|
for friend in filter(lambda x: type(x) is Friend, self._contacts):
|
||||||
self.friend_exit(friend.number)
|
self.friend_exit(friend.number)
|
||||||
for i in range(len(self._contacts)):
|
for i in range(len(self._contacts)):
|
||||||
del self._contacts[0]
|
del self._contacts[0]
|
||||||
@ -933,10 +969,10 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
friend_number,
|
friend_number,
|
||||||
file_number)
|
file_number)
|
||||||
accepted = False
|
accepted = False
|
||||||
if friend_number == self.get_active_number():
|
if friend_number == self.get_active_number() and self.is_active_a_friend():
|
||||||
item = self.create_file_transfer_item(tm)
|
item = self.create_file_transfer_item(tm)
|
||||||
if accepted:
|
if accepted:
|
||||||
self._file_transfers[(friend_number, file_number)].set_state_changed_handler(item.update)
|
self._file_transfers[(friend_number, file_number)].set_state_changed_handler(item.update_transfer_state)
|
||||||
self._messages.scrollToBottom()
|
self._messages.scrollToBottom()
|
||||||
else:
|
else:
|
||||||
friend.actions = True
|
friend.actions = True
|
||||||
@ -964,14 +1000,15 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
else:
|
else:
|
||||||
if not already_cancelled:
|
if not already_cancelled:
|
||||||
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
|
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
|
||||||
if friend_number == self.get_active_number():
|
if friend_number == self.get_active_number() and self.is_active_a_friend():
|
||||||
tmp = self._messages.count() + i
|
tmp = self._messages.count() + i
|
||||||
if tmp >= 0:
|
if tmp >= 0:
|
||||||
self._messages.itemWidget(self._messages.item(tmp)).update(TOX_FILE_TRANSFER_STATE['CANCELLED'],
|
self._messages.itemWidget(
|
||||||
|
self._messages.item(tmp)).update_transfer_state(TOX_FILE_TRANSFER_STATE['CANCELLED'],
|
||||||
0, -1)
|
0, -1)
|
||||||
|
|
||||||
def cancel_not_started_transfer(self, time):
|
def cancel_not_started_transfer(self, cancel_time):
|
||||||
self.get_curr_friend().delete_one_unsent_file(time)
|
self.get_curr_friend().delete_one_unsent_file(cancel_time)
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def pause_transfer(self, friend_number, file_number, by_friend=False):
|
def pause_transfer(self, friend_number, file_number, by_friend=False):
|
||||||
@ -1025,7 +1062,7 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
self._file_transfers[(friend_number, file_number)] = rt
|
self._file_transfers[(friend_number, file_number)] = rt
|
||||||
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['RESUME'])
|
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['RESUME'])
|
||||||
if item is not None:
|
if item is not None:
|
||||||
rt.set_state_changed_handler(item.update)
|
rt.set_state_changed_handler(item.update_transfer_state)
|
||||||
self.get_friend_by_number(friend_number).update_transfer_data(file_number,
|
self.get_friend_by_number(friend_number).update_transfer_data(file_number,
|
||||||
TOX_FILE_TRANSFER_STATE['RUNNING'])
|
TOX_FILE_TRANSFER_STATE['RUNNING'])
|
||||||
|
|
||||||
@ -1062,9 +1099,10 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
file_name,
|
file_name,
|
||||||
friend.number,
|
friend.number,
|
||||||
st.get_file_number())
|
st.get_file_number())
|
||||||
item = self.create_file_transfer_item(tm)
|
|
||||||
friend.append_message(tm)
|
friend.append_message(tm)
|
||||||
st.set_state_changed_handler(item.update)
|
if friend_number == self.get_active_number():
|
||||||
|
item = self.create_file_transfer_item(tm)
|
||||||
|
st.set_state_changed_handler(item.update_transfer_state)
|
||||||
self._messages.scrollToBottom()
|
self._messages.scrollToBottom()
|
||||||
|
|
||||||
def send_file(self, path, number=None, is_resend=False, file_id=None):
|
def send_file(self, path, number=None, is_resend=False, file_id=None):
|
||||||
@ -1097,7 +1135,7 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
st.get_file_number())
|
st.get_file_number())
|
||||||
if friend_number == self.get_active_number():
|
if friend_number == self.get_active_number():
|
||||||
item = self.create_file_transfer_item(tm)
|
item = self.create_file_transfer_item(tm)
|
||||||
st.set_state_changed_handler(item.update)
|
st.set_state_changed_handler(item.update_transfer_state)
|
||||||
self._messages.scrollToBottom()
|
self._messages.scrollToBottom()
|
||||||
self._contacts[friend_number].append_message(tm)
|
self._contacts[friend_number].append_message(tm)
|
||||||
|
|
||||||
@ -1113,13 +1151,12 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
"""
|
"""
|
||||||
self._file_transfers[(friend_number, file_number)].send_chunk(position, size)
|
self._file_transfers[(friend_number, file_number)].send_chunk(position, size)
|
||||||
|
|
||||||
@QtCore.Slot(int, int)
|
|
||||||
def transfer_finished(self, friend_number, file_number):
|
def transfer_finished(self, friend_number, file_number):
|
||||||
transfer = self._file_transfers[(friend_number, file_number)]
|
transfer = self._file_transfers[(friend_number, file_number)]
|
||||||
t = type(transfer)
|
t = type(transfer)
|
||||||
if t is ReceiveAvatar:
|
if t is ReceiveAvatar:
|
||||||
self.get_friend_by_number(friend_number).load_avatar()
|
self.get_friend_by_number(friend_number).load_avatar()
|
||||||
if friend_number == self.get_active_number():
|
if friend_number == self.get_active_number() and self.is_active_a_friend():
|
||||||
self.set_active(None)
|
self.set_active(None)
|
||||||
elif t is ReceiveToBuffer or (t is SendFromBuffer and Settings.get_instance()['allow_inline']): # inline image
|
elif t is ReceiveToBuffer or (t is SendFromBuffer and Settings.get_instance()['allow_inline']): # inline image
|
||||||
print('inline')
|
print('inline')
|
||||||
@ -1127,10 +1164,10 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
i = self.get_friend_by_number(friend_number).update_transfer_data(file_number,
|
i = self.get_friend_by_number(friend_number).update_transfer_data(file_number,
|
||||||
TOX_FILE_TRANSFER_STATE['FINISHED'],
|
TOX_FILE_TRANSFER_STATE['FINISHED'],
|
||||||
inline)
|
inline)
|
||||||
if friend_number == self.get_active_number():
|
if friend_number == self.get_active_number() and self.is_active_a_friend():
|
||||||
count = self._messages.count()
|
count = self._messages.count()
|
||||||
if count + i + 1 >= 0:
|
if count + i + 1 >= 0:
|
||||||
elem = QtGui.QListWidgetItem()
|
elem = QtWidgets.QListWidgetItem()
|
||||||
item = InlineImageItem(transfer.get_data(), self._messages.width(), elem)
|
item = InlineImageItem(transfer.get_data(), self._messages.width(), elem)
|
||||||
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
|
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
|
||||||
self._messages.insertItem(count + i + 1, elem)
|
self._messages.insertItem(count + i + 1, elem)
|
||||||
@ -1169,7 +1206,7 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
ra.set_transfer_finished_handler(self.transfer_finished)
|
ra.set_transfer_finished_handler(self.transfer_finished)
|
||||||
else:
|
else:
|
||||||
self.get_friend_by_number(friend_number).load_avatar()
|
self.get_friend_by_number(friend_number).load_avatar()
|
||||||
if self.get_active_number() == friend_number:
|
if self.get_active_number() == friend_number and self.is_active_a_friend():
|
||||||
self.set_active(None)
|
self.set_active(None)
|
||||||
|
|
||||||
def reset_avatar(self):
|
def reset_avatar(self):
|
||||||
@ -1194,17 +1231,17 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
def call_click(self, audio=True, video=False):
|
def call_click(self, audio=True, video=False):
|
||||||
"""User clicked audio button in main window"""
|
"""User clicked audio button in main window"""
|
||||||
num = self.get_active_number()
|
num = self.get_active_number()
|
||||||
|
if not self.is_active_a_friend():
|
||||||
|
return
|
||||||
if num not in self._call and self.is_active_online(): # start call
|
if num not in self._call and self.is_active_online(): # start call
|
||||||
if not Settings.get_instance().audio['enabled']:
|
if not Settings.get_instance().audio['enabled']:
|
||||||
return
|
return
|
||||||
self._call(num, audio, video)
|
self._call(num, audio, video)
|
||||||
self._screen.active_call()
|
self._screen.active_call()
|
||||||
if video:
|
if video:
|
||||||
text = QtGui.QApplication.translate("incoming_call", "Outgoing video call", None,
|
text = QtWidgets.QApplication.translate("incoming_call", "Outgoing video call")
|
||||||
QtGui.QApplication.UnicodeUTF8)
|
|
||||||
else:
|
else:
|
||||||
text = QtGui.QApplication.translate("incoming_call", "Outgoing audio call", None,
|
text = QtWidgets.QApplication.translate("incoming_call", "Outgoing audio call")
|
||||||
QtGui.QApplication.UnicodeUTF8)
|
|
||||||
self.get_curr_friend().append_message(InfoMessage(text, time.time()))
|
self.get_curr_friend().append_message(InfoMessage(text, time.time()))
|
||||||
self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
|
self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
|
||||||
self._messages.scrollToBottom()
|
self._messages.scrollToBottom()
|
||||||
@ -1213,17 +1250,15 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
|
|
||||||
def incoming_call(self, audio, video, friend_number):
|
def incoming_call(self, audio, video, friend_number):
|
||||||
"""
|
"""
|
||||||
Incoming call from friend. Only audio is supported now
|
Incoming call from friend.
|
||||||
"""
|
"""
|
||||||
if not Settings.get_instance().audio['enabled']:
|
if not Settings.get_instance().audio['enabled']:
|
||||||
return
|
return
|
||||||
friend = self.get_friend_by_number(friend_number)
|
friend = self.get_friend_by_number(friend_number)
|
||||||
if video:
|
if video:
|
||||||
text = QtGui.QApplication.translate("incoming_call", "Incoming video call", None,
|
text = QtWidgets.QApplication.translate("incoming_call", "Incoming video call")
|
||||||
QtGui.QApplication.UnicodeUTF8)
|
|
||||||
else:
|
else:
|
||||||
text = QtGui.QApplication.translate("incoming_call", "Incoming audio call", None,
|
text = QtWidgets.QApplication.translate("incoming_call", "Incoming audio call")
|
||||||
QtGui.QApplication.UnicodeUTF8)
|
|
||||||
friend.append_message(InfoMessage(text, time.time()))
|
friend.append_message(InfoMessage(text, time.time()))
|
||||||
self._incoming_calls.add(friend_number)
|
self._incoming_calls.add(friend_number)
|
||||||
if friend_number == self.get_active_number():
|
if friend_number == self.get_active_number():
|
||||||
@ -1232,10 +1267,9 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
self._messages.scrollToBottom()
|
self._messages.scrollToBottom()
|
||||||
else:
|
else:
|
||||||
friend.actions = True
|
friend.actions = True
|
||||||
# TODO: dict of widgets
|
self._call_widgets[friend_number] = avwidgets.IncomingCallWidget(friend_number, text, friend.name)
|
||||||
self._call_widget = avwidgets.IncomingCallWidget(friend_number, text, friend.name)
|
self._call_widgets[friend_number].set_pixmap(friend.get_pixmap())
|
||||||
self._call_widget.set_pixmap(friend.get_pixmap())
|
self._call_widgets[friend_number].show()
|
||||||
self._call_widget.show()
|
|
||||||
|
|
||||||
def accept_call(self, friend_number, audio, video):
|
def accept_call(self, friend_number, audio, video):
|
||||||
"""
|
"""
|
||||||
@ -1245,8 +1279,7 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
self._screen.active_call()
|
self._screen.active_call()
|
||||||
if friend_number in self._incoming_calls:
|
if friend_number in self._incoming_calls:
|
||||||
self._incoming_calls.remove(friend_number)
|
self._incoming_calls.remove(friend_number)
|
||||||
if hasattr(self, '_call_widget'):
|
del self._call_widgets[friend_number]
|
||||||
del self._call_widget
|
|
||||||
|
|
||||||
def stop_call(self, friend_number, by_friend):
|
def stop_call(self, friend_number, by_friend):
|
||||||
"""
|
"""
|
||||||
@ -1254,20 +1287,153 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
"""
|
"""
|
||||||
if friend_number in self._incoming_calls:
|
if friend_number in self._incoming_calls:
|
||||||
self._incoming_calls.remove(friend_number)
|
self._incoming_calls.remove(friend_number)
|
||||||
text = QtGui.QApplication.translate("incoming_call", "Call declined", None, QtGui.QApplication.UnicodeUTF8)
|
text = QtWidgets.QApplication.translate("incoming_call", "Call declined")
|
||||||
else:
|
else:
|
||||||
text = QtGui.QApplication.translate("incoming_call", "Call finished", None, QtGui.QApplication.UnicodeUTF8)
|
text = QtWidgets.QApplication.translate("incoming_call", "Call finished")
|
||||||
self._screen.call_finished()
|
self._screen.call_finished()
|
||||||
|
is_video = self._call.is_video_call(friend_number)
|
||||||
self._call.finish_call(friend_number, by_friend) # finish or decline call
|
self._call.finish_call(friend_number, by_friend) # finish or decline call
|
||||||
if hasattr(self, '_call_widget'):
|
if hasattr(self, '_call_widget'):
|
||||||
self._call_widget.close()
|
self._call_widget[friend_number].close()
|
||||||
del self._call_widget
|
del self._call_widget[friend_number]
|
||||||
|
|
||||||
|
def destroy_window():
|
||||||
|
if is_video:
|
||||||
|
cv2.destroyWindow(str(friend_number))
|
||||||
|
|
||||||
|
threading.Timer(2.0, destroy_window).start()
|
||||||
friend = self.get_friend_by_number(friend_number)
|
friend = self.get_friend_by_number(friend_number)
|
||||||
friend.append_message(InfoMessage(text, time.time()))
|
friend.append_message(InfoMessage(text, time.time()))
|
||||||
if friend_number == self.get_active_number():
|
if friend_number == self.get_active_number():
|
||||||
self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
|
self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
|
||||||
self._messages.scrollToBottom()
|
self._messages.scrollToBottom()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# GC support
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def is_active_a_friend(self):
|
||||||
|
return type(self.get_curr_friend()) is Friend
|
||||||
|
|
||||||
|
def get_group_by_number(self, number):
|
||||||
|
groups = filter(lambda x: type(x) is GroupChat and x.number == number, self._contacts)
|
||||||
|
return list(groups)[0]
|
||||||
|
|
||||||
|
def add_gc(self, number):
|
||||||
|
if number == -1:
|
||||||
|
return
|
||||||
|
widget = self.create_friend_item()
|
||||||
|
gc = GroupChat('Group chat #' + str(number), '', widget, self._tox, number)
|
||||||
|
self._contacts.append(gc)
|
||||||
|
|
||||||
|
def create_group_chat(self):
|
||||||
|
number = self._tox.add_av_groupchat()
|
||||||
|
self.add_gc(number)
|
||||||
|
|
||||||
|
def leave_gc(self, num):
|
||||||
|
gc = self._contacts[num]
|
||||||
|
self._tox.del_groupchat(gc.number)
|
||||||
|
del self._contacts[num]
|
||||||
|
self._screen.friends_list.takeItem(num)
|
||||||
|
if num == self._active_friend: # active friend was deleted
|
||||||
|
if not len(self._contacts): # last friend was deleted
|
||||||
|
self.set_active(-1)
|
||||||
|
else:
|
||||||
|
self.set_active(0)
|
||||||
|
|
||||||
|
def group_invite(self, friend_number, gc_type, data):
|
||||||
|
text = QtWidgets.QApplication.translate('MainWindow', 'User {} invites you to group chat. Accept?')
|
||||||
|
title = QtWidgets.QApplication.translate('MainWindow', 'Group chat invite')
|
||||||
|
friend = self.get_friend_by_number(friend_number)
|
||||||
|
reply = QtWidgets.QMessageBox.question(None, title, text.format(friend.name), QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
|
||||||
|
if reply == QtWidgets.QMessageBox.Yes: # accepted
|
||||||
|
if gc_type == TOX_GROUPCHAT_TYPE['TEXT']:
|
||||||
|
number = self._tox.join_groupchat(friend_number, data)
|
||||||
|
else:
|
||||||
|
number = self._tox.join_av_groupchat(friend_number, data)
|
||||||
|
self.add_gc(number)
|
||||||
|
|
||||||
|
def new_gc_message(self, group_number, peer_number, message_type, message):
|
||||||
|
name = self._tox.group_peername(group_number, peer_number)
|
||||||
|
message_type += 5
|
||||||
|
if group_number == self.get_active_number() and not self.is_active_a_friend(): # add message to list
|
||||||
|
t = time.time()
|
||||||
|
self.create_gc_message_item(message, t, MESSAGE_OWNER['FRIEND'], name, message_type)
|
||||||
|
self._messages.scrollToBottom()
|
||||||
|
self.get_curr_friend().append_message(
|
||||||
|
GroupChatMessage(message, MESSAGE_OWNER['FRIEND'], t, message_type, name))
|
||||||
|
else:
|
||||||
|
gc = self.get_group_by_number(group_number)
|
||||||
|
gc.inc_messages()
|
||||||
|
gc.append_message(
|
||||||
|
GroupChatMessage(message, MESSAGE_OWNER['FRIEND'], time.time(), message_type, name))
|
||||||
|
if not gc.visibility:
|
||||||
|
self.update_filtration()
|
||||||
|
|
||||||
|
def new_gc_title(self, group_number, title):
|
||||||
|
gc = self.get_group_by_number(group_number)
|
||||||
|
gc.new_title(title)
|
||||||
|
if not self.is_active_a_friend() and self.get_active_number() == group_number:
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def update_gc(self, group_number):
|
||||||
|
count = self._tox.group_number_peers(group_number)
|
||||||
|
gc = self.get_group_by_number(group_number)
|
||||||
|
text = QtWidgets.QApplication.translate('MainWindow', '{} users in chat')
|
||||||
|
gc.status_message = text.format(str(count)).encode('utf-8')
|
||||||
|
if not self.is_active_a_friend() and self.get_active_number() == group_number:
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def send_gc_message(self, text):
|
||||||
|
group_number = self.get_active_number()
|
||||||
|
if text.startswith('/me '):
|
||||||
|
text = text[4:]
|
||||||
|
self._tox.group_action_send(group_number, text.encode('utf-8'))
|
||||||
|
else:
|
||||||
|
self._tox.group_message_send(group_number, text.encode('utf-8'))
|
||||||
|
self._screen.messageEdit.clear()
|
||||||
|
|
||||||
|
def set_title(self, num):
|
||||||
|
"""
|
||||||
|
Set new title for gc
|
||||||
|
"""
|
||||||
|
gc = self._contacts[num]
|
||||||
|
name = gc.name
|
||||||
|
dialog = QtWidgets.QApplication.translate('MainWindow',
|
||||||
|
"Enter new title for group {}:")
|
||||||
|
dialog = dialog.format(name)
|
||||||
|
title = QtWidgets.QApplication.translate('MainWindow',
|
||||||
|
'Set title')
|
||||||
|
text, ok = QtWidgets.QInputDialog.getText(None,
|
||||||
|
title,
|
||||||
|
dialog,
|
||||||
|
QtWidgets.QLineEdit.Normal,
|
||||||
|
name)
|
||||||
|
if ok:
|
||||||
|
text = text.encode('utf-8')
|
||||||
|
self._tox.group_set_title(gc.number, text)
|
||||||
|
self.new_gc_title(gc.number, text)
|
||||||
|
|
||||||
|
def get_group_chats(self):
|
||||||
|
chats = filter(lambda x: type(x) is GroupChat, self._contacts)
|
||||||
|
chats = map(lambda c: (c.name, c.number), chats)
|
||||||
|
return list(chats)
|
||||||
|
|
||||||
|
def invite_friend(self, friend_num, group_number):
|
||||||
|
friend = self._contacts[friend_num]
|
||||||
|
self._tox.invite_friend(friend.number, group_number)
|
||||||
|
|
||||||
|
def get_gc_peer_name(self, text):
|
||||||
|
gc = self.get_curr_friend()
|
||||||
|
if type(gc) is not GroupChat:
|
||||||
|
return '\t'
|
||||||
|
names = gc.get_names()
|
||||||
|
name = re.split("\s+", text)[-1]
|
||||||
|
suggested_names = list(filter(lambda x: x.startswith(name), names))
|
||||||
|
if not len(suggested_names):
|
||||||
|
return '\t'
|
||||||
|
return suggested_names[0][len(name):] + ': '
|
||||||
|
|
||||||
|
|
||||||
def tox_factory(data=None, settings=None):
|
def tox_factory(data=None, settings=None):
|
||||||
"""
|
"""
|
||||||
@ -1278,6 +1444,8 @@ def tox_factory(data=None, settings=None):
|
|||||||
if settings is None:
|
if settings is None:
|
||||||
settings = Settings.get_default_settings()
|
settings = Settings.get_default_settings()
|
||||||
tox_options = Tox.options_new()
|
tox_options = Tox.options_new()
|
||||||
|
# see <https://github.com/irungentoo/toxcore/blob/master/toxcore/tox.h> lines 393-401
|
||||||
|
tox_options.contents.ipv6_enabled = settings['ipv6_enabled']
|
||||||
tox_options.contents.udp_enabled = settings['udp_enabled']
|
tox_options.contents.udp_enabled = settings['udp_enabled']
|
||||||
tox_options.contents.proxy_type = settings['proxy_type']
|
tox_options.contents.proxy_type = settings['proxy_type']
|
||||||
tox_options.contents.proxy_host = bytes(settings['proxy_host'], 'UTF-8')
|
tox_options.contents.proxy_host = bytes(settings['proxy_host'], 'UTF-8')
|
||||||
|
22
toxygen/screen_sharing.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import numpy as np
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
|
||||||
|
class DesktopGrabber:
|
||||||
|
|
||||||
|
def __init__(self, x, y, width, height):
|
||||||
|
self._x = x
|
||||||
|
self._y = y
|
||||||
|
self._width = width
|
||||||
|
self._height = height
|
||||||
|
self._width -= width % 4
|
||||||
|
self._height -= height % 4
|
||||||
|
self._screen = QtWidgets.QApplication.primaryScreen()
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
pixmap = self._screen.grabWindow(0, self._x, self._y, self._width, self._height)
|
||||||
|
image = pixmap.toImage()
|
||||||
|
s = image.bits().asstring(self._width * self._height * 4)
|
||||||
|
arr = np.fromstring(s, dtype=np.uint8).reshape((self._height, self._width, 4))
|
||||||
|
|
||||||
|
return True, arr
|
@ -35,6 +35,7 @@ class Settings(dict, Singleton):
|
|||||||
smileys.SmileyLoader(self)
|
smileys.SmileyLoader(self)
|
||||||
self.locked = False
|
self.locked = False
|
||||||
self.closing = False
|
self.closing = False
|
||||||
|
self.unlockScreen = False
|
||||||
p = pyaudio.PyAudio()
|
p = pyaudio.PyAudio()
|
||||||
input_devices = output_devices = 0
|
input_devices = output_devices = 0
|
||||||
for i in range(p.get_device_count()):
|
for i in range(p.get_device_count()):
|
||||||
@ -46,21 +47,25 @@ class Settings(dict, Singleton):
|
|||||||
self.audio = {'input': p.get_default_input_device_info()['index'] if input_devices else -1,
|
self.audio = {'input': p.get_default_input_device_info()['index'] if input_devices else -1,
|
||||||
'output': p.get_default_output_device_info()['index'] if output_devices else -1,
|
'output': p.get_default_output_device_info()['index'] if output_devices else -1,
|
||||||
'enabled': input_devices and output_devices}
|
'enabled': input_devices and output_devices}
|
||||||
|
self.video = {'device': -1, 'width': 640, 'height': 480, 'x': 0, 'y': 0}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_auto_profile():
|
def get_auto_profile():
|
||||||
p = Settings.get_default_path() + 'toxygen.json'
|
p = Settings.get_global_settings_path()
|
||||||
if os.path.isfile(p):
|
if os.path.isfile(p):
|
||||||
with open(p) as fl:
|
with open(p) as fl:
|
||||||
data = fl.read()
|
data = fl.read()
|
||||||
auto = json.loads(data)
|
auto = json.loads(data)
|
||||||
if 'path' in auto and 'name' in auto:
|
if 'path' in auto and 'name' in auto:
|
||||||
return str(auto['path']), str(auto['name'])
|
path = str(auto['path'])
|
||||||
|
name = str(auto['name'])
|
||||||
|
if os.path.isfile(append_slash(path) + name + '.tox'):
|
||||||
|
return path, name
|
||||||
return '', ''
|
return '', ''
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_auto_profile(path, name):
|
def set_auto_profile(path, name):
|
||||||
p = Settings.get_default_path() + 'toxygen.json'
|
p = Settings.get_global_settings_path()
|
||||||
if os.path.isfile(p):
|
if os.path.isfile(p):
|
||||||
with open(p) as fl:
|
with open(p) as fl:
|
||||||
data = fl.read()
|
data = fl.read()
|
||||||
@ -74,7 +79,7 @@ class Settings(dict, Singleton):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def reset_auto_profile():
|
def reset_auto_profile():
|
||||||
p = Settings.get_default_path() + 'toxygen.json'
|
p = Settings.get_global_settings_path()
|
||||||
if os.path.isfile(p):
|
if os.path.isfile(p):
|
||||||
with open(p) as fl:
|
with open(p) as fl:
|
||||||
data = fl.read()
|
data = fl.read()
|
||||||
@ -89,15 +94,8 @@ class Settings(dict, Singleton):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_active_profile(path, name):
|
def is_active_profile(path, name):
|
||||||
path = path + name + '.tox'
|
path = path + name + '.lock'
|
||||||
settings = Settings.get_default_path() + 'toxygen.json'
|
return os.path.isfile(path)
|
||||||
if os.path.isfile(settings):
|
|
||||||
with open(settings) as fl:
|
|
||||||
data = fl.read()
|
|
||||||
data = json.loads(data)
|
|
||||||
if 'active_profile' in data:
|
|
||||||
return path in data['active_profile']
|
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_default_settings():
|
def get_default_settings():
|
||||||
@ -105,8 +103,8 @@ class Settings(dict, Singleton):
|
|||||||
Default profile settings
|
Default profile settings
|
||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
'theme': 'default',
|
'theme': 'dark',
|
||||||
'ipv6_enabled': True,
|
'ipv6_enabled': False,
|
||||||
'udp_enabled': True,
|
'udp_enabled': True,
|
||||||
'proxy_type': 0,
|
'proxy_type': 0,
|
||||||
'proxy_host': '127.0.0.1',
|
'proxy_host': '127.0.0.1',
|
||||||
@ -146,15 +144,25 @@ class Settings(dict, Singleton):
|
|||||||
'show_welcome_screen': True,
|
'show_welcome_screen': True,
|
||||||
'close_to_tray': False,
|
'close_to_tray': False,
|
||||||
'font': 'Times New Roman',
|
'font': 'Times New Roman',
|
||||||
'update': 1
|
'update': 1,
|
||||||
|
'group_notifications': True,
|
||||||
|
'download_nodes_list': False
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def supported_languages():
|
def supported_languages():
|
||||||
return {
|
return {
|
||||||
'English': 'en_EN',
|
'English': 'en_EN',
|
||||||
|
'French': 'fr_FR',
|
||||||
'Russian': 'ru_RU',
|
'Russian': 'ru_RU',
|
||||||
'French': 'fr_FR'
|
'Ukrainian': 'uk_UA'
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def built_in_themes():
|
||||||
|
return {
|
||||||
|
'dark': '/styles/dark_style.qss',
|
||||||
|
'default': '/styles/style.qss'
|
||||||
}
|
}
|
||||||
|
|
||||||
def upgrade(self):
|
def upgrade(self):
|
||||||
@ -176,37 +184,19 @@ class Settings(dict, Singleton):
|
|||||||
fl.write(text)
|
fl.write(text)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
path = Settings.get_default_path() + 'toxygen.json'
|
profile_path = ProfileHelper.get_path()
|
||||||
|
path = str(profile_path + str(self.name) + '.lock')
|
||||||
if os.path.isfile(path):
|
if os.path.isfile(path):
|
||||||
with open(path) as fl:
|
os.remove(path)
|
||||||
data = fl.read()
|
|
||||||
app_settings = json.loads(data)
|
|
||||||
try:
|
|
||||||
app_settings['active_profile'].remove(str(ProfileHelper.get_path() + self.name + '.tox'))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
data = json.dumps(app_settings)
|
|
||||||
with open(path, 'w') as fl:
|
|
||||||
fl.write(data)
|
|
||||||
|
|
||||||
def set_active_profile(self):
|
def set_active_profile(self):
|
||||||
"""
|
"""
|
||||||
Mark current profile as active
|
Mark current profile as active
|
||||||
"""
|
"""
|
||||||
path = Settings.get_default_path() + 'toxygen.json'
|
|
||||||
if os.path.isfile(path):
|
|
||||||
with open(path) as fl:
|
|
||||||
data = fl.read()
|
|
||||||
app_settings = json.loads(data)
|
|
||||||
else:
|
|
||||||
app_settings = {}
|
|
||||||
if 'active_profile' not in app_settings:
|
|
||||||
app_settings['active_profile'] = []
|
|
||||||
profile_path = ProfileHelper.get_path()
|
profile_path = ProfileHelper.get_path()
|
||||||
app_settings['active_profile'].append(str(profile_path + str(self.name) + '.tox'))
|
path = str(profile_path + str(self.name) + '.lock')
|
||||||
data = json.dumps(app_settings)
|
|
||||||
with open(path, 'w') as fl:
|
with open(path, 'w') as fl:
|
||||||
fl.write(data)
|
fl.write('active')
|
||||||
|
|
||||||
def export(self, path):
|
def export(self, path):
|
||||||
text = json.dumps(self)
|
text = json.dumps(self)
|
||||||
@ -216,6 +206,10 @@ class Settings(dict, Singleton):
|
|||||||
def update_path(self):
|
def update_path(self):
|
||||||
self.path = ProfileHelper.get_path() + self.name + '.json'
|
self.path = ProfileHelper.get_path() + self.name + '.json'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_global_settings_path():
|
||||||
|
return curr_directory() + '/toxygen.json'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_default_path():
|
def get_default_path():
|
||||||
if system() == 'Windows':
|
if system() == 'Windows':
|
||||||
|
@ -2,10 +2,7 @@ import util
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
try:
|
from PyQt5 import QtCore
|
||||||
from PySide import QtCore
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore
|
|
||||||
|
|
||||||
|
|
||||||
class SmileyLoader(util.Singleton):
|
class SmileyLoader(util.Singleton):
|
||||||
|
1324
toxygen/styles/dark_style.qss
Normal file
@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PySide import QtCore
|
from PyQt5 import QtCore
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from PyQt4 import QtCore
|
from PyQt4 import QtCore
|
||||||
|
|
||||||
|
@ -41,6 +41,9 @@
|
|||||||
<file>rc/radio_unchecked.png</file>
|
<file>rc/radio_unchecked.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="qdarkstyle">
|
<qresource prefix="qdarkstyle">
|
||||||
|
<file>dark_style.qss</file>
|
||||||
|
</qresource>
|
||||||
|
<qresource prefix="defaultstyle">
|
||||||
<file>style.qss</file>
|
<file>style.qss</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
from ctypes import *
|
||||||
from ctypes import c_char_p, Structure, c_bool, byref, c_int, c_size_t, POINTER, c_uint16, c_void_p, c_uint64
|
|
||||||
from ctypes import create_string_buffer, ArgumentError, CFUNCTYPE, c_uint32, sizeof, c_uint8
|
|
||||||
from toxcore_enums_and_consts import *
|
from toxcore_enums_and_consts import *
|
||||||
from toxav import ToxAV
|
from toxav import ToxAV
|
||||||
from libtox import LibToxCore
|
from libtox import LibToxCore
|
||||||
@ -92,6 +90,11 @@ class Tox:
|
|||||||
self.file_recv_chunk_cb = None
|
self.file_recv_chunk_cb = None
|
||||||
self.friend_lossy_packet_cb = None
|
self.friend_lossy_packet_cb = None
|
||||||
self.friend_lossless_packet_cb = None
|
self.friend_lossless_packet_cb = None
|
||||||
|
self.group_namelist_change_cb = None
|
||||||
|
self.group_title_cb = None
|
||||||
|
self.group_action_cb = None
|
||||||
|
self.group_message_cb = None
|
||||||
|
self.group_invite_cb = None
|
||||||
|
|
||||||
self.AV = ToxAV(self._tox_pointer)
|
self.AV = ToxAV(self._tox_pointer)
|
||||||
|
|
||||||
@ -1510,3 +1513,89 @@ class Tox:
|
|||||||
return result
|
return result
|
||||||
elif tox_err_get_port == TOX_ERR_GET_PORT['NOT_BOUND']:
|
elif tox_err_get_port == TOX_ERR_GET_PORT['NOT_BOUND']:
|
||||||
raise RuntimeError('The instance was not bound to any port.')
|
raise RuntimeError('The instance was not bound to any port.')
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Group chats
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def del_groupchat(self, groupnumber):
|
||||||
|
result = Tox.libtoxcore.tox_del_groupchat(self._tox_pointer, c_int(groupnumber), None)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def group_peername(self, groupnumber, peernumber):
|
||||||
|
buffer = create_string_buffer(TOX_MAX_NAME_LENGTH)
|
||||||
|
result = Tox.libtoxcore.tox_group_peername(self._tox_pointer, c_int(groupnumber), c_int(peernumber),
|
||||||
|
buffer, None)
|
||||||
|
return str(buffer[:result], 'utf-8')
|
||||||
|
|
||||||
|
def invite_friend(self, friendnumber, groupnumber):
|
||||||
|
result = Tox.libtoxcore.tox_invite_friend(self._tox_pointer, c_int(friendnumber),
|
||||||
|
c_int(groupnumber), None)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def join_groupchat(self, friendnumber, data):
|
||||||
|
result = Tox.libtoxcore.tox_join_groupchat(self._tox_pointer,
|
||||||
|
c_int(friendnumber), c_char_p(data), c_uint16(len(data)), None)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def group_message_send(self, groupnumber, message):
|
||||||
|
result = Tox.libtoxcore.tox_group_message_send(self._tox_pointer, c_int(groupnumber), c_char_p(message),
|
||||||
|
c_uint16(len(message)), None)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def group_action_send(self, groupnumber, action):
|
||||||
|
result = Tox.libtoxcore.tox_group_action_send(self._tox_pointer,
|
||||||
|
c_int(groupnumber), c_char_p(action),
|
||||||
|
c_uint16(len(action)), None)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def group_set_title(self, groupnumber, title):
|
||||||
|
result = Tox.libtoxcore.tox_group_set_title(self._tox_pointer, c_int(groupnumber),
|
||||||
|
c_char_p(title), c_uint8(len(title)), None)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def group_get_title(self, groupnumber):
|
||||||
|
buffer = create_string_buffer(TOX_MAX_NAME_LENGTH)
|
||||||
|
result = Tox.libtoxcore.tox_group_get_title(self._tox_pointer,
|
||||||
|
c_int(groupnumber), buffer,
|
||||||
|
c_uint32(TOX_MAX_NAME_LENGTH), None)
|
||||||
|
return str(buffer[:result], 'utf-8')
|
||||||
|
|
||||||
|
def group_number_peers(self, groupnumber):
|
||||||
|
result = Tox.libtoxcore.tox_group_number_peers(self._tox_pointer, c_int(groupnumber), None)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def add_av_groupchat(self):
|
||||||
|
result = self.AV.libtoxav.toxav_add_av_groupchat(self._tox_pointer, None, None)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def join_av_groupchat(self, friendnumber, data):
|
||||||
|
result = self.AV.libtoxav.toxav_join_av_groupchat(self._tox_pointer, c_int32(friendnumber),
|
||||||
|
c_char_p(data), c_uint16(len(data)),
|
||||||
|
None, None)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def callback_group_invite(self, callback, user_data=None):
|
||||||
|
c_callback = CFUNCTYPE(None, c_void_p, c_int32, c_uint8, POINTER(c_uint8), c_uint16, c_void_p)
|
||||||
|
self.group_invite_cb = c_callback(callback)
|
||||||
|
Tox.libtoxcore.tox_callback_group_invite(self._tox_pointer, self.group_invite_cb, user_data)
|
||||||
|
|
||||||
|
def callback_group_message(self, callback, user_data=None):
|
||||||
|
c_callback = CFUNCTYPE(None, c_void_p, c_int, c_int, c_char_p, c_uint16, c_void_p)
|
||||||
|
self.group_message_cb = c_callback(callback)
|
||||||
|
Tox.libtoxcore.tox_callback_group_message(self._tox_pointer, self.group_message_cb, user_data)
|
||||||
|
|
||||||
|
def callback_group_action(self, callback, user_data=None):
|
||||||
|
c_callback = CFUNCTYPE(None, c_void_p, c_int, c_int, c_char_p, c_uint16, c_void_p)
|
||||||
|
self.group_action_cb = c_callback(callback)
|
||||||
|
Tox.libtoxcore.tox_callback_group_action(self._tox_pointer, self.group_action_cb, user_data)
|
||||||
|
|
||||||
|
def callback_group_title(self, callback, user_data=None):
|
||||||
|
c_callback = CFUNCTYPE(None, c_void_p, c_int, c_int, c_char_p, c_uint8, c_void_p)
|
||||||
|
self.group_title_cb = c_callback(callback)
|
||||||
|
Tox.libtoxcore.tox_callback_group_title(self._tox_pointer, self.group_title_cb, user_data)
|
||||||
|
|
||||||
|
def callback_group_namelist_change(self, callback, user_data=None):
|
||||||
|
c_callback = CFUNCTYPE(None, c_void_p, c_int, c_int, c_uint8, c_void_p)
|
||||||
|
self.group_namelist_change_cb = c_callback(callback)
|
||||||
|
Tox.libtoxcore.tox_callback_group_namelist_change(self._tox_pointer, self.group_namelist_change_cb, user_data)
|
||||||
|
@ -2,10 +2,7 @@ import json
|
|||||||
import urllib.request
|
import urllib.request
|
||||||
from util import log
|
from util import log
|
||||||
import settings
|
import settings
|
||||||
try:
|
from PyQt5 import QtNetwork, QtCore
|
||||||
from PySide import QtNetwork, QtCore
|
|
||||||
except:
|
|
||||||
from PyQt4 import QtNetwork, QtCore
|
|
||||||
|
|
||||||
|
|
||||||
def tox_dns(email):
|
def tox_dns(email):
|
||||||
@ -33,7 +30,8 @@ def tox_dns(email):
|
|||||||
netman.setProxy(proxy)
|
netman.setProxy(proxy)
|
||||||
for url in urls:
|
for url in urls:
|
||||||
try:
|
try:
|
||||||
request = QtNetwork.QNetworkRequest(url)
|
request = QtNetwork.QNetworkRequest()
|
||||||
|
request.setUrl(QtCore.QUrl(url))
|
||||||
request.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, "application/json")
|
request.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, "application/json")
|
||||||
reply = netman.post(request, bytes(json.dumps(data), 'utf-8'))
|
reply = netman.post(request, bytes(json.dumps(data), 'utf-8'))
|
||||||
|
|
||||||
|
@ -188,6 +188,17 @@ TOX_ERR_GET_PORT = {
|
|||||||
'NOT_BOUND': 1,
|
'NOT_BOUND': 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TOX_CHAT_CHANGE = {
|
||||||
|
'PEER_ADD': 0,
|
||||||
|
'PEER_DEL': 1,
|
||||||
|
'PEER_NAME': 2
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_GROUPCHAT_TYPE = {
|
||||||
|
'TEXT': 0,
|
||||||
|
'AV': 1
|
||||||
|
}
|
||||||
|
|
||||||
TOX_PUBLIC_KEY_SIZE = 32
|
TOX_PUBLIC_KEY_SIZE = 32
|
||||||
|
|
||||||
TOX_ADDRESS_SIZE = TOX_PUBLIC_KEY_SIZE + 6
|
TOX_ADDRESS_SIZE = TOX_PUBLIC_KEY_SIZE + 6
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
SOURCES = main.py profile.py menu.py list_items.py loginscreen.py mainscreen.py plugins/plugin_super_class.py callbacks.py widgets.py avwidgets.py mainscreen_widgets.py passwordscreen.py
|
SOURCES = main.py profile.py menu.py list_items.py loginscreen.py mainscreen.py plugins/plugin_super_class.py callbacks.py widgets.py avwidgets.py mainscreen_widgets.py passwordscreen.py
|
||||||
TRANSLATIONS = translations/en_GB.ts translations/ru_RU.ts translations/fr_FR.ts
|
TRANSLATIONS = translations/en_GB.ts translations/ru_RU.ts translations/fr_FR.ts translations/uk_UA.ts
|
||||||
|
BIN
toxygen/translations/uk_UA.qm
Normal file
1297
toxygen/translations/uk_UA.ts
Normal file
@ -3,10 +3,7 @@ import os
|
|||||||
import settings
|
import settings
|
||||||
import platform
|
import platform
|
||||||
import urllib
|
import urllib
|
||||||
try:
|
from PyQt5 import QtNetwork, QtCore
|
||||||
from PySide import QtNetwork, QtCore
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtNetwork, QtCore
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
@ -60,6 +57,9 @@ def get_url(version):
|
|||||||
|
|
||||||
def get_params(url, version):
|
def get_params(url, version):
|
||||||
if is_from_sources():
|
if is_from_sources():
|
||||||
|
if platform.system() == 'Windows':
|
||||||
|
return ['python', 'toxygen_updater.py', url, version]
|
||||||
|
else:
|
||||||
return ['python3', 'toxygen_updater.py', url, version]
|
return ['python3', 'toxygen_updater.py', url, version]
|
||||||
elif platform.system() == 'Windows':
|
elif platform.system() == 'Windows':
|
||||||
return [util.curr_directory() + '/toxygen_updater.exe', url, version]
|
return [util.curr_directory() + '/toxygen_updater.exe', url, version]
|
||||||
@ -90,7 +90,8 @@ def send_request(version):
|
|||||||
netman.setProxy(proxy)
|
netman.setProxy(proxy)
|
||||||
url = test_url(version)
|
url = test_url(version)
|
||||||
try:
|
try:
|
||||||
request = QtNetwork.QNetworkRequest(url)
|
request = QtNetwork.QNetworkRequest()
|
||||||
|
request.setUrl(QtCore.QUrl(url))
|
||||||
reply = netman.get(request)
|
reply = netman.get(request)
|
||||||
while not reply.isFinished():
|
while not reply.isFinished():
|
||||||
QtCore.QThread.msleep(1)
|
QtCore.QThread.msleep(1)
|
||||||
|
@ -4,14 +4,32 @@ import shutil
|
|||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
|
|
||||||
program_version = '0.2.7'
|
|
||||||
|
program_version = '0.4.3'
|
||||||
|
|
||||||
|
|
||||||
|
def cached(func):
|
||||||
|
saved_result = None
|
||||||
|
|
||||||
|
def wrapped_func():
|
||||||
|
nonlocal saved_result
|
||||||
|
if saved_result is None:
|
||||||
|
saved_result = func()
|
||||||
|
|
||||||
|
return saved_result
|
||||||
|
|
||||||
|
return wrapped_func
|
||||||
|
|
||||||
|
|
||||||
def log(data):
|
def log(data):
|
||||||
|
try:
|
||||||
with open(curr_directory() + '/logs.log', 'a') as fl:
|
with open(curr_directory() + '/logs.log', 'a') as fl:
|
||||||
fl.write(str(data) + '\n')
|
fl.write(str(data) + '\n')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@cached
|
||||||
def curr_directory():
|
def curr_directory():
|
||||||
return os.path.dirname(os.path.realpath(__file__))
|
return os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
@ -46,9 +64,8 @@ def convert_time(t):
|
|||||||
return '%02d:%02d' % (h, m)
|
return '%02d:%02d' % (h, m)
|
||||||
|
|
||||||
|
|
||||||
|
@cached
|
||||||
def time_offset():
|
def time_offset():
|
||||||
if hasattr(time_offset, 'offset'):
|
|
||||||
return time_offset.offset
|
|
||||||
hours = int(time.strftime('%H'))
|
hours = int(time.strftime('%H'))
|
||||||
minutes = int(time.strftime('%M'))
|
minutes = int(time.strftime('%M'))
|
||||||
sec = int(time.time()) - time.timezone
|
sec = int(time.time()) - time.timezone
|
||||||
@ -56,7 +73,6 @@ def time_offset():
|
|||||||
h, m = divmod(m, 60)
|
h, m = divmod(m, 60)
|
||||||
d, h = divmod(h, 24)
|
d, h = divmod(h, 24)
|
||||||
result = hours * 60 + minutes - h * 60 - m
|
result = hours * 60 + minutes - h * 60 - m
|
||||||
time_offset.offset = result
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@ -66,6 +82,7 @@ def append_slash(s):
|
|||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
@cached
|
||||||
def is_64_bit():
|
def is_64_bit():
|
||||||
return sys.maxsize > 2 ** 32
|
return sys.maxsize > 2 ** 32
|
||||||
|
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
try:
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
from PySide import QtCore, QtGui
|
|
||||||
except ImportError:
|
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
|
|
||||||
|
|
||||||
class DataLabel(QtGui.QLabel):
|
class DataLabel(QtWidgets.QLabel):
|
||||||
"""
|
"""
|
||||||
Label with elided text
|
Label with elided text
|
||||||
"""
|
"""
|
||||||
@ -15,14 +12,14 @@ class DataLabel(QtGui.QLabel):
|
|||||||
super().setText(text)
|
super().setText(text)
|
||||||
|
|
||||||
|
|
||||||
class ComboBox(QtGui.QComboBox):
|
class ComboBox(QtWidgets.QComboBox):
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
self.view().setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Expanding)
|
self.view().setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
|
||||||
|
|
||||||
|
|
||||||
class CenteredWidget(QtGui.QWidget):
|
class CenteredWidget(QtWidgets.QWidget):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(CenteredWidget, self).__init__()
|
super(CenteredWidget, self).__init__()
|
||||||
@ -30,12 +27,12 @@ class CenteredWidget(QtGui.QWidget):
|
|||||||
|
|
||||||
def center(self):
|
def center(self):
|
||||||
qr = self.frameGeometry()
|
qr = self.frameGeometry()
|
||||||
cp = QtGui.QDesktopWidget().availableGeometry().center()
|
cp = QtWidgets.QDesktopWidget().availableGeometry().center()
|
||||||
qr.moveCenter(cp)
|
qr.moveCenter(cp)
|
||||||
self.move(qr.topLeft())
|
self.move(qr.topLeft())
|
||||||
|
|
||||||
|
|
||||||
class LineEdit(QtGui.QLineEdit):
|
class LineEdit(QtWidgets.QLineEdit):
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super(LineEdit, self).__init__(parent)
|
super(LineEdit, self).__init__(parent)
|
||||||
@ -46,25 +43,27 @@ class LineEdit(QtGui.QLineEdit):
|
|||||||
del menu
|
del menu
|
||||||
|
|
||||||
|
|
||||||
class QRightClickButton(QtGui.QPushButton):
|
class QRightClickButton(QtWidgets.QPushButton):
|
||||||
"""
|
"""
|
||||||
Button with right click support
|
Button with right click support
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
rightClicked = QtCore.pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super(QRightClickButton, self).__init__(parent)
|
super(QRightClickButton, self).__init__(parent)
|
||||||
|
|
||||||
def mousePressEvent(self, event):
|
def mousePressEvent(self, event):
|
||||||
if event.button() == QtCore.Qt.RightButton:
|
if event.button() == QtCore.Qt.RightButton:
|
||||||
self.emit(QtCore.SIGNAL("rightClicked()"))
|
self.rightClicked.emit()
|
||||||
else:
|
else:
|
||||||
super(QRightClickButton, self).mousePressEvent(event)
|
super(QRightClickButton, self).mousePressEvent(event)
|
||||||
|
|
||||||
|
|
||||||
class RubberBand(QtGui.QRubberBand):
|
class RubberBand(QtWidgets.QRubberBand):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(RubberBand, self).__init__(QtGui.QRubberBand.Rectangle, None)
|
super(RubberBand, self).__init__(QtWidgets.QRubberBand.Rectangle, None)
|
||||||
self.setPalette(QtGui.QPalette(QtCore.Qt.transparent))
|
self.setPalette(QtGui.QPalette(QtCore.Qt.transparent))
|
||||||
self.pen = QtGui.QPen(QtCore.Qt.blue, 4)
|
self.pen = QtGui.QPen(QtCore.Qt.blue, 4)
|
||||||
self.pen.setStyle(QtCore.Qt.SolidLine)
|
self.pen.setStyle(QtCore.Qt.SolidLine)
|
||||||
@ -78,6 +77,42 @@ class RubberBand(QtGui.QRubberBand):
|
|||||||
self.painter.end()
|
self.painter.end()
|
||||||
|
|
||||||
|
|
||||||
|
class RubberBandWindow(QtWidgets.QWidget):
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__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()
|
||||||
|
self.rubberband.setWindowFlags(self.rubberband.windowFlags() | QtCore.Qt.FramelessWindowHint)
|
||||||
|
self.rubberband.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||||
|
|
||||||
|
def mousePressEvent(self, event):
|
||||||
|
self.origin = event.pos()
|
||||||
|
self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize()))
|
||||||
|
self.rubberband.show()
|
||||||
|
QtWidgets.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 keyPressEvent(self, event):
|
||||||
|
if event.key() == QtCore.Qt.Key_Escape:
|
||||||
|
self.rubberband.setHidden(True)
|
||||||
|
self.close()
|
||||||
|
else:
|
||||||
|
super().keyPressEvent(event)
|
||||||
|
|
||||||
|
|
||||||
def create_menu(menu):
|
def create_menu(menu):
|
||||||
"""
|
"""
|
||||||
:return translated menu
|
:return translated menu
|
||||||
@ -86,29 +121,21 @@ def create_menu(menu):
|
|||||||
text = action.text()
|
text = action.text()
|
||||||
if 'Link Location' in text:
|
if 'Link Location' in text:
|
||||||
text = text.replace('Copy &Link Location',
|
text = text.replace('Copy &Link Location',
|
||||||
QtGui.QApplication.translate("MainWindow", "Copy link location", None,
|
QtWidgets.QApplication.translate("MainWindow", "Copy link location"))
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
elif '&Copy' in text:
|
elif '&Copy' in text:
|
||||||
text = text.replace('&Copy', QtGui.QApplication.translate("MainWindow", "Copy", None,
|
text = text.replace('&Copy', QtWidgets.QApplication.translate("MainWindow", "Copy"))
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
elif 'All' in text:
|
elif 'All' in text:
|
||||||
text = text.replace('Select All', QtGui.QApplication.translate("MainWindow", "Select all", None,
|
text = text.replace('Select All', QtWidgets.QApplication.translate("MainWindow", "Select all"))
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
elif 'Delete' in text:
|
elif 'Delete' in text:
|
||||||
text = text.replace('Delete', QtGui.QApplication.translate("MainWindow", "Delete", None,
|
text = text.replace('Delete', QtWidgets.QApplication.translate("MainWindow", "Delete"))
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
elif '&Paste' in text:
|
elif '&Paste' in text:
|
||||||
text = text.replace('&Paste', QtGui.QApplication.translate("MainWindow", "Paste", None,
|
text = text.replace('&Paste', QtWidgets.QApplication.translate("MainWindow", "Paste"))
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
elif 'Cu&t' in text:
|
elif 'Cu&t' in text:
|
||||||
text = text.replace('Cu&t', QtGui.QApplication.translate("MainWindow", "Cut", None,
|
text = text.replace('Cu&t', QtWidgets.QApplication.translate("MainWindow", "Cut"))
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
elif '&Undo' in text:
|
elif '&Undo' in text:
|
||||||
text = text.replace('&Undo', QtGui.QApplication.translate("MainWindow", "Undo", None,
|
text = text.replace('&Undo', QtWidgets.QApplication.translate("MainWindow", "Undo"))
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
elif '&Redo' in text:
|
elif '&Redo' in text:
|
||||||
text = text.replace('&Redo', QtGui.QApplication.translate("MainWindow", "Redo", None,
|
text = text.replace('&Redo', QtWidgets.QApplication.translate("MainWindow", "Redo"))
|
||||||
QtGui.QApplication.UnicodeUTF8))
|
|
||||||
else:
|
else:
|
||||||
menu.removeAction(action)
|
menu.removeAction(action)
|
||||||
continue
|
continue
|
||||||
@ -124,12 +151,12 @@ class MultilineEdit(CenteredWidget):
|
|||||||
self.setMinimumSize(QtCore.QSize(350, 200))
|
self.setMinimumSize(QtCore.QSize(350, 200))
|
||||||
self.setMaximumSize(QtCore.QSize(350, 200))
|
self.setMaximumSize(QtCore.QSize(350, 200))
|
||||||
self.setWindowTitle(title)
|
self.setWindowTitle(title)
|
||||||
self.edit = QtGui.QTextEdit(self)
|
self.edit = QtWidgets.QTextEdit(self)
|
||||||
self.edit.setGeometry(QtCore.QRect(0, 0, 350, 150))
|
self.edit.setGeometry(QtCore.QRect(0, 0, 350, 150))
|
||||||
self.edit.setText(text)
|
self.edit.setText(text)
|
||||||
self.button = QtGui.QPushButton(self)
|
self.button = QtWidgets.QPushButton(self)
|
||||||
self.button.setGeometry(QtCore.QRect(0, 150, 350, 50))
|
self.button.setGeometry(QtCore.QRect(0, 150, 350, 50))
|
||||||
self.button.setText(QtGui.QApplication.translate("MainWindow", "Save", None, QtGui.QApplication.UnicodeUTF8))
|
self.button.setText(QtWidgets.QApplication.translate("MainWindow", "Save"))
|
||||||
self.button.clicked.connect(self.button_click)
|
self.button.clicked.connect(self.button_click)
|
||||||
self.center()
|
self.center()
|
||||||
self.save = save
|
self.save = save
|
||||||
@ -137,4 +164,3 @@ class MultilineEdit(CenteredWidget):
|
|||||||
def button_click(self):
|
def button_click(self):
|
||||||
self.save(self.edit.toPlainText())
|
self.save(self.edit.toPlainText())
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|