Compare commits
131 Commits
Author | SHA1 | Date | |
---|---|---|---|
4d4fd21fe9 | |||
6cbacef95b | |||
7a817eb82a | |||
49fc253c19 | |||
df5a1a901a | |||
54a2da4670 | |||
361f1f0e29 | |||
9031a4a3e3 | |||
0a378c1682 | |||
8bc4613407 | |||
ec6c04a7df | |||
769119c795 | |||
d1e90c6aef | |||
6d705deb55 | |||
464fba23c5 | |||
c60808a7da | |||
a2273e8c27 | |||
d0e2f61d03 | |||
f8a7087779 | |||
01546f0047 | |||
d8dd16e865 | |||
19fb905554 | |||
3ef581bc5d | |||
f897c7ce8d | |||
ba390eda91 | |||
5fea3e918d | |||
7cc404ce52 | |||
8a502b4082 | |||
b83ea6be18 | |||
85554eacd1 | |||
8bbefff6c7 | |||
019165aeac | |||
0cfb8efefa | |||
b227ed627a | |||
f41b5e5c97 | |||
bc9ec04171 | |||
05e4184c5d | |||
3194099f59 | |||
1a9db79ca2 | |||
21cc5837cf | |||
150942446d | |||
508db0acea | |||
de7f3359b8 | |||
8b56184510 | |||
1d33d298c3 | |||
704344fae2 | |||
3511031aff | |||
481e48f495 | |||
889d3d8f9c | |||
9b4965d591 | |||
5bdbb28e31 | |||
6cafd14883 | |||
9d939e7439 | |||
9e7e9b9012 | |||
2c4301e4f0 | |||
dc6ec7a6e8 | |||
6ae419441b | |||
1bdccf6f40 | |||
e854516183 | |||
5477a7d548 | |||
9f87f3dc3e | |||
137195c8f2 | |||
202c5a14a5 | |||
e598d027eb | |||
52a5d248c7 | |||
b0389537a1 | |||
34dd74ad48 | |||
e5a228906d | |||
3a90865fd0 | |||
b807daa3ff | |||
a83cd65f79 | |||
476f074d6a | |||
821dce5f28 | |||
67e9c92c09 | |||
9f745d9795 | |||
c4843148e4 | |||
56d8fa1cad | |||
1e6201b3fa | |||
ecf045182a | |||
5367764fdc | |||
417729d666 | |||
f782b99402 | |||
4c6205cc39 | |||
fd722f4628 | |||
dfab0491a5 | |||
8025c6a638 | |||
006b3cd197 | |||
9fe9ba4743 | |||
97ce2b9ceb | |||
337601f2a1 | |||
42e0ec005b | |||
fb1caa244a | |||
0fd75c5517 | |||
d81e3e781b | |||
43d9a41dae | |||
1caf7cd63c | |||
14816588f1 | |||
47b710acdd | |||
3668088f3e | |||
9f702afcb8 | |||
18775ff4b2 | |||
a7431cadd1 | |||
adcc32fc49 | |||
61e7aad847 | |||
742d853b11 | |||
39fe859fe5 | |||
2a0895018a | |||
d1437b3445 | |||
59154d081f | |||
99e8691f0b | |||
b0e82dfd08 | |||
3a64121d72 | |||
99f31cc302 | |||
19de605b79 | |||
9e410254bf | |||
e970fbed80 | |||
9ed62d4414 | |||
9516723c7f | |||
52e6ace847 | |||
c7f50af25c | |||
7d8646b432 | |||
883a30f806 | |||
3bd7655203 | |||
fdfc74521b | |||
3db10ead6a | |||
546eb9f042 | |||
08ef8294df | |||
af5db43248 | |||
697a9efb51 | |||
6297da1c69 | |||
e78ba3942b |
5
.gitignore
vendored
@ -15,9 +15,12 @@ toxygen/libs
|
||||
toxygen/build
|
||||
toxygen/dist
|
||||
*.spec
|
||||
dist/
|
||||
dist
|
||||
toxygen/avatars
|
||||
toxygen/__pycache__
|
||||
/*.egg-info
|
||||
/*.egg
|
||||
html
|
||||
Toxygen.egg-info
|
||||
*.tox
|
||||
|
||||
|
45
.travis.yml
Normal file
@ -0,0 +1,45 @@
|
||||
language: python
|
||||
python:
|
||||
- "3.5"
|
||||
- "3.6"
|
||||
before_install:
|
||||
- sudo apt-get update
|
||||
- sudo apt-get install -y checkinstall build-essential
|
||||
- sudo apt-get install portaudio19-dev
|
||||
- sudo apt-get install libconfig-dev libvpx-dev check -qq
|
||||
install:
|
||||
- pip install sip
|
||||
- pip install pyqt5
|
||||
- pip install pyaudio
|
||||
- pip install opencv-python
|
||||
before_script:
|
||||
# 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
|
||||
- ./configure
|
||||
- make -j3
|
||||
- sudo make install
|
||||
- cd ..
|
||||
# Libsodium
|
||||
- git clone git://github.com/jedisct1/libsodium.git
|
||||
- cd libsodium
|
||||
- git checkout tags/1.0.3
|
||||
- ./autogen.sh
|
||||
- ./configure && make -j$(nproc)
|
||||
- sudo checkinstall --install --pkgname libsodium --pkgversion 1.0.0 --nodoc -y
|
||||
- sudo ldconfig
|
||||
- cd ..
|
||||
# Toxcore
|
||||
- git clone https://github.com/irungentoo/toxcore.git
|
||||
- cd toxcore
|
||||
- autoreconf -if
|
||||
- ./configure
|
||||
- make -j$(nproc)
|
||||
- sudo make install
|
||||
- echo '/usr/local/lib/' | sudo tee -a /etc/ld.so.conf.d/locallib.conf
|
||||
- sudo ldconfig
|
||||
- cd ..
|
||||
script:
|
||||
- py.test tests/travis.py
|
||||
- py.test tests/tests.py
|
40
README.md
@ -1,20 +1,24 @@
|
||||
# Toxygen
|
||||
Toxygen is cross-platform [Tox](https://tox.chat/) client written in Python3
|
||||
|
||||
[](https://github.com/xveduk/toxygen/releases/latest)
|
||||
[](https://github.com/xveduk/toxygen/issues)
|
||||
[](https://raw.githubusercontent.com/xveduk/toxygen/master/LICENSE.md)
|
||||
Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pure Python3.
|
||||
|
||||
### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md)
|
||||
[](https://github.com/toxygen-project/toxygen/releases/latest)
|
||||
[](https://github.com/toxygen-project/toxygen/stargazers)
|
||||
[](https://github.com/toxygen-project/toxygen/issues)
|
||||
[](https://raw.githubusercontent.com/toxygen-project/toxygen/master/LICENSE.md)
|
||||
[](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) - [Updater](https://github.com/toxygen-project/toxygen_updater)
|
||||
|
||||
### Supported OS:
|
||||
- Windows
|
||||
- Linux
|
||||
|
||||
###Features
|
||||

|
||||
|
||||
### Features:
|
||||
|
||||
- [x] 1v1 messages
|
||||
- [x] File transfers
|
||||
- [x] Audio
|
||||
- [x] Audio calls
|
||||
- [x] Plugins support
|
||||
- [x] Chat history
|
||||
- [x] Emoticons
|
||||
@ -38,23 +42,25 @@ Toxygen is cross-platform [Tox](https://tox.chat/) client written in Python3
|
||||
- [x] Changing nospam
|
||||
- [x] File resuming
|
||||
- [x] Read receipts
|
||||
- [ ] Video
|
||||
- [ ] Video calls
|
||||
- [ ] Desktop sharing
|
||||
- [ ] Group chats
|
||||
|
||||
###Downloads
|
||||
[Releases](https://github.com/xveduk/toxygen/releases)
|
||||
### Downloads
|
||||
[Releases](https://github.com/toxygen-project/toxygen/releases)
|
||||
|
||||
[Download last stable version](https://github.com/xveduk/toxygen/archive/master.zip)
|
||||
[Download last stable version](https://github.com/toxygen-project/toxygen/archive/master.zip)
|
||||
|
||||
[Download develop version](https://github.com/xveduk/toxygen/archive/develop.zip)
|
||||
[Download develop version](https://github.com/toxygen-project/toxygen/archive/develop.zip)
|
||||
|
||||
###Screenshots
|
||||
### Screenshots
|
||||
*Toxygen on Ubuntu and Windows*
|
||||

|
||||

|
||||
|
||||
|
||||
###Docs
|
||||
### Docs
|
||||
[Check /docs/ for more info](/docs/)
|
||||
|
||||
Also visit [pythonhosted.org/Toxygen/](http://pythonhosted.org/Toxygen/)
|
||||
|
||||
[Wiki](https://wiki.tox.chat/clients/toxygen)
|
||||
|
@ -1,10 +1,11 @@
|
||||
#Compile Toxygen
|
||||
# Compile Toxygen
|
||||
|
||||
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/ (and /libs/libtox.dll, /libs/libsodium.a on Windows) to /dist/main/
|
||||
Don't forget to copy /images/, /sounds/, /translations/, /styles/, /smileys/, /stickers/, /plugins/ (and /libs/libtox.dll, /libs/libsodium.a on Windows) to /dist/main/
|
||||
|
5
docs/contact.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Contact us:
|
||||
|
||||
1) Using GitHub - open issue
|
||||
|
||||
2) Use Toxygen Tox Group - add bot kalina@toxme.io (or 12EDB939AA529641CE53830B518D6EB30241868EE0E5023C46A372363CAEC91C2C948AEFE4EB)
|
@ -4,15 +4,15 @@ Help us find all bugs in Toxygen! Please provide following info:
|
||||
|
||||
- OS
|
||||
- Toxygen version
|
||||
- Toxygen executable info - .py or precompiled binary
|
||||
- Toxygen executable info - .py or precompiled binary, how was it installed in system
|
||||
- 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
|
||||
|
||||
Developer? Feel free to open pull request. Our dev team is small so we glad to get help.
|
||||
Don't know what to do? Improve UI, fix [issues](https://github.com/xveduk/toxygen/issues) or implement features from our TODO list.
|
||||
Don't know what to do? Improve UI, fix [issues](https://github.com/toxygen-project/toxygen/issues) or implement features from our TODO list.
|
||||
You can find our TODO's in code, issues list and [here](/README.md). Also you can implement [plugins](/docs/plugins.md) for Toxygen.
|
||||
|
||||
#Translations
|
||||
|
@ -1,9 +1,9 @@
|
||||
# How to install Toxygen
|
||||
|
||||
## Use precompiled binary:
|
||||
[Check our releases page](https://github.com/xveduk/toxygen/releases)
|
||||
[Check our releases page](https://github.com/toxygen-project/toxygen/releases)
|
||||
|
||||
##Using pip3
|
||||
## Using pip3
|
||||
|
||||
### Windows
|
||||
|
||||
@ -21,17 +21,32 @@ Run app using ``toxygen`` command.
|
||||
``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.
|
||||
|
||||
## Packages
|
||||
|
||||
Arch Linux: [AUR](https://aur.archlinux.org/packages/toxygen-git/)
|
||||
|
||||
Debian/Ubuntu: [tox.chat](https://tox.chat/download.html#gnulinux)
|
||||
|
||||
## From source code (recommended for developers)
|
||||
|
||||
### 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) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
|
||||
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 \src\main.py.
|
||||
7. Run \toxygen\main.py.
|
||||
|
||||
Optional: install toxygen using setup.py: ``python3.4 setup.py install``
|
||||
|
||||
@ -47,7 +62,7 @@ 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).
|
||||
2. Install PySide: ``sudo apt-get install python3-pyside`` or install [PyQt4](https://riverbankcomputing.com/software/pyqt/download) (``sudo apt-get install python3-pyqt4``).
|
||||
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``)
|
||||
@ -56,7 +71,18 @@ Optional: install toxygen using setup.py: ``python3.4 setup.py install``
|
||||
7. Run app:
|
||||
``python3.4 main.py``
|
||||
|
||||
Optional: install toxygen using setup.py: ``python3 setup.py install``
|
||||
Optional: install toxygen using setup.py: ``python3.4 setup.py install``
|
||||
|
||||
## Compile Toxygen
|
||||
Check [compile.md](/docs/compile.md) for more info
|
||||
### OS X
|
||||
|
||||
1. [Download and install latest Python 3.4](https://www.python.org/downloads/mac-osx/)
|
||||
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4#installing-pyside-on-a-mac-os-x-system) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
|
||||
3. Install PortAudio:
|
||||
``brew install portaudio``
|
||||
4. Install PyAudio: ``pip3 install pyaudio``
|
||||
5. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system
|
||||
6. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
|
||||
7. Unpack archive
|
||||
8. Run \toxygen\main.py.
|
||||
|
||||
Optional: install toxygen using setup.py: ``python3 setup.py install``
|
||||
|
BIN
docs/os.png
Executable file
After Width: | Height: | Size: 28 KiB |
@ -1,4 +1,4 @@
|
||||
#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.
|
||||
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.
|
||||
@ -12,12 +12,13 @@ All plugin's data should be stored in following structure:
|
||||
|---plugin_short_name.py
|
||||
|---/plugin_short_name/
|
||||
|---settings.json
|
||||
|---readme.txt
|
||||
|---logs.txt
|
||||
|---other_files
|
||||
```
|
||||
|
||||
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.
|
||||
@ -50,7 +51,7 @@ Exceptions:
|
||||
|
||||
Plugin's methods MUST NOT raise exceptions.
|
||||
|
||||
#Examples
|
||||
# Examples
|
||||
|
||||
You can find examples in [official repo](https://github.com/ingvar1995/toxygen_plugins)
|
||||
You can find examples in [official repo](https://github.com/toxygen-project/toxygen_plugins)
|
||||
|
||||
|
@ -1,22 +1,22 @@
|
||||
#Plugins
|
||||
# Plugins
|
||||
|
||||
Toxygen is the first [Tox](https://tox.chat/) client with plugins support. Plugin is Python 3.4 module (.py file) and directory with plugin's data which provide some additional functionality.
|
||||
|
||||
#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)
|
@ -10,4 +10,4 @@ Animated smileys (.gif) are supported too.
|
||||
|
||||
Sticker is inline image. If you want to create your own smiley 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
|
||||
|
43
setup.py
@ -3,14 +3,25 @@ from setuptools.command.install import install
|
||||
from platform import system
|
||||
from subprocess import call
|
||||
from toxygen.util import program_version
|
||||
import sys
|
||||
|
||||
|
||||
version = program_version + '.0'
|
||||
|
||||
MODULES = ['PyAudio']
|
||||
MODULES = ['numpy', 'PyQt5']
|
||||
|
||||
if system() in ('Windows', 'Darwin'):
|
||||
MODULES.append('PyAudio')
|
||||
else:
|
||||
try:
|
||||
import pyaudio
|
||||
except ImportError:
|
||||
MODULES.append('PyAudio')
|
||||
|
||||
DEP_LINKS = []
|
||||
|
||||
if system() == 'Windows':
|
||||
MODULES.append('PySide')
|
||||
DEP_LINKS = [] # TODO: add opencv.whl
|
||||
|
||||
|
||||
class InstallScript(install):
|
||||
@ -18,29 +29,43 @@ class InstallScript(install):
|
||||
|
||||
def run(self):
|
||||
install.run(self)
|
||||
OS = system()
|
||||
if OS == 'Windows':
|
||||
try:
|
||||
if system() == 'Windows':
|
||||
call(["toxygen", "--configure"])
|
||||
elif OS == 'Linux':
|
||||
else:
|
||||
call(["toxygen", "--clean"])
|
||||
except:
|
||||
try:
|
||||
params = list(filter(lambda x: x.startswith('--prefix='), sys.argv))
|
||||
if params:
|
||||
path = params[0][len('--prefix='):]
|
||||
if path[-1] not in ('/', '\\'):
|
||||
path += '/'
|
||||
path += 'bin/toxygen'
|
||||
if system() == 'Windows':
|
||||
call([path, "--configure"])
|
||||
else:
|
||||
call([path, "--clean"])
|
||||
except:
|
||||
pass
|
||||
|
||||
setup(name='Toxygen',
|
||||
version=version,
|
||||
description='Toxygen - Tox client',
|
||||
long_description='Toxygen is powerful Tox client written in Python3',
|
||||
url='https://github.com/xveduk/toxygen/',
|
||||
url='https://github.com/toxygen-project/toxygen/',
|
||||
keywords='toxygen tox messenger',
|
||||
author='Ingvar',
|
||||
maintainer='Ingvar',
|
||||
license='GPL3',
|
||||
packages=['toxygen', 'toxygen.plugins', 'toxygen.styles'],
|
||||
install_requires=MODULES,
|
||||
dependency_links=DEP_LINKS,
|
||||
include_package_data=True,
|
||||
classifiers=[
|
||||
'Programming Language :: Python :: 3 :: Only',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
],
|
||||
entry_points={
|
||||
'console_scripts': ['toxygen=toxygen.main:main'],
|
||||
|
183
tests/tests.py
@ -1,31 +1,15 @@
|
||||
from toxygen.bootstrap import node_generator
|
||||
from toxygen.profile import *
|
||||
from toxygen.settings import ProfileHelper
|
||||
from toxygen.tox_dns import tox_dns
|
||||
import toxygen.toxencryptsave as encr
|
||||
|
||||
|
||||
class TestProfile:
|
||||
|
||||
def test_search(self):
|
||||
arr = ProfileHelper.find_profiles()
|
||||
assert len(arr) >= 2
|
||||
|
||||
def test_open(self):
|
||||
data = ProfileHelper(Settings.get_default_path(), 'alice').open_profile()
|
||||
assert data
|
||||
from toxygen.history import History
|
||||
from toxygen.smileys import SmileyLoader
|
||||
from toxygen.messages import *
|
||||
import toxygen.toxes as encr
|
||||
import toxygen.util as util
|
||||
import time
|
||||
|
||||
|
||||
class TestTox:
|
||||
|
||||
def test_loading(self):
|
||||
data = ProfileHelper(Settings.get_default_path(), 'alice').open_profile()
|
||||
settings = Settings.get_default_settings()
|
||||
tox = tox_factory(data, settings)
|
||||
for data in node_generator():
|
||||
tox.bootstrap(*data)
|
||||
del tox
|
||||
|
||||
def test_creation(self):
|
||||
name = b'Toxygen User'
|
||||
status_message = b'Toxing on Toxygen'
|
||||
@ -38,33 +22,156 @@ class TestTox:
|
||||
assert tox.self_get_name() == str(name, 'utf-8')
|
||||
assert tox.self_get_status_message() == str(status_message, 'utf-8')
|
||||
|
||||
def test_friend_list(self):
|
||||
data = ProfileHelper(Settings.get_default_path(), 'bob').open_profile()
|
||||
settings = Settings.get_default_settings()
|
||||
tox = tox_factory(data, settings)
|
||||
s = tox.self_get_friend_list()
|
||||
size = tox.self_get_friend_list_size()
|
||||
assert size <= 2
|
||||
assert len(s) <= 2
|
||||
del tox
|
||||
|
||||
class TestProfileHelper:
|
||||
|
||||
def test_creation(self):
|
||||
file_name, path = 'test.tox', os.path.dirname(os.path.realpath(__file__)) + '/'
|
||||
data = b'test'
|
||||
with open(path + file_name, 'wb') as fl:
|
||||
fl.write(data)
|
||||
ph = ProfileHelper(path, file_name[:4])
|
||||
assert ProfileHelper.get_path() == path
|
||||
assert ph.open_profile() == data
|
||||
assert os.path.exists(path + 'avatars/')
|
||||
|
||||
|
||||
class TestDNS:
|
||||
|
||||
def test_dns(self):
|
||||
Settings._instance = Settings.get_default_settings()
|
||||
bot_id = '56A1ADE4B65B86BCD51CC73E2CD4E542179F47959FE3E0E21B4B0ACDADE51855D34D34D37CB5'
|
||||
tox_id = tox_dns('groupbot@toxme.io')
|
||||
assert tox_id == bot_id
|
||||
|
||||
def test_dns2(self):
|
||||
Settings._instance = Settings.get_default_settings()
|
||||
bot_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6'
|
||||
tox_id = tox_dns('echobot@toxme.io')
|
||||
assert tox_id == bot_id
|
||||
|
||||
|
||||
class TestEncryption:
|
||||
|
||||
def test_encr_decr(self):
|
||||
with open(settings.Settings.get_default_path() + '/alice.tox', 'rb') as fl:
|
||||
data = fl.read()
|
||||
lib = encr.ToxEncryptSave()
|
||||
lib.set_password('easypassword')
|
||||
tox = tox_factory()
|
||||
data = tox.get_savedata()
|
||||
lib = encr.ToxES()
|
||||
for password in ('easypassword', 'njvnFjfn7vaGGV6', 'toxygen'):
|
||||
lib.set_password(password)
|
||||
copy_data = data[:]
|
||||
data = lib.pass_encrypt(data)
|
||||
data = lib.pass_decrypt(data)
|
||||
assert 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 test_friend_creation(self):
|
||||
create_singletons()
|
||||
name, status_message, number = 'Friend', 'I am friend!', 0
|
||||
tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6'
|
||||
friend = create_friend(name, status_message, number, tox_id)
|
||||
assert friend.name == name
|
||||
assert friend.tox_id == tox_id
|
||||
assert friend.status_message == status_message
|
||||
assert friend.number == number
|
||||
|
||||
def test_friend_corr(self):
|
||||
create_singletons()
|
||||
friend = create_random_friend()
|
||||
t = time.time()
|
||||
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(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 = 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(TextMessage('Hello! It is test!', MESSAGE_OWNER['ME'], t, 0))
|
||||
assert len(friend.get_corr()) == 2
|
||||
assert len(friend.get_corr_for_saving()) == 1
|
||||
|
||||
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)
|
||||
|
4
tests/travis.py
Normal file
@ -0,0 +1,4 @@
|
||||
class TestToxygen:
|
||||
|
||||
def test_main(self):
|
||||
import toxygen.main # check for syntax errors
|
@ -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,24 +14,25 @@ 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("Times New Roman")
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
font.setPointSize(16)
|
||||
font.setBold(True)
|
||||
self.name.setFont(font)
|
||||
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()
|
||||
|
||||
|
||||
|
118
toxygen/basecontact.py
Normal file
@ -0,0 +1,118 @@
|
||||
from settings import *
|
||||
from PyQt5 import QtCore, QtGui
|
||||
from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
|
||||
|
||||
|
||||
class BaseContact:
|
||||
"""
|
||||
Class encapsulating TOX contact
|
||||
Properties: name (alias of contact or name), status_message, status (connection status)
|
||||
widget - widget for update, tox id (or public key)
|
||||
Base class for all contacts.
|
||||
"""
|
||||
|
||||
def __init__(self, name, status_message, widget, tox_id):
|
||||
"""
|
||||
:param name: name, example: 'Toxygen user'
|
||||
:param status_message: status message, example: 'Toxing on Toxygen'
|
||||
:param widget: ContactItem instance
|
||||
:param tox_id: tox id of contact
|
||||
"""
|
||||
self._name, self._status_message = name, status_message
|
||||
self._status, self._widget = None, widget
|
||||
self._tox_id = tox_id
|
||||
self.init_widget()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Name - current name or alias of user
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_name(self):
|
||||
return self._name
|
||||
|
||||
def set_name(self, value):
|
||||
self._name = str(value, 'utf-8')
|
||||
self._widget.name.setText(self._name)
|
||||
self._widget.name.repaint()
|
||||
|
||||
name = property(get_name, set_name)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Status message
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_status_message(self):
|
||||
return self._status_message
|
||||
|
||||
def set_status_message(self, value):
|
||||
self._status_message = str(value, 'utf-8')
|
||||
self._widget.status_message.setText(self._status_message)
|
||||
self._widget.status_message.repaint()
|
||||
|
||||
status_message = property(get_status_message, set_status_message)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Status
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_status(self):
|
||||
return self._status
|
||||
|
||||
def set_status(self, value):
|
||||
self._status = value
|
||||
self._widget.connection_status.update(value)
|
||||
|
||||
status = property(get_status, set_status)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# TOX ID. WARNING: for friend it will return public key, for profile - full address
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_tox_id(self):
|
||||
return self._tox_id
|
||||
|
||||
tox_id = property(get_tox_id)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Avatars
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def load_avatar(self):
|
||||
"""
|
||||
Tries to load avatar of contact or uses default avatar
|
||||
"""
|
||||
prefix = ProfileHelper.get_path() + 'avatars/'
|
||||
avatar_path = prefix + '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
if not os.path.isfile(avatar_path) or not os.path.getsize(avatar_path): # load default image
|
||||
avatar_path = curr_directory() + '/images/avatar.png'
|
||||
width = self._widget.avatar_label.width()
|
||||
pixmap = QtGui.QPixmap(avatar_path)
|
||||
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
|
||||
QtCore.Qt.SmoothTransformation))
|
||||
self._widget.avatar_label.repaint()
|
||||
|
||||
def reset_avatar(self):
|
||||
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
if os.path.isfile(avatar_path):
|
||||
os.remove(avatar_path)
|
||||
self.load_avatar()
|
||||
|
||||
def set_avatar(self, avatar):
|
||||
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
with open(avatar_path, 'wb') as f:
|
||||
f.write(avatar)
|
||||
self.load_avatar()
|
||||
|
||||
def get_pixmap(self):
|
||||
return self._widget.avatar_label.pixmap()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Widgets
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def init_widget(self):
|
||||
if self._widget is not None:
|
||||
self._widget.name.setText(self._name)
|
||||
self._widget.status_message.setText(self._status_message)
|
||||
self._widget.connection_status.update(self._status)
|
||||
self.load_avatar()
|
@ -2,6 +2,7 @@ import random
|
||||
|
||||
|
||||
class Node:
|
||||
|
||||
def __init__(self, ip, port, tox_key, rand):
|
||||
self._ip, self._port, self._tox_key, self.rand = ip, port, tox_key, rand
|
||||
|
||||
|
@ -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
|
||||
@ -9,6 +6,15 @@ from toxcore_enums_and_consts import *
|
||||
from toxav_enums import *
|
||||
from tox import bin_to_string
|
||||
from plugin_support import PluginLoader
|
||||
import queue
|
||||
import threading
|
||||
import util
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Threads
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
class InvokeEvent(QtCore.QEvent):
|
||||
@ -33,6 +39,44 @@ _invoker = Invoker()
|
||||
def invoke_in_main_thread(fn, *args, **kwargs):
|
||||
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
|
||||
|
||||
|
||||
class FileTransfersThread(threading.Thread):
|
||||
|
||||
def __init__(self):
|
||||
self._queue = queue.Queue()
|
||||
self._timeout = 0.01
|
||||
self._continue = True
|
||||
super().__init__()
|
||||
|
||||
def execute(self, function, *args, **kwargs):
|
||||
self._queue.put((function, args, kwargs))
|
||||
|
||||
def stop(self):
|
||||
self._continue = False
|
||||
|
||||
def run(self):
|
||||
while self._continue:
|
||||
try:
|
||||
function, args, kwargs = self._queue.get(timeout=self._timeout)
|
||||
function(*args, **kwargs)
|
||||
except queue.Empty:
|
||||
pass
|
||||
except queue.Full:
|
||||
util.log('Queue is Full in _thread')
|
||||
except Exception as ex:
|
||||
util.log('Exception in _thread: ' + str(ex))
|
||||
|
||||
_thread = FileTransfersThread()
|
||||
|
||||
|
||||
def start():
|
||||
_thread.start()
|
||||
|
||||
|
||||
def stop():
|
||||
_thread.stop()
|
||||
_thread.join()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Callbacks - current user
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
@ -68,7 +112,7 @@ def friend_status(tox, friend_num, new_status, user_data):
|
||||
if friend.status is None and Settings.get_instance()['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
||||
sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
|
||||
invoke_in_main_thread(friend.set_status, new_status)
|
||||
invoke_in_main_thread(profile.send_files, friend_num)
|
||||
invoke_in_main_thread(QtCore.QTimer.singleShot, 5000, lambda: profile.send_files(friend_num))
|
||||
invoke_in_main_thread(profile.update_filtration)
|
||||
|
||||
|
||||
@ -179,7 +223,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'])
|
||||
@ -197,28 +241,15 @@ def file_recv_chunk(tox, friend_number, file_number, position, chunk, length, us
|
||||
"""
|
||||
Incoming chunk
|
||||
"""
|
||||
if not length:
|
||||
invoke_in_main_thread(Profile.get_instance().incoming_chunk,
|
||||
friend_number,
|
||||
file_number,
|
||||
position,
|
||||
None)
|
||||
else:
|
||||
Profile.get_instance().incoming_chunk(friend_number, file_number, position, chunk[:length])
|
||||
_thread.execute(Profile.get_instance().incoming_chunk, friend_number, file_number, position,
|
||||
chunk[:length] if length else None)
|
||||
|
||||
|
||||
def file_chunk_request(tox, friend_number, file_number, position, size, user_data):
|
||||
"""
|
||||
Outgoing chunk
|
||||
"""
|
||||
if size:
|
||||
Profile.get_instance().outgoing_chunk(friend_number, file_number, position, size)
|
||||
else:
|
||||
invoke_in_main_thread(Profile.get_instance().outgoing_chunk,
|
||||
friend_number,
|
||||
file_number,
|
||||
position,
|
||||
size)
|
||||
|
||||
|
||||
def file_recv_control(tox, friend_number, file_number, file_control, user_data):
|
||||
@ -241,16 +272,18 @@ def lossless_packet(tox, friend_number, data, length, user_data):
|
||||
"""
|
||||
Incoming lossless packet
|
||||
"""
|
||||
data = data[:length]
|
||||
plugin = PluginLoader.get_instance()
|
||||
invoke_in_main_thread(plugin.callback_lossless, friend_number, data, length)
|
||||
invoke_in_main_thread(plugin.callback_lossless, friend_number, data)
|
||||
|
||||
|
||||
def lossy_packet(tox, friend_number, data, length, user_data):
|
||||
"""
|
||||
Incoming lossy packet
|
||||
"""
|
||||
data = data[:length]
|
||||
plugin = PluginLoader.get_instance()
|
||||
invoke_in_main_thread(plugin.callback_lossy, friend_number, data, length)
|
||||
invoke_in_main_thread(plugin.callback_lossy, friend_number, data)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
@ -280,12 +313,67 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud
|
||||
"""
|
||||
New audio chunk
|
||||
"""
|
||||
# print(audio_samples_per_channel, audio_channels_count, rate)
|
||||
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
|
||||
For more info see callback_video_receive_frame docs
|
||||
"""
|
||||
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 - initialization
|
||||
@ -319,7 +407,7 @@ 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)
|
||||
|
||||
|
268
toxygen/calls.py
@ -3,14 +3,68 @@ 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
|
||||
# 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 +73,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 +86,75 @@ 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()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Threads
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def start_audio_thread(self):
|
||||
"""
|
||||
@ -92,7 +194,37 @@ 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']
|
||||
|
||||
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 +237,10 @@ class AV:
|
||||
output=True)
|
||||
self._out_stream.write(samples)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# AV sending
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def send_audio(self):
|
||||
"""
|
||||
This method sends audio to friends
|
||||
@ -114,10 +250,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 +262,71 @@ 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 Exception as e:
|
||||
print(e)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
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)
|
||||
|
@ -1,114 +1,286 @@
|
||||
import os
|
||||
from settings import *
|
||||
try:
|
||||
from PySide import QtCore, QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
|
||||
from PyQt5 import QtCore, QtGui
|
||||
from history import *
|
||||
import basecontact
|
||||
import util
|
||||
from messages import *
|
||||
import file_transfers as ft
|
||||
import re
|
||||
|
||||
|
||||
class Contact:
|
||||
class Contact(basecontact.BaseContact):
|
||||
"""
|
||||
Class encapsulating TOX contact
|
||||
Properties: name (alias of contact or name), status_message, status (connection status)
|
||||
widget - widget for update
|
||||
Properties: number, message getter, history etc. Base class for friend and gc classes
|
||||
"""
|
||||
|
||||
def __init__(self, name, status_message, widget, tox_id):
|
||||
def __init__(self, message_getter, number, name, status_message, widget, tox_id):
|
||||
"""
|
||||
:param name: name, example: 'Toxygen user'
|
||||
:param status_message: status message, example: 'Toxing on Toxygen'
|
||||
:param widget: ContactItem instance
|
||||
:param tox_id: tox id of contact
|
||||
:param message_getter: gets messages from db
|
||||
:param number: number of friend.
|
||||
"""
|
||||
self._name, self._status_message = name, status_message
|
||||
self._status, self._widget = None, widget
|
||||
self._widget.name.setText(name)
|
||||
self._widget.status_message.setText(status_message)
|
||||
self._tox_id = tox_id
|
||||
self.load_avatar()
|
||||
super().__init__(name, status_message, widget, tox_id)
|
||||
self._number = number
|
||||
self._new_messages = False
|
||||
self._visible = True
|
||||
self._alias = False
|
||||
self._message_getter = message_getter
|
||||
self._corr = []
|
||||
self._unsaved_messages = 0
|
||||
self._history_loaded = self._new_actions = False
|
||||
self._curr_text = self._search_string = ''
|
||||
self._search_index = 0
|
||||
|
||||
def __del__(self):
|
||||
self.set_visibility(False)
|
||||
del self._widget
|
||||
if hasattr(self, '_message_getter'):
|
||||
del self._message_getter
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# name - current name or alias of user
|
||||
# History support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_name(self):
|
||||
return self._name
|
||||
def load_corr(self, first_time=True):
|
||||
"""
|
||||
:param first_time: friend became active, load first part of messages
|
||||
"""
|
||||
if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')):
|
||||
return
|
||||
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()
|
||||
else:
|
||||
return
|
||||
data = list(map(lambda tupl: TextMessage(*tupl), data))
|
||||
self._corr = data + self._corr
|
||||
self._history_loaded = True
|
||||
|
||||
def load_all_corr(self):
|
||||
"""
|
||||
Get all chat history from db for current friend
|
||||
"""
|
||||
data = list(self._message_getter.get_all())
|
||||
if data is not None and len(data):
|
||||
data.reverse()
|
||||
data = list(map(lambda tupl: TextMessage(*tupl), data))
|
||||
self._corr = data + self._corr
|
||||
self._history_loaded = True
|
||||
|
||||
def get_corr_for_saving(self):
|
||||
"""
|
||||
Get data to save in db
|
||||
:return: list of unsaved messages or []
|
||||
"""
|
||||
messages = list(filter(lambda x: x.get_type() <= 1, self._corr))
|
||||
return list(map(lambda x: x.get_data(), messages[-self._unsaved_messages:])) if self._unsaved_messages else []
|
||||
|
||||
def get_corr(self):
|
||||
return self._corr[:]
|
||||
|
||||
def append_message(self, message):
|
||||
"""
|
||||
:param message: text or file transfer message
|
||||
"""
|
||||
self._corr.append(message)
|
||||
if message.get_type() <= 1:
|
||||
self._unsaved_messages += 1
|
||||
|
||||
def get_last_message_text(self):
|
||||
messages = list(filter(lambda x: x.get_type() <= 1 and x.get_owner() != MESSAGE_OWNER['FRIEND'], self._corr))
|
||||
if messages:
|
||||
return messages[-1].get_data()[0]
|
||||
else:
|
||||
return ''
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Unsent messages
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_unsent_messages(self):
|
||||
"""
|
||||
:return list of unsent messages
|
||||
"""
|
||||
messages = filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
|
||||
return list(messages)
|
||||
|
||||
def get_unsent_messages_for_saving(self):
|
||||
"""
|
||||
:return list of unsent messages for saving
|
||||
"""
|
||||
messages = filter(lambda x: x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
|
||||
return list(map(lambda x: x.get_data(), messages))
|
||||
|
||||
def mark_as_sent(self):
|
||||
try:
|
||||
message = list(filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr))[0]
|
||||
message.mark_as_sent()
|
||||
except Exception as ex:
|
||||
util.log('Mark as sent ex: ' + str(ex))
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Message deletion
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def delete_message(self, time):
|
||||
elem = list(filter(lambda x: type(x) is TextMessage and x.get_data()[2] == time, self._corr))[0]
|
||||
tmp = list(filter(lambda x: x.get_type() <= 1, self._corr))
|
||||
if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages:
|
||||
self._unsaved_messages -= 1
|
||||
self._corr.remove(elem)
|
||||
self._message_getter.delete_one()
|
||||
self._search_index = 0
|
||||
|
||||
def delete_old_messages(self):
|
||||
"""
|
||||
Delete old messages (reduces RAM usage if messages saving is not enabled)
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Clear messages list
|
||||
"""
|
||||
if hasattr(self, '_message_getter'):
|
||||
del self._message_getter
|
||||
self._search_index = 0
|
||||
# don't delete data about active file transfer
|
||||
if not save_unsent:
|
||||
self._corr = list(filter(lambda x: x.get_type() == 2 and
|
||||
x.get_status() in ft.ACTIVE_FILE_TRANSFERS, self._corr))
|
||||
self._unsaved_messages = 0
|
||||
else:
|
||||
self._corr = list(filter(lambda x: (x.get_type() == 2 and x.get_status() in ft.ACTIVE_FILE_TRANSFERS)
|
||||
or (x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT']),
|
||||
self._corr))
|
||||
self._unsaved_messages = len(self.get_unsent_messages())
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Chat history search
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def search_string(self, search_string):
|
||||
self._search_string, self._search_index = search_string, 0
|
||||
return self.search_prev()
|
||||
|
||||
def search_prev(self):
|
||||
while True:
|
||||
l = len(self._corr)
|
||||
for i in range(self._search_index - 1, -l - 1, -1):
|
||||
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:
|
||||
self._search_index = i
|
||||
return i
|
||||
self._search_index = -l
|
||||
self.load_corr(False)
|
||||
if len(self._corr) == l:
|
||||
return None # not found
|
||||
|
||||
def search_next(self):
|
||||
if not self._search_index:
|
||||
return None
|
||||
for i in range(self._search_index + 1, 0):
|
||||
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:
|
||||
self._search_index = i
|
||||
return i
|
||||
return None # not found
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Current text - text from message area
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_curr_text(self):
|
||||
return self._curr_text
|
||||
|
||||
def set_curr_text(self, value):
|
||||
self._curr_text = value
|
||||
|
||||
curr_text = property(get_curr_text, set_curr_text)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Alias support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def set_name(self, value):
|
||||
self._name = str(value, 'utf-8')
|
||||
self._widget.name.setText(self._name)
|
||||
self._widget.name.repaint()
|
||||
|
||||
name = property(get_name, set_name)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Status message
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_status_message(self):
|
||||
return self._status_message
|
||||
|
||||
def set_status_message(self, value):
|
||||
self._status_message = str(value, 'utf-8')
|
||||
self._widget.status_message.setText(self._status_message)
|
||||
self._widget.status_message.repaint()
|
||||
|
||||
status_message = property(get_status_message, set_status_message)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Status
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_status(self):
|
||||
return self._status
|
||||
|
||||
def set_status(self, value):
|
||||
self._status = value
|
||||
self._widget.connection_status.update(value)
|
||||
|
||||
status = property(get_status, set_status)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# TOX ID. WARNING: for friend it will return public key, for profile - full address
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_tox_id(self):
|
||||
return self._tox_id
|
||||
|
||||
tox_id = property(get_tox_id)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Avatars
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def load_avatar(self):
|
||||
"""
|
||||
Tries to load avatar of contact or uses default avatar
|
||||
Set new name or ignore if alias exists
|
||||
:param value: new name
|
||||
"""
|
||||
avatar_path = '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
os.chdir(ProfileHelper.get_path() + 'avatars/')
|
||||
if not os.path.isfile(avatar_path): # load default image
|
||||
avatar_path = 'avatar.png'
|
||||
os.chdir(curr_directory() + '/images/')
|
||||
width = self._widget.avatar_label.width()
|
||||
pixmap = QtGui.QPixmap(QtCore.QSize(width, width))
|
||||
pixmap.load(avatar_path)
|
||||
self._widget.avatar_label.setScaledContents(False)
|
||||
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio))
|
||||
self._widget.avatar_label.repaint()
|
||||
if not self._alias:
|
||||
super().set_name(value)
|
||||
|
||||
def reset_avatar(self):
|
||||
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
if os.path.isfile(avatar_path):
|
||||
os.remove(avatar_path)
|
||||
self.load_avatar()
|
||||
def set_alias(self, alias):
|
||||
self._alias = bool(alias)
|
||||
|
||||
def set_avatar(self, avatar):
|
||||
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
with open(avatar_path, 'wb') as f:
|
||||
f.write(avatar)
|
||||
self.load_avatar()
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Visibility in friends' list
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_pixmap(self):
|
||||
return self._widget.avatar_label.pixmap()
|
||||
def get_visibility(self):
|
||||
return self._visible
|
||||
|
||||
def set_visibility(self, value):
|
||||
self._visible = value
|
||||
|
||||
visibility = property(get_visibility, set_visibility)
|
||||
|
||||
def set_widget(self, widget):
|
||||
self._widget = widget
|
||||
self.init_widget()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Unread messages and other actions from friend
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_actions(self):
|
||||
return self._new_actions
|
||||
|
||||
def set_actions(self, value):
|
||||
self._new_actions = value
|
||||
self._widget.connection_status.update(self.status, value)
|
||||
|
||||
actions = property(get_actions, set_actions) # unread messages, incoming files, av calls
|
||||
|
||||
def get_messages(self):
|
||||
return self._new_messages
|
||||
|
||||
def inc_messages(self):
|
||||
self._new_messages += 1
|
||||
self._new_actions = True
|
||||
self._widget.connection_status.update(self.status, True)
|
||||
self._widget.messages.update(self._new_messages)
|
||||
|
||||
def reset_messages(self):
|
||||
self._new_actions = False
|
||||
self._new_messages = 0
|
||||
self._widget.messages.update(self._new_messages)
|
||||
self._widget.connection_status.update(self.status, False)
|
||||
|
||||
messages = property(get_messages)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Friend's number (can be used in toxcore)
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_number(self):
|
||||
return self._number
|
||||
|
||||
def set_number(self, value):
|
||||
self._number = value
|
||||
|
||||
number = property(get_number, set_number)
|
||||
|
@ -4,12 +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
|
||||
|
||||
# TODO: threads!
|
||||
from PyQt5 import QtCore
|
||||
|
||||
|
||||
TOX_FILE_TRANSFER_STATE = {
|
||||
@ -33,11 +28,18 @@ SHOW_PROGRESS_BAR = (0, 1, 4)
|
||||
ALLOWED_FILES = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png')
|
||||
|
||||
|
||||
def is_inline(file_name):
|
||||
return file_name in ALLOWED_FILES or file_name.startswith('qTox_Screenshot_')
|
||||
|
||||
|
||||
class StateSignal(QtCore.QObject):
|
||||
try:
|
||||
signal = QtCore.Signal(int, float, int) # state and progress
|
||||
except:
|
||||
signal = QtCore.pyqtSignal(int, float, int) # state and progress - pyqt4
|
||||
|
||||
signal = QtCore.pyqtSignal(int, float, int) # state, progress, time in sec
|
||||
|
||||
|
||||
class TransferFinishedSignal(QtCore.QObject):
|
||||
|
||||
signal = QtCore.pyqtSignal(int, int) # friend number, file number
|
||||
|
||||
|
||||
class FileTransfer(QtCore.QObject):
|
||||
@ -56,6 +58,8 @@ class FileTransfer(QtCore.QObject):
|
||||
self._size = float(size)
|
||||
self._done = 0
|
||||
self._state_changed = StateSignal()
|
||||
self._finished = TransferFinishedSignal()
|
||||
self._file_id = None
|
||||
|
||||
def set_tox(self, tox):
|
||||
self._tox = tox
|
||||
@ -63,6 +67,9 @@ class FileTransfer(QtCore.QObject):
|
||||
def set_state_changed_handler(self, handler):
|
||||
self._state_changed.signal.connect(handler)
|
||||
|
||||
def set_transfer_finished_handler(self, handler):
|
||||
self._finished.signal.connect(handler)
|
||||
|
||||
def signal(self):
|
||||
percentage = self._done / self._size if self._size else 0
|
||||
if self._creation_time is None or not percentage:
|
||||
@ -71,12 +78,21 @@ class FileTransfer(QtCore.QObject):
|
||||
t = ((time() - self._creation_time) / percentage) * (1 - percentage)
|
||||
self._state_changed.signal.emit(self.state, percentage, int(t))
|
||||
|
||||
def finished(self):
|
||||
self._finished.signal.emit(self._friend_number, self._file_number)
|
||||
|
||||
def get_file_number(self):
|
||||
return self._file_number
|
||||
|
||||
def get_friend_number(self):
|
||||
return self._friend_number
|
||||
|
||||
def get_id(self):
|
||||
return self._file_id
|
||||
|
||||
def get_path(self):
|
||||
return self._path
|
||||
|
||||
def cancel(self):
|
||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
||||
if hasattr(self, '_file'):
|
||||
@ -122,6 +138,7 @@ class SendTransfer(FileTransfer):
|
||||
self.state = TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
|
||||
self._file_number = tox.file_send(friend_number, kind, size, file_id,
|
||||
bytes(basename(path), 'utf-8') if path else b'')
|
||||
self._file_id = self.get_file_id()
|
||||
|
||||
def send_chunk(self, position, size):
|
||||
"""
|
||||
@ -136,11 +153,11 @@ class SendTransfer(FileTransfer):
|
||||
data = self._file.read(size)
|
||||
self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
|
||||
self._done += size
|
||||
self.signal()
|
||||
else:
|
||||
if hasattr(self, '_file'):
|
||||
self._file.close()
|
||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||
self.finished()
|
||||
self.signal()
|
||||
|
||||
|
||||
@ -180,9 +197,9 @@ class SendFromBuffer(FileTransfer):
|
||||
data = self._data[position:position + size]
|
||||
self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
|
||||
self._done += size
|
||||
self.signal()
|
||||
else:
|
||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||
self.finished()
|
||||
self.signal()
|
||||
|
||||
|
||||
@ -204,16 +221,23 @@ class SendFromFileBuffer(SendTransfer):
|
||||
|
||||
class ReceiveTransfer(FileTransfer):
|
||||
|
||||
def __init__(self, path, tox, friend_number, size, file_number):
|
||||
def __init__(self, path, tox, friend_number, size, file_number, position=0):
|
||||
super(ReceiveTransfer, self).__init__(path, tox, friend_number, size, file_number)
|
||||
self._file = open(self._path, 'wb')
|
||||
self._file.truncate(0)
|
||||
self._file_size = 0
|
||||
self._file_size = position
|
||||
self._file.truncate(position)
|
||||
self._missed = set()
|
||||
self._file_id = self.get_file_id()
|
||||
self._done = position
|
||||
|
||||
def cancel(self):
|
||||
super(ReceiveTransfer, self).cancel()
|
||||
remove(self._path)
|
||||
|
||||
def total_size(self):
|
||||
self._missed.add(self._file_size)
|
||||
return min(self._missed)
|
||||
|
||||
def write_chunk(self, position, data):
|
||||
"""
|
||||
Incoming chunk
|
||||
@ -225,12 +249,15 @@ class ReceiveTransfer(FileTransfer):
|
||||
if data is None:
|
||||
self._file.close()
|
||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||
self.signal()
|
||||
self.finished()
|
||||
else:
|
||||
data = bytearray(data)
|
||||
if self._file_size < position:
|
||||
self._file.seek(0, 2)
|
||||
self._file.write(b'\0' * (position - self._file_size))
|
||||
self._missed.add(self._file_size)
|
||||
else:
|
||||
self._missed.discard(position)
|
||||
self._file.seek(position)
|
||||
self._file.write(data)
|
||||
l = len(data)
|
||||
@ -258,6 +285,7 @@ class ReceiveToBuffer(FileTransfer):
|
||||
self._creation_time = time()
|
||||
if data is None:
|
||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||
self.finished()
|
||||
else:
|
||||
data = bytes(data)
|
||||
l = len(data)
|
||||
@ -312,3 +340,8 @@ class ReceiveAvatar(ReceiveTransfer):
|
||||
chdir(dirname(avatar_path))
|
||||
remove(avatar_path)
|
||||
rename(self._path, avatar_path)
|
||||
self.finished(True)
|
||||
|
||||
def finished(self, emit=False):
|
||||
if emit:
|
||||
super().finished()
|
||||
|
@ -1,148 +1,16 @@
|
||||
import contact
|
||||
from messages import *
|
||||
from history import *
|
||||
import util
|
||||
import file_transfers as ft
|
||||
import os
|
||||
|
||||
|
||||
class Friend(contact.Contact):
|
||||
"""
|
||||
Friend in list of friends. Can be hidden, properties 'has unread messages' and 'has alias' added
|
||||
Friend in list of friends.
|
||||
"""
|
||||
|
||||
def __init__(self, message_getter, number, *args):
|
||||
"""
|
||||
:param message_getter: gets messages from db
|
||||
:param number: number of friend.
|
||||
"""
|
||||
super(Friend, self).__init__(*args)
|
||||
self._number = number
|
||||
self._new_messages = False
|
||||
self._visible = True
|
||||
self._alias = False
|
||||
self._message_getter = message_getter
|
||||
self._corr = []
|
||||
self._unsaved_messages = 0
|
||||
self._history_loaded = self._new_actions = False
|
||||
def __init__(self, message_getter, number, name, status_message, widget, tox_id):
|
||||
super().__init__(message_getter, number, name, status_message, widget, tox_id)
|
||||
self._receipts = 0
|
||||
self._curr_text = ''
|
||||
|
||||
def __del__(self):
|
||||
self.set_visibility(False)
|
||||
del self._widget
|
||||
if hasattr(self, '_message_getter'):
|
||||
del self._message_getter
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# History support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_receipts(self):
|
||||
return self._receipts
|
||||
|
||||
receipts = property(get_receipts) # read receipts
|
||||
|
||||
def inc_receipts(self):
|
||||
self._receipts += 1
|
||||
|
||||
def dec_receipt(self):
|
||||
if self._receipts:
|
||||
self._receipts -= 1
|
||||
self.mark_as_sent()
|
||||
|
||||
def load_corr(self, first_time=True):
|
||||
"""
|
||||
:param first_time: friend became active, load first part of messages
|
||||
"""
|
||||
if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')):
|
||||
return
|
||||
data = list(self._message_getter.get(PAGE_SIZE))
|
||||
if data is not None and len(data):
|
||||
data.reverse()
|
||||
else:
|
||||
return
|
||||
data = list(map(lambda tupl: TextMessage(*tupl), data))
|
||||
self._corr = data + self._corr
|
||||
self._history_loaded = True
|
||||
|
||||
def get_corr_for_saving(self):
|
||||
"""
|
||||
Get data to save in db
|
||||
:return: list of unsaved messages or []
|
||||
"""
|
||||
messages = list(filter(lambda x: x.get_type() <= 1, self._corr))
|
||||
return list(map(lambda x: x.get_data(), messages[-self._unsaved_messages:])) if self._unsaved_messages else []
|
||||
|
||||
def get_corr(self):
|
||||
return self._corr[:]
|
||||
|
||||
def append_message(self, message):
|
||||
"""
|
||||
:param message: text or file transfer message
|
||||
"""
|
||||
self._corr.append(message)
|
||||
if message.get_type() <= 1:
|
||||
self._unsaved_messages += 1
|
||||
|
||||
def get_last_message_text(self):
|
||||
messages = list(filter(lambda x: x.get_type() <= 1 and x.get_owner() != MESSAGE_OWNER['FRIEND'], self._corr))
|
||||
if messages:
|
||||
return messages[-1].get_data()[0]
|
||||
else:
|
||||
return ''
|
||||
|
||||
def get_unsent_messages(self):
|
||||
"""
|
||||
:return list of unsent messages
|
||||
"""
|
||||
messages = filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
|
||||
return list(messages)
|
||||
|
||||
def get_unsent_messages_for_saving(self):
|
||||
"""
|
||||
:return list of unsent messages for saving
|
||||
"""
|
||||
messages = filter(lambda x: x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
|
||||
return list(map(lambda x: x.get_data(), messages))
|
||||
|
||||
def delete_message(self, time):
|
||||
elem = list(filter(lambda x: type(x) is TextMessage and x.get_data()[2] == time, self._corr))[0]
|
||||
tmp = list(filter(lambda x: x.get_type() <= 1, self._corr))
|
||||
if elem in tmp[-self._unsaved_messages:]:
|
||||
self._unsaved_messages -= 1
|
||||
self._corr.remove(elem)
|
||||
|
||||
def mark_as_sent(self):
|
||||
try:
|
||||
message = list(filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr))[0]
|
||||
message.mark_as_sent()
|
||||
except Exception as ex:
|
||||
util.log('Mark as sent ex: ' + str(ex))
|
||||
|
||||
def clear_corr(self, save_unsent=False):
|
||||
"""
|
||||
Clear messages list
|
||||
"""
|
||||
if hasattr(self, '_message_getter'):
|
||||
del self._message_getter
|
||||
# don't delete data about active file transfer
|
||||
if not save_unsent:
|
||||
self._corr = list(filter(lambda x: x.get_type() == 2 and
|
||||
x.get_status() in ft.ACTIVE_FILE_TRANSFERS, self._corr))
|
||||
self._unsaved_messages = 0
|
||||
else:
|
||||
self._corr = list(filter(lambda x: (x.get_type() == 2 and x.get_status() in ft.ACTIVE_FILE_TRANSFERS)
|
||||
or (x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT']),
|
||||
self._corr))
|
||||
self._unsaved_messages = len(self.get_unsent_messages())
|
||||
|
||||
def get_curr_text(self):
|
||||
return self._curr_text
|
||||
|
||||
def set_curr_text(self, value):
|
||||
self._curr_text = value
|
||||
|
||||
curr_text = property(get_curr_text, set_curr_text)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# File transfers support
|
||||
@ -170,74 +38,31 @@ 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))
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Alias support
|
||||
# History support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def set_name(self, value):
|
||||
"""
|
||||
Set new name or ignore if alias exists
|
||||
:param value: new name
|
||||
"""
|
||||
if not self._alias:
|
||||
super(Friend, self).set_name(value)
|
||||
def get_receipts(self):
|
||||
return self._receipts
|
||||
|
||||
def set_alias(self, alias):
|
||||
self._alias = bool(alias)
|
||||
receipts = property(get_receipts) # read receipts
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Visibility in friends' list
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
def inc_receipts(self):
|
||||
self._receipts += 1
|
||||
|
||||
def get_visibility(self):
|
||||
return self._visible
|
||||
|
||||
def set_visibility(self, value):
|
||||
self._visible = value
|
||||
|
||||
visibility = property(get_visibility, set_visibility)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Unread messages from friend
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_actions(self):
|
||||
return self._new_actions
|
||||
|
||||
def set_actions(self, value):
|
||||
self._new_actions = value
|
||||
self._widget.connection_status.update(self.status, value)
|
||||
|
||||
actions = property(get_actions, set_actions) # unread messages, incoming files, av calls
|
||||
|
||||
def get_messages(self):
|
||||
return self._new_messages
|
||||
|
||||
def inc_messages(self):
|
||||
self._new_messages += 1
|
||||
self._new_actions = True
|
||||
self._widget.connection_status.update(self.status, True)
|
||||
self._widget.messages.update(self._new_messages)
|
||||
|
||||
def reset_messages(self):
|
||||
self._new_actions = False
|
||||
self._new_messages = 0
|
||||
self._widget.messages.update(self._new_messages)
|
||||
self._widget.connection_status.update(self.status, False)
|
||||
|
||||
messages = property(get_messages)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Friend's number (can be used in toxcore)
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_number(self):
|
||||
return self._number
|
||||
|
||||
def set_number(self, value):
|
||||
self._number = value
|
||||
|
||||
number = property(get_number, set_number)
|
||||
def dec_receipt(self):
|
||||
if self._receipts:
|
||||
self._receipts -= 1
|
||||
self.mark_as_sent()
|
||||
|
@ -1,13 +1,16 @@
|
||||
# coding=utf-8
|
||||
from sqlite3 import connect
|
||||
import settings
|
||||
from os import chdir
|
||||
import os.path
|
||||
from toxencryptsave import ToxEncryptSave
|
||||
from toxes import ToxES
|
||||
|
||||
|
||||
PAGE_SIZE = 42
|
||||
|
||||
TIMEOUT = 11
|
||||
|
||||
SAVE_MESSAGES = 250
|
||||
|
||||
MESSAGE_OWNER = {
|
||||
'ME': 0,
|
||||
'FRIEND': 1,
|
||||
@ -22,7 +25,7 @@ class History:
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
path = settings.ProfileHelper.get_path() + self._name + '.hstr'
|
||||
if os.path.exists(path):
|
||||
decr = ToxEncryptSave.get_instance()
|
||||
decr = ToxES.get_instance()
|
||||
try:
|
||||
with open(path, 'rb') as fin:
|
||||
data = fin.read()
|
||||
@ -32,7 +35,7 @@ class History:
|
||||
fout.write(data)
|
||||
except:
|
||||
os.remove(path)
|
||||
db = connect(name + '.hstr')
|
||||
db = connect(name + '.hstr', timeout=TIMEOUT)
|
||||
cursor = db.cursor()
|
||||
cursor.execute('CREATE TABLE IF NOT EXISTS friends('
|
||||
' tox_id TEXT PRIMARY KEY'
|
||||
@ -40,7 +43,7 @@ class History:
|
||||
db.close()
|
||||
|
||||
def save(self):
|
||||
encr = ToxEncryptSave.get_instance()
|
||||
encr = ToxES.get_instance()
|
||||
if encr.has_password():
|
||||
path = settings.ProfileHelper.get_path() + self._name + '.hstr'
|
||||
with open(path, 'rb') as fin:
|
||||
@ -54,7 +57,7 @@ class History:
|
||||
new_path = directory + self._name + '.hstr'
|
||||
with open(path, 'rb') as fin:
|
||||
data = fin.read()
|
||||
encr = ToxEncryptSave.get_instance()
|
||||
encr = ToxES.get_instance()
|
||||
if encr.has_password():
|
||||
data = encr.pass_encrypt(data)
|
||||
with open(new_path, 'wb') as fout:
|
||||
@ -62,7 +65,7 @@ class History:
|
||||
|
||||
def add_friend_to_db(self, tox_id):
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
db = connect(self._name + '.hstr')
|
||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
||||
try:
|
||||
cursor = db.cursor()
|
||||
cursor.execute('INSERT INTO friends VALUES (?);', (tox_id, ))
|
||||
@ -75,28 +78,28 @@ class History:
|
||||
')')
|
||||
db.commit()
|
||||
except:
|
||||
print('Database is locked!')
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def delete_friend_from_db(self, tox_id):
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
db = connect(self._name + '.hstr')
|
||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
||||
try:
|
||||
cursor = db.cursor()
|
||||
cursor.execute('DELETE FROM friends WHERE tox_id=?;', (tox_id, ))
|
||||
cursor.execute('DROP TABLE id' + tox_id + ';')
|
||||
db.commit()
|
||||
except:
|
||||
print('Database is locked!')
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def friend_exists_in_db(self, tox_id):
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
db = connect(self._name + '.hstr')
|
||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
||||
cursor = db.cursor()
|
||||
cursor.execute('SELECT 0 FROM friends WHERE tox_id=?', (tox_id, ))
|
||||
result = cursor.fetchone()
|
||||
@ -105,56 +108,57 @@ class History:
|
||||
|
||||
def save_messages_to_db(self, tox_id, messages_iter):
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
db = connect(self._name + '.hstr')
|
||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
||||
try:
|
||||
cursor = db.cursor()
|
||||
cursor.executemany('INSERT INTO id' + tox_id + '(message, owner, unix_time, message_type) '
|
||||
'VALUES (?, ?, ?, ?);', messages_iter)
|
||||
db.commit()
|
||||
except:
|
||||
print('Database is locked!')
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def update_messages(self, tox_id, unsent_time):
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
db = connect(self._name + '.hstr')
|
||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
||||
try:
|
||||
cursor = db.cursor()
|
||||
cursor.execute('UPDATE id' + tox_id + ' SET owner = 0 '
|
||||
'WHERE unix_time < ' + str(unsent_time) + ' AND owner = 2;')
|
||||
db.commit()
|
||||
except:
|
||||
print('Database is locked!')
|
||||
db.rollback()
|
||||
raise
|
||||
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')
|
||||
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!')
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def delete_messages(self, tox_id):
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
db = connect(self._name + '.hstr')
|
||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
||||
try:
|
||||
cursor = db.cursor()
|
||||
cursor.execute('DELETE FROM id' + tox_id + ';')
|
||||
db.commit()
|
||||
except:
|
||||
print('Database is locked!')
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@ -162,21 +166,50 @@ class History:
|
||||
return History.MessageGetter(self._name, tox_id)
|
||||
|
||||
class MessageGetter:
|
||||
|
||||
def __init__(self, name, tox_id):
|
||||
self._count = 0
|
||||
self._name = name
|
||||
self._tox_id = tox_id
|
||||
self._db = self._cursor = None
|
||||
|
||||
def connect(self):
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
self._db = connect(name + '.hstr')
|
||||
self._db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
||||
self._cursor = self._db.cursor()
|
||||
self._cursor.execute('SELECT message, owner, unix_time, message_type FROM id' + tox_id +
|
||||
self._cursor.execute('SELECT message, owner, unix_time, message_type FROM id' + self._tox_id +
|
||||
' ORDER BY unix_time DESC;')
|
||||
|
||||
def disconnect(self):
|
||||
self._db.close()
|
||||
|
||||
def get_one(self):
|
||||
return self._cursor.fetchone()
|
||||
self.connect()
|
||||
self.skip()
|
||||
data = self._cursor.fetchone()
|
||||
self._count += 1
|
||||
self.disconnect()
|
||||
return data
|
||||
|
||||
def get_all(self):
|
||||
return self._cursor.fetchall()
|
||||
self.connect()
|
||||
data = self._cursor.fetchall()
|
||||
self.disconnect()
|
||||
self._count = len(data)
|
||||
return data
|
||||
|
||||
def get(self, count):
|
||||
return self._cursor.fetchmany(count)
|
||||
self.connect()
|
||||
self.skip()
|
||||
data = self._cursor.fetchmany(count)
|
||||
self.disconnect()
|
||||
self._count += len(data)
|
||||
return data
|
||||
|
||||
def __del__(self):
|
||||
self._db.close()
|
||||
def skip(self):
|
||||
if self._count:
|
||||
self._cursor.fetchmany(self._count)
|
||||
|
||||
def delete_one(self):
|
||||
if self._count:
|
||||
self._count -= 1
|
||||
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.0 KiB |
68
toxygen/items_factory.py
Normal file
@ -0,0 +1,68 @@
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
from list_items import *
|
||||
|
||||
|
||||
class ItemsFactory:
|
||||
|
||||
def __init__(self, friends_list, messages):
|
||||
self._friends = friends_list
|
||||
self._messages = messages
|
||||
|
||||
def friend_item(self):
|
||||
item = ContactItem()
|
||||
elem = QtWidgets.QListWidgetItem(self._friends)
|
||||
elem.setSizeHint(QtCore.QSize(250, item.height()))
|
||||
self._friends.addItem(elem)
|
||||
self._friends.setItemWidget(elem, item)
|
||||
return item
|
||||
|
||||
def message_item(self, text, time, name, sent, message_type, append, pixmap):
|
||||
item = MessageItem(text, time, name, sent, message_type, self._messages)
|
||||
if pixmap is not None:
|
||||
item.set_avatar(pixmap)
|
||||
elem = QtWidgets.QListWidgetItem()
|
||||
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
|
||||
if append:
|
||||
self._messages.addItem(elem)
|
||||
else:
|
||||
self._messages.insertItem(0, elem)
|
||||
self._messages.setItemWidget(elem, item)
|
||||
return item
|
||||
|
||||
def inline_item(self, data, append):
|
||||
elem = QtWidgets.QListWidgetItem()
|
||||
item = InlineImageItem(data, self._messages.width(), elem)
|
||||
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
|
||||
if append:
|
||||
self._messages.addItem(elem)
|
||||
else:
|
||||
self._messages.insertItem(0, elem)
|
||||
self._messages.setItemWidget(elem, item)
|
||||
return item
|
||||
|
||||
def unsent_file_item(self, file_name, size, name, time, append):
|
||||
item = UnsentFileItem(file_name,
|
||||
size,
|
||||
name,
|
||||
time,
|
||||
self._messages.width())
|
||||
elem = QtWidgets.QListWidgetItem()
|
||||
elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34))
|
||||
if append:
|
||||
self._messages.addItem(elem)
|
||||
else:
|
||||
self._messages.insertItem(0, elem)
|
||||
self._messages.setItemWidget(elem, item)
|
||||
return item
|
||||
|
||||
def file_transfer_item(self, data, append):
|
||||
data.append(self._messages.width())
|
||||
item = FileTransferItem(*data)
|
||||
elem = QtWidgets.QListWidgetItem()
|
||||
elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34))
|
||||
if append:
|
||||
self._messages.addItem(elem)
|
||||
else:
|
||||
self._messages.insertItem(0, elem)
|
||||
self._messages.setItemWidget(elem, item)
|
||||
return item
|
@ -6,13 +6,16 @@ import util
|
||||
class LibToxCore:
|
||||
|
||||
def __init__(self):
|
||||
if system() == 'Linux':
|
||||
# libtoxcore and libsodium must be installed in your os
|
||||
self._libtoxcore = CDLL('libtoxcore.so')
|
||||
elif system() == 'Windows':
|
||||
if system() == 'Windows':
|
||||
self._libtoxcore = CDLL(util.curr_directory() + '/libs/libtox.dll')
|
||||
elif system() == 'Darwin':
|
||||
self._libtoxcore = CDLL('libtoxcore.dylib')
|
||||
else:
|
||||
raise OSError('Unknown system.')
|
||||
# libtoxcore and libsodium must be installed in your os
|
||||
try:
|
||||
self._libtoxcore = CDLL('libtoxcore.so')
|
||||
except:
|
||||
self._libtoxcore = CDLL(util.curr_directory() + '/libs/libtoxcore.so')
|
||||
|
||||
def __getattr__(self, item):
|
||||
return self._libtoxcore.__getattr__(item)
|
||||
@ -21,14 +24,17 @@ class LibToxCore:
|
||||
class LibToxAV:
|
||||
|
||||
def __init__(self):
|
||||
if system() == 'Linux':
|
||||
# that /usr/lib/libtoxav.so must exists
|
||||
self._libtoxav = CDLL('libtoxav.so')
|
||||
elif system() == 'Windows':
|
||||
if system() == 'Windows':
|
||||
# on Windows av api is in libtox.dll
|
||||
self._libtoxav = CDLL(util.curr_directory() + '/libs/libtox.dll')
|
||||
elif system() == 'Darwin':
|
||||
self._libtoxav = CDLL('libtoxav.dylib')
|
||||
else:
|
||||
raise OSError('Unknown system.')
|
||||
# /usr/lib/libtoxav.so must exists
|
||||
try:
|
||||
self._libtoxav = CDLL('libtoxav.so')
|
||||
except:
|
||||
self._libtoxav = CDLL(util.curr_directory() + '/libs/libtoxav.so')
|
||||
|
||||
def __getattr__(self, item):
|
||||
return self._libtoxav.__getattr__(item)
|
||||
@ -37,14 +43,17 @@ class LibToxAV:
|
||||
class LibToxEncryptSave:
|
||||
|
||||
def __init__(self):
|
||||
if system() == 'Linux':
|
||||
# /usr/lib/libtoxencryptsave.so must exists
|
||||
self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.so')
|
||||
elif system() == 'Windows':
|
||||
if system() == 'Windows':
|
||||
# on Windows profile encryption api is in libtox.dll
|
||||
self._lib_tox_encrypt_save = CDLL(util.curr_directory() + '/libs/libtox.dll')
|
||||
elif system() == 'Darwin':
|
||||
self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.dylib')
|
||||
else:
|
||||
raise OSError('Unknown system.')
|
||||
# /usr/lib/libtoxencryptsave.so must exists
|
||||
try:
|
||||
self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.so')
|
||||
except:
|
||||
self._lib_tox_encrypt_save = CDLL(util.curr_directory() + '/libs/libtoxencryptsave.so')
|
||||
|
||||
def __getattr__(self, item):
|
||||
return self._lib_tox_encrypt_save.__getattr__(item)
|
||||
|
@ -1,8 +1,5 @@
|
||||
from toxcore_enums_and_consts import *
|
||||
try:
|
||||
from PySide import QtCore, QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
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
|
||||
@ -10,9 +7,10 @@ from widgets import DataLabel, create_menu
|
||||
import html as h
|
||||
import smileys
|
||||
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)
|
||||
@ -24,7 +22,9 @@ class MessageEdit(QtGui.QTextBrowser):
|
||||
self.setOpenExternalLinks(True)
|
||||
self.setAcceptRichText(True)
|
||||
self.setOpenLinks(False)
|
||||
self.setSearchPaths([smileys.SmileyLoader.get_instance().get_smileys_path()])
|
||||
path = smileys.SmileyLoader.get_instance().get_smileys_path()
|
||||
if path is not None:
|
||||
self.setSearchPaths([path])
|
||||
self.document().setDefaultStyleSheet('a { color: #306EFF; }')
|
||||
text = self.decoratedText(text)
|
||||
if message_type != TOX_MESSAGE_TYPE['NORMAL']:
|
||||
@ -32,7 +32,7 @@ class MessageEdit(QtGui.QTextBrowser):
|
||||
else:
|
||||
self.setHtml(text)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
font.setPixelSize(settings.Settings.get_instance()['message_font_size'])
|
||||
font.setBold(False)
|
||||
self.setFont(font)
|
||||
@ -42,10 +42,31 @@ class MessageEdit(QtGui.QTextBrowser):
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
menu = create_menu(self.createStandardContextMenu(event.pos()))
|
||||
quote = menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Quote selected text'))
|
||||
quote.triggered.connect(self.quote_text)
|
||||
text = self.textCursor().selection().toPlainText()
|
||||
if not text:
|
||||
quote.setEnabled(False)
|
||||
else:
|
||||
import plugin_support
|
||||
submenu = plugin_support.PluginLoader.get_instance().get_message_menu(menu, text)
|
||||
if len(submenu):
|
||||
plug = menu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins'))
|
||||
plug.addActions(submenu)
|
||||
menu.popup(event.globalPos())
|
||||
menu.exec_(event.globalPos())
|
||||
del menu
|
||||
|
||||
def quote_text(self):
|
||||
text = self.textCursor().selection().toPlainText()
|
||||
if text:
|
||||
import mainscreen
|
||||
window = mainscreen.MainWindow.get_instance()
|
||||
text = '>' + '\n>'.join(text.split('\n'))
|
||||
if window.messageEdit.toPlainText():
|
||||
text = '\n' + text
|
||||
window.messageEdit.appendPlainText(text)
|
||||
|
||||
def on_anchor_clicked(self, url):
|
||||
text = str(url.toString())
|
||||
if text.startswith('tox:'):
|
||||
@ -98,26 +119,24 @@ 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, 20))
|
||||
self.name.setGeometry(QtCore.QRect(2, 2, 95, 23))
|
||||
self.name.setTextFormat(QtCore.Qt.PlainText)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
font.setPointSize(11)
|
||||
font.setBold(True)
|
||||
self.name.setFont(font)
|
||||
self.name.setText(user)
|
||||
|
||||
self.time = QtGui.QLabel(self)
|
||||
self.time.setGeometry(QtCore.QRect(parent.width() - 50, 0, 50, 20))
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
self.time = QtWidgets.QLabel(self)
|
||||
self.time.setGeometry(QtCore.QRect(parent.width() - 60, 0, 50, 25))
|
||||
font.setPointSize(10)
|
||||
font.setBold(False)
|
||||
self.time.setFont(font)
|
||||
@ -131,19 +150,19 @@ class MessageItem(QtGui.QWidget):
|
||||
self.time.setText(convert_time(time))
|
||||
self.t = False
|
||||
|
||||
self.message = MessageEdit(text, parent.width() - 150, message_type, self)
|
||||
self.message = MessageEdit(text, parent.width() - 160, message_type, self)
|
||||
if message_type != TOX_MESSAGE_TYPE['NORMAL']:
|
||||
self.name.setStyleSheet("QLabel { color: #5CB3FF; }")
|
||||
self.message.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.time.setStyleSheet("QLabel { color: #5CB3FF; }")
|
||||
self.message.setGeometry(QtCore.QRect(100, 0, parent.width() - 150, self.message.height()))
|
||||
self.message.setGeometry(QtCore.QRect(100, 0, parent.width() - 160, self.message.height()))
|
||||
self.setFixedHeight(self.message.height())
|
||||
|
||||
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()
|
||||
@ -165,27 +184,52 @@ class MessageItem(QtGui.QWidget):
|
||||
self.setFixedHeight(max(self.height(), 36))
|
||||
self.name.setFixedHeight(self.height())
|
||||
self.message.setFixedHeight(self.height())
|
||||
self.name.setPixmap(pixmap.scaled(30, 30, QtCore.Qt.KeepAspectRatio))
|
||||
self.name.setPixmap(pixmap.scaled(30, 30, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))
|
||||
|
||||
def select_text(self, text):
|
||||
tmp = self.message.toHtml()
|
||||
text = h.escape(text)
|
||||
strings = re.findall(text, tmp, flags=re.IGNORECASE)
|
||||
for s in strings:
|
||||
tmp = self.replace_all(tmp, s)
|
||||
self.message.setHtml(tmp)
|
||||
|
||||
@staticmethod
|
||||
def replace_all(text, substring):
|
||||
i, l = 0, len(substring)
|
||||
while i < len(text) - l + 1:
|
||||
index = text[i:].find(substring)
|
||||
if index == -1:
|
||||
break
|
||||
i += index
|
||||
lgt, rgt = text[i:].find('<'), text[i:].find('>')
|
||||
if rgt < lgt:
|
||||
i += rgt + 1
|
||||
continue
|
||||
sub = '<font color="red"><b>{}</b></font>'.format(substring)
|
||||
text = text[:i] + sub + text[i + l:]
|
||||
i += len(sub)
|
||||
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(True)
|
||||
self.avatar_label.setScaledContents(False)
|
||||
self.avatar_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.name = DataLabel(self)
|
||||
self.name.setGeometry(QtCore.QRect(50 if mode else 75, 3 if mode else 10, 150, 15 if mode else 25))
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
font.setPointSize(10 if mode else 12)
|
||||
font.setBold(True)
|
||||
self.name.setFont(font)
|
||||
@ -200,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
|
||||
|
||||
@ -233,16 +277,16 @@ 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()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
font.setPointSize(12)
|
||||
font.setBold(True)
|
||||
self.label.setFont(font)
|
||||
@ -260,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; }')
|
||||
@ -275,24 +319,24 @@ class FileTransferItem(QtGui.QListWidget):
|
||||
self.state = state
|
||||
|
||||
self.name = DataLabel(self)
|
||||
self.name.setGeometry(QtCore.QRect(3, 7, 95, 20))
|
||||
self.name.setGeometry(QtCore.QRect(3, 7, 95, 25))
|
||||
self.name.setTextFormat(QtCore.Qt.PlainText)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
font.setPointSize(11)
|
||||
font.setBold(True)
|
||||
self.name.setFont(font)
|
||||
self.name.setText(user)
|
||||
|
||||
self.time = QtGui.QLabel(self)
|
||||
self.time.setGeometry(QtCore.QRect(width - 53, 7, 50, 20))
|
||||
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.setGeometry(QtCore.QRect(width - 120, 2, 30, 30))
|
||||
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)
|
||||
self.cancel.setIcon(icon)
|
||||
@ -301,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)
|
||||
@ -318,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; }')
|
||||
@ -339,8 +383,8 @@ 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.setGeometry(QtCore.QRect(width - 87, 7, 30, 20))
|
||||
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)
|
||||
self.time_left.setVisible(state == TOX_FILE_TRANSFER_STATE['RUNNING'])
|
||||
@ -357,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()
|
||||
@ -384,13 +428,12 @@ 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)
|
||||
self.time_left.setText('{0:02d}:{1:02d}'.format(m, s))
|
||||
if self.state != state:
|
||||
if self.state != state and self.state in ACTIVE_FILE_TRANSFERS:
|
||||
if state == TOX_FILE_TRANSFER_STATE['CANCELLED']:
|
||||
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
|
||||
self.cancel.setVisible(False)
|
||||
@ -448,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)
|
||||
@ -489,18 +532,15 @@ 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),
|
||||
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)
|
||||
if directory:
|
||||
fl = QtCore.QFile(directory + '/toxygen_inline_' + curr_time().replace(':', '_') + '.png')
|
||||
self._pixmap.save(fl, 'PNG')
|
||||
|
||||
return False
|
||||
|
||||
def mark_as_sent(self):
|
||||
return False
|
||||
|
||||
|
@ -1,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
|
||||
|
193
toxygen/main.py
@ -2,26 +2,24 @@ 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 PyQt5 import QtCore, QtGui, QtWidgets
|
||||
from bootstrap import node_generator
|
||||
from mainscreen import MainWindow
|
||||
from callbacks import init_callbacks
|
||||
from util import curr_directory, program_version
|
||||
from callbacks import init_callbacks, stop, start
|
||||
from util import curr_directory, program_version, remove, is_64_bit
|
||||
import styles.style
|
||||
import platform
|
||||
import toxencryptsave
|
||||
import toxes
|
||||
from passwordscreen import PasswordScreen, UnlockAppScreen, SetProfilePasswordScreen
|
||||
from plugin_support import PluginLoader
|
||||
import updater
|
||||
|
||||
|
||||
class Toxygen:
|
||||
|
||||
def __init__(self, path_or_uri=None):
|
||||
super(Toxygen, self).__init__()
|
||||
self.tox = self.ms = self.init = self.mainloop = self.avloop = None
|
||||
self.tox = self.ms = self.init = self.app = self.tray = self.mainloop = self.avloop = None
|
||||
if path_or_uri is None:
|
||||
self.uri = self.path = None
|
||||
elif path_or_uri.startswith('tox:'):
|
||||
@ -36,9 +34,9 @@ class Toxygen:
|
||||
Show password screen
|
||||
"""
|
||||
tmp = [data]
|
||||
p = PasswordScreen(toxencryptsave.ToxEncryptSave.get_instance(), tmp)
|
||||
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()
|
||||
@ -49,7 +47,7 @@ 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
|
||||
|
||||
@ -61,7 +59,7 @@ class Toxygen:
|
||||
dark_style = fl.read()
|
||||
app.setStyleSheet(dark_style)
|
||||
|
||||
encrypt_save = toxencryptsave.ToxEncryptSave()
|
||||
encrypt_save = toxes.ToxES()
|
||||
|
||||
if self.path is not None:
|
||||
path = os.path.dirname(self.path) + '/'
|
||||
@ -91,7 +89,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
|
||||
@ -100,32 +97,48 @@ 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,
|
||||
reply = QtWidgets.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:
|
||||
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_()
|
||||
ProfileHelper(Settings.get_default_path(), name).save_profile(self.tox.get_savedata())
|
||||
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() + '/'
|
||||
try:
|
||||
ProfileHelper(path, name).save_profile(self.tox.get_savedata())
|
||||
except Exception as ex:
|
||||
print(str(ex))
|
||||
log('Profile creation exception: ' + str(ex))
|
||||
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()
|
||||
settings = Settings(name)
|
||||
if curr_lang in langs:
|
||||
@ -149,12 +162,12 @@ 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,
|
||||
reply = QtWidgets.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:
|
||||
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()
|
||||
@ -166,19 +179,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:
|
||||
@ -192,55 +207,96 @@ 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(toxencryptsave.ToxEncryptSave.get_instance(), correct_pass)
|
||||
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()
|
||||
|
||||
m.connect(show, QtCore.SIGNAL("triggered()"), show_window)
|
||||
m.connect(exit, QtCore.SIGNAL("triggered()"), lambda: app.exit())
|
||||
m.connect(m, QtCore.SIGNAL("aboutToShow()"), lambda: m.aboutToShow())
|
||||
sub.connect(onl, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(0))
|
||||
sub.connect(away, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(1))
|
||||
sub.connect(busy, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(2))
|
||||
def tray_activated(reason):
|
||||
if reason == QtWidgets.QSystemTrayIcon.DoubleClick:
|
||||
show_window()
|
||||
|
||||
def close_app():
|
||||
if not Settings.get_instance().locked:
|
||||
settings.closing = True
|
||||
self.ms.close()
|
||||
|
||||
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()
|
||||
self.tray.activated.connect(tray_activated)
|
||||
|
||||
self.ms.show()
|
||||
|
||||
updating = False
|
||||
if settings['update'] and updater.updater_available() and updater.connection_available(): # auto update
|
||||
version = updater.check_for_updates()
|
||||
if version is not None:
|
||||
if settings['update'] == 2:
|
||||
updater.download(version)
|
||||
updating = True
|
||||
else:
|
||||
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
|
||||
|
||||
if updating:
|
||||
data = self.tox.get_savedata()
|
||||
ProfileHelper.get_instance().save_profile(data)
|
||||
settings.close()
|
||||
del self.tox
|
||||
return
|
||||
|
||||
plugin_helper = PluginLoader(self.tox, settings) # plugin support
|
||||
plugin_helper.load()
|
||||
|
||||
start()
|
||||
# init thread
|
||||
self.init = self.InitThread(self.tox, self.ms, self.tray)
|
||||
self.init.start()
|
||||
@ -254,12 +310,14 @@ 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
|
||||
self.mainloop.stop = True
|
||||
self.avloop.stop = True
|
||||
plugin_helper.stop()
|
||||
stop()
|
||||
self.mainloop.wait()
|
||||
self.init.wait()
|
||||
self.avloop.wait()
|
||||
@ -387,15 +445,13 @@ class Toxygen:
|
||||
def clean():
|
||||
"""Removes all windows libs from libs folder"""
|
||||
d = curr_directory() + '/libs/'
|
||||
for fl in ('libtox64.dll', 'libtox.dll', 'libsodium64.a', 'libsodium.a'):
|
||||
if os.path.exists(d + fl):
|
||||
os.remove(d + fl)
|
||||
remove(d)
|
||||
|
||||
|
||||
def configure():
|
||||
"""Removes unused libs"""
|
||||
d = curr_directory() + '/libs/'
|
||||
is_64bits = sys.maxsize > 2 ** 32
|
||||
is_64bits = is_64_bit()
|
||||
if not is_64bits:
|
||||
if os.path.exists(d + 'libtox64.dll'):
|
||||
os.remove(d + 'libtox64.dll')
|
||||
@ -413,16 +469,20 @@ def configure():
|
||||
pass
|
||||
|
||||
|
||||
def reset():
|
||||
Settings.reset_auto_profile()
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) == 1:
|
||||
toxygen = Toxygen()
|
||||
else: # started with argument(s)
|
||||
arg = sys.argv[1]
|
||||
if arg == '--version':
|
||||
print('Toxygen ' + program_version)
|
||||
print('Toxygen v' + program_version)
|
||||
return
|
||||
elif arg == '--help':
|
||||
print('Usage:\ntoxygen path_to_profile\ntoxygen tox_id\ntoxygen --version')
|
||||
print('Usage:\ntoxygen path_to_profile\ntoxygen tox_id\ntoxygen --version\ntoxygen --reset')
|
||||
return
|
||||
elif arg == '--configure':
|
||||
configure()
|
||||
@ -430,6 +490,9 @@ def main():
|
||||
elif arg == '--clean':
|
||||
clean()
|
||||
return
|
||||
elif arg == '--reset':
|
||||
reset()
|
||||
return
|
||||
else:
|
||||
toxygen = Toxygen(arg)
|
||||
toxygen.main()
|
||||
|
@ -1,61 +1,73 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from menu import *
|
||||
from profile import *
|
||||
from list_items import *
|
||||
from widgets import MultilineEdit, LineEdit
|
||||
from widgets import MultilineEdit, ComboBox
|
||||
import plugin_support
|
||||
from mainscreen_widgets import *
|
||||
import settings
|
||||
import platform
|
||||
import toxes
|
||||
|
||||
|
||||
class MainWindow(QtGui.QMainWindow):
|
||||
class MainWindow(QtWidgets.QMainWindow, Singleton):
|
||||
|
||||
def __init__(self, tox, reset, tray):
|
||||
super(MainWindow, self).__init__()
|
||||
super().__init__()
|
||||
Singleton.__init__(self)
|
||||
self.reset = reset
|
||||
self.tray = tray
|
||||
self.setAcceptDrops(True)
|
||||
self.initUI(tox)
|
||||
self._saved = False
|
||||
if settings.Settings.get_instance()['show_welcome_screen']:
|
||||
self.ws = WelcomeScreen()
|
||||
|
||||
def setup_menu(self, MainWindow):
|
||||
self.menubar = QtGui.QMenuBar(MainWindow)
|
||||
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)
|
||||
def setup_menu(self, Form):
|
||||
box = QtWidgets.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)
|
||||
|
||||
self.menuProfile = QtGui.QMenu(self.menubar)
|
||||
self.menuProfile = QtWidgets.QMenu()
|
||||
self.menuProfile.setObjectName("menuProfile")
|
||||
self.menuSettings = QtGui.QMenu(self.menubar)
|
||||
self.menuSettings = QtWidgets.QMenu()
|
||||
self.menuSettings.setObjectName("menuSettings")
|
||||
self.menuPlugins = QtGui.QMenu(self.menubar)
|
||||
self.menuPlugins = QtWidgets.QMenu()
|
||||
self.menuPlugins.setObjectName("menuPlugins")
|
||||
self.menuAbout = QtGui.QMenu(self.menubar)
|
||||
self.menuAbout = QtWidgets.QMenu()
|
||||
self.menuAbout.setObjectName("menuAbout")
|
||||
|
||||
self.actionAdd_friend = QtGui.QAction(MainWindow)
|
||||
self.actionAdd_friend = QtWidgets.QAction(Form)
|
||||
self.actionAdd_friend.setObjectName("actionAdd_friend")
|
||||
self.actionprofilesettings = QtGui.QAction(MainWindow)
|
||||
self.actionprofilesettings = QtWidgets.QAction(Form)
|
||||
self.actionprofilesettings.setObjectName("actionprofilesettings")
|
||||
self.actionPrivacy_settings = QtGui.QAction(MainWindow)
|
||||
self.actionPrivacy_settings = QtWidgets.QAction(Form)
|
||||
self.actionPrivacy_settings.setObjectName("actionPrivacy_settings")
|
||||
self.actionInterface_settings = QtGui.QAction(MainWindow)
|
||||
self.actionInterface_settings = QtWidgets.QAction(Form)
|
||||
self.actionInterface_settings.setObjectName("actionInterface_settings")
|
||||
self.actionNotifications = QtGui.QAction(MainWindow)
|
||||
self.actionNotifications = QtWidgets.QAction(Form)
|
||||
self.actionNotifications.setObjectName("actionNotifications")
|
||||
self.actionNetwork = QtGui.QAction(MainWindow)
|
||||
self.actionNetwork = QtWidgets.QAction(Form)
|
||||
self.actionNetwork.setObjectName("actionNetwork")
|
||||
self.actionAbout_program = QtGui.QAction(MainWindow)
|
||||
self.actionAbout_program = QtWidgets.QAction(Form)
|
||||
self.actionAbout_program.setObjectName("actionAbout_program")
|
||||
self.actionSettings = QtGui.QAction(MainWindow)
|
||||
self.updateSettings = QtWidgets.QAction(Form)
|
||||
self.actionSettings = QtWidgets.QAction(Form)
|
||||
self.actionSettings.setObjectName("actionSettings")
|
||||
self.audioSettings = QtGui.QAction(MainWindow)
|
||||
self.pluginData = QtGui.QAction(MainWindow)
|
||||
self.importPlugin = QtGui.QAction(MainWindow)
|
||||
self.lockApp = QtGui.QAction(MainWindow)
|
||||
self.audioSettings = QtWidgets.QAction(Form)
|
||||
self.videoSettings = QtWidgets.QAction(Form)
|
||||
self.pluginData = QtWidgets.QAction(Form)
|
||||
self.importPlugin = QtWidgets.QAction(Form)
|
||||
self.reloadPlugins = QtWidgets.QAction(Form)
|
||||
self.lockApp = QtWidgets.QAction(Form)
|
||||
self.menuProfile.addAction(self.actionAdd_friend)
|
||||
self.menuProfile.addAction(self.actionSettings)
|
||||
self.menuProfile.addAction(self.lockApp)
|
||||
@ -64,26 +76,35 @@ class MainWindow(QtGui.QMainWindow):
|
||||
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.menubar.addAction(self.menuProfile.menuAction())
|
||||
self.menubar.addAction(self.menuSettings.menuAction())
|
||||
self.menubar.addAction(self.menuPlugins.menuAction())
|
||||
self.menubar.addAction(self.menuAbout.menuAction())
|
||||
|
||||
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.actionAbout_program.triggered.connect(self.about_program)
|
||||
self.actionNetwork.triggered.connect(self.network_settings)
|
||||
self.actionAdd_friend.triggered.connect(self.add_contact)
|
||||
self.actionSettings.triggered.connect(self.profilesettings)
|
||||
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)
|
||||
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
||||
self.reloadPlugins.triggered.connect(self.reload_plugins)
|
||||
|
||||
Form.setLayout(box)
|
||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||
|
||||
def languageChange(self, *args, **kwargs):
|
||||
self.retranslateUi()
|
||||
@ -91,32 +112,42 @@ class MainWindow(QtGui.QMainWindow):
|
||||
def event(self, event):
|
||||
if event.type() == QtCore.QEvent.WindowActivate:
|
||||
self.tray.setIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
|
||||
self.messages.repaint()
|
||||
return super(MainWindow, self).event(event)
|
||||
|
||||
def retranslateUi(self):
|
||||
self.lockApp.setText(QtGui.QApplication.translate("MainWindow", "Lock", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.menuPlugins.setTitle(QtGui.QApplication.translate("MainWindow", "Plugins", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.pluginData.setText(QtGui.QApplication.translate("MainWindow", "List of plugins", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.menuProfile.setTitle(QtGui.QApplication.translate("MainWindow", "Profile", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.menuSettings.setTitle(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.menuAbout.setTitle(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8))
|
||||
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.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.plugins_button.setText(QtWidgets.QApplication.translate("MainWindow", "Plugins"))
|
||||
self.pluginData.setText(QtWidgets.QApplication.translate("MainWindow", "List of plugins"))
|
||||
self.profile_button.setText(QtWidgets.QApplication.translate("MainWindow", "Profile"))
|
||||
self.settings_button.setText(QtWidgets.QApplication.translate("MainWindow", "Settings"))
|
||||
self.about_button.setText(QtWidgets.QApplication.translate("MainWindow", "About"))
|
||||
self.actionAdd_friend.setText(QtWidgets.QApplication.translate("MainWindow", "Add contact"))
|
||||
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.setCurrentIndex(int(Settings.get_instance()['show_online_friends']))
|
||||
self.importPlugin.setText(QtGui.QApplication.translate("MainWindow", "Import plugin", 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(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)
|
||||
@ -124,10 +155,11 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.messageEdit.setGeometry(QtCore.QRect(0, 3, 450, 55))
|
||||
self.messageEdit.setObjectName("messageEdit")
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(10)
|
||||
font.setPointSize(11)
|
||||
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")
|
||||
|
||||
@ -150,7 +182,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||
|
||||
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')
|
||||
@ -162,7 +194,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.contact_name.setObjectName("contact_name")
|
||||
self.contact_name.textChanged.connect(self.filtering)
|
||||
|
||||
self.online_contacts = QtGui.QComboBox(Form)
|
||||
self.online_contacts = ComboBox(Form)
|
||||
self.online_contacts.setGeometry(QtCore.QRect(150, 0, 120, 25))
|
||||
self.online_contacts.activated[int].connect(lambda x: self.filtering())
|
||||
self.search_label.raise_()
|
||||
@ -171,67 +203,67 @@ class MainWindow(QtGui.QMainWindow):
|
||||
|
||||
def setup_left_top(self, Form):
|
||||
Form.setCursor(QtCore.Qt.PointingHandCursor)
|
||||
Form.setMinimumSize(QtCore.QSize(270, 100))
|
||||
Form.setMaximumSize(QtCore.QSize(270, 100))
|
||||
Form.setBaseSize(QtCore.QSize(270, 100))
|
||||
self.avatar_label = Form.avatar_label = QtGui.QLabel(Form)
|
||||
self.avatar_label.setGeometry(QtCore.QRect(5, 30, 64, 64))
|
||||
self.avatar_label.setScaledContents(True)
|
||||
Form.setMinimumSize(QtCore.QSize(270, 75))
|
||||
Form.setMaximumSize(QtCore.QSize(270, 75))
|
||||
Form.setBaseSize(QtCore.QSize(270, 75))
|
||||
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)
|
||||
self.name = Form.name = DataLabel(Form)
|
||||
Form.name.setGeometry(QtCore.QRect(75, 40, 150, 25))
|
||||
Form.name.setGeometry(QtCore.QRect(75, 15, 150, 25))
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
font.setPointSize(14)
|
||||
font.setBold(True)
|
||||
Form.name.setFont(font)
|
||||
Form.name.setObjectName("name")
|
||||
self.status_message = Form.status_message = DataLabel(Form)
|
||||
Form.status_message.setGeometry(QtCore.QRect(75, 60, 170, 25))
|
||||
Form.status_message.setGeometry(QtCore.QRect(75, 35, 170, 25))
|
||||
font.setPointSize(12)
|
||||
font.setBold(False)
|
||||
Form.status_message.setFont(font)
|
||||
Form.status_message.setObjectName("status_message")
|
||||
self.connection_status = Form.connection_status = StatusCircle(Form)
|
||||
Form.connection_status.setGeometry(QtCore.QRect(230, 35, 32, 32))
|
||||
self.avatar_label.mouseReleaseEvent = self.profilesettings
|
||||
self.status_message.mouseReleaseEvent = self.profilesettings
|
||||
self.name.mouseReleaseEvent = self.profilesettings
|
||||
Form.connection_status.setGeometry(QtCore.QRect(230, 10, 32, 32))
|
||||
self.avatar_label.mouseReleaseEvent = self.profile_settings
|
||||
self.status_message.mouseReleaseEvent = self.profile_settings
|
||||
self.name.mouseReleaseEvent = self.profile_settings
|
||||
self.connection_status.raise_()
|
||||
Form.connection_status.setObjectName("connection_status")
|
||||
|
||||
def setup_right_top(self, Form):
|
||||
Form.resize(650, 100)
|
||||
self.account_avatar = QtGui.QLabel(Form)
|
||||
self.account_avatar.setGeometry(QtCore.QRect(10, 30, 64, 64))
|
||||
self.account_avatar.setScaledContents(True)
|
||||
Form.resize(650, 75)
|
||||
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)
|
||||
self.account_name.setGeometry(QtCore.QRect(100, 25, 400, 25))
|
||||
self.account_name.setGeometry(QtCore.QRect(100, 0, 400, 25))
|
||||
self.account_name.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
font.setPointSize(14)
|
||||
font.setBold(True)
|
||||
self.account_name.setFont(font)
|
||||
self.account_name.setObjectName("account_name")
|
||||
self.account_status = DataLabel(Form)
|
||||
self.account_status.setGeometry(QtCore.QRect(100, 45, 400, 25))
|
||||
self.account_status.setGeometry(QtCore.QRect(100, 20, 400, 25))
|
||||
self.account_status.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse)
|
||||
font.setPointSize(12)
|
||||
font.setBold(False)
|
||||
self.account_status.setFont(font)
|
||||
self.account_status.setObjectName("account_status")
|
||||
self.callButton = QtGui.QPushButton(Form)
|
||||
self.callButton.setGeometry(QtCore.QRect(550, 30, 50, 50))
|
||||
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.setGeometry(QtCore.QRect(550, 30, 50, 50))
|
||||
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.setGeometry(QtCore.QRect(500, 50, 50, 30))
|
||||
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')
|
||||
self.typing.setScaledContents(False)
|
||||
@ -240,30 +272,34 @@ class MainWindow(QtGui.QMainWindow):
|
||||
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)
|
||||
self.messages.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||
self.messages.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.messages.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.messages.focusOutEvent = lambda event: self.messages.clearSelection()
|
||||
self.messages.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu)
|
||||
|
||||
def load(pos):
|
||||
if not pos:
|
||||
self.profile.load_history()
|
||||
self.messages.verticalScrollBar().setValue(1)
|
||||
self.messages.verticalScrollBar().valueChanged.connect(load)
|
||||
self.messages.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
|
||||
self.messages.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||
self.messages.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
|
||||
def initUI(self, tox):
|
||||
self.setMinimumSize(920, 500)
|
||||
@ -271,86 +307,115 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.setGeometry(s['x'], s['y'], s['width'], s['height'])
|
||||
self.setWindowTitle('Toxygen')
|
||||
os.chdir(curr_directory() + '/images/')
|
||||
main = QtGui.QWidget()
|
||||
grid = QtGui.QGridLayout()
|
||||
search = QtGui.QWidget()
|
||||
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)
|
||||
self.setup_right_top(info)
|
||||
self.setup_right_bottom(message_buttons)
|
||||
self.setup_left_center(main_list)
|
||||
self.setup_menu(menu)
|
||||
if not Settings.get_instance()['mirror_mode']:
|
||||
grid.addWidget(search, 1, 0)
|
||||
grid.addWidget(name, 0, 0)
|
||||
grid.addWidget(messages, 1, 1, 2, 1)
|
||||
grid.addWidget(info, 0, 1)
|
||||
grid.addWidget(message_buttons, 3, 1)
|
||||
grid.addWidget(main_list, 2, 0, 2, 1)
|
||||
grid.addWidget(search, 2, 0)
|
||||
grid.addWidget(name, 1, 0)
|
||||
grid.addWidget(messages, 2, 1, 2, 1)
|
||||
grid.addWidget(info, 1, 1)
|
||||
grid.addWidget(message_buttons, 4, 1)
|
||||
grid.addWidget(main_list, 3, 0, 2, 1)
|
||||
grid.setColumnMinimumWidth(1, 500)
|
||||
grid.setColumnMinimumWidth(0, 270)
|
||||
else:
|
||||
grid.addWidget(search, 1, 1)
|
||||
grid.addWidget(name, 0, 1)
|
||||
grid.addWidget(messages, 1, 0, 2, 1)
|
||||
grid.addWidget(info, 0, 0)
|
||||
grid.addWidget(message_buttons, 3, 0)
|
||||
grid.addWidget(main_list, 2, 1, 2, 1)
|
||||
grid.addWidget(search, 2, 1)
|
||||
grid.addWidget(name, 1, 1)
|
||||
grid.addWidget(messages, 2, 0, 2, 1)
|
||||
grid.addWidget(info, 1, 0)
|
||||
grid.addWidget(message_buttons, 4, 0)
|
||||
grid.addWidget(main_list, 3, 1, 2, 1)
|
||||
grid.setColumnMinimumWidth(0, 500)
|
||||
grid.setColumnMinimumWidth(1, 270)
|
||||
|
||||
grid.addWidget(menu, 0, 0, 1, 2)
|
||||
grid.setSpacing(0)
|
||||
grid.setContentsMargins(0, 0, 0, 0)
|
||||
grid.setRowMinimumHeight(0, 100)
|
||||
grid.setRowMinimumHeight(1, 25)
|
||||
grid.setRowMinimumHeight(2, 320)
|
||||
grid.setRowMinimumHeight(3, 55)
|
||||
grid.setRowMinimumHeight(0, 25)
|
||||
grid.setRowMinimumHeight(1, 75)
|
||||
grid.setRowMinimumHeight(2, 25)
|
||||
grid.setRowMinimumHeight(3, 320)
|
||||
grid.setRowMinimumHeight(4, 55)
|
||||
grid.setColumnStretch(1, 1)
|
||||
grid.setRowStretch(2, 1)
|
||||
grid.setRowStretch(3, 1)
|
||||
main.setLayout(grid)
|
||||
self.setCentralWidget(main)
|
||||
self.setup_menu(self)
|
||||
self.messageEdit.setFocus()
|
||||
self.user_info = name
|
||||
self.friend_info = info
|
||||
self.retranslateUi()
|
||||
self.profile = Profile(tox, self)
|
||||
|
||||
def closeEvent(self, *args, **kwargs):
|
||||
def closeEvent(self, event):
|
||||
s = Settings.get_instance()
|
||||
if not s['close_to_tray'] or s.closing:
|
||||
if not self._saved:
|
||||
self._saved = True
|
||||
self.profile.save_history()
|
||||
self.profile.close()
|
||||
s = Settings.get_instance()
|
||||
s['x'] = self.pos().x()
|
||||
s['y'] = self.pos().y()
|
||||
s['x'] = self.geometry().x()
|
||||
s['y'] = self.geometry().y()
|
||||
s['width'] = self.width()
|
||||
s['height'] = self.height()
|
||||
s.save()
|
||||
QtGui.QApplication.closeAllWindows()
|
||||
QtWidgets.QApplication.closeAllWindows()
|
||||
event.accept()
|
||||
elif QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
|
||||
event.ignore()
|
||||
self.hide()
|
||||
|
||||
def close_window(self):
|
||||
Settings.get_instance().closing = True
|
||||
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.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 40, 50, 50))
|
||||
self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 40, 50, 50))
|
||||
self.typing.setGeometry(QtCore.QRect(self.width() - 450, 50, 50, 30))
|
||||
self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 10, 50, 50))
|
||||
self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 10, 50, 50))
|
||||
self.typing.setGeometry(QtCore.QRect(self.width() - 450, 20, 50, 30))
|
||||
|
||||
self.messageEdit.setGeometry(QtCore.QRect(55, 0, self.width() - 395, 55))
|
||||
self.menuButton.setGeometry(QtCore.QRect(0, 0, 55, 55))
|
||||
self.sendMessageButton.setGeometry(QtCore.QRect(self.width() - 340, 0, 70, 55))
|
||||
|
||||
self.account_name.setGeometry(QtCore.QRect(100, 40, self.width() - 560, 25))
|
||||
self.account_status.setGeometry(QtCore.QRect(100, 60, self.width() - 560, 25))
|
||||
self.account_name.setGeometry(QtCore.QRect(100, 15, self.width() - 560, 25))
|
||||
self.account_status.setGeometry(QtCore.QRect(100, 35, self.width() - 560, 25))
|
||||
self.messageEdit.setFocus()
|
||||
self.profile.update()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == QtCore.Qt.Key_Escape:
|
||||
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 = 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()
|
||||
elif event.key() == QtCore.Qt.Key_F and event.modifiers() & QtCore.Qt.ControlModifier:
|
||||
self.show_search_field()
|
||||
else:
|
||||
super(MainWindow, self).keyPressEvent(event)
|
||||
|
||||
@ -360,10 +425,10 @@ class MainWindow(QtGui.QMainWindow):
|
||||
|
||||
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.setText(text + util.program_version + '\nGitHub: github.com/xveduk/toxygen/')
|
||||
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_()
|
||||
|
||||
def network_settings(self):
|
||||
@ -375,10 +440,10 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.p_s.show()
|
||||
|
||||
def add_contact(self, link=''):
|
||||
self.a_c = AddContact(link)
|
||||
self.a_c = AddContact(link or '')
|
||||
self.a_c.show()
|
||||
|
||||
def profilesettings(self, *args):
|
||||
def profile_settings(self, *args):
|
||||
self.p_s = ProfileSettings()
|
||||
self.p_s.show()
|
||||
|
||||
@ -398,37 +463,46 @@ class MainWindow(QtGui.QMainWindow):
|
||||
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):
|
||||
if toxencryptsave.ToxEncryptSave.get_instance().has_password():
|
||||
if toxes.ToxES.get_instance().has_password():
|
||||
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):
|
||||
@ -451,8 +525,8 @@ class MainWindow(QtGui.QMainWindow):
|
||||
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)
|
||||
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])
|
||||
|
||||
@ -493,14 +567,15 @@ class MainWindow(QtGui.QMainWindow):
|
||||
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))
|
||||
@ -513,34 +588,47 @@ class MainWindow(QtGui.QMainWindow):
|
||||
item = self.friends_list.itemAt(pos)
|
||||
num = self.friends_list.indexFromItem(item).row()
|
||||
friend = Profile.get_instance().get_friend(num)
|
||||
if friend is None:
|
||||
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))
|
||||
clear_history_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8))
|
||||
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))
|
||||
self.listMenu = QtWidgets.QMenu()
|
||||
set_alias_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Set alias'))
|
||||
|
||||
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(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'))
|
||||
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))
|
||||
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)
|
||||
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(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8))
|
||||
plug = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins'))
|
||||
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))
|
||||
set_alias_item.triggered.connect(lambda: self.set_alias(num))
|
||||
remove_item.triggered.connect(lambda: self.remove_friend(num))
|
||||
block_item.triggered.connect(lambda: self.block_friend(num))
|
||||
copy_key_item.triggered.connect(lambda: self.copy_friend_key(num))
|
||||
clear_history_item.triggered.connect(lambda: self.clear_history(num))
|
||||
auto_accept_item.triggered.connect(lambda: self.auto_accept(num, not allowed))
|
||||
notes_item.triggered.connect(lambda: self.show_note(friend))
|
||||
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()
|
||||
@ -548,7 +636,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||
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):
|
||||
@ -560,23 +648,40 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.note = MultilineEdit(user, note, save_note)
|
||||
self.note.show()
|
||||
|
||||
def export_history(self, num, as_text=True):
|
||||
s = self.profile.export_history(num, as_text)
|
||||
directory = QtWidgets.QFileDialog.getExistingDirectory(None,
|
||||
QtWidgets.QApplication.translate("MainWindow",
|
||||
'Choose folder'),
|
||||
curr_directory(),
|
||||
QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
|
||||
|
||||
if directory:
|
||||
name = 'exported_history_{}.{}'.format(convert_time(time.time()), 'txt' if as_text else 'html')
|
||||
with open(directory + '/' + name, 'wt') as fl:
|
||||
fl.write(s)
|
||||
|
||||
def set_alias(self, num):
|
||||
self.profile.set_alias(num)
|
||||
|
||||
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):
|
||||
@ -607,6 +712,22 @@ class MainWindow(QtGui.QMainWindow):
|
||||
else:
|
||||
super(MainWindow, self).mouseReleaseEvent(event)
|
||||
|
||||
def filtering(self):
|
||||
self.profile.filtration(self.online_contacts.currentIndex() == 1, self.contact_name.text())
|
||||
def show(self):
|
||||
super().show()
|
||||
self.profile.update()
|
||||
|
||||
def filtering(self):
|
||||
ind = self.online_contacts.currentIndex()
|
||||
d = {0: 0, 1: 1, 2: 2, 3: 4, 4: 1 | 4, 5: 2 | 4}
|
||||
self.profile.filtration_and_sorting(d[ind], self.contact_name.text())
|
||||
|
||||
def show_search_field(self):
|
||||
if hasattr(self, 'search_field') and self.search_field.isVisible():
|
||||
return
|
||||
if self.profile.get_curr_friend() is None:
|
||||
return
|
||||
self.search_field = SearchScreen(self.messages, self.messages.width(), self.messages.parent())
|
||||
x, y = self.messages.x(), self.messages.y() + self.messages.height() - 40
|
||||
self.search_field.setGeometry(x, y, self.messages.width(), 40)
|
||||
self.messages.setGeometry(x, self.messages.y(), self.messages.width(), self.messages.height() - 40)
|
||||
self.search_field.show()
|
||||
|
@ -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
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
from widgets import RubberBand, 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,6 +17,11 @@ class MessageArea(QtGui.QPlainTextEdit):
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.matches(QtGui.QKeySequence.Paste):
|
||||
mimeData = QtWidgets.QApplication.clipboard().mimeData()
|
||||
if mimeData.hasUrls():
|
||||
for url in mimeData.urls():
|
||||
self.pasteEvent(url.toString())
|
||||
else:
|
||||
self.pasteEvent()
|
||||
elif event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
|
||||
modifiers = event.modifiers()
|
||||
@ -51,21 +53,25 @@ class MessageArea(QtGui.QPlainTextEdit):
|
||||
e.accept()
|
||||
|
||||
def dropEvent(self, e):
|
||||
if e.mimeData().hasFormat('text/plain'):
|
||||
if e.mimeData().hasFormat('text/plain') or e.mimeData().hasFormat('text/html'):
|
||||
e.accept()
|
||||
self.pasteEvent(e.mimeData().text())
|
||||
elif e.mimeData().hasUrls():
|
||||
for url in e.mimeData().urls():
|
||||
self.pasteEvent(url.toString())
|
||||
e.accept()
|
||||
else:
|
||||
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):
|
||||
class ScreenShotWindow(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, parent):
|
||||
super(ScreenShotWindow, self).__init__()
|
||||
@ -75,6 +81,8 @@ class ScreenShotWindow(QtGui.QWidget):
|
||||
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 closeEvent(self, *args):
|
||||
if self.parent.isHidden():
|
||||
@ -84,7 +92,7 @@ class ScreenShotWindow(QtGui.QWidget):
|
||||
self.origin = event.pos()
|
||||
self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize()))
|
||||
self.rubberband.show()
|
||||
QtGui.QWidget.mousePressEvent(self, event)
|
||||
QtWidgets.QWidget.mousePressEvent(self, event)
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
if self.rubberband.isVisible():
|
||||
@ -100,7 +108,8 @@ class ScreenShotWindow(QtGui.QWidget):
|
||||
self.rubberband.hide()
|
||||
rect = self.rubberband.geometry()
|
||||
if rect.width() and rect.height():
|
||||
p = QtGui.QPixmap.grabWindow(QtGui.QApplication.desktop().winId(),
|
||||
screen = QtWidgets.QApplication.primaryScreen()
|
||||
p = screen.grabWindow(0,
|
||||
rect.x() + 4,
|
||||
rect.y() + 4,
|
||||
rect.width() - 8,
|
||||
@ -120,7 +129,7 @@ class ScreenShotWindow(QtGui.QWidget):
|
||||
super(ScreenShotWindow, self).keyPressEvent(event)
|
||||
|
||||
|
||||
class SmileyWindow(QtGui.QWidget):
|
||||
class SmileyWindow(QtWidgets.QWidget):
|
||||
"""
|
||||
Smiley selection window
|
||||
"""
|
||||
@ -142,7 +151,7 @@ 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))
|
||||
self.radio.append(elem)
|
||||
@ -151,7 +160,7 @@ class SmileyWindow(QtGui.QWidget):
|
||||
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))
|
||||
self.buttons.append(b)
|
||||
@ -181,7 +190,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)
|
||||
@ -192,86 +201,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)
|
||||
@ -281,7 +277,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):
|
||||
@ -289,16 +285,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
|
||||
@ -320,60 +316,44 @@ 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)
|
||||
elif num == 6:
|
||||
text = QtGui.QApplication.translate('WelcomeScreen',
|
||||
'New in Toxygen v0.2.3:<br>TCS compliance<br>Plugins, smileys and stickers import<br>Bug fixes',
|
||||
None, QtGui.QApplication.UnicodeUTF8)
|
||||
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',
|
||||
'Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/xveduk/toxygen/blob/master/docs/plugins.md">Read more</a>')
|
||||
elif num in (6, 7):
|
||||
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 == 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)
|
||||
@ -384,3 +364,138 @@ class WelcomeScreen(CenteredWidget):
|
||||
s['show_welcome_screen'] = False
|
||||
s.save()
|
||||
|
||||
|
||||
class MainMenuButton(QtWidgets.QPushButton):
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
self.setObjectName("mainmenubutton")
|
||||
|
||||
def setText(self, text):
|
||||
metrics = QtGui.QFontMetrics(self.font())
|
||||
self.setFixedWidth(metrics.size(QtCore.Qt.TextSingleLine, text).width() + 20)
|
||||
super().setText(text)
|
||||
|
||||
|
||||
class ClickableLabel(QtWidgets.QLabel):
|
||||
|
||||
clicked = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
|
||||
def mouseReleaseEvent(self, ev):
|
||||
self.clicked.emit()
|
||||
|
||||
|
||||
class SearchScreen(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, messages, width, *args):
|
||||
super().__init__(*args)
|
||||
self.setMaximumSize(width, 40)
|
||||
self.setMinimumSize(width, 40)
|
||||
self._messages = messages
|
||||
|
||||
self.search_text = LineEdit(self)
|
||||
self.search_text.setGeometry(0, 0, width - 160, 40)
|
||||
|
||||
self.search_button = ClickableLabel(self)
|
||||
self.search_button.setGeometry(width - 160, 0, 40, 40)
|
||||
pixmap = QtGui.QPixmap()
|
||||
pixmap.load(util.curr_directory() + '/images/search.png')
|
||||
self.search_button.setScaledContents(False)
|
||||
self.search_button.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.search_button.setPixmap(pixmap)
|
||||
self.search_button.clicked.connect(self.search)
|
||||
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(32)
|
||||
font.setBold(True)
|
||||
|
||||
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 = 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 = QtWidgets.QPushButton(self)
|
||||
self.close_button.setGeometry(width - 40, 0, 40, 40)
|
||||
self.close_button.clicked.connect(self.close)
|
||||
self.close_button.setText('×')
|
||||
self.close_button.setFont(font)
|
||||
|
||||
font.setPointSize(18)
|
||||
self.next_button.setFont(font)
|
||||
self.prev_button.setFont(font)
|
||||
|
||||
self.retranslateUi()
|
||||
|
||||
def retranslateUi(self):
|
||||
self.search_text.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "Search"))
|
||||
|
||||
def show(self):
|
||||
super().show()
|
||||
self.search_text.setFocus()
|
||||
|
||||
def search(self):
|
||||
Profile.get_instance().update()
|
||||
text = self.search_text.text()
|
||||
friend = Profile.get_instance().get_curr_friend()
|
||||
if text and friend and util.is_re_valid(text):
|
||||
index = friend.search_string(text)
|
||||
self.load_messages(index)
|
||||
|
||||
def prev(self):
|
||||
friend = Profile.get_instance().get_curr_friend()
|
||||
if friend is not None:
|
||||
index = friend.search_prev()
|
||||
self.load_messages(index)
|
||||
|
||||
def next(self):
|
||||
friend = Profile.get_instance().get_curr_friend()
|
||||
text = self.search_text.text()
|
||||
if friend is not None:
|
||||
index = friend.search_next()
|
||||
if index is not None:
|
||||
count = self._messages.count()
|
||||
index += count
|
||||
item = self._messages.item(index)
|
||||
self._messages.scrollToItem(item)
|
||||
self._messages.itemWidget(item).select_text(text)
|
||||
else:
|
||||
self.not_found(text)
|
||||
|
||||
def load_messages(self, index):
|
||||
text = self.search_text.text()
|
||||
if index is not None:
|
||||
profile = Profile.get_instance()
|
||||
count = self._messages.count()
|
||||
while count + index < 0:
|
||||
profile.load_history()
|
||||
count = self._messages.count()
|
||||
index += count
|
||||
item = self._messages.item(index)
|
||||
self._messages.scrollToItem(item)
|
||||
self._messages.itemWidget(item).select_text(text)
|
||||
else:
|
||||
self.not_found(text)
|
||||
|
||||
def closeEvent(self, *args):
|
||||
Profile.get_instance().update()
|
||||
self._messages.setGeometry(0, 0, self._messages.width(), self._messages.height() + 40)
|
||||
super().closeEvent(*args)
|
||||
|
||||
@staticmethod
|
||||
def not_found(text):
|
||||
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_()
|
||||
|
628
toxygen/menu.py
@ -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:
|
||||
@ -45,8 +42,7 @@ class AudioFile:
|
||||
format=self.p.get_format_from_width(self.wf.getsampwidth()),
|
||||
channels=self.wf.getnchannels(),
|
||||
rate=self.wf.getframerate(),
|
||||
output=True
|
||||
)
|
||||
output=True)
|
||||
|
||||
def play(self):
|
||||
data = self.wf.readframes(self.chunk)
|
||||
|
@ -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)
|
||||
|
@ -4,7 +4,7 @@ import os
|
||||
import importlib
|
||||
import inspect
|
||||
import plugins.plugin_super_class as pl
|
||||
import toxencryptsave
|
||||
import toxes
|
||||
import sys
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ class PluginLoader(util.Singleton):
|
||||
self._settings = settings
|
||||
self._plugins = {} # dict. key - plugin unique short name, value - tuple (plugin instance, is active)
|
||||
self._tox = tox
|
||||
self._encr = toxencryptsave.ToxEncryptSave.get_instance()
|
||||
self._encr = toxes.ToxES.get_instance()
|
||||
|
||||
def set_tox(self, tox):
|
||||
"""
|
||||
@ -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)
|
||||
@ -64,23 +65,23 @@ class PluginLoader(util.Singleton):
|
||||
self._plugins[inst.get_short_name()] = [inst, autostart] # (inst, is active)
|
||||
break
|
||||
|
||||
def callback_lossless(self, friend_number, data, length):
|
||||
def callback_lossless(self, friend_number, data):
|
||||
"""
|
||||
New incoming custom lossless packet (callback)
|
||||
"""
|
||||
l = data[0] - pl.LOSSLESS_FIRST_BYTE
|
||||
name = ''.join(chr(x) for x in data[1:l + 1])
|
||||
if name in self._plugins and self._plugins[name][1]:
|
||||
self._plugins[name][0].lossless_packet(''.join(chr(x) for x in data[l + 1:length]), friend_number)
|
||||
self._plugins[name][0].lossless_packet(''.join(chr(x) for x in data[l + 1:]), friend_number)
|
||||
|
||||
def callback_lossy(self, friend_number, data, length):
|
||||
def callback_lossy(self, friend_number, data):
|
||||
"""
|
||||
New incoming custom lossy packet (callback)
|
||||
"""
|
||||
l = data[0] - pl.LOSSY_FIRST_BYTE
|
||||
name = ''.join(chr(x) for x in data[1:l + 1])
|
||||
if name in self._plugins and self._plugins[name][1]:
|
||||
self._plugins[name][0].lossy_packet(''.join(chr(x) for x in data[l + 1:length]), friend_number)
|
||||
self._plugins[name][0].lossy_packet(''.join(chr(x) for x in data[l + 1:]), friend_number)
|
||||
|
||||
def friend_online(self, friend_number):
|
||||
"""
|
||||
@ -147,6 +148,16 @@ class PluginLoader(util.Singleton):
|
||||
continue
|
||||
return result
|
||||
|
||||
def get_message_menu(self, menu, selected_text):
|
||||
result = []
|
||||
for elem in self._plugins.values():
|
||||
if elem[1]:
|
||||
try:
|
||||
result.extend(elem[0].get_message_menu(menu, selected_text))
|
||||
except:
|
||||
continue
|
||||
return result
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
App is closing, stop all plugins
|
||||
@ -155,3 +166,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()
|
||||
|
@ -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
|
||||
@ -26,12 +23,12 @@ def log(name, data):
|
||||
:param data: data for saving in log
|
||||
"""
|
||||
with open(path_to_data(name) + 'logs.txt', 'a') as fl:
|
||||
fl.write(bytes(data, 'utf-8') + b'\n')
|
||||
fl.write(str(data) + '\n')
|
||||
|
||||
|
||||
class PluginSuperClass:
|
||||
"""
|
||||
Superclass for all plugins. Plugin is python module with at least one class derived from PluginSuperClass.
|
||||
Superclass for all plugins. Plugin is Python3 module with at least one class derived from PluginSuperClass.
|
||||
"""
|
||||
is_plugin = True
|
||||
|
||||
@ -43,7 +40,7 @@ class PluginSuperClass:
|
||||
:param tox: tox instance
|
||||
:param profile: profile instance
|
||||
:param settings: profile settings
|
||||
:param encrypt_save: LibToxEncryptSave instance.
|
||||
:param encrypt_save: ToxES instance.
|
||||
"""
|
||||
self._settings = settings
|
||||
self._profile = profile
|
||||
@ -88,6 +85,15 @@ class PluginSuperClass:
|
||||
"""
|
||||
return []
|
||||
|
||||
def get_message_menu(self, menu, text):
|
||||
"""
|
||||
This method creates items for menu which called on right click in message
|
||||
:param menu: menu instance
|
||||
:param text: selected text
|
||||
:return list of QAction's
|
||||
"""
|
||||
return []
|
||||
|
||||
def get_window(self):
|
||||
"""
|
||||
This method should return window for plugins with GUI or None
|
||||
@ -120,7 +126,7 @@ class PluginSuperClass:
|
||||
"""
|
||||
App is closing
|
||||
"""
|
||||
pass
|
||||
self.stop()
|
||||
|
||||
def command(self, command):
|
||||
"""
|
||||
@ -128,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_()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
@ -142,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:
|
||||
@ -160,6 +166,7 @@ class PluginSuperClass:
|
||||
def load_settings(self):
|
||||
"""
|
||||
This method loads settings of plugin and returns raw data
|
||||
If file doesn't exist this method raises exception
|
||||
"""
|
||||
with open(path_to_data(self._short_name) + 'settings.json', 'rb') as fl:
|
||||
data = fl.read()
|
||||
|
@ -1,10 +1,9 @@
|
||||
from platform import system
|
||||
import json
|
||||
import os
|
||||
import locale
|
||||
from util import Singleton, curr_directory, log
|
||||
from util import Singleton, curr_directory, log, copy, append_slash
|
||||
import pyaudio
|
||||
from toxencryptsave import ToxEncryptSave
|
||||
from toxes import ToxES
|
||||
import smileys
|
||||
|
||||
|
||||
@ -20,7 +19,7 @@ class Settings(dict, Singleton):
|
||||
if os.path.isfile(self.path):
|
||||
with open(self.path, 'rb') as fl:
|
||||
data = fl.read()
|
||||
inst = ToxEncryptSave.get_instance()
|
||||
inst = ToxES.get_instance()
|
||||
try:
|
||||
if inst.is_data_encrypted(data):
|
||||
data = inst.pass_decrypt(data)
|
||||
@ -34,16 +33,27 @@ class Settings(dict, Singleton):
|
||||
super(Settings, self).__init__(Settings.get_default_settings())
|
||||
self.save()
|
||||
smileys.SmileyLoader(self)
|
||||
p = pyaudio.PyAudio()
|
||||
self.locked = False
|
||||
self.audio = {'input': p.get_default_input_device_info()['index'],
|
||||
'output': p.get_default_output_device_info()['index']}
|
||||
self.closing = False
|
||||
self.unlockScreen = False
|
||||
p = pyaudio.PyAudio()
|
||||
input_devices = output_devices = 0
|
||||
for i in range(p.get_device_count()):
|
||||
device = p.get_device_info_by_index(i)
|
||||
if device["maxInputChannels"]:
|
||||
input_devices += 1
|
||||
if device["maxOutputChannels"]:
|
||||
output_devices += 1
|
||||
self.audio = {'input': p.get_default_input_device_info()['index'] if input_devices else -1,
|
||||
'output': p.get_default_output_device_info()['index'] if output_devices else -1,
|
||||
'enabled': input_devices and output_devices}
|
||||
self.video = {'device': 0, 'width': 640, 'height': 480}
|
||||
|
||||
@staticmethod
|
||||
def get_auto_profile():
|
||||
path = Settings.get_default_path() + 'toxygen.json'
|
||||
if os.path.isfile(path):
|
||||
with open(path) as fl:
|
||||
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:
|
||||
@ -52,10 +62,13 @@ class Settings(dict, Singleton):
|
||||
|
||||
@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()
|
||||
data = json.loads(data)
|
||||
else:
|
||||
data = {}
|
||||
data['path'] = str(path)
|
||||
data['name'] = str(name)
|
||||
with open(p, 'w') as fl:
|
||||
@ -63,10 +76,13 @@ 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()
|
||||
data = json.loads(data)
|
||||
else:
|
||||
data = {}
|
||||
if 'path' in data:
|
||||
del data['path']
|
||||
del data['name']
|
||||
@ -75,15 +91,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():
|
||||
@ -107,8 +116,10 @@ class Settings(dict, Singleton):
|
||||
'allow_inline': True,
|
||||
'allow_auto_accept': True,
|
||||
'auto_accept_path': None,
|
||||
'show_online_friends': False,
|
||||
'sorting': 0,
|
||||
'auto_accept_from_friends': [],
|
||||
'paused_file_transfers': {},
|
||||
'resend_files': True,
|
||||
'friends_aliases': [],
|
||||
'show_avatars': False,
|
||||
'typing_notifications': False,
|
||||
@ -127,7 +138,10 @@ class Settings(dict, Singleton):
|
||||
'unread_color': 'red',
|
||||
'save_unsent_only': False,
|
||||
'compact_mode': False,
|
||||
'show_welcome_screen': True
|
||||
'show_welcome_screen': True,
|
||||
'close_to_tray': False,
|
||||
'font': 'Times New Roman',
|
||||
'update': 1
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@ -148,7 +162,7 @@ class Settings(dict, Singleton):
|
||||
|
||||
def save(self):
|
||||
text = json.dumps(self)
|
||||
inst = ToxEncryptSave.get_instance()
|
||||
inst = ToxES.get_instance()
|
||||
if inst.has_password():
|
||||
text = bytes(inst.pass_encrypt(bytes(text, 'utf-8')))
|
||||
else:
|
||||
@ -157,49 +171,40 @@ 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'] = []
|
||||
profilepath = ProfileHelper.get_path()
|
||||
app_settings['active_profile'].append(str(profilepath + str(self.name) + '.tox'))
|
||||
data = json.dumps(app_settings)
|
||||
profile_path = ProfileHelper.get_path()
|
||||
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)
|
||||
with open(path + str(self.name) + '.json', 'w') as fl:
|
||||
fl.write(text)
|
||||
|
||||
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() == 'Linux':
|
||||
return os.getenv('HOME') + '/.config/tox/'
|
||||
elif system() == 'Windows':
|
||||
if system() == 'Windows':
|
||||
return os.getenv('APPDATA') + '/Tox/'
|
||||
elif system() == 'Darwin':
|
||||
return os.getenv('HOME') + '/Library/Application Support/Tox/'
|
||||
else:
|
||||
return os.getenv('HOME') + '/.config/tox/'
|
||||
|
||||
|
||||
class ProfileHelper(Singleton):
|
||||
@ -208,6 +213,7 @@ class ProfileHelper(Singleton):
|
||||
"""
|
||||
def __init__(self, path, name):
|
||||
Singleton.__init__(self)
|
||||
path = append_slash(path)
|
||||
self._path = path + name + '.tox'
|
||||
self._directory = path
|
||||
# create /avatars if not exists:
|
||||
@ -227,20 +233,25 @@ class ProfileHelper(Singleton):
|
||||
return self._directory
|
||||
|
||||
def save_profile(self, data):
|
||||
inst = ToxEncryptSave.get_instance()
|
||||
inst = ToxES.get_instance()
|
||||
if inst.has_password():
|
||||
data = inst.pass_encrypt(data)
|
||||
with open(self._path, 'wb') as fl:
|
||||
fl.write(data)
|
||||
print('Profile saved successfully')
|
||||
|
||||
def export_profile(self, new_path):
|
||||
new_path += os.path.basename(self._path)
|
||||
def export_profile(self, new_path, use_new_path):
|
||||
path = new_path + os.path.basename(self._path)
|
||||
with open(self._path, 'rb') as fin:
|
||||
data = fin.read()
|
||||
with open(new_path, 'wb') as fout:
|
||||
with open(path, 'wb') as fout:
|
||||
fout.write(data)
|
||||
print('Profile exported successfully')
|
||||
copy(self._directory + 'avatars', new_path + 'avatars')
|
||||
if use_new_path:
|
||||
self._path = new_path + os.path.basename(self._path)
|
||||
self._directory = new_path
|
||||
Settings.get_instance().update_path()
|
||||
|
||||
@staticmethod
|
||||
def find_profiles():
|
||||
|
@ -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):
|
||||
@ -48,7 +45,7 @@ class SmileyLoader(util.Singleton):
|
||||
print('Smiley pack {} was not loaded. Error: {}'.format(pack_name, ex))
|
||||
|
||||
def get_smileys_path(self):
|
||||
return util.curr_directory() + '/smileys/' + self._curr_pack + '/'
|
||||
return util.curr_directory() + '/smileys/' + self._curr_pack + '/' if self._curr_pack is not None else None
|
||||
|
||||
def get_packs_list(self):
|
||||
d = util.curr_directory() + '/smileys/'
|
||||
|
@ -1070,12 +1070,6 @@ QToolButton::menu-arrow:open {
|
||||
border: 1px solid #3A3939;
|
||||
}
|
||||
|
||||
QPushButton::menu-indicator {
|
||||
subcontrol-origin: padding;
|
||||
subcontrol-position: bottom right;
|
||||
left: 8px;
|
||||
}
|
||||
|
||||
QTableView
|
||||
{
|
||||
border: 1px solid #444;
|
||||
@ -1245,10 +1239,20 @@ QPushButton:hover
|
||||
}
|
||||
|
||||
#messages:item:selected
|
||||
{
|
||||
background-color: #1E90FF;
|
||||
}
|
||||
|
||||
MessageEdit
|
||||
{
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#messages:item:selected QListWidgetItem
|
||||
{
|
||||
background-color: #1E90FF;
|
||||
}
|
||||
|
||||
#friends_list:item:selected
|
||||
{
|
||||
background-color: #333333;
|
||||
@ -1277,3 +1281,44 @@ QListWidget > QLabel
|
||||
{
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
#mainmenubutton
|
||||
{
|
||||
border: 1px solid #3A3939;
|
||||
color: silver;
|
||||
margin: 0px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#mainmenubutton:hover
|
||||
{
|
||||
background: transparent;
|
||||
border: 1px solid #A9A9A9;
|
||||
background-color: #302F2F;
|
||||
}
|
||||
|
||||
#mainmenubutton:pressed
|
||||
{
|
||||
background: transparent;
|
||||
border: 1px solid #A9A9A9;
|
||||
background-color: #302F2F;
|
||||
}
|
||||
|
||||
#mainmenubutton::menu-indicator
|
||||
{
|
||||
image: none;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
ClickableLabel:focus
|
||||
{
|
||||
border-width: 1px;
|
||||
border-color: #4A4949;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
ClickableLabel:hover
|
||||
{
|
||||
background-color: #4A4949;
|
||||
}
|
||||
|
@ -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'))
|
||||
|
||||
|
@ -12,8 +12,6 @@ class ToxAV:
|
||||
peers.
|
||||
"""
|
||||
|
||||
libtoxav = LibToxAV()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Creation and destruction
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
@ -24,9 +22,10 @@ class ToxAV:
|
||||
|
||||
:param tox_pointer: pointer to Tox instance
|
||||
"""
|
||||
self.libtoxav = LibToxAV()
|
||||
toxav_err_new = c_int()
|
||||
ToxAV.libtoxav.toxav_new.restype = POINTER(c_void_p)
|
||||
self._toxav_pointer = ToxAV.libtoxav.toxav_new(tox_pointer, byref(toxav_err_new))
|
||||
self.libtoxav.toxav_new.restype = POINTER(c_void_p)
|
||||
self._toxav_pointer = self.libtoxav.toxav_new(tox_pointer, byref(toxav_err_new))
|
||||
toxav_err_new = toxav_err_new.value
|
||||
if toxav_err_new == TOXAV_ERR_NEW['NULL']:
|
||||
raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
|
||||
@ -48,7 +47,7 @@ class ToxAV:
|
||||
If any calls were ongoing, these will be forcibly terminated without notifying peers. After calling this
|
||||
function, no other functions may be called and the av pointer becomes invalid.
|
||||
"""
|
||||
ToxAV.libtoxav.toxav_kill(self._toxav_pointer)
|
||||
self.libtoxav.toxav_kill(self._toxav_pointer)
|
||||
|
||||
def get_tox_pointer(self):
|
||||
"""
|
||||
@ -56,8 +55,8 @@ class ToxAV:
|
||||
|
||||
:return: pointer to the Tox instance
|
||||
"""
|
||||
ToxAV.libtoxav.toxav_get_tox.restype = POINTER(c_void_p)
|
||||
return ToxAV.libtoxav.toxav_get_tox(self._toxav_pointer)
|
||||
self.libtoxav.toxav_get_tox.restype = POINTER(c_void_p)
|
||||
return self.libtoxav.toxav_get_tox(self._toxav_pointer)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# A/V event loop
|
||||
@ -70,14 +69,14 @@ class ToxAV:
|
||||
|
||||
:return: interval in milliseconds
|
||||
"""
|
||||
return ToxAV.libtoxav.toxav_iteration_interval(self._toxav_pointer)
|
||||
return self.libtoxav.toxav_iteration_interval(self._toxav_pointer)
|
||||
|
||||
def iterate(self):
|
||||
"""
|
||||
Main loop for the session. This function needs to be called in intervals of toxav_iteration_interval()
|
||||
milliseconds. It is best called in the separate thread from tox_iterate.
|
||||
"""
|
||||
ToxAV.libtoxav.toxav_iterate(self._toxav_pointer)
|
||||
self.libtoxav.toxav_iterate(self._toxav_pointer)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Call setup
|
||||
@ -97,7 +96,7 @@ class ToxAV:
|
||||
:return: True on success.
|
||||
"""
|
||||
toxav_err_call = c_int()
|
||||
result = ToxAV.libtoxav.toxav_call(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
|
||||
result = self.libtoxav.toxav_call(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
|
||||
c_uint32(video_bit_rate), byref(toxav_err_call))
|
||||
toxav_err_call = toxav_err_call.value
|
||||
if toxav_err_call == TOXAV_ERR_CALL['OK']:
|
||||
@ -131,7 +130,7 @@ class ToxAV:
|
||||
"""
|
||||
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_bool, c_bool, c_void_p)
|
||||
self.call_cb = c_callback(callback)
|
||||
ToxAV.libtoxav.toxav_callback_call(self._toxav_pointer, self.call_cb, user_data)
|
||||
self.libtoxav.toxav_callback_call(self._toxav_pointer, self.call_cb, user_data)
|
||||
|
||||
def answer(self, friend_number, audio_bit_rate, video_bit_rate):
|
||||
"""
|
||||
@ -146,7 +145,7 @@ class ToxAV:
|
||||
:return: True on success.
|
||||
"""
|
||||
toxav_err_answer = c_int()
|
||||
result = ToxAV.libtoxav.toxav_answer(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
|
||||
result = self.libtoxav.toxav_answer(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
|
||||
c_uint32(video_bit_rate), byref(toxav_err_answer))
|
||||
toxav_err_answer = toxav_err_answer.value
|
||||
if toxav_err_answer == TOXAV_ERR_ANSWER['OK']:
|
||||
@ -184,7 +183,7 @@ class ToxAV:
|
||||
"""
|
||||
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_void_p)
|
||||
self.call_state_cb = c_callback(callback)
|
||||
ToxAV.libtoxav.toxav_callback_call_state(self._toxav_pointer, self.call_state_cb, user_data)
|
||||
self.libtoxav.toxav_callback_call_state(self._toxav_pointer, self.call_state_cb, user_data)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Call control
|
||||
@ -199,7 +198,7 @@ class ToxAV:
|
||||
:return: True on success.
|
||||
"""
|
||||
toxav_err_call_control = c_int()
|
||||
result = ToxAV.libtoxav.toxav_call_control(self._toxav_pointer, c_uint32(friend_number), c_int(control),
|
||||
result = self.libtoxav.toxav_call_control(self._toxav_pointer, c_uint32(friend_number), c_int(control),
|
||||
byref(toxav_err_call_control))
|
||||
toxav_err_call_control = toxav_err_call_control.value
|
||||
if toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['OK']:
|
||||
@ -241,7 +240,7 @@ class ToxAV:
|
||||
24000, or 48000.
|
||||
"""
|
||||
toxav_err_send_frame = c_int()
|
||||
result = ToxAV.libtoxav.toxav_audio_send_frame(self._toxav_pointer, c_uint32(friend_number),
|
||||
result = self.libtoxav.toxav_audio_send_frame(self._toxav_pointer, c_uint32(friend_number),
|
||||
cast(pcm, c_void_p),
|
||||
c_size_t(sample_count), c_uint8(channels),
|
||||
c_uint32(sampling_rate), byref(toxav_err_send_frame))
|
||||
@ -281,7 +280,7 @@ class ToxAV:
|
||||
:param v: V (Chroma) plane data.
|
||||
"""
|
||||
toxav_err_send_frame = c_int()
|
||||
result = ToxAV.libtoxav.toxav_video_send_frame(self._toxav_pointer, c_uint32(friend_number), c_uint16(width),
|
||||
result = self.libtoxav.toxav_video_send_frame(self._toxav_pointer, c_uint32(friend_number), c_uint16(width),
|
||||
c_uint16(height), c_char_p(y), c_char_p(u), c_char_p(v),
|
||||
byref(toxav_err_send_frame))
|
||||
toxav_err_send_frame = toxav_err_send_frame.value
|
||||
@ -328,7 +327,7 @@ class ToxAV:
|
||||
"""
|
||||
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t, c_uint8, c_uint32, c_void_p)
|
||||
self.audio_receive_frame_cb = c_callback(callback)
|
||||
ToxAV.libtoxav.toxav_callback_audio_receive_frame(self._toxav_pointer, self.audio_receive_frame_cb, user_data)
|
||||
self.libtoxav.toxav_callback_audio_receive_frame(self._toxav_pointer, self.audio_receive_frame_cb, user_data)
|
||||
|
||||
def callback_video_receive_frame(self, callback, user_data):
|
||||
"""
|
||||
@ -360,4 +359,4 @@ class ToxAV:
|
||||
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint16, c_uint16, POINTER(c_uint8), POINTER(c_uint8),
|
||||
POINTER(c_uint8), c_int32, c_int32, c_int32, c_void_p)
|
||||
self.video_receive_frame_cb = c_callback(callback)
|
||||
ToxAV.libtoxav.toxav_callback_video_receive_frame(self._toxav_pointer, self.video_receive_frame_cb, user_data)
|
||||
self.libtoxav.toxav_callback_video_receive_frame(self._toxav_pointer, self.video_receive_frame_cb, user_data)
|
||||
|
@ -1,65 +1,25 @@
|
||||
import libtox
|
||||
import util
|
||||
from ctypes import c_size_t, create_string_buffer, byref, c_int, ArgumentError, c_char_p, c_bool
|
||||
from toxencryptsave_enums_and_consts import *
|
||||
|
||||
|
||||
TOX_ERR_ENCRYPTION = {
|
||||
# The function returned successfully.
|
||||
'OK': 0,
|
||||
# Some input data, or maybe the output pointer, was null.
|
||||
'NULL': 1,
|
||||
# The crypto lib was unable to derive a key from the given passphrase, which is usually a lack of memory issue. The
|
||||
# functions accepting keys do not produce this error.
|
||||
'KEY_DERIVATION_FAILED': 2,
|
||||
# The encryption itself failed.
|
||||
'FAILED': 3
|
||||
}
|
||||
|
||||
TOX_ERR_DECRYPTION = {
|
||||
# The function returned successfully.
|
||||
'OK': 0,
|
||||
# Some input data, or maybe the output pointer, was null.
|
||||
'NULL': 1,
|
||||
# The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes
|
||||
'INVALID_LENGTH': 2,
|
||||
# The input data is missing the magic number (i.e. wasn't created by this module, or is corrupted)
|
||||
'BAD_FORMAT': 3,
|
||||
# The crypto lib was unable to derive a key from the given passphrase, which is usually a lack of memory issue. The
|
||||
# functions accepting keys do not produce this error.
|
||||
'KEY_DERIVATION_FAILED': 4,
|
||||
# The encrypted byte array could not be decrypted. Either the data was corrupt or the password/key was incorrect.
|
||||
'FAILED': 5,
|
||||
}
|
||||
|
||||
TOX_PASS_ENCRYPTION_EXTRA_LENGTH = 80
|
||||
|
||||
|
||||
class ToxEncryptSave(util.Singleton):
|
||||
|
||||
libtoxencryptsave = libtox.LibToxEncryptSave()
|
||||
class ToxEncryptSave:
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._passphrase = None
|
||||
|
||||
def set_password(self, passphrase):
|
||||
self._passphrase = passphrase
|
||||
|
||||
def has_password(self):
|
||||
return bool(self._passphrase)
|
||||
|
||||
def is_password(self, password):
|
||||
return self._passphrase == password
|
||||
self.libtoxencryptsave = libtox.LibToxEncryptSave()
|
||||
|
||||
def is_data_encrypted(self, data):
|
||||
"""
|
||||
Checks if given data is encrypted
|
||||
"""
|
||||
func = self.libtoxencryptsave.tox_is_data_encrypted
|
||||
func.restype = c_bool
|
||||
result = func(c_char_p(bytes(data)))
|
||||
return result
|
||||
|
||||
def pass_encrypt(self, data):
|
||||
def pass_encrypt(self, data, password):
|
||||
"""
|
||||
Encrypts the given data with the given passphrase.
|
||||
Encrypts the given data with the given password.
|
||||
|
||||
:return: output array
|
||||
"""
|
||||
@ -67,8 +27,8 @@ class ToxEncryptSave(util.Singleton):
|
||||
tox_err_encryption = c_int()
|
||||
self.libtoxencryptsave.tox_pass_encrypt(c_char_p(data),
|
||||
c_size_t(len(data)),
|
||||
c_char_p(bytes(self._passphrase, 'utf-8')),
|
||||
c_size_t(len(self._passphrase)),
|
||||
c_char_p(bytes(password, 'utf-8')),
|
||||
c_size_t(len(password)),
|
||||
out,
|
||||
byref(tox_err_encryption))
|
||||
tox_err_encryption = tox_err_encryption.value
|
||||
@ -82,9 +42,9 @@ class ToxEncryptSave(util.Singleton):
|
||||
elif tox_err_encryption == TOX_ERR_ENCRYPTION['FAILED']:
|
||||
raise RuntimeError('The encryption itself failed.')
|
||||
|
||||
def pass_decrypt(self, data):
|
||||
def pass_decrypt(self, data, password):
|
||||
"""
|
||||
Decrypts the given data with the given passphrase.
|
||||
Decrypts the given data with the given password.
|
||||
|
||||
:return: output array
|
||||
"""
|
||||
@ -92,8 +52,8 @@ class ToxEncryptSave(util.Singleton):
|
||||
tox_err_decryption = c_int()
|
||||
self.libtoxencryptsave.tox_pass_decrypt(c_char_p(bytes(data)),
|
||||
c_size_t(len(data)),
|
||||
c_char_p(bytes(self._passphrase, 'utf-8')),
|
||||
c_size_t(len(self._passphrase)),
|
||||
c_char_p(bytes(password, 'utf-8')),
|
||||
c_size_t(len(password)),
|
||||
out,
|
||||
byref(tox_err_decryption))
|
||||
tox_err_decryption = tox_err_decryption.value
|
||||
|
29
toxygen/toxencryptsave_enums_and_consts.py
Normal file
@ -0,0 +1,29 @@
|
||||
TOX_ERR_ENCRYPTION = {
|
||||
# The function returned successfully.
|
||||
'OK': 0,
|
||||
# Some input data, or maybe the output pointer, was null.
|
||||
'NULL': 1,
|
||||
# The crypto lib was unable to derive a key from the given passphrase, which is usually a lack of memory issue. The
|
||||
# functions accepting keys do not produce this error.
|
||||
'KEY_DERIVATION_FAILED': 2,
|
||||
# The encryption itself failed.
|
||||
'FAILED': 3
|
||||
}
|
||||
|
||||
TOX_ERR_DECRYPTION = {
|
||||
# The function returned successfully.
|
||||
'OK': 0,
|
||||
# Some input data, or maybe the output pointer, was null.
|
||||
'NULL': 1,
|
||||
# The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes
|
||||
'INVALID_LENGTH': 2,
|
||||
# The input data is missing the magic number (i.e. wasn't created by this module, or is corrupted)
|
||||
'BAD_FORMAT': 3,
|
||||
# The crypto lib was unable to derive a key from the given passphrase, which is usually a lack of memory issue. The
|
||||
# functions accepting keys do not produce this error.
|
||||
'KEY_DERIVATION_FAILED': 4,
|
||||
# The encrypted byte array could not be decrypted. Either the data was corrupt or the password/key was incorrect.
|
||||
'FAILED': 5,
|
||||
}
|
||||
|
||||
TOX_PASS_ENCRYPTION_EXTRA_LENGTH = 80
|
28
toxygen/toxes.py
Normal file
@ -0,0 +1,28 @@
|
||||
import util
|
||||
import toxencryptsave
|
||||
|
||||
|
||||
class ToxES(util.Singleton):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._toxencryptsave = toxencryptsave.ToxEncryptSave()
|
||||
self._passphrase = None
|
||||
|
||||
def set_password(self, passphrase):
|
||||
self._passphrase = passphrase
|
||||
|
||||
def has_password(self):
|
||||
return bool(self._passphrase)
|
||||
|
||||
def is_password(self, password):
|
||||
return self._passphrase == password
|
||||
|
||||
def is_data_encrypted(self, data):
|
||||
return len(data) > 0 and self._toxencryptsave.is_data_encrypted(data)
|
||||
|
||||
def pass_encrypt(self, data):
|
||||
return self._toxencryptsave.pass_encrypt(data, self._passphrase)
|
||||
|
||||
def pass_decrypt(self, data):
|
||||
return self._toxencryptsave.pass_decrypt(data, self._passphrase)
|
@ -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
|
||||
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
|
||||
|
106
toxygen/updater.py
Normal file
@ -0,0 +1,106 @@
|
||||
import util
|
||||
import os
|
||||
import settings
|
||||
import platform
|
||||
import urllib
|
||||
from PyQt5 import QtNetwork, QtCore
|
||||
import subprocess
|
||||
|
||||
|
||||
def connection_available():
|
||||
try:
|
||||
urllib.request.urlopen('http://216.58.192.142', timeout=1) # google.com
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def updater_available():
|
||||
if is_from_sources():
|
||||
return os.path.exists(util.curr_directory() + '/toxygen_updater.py')
|
||||
elif platform.system() == 'Windows':
|
||||
return os.path.exists(util.curr_directory() + '/toxygen_updater.exe')
|
||||
else:
|
||||
return os.path.exists(util.curr_directory() + '/toxygen_updater')
|
||||
|
||||
|
||||
def check_for_updates():
|
||||
current_version = util.program_version
|
||||
major, minor, patch = list(map(lambda x: int(x), current_version.split('.')))
|
||||
versions = generate_versions(major, minor, patch)
|
||||
for version in versions:
|
||||
if send_request(version):
|
||||
return version
|
||||
return None # no new version was found
|
||||
|
||||
|
||||
def is_from_sources():
|
||||
return __file__.endswith('.py')
|
||||
|
||||
|
||||
def test_url(version):
|
||||
return 'https://github.com/toxygen-project/toxygen/releases/tag/v' + version
|
||||
|
||||
|
||||
def get_url(version):
|
||||
if is_from_sources():
|
||||
return 'https://github.com/toxygen-project/toxygen/archive/v' + version + '.zip'
|
||||
else:
|
||||
if platform.system() == 'Windows':
|
||||
name = 'toxygen_windows.zip'
|
||||
elif util.is_64_bit():
|
||||
name = 'toxygen_linux_64.tar.gz'
|
||||
else:
|
||||
name = 'toxygen_linux.tar.gz'
|
||||
return 'https://github.com/toxygen-project/toxygen/releases/download/v{}/{}'.format(version, name)
|
||||
|
||||
|
||||
def get_params(url, version):
|
||||
if is_from_sources():
|
||||
return ['python3', 'toxygen_updater.py', url, version]
|
||||
elif platform.system() == 'Windows':
|
||||
return [util.curr_directory() + '/toxygen_updater.exe', url, version]
|
||||
else:
|
||||
return ['./toxygen_updater', url, version]
|
||||
|
||||
|
||||
def download(version):
|
||||
os.chdir(util.curr_directory())
|
||||
url = get_url(version)
|
||||
params = get_params(url, version)
|
||||
print('Updating Toxygen')
|
||||
util.log('Updating Toxygen')
|
||||
try:
|
||||
subprocess.Popen(params)
|
||||
except Exception as ex:
|
||||
util.log('Exception: running updater failed with ' + str(ex))
|
||||
|
||||
|
||||
def send_request(version):
|
||||
s = settings.Settings.get_instance()
|
||||
netman = QtNetwork.QNetworkAccessManager()
|
||||
proxy = QtNetwork.QNetworkProxy()
|
||||
if s['proxy_type']:
|
||||
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)
|
||||
url = test_url(version)
|
||||
try:
|
||||
request = QtNetwork.QNetworkRequest(url)
|
||||
reply = netman.get(request)
|
||||
while not reply.isFinished():
|
||||
QtCore.QThread.msleep(1)
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
attr = reply.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)
|
||||
return attr is not None and 200 <= attr < 300
|
||||
except Exception as ex:
|
||||
util.log('TOXYGEN UPDATER ERROR: ' + str(ex))
|
||||
return False
|
||||
|
||||
|
||||
def generate_versions(major, minor, patch):
|
||||
new_major = '.'.join([str(major + 1), '0', '0'])
|
||||
new_minor = '.'.join([str(major), str(minor + 1), '0'])
|
||||
new_patch = '.'.join([str(major), str(minor), str(patch + 1)])
|
||||
return new_major, new_minor, new_patch
|
@ -1,8 +1,10 @@
|
||||
import os
|
||||
import time
|
||||
import shutil
|
||||
import sys
|
||||
import re
|
||||
|
||||
program_version = '0.2.3'
|
||||
program_version = '0.3.0'
|
||||
|
||||
|
||||
def log(data):
|
||||
@ -30,14 +32,53 @@ def copy(src, dest):
|
||||
copy(full_file_name, os.path.join(dest, file_name))
|
||||
|
||||
|
||||
def remove(folder):
|
||||
if os.path.isdir(folder):
|
||||
shutil.rmtree(folder)
|
||||
|
||||
|
||||
def convert_time(t):
|
||||
sec = int(t) - time.timezone
|
||||
offset = time.timezone + time_offset() * 60
|
||||
sec = int(t) - offset
|
||||
m, s = divmod(sec, 60)
|
||||
h, m = divmod(m, 60)
|
||||
d, h = divmod(h, 24)
|
||||
return '%02d:%02d' % (h, m)
|
||||
|
||||
|
||||
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
|
||||
m, s = divmod(sec, 60)
|
||||
h, m = divmod(m, 60)
|
||||
d, h = divmod(h, 24)
|
||||
result = hours * 60 + minutes - h * 60 - m
|
||||
time_offset.offset = result
|
||||
return result
|
||||
|
||||
|
||||
def append_slash(s):
|
||||
if len(s) and s[-1] not in ('\\', '/'):
|
||||
s += '/'
|
||||
return s
|
||||
|
||||
|
||||
def is_64_bit():
|
||||
return sys.maxsize > 2 ** 32
|
||||
|
||||
|
||||
def is_re_valid(regex):
|
||||
try:
|
||||
re.compile(regex)
|
||||
except re.error:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
class Singleton:
|
||||
_instance = None
|
||||
|
||||
|
@ -1,21 +1,25 @@
|
||||
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
|
||||
"""
|
||||
def setText(self, text):
|
||||
text = ''.join(c if c <= '\u10FFFF' else '\u25AF' for c in text)
|
||||
text = ''.join('\u25AF' if len(bytes(c, 'utf-8')) >= 4 else c for c in text)
|
||||
metrics = QtGui.QFontMetrics(self.font())
|
||||
text = metrics.elidedText(text, QtCore.Qt.ElideRight, self.width())
|
||||
super().setText(text)
|
||||
|
||||
|
||||
class CenteredWidget(QtGui.QWidget):
|
||||
class ComboBox(QtWidgets.QComboBox):
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
self.view().setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
|
||||
|
||||
|
||||
class CenteredWidget(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super(CenteredWidget, self).__init__()
|
||||
@ -23,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)
|
||||
@ -39,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)
|
||||
@ -79,29 +85,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
|
||||
@ -117,12 +115,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
|
||||
|