100 Commits

Author SHA1 Message Date
f76a1c0fbe manifest.in updated 2018-01-27 19:53:07 +03:00
bb2a857ecf use opencv-python module on linux 2018-01-26 18:43:19 +03:00
62c5df751d fix and translations update 2018-01-24 23:42:03 +03:00
55a127a820 ability to use nodes from tox.chat added 2018-01-24 22:45:58 +03:00
32055050ee hide tray icon on exit 2017-11-05 12:13:28 +03:00
a6633f1e77 minor video changes 2017-10-24 21:43:12 +03:00
23b55522ba file transfer cancelling fix 2017-10-22 16:36:46 +03:00
5a5b0e9069 desktop sharing bug fix 2017-10-08 00:39:08 +03:00
24c8b18f7e minor fixes 2017-08-30 22:20:31 +03:00
3ddb7470fc v0.4.0 2017-07-21 18:39:10 +03:00
80b0ea4f0e history for gc fixes 2017-07-20 23:51:40 +03:00
6efb1790bb notifications fix 2017-07-19 19:39:56 +03:00
d5d1e616ba translations update and bug fix 2017-07-19 00:14:41 +03:00
1ea919bdc2 tab && bug fix 2017-07-18 23:36:40 +03:00
65167de1fe group notifications and bug fixes 2017-07-18 21:36:14 +03:00
db519e2608 bug fix and version++ 2017-07-17 22:27:52 +03:00
19893c5c28 chat menu 2017-07-17 22:15:29 +03:00
8e6d37e23c minimal working functionality 2017-07-17 21:53:35 +03:00
aae71d081f base backend for gc 2017-07-17 01:11:09 +03:00
9c129e925b base gc class, callbacks part1 2017-07-16 22:51:20 +03:00
87392ea95a wrapper for old gc (gen) 2017-07-16 20:02:33 +03:00
1bbd9a629c video calls fix 2017-07-15 23:11:49 +03:00
f4d806f5fc readme update 2017-07-15 12:28:19 +03:00
4854b6151d desktop sharing - area selection fix 2017-07-14 21:37:50 +03:00
c755b4a52a light theme fix 2017-07-14 21:21:53 +03:00
7505b06ddf translations update 2017-07-13 21:19:13 +03:00
ace663804e screen sharing - area selection 2017-07-13 21:02:42 +03:00
2ff41313f8 default profile bug fix. install.md fix 2017-07-12 21:36:19 +03:00
1e1772e306 screen sharing initial commit 2017-07-12 21:18:21 +03:00
300b28bdfa set alias fix 2017-07-10 18:23:20 +03:00
1f4e81af35 export fix. version++ 2017-07-09 17:37:05 +03:00
335d646c42 avatars fix 2017-07-09 17:22:37 +03:00
b6f5123495 setup.py fix for packages 2017-07-09 13:17:51 +03:00
fbe0b1f819 installation updates 2017-07-07 20:30:04 +03:00
000a4c7920 travis.yml update 2017-07-06 22:16:12 +03:00
262714d3ee fix and cleanup 2017-07-06 21:39:15 +03:00
d06982b38a updates for pip3 2017-07-06 21:32:35 +03:00
c21e39b158 bug fixes for updates 2017-07-03 21:36:11 +03:00
8d0426f775 .qm for french translation 2017-07-03 13:01:07 +03:00
2c031fce3f Merge pull request #48 from limalayla/develop
French translation up to v0.3.0
2017-07-03 02:49:55 -07:00
6e1b8a9f17 French translation up to v0.3.0 2017-07-03 00:51:43 +02:00
4b85401adf os x removed, minor updates 2017-06-29 22:14:52 +03:00
5932d8cb84 installation docs update 2017-06-24 15:44:55 +03:00
adf6cefd1f pyqt5 fixes - menu and smileys 2017-06-20 22:55:48 +03:00
142255ccc8 translations update. docs partial update 2017-06-20 22:34:24 +03:00
1b6b8e043a back to menu 2017-06-20 21:31:23 +03:00
1b4c211c1d version info updated 2017-06-20 20:10:17 +03:00
43c71ec1a5 Merge branch 'video' into develop
Conflicts:
	.travis.yml
	toxygen/calls.py
	toxygen/loginscreen.py
	toxygen/util.py
2017-06-20 19:58:59 +03:00
4d4fd21fe9 removed audio/video messages functionality. tox dns fixed 2017-06-20 19:41:08 +03:00
6cbacef95b travis.yml fix 2017-06-18 17:48:32 +03:00
7a817eb82a icons fixes 2017-06-18 13:05:26 +03:00
49fc253c19 video call icons 2017-06-18 12:43:11 +03:00
df5a1a901a video settings 2017-06-18 00:50:42 +03:00
54a2da4670 video setting - selector 2017-06-18 00:30:08 +03:00
361f1f0e29 call timeout 2017-06-16 18:47:00 +03:00
9031a4a3e3 incoming call widget update 2017-06-15 21:34:43 +03:00
0a378c1682 ui fixes for video 2017-06-15 00:25:16 +03:00
8bc4613407 device selection in settings 2017-06-13 22:42:05 +03:00
ec6c04a7df video settings to main menu 2017-06-13 22:37:01 +03:00
769119c795 video settings (untested) 2017-06-13 22:32:32 +03:00
d1e90c6aef fixed bug with video sending 2017-06-13 21:50:00 +03:00
6d705deb55 calls.py rewriting 2017-06-13 21:40:54 +03:00
464fba23c5 incoming video yuv => bgr fix 2017-06-13 21:14:05 +03:00
c60808a7da cleanup and few todo's 2017-06-13 00:36:45 +03:00
a2273e8c27 video sending and playing, temporary hardcoded size 2017-06-13 00:26:21 +03:00
a20a00130d email notifications disabled 2017-06-11 22:58:11 +03:00
d0e2f61d03 merge with pyqt5 branch and video sending 2017-06-11 15:35:52 +03:00
8ea1a77186 version++ 2017-05-06 19:35:24 +03:00
bf1bea1e93 Merge pull request #44 from SHooZ/light_theme
Default system theme
2017-05-03 19:51:38 +03:00
124decc34a Add padding for search field and contacts nicknames in default theme 2017-05-03 19:44:25 +03:00
89caef6905 Edit default avatar image for light themes compatibility 2017-05-03 18:43:22 +03:00
9118e01775 Set dark style as default in load screen 2017-05-02 20:59:27 +03:00
138135b9e9 Add ability to change theme 2017-05-02 02:59:24 +03:00
2863eb790d Merge pull request #42 from SHooZ/ukrainian_translation
Add ukrainian translation
2017-05-01 18:21:35 +03:00
06e8c79b3f Update toxygen.pro 2017-05-01 18:09:42 +03:00
81695737cd Add ukrainian translation 2017-05-01 00:44:55 +03:00
ac07cb529f reconnection bug fixed 2017-04-22 22:35:32 +03:00
4f77e2c105 @cached 2017-04-17 22:04:22 +03:00
47ce9252b7 Merge pull request #41 from nurupo/fix-md-formatting
Fix markdown formatting
2017-04-12 17:16:06 +03:00
9153836ead Fix markdown formatting 2017-04-12 10:10:48 -04:00
f8a7087779 video recording and thread 2017-04-11 23:18:59 +03:00
01546f0047 dict on incoming call widgets, calls.py update 2017-04-11 23:18:59 +03:00
d8dd16e865 init commit 2017-04-11 23:18:58 +03:00
19fb905554 some fixes 2017-04-06 21:28:34 +03:00
3ef581bc5d some messages improvements 2017-04-05 23:46:32 +03:00
f897c7ce8d unlock screen crash fixed 2017-03-27 00:04:32 +03:00
ba390eda91 message splitting bug fix 2017-03-26 18:28:30 +03:00
5fea3e918d plugin reloading refactoring 2017-03-25 18:21:25 +03:00
7cc404ce52 plugins improvements 2017-03-15 23:17:38 +03:00
8a502b4082 block user option in friend menu and translations update 2017-03-08 13:37:19 +03:00
b83ea6be18 reconnection bug fix 2017-03-08 13:19:41 +03:00
85554eacd1 docs updates 2017-03-04 23:35:46 +03:00
8bbefff6c7 history - travis tests fix 2017-03-04 23:18:26 +03:00
019165aeac unsent files fix 2017-03-04 22:15:42 +03:00
0cfb8efefa reconnection fixes 2017-03-03 22:09:45 +03:00
b227ed627a more tests 2017-02-27 21:44:35 +03:00
f41b5e5c97 history test 2017-02-25 23:53:33 +03:00
bc9ec04171 version number fix 2017-02-20 23:47:55 +03:00
05e4184c5d travis fix 2017-02-20 22:03:35 +03:00
3194099f59 default config file moved to app dir 2017-02-20 21:33:04 +03:00
63 changed files with 6074 additions and 3687 deletions

3
.gitignore vendored
View File

@ -6,6 +6,7 @@ tests/tests
tests/libs
tests/.cache
tests/__pycache__
tests/avatars
toxygen/libs
.idea
*~
@ -23,4 +24,4 @@ toxygen/__pycache__
html
Toxygen.egg-info
*.tox
.cache

View File

@ -1,16 +1,25 @@
language: python
python:
- "3.4"
- "3.5"
- "3.6"
os:
- linux
dist: trusty
notifications:
email: false
before_install:
- sudo apt-get update
- sudo apt-get install -y checkinstall build-essential
- sudo apt-get install portaudio19-dev
- sudo apt-get install libsecret-1-dev
- sudo apt-get install libconfig-dev libvpx-dev check -qq
install:
- pip install PySide --no-index --find-links https://parkin.github.io/python-wheelhouse/;
- python ~/virtualenv/python${TRAVIS_PYTHON_VERSION}/bin/pyside_postinstall.py -install
- pip install sip
- pip install pyqt5
- pip install pyaudio
- pip install opencv-python
before_script:
# OPUS
# Opus
- wget http://downloads.xiph.org/releases/opus/opus-1.0.3.tar.gz
- tar xzf opus-1.0.3.tar.gz
- cd opus-1.0.3

View File

@ -12,9 +12,8 @@ include toxygen/smileys/starwars/*.png
include toxygen/smileys/starwars/config.json
include toxygen/smileys/ksk/*.png
include toxygen/smileys/ksk/config.json
include toxygen/styles/style.qss
include toxygen/styles/*.qss
include toxygen/translations/*.qm
include toxygen/libs/libtox.dll
include toxygen/libs/libsodium.a
include toxygen/libs/libtox64.dll
include toxygen/libs/libsodium64.a
include toxygen/nodes.json

View File

@ -2,49 +2,47 @@
Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pure Python3.
[![Release](https://img.shields.io/github/release/xveduk/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/releases/latest)
[![Stars](https://img.shields.io/github/stars/xveduk/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/stargazers)
[![Open issues](https://img.shields.io/github/issues/xveduk/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/issues)
[![Release](https://img.shields.io/github/release/toxygen-project/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/releases/latest)
[![Stars](https://img.shields.io/github/stars/toxygen-project/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/stargazers)
[![Open issues](https://img.shields.io/github/issues/toxygen-project/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/issues)
[![License](https://img.shields.io/badge/license-GPLv3-blue.svg?style=flat)](https://raw.githubusercontent.com/toxygen-project/toxygen/master/LICENSE.md)
[![Build Status](https://travis-ci.org/toxygen-project/toxygen.svg?branch=master)](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:
![Linux, Windows and OS X](/docs/os.png)
### Supported OS: Linux and Windows
### Features:
- [x] 1v1 messages
- [x] File transfers
- [x] Audio
- [x] Plugins support
- [x] Chat history
- [x] Emoticons
- [x] Stickers
- [x] Screenshots
- [x] Name lookups (toxme.io support)
- [x] Save file encryption
- [x] Profile import and export
- [x] Faux offline messaging
- [x] Faux offline file transfers
- [x] Inline images
- [x] Message splitting
- [x] Proxy support
- [x] Avatars
- [x] Multiprofile
- [x] Multilingual
- [x] Sound notifications
- [x] Contact aliases
- [x] Contact blocking
- [x] Typing notifications
- [x] Changing nospam
- [x] File resuming
- [x] Read receipts
- [ ] Video
- [ ] Desktop sharing
- [ ] Group chats
- 1v1 messages
- File transfers
- Audio calls
- Video calls
- Group chats
- Plugins support
- Desktop sharing
- Chat history
- Emoticons
- Stickers
- Screenshots
- Name lookups (toxme.io support)
- Save file encryption
- Profile import and export
- Faux offline messaging
- Faux offline file transfers
- Inline images
- Message splitting
- Proxy support
- Avatars
- Multiprofile
- Multilingual
- Sound notifications
- Contact aliases
- Contact blocking
- Typing notifications
- Changing nospam
- File resuming
- Read receipts
### Downloads
[Releases](https://github.com/toxygen-project/toxygen/releases)

View File

@ -5,6 +5,7 @@ You can compile Toxygen using [PyInstaller](http://www.pyinstaller.org/)
Install PyInstaller:
``pip3 install pyinstaller``
Compile Toxygen:
``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/

View File

@ -1,20 +1,22 @@
#Issues
# Issues
Help us find all bugs in Toxygen! Please provide following info:
- OS
- 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
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.
Don't know what to do? Improve UI, fix [issues](https://github.com/xveduk/toxygen/issues) or implement features from our TODO list.
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/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.
#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.

View File

@ -1,13 +1,13 @@
# How to install Toxygen
## Use precompiled binary:
[Check our releases page](https://github.com/xveduk/toxygen/releases)
## Use precompiled binary (recommended for users):
[Check our releases page](https://github.com/toxygen-project/toxygen/releases)
## Using pip3
### Windows
``pip3.4 install toxygen``
``pip install toxygen``
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/)
2. Install PortAudio:
``sudo apt-get install portaudio19-dev``
3. Install PySide: ``sudo apt-get install python3-pyside``
4. Install toxygen:
``sudo pip3.4 install toxygen``
5. 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.
3. For 32-bit Linux install PyQt5: ``sudo apt-get install python3-pyqt5``
4. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo pip3 install opencv-python``
5. Install toxygen:
``sudo pip3 install toxygen``
6. Run toxygen using ``toxygen`` command.
## Packages
@ -40,15 +32,19 @@ Debian/Ubuntu: [tox.chat](https://tox.chat/download.html#gnulinux)
### Windows
1. [Download and install latest Python 3.4](https://www.python.org/downloads/windows/)
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4#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.
Note: 32-bit Python isn't supported due to bug with videocalls. It is strictly recommended to use 64-bit Python.
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)
@ -62,27 +58,15 @@ Optional: install toxygen using setup.py: ``python3.4 setup.py install``
1. Install latest 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/)
4. Install PyAudio:
``sudo apt-get install portaudio19-dev`` and ``sudo apt-get install python3-pyaudio`` (or ``pip3 install pyaudio``)
5. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
6. Unpack archive
7. Run app:
``python3.4 main.py``
Optional: install toxygen using setup.py: ``python3.4 setup.py install``
### 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.
``sudo apt-get install portaudio19-dev`` and ``sudo apt-get install python3-pyaudio`` (or ``sudo pip3 install pyaudio``)
5. Install NumPy: ``sudo pip3 install numpy``
6. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo pip3 install opencv-python``
7. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip)
8. Unpack archive
9. Run app:
``python3 main.py``
Optional: install toxygen using setup.py: ``python3 setup.py install``

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

@ -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 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:
- __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:
- 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:
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:
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)

View File

@ -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
#How to install plugin
# How to install plugin
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)
2. Restart Toxygen
1. Put plugin and directory with its data into /src/plugins/ or import it via GUI (In menu: Plugins => Import plugin)
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!
[Main repo](https://github.com/ingvar1995/toxygen_plugins)
[Main repo](https://github.com/toxygen-project/toxygen_plugins)

View File

@ -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:
@ -6,8 +6,8 @@ Toxygen support smileys. Smiley is small picture which replaces some symbol or c
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

View File

@ -8,15 +8,27 @@ import sys
version = program_version + '.0'
MODULES = []
if system() in ('Windows', 'Darwin'):
MODULES = ['PyAudio', 'PySide']
if system() == 'Windows':
MODULES = ['PyQt5', 'PyAudio', 'numpy', 'opencv-python']
else:
MODULES = []
try:
import pyaudio
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):
@ -25,9 +37,7 @@ class InstallScript(install):
def run(self):
install.run(self)
try:
if system() == 'Windows':
call(["toxygen", "--configure"])
else:
if system() != 'Windows':
call(["toxygen", "--clean"])
except:
try:
@ -37,13 +47,12 @@ class InstallScript(install):
if path[-1] not in ('/', '\\'):
path += '/'
path += 'bin/toxygen'
if system() == 'Windows':
call([path, "--configure"])
else:
if system() != 'Windows':
call([path, "--clean"])
except:
pass
setup(name='Toxygen',
version=version,
description='Toxygen - Tox client',
@ -58,12 +67,12 @@ setup(name='Toxygen',
include_package_data=True,
classifiers=[
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
],
entry_points={
'console_scripts': ['toxygen=toxygen.main:main'],
},
cmdclass={
'install': InstallScript,
},
)
})

View File

@ -1,7 +1,10 @@
from toxygen.profile import *
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.messages as m
import toxygen.util as util
import time
@ -58,56 +61,117 @@ class TestEncryption:
lib.set_password(password)
copy_data = data[:]
new_data = lib.pass_encrypt(data)
assert lib.is_data_encrypted(new_data)
new_data = lib.pass_decrypt(new_data)
assert copy_data == new_data
class TestSmileys:
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()
if not os.path.exists(folder):
os.makedirs(folder)
ProfileHelper(folder, 'test')
def create_friend(name, status_message, number, tox_id):
friend = Friend(None, number, name, status_message, None, tox_id)
return friend
def create_random_friend():
name, status_message, number = 'Friend', 'I am friend!', 0
tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6'
friend = create_friend(name, status_message, number, tox_id)
return friend
class TestFriend:
def create_singletons(self):
Settings._instance = Settings.get_default_settings()
ProfileHelper('abc', 'test')
def create_friend(self, name, status_message, number, tox_id):
friend = Friend(None, number, name, status_message, None, tox_id)
return friend
def test_friend_creation(self):
self.create_singletons()
create_singletons()
name, status_message, number = 'Friend', 'I am friend!', 0
tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6'
friend = self.create_friend(name, status_message, number, tox_id)
friend = create_friend(name, status_message, number, tox_id)
assert friend.name == name
assert friend.tox_id == tox_id
assert friend.status_message == status_message
assert friend.number == number
def test_friend_corr(self):
self.create_singletons()
name, status_message, number = 'Friend', 'I am friend!', 0
tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6'
friend = self.create_friend(name, status_message, number, tox_id)
create_singletons()
friend = create_random_friend()
t = time.time()
friend.append_message(m.InfoMessage('Info message', t))
friend.append_message(m.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(InfoMessage('Info message', t))
friend.append_message(TextMessage('Hello! It is test!', MESSAGE_OWNER['ME'], t + 0.001, 0))
friend.append_message(TextMessage('Hello!', MESSAGE_OWNER['FRIEND'], t + 0.002, 0))
assert friend.get_last_message_text() == 'Hello! It is test!'
assert len(friend.get_corr()) == 3
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()
assert len(arr) == 1
assert arr[0][0] == 'Not sent'
tm = m.TransferMessage(MESSAGE_OWNER['FRIEND'],
time.time(),
TOX_FILE_TRANSFER_STATE['RUNNING'],
100, 'file_name', friend.number, 0)
tm = TransferMessage(MESSAGE_OWNER['FRIEND'],
time.time(),
TOX_FILE_TRANSFER_STATE['RUNNING'],
100, 'file_name', friend.number, 0)
friend.append_message(tm)
friend.clear_corr()
assert len(friend.get_corr()) == 1
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_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)

View File

@ -1,7 +1,4 @@
try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
from PyQt5 import QtCore, QtGui, QtWidgets
import widgets
import profile
import util
@ -17,11 +14,12 @@ class IncomingCallWidget(widgets.CenteredWidget):
super(IncomingCallWidget, self).__init__()
self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowStaysOnTopHint)
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.setScaledContents(False)
self.name = widgets.DataLabel(self)
self.name.setGeometry(QtCore.QRect(90, 20, 300, 25))
self._friend_number = friend_number
font = QtGui.QFont()
font.setFamily(settings.Settings.get_instance()['font'])
font.setPointSize(16)
@ -30,11 +28,11 @@ class IncomingCallWidget(widgets.CenteredWidget):
self.call_type = widgets.DataLabel(self)
self.call_type.setGeometry(QtCore.QRect(90, 55, 300, 25))
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_video = QtGui.QPushButton(self)
self.accept_video = QtWidgets.QPushButton(self)
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))
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/accept_audio.png')
icon = QtGui.QIcon(pixmap)
@ -54,15 +52,16 @@ class IncomingCallWidget(widgets.CenteredWidget):
self.setWindowTitle(text)
self.name.setText(name)
self.call_type.setText(text)
pr = profile.Profile.get_instance()
self.accept_audio.clicked.connect(lambda: pr.accept_call(friend_number, True, False) or self.stop())
# self.accept_video.clicked.connect(lambda: pr.start_call(friend_number, True, True))
self.decline.clicked.connect(lambda: pr.stop_call(friend_number, False) or self.stop())
self._processing = False
self.accept_audio.clicked.connect(self.accept_call_with_audio)
self.accept_video.clicked.connect(self.accept_call_with_video)
self.decline.clicked.connect(self.decline_call)
class SoundPlay(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
self.a = None
def run(self):
class AudioFile:
@ -107,33 +106,29 @@ class IncomingCallWidget(widgets.CenteredWidget):
self.thread.wait()
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):
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()

View File

@ -1,8 +1,5 @@
from settings import *
try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
from PyQt5 import QtCore, QtGui
from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
@ -109,6 +106,10 @@ class BaseContact:
def get_pixmap(self):
return self._widget.avatar_label.pixmap()
# -----------------------------------------------------------------------------------------------------------------
# Widgets
# -----------------------------------------------------------------------------------------------------------------
def init_widget(self):
if self._widget is not None:
self._widget.name.setText(self._name)

View File

@ -1,84 +1,75 @@
import random
import urllib.request
from util import log, curr_directory
import settings
from PyQt5 import QtNetwork, QtCore
import json
class Node:
def __init__(self, ip, port, tox_key, rand):
self._ip, self._port, self._tox_key, self.rand = ip, port, tox_key, rand
def __init__(self, node):
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):
return bytes(self._ip, 'utf-8'), self._port, self._tox_key
def node_generator():
nodes = []
ips = [
"144.76.60.215", "23.226.230.47", "195.154.119.113", "biribiri.org",
"46.38.239.179", "178.62.250.138", "130.133.110.14", "104.167.101.29",
"205.185.116.116", "198.98.51.198", "80.232.246.79", "108.61.165.198",
"212.71.252.109", "194.249.212.109", "185.25.116.107", "192.99.168.140",
"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 generate_nodes():
with open(curr_directory() + '/nodes.json', 'rt') as fl:
json_nodes = json.loads(fl.read())['nodes']
nodes = map(lambda json_node: Node(json_node), json_nodes)
sorted_nodes = sorted(nodes, key=lambda x: x.priority)[-4:]
for node in sorted_nodes:
yield node.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))

View File

@ -1,7 +1,4 @@
try:
from PySide import QtCore
except ImportError:
from PyQt4 import QtCore
from PyQt5 import QtCore, QtGui, QtWidgets
from notifications import *
from settings import Settings
from profile import Profile
@ -12,7 +9,8 @@ from plugin_support import PluginLoader
import queue
import threading
import util
import cv2
import numpy as np
# -----------------------------------------------------------------------------------------------------------------
# Threads
@ -35,6 +33,7 @@ class Invoker(QtCore.QObject):
event.fn(*event.args, **event.kwargs)
return True
_invoker = Invoker()
@ -68,6 +67,7 @@ class FileTransfersThread(threading.Thread):
except Exception as ex:
util.log('Exception in _thread: ' + str(ex))
_thread = FileTransfersThread()
@ -225,7 +225,7 @@ def tox_file_recv(window, tray):
if not window.isActiveWindow():
friend = profile.get_friend_by_number(friend_number)
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] 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)
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
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
"""
Profile.get_instance().call.chunk(
Profile.get_instance().call.audio_chunk(
bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]),
audio_channels_count,
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
@ -353,7 +457,13 @@ def init_callbacks(tox, window, tray):
toxav.callback_call_state(call_state, 0)
toxav.callback_call(call, 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_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)

View File

@ -3,14 +3,69 @@ import time
import threading
import settings
from toxav_enums import *
# TODO: play sound until outgoing call will be started or cancelled and add timeout
# TODO: add widget for call
import cv2
import itertools
import numpy as np
import screen_sharing
# TODO: play sound until outgoing call will be started or cancelled
CALL_TYPE = {
'NONE': 0,
'AUDIO': 1,
'VIDEO': 2
}
class Call:
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._in_video = value
out_video = property(get_out_video, set_out_video)
class AV:
@ -19,7 +74,7 @@ class AV:
self._toxav = toxav
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_stream = None
@ -32,27 +87,78 @@ class AV:
self._audio_duration = 60
self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration // 1000
def __contains__(self, friend_number):
return friend_number in self._calls
self._video = None
self._video_thread = None
self._video_running = False
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_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()
self._video_width = 640
self._video_height = 480
def stop(self):
self._running = False
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']
call.in_video = state | TOXAV_FRIEND_CALL_STATE['SENDING_V']
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 self._calls[number].in_video
# -----------------------------------------------------------------------------------------------------------------
# Threads
# -----------------------------------------------------------------------------------------------------------------
def start_audio_thread(self):
"""
@ -92,7 +198,41 @@ class AV:
self._out_stream.close()
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
"""
@ -105,6 +245,10 @@ class AV:
output=True)
self._out_stream.write(samples)
# -----------------------------------------------------------------------------------------------------------------
# AV sending
# -----------------------------------------------------------------------------------------------------------------
def send_audio(self):
"""
This method sends audio to friends
@ -114,10 +258,10 @@ class AV:
try:
pcm = self._audio_stream.read(self._audio_sample_count)
if pcm:
for friend in self._calls:
if self._calls[friend] & 1:
for friend_num in self._calls:
if self._calls[friend_num].out_audio:
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)
except:
pass
@ -126,19 +270,70 @@ class AV:
time.sleep(0.01)
def accept_call(self, friend_number, audio_enabled, video_enabled):
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):
def send_video(self):
"""
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']:
self._calls[friend_number] |= 1
time.sleep(0.01)
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)

View File

@ -1,7 +1,4 @@
try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
from PyQt5 import QtCore, QtGui
from history import *
import basecontact
import util
@ -30,7 +27,6 @@ class Contact(basecontact.BaseContact):
self._corr = []
self._unsaved_messages = 0
self._history_loaded = self._new_actions = False
self._receipts = 0
self._curr_text = self._search_string = ''
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')):
return
if self._message_getter is None:
return
data = list(self._message_getter.get(PAGE_SIZE))
if data is not None and len(data):
data.reverse()
@ -63,6 +61,8 @@ class Contact(basecontact.BaseContact):
"""
Get all chat history from db for current friend
"""
if self._message_getter is None:
return
data = list(self._message_getter.get_all())
if data is not None and len(data):
data.reverse()
@ -126,7 +126,7 @@ class Contact(basecontact.BaseContact):
# -----------------------------------------------------------------------------------------------------------------
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))
if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages:
self._unsaved_messages -= 1
@ -138,12 +138,15 @@ class Contact(basecontact.BaseContact):
"""
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),
self._corr[:-SAVE_MESSAGES])
old = list(old)
l = max(len(self._corr) - SAVE_MESSAGES, 0) - len(old)
self._unsaved_messages -= l
self._corr = old + self._corr[-SAVE_MESSAGES:]
def save_message(x):
if x.get_type() == 2 and (x.get_status() >= 2 or x.get_status() is None):
return True
return x.get_owner() == MESSAGE_OWNER['NOT_SENT']
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
def clear_corr(self, save_unsent=False):
@ -176,7 +179,7 @@ class Contact(basecontact.BaseContact):
while True:
l = len(self._corr)
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
message = self._corr[i].get_data()[0]
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:
return None
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
message = self._corr[i].get_data()[0]
if re.search(self._search_string, message, re.IGNORECASE) is not None:

View File

@ -4,11 +4,7 @@ from os import remove, rename, chdir
from time import time, sleep
from tox import Tox
import settings
try:
from PySide import QtCore
except ImportError:
from PyQt4 import QtCore
QtCore.Signal = QtCore.pyqtSignal
from PyQt5 import QtCore
TOX_FILE_TRANSFER_STATE = {
@ -38,12 +34,12 @@ def is_inline(file_name):
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):
signal = QtCore.Signal(int, int) # friend number, file number
signal = QtCore.pyqtSignal(int, int) # friend number, file number
class FileTransfer(QtCore.QObject):

View File

@ -1,5 +1,6 @@
import contact
from messages import *
import os
class Friend(contact.Contact):
@ -37,6 +38,15 @@ class Friend(contact.Contact):
def clear_unsent_files(self):
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):
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:
self._receipts -= 1
self.mark_as_sent()
# -----------------------------------------------------------------------------------------------------------------
# Full status
# -----------------------------------------------------------------------------------------------------------------
def get_full_status(self):
return self._status_message

49
toxygen/group_chat.py Normal file
View 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)

View File

@ -9,7 +9,7 @@ PAGE_SIZE = 42
TIMEOUT = 11
SAVE_MESSAGES = 150
SAVE_MESSAGES = 250
MESSAGE_OWNER = {
'ME': 0,
@ -133,14 +133,15 @@ class History:
db.rollback()
finally:
db.close()
pass
def delete_message(self, tox_id, time):
start, end = str(time - 0.01), str(time + 0.01)
chdir(settings.ProfileHelper.get_path())
db = connect(self._name + '.hstr', timeout=TIMEOUT)
try:
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()
except:
print('Database is locked!')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
toxygen/images/group.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -1,7 +1,4 @@
try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
from PyQt5 import QtWidgets, QtCore
from list_items import *
@ -13,7 +10,7 @@ class ItemsFactory:
def friend_item(self):
item = ContactItem()
elem = QtGui.QListWidgetItem(self._friends)
elem = QtWidgets.QListWidgetItem(self._friends)
elem.setSizeHint(QtCore.QSize(250, item.height()))
self._friends.addItem(elem)
self._friends.setItemWidget(elem, item)
@ -23,7 +20,7 @@ class ItemsFactory:
item = MessageItem(text, time, name, sent, message_type, self._messages)
if pixmap is not None:
item.set_avatar(pixmap)
elem = QtGui.QListWidgetItem()
elem = QtWidgets.QListWidgetItem()
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
if append:
self._messages.addItem(elem)
@ -33,7 +30,7 @@ class ItemsFactory:
return item
def inline_item(self, data, append):
elem = QtGui.QListWidgetItem()
elem = QtWidgets.QListWidgetItem()
item = InlineImageItem(data, self._messages.width(), elem)
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
if append:
@ -49,7 +46,7 @@ class ItemsFactory:
name,
time,
self._messages.width())
elem = QtGui.QListWidgetItem()
elem = QtWidgets.QListWidgetItem()
elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34))
if append:
self._messages.addItem(elem)
@ -61,7 +58,7 @@ class ItemsFactory:
def file_transfer_item(self, data, append):
data.append(self._messages.width())
item = FileTransferItem(*data)
elem = QtGui.QListWidgetItem()
elem = QtWidgets.QListWidgetItem()
elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34))
if append:
self._messages.addItem(elem)

View File

@ -1,9 +1,5 @@
from toxcore_enums_and_consts import *
try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
QtCore.Slot = QtCore.pyqtSlot
from PyQt5 import QtCore, QtGui, QtWidgets
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 util import curr_directory, convert_time, curr_time
@ -14,7 +10,7 @@ import settings
import re
class MessageEdit(QtGui.QTextBrowser):
class MessageEdit(QtWidgets.QTextBrowser):
def __init__(self, text, width, message_type, parent=None):
super(MessageEdit, self).__init__(parent)
@ -46,7 +42,7 @@ class MessageEdit(QtGui.QTextBrowser):
def contextMenuEvent(self, event):
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)
text = self.textCursor().selection().toPlainText()
if not text:
@ -55,7 +51,7 @@ class MessageEdit(QtGui.QTextBrowser):
import plugin_support
submenu = plugin_support.PluginLoader.get_instance().get_message_menu(menu, text)
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)
menu.popup(event.globalPos())
menu.exec_(event.globalPos())
@ -123,12 +119,12 @@ class MessageEdit(QtGui.QTextBrowser):
return text
class MessageItem(QtGui.QWidget):
class MessageItem(QtWidgets.QWidget):
"""
Message in messages list
"""
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.setGeometry(QtCore.QRect(2, 2, 95, 23))
self.name.setTextFormat(QtCore.Qt.PlainText)
@ -139,7 +135,7 @@ class MessageItem(QtGui.QWidget):
self.name.setFont(font)
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))
font.setPointSize(10)
font.setBold(False)
@ -164,9 +160,9 @@ class MessageItem(QtGui.QWidget):
def mouseReleaseEvent(self, event):
if event.button() == QtCore.Qt.RightButton and event.x() > self.time.x():
self.listMenu = QtGui.QMenu()
delete_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Delete message', None, QtGui.QApplication.UnicodeUTF8))
self.connect(delete_item, QtCore.SIGNAL("triggered()"), self.delete)
self.listMenu = QtWidgets.QMenu()
delete_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Delete message'))
delete_item.triggered.connect(self.delete)
parent_position = self.time.mapToGlobal(QtCore.QPoint(0, 0))
self.listMenu.move(parent_position)
self.listMenu.show()
@ -216,16 +212,16 @@ class MessageItem(QtGui.QWidget):
return text
class ContactItem(QtGui.QWidget):
class ContactItem(QtWidgets.QWidget):
"""
Contact in friends list
"""
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
QtWidgets.QWidget.__init__(self, parent)
mode = settings.Settings.get_instance()['compact_mode']
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
self.avatar_label.setGeometry(QtCore.QRect(3, 4, size, size))
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))
class StatusCircle(QtGui.QWidget):
class StatusCircle(QtWidgets.QWidget):
"""
Connection status
"""
def __init__(self, parent):
QtGui.QWidget.__init__(self, parent)
QtWidgets.QWidget.__init__(self, parent)
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.unread = False
@ -281,12 +277,12 @@ class StatusCircle(QtGui.QWidget):
self.label.setPixmap(pixmap)
class UnreadMessagesCount(QtGui.QWidget):
class UnreadMessagesCount(QtWidgets.QWidget):
def __init__(self, parent=None):
super(UnreadMessagesCount, self).__init__(parent)
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.setVisible(False)
font = QtGui.QFont()
@ -308,11 +304,11 @@ class UnreadMessagesCount(QtGui.QWidget):
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):
QtGui.QListWidget.__init__(self, parent)
QtWidgets.QListWidget.__init__(self, parent)
self.resize(QtCore.QSize(width, 34))
if state == TOX_FILE_TRANSFER_STATE['CANCELLED']:
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
@ -332,14 +328,14 @@ class FileTransferItem(QtGui.QListWidget):
self.name.setFont(font)
self.name.setText(user)
self.time = QtGui.QLabel(self)
self.time = QtWidgets.QLabel(self)
self.time.setGeometry(QtCore.QRect(width - 60, 7, 50, 25))
font.setPointSize(10)
font.setBold(False)
self.time.setFont(font)
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))
pixmap = QtGui.QPixmap(curr_directory() + '/images/decline.png')
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.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))
if state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
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.pb = QtGui.QProgressBar(self)
self.pb = QtWidgets.QProgressBar(self)
self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20))
self.pb.setValue(0)
self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }')
@ -387,7 +383,7 @@ class FileTransferItem(QtGui.QListWidget):
self.file_name.setText(file_data)
self.file_name.setToolTip(file_name)
self.saved_name = file_name
self.time_left = QtGui.QLabel(self)
self.time_left = QtWidgets.QLabel(self)
self.time_left.setGeometry(QtCore.QRect(width - 92, 7, 30, 20))
font.setPointSize(10)
self.time_left.setFont(font)
@ -405,10 +401,10 @@ class FileTransferItem(QtGui.QListWidget):
def accept_or_pause_transfer(self, friend_number, file_number, size):
if self.state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
directory = QtGui.QFileDialog.getExistingDirectory(self,
QtGui.QApplication.translate("MainWindow", 'Choose folder', None, QtGui.QApplication.UnicodeUTF8),
directory = QtWidgets.QFileDialog.getExistingDirectory(self,
QtWidgets.QApplication.translate("MainWindow", 'Choose folder'),
curr_directory(),
QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog)
QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
self.pb.setVisible(True)
if directory:
pr = profile.Profile.get_instance()
@ -432,8 +428,7 @@ class FileTransferItem(QtGui.QListWidget):
self.accept_or_pause.setIcon(icon)
self.accept_or_pause.setIconSize(QtCore.QSize(30, 30))
@QtCore.Slot(int, float, int)
def update(self, state, progress, time):
def update_transfer_state(self, state, progress, time):
self.pb.setValue(int(progress * 100))
if time + 1:
m, s = divmod(time, 60)
@ -496,14 +491,14 @@ class UnsentFileItem(FileTransferItem):
pr.cancel_not_started_transfer(self._time)
class InlineImageItem(QtGui.QScrollArea):
class InlineImageItem(QtWidgets.QScrollArea):
def __init__(self, data, width, elem):
QtGui.QScrollArea.__init__(self)
QtWidgets.QScrollArea.__init__(self)
self.setFocusPolicy(QtCore.Qt.NoFocus)
self._elem = elem
self._image_label = QtGui.QLabel(self)
self._image_label = QtWidgets.QLabel(self)
self._image_label.raise_()
self.setWidget(self._image_label)
self._image_label.setScaledContents(False)
@ -537,21 +532,14 @@ class InlineImageItem(QtGui.QScrollArea):
self._full_size = not self._full_size
self._elem.setSizeHint(QtCore.QSize(self.width(), self.height()))
elif event.button() == QtCore.Qt.RightButton: # save inline
directory = QtGui.QFileDialog.getExistingDirectory(self,
QtGui.QApplication.translate("MainWindow",
'Choose folder', None,
QtGui.QApplication.UnicodeUTF8),
curr_directory(),
QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog)
directory = QtWidgets.QFileDialog.getExistingDirectory(self,
QtWidgets.QApplication.translate("MainWindow",
'Choose folder'),
curr_directory(),
QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
if directory:
fl = QtCore.QFile(directory + '/toxygen_inline_' + curr_time().replace(':', '_') + '.png')
self._pixmap.save(fl, 'PNG')
return False
def mark_as_sent(self):
return False

View File

@ -1,9 +1,4 @@
# -*- coding: utf-8 -*-
try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
from PyQt5 import QtWidgets, QtCore
from widgets import *
@ -31,25 +26,25 @@ class LoginScreen(CenteredWidget):
self.resize(400, 200)
self.setMinimumSize(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.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.new_name = NickEdit(self)
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.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.groupBox = QtGui.QGroupBox(self)
self.groupBox = QtWidgets.QGroupBox(self)
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.groupBox_2 = QtGui.QGroupBox(self)
self.groupBox_2 = QtWidgets.QGroupBox(self)
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_2.raise_()
self.comboBox.raise_()
@ -71,15 +66,15 @@ class LoginScreen(CenteredWidget):
QtCore.QMetaObject.connectSlotsByName(self)
def retranslateUi(self):
self.new_name.setPlaceholderText(QtGui.QApplication.translate("login", "Profile name", None, QtGui.QApplication.UnicodeUTF8))
self.setWindowTitle(QtGui.QApplication.translate("login", "Log in", None, QtGui.QApplication.UnicodeUTF8))
self.new_profile.setText(QtGui.QApplication.translate("login", "Create", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("login", "Profile name:", None, QtGui.QApplication.UnicodeUTF8))
self.load_profile.setText(QtGui.QApplication.translate("login", "Load profile", None, QtGui.QApplication.UnicodeUTF8))
self.default.setText(QtGui.QApplication.translate("login", "Use as default", None, QtGui.QApplication.UnicodeUTF8))
self.groupBox.setTitle(QtGui.QApplication.translate("login", "Load existing profile", None, QtGui.QApplication.UnicodeUTF8))
self.groupBox_2.setTitle(QtGui.QApplication.translate("login", "Create new profile", None, QtGui.QApplication.UnicodeUTF8))
self.toxygen.setText(QtGui.QApplication.translate("login", "toxygen", None, QtGui.QApplication.UnicodeUTF8))
self.new_name.setPlaceholderText(QtWidgets.QApplication.translate("login", "Profile name"))
self.setWindowTitle(QtWidgets.QApplication.translate("login", "Log in"))
self.new_profile.setText(QtWidgets.QApplication.translate("login", "Create"))
self.label.setText(QtWidgets.QApplication.translate("login", "Profile name:"))
self.load_profile.setText(QtWidgets.QApplication.translate("login", "Load profile"))
self.default.setText(QtWidgets.QApplication.translate("login", "Use as default"))
self.groupBox.setTitle(QtWidgets.QApplication.translate("login", "Load existing profile"))
self.groupBox_2.setTitle(QtWidgets.QApplication.translate("login", "Create new profile"))
self.toxygen.setText(QtWidgets.QApplication.translate("login", "toxygen"))
def create_profile(self):
self.type = 1

View File

@ -2,15 +2,12 @@ import sys
from loginscreen import LoginScreen
import profile
from settings import *
try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
from bootstrap import node_generator
from PyQt5 import QtCore, QtGui, QtWidgets
from bootstrap import generate_nodes, download_nodes_list
from mainscreen import MainWindow
from callbacks import init_callbacks, stop, start
from util import curr_directory, program_version, remove, is_64_bit
import styles.style
from util import curr_directory, program_version, remove
import styles.style # reqired for styles loading
import platform
import toxes
from passwordscreen import PasswordScreen, UnlockAppScreen, SetProfilePasswordScreen
@ -39,7 +36,7 @@ class Toxygen:
tmp = [data]
p = PasswordScreen(toxes.ToxES.get_instance(), tmp)
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_()
if tmp[0] == data:
raise SystemExit()
@ -50,17 +47,16 @@ class Toxygen:
"""
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'))
self.app = app
if platform.system() == 'Linux':
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
# application color scheme
with open(curr_directory() + '/styles/style.qss') as fl:
dark_style = fl.read()
app.setStyleSheet(dark_style)
with open(curr_directory() + '/styles/dark_style.qss') as fl:
style = fl.read()
app.setStyleSheet(style)
encrypt_save = toxes.ToxES()
@ -92,7 +88,6 @@ class Toxygen:
_login = self.Login(profiles)
ls.update_on_close(_login.login_screen_close)
ls.show()
app.connect(app, QtCore.SIGNAL("lastWindowClosed()"), app, QtCore.SLOT("quit()"))
app.exec_()
if not _login.t:
return
@ -101,40 +96,35 @@ class Toxygen:
name = _login.name if _login.name else 'toxygen_user'
pr = map(lambda x: x[1], ProfileHelper.find_profiles())
if name in list(pr):
msgBox = QtGui.QMessageBox()
msgBox = QtWidgets.QMessageBox()
msgBox.setWindowTitle(
QtGui.QApplication.translate("MainWindow", "Error", None, QtGui.QApplication.UnicodeUTF8))
text = (QtGui.QApplication.translate("MainWindow",
'Profile with this name already exists',
None, QtGui.QApplication.UnicodeUTF8))
QtWidgets.QApplication.translate("MainWindow", "Error"))
text = (QtWidgets.QApplication.translate("MainWindow",
'Profile with this name already exists'))
msgBox.setText(text)
msgBox.exec_()
return
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_status_message(b'Toxing on Toxygen')
reply = QtGui.QMessageBox.question(None,
'Profile {}'.format(name),
QtGui.QApplication.translate("login",
'Do you want to set profile password?',
None,
QtGui.QApplication.UnicodeUTF8),
QtGui.QMessageBox.Yes,
QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
reply = QtWidgets.QMessageBox.question(None,
'Profile {}'.format(name),
QtWidgets.QApplication.translate("login",
'Do you want to set profile password?'),
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
set_pass = SetProfilePasswordScreen(encrypt_save)
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_()
reply = QtGui.QMessageBox.question(None,
'Profile {}'.format(name),
QtGui.QApplication.translate("login",
'Do you want to save profile in default folder? If no, profile will be saved in program folder',
None,
QtGui.QApplication.UnicodeUTF8),
QtGui.QMessageBox.Yes,
QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
reply = QtWidgets.QMessageBox.question(None,
'Profile {}'.format(name),
QtWidgets.QApplication.translate("login",
'Do you want to save profile in default folder? If no, profile will be saved in program folder'),
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
path = Settings.get_default_path()
else:
path = curr_directory() + '/'
@ -143,11 +133,9 @@ class Toxygen:
except Exception as ex:
print(str(ex))
log('Profile creation exception: ' + str(ex))
msgBox = QtGui.QMessageBox()
msgBox.setText(QtGui.QApplication.translate("login",
'Profile saving error! Does Toxygen have permission to write to this directory?',
None,
QtGui.QApplication.UnicodeUTF8))
msgBox = QtWidgets.QMessageBox()
msgBox.setText(QtWidgets.QApplication.translate("login",
'Profile saving error! Does Toxygen have permission to write to this directory?'))
msgBox.exec_()
return
path = Settings.get_default_path()
@ -173,16 +161,23 @@ class Toxygen:
self.tox = profile.tox_factory(data, settings)
if Settings.is_active_profile(path, name): # profile is in use
reply = QtGui.QMessageBox.question(None,
'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),
QtGui.QMessageBox.Yes,
QtGui.QMessageBox.No)
if reply != QtGui.QMessageBox.Yes:
reply = QtWidgets.QMessageBox.question(None,
'Profile {}'.format(name),
QtWidgets.QApplication.translate("login", 'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?'),
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if reply != QtWidgets.QMessageBox.Yes:
return
else:
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']]
translator = QtCore.QTranslator()
translator.load(curr_directory() + '/translations/' + lang)
@ -190,21 +185,21 @@ class Toxygen:
app.translator = translator
# 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.ms = MainWindow(self.tox, self.reset, self.tray)
app.aboutToQuit.connect(self.ms.close_window)
class Menu(QtGui.QMenu):
class Menu(QtWidgets.QMenu):
def newStatus(self, status):
if not Settings.get_instance().locked:
profile.Profile.get_instance().set_status(status)
self.aboutToShow()
self.aboutToShowHandler()
self.hide()
def aboutToShow(self):
def aboutToShowHandler(self):
status = profile.Profile.get_instance().status
act = self.act
if status is None or Settings.get_instance().locked:
@ -218,42 +213,47 @@ class Toxygen:
self.actions()[2].setVisible(not Settings.get_instance().locked)
def languageChange(self, *args, **kwargs):
self.actions()[0].setText(QtGui.QApplication.translate('tray', 'Open Toxygen', None, QtGui.QApplication.UnicodeUTF8))
self.actions()[1].setText(QtGui.QApplication.translate('tray', 'Set status', None, QtGui.QApplication.UnicodeUTF8))
self.actions()[2].setText(QtGui.QApplication.translate('tray', 'Exit', None, QtGui.QApplication.UnicodeUTF8))
self.act.actions()[0].setText(QtGui.QApplication.translate('tray', 'Online', None, QtGui.QApplication.UnicodeUTF8))
self.act.actions()[1].setText(QtGui.QApplication.translate('tray', 'Away', None, QtGui.QApplication.UnicodeUTF8))
self.act.actions()[2].setText(QtGui.QApplication.translate('tray', 'Busy', None, QtGui.QApplication.UnicodeUTF8))
self.actions()[0].setText(QtWidgets.QApplication.translate('tray', 'Open Toxygen'))
self.actions()[1].setText(QtWidgets.QApplication.translate('tray', 'Set status'))
self.actions()[2].setText(QtWidgets.QApplication.translate('tray', 'Exit'))
self.act.actions()[0].setText(QtWidgets.QApplication.translate('tray', 'Online'))
self.act.actions()[1].setText(QtWidgets.QApplication.translate('tray', 'Away'))
self.act.actions()[2].setText(QtWidgets.QApplication.translate('tray', 'Busy'))
m = Menu()
show = m.addAction(QtGui.QApplication.translate('tray', 'Open Toxygen', None, QtGui.QApplication.UnicodeUTF8))
sub = m.addMenu(QtGui.QApplication.translate('tray', 'Set status', None, QtGui.QApplication.UnicodeUTF8))
onl = sub.addAction(QtGui.QApplication.translate('tray', 'Online', None, QtGui.QApplication.UnicodeUTF8))
away = sub.addAction(QtGui.QApplication.translate('tray', 'Away', None, QtGui.QApplication.UnicodeUTF8))
busy = sub.addAction(QtGui.QApplication.translate('tray', 'Busy', None, QtGui.QApplication.UnicodeUTF8))
show = m.addAction(QtWidgets.QApplication.translate('tray', 'Open Toxygen'))
sub = m.addMenu(QtWidgets.QApplication.translate('tray', 'Set status'))
onl = sub.addAction(QtWidgets.QApplication.translate('tray', 'Online'))
away = sub.addAction(QtWidgets.QApplication.translate('tray', 'Away'))
busy = sub.addAction(QtWidgets.QApplication.translate('tray', 'Busy'))
onl.setCheckable(True)
away.setCheckable(True)
busy.setCheckable(True)
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():
s = Settings.get_instance()
def show():
if not self.ms.isActiveWindow():
self.ms.setWindowState(self.ms.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
self.ms.activateWindow()
self.ms.show()
if not Settings.get_instance().locked:
if not s.locked:
show()
else:
def correct_pass():
show()
Settings.get_instance().locked = False
self.p = UnlockAppScreen(toxes.ToxES.get_instance(), correct_pass)
self.p.show()
s.locked = False
s.unlockScreen = False
if not s.unlockScreen:
s.unlockScreen = True
self.p = UnlockAppScreen(toxes.ToxES.get_instance(), correct_pass)
self.p.show()
def tray_activated(reason):
if reason == QtGui.QSystemTrayIcon.DoubleClick:
if reason == QtWidgets.QSystemTrayIcon.DoubleClick:
show_window()
def close_app():
@ -261,12 +261,12 @@ class Toxygen:
settings.closing = True
self.ms.close()
m.connect(show, QtCore.SIGNAL("triggered()"), show_window)
m.connect(exit, QtCore.SIGNAL("triggered()"), close_app)
m.connect(m, QtCore.SIGNAL("aboutToShow()"), lambda: m.aboutToShow())
sub.connect(onl, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(0))
sub.connect(away, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(1))
sub.connect(busy, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(2))
show.triggered.connect(show_window)
exit.triggered.connect(close_app)
m.aboutToShow.connect(lambda: m.aboutToShowHandler())
onl.triggered.connect(lambda: m.newStatus(0))
away.triggered.connect(lambda: m.newStatus(1))
busy.triggered.connect(lambda: m.newStatus(2))
self.tray.setContextMenu(m)
self.tray.show()
@ -282,15 +282,13 @@ class Toxygen:
updater.download(version)
updating = True
else:
reply = QtGui.QMessageBox.question(None,
'Toxygen',
QtGui.QApplication.translate("login",
'Update for Toxygen was found. Download and install it?',
None,
QtGui.QApplication.UnicodeUTF8),
QtGui.QMessageBox.Yes,
QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
reply = QtWidgets.QMessageBox.question(None,
'Toxygen',
QtWidgets.QApplication.translate("login",
'Update for Toxygen was found. Download and install it?'),
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
updater.download(version)
updating = True
@ -318,7 +316,7 @@ class Toxygen:
if self.uri is not None:
self.ms.add_contact(self.uri)
app.connect(app, QtCore.SIGNAL("lastWindowClosed()"), app, QtCore.SLOT("quit()"))
app.lastWindowClosed.connect(app.quit)
app.exec_()
self.init.stop = True
@ -329,6 +327,7 @@ class Toxygen:
self.mainloop.wait()
self.init.wait()
self.avloop.wait()
self.tray.hide()
data = self.tox.get_savedata()
ProfileHelper.get_instance().save_profile(data)
settings.close()
@ -380,9 +379,11 @@ class Toxygen:
def run(self):
# initializing callbacks
init_callbacks(self.tox, self.ms, self.tray)
# download list of nodes if needed
download_nodes_list()
# bootstrap
try:
for data in node_generator():
for data in generate_nodes():
if self.stop:
return
self.tox.bootstrap(*data)
@ -395,7 +396,7 @@ class Toxygen:
self.msleep(1000)
while not self.tox.self_get_connection_status():
try:
for data in node_generator():
for data in generate_nodes():
if self.stop:
return
self.tox.bootstrap(*data)
@ -456,27 +457,6 @@ def clean():
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():
Settings.reset_auto_profile()
@ -492,9 +472,6 @@ def main():
elif arg == '--help':
print('Usage:\ntoxygen path_to_profile\ntoxygen tox_id\ntoxygen --version\ntoxygen --reset')
return
elif arg == '--configure':
configure()
return
elif arg == '--clean':
clean()
return

View File

@ -1,15 +1,14 @@
from menu import *
from profile import *
from list_items import *
from widgets import MultilineEdit, LineEdit, ComboBox
from widgets import MultilineEdit, ComboBox
import plugin_support
from mainscreen_widgets import *
import settings
import platform
import toxes
class MainWindow(QtGui.QMainWindow, Singleton):
class MainWindow(QtWidgets.QMainWindow, Singleton):
def __init__(self, tox, reset, tray):
super().__init__()
@ -22,51 +21,50 @@ class MainWindow(QtGui.QMainWindow, Singleton):
if settings.Settings.get_instance()['show_welcome_screen']:
self.ws = WelcomeScreen()
def setup_menu(self, Form):
box = QtGui.QHBoxLayout()
box.setContentsMargins(0, 0, 0, 0)
box.setAlignment(QtCore.Qt.AlignLeft)
self.profile_button = MainMenuButton(Form)
box.addWidget(self.profile_button)
self.settings_button = MainMenuButton(Form)
box.addWidget(self.settings_button)
self.plugins_button = MainMenuButton(Form)
box.addWidget(self.plugins_button)
self.about_button = MainMenuButton(Form)
box.addWidget(self.about_button)
box.setSpacing(0)
def setup_menu(self, window):
self.menubar = QtWidgets.QMenuBar(window)
self.menubar.setObjectName("menubar")
self.menubar.setNativeMenuBar(False)
self.menubar.setMinimumSize(self.width(), 25)
self.menubar.setMaximumSize(self.width(), 25)
self.menubar.setBaseSize(self.width(), 25)
self.menuProfile = QtWidgets.QMenu(self.menubar)
self.menuProfile = QtGui.QMenu()
self.menuProfile = QtWidgets.QMenu(self.menubar)
self.menuProfile.setObjectName("menuProfile")
self.menuSettings = QtGui.QMenu()
self.menuSettings = QtWidgets.QMenu(self.menubar)
self.menuSettings.setObjectName("menuSettings")
self.menuPlugins = QtGui.QMenu()
self.menuPlugins = QtWidgets.QMenu(self.menubar)
self.menuPlugins.setObjectName("menuPlugins")
self.menuAbout = QtGui.QMenu()
self.menuAbout = QtWidgets.QMenu(self.menubar)
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.actionprofilesettings = QtGui.QAction(Form)
self.actionprofilesettings = QtWidgets.QAction(window)
self.actionprofilesettings.setObjectName("actionprofilesettings")
self.actionPrivacy_settings = QtGui.QAction(Form)
self.actionPrivacy_settings = QtWidgets.QAction(window)
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.actionNotifications = QtGui.QAction(Form)
self.actionNotifications = QtWidgets.QAction(window)
self.actionNotifications.setObjectName("actionNotifications")
self.actionNetwork = QtGui.QAction(Form)
self.actionNetwork = QtWidgets.QAction(window)
self.actionNetwork.setObjectName("actionNetwork")
self.actionAbout_program = QtGui.QAction(Form)
self.actionAbout_program = QtWidgets.QAction(window)
self.actionAbout_program.setObjectName("actionAbout_program")
self.updateSettings = QtGui.QAction(Form)
self.actionSettings = QtGui.QAction(Form)
self.updateSettings = QtWidgets.QAction(window)
self.actionSettings = QtWidgets.QAction(window)
self.actionSettings.setObjectName("actionSettings")
self.audioSettings = QtGui.QAction(Form)
self.pluginData = QtGui.QAction(Form)
self.importPlugin = QtGui.QAction(Form)
self.lockApp = QtGui.QAction(Form)
self.audioSettings = QtWidgets.QAction(window)
self.videoSettings = QtWidgets.QAction(window)
self.pluginData = QtWidgets.QAction(window)
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_gc)
self.menuProfile.addAction(self.actionSettings)
self.menuProfile.addAction(self.lockApp)
self.menuSettings.addAction(self.actionPrivacy_settings)
@ -74,31 +72,33 @@ class MainWindow(QtGui.QMainWindow, Singleton):
self.menuSettings.addAction(self.actionNotifications)
self.menuSettings.addAction(self.actionNetwork)
self.menuSettings.addAction(self.audioSettings)
self.menuSettings.addAction(self.videoSettings)
self.menuSettings.addAction(self.updateSettings)
self.menuPlugins.addAction(self.pluginData)
self.menuPlugins.addAction(self.importPlugin)
self.menuPlugins.addAction(self.reloadPlugins)
self.menuAbout.addAction(self.actionAbout_program)
self.profile_button.setMenu(self.menuProfile)
self.settings_button.setMenu(self.menuSettings)
self.plugins_button.setMenu(self.menuPlugins)
self.about_button.setMenu(self.menuAbout)
self.menubar.addAction(self.menuProfile.menuAction())
self.menubar.addAction(self.menuSettings.menuAction())
self.menubar.addAction(self.menuPlugins.menuAction())
self.menubar.addAction(self.menuAbout.menuAction())
self.actionAbout_program.triggered.connect(self.about_program)
self.actionNetwork.triggered.connect(self.network_settings)
self.actionAdd_friend.triggered.connect(self.add_contact)
self.actionAdd_gc.triggered.connect(self.create_gc)
self.actionSettings.triggered.connect(self.profile_settings)
self.actionPrivacy_settings.triggered.connect(self.privacy_settings)
self.actionInterface_settings.triggered.connect(self.interface_settings)
self.actionNotifications.triggered.connect(self.notification_settings)
self.audioSettings.triggered.connect(self.audio_settings)
self.videoSettings.triggered.connect(self.video_settings)
self.updateSettings.triggered.connect(self.update_settings)
self.pluginData.triggered.connect(self.plugins_menu)
self.lockApp.triggered.connect(self.lock_app)
self.importPlugin.triggered.connect(self.import_plugin)
Form.setLayout(box)
QtCore.QMetaObject.connectSlotsByName(Form)
self.reloadPlugins.triggered.connect(self.reload_plugins)
def languageChange(self, *args, **kwargs):
self.retranslateUi()
@ -110,36 +110,39 @@ class MainWindow(QtGui.QMainWindow, Singleton):
return super(MainWindow, self).event(event)
def retranslateUi(self):
self.lockApp.setText(QtGui.QApplication.translate("MainWindow", "Lock", None, QtGui.QApplication.UnicodeUTF8))
self.plugins_button.setText(QtGui.QApplication.translate("MainWindow", "Plugins", None, QtGui.QApplication.UnicodeUTF8))
self.pluginData.setText(QtGui.QApplication.translate("MainWindow", "List of plugins", None, QtGui.QApplication.UnicodeUTF8))
self.profile_button.setText(QtGui.QApplication.translate("MainWindow", "Profile", None, QtGui.QApplication.UnicodeUTF8))
self.settings_button.setText(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8))
self.about_button.setText(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8))
self.actionAdd_friend.setText(QtGui.QApplication.translate("MainWindow", "Add contact", None, QtGui.QApplication.UnicodeUTF8))
self.actionprofilesettings.setText(QtGui.QApplication.translate("MainWindow", "Profile", None, QtGui.QApplication.UnicodeUTF8))
self.actionPrivacy_settings.setText(QtGui.QApplication.translate("MainWindow", "Privacy", None, QtGui.QApplication.UnicodeUTF8))
self.actionInterface_settings.setText(QtGui.QApplication.translate("MainWindow", "Interface", None, QtGui.QApplication.UnicodeUTF8))
self.actionNotifications.setText(QtGui.QApplication.translate("MainWindow", "Notifications", None, QtGui.QApplication.UnicodeUTF8))
self.actionNetwork.setText(QtGui.QApplication.translate("MainWindow", "Network", None, QtGui.QApplication.UnicodeUTF8))
self.actionAbout_program.setText(QtGui.QApplication.translate("MainWindow", "About program", None, QtGui.QApplication.UnicodeUTF8))
self.actionSettings.setText(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8))
self.audioSettings.setText(QtGui.QApplication.translate("MainWindow", "Audio", None, QtGui.QApplication.UnicodeUTF8))
self.updateSettings.setText(QtGui.QApplication.translate("MainWindow", "Updates", None, QtGui.QApplication.UnicodeUTF8))
self.contact_name.setPlaceholderText(QtGui.QApplication.translate("MainWindow", "Search", None, QtGui.QApplication.UnicodeUTF8))
self.sendMessageButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Send message", None, QtGui.QApplication.UnicodeUTF8))
self.callButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Start audio call with friend", None, QtGui.QApplication.UnicodeUTF8))
self.lockApp.setText(QtWidgets.QApplication.translate("MainWindow", "Lock"))
self.menuPlugins.setTitle(QtWidgets.QApplication.translate("MainWindow", "Plugins"))
self.pluginData.setText(QtWidgets.QApplication.translate("MainWindow", "List of plugins"))
self.menuProfile.setTitle(QtWidgets.QApplication.translate("MainWindow", "Profile"))
self.menuSettings.setTitle(QtWidgets.QApplication.translate("MainWindow", "Settings"))
self.menuAbout.setTitle(QtWidgets.QApplication.translate("MainWindow", "About"))
self.actionAdd_friend.setText(QtWidgets.QApplication.translate("MainWindow", "Add contact"))
self.actionAdd_gc.setText(QtWidgets.QApplication.translate("MainWindow", "Create group chat"))
self.actionprofilesettings.setText(QtWidgets.QApplication.translate("MainWindow", "Profile"))
self.actionPrivacy_settings.setText(QtWidgets.QApplication.translate("MainWindow", "Privacy"))
self.actionInterface_settings.setText(QtWidgets.QApplication.translate("MainWindow", "Interface"))
self.actionNotifications.setText(QtWidgets.QApplication.translate("MainWindow", "Notifications"))
self.actionNetwork.setText(QtWidgets.QApplication.translate("MainWindow", "Network"))
self.actionAbout_program.setText(QtWidgets.QApplication.translate("MainWindow", "About program"))
self.actionSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Settings"))
self.audioSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Audio"))
self.videoSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Video"))
self.updateSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Updates"))
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.addItem(QtGui.QApplication.translate("MainWindow", "All", None, QtGui.QApplication.UnicodeUTF8))
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online", None, QtGui.QApplication.UnicodeUTF8))
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online first", None, QtGui.QApplication.UnicodeUTF8))
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Name", None, QtGui.QApplication.UnicodeUTF8))
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online and by name", None, QtGui.QApplication.UnicodeUTF8))
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", "All"))
self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online"))
self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online first"))
self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Name"))
self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online and by name"))
self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online first and by name"))
ind = Settings.get_instance()['sorting']
d = {0: 0, 1: 1, 2: 2, 3: 4, 1 | 4: 4, 2 | 4: 5}
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):
Form.resize(650, 60)
@ -151,7 +154,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
font.setFamily(settings.Settings.get_instance()['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.setObjectName("sendMessageButton")
@ -174,7 +177,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
def setup_left_center_menu(self, Form):
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))
pixmap = QtGui.QPixmap()
pixmap.load(curr_directory() + '/images/search.png')
@ -198,7 +201,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
Form.setMinimumSize(QtCore.QSize(270, 75))
Form.setMaximumSize(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.setScaledContents(False)
self.avatar_label.setAlignment(QtCore.Qt.AlignCenter)
@ -226,7 +229,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
def setup_right_top(self, Form):
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.setScaledContents(False)
self.account_name = DataLabel(Form)
@ -245,16 +248,16 @@ class MainWindow(QtGui.QMainWindow, Singleton):
font.setBold(False)
self.account_status.setFont(font)
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.setObjectName("callButton")
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.setObjectName("videocallButton")
self.videocallButton.clicked.connect(lambda: self.profile.call_click(True, True))
self.update_call_state('call')
self.typing = QtGui.QLabel(Form)
self.typing = QtWidgets.QLabel(Form)
self.typing.setGeometry(QtCore.QRect(500, 25, 50, 30))
pixmap = QtGui.QPixmap(QtCore.QSize(50, 30))
pixmap.load(curr_directory() + '/images/typing.png')
@ -264,20 +267,19 @@ class MainWindow(QtGui.QMainWindow, Singleton):
QtCore.QMetaObject.connectSlotsByName(Form)
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.setGeometry(0, 0, 270, 310)
self.friends_list.clicked.connect(self.friend_click)
self.friends_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.friends_list.connect(self.friends_list, QtCore.SIGNAL("customContextMenuRequested(QPoint)"),
self.friend_right_click)
self.friends_list.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
self.friends_list.customContextMenuRequested.connect(self.friend_right_click)
self.friends_list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.friends_list.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.friends_list.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.friends_list.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu)
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.setObjectName("messages")
self.messages.setSpacing(1)
@ -291,8 +293,8 @@ class MainWindow(QtGui.QMainWindow, Singleton):
self.profile.load_history()
self.messages.verticalScrollBar().setValue(1)
self.messages.verticalScrollBar().valueChanged.connect(load)
self.messages.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
self.messages.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.messages.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.messages.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
def initUI(self, tox):
self.setMinimumSize(920, 500)
@ -300,15 +302,15 @@ class MainWindow(QtGui.QMainWindow, Singleton):
self.setGeometry(s['x'], s['y'], s['width'], s['height'])
self.setWindowTitle('Toxygen')
os.chdir(curr_directory() + '/images/')
menu = QtGui.QWidget()
main = QtGui.QWidget()
grid = QtGui.QGridLayout()
search = QtGui.QWidget()
name = QtGui.QWidget()
info = QtGui.QWidget()
main_list = QtGui.QWidget()
messages = QtGui.QWidget()
message_buttons = QtGui.QWidget()
menu = QtWidgets.QWidget()
main = QtWidgets.QWidget()
grid = QtWidgets.QGridLayout()
search = QtWidgets.QWidget()
name = QtWidgets.QWidget()
info = QtWidgets.QWidget()
main_list = QtWidgets.QWidget()
messages = QtWidgets.QWidget()
message_buttons = QtWidgets.QWidget()
self.setup_left_center_menu(search)
self.setup_left_top(name)
self.setup_right_center(messages)
@ -365,9 +367,9 @@ class MainWindow(QtGui.QMainWindow, Singleton):
s['width'] = self.width()
s['height'] = self.height()
s.save()
QtGui.QApplication.closeAllWindows()
QtWidgets.QApplication.closeAllWindows()
event.accept()
elif QtGui.QSystemTrayIcon.isSystemTrayAvailable():
elif QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
event.ignore()
self.hide()
@ -376,12 +378,8 @@ class MainWindow(QtGui.QMainWindow, Singleton):
self.close()
def resizeEvent(self, *args, **kwargs):
if platform.system() == 'Windows':
self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155)
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.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155)
self.friends_list.setGeometry(0, 0, 270, self.height() - 125)
self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 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()
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()
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()))
indexes = (rows[0] - self.messages.count(), rows[-1] - self.messages.count())
s = self.profile.export_history(self.profile.active_friend, True, indexes)
clipboard = QtGui.QApplication.clipboard()
clipboard = QtWidgets.QApplication.clipboard()
clipboard.setText(s)
elif event.key() == QtCore.Qt.Key_Z and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
self.messages.clearSelection()
@ -418,9 +416,9 @@ class MainWindow(QtGui.QMainWindow, Singleton):
def about_program(self):
import util
msgBox = QtGui.QMessageBox()
msgBox.setWindowTitle(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8))
text = (QtGui.QApplication.translate("MainWindow", 'Toxygen is Tox client written on Python.\nVersion: ', None, QtGui.QApplication.UnicodeUTF8))
msgBox = QtWidgets.QMessageBox()
msgBox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "About"))
text = (QtWidgets.QApplication.translate("MainWindow", 'Toxygen is Tox client written on Python.\nVersion: '))
msgBox.setText(text + util.program_version + '\nGitHub: https://github.com/toxygen-project/toxygen/')
msgBox.exec_()
@ -436,6 +434,9 @@ class MainWindow(QtGui.QMainWindow, Singleton):
self.a_c = AddContact(link or '')
self.a_c.show()
def create_gc(self):
self.profile.create_group_chat()
def profile_settings(self, *args):
self.p_s = ProfileSettings()
self.p_s.show()
@ -456,28 +457,34 @@ class MainWindow(QtGui.QMainWindow, Singleton):
self.audio_s = AudioSettings()
self.audio_s.show()
def video_settings(self):
self.video_s = VideoSettings()
self.video_s.show()
def update_settings(self):
self.update_s = UpdateSettings()
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):
import util
directory = QtGui.QFileDialog.getExistingDirectory(self,
QtGui.QApplication.translate("MainWindow", 'Choose folder with plugin',
None,
QtGui.QApplication.UnicodeUTF8),
directory = QtWidgets.QFileDialog.getExistingDirectory(self,
QtWidgets.QApplication.translate("MainWindow", 'Choose folder with plugin'),
util.curr_directory(),
QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog)
QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
if directory:
src = directory + '/'
dest = curr_directory() + '/plugins/'
util.copy(src, dest)
msgBox = QtGui.QMessageBox()
msgBox = QtWidgets.QMessageBox()
msgBox.setWindowTitle(
QtGui.QApplication.translate("MainWindow", "Restart Toxygen", None, QtGui.QApplication.UnicodeUTF8))
QtWidgets.QApplication.translate("MainWindow", "Restart Toxygen"))
msgBox.setText(
QtGui.QApplication.translate("MainWindow", 'Plugin will be loaded after restart', None,
QtGui.QApplication.UnicodeUTF8))
QtWidgets.QApplication.translate("MainWindow", 'Plugin will be loaded after restart'))
msgBox.exec_()
def lock_app(self):
@ -485,12 +492,11 @@ class MainWindow(QtGui.QMainWindow, Singleton):
Settings.get_instance().locked = True
self.hide()
else:
msgBox = QtGui.QMessageBox()
msgBox = QtWidgets.QMessageBox()
msgBox.setWindowTitle(
QtGui.QApplication.translate("MainWindow", "Cannot lock app", None, QtGui.QApplication.UnicodeUTF8))
QtWidgets.QApplication.translate("MainWindow", "Cannot lock app"))
msgBox.setText(
QtGui.QApplication.translate("MainWindow", 'Error. Profile password is not set.', None,
QtGui.QApplication.UnicodeUTF8))
QtWidgets.QApplication.translate("MainWindow", 'Error. Profile password is not set.'))
msgBox.exec_()
def show_menu(self):
@ -512,15 +518,15 @@ class MainWindow(QtGui.QMainWindow, Singleton):
def send_file(self):
self.menu.hide()
if self.profile.active_friend + 1:
choose = QtGui.QApplication.translate("MainWindow", 'Choose file', None, QtGui.QApplication.UnicodeUTF8)
name = QtGui.QFileDialog.getOpenFileName(self, choose, options=QtGui.QFileDialog.DontUseNativeDialog)
if self.profile.active_friend + 1and self.profile.is_active_a_friend():
choose = QtWidgets.QApplication.translate("MainWindow", 'Choose file')
name = QtWidgets.QFileDialog.getOpenFileName(self, choose, options=QtWidgets.QFileDialog.DontUseNativeDialog)
if name[0]:
self.profile.send_file(name[0])
def send_screenshot(self, hide=False):
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.show()
if hide:
@ -538,7 +544,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
def send_sticker(self):
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.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(),
self.y() + self.height() - 200,
@ -555,14 +561,15 @@ class MainWindow(QtGui.QMainWindow, Singleton):
def call_finished(self):
self.update_call_state('call')
def update_call_state(self, fl):
# TODO: do smth with video call button
def update_call_state(self, state):
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)
self.callButton.setIcon(icon)
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)
self.videocallButton.setIcon(icon)
self.videocallButton.setIconSize(QtCore.QSize(35, 35))
@ -579,40 +586,59 @@ class MainWindow(QtGui.QMainWindow, Singleton):
return
settings = Settings.get_instance()
allowed = friend.tox_id in settings['auto_accept_from_friends']
auto = QtGui.QApplication.translate("MainWindow", 'Disallow auto accept', None, QtGui.QApplication.UnicodeUTF8) if allowed else QtGui.QApplication.translate("MainWindow", 'Allow auto accept', None, QtGui.QApplication.UnicodeUTF8)
auto = QtWidgets.QApplication.translate("MainWindow", 'Disallow auto accept') if allowed else QtWidgets.QApplication.translate("MainWindow", 'Allow auto accept')
if item is not None:
self.listMenu = QtGui.QMenu()
set_alias_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Set alias', None, QtGui.QApplication.UnicodeUTF8))
self.listMenu = QtWidgets.QMenu()
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))
clear_history_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8))
export_to_text_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Export as text', None, QtGui.QApplication.UnicodeUTF8))
export_to_html_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Export as HTML', None, QtGui.QApplication.UnicodeUTF8))
history_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Chat history'))
clear_history_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Clear history'))
export_to_text_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Export as text'))
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_name_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Name', None, QtGui.QApplication.UnicodeUTF8))
copy_status_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Status message', None, QtGui.QApplication.UnicodeUTF8))
copy_key_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Public key', None, QtGui.QApplication.UnicodeUTF8))
copy_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Copy'))
copy_name_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Name'))
copy_status_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Status message'))
if is_friend:
copy_key_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Public key'))
auto_accept_item = self.listMenu.addAction(auto)
remove_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Remove friend', None, QtGui.QApplication.UnicodeUTF8))
notes_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Notes', None, QtGui.QApplication.UnicodeUTF8))
auto_accept_item = self.listMenu.addAction(auto)
remove_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Remove friend'))
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)
if len(submenu):
plug = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8))
plug.addActions(submenu)
self.connect(set_alias_item, QtCore.SIGNAL("triggered()"), lambda: self.set_alias(num))
self.connect(remove_item, QtCore.SIGNAL("triggered()"), lambda: self.remove_friend(num))
self.connect(copy_key_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_friend_key(num))
self.connect(clear_history_item, QtCore.SIGNAL("triggered()"), lambda: self.clear_history(num))
self.connect(auto_accept_item, QtCore.SIGNAL("triggered()"), lambda: self.auto_accept(num, not allowed))
self.connect(notes_item, QtCore.SIGNAL("triggered()"), lambda: self.show_note(friend))
self.connect(copy_name_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_name(friend))
self.connect(copy_status_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_status(friend))
self.connect(export_to_text_item, QtCore.SIGNAL("triggered()"), lambda: self.export_history(num))
self.connect(export_to_html_item, QtCore.SIGNAL("triggered()"),
lambda: self.export_history(num, False))
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: 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):
plug = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins'))
plug.addActions(submenu)
copy_key_item.triggered.connect(lambda: self.copy_friend_key(num))
remove_item.triggered.connect(lambda: self.remove_friend(num))
block_item.triggered.connect(lambda: self.block_friend(num))
auto_accept_item.triggered.connect(lambda: self.auto_accept(num, not allowed))
notes_item.triggered.connect(lambda: self.show_note(friend))
else:
leave_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Leave chat'))
set_title_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Set title'))
leave_item.triggered.connect(lambda: self.leave_gc(num))
set_title_item.triggered.connect(lambda: self.set_title(num))
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))
self.listMenu.move(parent_position + pos)
self.listMenu.show()
@ -620,7 +646,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
def show_note(self, friend):
s = Settings.get_instance()
note = s['notes'][friend.tox_id] if friend.tox_id in s['notes'] else ''
user = QtGui.QApplication.translate("MainWindow", 'Notes about user', None, QtGui.QApplication.UnicodeUTF8)
user = QtWidgets.QApplication.translate("MainWindow", 'Notes about user')
user = '{} {}'.format(user, friend.name)
def save_note(text):
@ -634,12 +660,11 @@ class MainWindow(QtGui.QMainWindow, Singleton):
def export_history(self, num, as_text=True):
s = self.profile.export_history(num, as_text)
directory = QtGui.QFileDialog.getExistingDirectory(None,
QtGui.QApplication.translate("MainWindow", 'Choose folder',
None,
QtGui.QApplication.UnicodeUTF8),
directory = QtWidgets.QFileDialog.getExistingDirectory(None,
QtWidgets.QApplication.translate("MainWindow",
'Choose folder'),
curr_directory(),
QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog)
QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
if directory:
name = 'exported_history_{}.{}'.format(convert_time(time.time()), 'txt' if as_text else 'html')
@ -652,22 +677,32 @@ class MainWindow(QtGui.QMainWindow, Singleton):
def remove_friend(self, 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):
tox_id = self.profile.friend_public_key(num)
clipboard = QtGui.QApplication.clipboard()
clipboard = QtWidgets.QApplication.clipboard()
clipboard.setText(tox_id)
def copy_name(self, friend):
clipboard = QtGui.QApplication.clipboard()
clipboard = QtWidgets.QApplication.clipboard()
clipboard.setText(friend.name)
def copy_status(self, friend):
clipboard = QtGui.QApplication.clipboard()
clipboard = QtWidgets.QApplication.clipboard()
clipboard.setText(friend.status_message)
def clear_history(self, 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):
settings = Settings.get_instance()
tox_id = self.profile.friend_public_key(num)
@ -677,6 +712,9 @@ class MainWindow(QtGui.QMainWindow, Singleton):
settings['auto_accept_from_friends'].remove(tox_id)
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
# -----------------------------------------------------------------------------------------------------------------

View File

@ -1,14 +1,11 @@
try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
from widgets import RubberBand, create_menu, QRightClickButton, CenteredWidget, LineEdit
from PyQt5 import QtCore, QtGui, QtWidgets
from widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit
from profile import Profile
import smileys
import util
class MessageArea(QtGui.QPlainTextEdit):
class MessageArea(QtWidgets.QPlainTextEdit):
"""User types messages here"""
def __init__(self, parent, form):
@ -20,7 +17,7 @@ class MessageArea(QtGui.QPlainTextEdit):
def keyPressEvent(self, event):
if event.matches(QtGui.QKeySequence.Paste):
mimeData = QtGui.QApplication.clipboard().mimeData()
mimeData = QtWidgets.QApplication.clipboard().mimeData()
if mimeData.hasUrls():
for url in mimeData.urls():
self.pasteEvent(url.toString())
@ -37,6 +34,10 @@ class MessageArea(QtGui.QPlainTextEdit):
self.parent.send_message()
elif event.key() == QtCore.Qt.Key_Up and not self.toPlainText():
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:
self.parent.profile.send_typing(True)
if self.timer.isActive():
@ -67,53 +68,30 @@ class MessageArea(QtGui.QPlainTextEdit):
e.ignore()
def pasteEvent(self, text=None):
text = text or QtGui.QApplication.clipboard().text()
text = text or QtWidgets.QApplication.clipboard().text()
if text.startswith('file://'):
self.parent.profile.send_file(text[7:])
else:
self.insertPlainText(text)
class ScreenShotWindow(QtGui.QWidget):
def __init__(self, parent):
super(ScreenShotWindow, self).__init__()
self.parent = parent
self.setMouseTracking(True)
self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
self.showFullScreen()
self.setWindowOpacity(0.5)
self.rubberband = RubberBand()
class ScreenShotWindow(RubberBandWindow):
def closeEvent(self, *args):
if self.parent.isHidden():
self.parent.show()
def mousePressEvent(self, event):
self.origin = event.pos()
self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize()))
self.rubberband.show()
QtGui.QWidget.mousePressEvent(self, event)
def mouseMoveEvent(self, event):
if self.rubberband.isVisible():
self.rubberband.setGeometry(QtCore.QRect(self.origin, event.pos()).normalized())
left = QtGui.QRegion(QtCore.QRect(0, 0, self.rubberband.x(), self.height()))
right = QtGui.QRegion(QtCore.QRect(self.rubberband.x() + self.rubberband.width(), 0, self.width(), self.height()))
top = QtGui.QRegion(0, 0, self.width(), self.rubberband.y())
bottom = QtGui.QRegion(0, self.rubberband.y() + self.rubberband.height(), self.width(), self.height())
self.setMask(left + right + top + bottom)
def mouseReleaseEvent(self, event):
if self.rubberband.isVisible():
self.rubberband.hide()
rect = self.rubberband.geometry()
if rect.width() and rect.height():
p = QtGui.QPixmap.grabWindow(QtGui.QApplication.desktop().winId(),
rect.x() + 4,
rect.y() + 4,
rect.width() - 8,
rect.height() - 8)
screen = QtWidgets.QApplication.primaryScreen()
p = screen.grabWindow(0,
rect.x() + 4,
rect.y() + 4,
rect.width() - 8,
rect.height() - 8)
byte_array = QtCore.QByteArray()
buffer = QtCore.QBuffer(byte_array)
buffer.open(QtCore.QIODevice.WriteOnly)
@ -121,15 +99,8 @@ class ScreenShotWindow(QtGui.QWidget):
Profile.get_instance().send_screenshot(bytes(byte_array.data()))
self.close()
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Escape:
self.rubberband.setHidden(True)
self.close()
else:
super(ScreenShotWindow, self).keyPressEvent(event)
class SmileyWindow(QtGui.QWidget):
class SmileyWindow(QtWidgets.QWidget):
"""
Smiley selection window
"""
@ -151,18 +122,18 @@ class SmileyWindow(QtGui.QWidget):
self.radio = []
self.parent = parent
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.clicked.connect(lambda i=i: self.checked(i))
elem.clicked.connect(lambda c, t=i: self.checked(t))
self.radio.append(elem)
width = max(self.page_count * 20 + 30, (self.page_size + 5) * 8 // 10)
self.setMaximumSize(width, 200)
self.setMinimumSize(width, 200)
self.buttons = []
for i in range(self.page_size): # pages - radio buttons
b = QtGui.QPushButton(self)
b = QtWidgets.QPushButton(self)
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.checked(0)
@ -190,7 +161,7 @@ class SmileyWindow(QtGui.QWidget):
self.close()
class MenuButton(QtGui.QPushButton):
class MenuButton(QtWidgets.QPushButton):
def __init__(self, parent, enter):
super(MenuButton, self).__init__(parent)
@ -201,86 +172,73 @@ class MenuButton(QtGui.QPushButton):
super(MenuButton, self).enterEvent(event)
class DropdownMenu(QtGui.QWidget):
class DropdownMenu(QtWidgets.QWidget):
def __init__(self, parent):
super(DropdownMenu, self).__init__(parent)
self.installEventFilter(self)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.setMaximumSize(180, 120)
self.setMinimumSize(180, 120)
self.setMaximumSize(120, 120)
self.setMinimumSize(120, 120)
self.screenshotButton = QRightClickButton(self)
self.screenshotButton.setGeometry(QtCore.QRect(0, 60, 60, 60))
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.setObjectName("fileTransferButton")
self.audioMessageButton = QtGui.QPushButton(self)
self.audioMessageButton.setGeometry(QtCore.QRect(120, 60, 60, 60))
self.smileyButton = QtGui.QPushButton(self)
self.smileyButton = QtWidgets.QPushButton(self)
self.smileyButton.setGeometry(QtCore.QRect(0, 0, 60, 60))
self.videoMessageButton = QtGui.QPushButton(self)
self.videoMessageButton.setGeometry(QtCore.QRect(120, 0, 60, 60))
self.stickerButton = QtGui.QPushButton(self)
self.stickerButton = QtWidgets.QPushButton(self)
self.stickerButton.setGeometry(QtCore.QRect(60, 0, 60, 60))
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/file.png')
icon = QtGui.QIcon(pixmap)
self.fileTransferButton.setIcon(icon)
self.fileTransferButton.setIconSize(QtCore.QSize(50, 50))
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/screenshot.png')
icon = QtGui.QIcon(pixmap)
self.screenshotButton.setIcon(icon)
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')
icon = QtGui.QIcon(pixmap)
self.smileyButton.setIcon(icon)
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')
icon = QtGui.QIcon(pixmap)
self.stickerButton.setIcon(icon)
self.stickerButton.setIconSize(QtCore.QSize(55, 55))
self.screenshotButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send screenshot", None, QtGui.QApplication.UnicodeUTF8))
self.fileTransferButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send file", None, QtGui.QApplication.UnicodeUTF8))
self.audioMessageButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send audio message", None, QtGui.QApplication.UnicodeUTF8))
self.videoMessageButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send video message", None, QtGui.QApplication.UnicodeUTF8))
self.smileyButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Add smiley", None, QtGui.QApplication.UnicodeUTF8))
self.stickerButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send sticker", None, QtGui.QApplication.UnicodeUTF8))
self.screenshotButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send screenshot"))
self.fileTransferButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send file"))
self.smileyButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Add smiley"))
self.stickerButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send sticker"))
self.fileTransferButton.clicked.connect(parent.send_file)
self.screenshotButton.clicked.connect(parent.send_screenshot)
self.connect(self.screenshotButton, QtCore.SIGNAL("rightClicked()"), lambda: parent.send_screenshot(True))
self.screenshotButton.rightClicked.connect(lambda: parent.send_screenshot(True))
self.smileyButton.clicked.connect(parent.send_smiley)
self.stickerButton.clicked.connect(parent.send_sticker)
def leaveEvent(self, event):
self.close()
def eventFilter(self, object, event):
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.WindowDeactivate:
self.close()
return False
class StickerItem(QtGui.QWidget):
class StickerItem(QtWidgets.QWidget):
def __init__(self, fl):
super(StickerItem, self).__init__()
self._image_label = QtGui.QLabel(self)
self._image_label = QtWidgets.QLabel(self)
self.path = fl
self.pixmap = QtGui.QPixmap()
self.pixmap.load(fl)
@ -290,7 +248,7 @@ class StickerItem(QtGui.QWidget):
self._image_label.setPixmap(self.pixmap)
class StickerWindow(QtGui.QWidget):
class StickerWindow(QtWidgets.QWidget):
"""Sticker selection window"""
def __init__(self, parent):
@ -298,16 +256,16 @@ class StickerWindow(QtGui.QWidget):
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.setMaximumSize(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.arr = smileys.sticker_loader()
for sticker in self.arr:
item = StickerItem(sticker)
elem = QtGui.QListWidgetItem()
elem = QtWidgets.QListWidgetItem()
elem.setSizeHint(QtCore.QSize(250, item.height()))
self.list.addItem(elem)
self.list.setItemWidget(elem, item)
self.list.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
self.list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.list.setSpacing(3)
self.list.clicked.connect(self.click)
self.parent = parent
@ -329,60 +287,47 @@ class WelcomeScreen(CenteredWidget):
self.setMinimumSize(250, 200)
self.center()
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.setOpenExternalLinks(True)
self.checkbox = QtGui.QCheckBox(self)
self.checkbox = QtWidgets.QCheckBox(self)
self.checkbox.setGeometry(QtCore.QRect(5, 170, 240, 30))
self.checkbox.setText(QtGui.QApplication.translate('WelcomeScreen', "Don't show again",
None, QtGui.QApplication.UnicodeUTF8))
self.setWindowTitle(QtGui.QApplication.translate('WelcomeScreen', 'Tip of the day',
None, QtGui.QApplication.UnicodeUTF8))
self.checkbox.setText(QtWidgets.QApplication.translate('WelcomeScreen', "Don't show again"))
self.setWindowTitle(QtWidgets.QApplication.translate('WelcomeScreen', 'Tip of the day'))
import random
num = random.randint(0, 10)
if num == 0:
text = QtGui.QApplication.translate('WelcomeScreen', 'Press Esc if you want hide app to tray.',
None, QtGui.QApplication.UnicodeUTF8)
text = QtWidgets.QApplication.translate('WelcomeScreen', 'Press Esc if you want hide app to tray.')
elif num == 1:
text = QtGui.QApplication.translate('WelcomeScreen',
'Right click on screenshot button hides app to tray during screenshot.',
None, QtGui.QApplication.UnicodeUTF8)
text = QtWidgets.QApplication.translate('WelcomeScreen',
'Right click on screenshot button hides app to tray during screenshot.')
elif num == 2:
text = QtGui.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>',
None, QtGui.QApplication.UnicodeUTF8)
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>')
elif num == 3:
text = QtGui.QApplication.translate('WelcomeScreen',
'Use Settings -> Interface to customize interface.',
None, QtGui.QApplication.UnicodeUTF8)
text = QtWidgets.QApplication.translate('WelcomeScreen',
'Use Settings -> Interface to customize interface.')
elif num == 4:
text = QtGui.QApplication.translate('WelcomeScreen',
'Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.',
None, QtGui.QApplication.UnicodeUTF8)
text = QtWidgets.QApplication.translate('WelcomeScreen',
'Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.')
elif num == 5:
text = QtGui.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>',
None, QtGui.QApplication.UnicodeUTF8)
text = QtWidgets.QApplication.translate('WelcomeScreen',
'Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a>')
elif num == 6:
text = QtGui.QApplication.translate('WelcomeScreen',
'New in Toxygen v0.2.6:<br>Updater<br>Better contact sorting<br>Plugins improvements',
None, QtGui.QApplication.UnicodeUTF8)
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.')
elif num == 7:
text = QtGui.QApplication.translate('WelcomeScreen',
'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)
text = QtWidgets.QApplication.translate('WelcomeScreen',
'New in Toxygen 0.4.1:<br>Downloading nodes from tox.chat<br>Bug fixes')
elif num == 8:
text = QtGui.QApplication.translate('WelcomeScreen',
'Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu',
None, QtGui.QApplication.UnicodeUTF8)
text = QtWidgets.QApplication.translate('WelcomeScreen',
'Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu')
elif num == 9:
text = QtGui.QApplication.translate('WelcomeScreen',
'Use right click on inline image to save it',
None, QtGui.QApplication.UnicodeUTF8)
text = QtWidgets.QApplication.translate('WelcomeScreen',
'Use right click on inline image to save it')
else:
text = QtGui.QApplication.translate('WelcomeScreen',
'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.',
None, QtGui.QApplication.UnicodeUTF8)
text = QtWidgets.QApplication.translate('WelcomeScreen',
'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.')
self.text.setHtml(text)
self.checkbox.stateChanged.connect(self.not_show)
QtCore.QTimer.singleShot(1000, self.show)
@ -394,7 +339,7 @@ class WelcomeScreen(CenteredWidget):
s.save()
class MainMenuButton(QtGui.QPushButton):
class MainMenuButton(QtWidgets.QPushButton):
def __init__(self, *args):
super().__init__(*args)
@ -406,16 +351,18 @@ class MainMenuButton(QtGui.QPushButton):
super().setText(text)
class ClickableLabel(QtGui.QLabel):
class ClickableLabel(QtWidgets.QLabel):
clicked = QtCore.pyqtSignal()
def __init__(self, *args):
super().__init__(*args)
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):
super().__init__(*args)
@ -433,23 +380,23 @@ class SearchScreen(QtGui.QWidget):
self.search_button.setScaledContents(False)
self.search_button.setAlignment(QtCore.Qt.AlignCenter)
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.setPointSize(32)
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.clicked.connect(self.prev)
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.clicked.connect(self.next)
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.clicked.connect(self.close)
self.close_button.setText('×')
@ -462,8 +409,7 @@ class SearchScreen(QtGui.QWidget):
self.retranslateUi()
def retranslateUi(self):
self.search_text.setPlaceholderText(QtGui.QApplication.translate("MainWindow", "Search", None,
QtGui.QApplication.UnicodeUTF8))
self.search_text.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "Search"))
def show(self):
super().show()
@ -519,13 +465,11 @@ class SearchScreen(QtGui.QWidget):
@staticmethod
def not_found(text):
mbox = QtGui.QMessageBox()
mbox.setText(QtGui.QApplication.translate("MainWindow",
'Text "{}" was not found'.format(text),
None,
QtGui.QApplication.UnicodeUTF8))
mbox.setWindowTitle(QtGui.QApplication.translate("MainWindow",
'Not found',
None,
QtGui.QApplication.UnicodeUTF8))
mbox = QtWidgets.QMessageBox()
mbox_text = QtWidgets.QApplication.translate("MainWindow",
'Text "{}" was not found')
mbox.setText(mbox_text.format(text))
mbox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow",
'Not found'))
mbox.exec_()

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,9 @@ MESSAGE_TYPE = {
'ACTION': 1,
'FILE_TRANSFER': 2,
'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
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):
"""
Message with info about file transfer

1
toxygen/nodes.json Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,4 @@
try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
from PyQt5 import QtCore, QtWidgets
from util import curr_directory
import wave
import pyaudio
@ -23,16 +20,16 @@ def tray_notification(title, text, tray, window):
:param tray: ref to tray icon
:param window: main window
"""
if QtGui.QSystemTrayIcon.isSystemTrayAvailable():
if QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
if len(text) > 30:
text = text[:27] + '...'
tray.showMessage(title, text, QtGui.QSystemTrayIcon.NoIcon, 3000)
QtGui.QApplication.alert(window, 0)
tray.showMessage(title, text, QtWidgets.QSystemTrayIcon.NoIcon, 3000)
QtWidgets.QApplication.alert(window, 0)
def message_clicked():
window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
window.activateWindow()
tray.connect(tray, QtCore.SIGNAL("messageClicked()"), message_clicked)
tray.messageClicked.connect(message_clicked)
class AudioFile:

View File

@ -1,8 +1,5 @@
from widgets import CenteredWidget, LineEdit
try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
from PyQt5 import QtCore, QtWidgets
class PasswordArea(LineEdit):
@ -10,7 +7,7 @@ class PasswordArea(LineEdit):
def __init__(self, parent):
super(PasswordArea, self).__init__(parent)
self.parent = parent
self.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
self.setEchoMode(QtWidgets.QLineEdit.Password)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Return:
@ -31,18 +28,18 @@ class PasswordScreenBase(CenteredWidget):
self.setMinimumSize(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.password = PasswordArea(self)
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.setText('OK')
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.setStyleSheet('QLabel { color: #F70D1A; }')
self.warning.setVisible(False)
@ -61,9 +58,9 @@ class PasswordScreenBase(CenteredWidget):
super(PasswordScreenBase, self).keyPressEvent(event)
def retranslateUi(self):
self.setWindowTitle(QtGui.QApplication.translate("pass", "Enter password", None, QtGui.QApplication.UnicodeUTF8))
self.enter_pass.setText(QtGui.QApplication.translate("pass", "Password:", None, QtGui.QApplication.UnicodeUTF8))
self.warning.setText(QtGui.QApplication.translate("pass", "Incorrect password", None, QtGui.QApplication.UnicodeUTF8))
self.setWindowTitle(QtWidgets.QApplication.translate("pass", "Enter password"))
self.enter_pass.setText(QtWidgets.QApplication.translate("pass", "Password:"))
self.warning.setText(QtWidgets.QApplication.translate("pass", "Incorrect password"))
class PasswordScreen(PasswordScreenBase):
@ -116,37 +113,32 @@ class SetProfilePasswordScreen(CenteredWidget):
self.setMaximumSize(QtCore.QSize(700, 200))
self.password = LineEdit(self)
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.setGeometry(QtCore.QRect(40, 50, 300, 30))
self.confirm_password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
self.set_password = QtGui.QPushButton(self)
self.confirm_password.setEchoMode(QtWidgets.QLineEdit.Password)
self.set_password = QtWidgets.QPushButton(self)
self.set_password.setGeometry(QtCore.QRect(40, 100, 300, 30))
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.setVisible(False)
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.setStyleSheet('QLabel { color: #BC1C1C; }')
def retranslateUi(self):
self.setWindowTitle(QtGui.QApplication.translate("PasswordScreen", "Profile password", None,
QtGui.QApplication.UnicodeUTF8))
self.setWindowTitle(QtWidgets.QApplication.translate("PasswordScreen", "Profile password"))
self.password.setPlaceholderText(
QtGui.QApplication.translate("PasswordScreen", "Password (at least 8 symbols)", None,
QtGui.QApplication.UnicodeUTF8))
QtWidgets.QApplication.translate("PasswordScreen", "Password (at least 8 symbols)"))
self.confirm_password.setPlaceholderText(
QtGui.QApplication.translate("PasswordScreen", "Confirm password", None,
QtGui.QApplication.UnicodeUTF8))
QtWidgets.QApplication.translate("PasswordScreen", "Confirm password"))
self.set_password.setText(
QtGui.QApplication.translate("PasswordScreen", "Set password", None, QtGui.QApplication.UnicodeUTF8))
self.not_match.setText(QtGui.QApplication.translate("PasswordScreen", "Passwords do not match", None,
QtGui.QApplication.UnicodeUTF8))
QtWidgets.QApplication.translate("PasswordScreen", "Set password"))
self.not_match.setText(QtWidgets.QApplication.translate("PasswordScreen", "Passwords do not match"))
self.warning.setText(
QtGui.QApplication.translate("PasswordScreen", "There is no way to recover lost passwords", None,
QtGui.QApplication.UnicodeUTF8))
QtWidgets.QApplication.translate("PasswordScreen", "There is no way to recover lost passwords"))
def new_password(self):
if self.password.text() == self.confirm_password.text():
@ -155,10 +147,8 @@ class SetProfilePasswordScreen(CenteredWidget):
self.close()
else:
self.not_match.setText(
QtGui.QApplication.translate("PasswordScreen", "Password must be at least 8 symbols", None,
QtGui.QApplication.UnicodeUTF8))
QtWidgets.QApplication.translate("PasswordScreen", "Password must be at least 8 symbols"))
self.not_match.setVisible(True)
else:
self.not_match.setText(QtGui.QApplication.translate("PasswordScreen", "Passwords do not match", None,
QtGui.QApplication.UnicodeUTF8))
self.not_match.setText(QtWidgets.QApplication.translate("PasswordScreen", "Passwords do not match"))
self.not_match.setVisible(True)

View File

@ -51,7 +51,8 @@ class PluginLoader(util.Singleton):
continue
for elem in dir(module):
obj = getattr(module, elem)
if inspect.isclass(obj) and hasattr(obj, 'is_plugin') and obj.is_plugin: # looking for plugin class in module
# looking for plugin class in module
if inspect.isclass(obj) and hasattr(obj, 'is_plugin') and obj.is_plugin:
print('Plugin', elem)
try: # create instance of plugin class
inst = obj(self._tox, self._profile, self._settings, self._encr)
@ -96,10 +97,13 @@ class PluginLoader(util.Singleton):
"""
result = []
for data in self._plugins.values():
result.append([data[0].get_name(), # plugin full name
data[1], # is enabled
data[0].get_description(), # plugin description
data[0].get_short_name()]) # key - short unique name
try:
result.append([data[0].get_name(), # plugin full name
data[1], # is enabled
data[0].get_description(), # plugin description
data[0].get_short_name()]) # key - short unique name
except:
continue
return result
def plugin_window(self, key):
@ -165,3 +169,8 @@ class PluginLoader(util.Singleton):
if self._plugins[key][1]:
self._plugins[key][0].close()
del self._plugins[key]
def reload(self):
print('Reloading plugins')
self.stop()
self.load()

View File

@ -1,8 +1,5 @@
import os
try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
from PyQt5 import QtCore, QtWidgets
MAX_SHORT_NAME_LENGTH = 5
@ -137,10 +134,10 @@ class PluginSuperClass:
:param command: string with command
"""
if command == 'help':
msgbox = QtGui.QMessageBox()
title = QtGui.QApplication.translate("PluginWindow", "List of commands for plugin {}", None, QtGui.QApplication.UnicodeUTF8)
msgbox = QtWidgets.QMessageBox()
title = QtWidgets.QApplication.translate("PluginWindow", "List of commands for plugin {}")
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_()
# -----------------------------------------------------------------------------------------------------------------
@ -151,7 +148,7 @@ class PluginSuperClass:
"""
This method loads translations for GUI
"""
app = QtGui.QApplication.instance()
app = QtWidgets.QApplication.instance()
langs = self._settings.supported_languages()
curr_lang = self._settings['language']
if curr_lang in langs:

View File

@ -1,8 +1,5 @@
from list_items import *
try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
from PyQt5 import QtGui, QtWidgets
from friend import *
from settings import *
from toxcore_enums_and_consts import *
@ -17,6 +14,10 @@ import avwidgets
import plugin_support
import basecontact
import items_factory
import cv2
import threading
from group_chat import *
import re
class Profile(basecontact.BaseContact, Singleton):
@ -39,8 +40,10 @@ class Profile(basecontact.BaseContact, Singleton):
self._tox = tox
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_widgets = {} # dict of incoming call widgets
self._incoming_calls = set()
self._load_history = True
self._waiting_for_reconnection = False
self._factory = items_factory.ItemsFactory(self._screen.friends_list, self._messages)
settings = Settings.get_instance()
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.set_alias(alias)
self._contacts.append(friend)
if len(self._contacts):
self.set_active(0)
self.filtration_and_sorting(self._sorting)
# -----------------------------------------------------------------------------------------------------------------
@ -86,8 +91,9 @@ class Profile(basecontact.BaseContact, Singleton):
super(Profile, self).set_status(status)
if status is not None:
self._tox.self_set_status(status)
else:
QtCore.QTimer.singleShot(45000, self.reconnect)
elif not self._waiting_for_reconnection:
self._waiting_for_reconnection = True
QtCore.QTimer.singleShot(50000, self.reconnect)
def set_name(self, value):
if self.name == value:
@ -95,8 +101,7 @@ class Profile(basecontact.BaseContact, Singleton):
tmp = self.name
super(Profile, self).set_name(value.encode('utf-8'))
self._tox.self_set_name(self._name.encode('utf-8'))
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, value)
for friend in self._contacts:
friend.append_message(InfoMessage(message, time.time()))
@ -128,6 +133,7 @@ class Profile(basecontact.BaseContact, Singleton):
filter_str = filter_str.lower()
settings = Settings.get_instance()
number = self.get_active_number()
is_friend = self.is_active_a_friend()
if sorting > 1:
if sorting & 2:
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
settings['sorting'] = self._sorting
settings.save()
self.set_active_by_number(number)
self.set_active_by_number_and_type(number, is_friend)
def update_filtration(self):
"""
@ -176,7 +182,7 @@ class Profile(basecontact.BaseContact, Singleton):
# -----------------------------------------------------------------------------------------------------------------
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):
if num < 0 or num >= len(self._contacts):
@ -203,6 +209,7 @@ class Profile(basecontact.BaseContact, Singleton):
if value == -1: # all friends were deleted
self._screen.account_name.setText('')
self._screen.account_status.setText('')
self._screen.account_status.setToolTip('')
self._active_friend = -1
self._screen.account_avatar.setHidden(True)
self._messages.clear()
@ -218,6 +225,7 @@ class Profile(basecontact.BaseContact, Singleton):
except:
pass
friend = self._contacts[value]
friend.remove_invalid_unsent_files()
if self._active_friend != value:
self._screen.messageEdit.setPlainText(friend.curr_text)
self._active_friend = value
@ -243,18 +251,21 @@ class Profile(basecontact.BaseContact, Singleton):
if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer
try:
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()
except:
print('Incoming not started transfer - no info found')
elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline
self.create_inline_item(message.get_data())
else: # info message
elif message.get_type() < 5: # info message
data = message.get_data()
self.create_message_item(data[0],
data[2],
'',
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._load_history = True
if value in self._call:
@ -268,7 +279,11 @@ class Profile(basecontact.BaseContact, Singleton):
self._screen.account_name.setText(friend.name)
self._screen.account_status.setText(friend.status_message)
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(friend.tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
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])
if not os.path.isfile(avatar_path): # load default image
avatar_path = curr_directory() + '/images/avatar.png'
os.chdir(os.path.dirname(avatar_path))
@ -280,9 +295,10 @@ class Profile(basecontact.BaseContact, Singleton):
log('Error in set active: ' + str(ex))
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)):
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
break
@ -309,7 +325,7 @@ class Profile(basecontact.BaseContact, Singleton):
friend.set_name(name)
name = str(name, 'utf-8')
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)
friend.append_message(InfoMessage(message, time.time()))
friend.actions = True
@ -328,6 +344,7 @@ class Profile(basecontact.BaseContact, Singleton):
def send_files(self, friend_number):
friend = self.get_friend_by_number(friend_number)
friend.remove_invalid_unsent_files()
files = friend.get_unsent_files()
try:
for fl in files:
@ -344,7 +361,7 @@ class Profile(basecontact.BaseContact, Singleton):
elif data[1] == friend_number and not data[2]:
self.send_file(data[0], friend_number, True, 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()
except Exception as ex:
print('Exception in file sending: ' + str(ex))
@ -386,7 +403,7 @@ class Profile(basecontact.BaseContact, Singleton):
"""
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)
# -----------------------------------------------------------------------------------------------------------------
@ -409,25 +426,25 @@ class Profile(basecontact.BaseContact, Singleton):
for message in messages:
self.split_and_send(friend_number, message.get_data()[-1], message.get_data()[0].encode('utf-8'))
friend.inc_receipts()
except:
pass
except Exception as ex:
log('Sending pending messages failed with ' + str(ex))
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 message_type: type of message
:param message: message text
"""
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]
if ' ' in last_part:
index = last_part.index(' ')
elif ',' in last_part:
index = last_part.index(',')
elif '.' in last_part:
index = last_part.index('.')
if b' ' in last_part:
index = last_part.index(b' ')
elif b',' in last_part:
index = last_part.index(b',')
elif b'.' in last_part:
index = last_part.index(b'.')
else:
index = TOX_MAX_MESSAGE_LENGTH - 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: 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()
self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type)
self._messages.scrollToBottom()
@ -462,6 +479,9 @@ class Profile(basecontact.BaseContact, Singleton):
:param text: message text
:param friend_num: num of friend
"""
if not self.is_active_a_friend():
self.send_gc_message(text)
return
if friend_num is None:
friend_num = self.get_active_number()
if text.startswith('/plugin '):
@ -477,8 +497,8 @@ class Profile(basecontact.BaseContact, Singleton):
friend.inc_receipts()
if friend.status is not None:
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._screen.messageEdit.clear()
self._messages.scrollToBottom()
@ -501,7 +521,7 @@ class Profile(basecontact.BaseContact, Singleton):
s = Settings.get_instance()
if hasattr(self, '_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):
self._history.add_friend_to_db(friend.tox_id)
if not s['save_unsent_only']:
@ -562,7 +582,7 @@ class Profile(basecontact.BaseContact, Singleton):
if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer
try:
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()
except:
print('Incoming not started transfer - no info found')
@ -633,6 +653,16 @@ class Profile(basecontact.BaseContact, Singleton):
return self._factory.message_item(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'],
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):
data = list(tm.get_data())
data[3] = self.get_friend_by_number(data[4]).name if data[3] else self._name
@ -659,18 +689,16 @@ class Profile(basecontact.BaseContact, Singleton):
"""
friend = self._contacts[num]
name = friend.name
dialog = QtGui.QApplication.translate('MainWindow',
"Enter new alias for friend {} or leave empty to use friend's name:",
None, QtGui.QApplication.UnicodeUTF8)
dialog = QtWidgets.QApplication.translate('MainWindow',
"Enter new alias for friend {} or leave empty to use friend's name:")
dialog = dialog.format(name)
title = QtGui.QApplication.translate('MainWindow',
'Set alias',
None, QtGui.QApplication.UnicodeUTF8)
text, ok = QtGui.QInputDialog.getText(None,
title,
dialog,
QtGui.QLineEdit.Normal,
name)
title = QtWidgets.QApplication.translate('MainWindow',
'Set alias')
text, ok = QtWidgets.QInputDialog.getText(None,
title,
dialog,
QtWidgets.QLineEdit.Normal,
name)
if ok:
settings = Settings.get_instance()
aliases = settings['friends_aliases']
@ -691,7 +719,7 @@ class Profile(basecontact.BaseContact, Singleton):
except:
pass
settings.save()
if num == self.get_active_number():
if num == self.get_active_number() and self.is_active_a_friend():
self.update()
def friend_public_key(self, num):
@ -794,9 +822,9 @@ class Profile(basecontact.BaseContact, Singleton):
raise Exception('TOX DNS lookup failed')
if len(tox_id) == TOX_PUBLIC_KEY_SIZE * 2: # public key
self.add_friend(tox_id)
msgBox = QtGui.QMessageBox()
msgBox.setWindowTitle(QtGui.QApplication.translate("MainWindow", "Friend added", None, QtGui.QApplication.UnicodeUTF8))
text = (QtGui.QApplication.translate("MainWindow", 'Friend added without sending friend request', None, QtGui.QApplication.UnicodeUTF8))
msgBox = QtWidgets.QMessageBox()
msgBox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "Friend added"))
text = (QtWidgets.QApplication.translate("MainWindow", 'Friend added without sending friend request'))
msgBox.setText(text)
msgBox.exec_()
else:
@ -822,11 +850,11 @@ class Profile(basecontact.BaseContact, Singleton):
:param message: message
"""
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)
fr_req = QtGui.QApplication.translate('MainWindow', 'Friend request', None, QtGui.QApplication.UnicodeUTF8)
reply = QtGui.QMessageBox.question(None, fr_req, info, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes: # accepted
fr_req = QtWidgets.QApplication.translate('MainWindow', 'Friend request')
reply = QtWidgets.QMessageBox.question(None, fr_req, info, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes: # accepted
self.add_friend(tox_id)
data = self._tox.get_savedata()
ProfileHelper.get_instance().save_profile(data)
@ -855,12 +883,14 @@ class Profile(basecontact.BaseContact, Singleton):
self.update_filtration()
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):
self._waiting_for_reconnection = True
self.reset(self._screen.reset)
QtCore.QTimer.singleShot(45000, self.reconnect)
QtCore.QTimer.singleShot(50000, self.reconnect)
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)
for i in range(len(self._contacts)):
del self._contacts[0]
@ -933,10 +963,10 @@ class Profile(basecontact.BaseContact, Singleton):
friend_number,
file_number)
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)
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()
else:
friend.actions = True
@ -964,14 +994,15 @@ class Profile(basecontact.BaseContact, Singleton):
else:
if not already_cancelled:
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
if tmp >= 0:
self._messages.itemWidget(self._messages.item(tmp)).update(TOX_FILE_TRANSFER_STATE['CANCELLED'],
0, -1)
self._messages.itemWidget(
self._messages.item(tmp)).update_transfer_state(TOX_FILE_TRANSFER_STATE['CANCELLED'],
0, -1)
def cancel_not_started_transfer(self, time):
self.get_curr_friend().delete_one_unsent_file(time)
def cancel_not_started_transfer(self, cancel_time):
self.get_curr_friend().delete_one_unsent_file(cancel_time)
self.update()
def pause_transfer(self, friend_number, file_number, by_friend=False):
@ -1025,7 +1056,7 @@ class Profile(basecontact.BaseContact, Singleton):
self._file_transfers[(friend_number, file_number)] = rt
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['RESUME'])
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,
TOX_FILE_TRANSFER_STATE['RUNNING'])
@ -1064,7 +1095,7 @@ class Profile(basecontact.BaseContact, Singleton):
st.get_file_number())
item = self.create_file_transfer_item(tm)
friend.append_message(tm)
st.set_state_changed_handler(item.update)
st.set_state_changed_handler(item.update_transfer_state)
self._messages.scrollToBottom()
def send_file(self, path, number=None, is_resend=False, file_id=None):
@ -1097,7 +1128,7 @@ class Profile(basecontact.BaseContact, Singleton):
st.get_file_number())
if friend_number == self.get_active_number():
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._contacts[friend_number].append_message(tm)
@ -1113,13 +1144,12 @@ class Profile(basecontact.BaseContact, Singleton):
"""
self._file_transfers[(friend_number, file_number)].send_chunk(position, size)
@QtCore.Slot(int, int)
def transfer_finished(self, friend_number, file_number):
transfer = self._file_transfers[(friend_number, file_number)]
t = type(transfer)
if t is ReceiveAvatar:
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)
elif t is ReceiveToBuffer or (t is SendFromBuffer and Settings.get_instance()['allow_inline']): # inline image
print('inline')
@ -1127,10 +1157,10 @@ class Profile(basecontact.BaseContact, Singleton):
i = self.get_friend_by_number(friend_number).update_transfer_data(file_number,
TOX_FILE_TRANSFER_STATE['FINISHED'],
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()
if count + i + 1 >= 0:
elem = QtGui.QListWidgetItem()
elem = QtWidgets.QListWidgetItem()
item = InlineImageItem(transfer.get_data(), self._messages.width(), elem)
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
self._messages.insertItem(count + i + 1, elem)
@ -1169,7 +1199,7 @@ class Profile(basecontact.BaseContact, Singleton):
ra.set_transfer_finished_handler(self.transfer_finished)
else:
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)
def reset_avatar(self):
@ -1194,17 +1224,17 @@ class Profile(basecontact.BaseContact, Singleton):
def call_click(self, audio=True, video=False):
"""User clicked audio button in main window"""
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 not Settings.get_instance().audio['enabled']:
return
self._call(num, audio, video)
self._screen.active_call()
if video:
text = QtGui.QApplication.translate("incoming_call", "Outgoing video call", None,
QtGui.QApplication.UnicodeUTF8)
text = QtWidgets.QApplication.translate("incoming_call", "Outgoing video call")
else:
text = QtGui.QApplication.translate("incoming_call", "Outgoing audio call", None,
QtGui.QApplication.UnicodeUTF8)
text = QtWidgets.QApplication.translate("incoming_call", "Outgoing audio call")
self.get_curr_friend().append_message(InfoMessage(text, time.time()))
self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
self._messages.scrollToBottom()
@ -1213,17 +1243,15 @@ class Profile(basecontact.BaseContact, Singleton):
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']:
return
friend = self.get_friend_by_number(friend_number)
if video:
text = QtGui.QApplication.translate("incoming_call", "Incoming video call", None,
QtGui.QApplication.UnicodeUTF8)
text = QtWidgets.QApplication.translate("incoming_call", "Incoming video call")
else:
text = QtGui.QApplication.translate("incoming_call", "Incoming audio call", None,
QtGui.QApplication.UnicodeUTF8)
text = QtWidgets.QApplication.translate("incoming_call", "Incoming audio call")
friend.append_message(InfoMessage(text, time.time()))
self._incoming_calls.add(friend_number)
if friend_number == self.get_active_number():
@ -1232,10 +1260,9 @@ class Profile(basecontact.BaseContact, Singleton):
self._messages.scrollToBottom()
else:
friend.actions = True
# TODO: dict of widgets
self._call_widget = avwidgets.IncomingCallWidget(friend_number, text, friend.name)
self._call_widget.set_pixmap(friend.get_pixmap())
self._call_widget.show()
self._call_widgets[friend_number] = avwidgets.IncomingCallWidget(friend_number, text, friend.name)
self._call_widgets[friend_number].set_pixmap(friend.get_pixmap())
self._call_widgets[friend_number].show()
def accept_call(self, friend_number, audio, video):
"""
@ -1245,8 +1272,7 @@ class Profile(basecontact.BaseContact, Singleton):
self._screen.active_call()
if friend_number in self._incoming_calls:
self._incoming_calls.remove(friend_number)
if hasattr(self, '_call_widget'):
del self._call_widget
del self._call_widgets[friend_number]
def stop_call(self, friend_number, by_friend):
"""
@ -1254,20 +1280,151 @@ class Profile(basecontact.BaseContact, Singleton):
"""
if friend_number in self._incoming_calls:
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:
text = QtGui.QApplication.translate("incoming_call", "Call finished", None, QtGui.QApplication.UnicodeUTF8)
text = QtWidgets.QApplication.translate("incoming_call", "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
if hasattr(self, '_call_widget'):
self._call_widget.close()
del self._call_widget
self._call_widget[friend_number].close()
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.append_message(InfoMessage(text, time.time()))
if friend_number == self.get_active_number():
self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
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):
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):
"""

22
toxygen/screen_sharing.py Normal file
View 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

View File

@ -35,6 +35,7 @@ class Settings(dict, Singleton):
smileys.SmileyLoader(self)
self.locked = False
self.closing = False
self.unlockScreen = False
p = pyaudio.PyAudio()
input_devices = output_devices = 0
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,
'output': p.get_default_output_device_info()['index'] if output_devices else -1,
'enabled': input_devices and output_devices}
self.video = {'device': -1, 'width': 640, 'height': 480, 'x': 0, 'y': 0}
@staticmethod
def get_auto_profile():
p = Settings.get_default_path() + 'toxygen.json'
p = Settings.get_global_settings_path()
if os.path.isfile(p):
with open(p) as fl:
data = fl.read()
auto = json.loads(data)
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 '', ''
@staticmethod
def set_auto_profile(path, name):
p = Settings.get_default_path() + 'toxygen.json'
p = Settings.get_global_settings_path()
if os.path.isfile(p):
with open(p) as fl:
data = fl.read()
@ -74,7 +79,7 @@ class Settings(dict, Singleton):
@staticmethod
def reset_auto_profile():
p = Settings.get_default_path() + 'toxygen.json'
p = Settings.get_global_settings_path()
if os.path.isfile(p):
with open(p) as fl:
data = fl.read()
@ -89,15 +94,8 @@ class Settings(dict, Singleton):
@staticmethod
def is_active_profile(path, name):
path = path + name + '.tox'
settings = Settings.get_default_path() + 'toxygen.json'
if os.path.isfile(settings):
with open(settings) as fl:
data = fl.read()
data = json.loads(data)
if 'active_profile' in data:
return path in data['active_profile']
return False
path = path + name + '.lock'
return os.path.isfile(path)
@staticmethod
def get_default_settings():
@ -105,7 +103,7 @@ class Settings(dict, Singleton):
Default profile settings
"""
return {
'theme': 'default',
'theme': 'dark',
'ipv6_enabled': True,
'udp_enabled': True,
'proxy_type': 0,
@ -146,15 +144,25 @@ class Settings(dict, Singleton):
'show_welcome_screen': True,
'close_to_tray': False,
'font': 'Times New Roman',
'update': 1
'update': 1,
'group_notifications': True,
'download_nodes_list': False
}
@staticmethod
def supported_languages():
return {
'English': 'en_EN',
'French': 'fr_FR',
'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):
@ -176,37 +184,19 @@ class Settings(dict, Singleton):
fl.write(text)
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):
with open(path) as fl:
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)
os.remove(path)
def set_active_profile(self):
"""
Mark current profile as active
"""
path = Settings.get_default_path() + 'toxygen.json'
if os.path.isfile(path):
with open(path) as fl:
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()
app_settings['active_profile'].append(str(profile_path + str(self.name) + '.tox'))
data = json.dumps(app_settings)
path = str(profile_path + str(self.name) + '.lock')
with open(path, 'w') as fl:
fl.write(data)
fl.write('active')
def export(self, path):
text = json.dumps(self)
@ -216,6 +206,10 @@ class Settings(dict, Singleton):
def update_path(self):
self.path = ProfileHelper.get_path() + self.name + '.json'
@staticmethod
def get_global_settings_path():
return curr_directory() + '/toxygen.json'
@staticmethod
def get_default_path():
if system() == 'Windows':

View File

@ -2,10 +2,7 @@ import util
import json
import os
from collections import OrderedDict
try:
from PySide import QtCore
except ImportError:
from PyQt4 import QtCore
from PyQt5 import QtCore
class SmileyLoader(util.Singleton):

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
try:
from PySide import QtCore
from PyQt5 import QtCore
except ImportError:
from PyQt4 import QtCore

View File

@ -41,6 +41,9 @@
<file>rc/radio_unchecked.png</file>
</qresource>
<qresource prefix="qdarkstyle">
<file>dark_style.qss</file>
</qresource>
<qresource prefix="defaultstyle">
<file>style.qss</file>
</qresource>
</RCC>

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
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 ctypes import *
from toxcore_enums_and_consts import *
from toxav import ToxAV
from libtox import LibToxCore
@ -92,6 +90,11 @@ class Tox:
self.file_recv_chunk_cb = None
self.friend_lossy_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)
@ -1510,3 +1513,89 @@ class Tox:
return result
elif tox_err_get_port == TOX_ERR_GET_PORT['NOT_BOUND']:
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)

View File

@ -2,10 +2,7 @@ import json
import urllib.request
from util import log
import settings
try:
from PySide import QtNetwork, QtCore
except:
from PyQt4 import QtNetwork, QtCore
from PyQt5 import QtNetwork, QtCore
def tox_dns(email):
@ -33,7 +30,8 @@ def tox_dns(email):
netman.setProxy(proxy)
for url in urls:
try:
request = QtNetwork.QNetworkRequest(url)
request = QtNetwork.QNetworkRequest()
request.setUrl(QtCore.QUrl(url))
request.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, "application/json")
reply = netman.post(request, bytes(json.dumps(data), 'utf-8'))

View File

@ -188,6 +188,17 @@ TOX_ERR_GET_PORT = {
'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_ADDRESS_SIZE = TOX_PUBLIC_KEY_SIZE + 6

View File

@ -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
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

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -3,10 +3,7 @@ import os
import settings
import platform
import urllib
try:
from PySide import QtNetwork, QtCore
except ImportError:
from PyQt4 import QtNetwork, QtCore
from PyQt5 import QtNetwork, QtCore
import subprocess
@ -60,7 +57,10 @@ def get_url(version):
def get_params(url, version):
if is_from_sources():
return ['python3', 'toxygen_updater.py', url, version]
if platform.system() == 'Windows':
return ['python', 'toxygen_updater.py', url, version]
else:
return ['python3', 'toxygen_updater.py', url, version]
elif platform.system() == 'Windows':
return [util.curr_directory() + '/toxygen_updater.exe', url, version]
else:
@ -90,7 +90,8 @@ def send_request(version):
netman.setProxy(proxy)
url = test_url(version)
try:
request = QtNetwork.QNetworkRequest(url)
request = QtNetwork.QNetworkRequest()
request.setUrl(QtCore.QUrl(url))
reply = netman.get(request)
while not reply.isFinished():
QtCore.QThread.msleep(1)

View File

@ -4,14 +4,32 @@ import shutil
import sys
import re
program_version = '0.2.7'
program_version = '0.4.1'
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):
with open(curr_directory() + '/logs.log', 'a') as fl:
fl.write(str(data) + '\n')
try:
with open(curr_directory() + '/logs.log', 'a') as fl:
fl.write(str(data) + '\n')
except:
pass
@cached
def curr_directory():
return os.path.dirname(os.path.realpath(__file__))
@ -46,9 +64,8 @@ def convert_time(t):
return '%02d:%02d' % (h, m)
@cached
def time_offset():
if hasattr(time_offset, 'offset'):
return time_offset.offset
hours = int(time.strftime('%H'))
minutes = int(time.strftime('%M'))
sec = int(time.time()) - time.timezone
@ -56,7 +73,6 @@ def time_offset():
h, m = divmod(m, 60)
d, h = divmod(h, 24)
result = hours * 60 + minutes - h * 60 - m
time_offset.offset = result
return result
@ -66,6 +82,7 @@ def append_slash(s):
return s
@cached
def is_64_bit():
return sys.maxsize > 2 ** 32

View File

@ -1,10 +1,7 @@
try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
from PyQt5 import QtCore, QtGui, QtWidgets
class DataLabel(QtGui.QLabel):
class DataLabel(QtWidgets.QLabel):
"""
Label with elided text
"""
@ -15,14 +12,14 @@ class DataLabel(QtGui.QLabel):
super().setText(text)
class ComboBox(QtGui.QComboBox):
class ComboBox(QtWidgets.QComboBox):
def __init__(self, *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):
super(CenteredWidget, self).__init__()
@ -30,12 +27,12 @@ class CenteredWidget(QtGui.QWidget):
def center(self):
qr = self.frameGeometry()
cp = QtGui.QDesktopWidget().availableGeometry().center()
cp = QtWidgets.QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
class LineEdit(QtGui.QLineEdit):
class LineEdit(QtWidgets.QLineEdit):
def __init__(self, parent=None):
super(LineEdit, self).__init__(parent)
@ -46,25 +43,27 @@ class LineEdit(QtGui.QLineEdit):
del menu
class QRightClickButton(QtGui.QPushButton):
class QRightClickButton(QtWidgets.QPushButton):
"""
Button with right click support
"""
rightClicked = QtCore.pyqtSignal()
def __init__(self, parent):
super(QRightClickButton, self).__init__(parent)
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.RightButton:
self.emit(QtCore.SIGNAL("rightClicked()"))
self.rightClicked.emit()
else:
super(QRightClickButton, self).mousePressEvent(event)
class RubberBand(QtGui.QRubberBand):
class RubberBand(QtWidgets.QRubberBand):
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.pen = QtGui.QPen(QtCore.Qt.blue, 4)
self.pen.setStyle(QtCore.Qt.SolidLine)
@ -78,6 +77,42 @@ class RubberBand(QtGui.QRubberBand):
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):
"""
:return translated menu
@ -86,29 +121,21 @@ def create_menu(menu):
text = action.text()
if 'Link Location' in text:
text = text.replace('Copy &Link Location',
QtGui.QApplication.translate("MainWindow", "Copy link location", None,
QtGui.QApplication.UnicodeUTF8))
QtWidgets.QApplication.translate("MainWindow", "Copy link location"))
elif '&Copy' in text:
text = text.replace('&Copy', QtGui.QApplication.translate("MainWindow", "Copy", None,
QtGui.QApplication.UnicodeUTF8))
text = text.replace('&Copy', QtWidgets.QApplication.translate("MainWindow", "Copy"))
elif 'All' in text:
text = text.replace('Select All', QtGui.QApplication.translate("MainWindow", "Select all", None,
QtGui.QApplication.UnicodeUTF8))
text = text.replace('Select All', QtWidgets.QApplication.translate("MainWindow", "Select all"))
elif 'Delete' in text:
text = text.replace('Delete', QtGui.QApplication.translate("MainWindow", "Delete", None,
QtGui.QApplication.UnicodeUTF8))
text = text.replace('Delete', QtWidgets.QApplication.translate("MainWindow", "Delete"))
elif '&Paste' in text:
text = text.replace('&Paste', QtGui.QApplication.translate("MainWindow", "Paste", None,
QtGui.QApplication.UnicodeUTF8))
text = text.replace('&Paste', QtWidgets.QApplication.translate("MainWindow", "Paste"))
elif 'Cu&t' in text:
text = text.replace('Cu&t', QtGui.QApplication.translate("MainWindow", "Cut", None,
QtGui.QApplication.UnicodeUTF8))
text = text.replace('Cu&t', QtWidgets.QApplication.translate("MainWindow", "Cut"))
elif '&Undo' in text:
text = text.replace('&Undo', QtGui.QApplication.translate("MainWindow", "Undo", None,
QtGui.QApplication.UnicodeUTF8))
text = text.replace('&Undo', QtWidgets.QApplication.translate("MainWindow", "Undo"))
elif '&Redo' in text:
text = text.replace('&Redo', QtGui.QApplication.translate("MainWindow", "Redo", None,
QtGui.QApplication.UnicodeUTF8))
text = text.replace('&Redo', QtWidgets.QApplication.translate("MainWindow", "Redo"))
else:
menu.removeAction(action)
continue
@ -124,12 +151,12 @@ class MultilineEdit(CenteredWidget):
self.setMinimumSize(QtCore.QSize(350, 200))
self.setMaximumSize(QtCore.QSize(350, 200))
self.setWindowTitle(title)
self.edit = QtGui.QTextEdit(self)
self.edit = QtWidgets.QTextEdit(self)
self.edit.setGeometry(QtCore.QRect(0, 0, 350, 150))
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.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.center()
self.save = save
@ -137,4 +164,3 @@ class MultilineEdit(CenteredWidget):
def button_click(self):
self.save(self.edit.toPlainText())
self.close()