diff --git a/.gitignore b/.gitignore
index 0a8182a..47d34c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,11 @@
*.pyc
*.pyo
+*.ui
toxygen/toxcore
tests/tests
tests/libs
tests/.cache
tests/__pycache__
-tests/avatars
toxygen/libs
.idea
*~
@@ -15,14 +15,9 @@ toxygen/libs
toxygen/build
toxygen/dist
*.spec
-dist
+dist/
toxygen/avatars
toxygen/__pycache__
/*.egg-info
/*.egg
-html
-Toxygen.egg-info
-*.tox
-.cache
-*.db
diff --git a/.travis.yml b/.travis.yml
index a4011e1..5bd30bb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,33 +1,14 @@
language: python
python:
- - "3.5"
- - "3.6"
-os:
- - linux
-dist: trusty
-notifications:
- email: false
+ - "3.4"
before_install:
- - sudo apt-get update
- sudo apt-get install -y checkinstall build-essential
- sudo apt-get install portaudio19-dev
- - sudo apt-get install libsecret-1-dev
- - sudo apt-get install libconfig-dev libvpx-dev check -qq
install:
- - pip install sip
- - pip install pyqt5
+ - pip install PySide --no-index --find-links https://parkin.github.io/python-wheelhouse/;
+ - python ~/virtualenv/python${TRAVIS_PYTHON_VERSION}/bin/pyside_postinstall.py -install
- pip install pyaudio
- - pip install opencv-python
- - pip install pydenticon
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
@@ -38,16 +19,13 @@ before_script:
- sudo ldconfig
- cd ..
# Toxcore
- - git clone https://github.com/ingvar1995/toxcore.git --branch=ngc_rebase
+ - git clone https://github.com/irungentoo/toxcore.git
- cd toxcore
- - mkdir _build && cd _build
- - cmake ..
+ - 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 ..
- - cd ..
-script:
- - py.test tests/travis.py
- - py.test tests/tests.py
+script: py.test tests/travis.py
diff --git a/MANIFEST.in b/MANIFEST.in
index 89e57c6..9bf65b8 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -12,8 +12,9 @@ include toxygen/smileys/starwars/*.png
include toxygen/smileys/starwars/config.json
include toxygen/smileys/ksk/*.png
include toxygen/smileys/ksk/config.json
-include toxygen/styles/*.qss
+include toxygen/styles/style.qss
include toxygen/translations/*.qm
include toxygen/libs/libtox.dll
include toxygen/libs/libsodium.a
-include toxygen/bootstrap/nodes.json
+include toxygen/libs/libtox64.dll
+include toxygen/libs/libsodium64.a
\ No newline at end of file
diff --git a/README.md b/README.md
index 914fdfe..5b75a6d 100644
--- a/README.md
+++ b/README.md
@@ -1,48 +1,51 @@
# Toxygen
-Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pure Python3.
+Toxygen is cross-platform [Tox](https://tox.chat/) client written in pure Python3
-[](https://github.com/toxygen-project/toxygen/releases/latest)
-[](https://github.com/toxygen-project/toxygen/stargazers)
-[](https://github.com/toxygen-project/toxygen/issues)
+[](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)
+### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md) - [Compile](/docs/compile.md) - [Contact](/docs/contact.md)
-### Supported OS: Linux and Windows
+### Supported OS:
-### Features:
+- Windows
+- Linux
+- OS X
-- 1v1 messages
-- File transfers
-- Audio calls
-- Video calls
-- Group chats
-- Plugins support
-- Desktop sharing
-- Chat history
-- Emoticons
-- Stickers
-- Screenshots
-- Name lookups (toxme.io support)
-- Save file encryption
-- Profile import and export
-- Faux offline messaging
-- Faux offline file transfers
-- Inline images
-- Message splitting
-- Proxy support
-- Avatars
-- Multiprofile
-- Multilingual
-- Sound notifications
-- Contact aliases
-- Contact blocking
-- Typing notifications
-- Changing nospam
-- File resuming
-- Read receipts
+### Features
+
+- [x] 1v1 messages
+- [x] File transfers
+- [x] Audio
+- [x] Plugins support
+- [x] Chat history
+- [x] Emoticons
+- [x] Stickers
+- [x] Screenshots
+- [x] Name lookups (toxme.io support)
+- [x] Save file encryption
+- [x] Profile import and export
+- [x] Faux offline messaging
+- [x] Faux offline file transfers
+- [x] Inline images
+- [x] Message splitting
+- [x] Proxy support
+- [x] Avatars
+- [x] Multiprofile
+- [x] Multilingual
+- [x] Sound notifications
+- [x] Contact aliases
+- [x] Contact blocking
+- [x] Typing notifications
+- [x] Changing nospam
+- [x] File resuming
+- [x] Read receipts
+- [ ] Video
+- [ ] Desktop sharing
+- [ ] Group chats
### Downloads
[Releases](https://github.com/toxygen-project/toxygen/releases)
@@ -58,7 +61,3 @@ Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pu
### Docs
[Check /docs/ for more info](/docs/)
-
-Also visit [pythonhosted.org/Toxygen/](http://pythonhosted.org/Toxygen/)
-
-[Wiki](https://wiki.tox.chat/clients/toxygen)
diff --git a/build/Dockerfile b/build/Dockerfile
deleted file mode 100644
index 0b45358..0000000
--- a/build/Dockerfile
+++ /dev/null
@@ -1,13 +0,0 @@
-FROM ubuntu:16.04
-
-RUN apt-get update && \
-apt-get install build-essential libtool autotools-dev automake checkinstall cmake check git yasm libsodium-dev libopus-dev libvpx-dev pkg-config -y && \
-git clone https://github.com/ingvar1995/toxcore.git --branch=ngc_rebase && \
-cd toxcore && mkdir _build && cd _build && \
-cmake .. && make && make install
-
-RUN apt-get install portaudio19-dev python3-pyqt5 python3-pyaudio python3-pip -y && \
-pip3 install numpy pydenticon opencv-python pyinstaller
-
-RUN useradd -ms /bin/bash toxygen
-USER toxygen
diff --git a/build/build.sh b/build/build.sh
deleted file mode 100644
index fb6c4b2..0000000
--- a/build/build.sh
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/usr/bin/env bash
-
-cd ~
-git clone https://github.com/toxygen-project/toxygen.git --branch=next_gen
-cd toxygen/toxygen
-
-pyinstaller --windowed --icon=images/icon.ico main.py
-
-cp -r styles dist/main/
-find . -type f ! -name '*.qss' -delete
-cp -r plugins dist/main/
-mkdir -p dist/main/ui/views
-cp -r ui/views dist/main/ui/
-cp -r sounds dist/main/
-cp -r smileys dist/main/
-cp -r stickers dist/main/
-cp -r bootstrap dist/main/
-find . -type f ! -name '*.json' -delete
-cp -r images dist/main/
-cp -r translations dist/main/
-find . -name "*.ts" -type f -delete
-
-cd dist
-mv main toxygen
-cd toxygen
-mv main toxygen
-wget -O updater https://github.com/toxygen-project/toxygen_updater/releases/download/v0.1/toxygen_updater_linux_64
-echo "[Paths]" >> qt.conf
-echo "Prefix = PyQt5/Qt" >> qt.conf
-cd ..
-
-tar -zcvf toxygen_linux_64.tar.gz toxygen > /dev/null
-rm -rf toxygen
diff --git a/docs/compile.md b/docs/compile.md
index b4f6810..5b2b5fe 100644
--- a/docs/compile.md
+++ b/docs/compile.md
@@ -2,18 +2,9 @@
You can compile Toxygen using [PyInstaller](http://www.pyinstaller.org/)
-Use Dockerfile and build script from `build` directory:
+Install PyInstaller:
+``pip3 install pyinstaller``
-1. Build image:
-```
-docker build -t toxygen .
-```
+``pyinstaller --windowed --icon images/icon.ico main.py``
-2. Run container:
-```
-docker run -it toxygen bash
-```
-
-3. Execute `build.sh` script:
-
-```./build.sh```
+Don't forget to copy /images/, /sounds/, /translations/, /styles/, /smileys/, /stickers/, /plugins/ (and /libs/libtox.dll, /libs/libsodium.a on Windows) to /dist/main/
diff --git a/docs/contact.md b/docs/contact.md
index 5eb2fa6..c66da1c 100644
--- a/docs/contact.md
+++ b/docs/contact.md
@@ -1,6 +1,5 @@
# Contact us:
-1) https://git.plastiras.org/emdee/toxygen/issues
+1) Using GitHub - open issue
-2) Use Toxygen Tox Group (NGC) -
-ID: 59D68B2709E81A679CF91416CB0E3692851C6CFCABEFF98B7131E3805A6D75FA
+2) Use Toxygen Tox Group - add bot kalina@toxme.io (or 12EDB939AA529641CE53830B518D6EB30241868EE0E5023C46A372363CAEC91C2C948AEFE4EB)
diff --git a/docs/contributing.md b/docs/contributing.md
index b2cebf4..8b1e7fa 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -1,25 +1,20 @@
-# Issues
+#Issues
Help us find all bugs in Toxygen! Please provide following info:
- OS
- Toxygen version
-- Toxygen executable info - python executable (.py), precompiled binary, from package etc.
+- Toxygen executable info - .py or precompiled binary
- Steps to reproduce the bug
-Want to see new feature in Toxygen?
-[Ask for it!](https://git.plastiras.org/emdee/toxygen/issues)
+Want to see new feature in Toxygen? [Ask for it!](https://github.com/xveduk/toxygen/issues)
-# Pull requests
+#Pull requests
-Developer? Feel free to open pull request. Our dev team is small so we glad to get help.
-Don't know what to do? Improve UI, fix
-[issues](https://git.plastiras.org/emdee/toxygen/issues)
-or implement features from our TODO list.
+Developer? Feel free to open pull request. Our dev team is small so we glad to get help.
+Don't know what to do? Improve UI, fix [issues](https://github.com/xveduk/toxygen/issues) or implement features from our TODO list.
You can find our TODO's in code, issues list and [here](/README.md). Also you can implement [plugins](/docs/plugins.md) for Toxygen.
-Note that we have a lot of branches for different purposes. Master branch is for stable versions (releases) only, so I recommend to open PR's to develop branch. Development of next Toxygen version usually goes there. Other branches used for implementing different tasks such as file transfers improvements or audio calls implementation etc.
+#Translations
-# Translations
-
-Help us translate Toxygen! Translation can be created using pylupdate (``pylupdate5 toxygen.pro``) and QT Linguist.
+Help us translate Toxygen! Translation can be created using pyside-lupdate (``pyside-lupdate toxygen.pro``) and QT Linguist.
\ No newline at end of file
diff --git a/docs/install.md b/docs/install.md
index b3c5457..bf79c67 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -1,44 +1,86 @@
# How to install Toxygen
+## Use precompiled binary:
+[Check our releases page](https://github.com/xveduk/toxygen/releases)
+
+## Using pip3
+
+### Windows
+
+``pip3.4 install toxygen``
+
+Run app using ``toxygen`` command.
+
### Linux
-1. Install [c-toxcore](https://github.com/TokTok/c-toxcore/)
+1. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system (install in /usr/lib/)
2. Install PortAudio:
``sudo apt-get install portaudio19-dev``
-3. For 32-bit Linux install PyQt5: ``sudo apt-get install python3-pyqt5``
-4. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo pip3 install opencv-python``
-5. Install [toxygen](https://git.plastiras.org/emdee/toxygen/)
-6. Run toxygen using ``toxygen`` command.
+3. Install PySide: ``sudo apt-get install python3-pyside``
+4. Install toxygen:
+``sudo pip3.4 install toxygen``
+5. Run toxygen using ``toxygen`` command.
+
+### OS X
+
+1. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system
+2. Install PortAudio:
+``brew install portaudio``
+3. Install toxygen:
+``pip3 install toxygen``
+4. Run toxygen using ``toxygen`` command.
+
+## Packages
+
+Coming soon.
## From source code (recommended for developers)
### Windows
-Note: 32-bit Python isn't supported due to bug with videocalls. It is strictly recommended to use 64-bit Python.
+1. [Download and install latest Python 3.4](https://www.python.org/downloads/windows/)
+2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4#installing-pyside-on-a-windows-system) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
+3. Install PyAudio: ``pip3.4 install pyaudio``
+4. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
+5. Unpack archive
+6. Download latest libtox.dll build, download latest libsodium.a build, put it into \src\libs\
+7. Run \toxygen\main.py.
-1. [Download and install latest Python 3 64-bit](https://www.python.org/downloads/windows/)
-2. Install PyQt5: ``pip install pyqt5``
-3. Install PyAudio: ``pip install pyaudio``
-4. Install numpy: ``pip install numpy``
-5. Install OpenCV: ``pip install opencv-python``
-6. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip)
-7. Unpack archive
-8. Download latest libtox.dll build, download latest libsodium.a build, put it into \toxygen\libs\
-9. Run \toxygen\main.py.
+Optional: install toxygen using setup.py: ``python3.4 setup.py install``
+
+[libtox.dll for 32-bit Python](https://build.tox.chat/view/libtoxcore/job/libtoxcore_build_windows_x86_shared_release/lastSuccessfulBuild/artifact/libtoxcore_build_windows_x86_shared_release.zip)
+
+[libtox.dll for 64-bit Python](https://build.tox.chat/view/libtoxcore/job/libtoxcore_build_windows_x86-64_shared_release/lastSuccessfulBuild/artifact/libtoxcore_build_windows_x86-64_shared_release.zip)
+
+[libsodium.a for 32-bit Python](https://build.tox.chat/view/libsodium/job/libsodium_build_windows_x86_static_release/lastSuccessfulBuild/artifact/libsodium_build_windows_x86_static_release.zip)
+
+[libsodium.a for 64-bit Python](https://build.tox.chat/view/libsodium/job/libsodium_build_windows_x86-64_static_release/lastSuccessfulBuild/artifact/libsodium_build_windows_x86-64_static_release.zip)
### Linux
1. Install latest Python3:
``sudo apt-get install python3``
-2. Install PyQt5: ``sudo apt-get install python3-pyqt5`` or ``sudo pip3 install pyqt5``
-3. Install [toxcore](https://github.com/TokTok/c-toxcore) with toxav support)
+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 ``sudo pip3 install pyaudio``)
-5. Install NumPy: ``sudo pip3 install numpy``
-6. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo pip3 install opencv-python``
-7. [Download toxygen](https://git.plastiras.org/emdee/toxygen/)
-8. Unpack archive
-9. Run app:
-``python3 main.py``
+``sudo apt-get install portaudio19-dev`` and ``sudo apt-get install python3-pyaudio`` (or ``pip3 install pyaudio``)
+5. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
+6. Unpack archive
+7. Run app:
+``python3.4 main.py``
+
+Optional: install toxygen using setup.py: ``python3.4 setup.py install``
+
+### OS X
+
+1. [Download and install latest Python 3.4](https://www.python.org/downloads/mac-osx/)
+2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4#installing-pyside-on-a-mac-os-x-system) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
+3. Install PortAudio:
+``brew install portaudio``
+4. Install PyAudio: ``pip3 install pyaudio``
+5. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system
+6. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
+7. Unpack archive
+8. Run \toxygen\main.py.
Optional: install toxygen using setup.py: ``python3 setup.py install``
diff --git a/docs/plugin_api.md b/docs/plugin_api.md
index 9eb30a4..f523270 100644
--- a/docs/plugin_api.md
+++ b/docs/plugin_api.md
@@ -1,6 +1,6 @@
-# Plugins API
+#Plugins API
-In Toxygen plugin is single python module (.py file) and directory with data associated with it.
+In Toxygen plugin is single python (supported Python 3.0 - 3.4) module (.py file) and directory with data associated with it.
Every module must contain one class derived from PluginSuperClass defined in [plugin_super_class.py](/src/plugins/plugin_super_class.py). Instance of this class will be created by PluginLoader class (defined in [plugin_support.py](/src/plugin_support.py) ). This class can enable/disable plugins and send data to it.
Every plugin has its own full name and unique short name (1-5 symbols). Main app can get it using special methods.
@@ -18,7 +18,7 @@ All plugin's data should be stored in following structure:
```
Plugin MUST override:
-- __init__ with params: tox (Tox instance), profile (Profile instance), settings (Settings instance), encrypt_save (ToxES 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 (ToxEncryptSave instance). Call super().__init__ with params plugin_full_name, plugin_short_name, tox, profile, settings, encrypt_save.
Plugin can override following methods:
- get_description - this method should return plugin description.
@@ -45,13 +45,13 @@ Import statement will not work in case you import module that wasn't previously
About GUI:
-GUI is available via PyQt5. Plugin can have no GUI at all.
+It's strictly recommended to support both PySide and PyQt4 in GUI. Plugin can have no GUI at all.
Exceptions:
Plugin's methods MUST NOT raise exceptions.
-# Examples
+#Examples
-You can find examples in [official repo](https://github.com/toxygen-project/toxygen_plugins)
+You can find examples in [official repo](https://github.com/ingvar1995/toxygen_plugins)
diff --git a/docs/plugins.md b/docs/plugins.md
index 98fbac8..ee73415 100644
--- a/docs/plugins.md
+++ b/docs/plugins.md
@@ -1,22 +1,22 @@
-# Plugins
+#Plugins
-Toxygen is the first [Tox](https://tox.chat/) client with plugins support. Plugin is Python 3.5 - 3.6 module (.py file) and directory with plugin's data which provide some additional functionality.
+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 or choose Plugins => Reload plugins in menu.
+1. Put plugin and directory with its data into /src/plugins/ or import it via GUI (In menu: Plugins -> Import plugin)
+2. Restart Toxygen
-## 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/toxygen-project/toxygen_plugins)
\ No newline at end of file
+[Main repo](https://github.com/ingvar1995/toxygen_plugins)
\ No newline at end of file
diff --git a/docs/smileys_and_stickers.md b/docs/smileys_and_stickers.md
index 8705ba8..53a360b 100644
--- a/docs/smileys_and_stickers.md
+++ b/docs/smileys_and_stickers.md
@@ -1,4 +1,4 @@
-# Smileys
+#Smileys
Toxygen support smileys. Smiley is small picture which replaces some symbol or combination of symbols. If you want to create your own smiley pack, create directory in src/smileys/. This directory must contain images with smileys and config.json. Example of config.json:
@@ -6,8 +6,8 @@ Toxygen support smileys. Smiley is small picture which replaces some symbol or c
Animated smileys (.gif) are supported too.
-# Stickers
+#Stickers
-Sticker is inline image. If you want to create your own sticker pack, create directory in src/stickers/ and place your stickers there.
+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 smileys and stickers using menu: Settings -> Interface
+Users can import plugins and stickers packs using menu: Settings -> Interface
\ No newline at end of file
diff --git a/docs/ubuntu.png b/docs/ubuntu.png
old mode 100644
new mode 100755
index 67951a5..cd80444
Binary files a/docs/ubuntu.png and b/docs/ubuntu.png differ
diff --git a/docs/windows.png b/docs/windows.png
old mode 100644
new mode 100755
index f13f4c0..d4ed323
Binary files a/docs/windows.png and b/docs/windows.png differ
diff --git a/setup.py b/setup.py
index fb80363..4eb163e 100644
--- a/setup.py
+++ b/setup.py
@@ -2,48 +2,21 @@ from setuptools import setup
from setuptools.command.install import install
from platform import system
from subprocess import call
-import main
+from toxygen.util import program_version
import sys
-import os
-from utils.util import curr_directory, join_path
-version = main.__version__ + '.0'
+version = program_version + '.0'
+MODULES = []
-if system() == 'Windows':
- MODULES = ['PyQt5', 'PyAudio', 'numpy', 'opencv-python', 'pydenticon']
+if system() in ('Windows', 'Darwin'):
+ MODULES = ['PyAudio', 'PySide']
else:
- MODULES = []
try:
import pyaudio
except ImportError:
- MODULES.append('PyAudio')
- try:
- import PyQt5
- except ImportError:
- MODULES.append('PyQt5')
- try:
- import numpy
- except ImportError:
- MODULES.append('numpy')
- try:
- import cv2
- except ImportError:
- MODULES.append('opencv-python')
- try:
- import pydenticon
- except ImportError:
- MODULES.append('pydenticon')
-
-
-def get_packages():
- directory = join_path(curr_directory(__file__), 'toxygen')
- for root, dirs, files in os.walk(directory):
- packages = map(lambda d: 'toxygen.' + d, dirs)
- packages = ['toxygen'] + list(packages)
-
- return packages
+ MODULES = ['PyAudio']
class InstallScript(install):
@@ -52,7 +25,9 @@ class InstallScript(install):
def run(self):
install.run(self)
try:
- if system() != 'Windows':
+ if system() == 'Windows':
+ call(["toxygen", "--configure"])
+ else:
call(["toxygen", "--clean"])
except:
try:
@@ -62,32 +37,35 @@ class InstallScript(install):
if path[-1] not in ('/', '\\'):
path += '/'
path += 'bin/toxygen'
- if system() != 'Windows':
+ 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/toxygen-project/toxygen/',
+ url='https://github.com/xveduk/toxygen/',
keywords='toxygen tox messenger',
author='Ingvar',
maintainer='Ingvar',
license='GPL3',
- packages=get_packages(),
+ packages=['toxygen', 'toxygen.plugins', 'toxygen.styles'],
install_requires=MODULES,
include_package_data=True,
classifiers=[
'Programming Language :: Python :: 3 :: Only',
- 'Programming Language :: Python :: 3.5',
- 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.2',
+ 'Programming Language :: Python :: 3.3',
+ 'Programming Language :: Python :: 3.4',
],
entry_points={
- 'console_scripts': ['toxygen=toxygen.main:main']
+ 'console_scripts': ['toxygen=toxygen.main:main'],
},
cmdclass={
- 'install': InstallScript
- })
+ 'install': InstallScript,
+ },
+ )
diff --git a/tests/tests.py b/tests/tests.py
index e3c9b6b..c9f5ca6 100644
--- a/tests/tests.py
+++ b/tests/tests.py
@@ -1,18 +1,70 @@
-from toxygen.middleware.tox_factory import *
+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
-# TODO: add new tests
+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
+
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 = 'Toxygen User'
- status_message = 'Toxing on Toxygen'
+ name = b'Toxygen User'
+ status_message = b'Toxing on Toxygen'
tox = tox_factory()
tox.self_set_name(name)
tox.self_set_status_message(status_message)
data = tox.get_savedata()
del tox
tox = tox_factory(data)
- assert tox.self_get_name() == name
- assert tox.self_get_status_message() == status_message
+ 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 TestDNS:
+
+ def test_dns(self):
+ bot_id = '56A1ADE4B65B86BCD51CC73E2CD4E542179F47959FE3E0E21B4B0ACDADE51855D34D34D37CB5'
+ tox_id = tox_dns('groupbot@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')
+ copy_data = data[:]
+ data = lib.pass_encrypt(data)
+ data = lib.pass_decrypt(data)
+ assert copy_data == data
diff --git a/tests/travis.py b/tests/travis.py
index 30d5edd..474d961 100644
--- a/tests/travis.py
+++ b/tests/travis.py
@@ -1,4 +1,4 @@
class TestToxygen:
def test_main(self):
- import toxygen.main # check for syntax errors
+ import toxygen.main
diff --git a/toxygen/app.py b/toxygen/app.py
deleted file mode 100644
index a23816d..0000000
--- a/toxygen/app.py
+++ /dev/null
@@ -1,424 +0,0 @@
-from middleware import threads
-import middleware.callbacks as callbacks
-from PyQt5 import QtWidgets, QtGui, QtCore
-import ui.password_screen as password_screen
-import updater.updater as updater
-import os
-from middleware.tox_factory import tox_factory
-import wrapper.toxencryptsave as tox_encrypt_save
-import user_data.toxes
-from user_data.settings import Settings
-from ui.login_screen import LoginScreen
-from user_data.profile_manager import ProfileManager
-from plugin_support.plugin_support import PluginLoader
-from ui.main_screen import MainWindow
-from ui import tray
-import utils.ui as util_ui
-import utils.util as util
-from contacts.profile import Profile
-from file_transfers.file_transfers_handler import FileTransfersHandler
-from contacts.contact_provider import ContactProvider
-from contacts.friend_factory import FriendFactory
-from contacts.group_factory import GroupFactory
-from contacts.contacts_manager import ContactsManager
-from av.calls_manager import CallsManager
-from history.database import Database
-from ui.widgets_factory import WidgetsFactory
-from smileys.smileys import SmileyLoader
-from ui.items_factories import MessagesItemsFactory, ContactItemsFactory
-from messenger.messenger import Messenger
-from network.tox_dns import ToxDns
-from history.history import History
-from file_transfers.file_transfers_messages_service import FileTransfersMessagesService
-from groups.groups_service import GroupsService
-from ui.create_profile_screen import CreateProfileScreen
-from common.provider import Provider
-from contacts.group_peer_factory import GroupPeerFactory
-from user_data.backup_service import BackupService
-import styles.style # TODO: dynamic loading
-
-
-class App:
-
- def __init__(self, version, path_to_profile=None, uri=None):
- self._version = version
- self._app = self._settings = self._profile_manager = self._plugin_loader = self._messenger = None
- self._tox = self._ms = self._init = self._main_loop = self._av_loop = None
- self._uri = self._toxes = self._tray = self._file_transfer_handler = self._contacts_provider = None
- self._friend_factory = self._calls_manager = self._contacts_manager = self._smiley_loader = None
- self._group_peer_factory = self._tox_dns = self._backup_service = None
- self._group_factory = self._groups_service = self._profile = None
- if uri is not None and uri.startswith('tox:'):
- self._uri = uri[4:]
- self._path = path_to_profile
-
- # -----------------------------------------------------------------------------------------------------------------
- # Public methods
- # -----------------------------------------------------------------------------------------------------------------
-
- def main(self):
- """
- Main function of app. loads login screen if needed and starts main screen
- """
- self._app = QtWidgets.QApplication([])
- self._load_icon()
-
- if util.get_platform() == 'Linux':
- QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
-
- self._load_base_style()
-
- if not self._select_and_load_profile():
- return
-
- if self._try_to_update():
- return
-
- self._load_app_styles()
- self._load_app_translations()
-
- self._create_dependencies()
- self._start_threads()
-
- if self._uri is not None:
- self._ms.add_contact(self._uri)
-
- self._app.lastWindowClosed.connect(self._app.quit)
-
- self._execute_app()
-
- self._stop_app()
-
- # -----------------------------------------------------------------------------------------------------------------
- # App executing
- # -----------------------------------------------------------------------------------------------------------------
-
- def _execute_app(self):
- while True:
- try:
- self._app.exec_()
- except Exception as ex:
- util.log('Unhandled exception: ' + str(ex))
- else:
- break
-
- def _stop_app(self):
- self._plugin_loader.stop()
- self._stop_threads()
- self._file_transfer_handler.stop()
- self._tray.hide()
- self._save_profile()
- self._settings.close()
- self._kill_toxav()
- self._kill_tox()
-
- # -----------------------------------------------------------------------------------------------------------------
- # App loading
- # -----------------------------------------------------------------------------------------------------------------
-
- def _load_base_style(self):
- with open(util.join_path(util.get_styles_directory(), 'dark_style.qss')) as fl:
- style = fl.read()
- self._app.setStyleSheet(style)
-
- def _load_app_styles(self):
- # application color scheme
- if self._settings['theme'] == 'dark':
- return
- for theme in self._settings.built_in_themes().keys():
- if self._settings['theme'] != theme:
- continue
- theme_path = self._settings.built_in_themes()[theme]
- file_path = util.join_path(util.get_styles_directory(), theme_path)
- with open(file_path) as fl:
- style = fl.read()
- self._app.setStyleSheet(style)
- break
-
- def _load_login_screen_translations(self):
- current_language, supported_languages = self._get_languages()
- if current_language not in supported_languages:
- return
- lang_path = supported_languages[current_language]
- translator = QtCore.QTranslator()
- translator.load(util.get_translations_directory() + lang_path)
- self._app.installTranslator(translator)
- self._app.translator = translator
-
- def _load_icon(self):
- icon_file = os.path.join(util.get_images_directory(), 'icon.png')
- self._app.setWindowIcon(QtGui.QIcon(icon_file))
-
- @staticmethod
- def _get_languages():
- current_locale = QtCore.QLocale()
- curr_language = current_locale.languageToString(current_locale.language())
- supported_languages = Settings.supported_languages()
-
- return curr_language, supported_languages
-
- def _load_app_translations(self):
- lang = Settings.supported_languages()[self._settings['language']]
- translator = QtCore.QTranslator()
- translator.load(os.path.join(util.get_translations_directory(), lang))
- self._app.installTranslator(translator)
- self._app.translator = translator
-
- def _select_and_load_profile(self):
- encrypt_save = tox_encrypt_save.ToxEncryptSave()
- self._toxes = user_data.toxes.ToxES(encrypt_save)
-
- if self._path is not None: # toxygen was started with path to profile
- self._load_existing_profile(self._path)
- else:
- auto_profile = Settings.get_auto_profile()
- if auto_profile is None: # no default profile
- result = self._select_profile()
- if result is None:
- return False
- if result.is_new_profile(): # create new profile
- if not self._create_new_profile(result.profile_path):
- return False
- else: # load existing profile
- self._load_existing_profile(result.profile_path)
- self._path = result.profile_path
- else: # default profile
- self._path = auto_profile
- self._load_existing_profile(auto_profile)
-
- if Settings.is_active_profile(self._path): # profile is in use
- profile_name = util.get_profile_name_from_path(self._path)
- title = util_ui.tr('Profile {}').format(profile_name)
- text = util_ui.tr(
- 'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?')
- reply = util_ui.question(text, title)
- if not reply:
- return False
-
- self._settings.set_active_profile()
-
- return True
-
- # -----------------------------------------------------------------------------------------------------------------
- # Threads
- # -----------------------------------------------------------------------------------------------------------------
-
- def _start_threads(self, initial_start=True):
- # init thread
- self._init = threads.InitThread(self._tox, self._plugin_loader, self._settings, initial_start)
- self._init.start()
-
- # starting threads for tox iterate and toxav iterate
- self._main_loop = threads.ToxIterateThread(self._tox)
- self._main_loop.start()
- self._av_loop = threads.ToxAVIterateThread(self._tox.AV)
- self._av_loop.start()
-
- if initial_start:
- threads.start_file_transfer_thread()
-
- def _stop_threads(self, is_app_closing=True):
- self._init.stop_thread()
-
- self._av_loop.stop_thread()
- self._main_loop.stop_thread()
-
- if is_app_closing:
- threads.stop_file_transfer_thread()
-
- # -----------------------------------------------------------------------------------------------------------------
- # Profiles
- # -----------------------------------------------------------------------------------------------------------------
-
- def _select_profile(self):
- self._load_login_screen_translations()
- ls = LoginScreen()
- profiles = ProfileManager.find_profiles()
- ls.update_select(profiles)
- ls.show()
- self._app.exec_()
-
- return ls.result
-
- def _load_existing_profile(self, profile_path):
- self._profile_manager = ProfileManager(self._toxes, profile_path)
- data = self._profile_manager.open_profile()
- if self._toxes.is_data_encrypted(data):
- data = self._enter_password(data)
- self._settings = Settings(self._toxes, profile_path.replace('.tox', '.json'))
- self._tox = self._create_tox(data)
-
- def _create_new_profile(self, profile_name):
- result = self._get_create_profile_screen_result()
- if result is None:
- return False
- if result.save_into_default_folder:
- profile_path = util.join_path(Settings.get_default_path(), profile_name + '.tox')
- else:
- profile_path = util.join_path(util.curr_directory(__file__), profile_name + '.tox')
- if os.path.isfile(profile_path):
- util_ui.message_box(util_ui.tr('Profile with this name already exists'),
- util_ui.tr('Error'))
- return False
- name = profile_name or 'toxygen_user'
- self._tox = tox_factory()
- self._tox.self_set_name(name if name else 'Toxygen User')
- self._tox.self_set_status_message('Toxing on Toxygen')
- self._path = profile_path
- if result.password:
- self._toxes.set_password(result.password)
- self._settings = Settings(self._toxes, self._path.replace('.tox', '.json'))
- self._profile_manager = ProfileManager(self._toxes, profile_path)
- try:
- self._save_profile()
- except Exception as ex:
- print(ex)
- util.log('Profile creation exception: ' + str(ex))
- text = util_ui.tr('Profile saving error! Does Toxygen have permission to write to this directory?')
- util_ui.message_box(text, util_ui.tr('Error'))
-
- return False
- current_language, supported_languages = self._get_languages()
- if current_language in supported_languages:
- self._settings['language'] = current_language
- self._settings.save()
-
- return True
-
- def _get_create_profile_screen_result(self):
- cps = CreateProfileScreen()
- cps.show()
- self._app.exec_()
-
- return cps.result
-
- def _save_profile(self, data=None):
- data = data or self._tox.get_savedata()
- self._profile_manager.save_profile(data)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Other private methods
- # -----------------------------------------------------------------------------------------------------------------
-
- def _enter_password(self, data):
- """
- Show password screen
- """
- p = password_screen.PasswordScreen(self._toxes, data)
- p.show()
- self._app.lastWindowClosed.connect(self._app.quit)
- self._app.exec_()
- if p.result is not None:
- return p.result
- self._force_exit()
-
- def _reset(self):
- """
- Create new tox instance (new network settings)
- :return: tox instance
- """
- self._contacts_manager.reset_contacts_statuses()
- self._stop_threads(False)
- data = self._tox.get_savedata()
- self._save_profile(data)
- self._kill_toxav()
- self._kill_tox()
- # create new tox instance
- self._tox = self._create_tox(data)
- self._start_threads(False)
-
- tox_savers = [self._friend_factory, self._group_factory, self._plugin_loader, self._contacts_manager,
- self._contacts_provider, self._messenger, self._file_transfer_handler, self._groups_service,
- self._profile]
- for tox_saver in tox_savers:
- tox_saver.set_tox(self._tox)
-
- self._calls_manager.set_toxav(self._tox.AV)
- self._contacts_manager.update_friends_numbers()
- self._contacts_manager.update_groups_lists()
- self._contacts_manager.update_groups_numbers()
-
- self._init_callbacks()
-
- def _create_dependencies(self):
- self._backup_service = BackupService(self._settings, self._profile_manager)
- self._smiley_loader = SmileyLoader(self._settings)
- self._tox_dns = ToxDns(self._settings)
- self._ms = MainWindow(self._settings, self._tray)
- db = Database(self._path.replace('.tox', '.db'), self._toxes)
-
- contact_items_factory = ContactItemsFactory(self._settings, self._ms)
- self._friend_factory = FriendFactory(self._profile_manager, self._settings,
- self._tox, db, contact_items_factory)
- self._group_factory = GroupFactory(self._profile_manager, self._settings, self._tox, db, contact_items_factory)
- self._group_peer_factory = GroupPeerFactory(self._tox, self._profile_manager, db, contact_items_factory)
- self._contacts_provider = ContactProvider(self._tox, self._friend_factory, self._group_factory,
- self._group_peer_factory)
- self._profile = Profile(self._profile_manager, self._tox, self._ms, self._contacts_provider, self._reset)
- self._init_profile()
- self._plugin_loader = PluginLoader(self._settings, self)
- history = None
- messages_items_factory = MessagesItemsFactory(self._settings, self._plugin_loader, self._smiley_loader,
- self._ms, lambda m: history.delete_message(m))
- history = History(self._contacts_provider, db, self._settings, self._ms, messages_items_factory)
- self._contacts_manager = ContactsManager(self._tox, self._settings, self._ms, self._profile_manager,
- self._contacts_provider, history, self._tox_dns,
- messages_items_factory)
- history.set_contacts_manager(self._contacts_manager)
- self._calls_manager = CallsManager(self._tox.AV, self._settings, self._ms, self._contacts_manager)
- self._messenger = Messenger(self._tox, self._plugin_loader, self._ms, self._contacts_manager,
- self._contacts_provider, messages_items_factory, self._profile,
- self._calls_manager)
- file_transfers_message_service = FileTransfersMessagesService(self._contacts_manager, messages_items_factory,
- self._profile, self._ms)
- self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider,
- file_transfers_message_service, self._profile)
- messages_items_factory.set_file_transfers_handler(self._file_transfer_handler)
- widgets_factory = None
- widgets_factory_provider = Provider(lambda: widgets_factory)
- self._groups_service = GroupsService(self._tox, self._contacts_manager, self._contacts_provider, self._ms,
- widgets_factory_provider)
- widgets_factory = WidgetsFactory(self._settings, self._profile, self._profile_manager, self._contacts_manager,
- self._file_transfer_handler, self._smiley_loader, self._plugin_loader,
- self._toxes, self._version, self._groups_service, history,
- self._contacts_provider)
- self._tray = tray.init_tray(self._profile, self._settings, self._ms, self._toxes)
- self._ms.set_dependencies(widgets_factory, self._tray, self._contacts_manager, self._messenger, self._profile,
- self._plugin_loader, self._file_transfer_handler, history, self._calls_manager,
- self._groups_service, self._toxes)
-
- self._tray.show()
- self._ms.show()
-
- self._init_callbacks()
-
- def _try_to_update(self):
- updating = updater.start_update_if_needed(self._version, self._settings)
- if updating:
- self._save_profile()
- self._settings.close()
- self._kill_toxav()
- self._kill_tox()
- return updating
-
- def _create_tox(self, data):
- return tox_factory(data, self._settings)
-
- def _force_exit(self):
- raise SystemExit()
-
- def _init_callbacks(self):
- callbacks.init_callbacks(self._tox, self._profile, self._settings, self._plugin_loader, self._contacts_manager,
- self._calls_manager, self._file_transfer_handler, self._ms, self._tray,
- self._messenger, self._groups_service, self._contacts_provider)
-
- def _init_profile(self):
- if not self._profile.has_avatar():
- self._profile.reset_avatar(self._settings['identicons'])
-
- def _kill_toxav(self):
- self._calls_manager.set_toxav(None)
- self._tox.AV.kill()
-
- def _kill_tox(self):
- self._tox.kill()
diff --git a/toxygen/av/__init__.py b/toxygen/av/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/toxygen/av/call.py b/toxygen/av/call.py
deleted file mode 100644
index d3e023b..0000000
--- a/toxygen/av/call.py
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
-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._out_video = value
-
- out_video = property(get_out_video, set_out_video)
diff --git a/toxygen/av/calls.py b/toxygen/av/calls.py
deleted file mode 100644
index d5f2fe7..0000000
--- a/toxygen/av/calls.py
+++ /dev/null
@@ -1,281 +0,0 @@
-import pyaudio
-import time
-import threading
-from wrapper.toxav_enums import *
-import cv2
-import itertools
-import numpy as np
-from av import screen_sharing
-from av.call import Call
-import common.tox_save
-
-
-class AV(common.tox_save.ToxAvSave):
-
- def __init__(self, toxav, settings):
- super().__init__(toxav)
- self._settings = settings
- self._running = True
-
- self._calls = {} # dict: key - friend number, value - Call instance
-
- self._audio = None
- self._audio_stream = None
- self._audio_thread = None
- self._audio_running = False
- self._out_stream = None
-
- self._audio_rate = 8000
- self._audio_channels = 1
- self._audio_duration = 60
- self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration // 1000
-
- self._video = None
- self._video_thread = None
- self._video_running = False
-
- 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'] > 0
- call.in_video = state | TOXAV_FRIEND_CALL_STATE['SENDING_V'] > 0
-
- if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_A'] and call.out_audio:
- self.start_audio_thread()
-
- if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_V'] and call.out_video:
- self.start_video_thread()
-
- def is_video_call(self, number):
- return number in self and self._calls[number].in_video
-
- # -----------------------------------------------------------------------------------------------------------------
- # Threads
- # -----------------------------------------------------------------------------------------------------------------
-
- def start_audio_thread(self):
- """
- Start audio sending
- """
- if self._audio_thread is not None:
- return
-
- self._audio_running = True
-
- self._audio = pyaudio.PyAudio()
- self._audio_stream = self._audio.open(format=pyaudio.paInt16,
- rate=self._audio_rate,
- channels=self._audio_channels,
- input=True,
- input_device_index=self._settings.audio['input'],
- frames_per_buffer=self._audio_sample_count * 10)
-
- self._audio_thread = threading.Thread(target=self.send_audio)
- self._audio_thread.start()
-
- def stop_audio_thread(self):
-
- if self._audio_thread is None:
- return
-
- self._audio_running = False
-
- self._audio_thread.join()
-
- self._audio_thread = None
- self._audio_stream = None
- self._audio = None
-
- if self._out_stream is not None:
- self._out_stream.stop_stream()
- self._out_stream.close()
- self._out_stream = None
-
- def start_video_thread(self):
- if self._video_thread is not None:
- return
-
- self._video_running = True
- self._video_width = s.video['width']
- self._video_height = s.video['height']
-
- if s.video['device'] == -1:
- self._video = screen_sharing.DesktopGrabber(self._settings.video['x'], self._settings.video['y'],
- self._settings.video['width'], self._settings.video['height'])
- else:
- self._video = cv2.VideoCapture(self._settings.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
- """
-
- if self._out_stream is None:
- self._out_stream = self._audio.open(format=pyaudio.paInt16,
- channels=channels_count,
- rate=rate,
- output_device_index=self._settings.audio['output'],
- output=True)
- self._out_stream.write(samples)
-
- # -----------------------------------------------------------------------------------------------------------------
- # AV sending
- # -----------------------------------------------------------------------------------------------------------------
-
- def send_audio(self):
- """
- This method sends audio to friends
- """
-
- while self._audio_running:
- try:
- pcm = self._audio_stream.read(self._audio_sample_count)
- if pcm:
- for friend_num in self._calls:
- if self._calls[friend_num].out_audio:
- try:
- self._toxav.audio_send_frame(friend_num, pcm, self._audio_sample_count,
- self._audio_channels, self._audio_rate)
- except:
- pass
- except:
- pass
-
- time.sleep(0.01)
-
- def send_video(self):
- """
- This method sends video to friends
- """
- while self._video_running:
- try:
- result, frame = self._video.read()
- if result:
- height, width, channels = frame.shape
- for friend_num in self._calls:
- if self._calls[friend_num].out_video:
- try:
- y, u, v = self.convert_bgr_to_yuv(frame)
- self._toxav.video_send_frame(friend_num, width, height, y, u, v)
- except:
- pass
- except:
- pass
-
- 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)
diff --git a/toxygen/av/calls_manager.py b/toxygen/av/calls_manager.py
deleted file mode 100644
index 5a48672..0000000
--- a/toxygen/av/calls_manager.py
+++ /dev/null
@@ -1,116 +0,0 @@
-import threading
-import cv2
-import av.calls
-from messenger.messages import *
-from ui import av_widgets
-import common.event as event
-
-
-class CallsManager:
-
- def __init__(self, toxav, settings, screen, contacts_manager):
- self._call = av.calls.AV(toxav, settings) # object with data about calls
- self._call_widgets = {} # dict of incoming call widgets
- self._incoming_calls = set()
- self._settings = settings
- self._screen = screen
- self._contacts_manager = contacts_manager
- self._call_started_event = event.Event() # friend_number, audio, video, is_outgoing
- self._call_finished_event = event.Event() # friend_number, is_declined
-
- def set_toxav(self, toxav):
- self._call.set_toxav(toxav)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Events
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_call_started_event(self):
- return self._call_started_event
-
- call_started_event = property(get_call_started_event)
-
- def get_call_finished_event(self):
- return self._call_finished_event
-
- call_finished_event = property(get_call_finished_event)
-
- # -----------------------------------------------------------------------------------------------------------------
- # AV support
- # -----------------------------------------------------------------------------------------------------------------
-
- def call_click(self, audio=True, video=False):
- """User clicked audio button in main window"""
- num = self._contacts_manager.get_active_number()
- if not self._contacts_manager.is_active_a_friend():
- return
- if num not in self._call and self._contacts_manager.is_active_online(): # start call
- if not self._settings.audio['enabled']:
- return
- self._call(num, audio, video)
- self._screen.active_call()
- self._call_started_event(num, audio, video, True)
- elif num in self._call: # finish or cancel call if you call with active friend
- self.stop_call(num, False)
-
- def incoming_call(self, audio, video, friend_number):
- """
- Incoming call from friend.
- """
- if not self._settings.audio['enabled']:
- return
- friend = self._contacts_manager.get_friend_by_number(friend_number)
- self._call_started_event(friend_number, audio, video, False)
- self._incoming_calls.add(friend_number)
- if friend_number == self._contacts_manager.get_active_number():
- self._screen.incoming_call()
- else:
- friend.actions = True
- text = util_ui.tr("Incoming video call") if video else util_ui.tr("Incoming audio call")
- self._call_widgets[friend_number] = self._get_incoming_call_widget(friend_number, text, friend.name)
- self._call_widgets[friend_number].set_pixmap(friend.get_pixmap())
- self._call_widgets[friend_number].show()
-
- def accept_call(self, friend_number, audio, video):
- """
- Accept incoming call with audio or video
- """
- self._call.accept_call(friend_number, audio, video)
- self._screen.active_call()
- if friend_number in self._incoming_calls:
- self._incoming_calls.remove(friend_number)
- del self._call_widgets[friend_number]
-
- def stop_call(self, friend_number, by_friend):
- """
- Stop call with friend
- """
- if friend_number in self._incoming_calls:
- self._incoming_calls.remove(friend_number)
- is_declined = True
- else:
- is_declined = False
- self._screen.call_finished()
- is_video = self._call.is_video_call(friend_number)
- self._call.finish_call(friend_number, by_friend) # finish or decline call
- if friend_number in self._call_widgets:
- self._call_widgets[friend_number].close()
- del self._call_widgets[friend_number]
-
- def destroy_window():
- if is_video:
- cv2.destroyWindow(str(friend_number))
-
- threading.Timer(2.0, destroy_window).start()
- self._call_finished_event(friend_number, is_declined)
-
- def friend_exit(self, friend_number):
- if friend_number in self._call:
- self._call.finish_call(friend_number, True)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Private methods
- # -----------------------------------------------------------------------------------------------------------------
-
- def _get_incoming_call_widget(self, friend_number, text, friend_name):
- return av_widgets.IncomingCallWidget(self._settings, self, friend_number, text, friend_name)
diff --git a/toxygen/av/screen_sharing.py b/toxygen/av/screen_sharing.py
deleted file mode 100644
index 265658c..0000000
--- a/toxygen/av/screen_sharing.py
+++ /dev/null
@@ -1,22 +0,0 @@
-import numpy as np
-from PyQt5 import QtWidgets
-
-
-class DesktopGrabber:
-
- def __init__(self, x, y, width, height):
- self._x = x
- self._y = y
- self._width = width
- self._height = height
- self._width -= width % 4
- self._height -= height % 4
- self._screen = QtWidgets.QApplication.primaryScreen()
-
- def read(self):
- pixmap = self._screen.grabWindow(0, self._x, self._y, self._width, self._height)
- image = pixmap.toImage()
- s = image.bits().asstring(self._width * self._height * 4)
- arr = np.fromstring(s, dtype=np.uint8).reshape((self._height, self._width, 4))
-
- return True, arr
diff --git a/toxygen/ui/av_widgets.py b/toxygen/avwidgets.py
similarity index 57%
rename from toxygen/ui/av_widgets.py
rename to toxygen/avwidgets.py
index e5773a8..a01b52d 100644
--- a/toxygen/ui/av_widgets.py
+++ b/toxygen/avwidgets.py
@@ -1,45 +1,48 @@
-from PyQt5 import QtCore, QtGui, QtWidgets
-from ui import widgets
-import utils.util as util
+try:
+ from PySide import QtCore, QtGui
+except ImportError:
+ from PyQt4 import QtCore, QtGui
+import widgets
+import profile
+import util
import pyaudio
import wave
+import settings
+from util import curr_directory
class IncomingCallWidget(widgets.CenteredWidget):
- def __init__(self, settings, calls_manager, friend_number, text, name):
- super().__init__()
- self._settings = settings
- self._calls_manager = calls_manager
+ def __init__(self, friend_number, text, name):
+ super(IncomingCallWidget, self).__init__()
self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowStaysOnTopHint)
self.resize(QtCore.QSize(500, 270))
- self.avatar_label = QtWidgets.QLabel(self)
+ self.avatar_label = QtGui.QLabel(self)
self.avatar_label.setGeometry(QtCore.QRect(10, 20, 64, 64))
self.avatar_label.setScaledContents(False)
self.name = widgets.DataLabel(self)
self.name.setGeometry(QtCore.QRect(90, 20, 300, 25))
- self._friend_number = friend_number
font = QtGui.QFont()
- font.setFamily(settings['font'])
+ 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 = QtWidgets.QPushButton(self)
+ self.accept_audio = QtGui.QPushButton(self)
self.accept_audio.setGeometry(QtCore.QRect(20, 100, 150, 150))
- self.accept_video = QtWidgets.QPushButton(self)
+ self.accept_video = QtGui.QPushButton(self)
self.accept_video.setGeometry(QtCore.QRect(170, 100, 150, 150))
- self.decline = QtWidgets.QPushButton(self)
+ self.decline = QtGui.QPushButton(self)
self.decline.setGeometry(QtCore.QRect(320, 100, 150, 150))
- pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'accept_audio.png'))
+ pixmap = QtGui.QPixmap(util.curr_directory() + '/images/accept_audio.png')
icon = QtGui.QIcon(pixmap)
self.accept_audio.setIcon(icon)
- pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'accept_video.png'))
+ pixmap = QtGui.QPixmap(util.curr_directory() + '/images/accept_video.png')
icon = QtGui.QIcon(pixmap)
self.accept_video.setIcon(icon)
- pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'decline_call.png'))
+ pixmap = QtGui.QPixmap(util.curr_directory() + '/images/decline_call.png')
icon = QtGui.QIcon(pixmap)
self.decline.setIcon(icon)
self.accept_audio.setIconSize(QtCore.QSize(150, 150))
@@ -51,16 +54,15 @@ class IncomingCallWidget(widgets.CenteredWidget):
self.setWindowTitle(text)
self.name.setText(name)
self.call_type.setText(text)
- 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)
+ 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())
class SoundPlay(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
- self.a = None
def run(self):
class AudioFile:
@@ -89,11 +91,11 @@ class IncomingCallWidget(widgets.CenteredWidget):
self.stream.close()
self.p.terminate()
- self.a = AudioFile(util.join_path(util.get_sounds_directory(), 'call.wav'))
+ self.a = AudioFile(curr_directory() + '/sounds/call.wav')
self.a.play()
self.a.close()
- if self._settings['calls_sound']:
+ if settings.Settings.get_instance()['calls_sound']:
self.thread = SoundPlay()
self.thread.start()
else:
@@ -105,26 +107,33 @@ class IncomingCallWidget(widgets.CenteredWidget):
self.thread.wait()
self.close()
- def accept_call_with_audio(self):
- if self._processing:
- return
- self._processing = True
- self._calls_manager.accept_call(self._friend_number, True, False)
- self.stop()
-
- def accept_call_with_video(self):
- if self._processing:
- return
- self._processing = True
- self._calls_manager.accept_call(self._friend_number, True, True)
- self.stop()
-
- def decline_call(self):
- if self._processing:
- return
- self._processing = True
- self._calls_manager.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()
+
+
diff --git a/toxygen/bootstrap.py b/toxygen/bootstrap.py
new file mode 100644
index 0000000..89534b7
--- /dev/null
+++ b/toxygen/bootstrap.py
@@ -0,0 +1,83 @@
+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
+
+ def get_data(self):
+ return bytes(self._ip, 'utf-8'), self._port, self._tox_key
+
+
+def node_generator():
+ nodes = []
+ ips = [
+ "144.76.60.215", "23.226.230.47", "195.154.119.113", "biribiri.org",
+ "46.38.239.179", "178.62.250.138", "130.133.110.14", "104.167.101.29",
+ "205.185.116.116", "198.98.51.198", "80.232.246.79", "108.61.165.198",
+ "212.71.252.109", "194.249.212.109", "185.25.116.107", "192.99.168.140",
+ "46.101.197.175", "95.215.46.114", "5.189.176.217", "148.251.23.146",
+ "104.223.122.15", "78.47.114.252", "d4rk4.ru", "81.4.110.149",
+ "95.31.20.151", "104.233.104.126", "51.254.84.212", "home.vikingmakt.com.br",
+ "5.135.59.163", "185.58.206.164", "188.244.38.183", "mrflibble.c4.ee",
+ "82.211.31.116", "128.199.199.197", "103.230.156.174", "91.121.66.124",
+ "92.54.84.70", "tox1.privacydragon.me"
+ ]
+ ports = [
+ 33445, 33445, 33445, 33445,
+ 33445, 33445, 33445, 33445,
+ 33445, 33445, 33445, 33445,
+ 33445, 33445, 33445, 33445,
+ 443, 33445, 5190, 2306,
+ 33445, 33445, 1813, 33445,
+ 33445, 33445, 33445, 33445,
+ 33445, 33445, 33445, 33445,
+ 33445, 33445, 33445, 33445,
+ 33445, 33445
+ ]
+ ids = [
+ "04119E835DF3E78BACF0F84235B300546AF8B936F035185E2A8E9E0A67C8924F",
+ "A09162D68618E742FFBCA1C2C70385E6679604B2D80EA6E84AD0996A1AC8A074",
+ "E398A69646B8CEACA9F0B84F553726C1C49270558C57DF5F3C368F05A7D71354",
+ "F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67",
+ "F5A1A38EFB6BD3C2C8AF8B10D85F0F89E931704D349F1D0720C3C4059AF2440A",
+ "788236D34978D1D5BD822F0A5BEBD2C53C64CC31CD3149350EE27D4D9A2F9B6B",
+ "461FA3776EF0FA655F1A05477DF1B3B614F7D6B124F7DB1DD4FE3C08B03B640F",
+ "5918AC3C06955962A75AD7DF4F80A5D7C34F7DB9E1498D2E0495DE35B3FE8A57",
+ "A179B09749AC826FF01F37A9613F6B57118AE014D4196A0E1105A98F93A54702",
+ "1D5A5F2F5D6233058BF0259B09622FB40B482E4FA0931EB8FD3AB8E7BF7DAF6F",
+ "CF6CECA0A14A31717CC8501DA51BE27742B70746956E6676FF423A529F91ED5D",
+ "8E7D0B859922EF569298B4D261A8CCB5FEA14FB91ED412A7603A585A25698832",
+ "C4CEB8C7AC607C6B374E2E782B3C00EA3A63B80D4910B8649CCACDD19F260819",
+ "3CEE1F054081E7A011234883BC4FC39F661A55B73637A5AC293DDF1251D9432B",
+ "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43",
+ "6A4D0607A296838434A6A7DDF99F50EF9D60A2C510BBF31FE538A25CB6B4652F",
+ "CD133B521159541FB1D326DE9850F5E56A6C724B5B8E5EB5CD8D950408E95707",
+ "5823FB947FF24CF83DDFAC3F3BAA18F96EA2018B16CC08429CB97FA502F40C23",
+ "2B2137E094F743AC8BD44652C55F41DFACC502F125E99E4FE24D40537489E32F",
+ "7AED21F94D82B05774F697B209628CD5A9AD17E0C073D9329076A4C28ED28147",
+ "0FB96EEBFB1650DDB52E70CF773DDFCABE25A95CC3BB50FC251082E4B63EF82A",
+ "1C5293AEF2114717547B39DA8EA6F1E331E5E358B35F9B6B5F19317911C5F976",
+ "53737F6D47FA6BD2808F378E339AF45BF86F39B64E79D6D491C53A1D522E7039",
+ "9E7BD4793FFECA7F32238FA2361040C09025ED3333744483CA6F3039BFF0211E",
+ "9CA69BB74DE7C056D1CC6B16AB8A0A38725C0349D187D8996766958584D39340",
+ "EDEE8F2E839A57820DE3DA4156D88350E53D4161447068A3457EE8F59F362414",
+ "AEC204B9A4501412D5F0BB67D9C81B5DB3EE6ADA64122D32A3E9B093D544327D",
+ "188E072676404ED833A4E947DC1D223DF8EFEBE5F5258573A236573688FB9761",
+ "2D320F971EF2CA18004416C2AAE7BA52BF7949DB34EA8E2E21AF67BD367BE211",
+ "24156472041E5F220D1FA11D9DF32F7AD697D59845701CDD7BE7D1785EB9DB39",
+ "15A0F9684E2423F9F46CFA5A50B562AE42525580D840CC50E518192BF333EE38",
+ "FAAB17014F42F7F20949F61E55F66A73C230876812A9737F5F6D2DCE4D9E4207",
+ "AF97B76392A6474AF2FD269220FDCF4127D86A42EF3A242DF53A40A268A2CD7C",
+ "B05C8869DBB4EDDD308F43C1A974A20A725A36EACCA123862FDE9945BF9D3E09",
+ "5C4C7A60183D668E5BD8B3780D1288203E2F1BAE4EEF03278019E21F86174C1D",
+ "4E3F7D37295664BBD0741B6DBCB6431D6CD77FC4105338C2FC31567BF5C8224A",
+ "5625A62618CB4FCA70E147A71B29695F38CC65FF0CBD68AD46254585BE564802",
+ "31910C0497D347FF160D6F3A6C0E317BAFA71E8E03BC4CBB2A185C9D4FB8B31E"
+ ]
+ for i in range(len(ips)):
+ nodes.append(Node(ips[i], ports[i], ids[i], random.randint(0, 1000000)))
+ arr = sorted(nodes, key=lambda x: x.rand)[:4]
+ for elem in arr:
+ yield elem.get_data()
+
diff --git a/toxygen/bootstrap/__init__.py b/toxygen/bootstrap/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/toxygen/bootstrap/bootstrap.py b/toxygen/bootstrap/bootstrap.py
deleted file mode 100644
index fad68c4..0000000
--- a/toxygen/bootstrap/bootstrap.py
+++ /dev/null
@@ -1,83 +0,0 @@
-import random
-import urllib.request
-from utils.util import *
-from PyQt5 import QtNetwork, QtCore
-import json
-
-
-DEFAULT_NODES_COUNT = 4
-
-
-class Node:
-
- def __init__(self, node):
- self._ip, self._port, self._tox_key = node['ipv4'], node['port'], node['public_key']
- self._priority = random.randint(1, 1000000) if node['status_tcp'] and node['status_udp'] else 0
-
- def get_priority(self):
- return self._priority
-
- priority = property(get_priority)
-
- def get_data(self):
- return self._ip, self._port, self._tox_key
-
-
-def generate_nodes(nodes_count=DEFAULT_NODES_COUNT):
- with open(_get_nodes_path(), 'rt') as fl:
- json_nodes = json.loads(fl.read())['nodes']
- nodes = map(lambda json_node: Node(json_node), json_nodes)
- nodes = filter(lambda n: n.priority > 0, nodes)
- sorted_nodes = sorted(nodes, key=lambda x: x.priority)
- if nodes_count is not None:
- sorted_nodes = sorted_nodes[-DEFAULT_NODES_COUNT:]
- for node in sorted_nodes:
- yield node.get_data()
-
-
-def download_nodes_list(settings):
- url = 'https://nodes.tox.chat/json'
- if not settings['download_nodes_list']:
- return
-
- if not settings['proxy_type']: # no proxy
- try:
- req = urllib.request.Request(url)
- req.add_header('Content-Type', 'application/json')
- response = urllib.request.urlopen(req)
- result = response.read()
- _save_nodes(result)
- except Exception as ex:
- log('TOX nodes loading error: ' + str(ex))
- else: # proxy
- netman = QtNetwork.QNetworkAccessManager()
- proxy = QtNetwork.QNetworkProxy()
- proxy.setType(
- QtNetwork.QNetworkProxy.Socks5Proxy if settings['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy)
- proxy.setHostName(settings['proxy_host'])
- proxy.setPort(settings['proxy_port'])
- netman.setProxy(proxy)
- try:
- request = QtNetwork.QNetworkRequest()
- request.setUrl(QtCore.QUrl(url))
- reply = netman.get(request)
-
- while not reply.isFinished():
- QtCore.QThread.msleep(1)
- QtCore.QCoreApplication.processEvents()
- data = bytes(reply.readAll().data())
- _save_nodes(data)
- except Exception as ex:
- log('TOX nodes loading error: ' + str(ex))
-
-
-def _get_nodes_path():
- return join_path(curr_directory(__file__), 'nodes.json')
-
-
-def _save_nodes(nodes):
- if not nodes:
- return
- print('Saving nodes...')
- with open(_get_nodes_path(), 'wb') as fl:
- fl.write(nodes)
diff --git a/toxygen/bootstrap/nodes.json b/toxygen/bootstrap/nodes.json
deleted file mode 100644
index 5314998..0000000
--- a/toxygen/bootstrap/nodes.json
+++ /dev/null
@@ -1 +0,0 @@
-{"nodes":[{"ipv4":"80.211.19.83","ipv6":"-","port":33445,"public_key":"A2D7BF17C10A12C339B9F4E8DD77DEEE8457D580535A6F0D0F9AF04B8B4C4420","status_udp":true,"status_tcp":true}]}
\ No newline at end of file
diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py
new file mode 100644
index 0000000..d5b9784
--- /dev/null
+++ b/toxygen/callbacks.py
@@ -0,0 +1,357 @@
+try:
+ from PySide import QtCore
+except ImportError:
+ from PyQt4 import QtCore
+from notifications import *
+from settings import Settings
+from profile import Profile
+from toxcore_enums_and_consts import *
+from toxav_enums import *
+from tox import bin_to_string
+from plugin_support import PluginLoader
+import queue
+import threading
+import util
+
+
+# -----------------------------------------------------------------------------------------------------------------
+# Threads
+# -----------------------------------------------------------------------------------------------------------------
+
+
+class InvokeEvent(QtCore.QEvent):
+ EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
+
+ def __init__(self, fn, *args, **kwargs):
+ QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
+ self.fn = fn
+ self.args = args
+ self.kwargs = kwargs
+
+
+class Invoker(QtCore.QObject):
+
+ def event(self, event):
+ event.fn(*event.args, **event.kwargs)
+ return True
+
+_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
+# -----------------------------------------------------------------------------------------------------------------
+
+
+def self_connection_status(tox_link):
+ """
+ Current user changed connection status (offline, UDP, TCP)
+ """
+ def wrapped(tox, connection, user_data):
+ print('Connection status: ', str(connection))
+ profile = Profile.get_instance()
+ if profile.status is None:
+ status = tox_link.self_get_status()
+ invoke_in_main_thread(profile.set_status, status)
+ elif connection == TOX_CONNECTION['NONE']:
+ invoke_in_main_thread(profile.set_status, None)
+ return wrapped
+
+
+# -----------------------------------------------------------------------------------------------------------------
+# Callbacks - friends
+# -----------------------------------------------------------------------------------------------------------------
+
+
+def friend_status(tox, friend_num, new_status, user_data):
+ """
+ Check friend's status (none, busy, away)
+ """
+ print("Friend's #{} status changed!".format(friend_num))
+ profile = Profile.get_instance()
+ friend = profile.get_friend_by_number(friend_num)
+ if friend.status is None and Settings.get_instance()['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
+ sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
+ invoke_in_main_thread(friend.set_status, new_status)
+ invoke_in_main_thread(QtCore.QTimer.singleShot, 5000, lambda: profile.send_files(friend_num))
+ invoke_in_main_thread(profile.update_filtration)
+
+
+def friend_connection_status(tox, friend_num, new_status, user_data):
+ """
+ Check friend's connection status (offline, udp, tcp)
+ """
+ print("Friend #{} connection status: {}".format(friend_num, new_status))
+ profile = Profile.get_instance()
+ friend = profile.get_friend_by_number(friend_num)
+ if new_status == TOX_CONNECTION['NONE']:
+ invoke_in_main_thread(profile.friend_exit, friend_num)
+ invoke_in_main_thread(profile.update_filtration)
+ if Settings.get_instance()['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
+ sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
+ elif friend.status is None:
+ invoke_in_main_thread(profile.send_avatar, friend_num)
+ invoke_in_main_thread(PluginLoader.get_instance().friend_online, friend_num)
+
+
+def friend_name(tox, friend_num, name, size, user_data):
+ """
+ Friend changed his name
+ """
+ profile = Profile.get_instance()
+ print('New name friend #' + str(friend_num))
+ invoke_in_main_thread(profile.new_name, friend_num, name)
+
+
+def friend_status_message(tox, friend_num, status_message, size, user_data):
+ """
+ :return: function for callback friend_status_message. It updates friend's status message
+ and calls window repaint
+ """
+ profile = Profile.get_instance()
+ friend = profile.get_friend_by_number(friend_num)
+ invoke_in_main_thread(friend.set_status_message, status_message)
+ print('User #{} has new status'.format(friend_num))
+ invoke_in_main_thread(profile.send_messages, friend_num)
+ if profile.get_active_number() == friend_num:
+ invoke_in_main_thread(profile.set_active)
+
+
+def friend_message(window, tray):
+ """
+ New message from friend
+ """
+ def wrapped(tox, friend_number, message_type, message, size, user_data):
+ profile = Profile.get_instance()
+ settings = Settings.get_instance()
+ message = str(message, 'utf-8')
+ invoke_in_main_thread(profile.new_message, friend_number, message_type, message)
+ if not window.isActiveWindow():
+ friend = profile.get_friend_by_number(friend_number)
+ if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
+ invoke_in_main_thread(tray_notification, friend.name, message, tray, window)
+ if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
+ sound_notification(SOUND_NOTIFICATION['MESSAGE'])
+ invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png'))
+ return wrapped
+
+
+def friend_request(tox, public_key, message, message_size, user_data):
+ """
+ Called when user get new friend request
+ """
+ print('Friend request')
+ profile = Profile.get_instance()
+ key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE])
+ tox_id = bin_to_string(key, TOX_PUBLIC_KEY_SIZE)
+ if tox_id not in Settings.get_instance()['blocked']:
+ invoke_in_main_thread(profile.process_friend_request, tox_id, str(message, 'utf-8'))
+
+
+def friend_typing(tox, friend_number, typing, user_data):
+ invoke_in_main_thread(Profile.get_instance().friend_typing, friend_number, typing)
+
+
+def friend_read_receipt(tox, friend_number, message_id, user_data):
+ profile = Profile.get_instance()
+ profile.get_friend_by_number(friend_number).dec_receipt()
+ if friend_number == profile.get_active_number():
+ invoke_in_main_thread(profile.receipt)
+
+# -----------------------------------------------------------------------------------------------------------------
+# Callbacks - file transfers
+# -----------------------------------------------------------------------------------------------------------------
+
+
+def tox_file_recv(window, tray):
+ """
+ New incoming file
+ """
+ def wrapped(tox, friend_number, file_number, file_type, size, file_name, file_name_size, user_data):
+ profile = Profile.get_instance()
+ settings = Settings.get_instance()
+ if file_type == TOX_FILE_KIND['DATA']:
+ print('File')
+ try:
+ file_name = str(file_name[:file_name_size], 'utf-8')
+ except:
+ file_name = 'toxygen_file'
+ invoke_in_main_thread(profile.incoming_file_transfer,
+ friend_number,
+ file_number,
+ size,
+ file_name)
+ 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)
+ invoke_in_main_thread(tray_notification, file_from + ' ' + friend.name, file_name, tray, window)
+ if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
+ sound_notification(SOUND_NOTIFICATION['FILE_TRANSFER'])
+ invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png'))
+ else: # AVATAR
+ print('Avatar')
+ invoke_in_main_thread(profile.incoming_avatar,
+ friend_number,
+ file_number,
+ size)
+ return wrapped
+
+
+def file_recv_chunk(tox, friend_number, file_number, position, chunk, length, user_data):
+ """
+ Incoming chunk
+ """
+ _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
+ """
+ Profile.get_instance().outgoing_chunk(friend_number, file_number, position, size)
+
+
+def file_recv_control(tox, friend_number, file_number, file_control, user_data):
+ """
+ Friend cancelled, paused or resumed file transfer
+ """
+ if file_control == TOX_FILE_CONTROL['CANCEL']:
+ invoke_in_main_thread(Profile.get_instance().cancel_transfer, friend_number, file_number, True)
+ elif file_control == TOX_FILE_CONTROL['PAUSE']:
+ invoke_in_main_thread(Profile.get_instance().pause_transfer, friend_number, file_number, True)
+ elif file_control == TOX_FILE_CONTROL['RESUME']:
+ invoke_in_main_thread(Profile.get_instance().resume_transfer, friend_number, file_number, True)
+
+# -----------------------------------------------------------------------------------------------------------------
+# Callbacks - custom packets
+# -----------------------------------------------------------------------------------------------------------------
+
+
+def lossless_packet(tox, friend_number, data, length, user_data):
+ """
+ Incoming lossless packet
+ """
+ plugin = PluginLoader.get_instance()
+ invoke_in_main_thread(plugin.callback_lossless, friend_number, data, length)
+
+
+def lossy_packet(tox, friend_number, data, length, user_data):
+ """
+ Incoming lossy packet
+ """
+ plugin = PluginLoader.get_instance()
+ invoke_in_main_thread(plugin.callback_lossy, friend_number, data, length)
+
+
+# -----------------------------------------------------------------------------------------------------------------
+# Callbacks - audio
+# -----------------------------------------------------------------------------------------------------------------
+
+def call_state(toxav, friend_number, mask, user_data):
+ """
+ New call state
+ """
+ print(friend_number, mask)
+ if mask == TOXAV_FRIEND_CALL_STATE['FINISHED'] or mask == TOXAV_FRIEND_CALL_STATE['ERROR']:
+ invoke_in_main_thread(Profile.get_instance().stop_call, friend_number, True)
+ else:
+ Profile.get_instance().call.toxav_call_state_cb(friend_number, mask)
+
+
+def call(toxav, friend_number, audio, video, user_data):
+ """
+ Incoming call from friend
+ """
+ print(friend_number, audio, video)
+ invoke_in_main_thread(Profile.get_instance().incoming_call, audio, video, friend_number)
+
+
+def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, audio_channels_count, rate, user_data):
+ """
+ New audio chunk
+ """
+ Profile.get_instance().call.chunk(
+ bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]),
+ audio_channels_count,
+ rate)
+
+
+# -----------------------------------------------------------------------------------------------------------------
+# Callbacks - initialization
+# -----------------------------------------------------------------------------------------------------------------
+
+
+def init_callbacks(tox, window, tray):
+ """
+ Initialization of all callbacks.
+ :param tox: tox instance
+ :param window: main window
+ :param tray: tray (for notifications)
+ """
+ tox.callback_self_connection_status(self_connection_status(tox), 0)
+
+ tox.callback_friend_status(friend_status, 0)
+ tox.callback_friend_message(friend_message(window, tray), 0)
+ tox.callback_friend_connection_status(friend_connection_status, 0)
+ tox.callback_friend_name(friend_name, 0)
+ tox.callback_friend_status_message(friend_status_message, 0)
+ tox.callback_friend_request(friend_request, 0)
+ tox.callback_friend_typing(friend_typing, 0)
+ tox.callback_friend_read_receipt(friend_read_receipt, 0)
+
+ tox.callback_file_recv(tox_file_recv(window, tray), 0)
+ tox.callback_file_recv_chunk(file_recv_chunk, 0)
+ tox.callback_file_chunk_request(file_chunk_request, 0)
+ tox.callback_file_recv_control(file_recv_control, 0)
+
+ toxav = tox.AV
+ toxav.callback_call_state(call_state, 0)
+ toxav.callback_call(call, 0)
+ toxav.callback_audio_receive_frame(callback_audio, 0)
+
+ tox.callback_friend_lossless_packet(lossless_packet, 0)
+ tox.callback_friend_lossy_packet(lossy_packet, 0)
+
diff --git a/toxygen/calls.py b/toxygen/calls.py
new file mode 100644
index 0000000..16cef47
--- /dev/null
+++ b/toxygen/calls.py
@@ -0,0 +1,144 @@
+import pyaudio
+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
+
+CALL_TYPE = {
+ 'NONE': 0,
+ 'AUDIO': 1,
+ 'VIDEO': 2
+}
+
+
+class AV:
+
+ def __init__(self, toxav):
+ self._toxav = toxav
+ self._running = True
+
+ self._calls = {} # dict: key - friend number, value - call type
+
+ self._audio = None
+ self._audio_stream = None
+ self._audio_thread = None
+ self._audio_running = False
+ self._out_stream = None
+
+ self._audio_rate = 8000
+ self._audio_channels = 1
+ self._audio_duration = 60
+ self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration // 1000
+
+ def __contains__(self, friend_number):
+ return friend_number in self._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_TYPE['AUDIO']
+ self.start_audio_thread()
+
+ def finish_call(self, friend_number, by_friend=False):
+
+ if not by_friend:
+ self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL'])
+ if friend_number in self._calls:
+ del self._calls[friend_number]
+ if not len(self._calls):
+ self.stop_audio_thread()
+
+ def stop(self):
+ self._running = False
+ self.stop_audio_thread()
+
+ def start_audio_thread(self):
+ """
+ Start audio sending
+ """
+ if self._audio_thread is not None:
+ return
+
+ self._audio_running = True
+
+ self._audio = pyaudio.PyAudio()
+ self._audio_stream = self._audio.open(format=pyaudio.paInt16,
+ rate=self._audio_rate,
+ channels=self._audio_channels,
+ input=True,
+ input_device_index=settings.Settings.get_instance().audio['input'],
+ frames_per_buffer=self._audio_sample_count * 10)
+
+ self._audio_thread = threading.Thread(target=self.send_audio)
+ self._audio_thread.start()
+
+ def stop_audio_thread(self):
+
+ if self._audio_thread is None:
+ return
+
+ self._audio_running = False
+
+ self._audio_thread.join()
+
+ self._audio_thread = None
+ self._audio_stream = None
+ self._audio = None
+
+ if self._out_stream is not None:
+ self._out_stream.stop_stream()
+ self._out_stream.close()
+ self._out_stream = None
+
+ def chunk(self, samples, channels_count, rate):
+ """
+ Incoming chunk
+ """
+
+ if self._out_stream is None:
+ self._out_stream = self._audio.open(format=pyaudio.paInt16,
+ channels=channels_count,
+ rate=rate,
+ output_device_index=settings.Settings.get_instance().audio['output'],
+ output=True)
+ self._out_stream.write(samples)
+
+ def send_audio(self):
+ """
+ This method sends audio to friends
+ """
+
+ while self._audio_running:
+ try:
+ pcm = self._audio_stream.read(self._audio_sample_count)
+ if pcm:
+ for friend in self._calls:
+ if self._calls[friend] & 1:
+ try:
+ self._toxav.audio_send_frame(friend, pcm, self._audio_sample_count,
+ self._audio_channels, self._audio_rate)
+ except:
+ pass
+ except:
+ pass
+
+ 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):
+ """
+ New call state
+ """
+ if self._running:
+
+ if state & TOXAV_FRIEND_CALL_STATE['ACCEPTING_A']:
+ self._calls[friend_number] |= 1
+
diff --git a/toxygen/common/__init__.py b/toxygen/common/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/toxygen/common/event.py b/toxygen/common/event.py
deleted file mode 100644
index 687a34d..0000000
--- a/toxygen/common/event.py
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-class Event:
-
- def __init__(self):
- self._callbacks = set()
-
- def __iadd__(self, callback):
- self.add_callback(callback)
-
- return self
-
- def __isub__(self, callback):
- self.remove_callback(callback)
-
- return self
-
- def __call__(self, *args, **kwargs):
- for callback in self._callbacks:
- callback(*args, **kwargs)
-
- def add_callback(self, callback):
- self._callbacks.add(callback)
-
- def remove_callback(self, callback):
- self._callbacks.discard(callback)
diff --git a/toxygen/common/provider.py b/toxygen/common/provider.py
deleted file mode 100644
index d16edb4..0000000
--- a/toxygen/common/provider.py
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-class Provider:
-
- def __init__(self, get_item_action):
- self._get_item_action = get_item_action
- self._item = None
-
- def get_item(self):
- if self._item is None:
- self._item = self._get_item_action()
-
- return self._item
diff --git a/toxygen/common/tox_save.py b/toxygen/common/tox_save.py
deleted file mode 100644
index 09c159b..0000000
--- a/toxygen/common/tox_save.py
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-class ToxSave:
-
- def __init__(self, tox):
- self._tox = tox
-
- def set_tox(self, tox):
- self._tox = tox
-
-
-class ToxAvSave:
-
- def __init__(self, toxav):
- self._toxav = toxav
-
- def set_toxav(self, toxav):
- self._toxav = toxav
diff --git a/toxygen/contact.py b/toxygen/contact.py
new file mode 100644
index 0000000..1dbfcdf
--- /dev/null
+++ b/toxygen/contact.py
@@ -0,0 +1,113 @@
+import os
+from settings import *
+try:
+ from PySide import QtCore, QtGui
+except ImportError:
+ from PyQt4 import QtCore, QtGui
+from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
+
+
+class Contact:
+ """
+ Class encapsulating TOX contact
+ Properties: name (alias of contact or name), status_message, status (connection status)
+ widget - widget for update
+ """
+
+ def __init__(self, name, status_message, widget, tox_id):
+ """
+ :param name: name, example: 'Toxygen user'
+ :param status_message: status message, example: 'Toxing on Toxygen'
+ :param widget: ContactItem instance
+ :param tox_id: tox id of contact
+ """
+ self._name, self._status_message = name, status_message
+ self._status, self._widget = None, widget
+ self._widget.name.setText(name)
+ self._widget.status_message.setText(status_message)
+ self._tox_id = tox_id
+ self.load_avatar()
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # name - current name or alias of user
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def get_name(self):
+ return self._name
+
+ def set_name(self, value):
+ self._name = str(value, 'utf-8')
+ self._widget.name.setText(self._name)
+ self._widget.name.repaint()
+
+ name = property(get_name, set_name)
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Status message
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def get_status_message(self):
+ return self._status_message
+
+ def set_status_message(self, value):
+ self._status_message = str(value, 'utf-8')
+ self._widget.status_message.setText(self._status_message)
+ self._widget.status_message.repaint()
+
+ status_message = property(get_status_message, set_status_message)
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Status
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def get_status(self):
+ return self._status
+
+ def set_status(self, value):
+ self._status = value
+ self._widget.connection_status.update(value)
+
+ status = property(get_status, set_status)
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # TOX ID. WARNING: for friend it will return public key, for profile - full address
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def get_tox_id(self):
+ return self._tox_id
+
+ tox_id = property(get_tox_id)
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Avatars
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def load_avatar(self):
+ """
+ Tries to load avatar of contact or uses default avatar
+ """
+ avatar_path = '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
+ os.chdir(ProfileHelper.get_path() + 'avatars/')
+ if not os.path.isfile(avatar_path): # load default image
+ avatar_path = 'avatar.png'
+ os.chdir(curr_directory() + '/images/')
+ 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()
diff --git a/toxygen/contacts/__init__.py b/toxygen/contacts/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/toxygen/contacts/basecontact.py b/toxygen/contacts/basecontact.py
deleted file mode 100644
index 2058890..0000000
--- a/toxygen/contacts/basecontact.py
+++ /dev/null
@@ -1,180 +0,0 @@
-from user_data.settings import *
-from PyQt5 import QtCore, QtGui
-from wrapper.toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
-import utils.util as util
-import common.event as event
-import contacts.common as common
-
-
-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, profile_manager, 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._profile_manager = profile_manager
- self._name, self._status_message = name, status_message
- self._status, self._widget = None, widget
- self._tox_id = tox_id
- self._name_changed_event = event.Event()
- self._status_message_changed_event = event.Event()
- self._status_changed_event = event.Event()
- self._avatar_changed_event = event.Event()
- self.init_widget()
-
- # -----------------------------------------------------------------------------------------------------------------
- # Name - current name or alias of user
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_name(self):
- return self._name
-
- def set_name(self, value):
- if self._name == value:
- return
- self._name = value
- self._widget.name.setText(self._name)
- self._widget.name.repaint()
- self._name_changed_event(self._name)
-
- name = property(get_name, set_name)
-
- def get_name_changed_event(self):
- return self._name_changed_event
-
- name_changed_event = property(get_name_changed_event)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Status message
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_status_message(self):
- return self._status_message
-
- def set_status_message(self, value):
- if self._status_message == value:
- return
- self._status_message = value
- self._widget.status_message.setText(self._status_message)
- self._widget.status_message.repaint()
- self._status_message_changed_event(self._status_message)
-
- status_message = property(get_status_message, set_status_message)
-
- def get_status_message_changed_event(self):
- return self._status_message_changed_event
-
- status_message_changed_event = property(get_status_message_changed_event)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Status
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_status(self):
- return self._status
-
- def set_status(self, value):
- if self._status == value:
- return
- self._status = value
- self._widget.connection_status.update(value)
- self._status_changed_event(self._status)
-
- status = property(get_status, set_status)
-
- def get_status_changed_event(self):
- return self._status_changed_event
-
- status_changed_event = property(get_status_changed_event)
-
- # -----------------------------------------------------------------------------------------------------------------
- # TOX ID. WARNING: for friend it will return public key, for profile - full address
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_tox_id(self):
- return self._tox_id
-
- tox_id = property(get_tox_id)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Avatars
- # -----------------------------------------------------------------------------------------------------------------
-
- def load_avatar(self):
- """
- Tries to load avatar of contact or uses default avatar
- """
- avatar_path = self.get_avatar_path()
- 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()
- self._avatar_changed_event(avatar_path)
-
- def reset_avatar(self, generate_new):
- avatar_path = self.get_avatar_path()
- if os.path.isfile(avatar_path) and not avatar_path == self._get_default_avatar_path():
- os.remove(avatar_path)
- if generate_new:
- self.set_avatar(common.generate_avatar(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]))
- else:
- self.load_avatar()
-
- def set_avatar(self, avatar):
- avatar_path = self.get_contact_avatar_path()
- with open(avatar_path, 'wb') as f:
- f.write(avatar)
- self.load_avatar()
-
- def get_pixmap(self):
- return self._widget.avatar_label.pixmap()
-
- def get_avatar_path(self):
- avatar_path = self.get_contact_avatar_path()
- if not os.path.isfile(avatar_path) or not os.path.getsize(avatar_path): # load default image
- avatar_path = self._get_default_avatar_path()
-
- return avatar_path
-
- def get_contact_avatar_path(self):
- directory = util.join_path(self._profile_manager.get_dir(), 'avatars')
-
- return util.join_path(directory, '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]))
-
- def has_avatar(self):
- path = self.get_contact_avatar_path()
-
- return util.file_exists(path)
-
- def get_avatar_changed_event(self):
- return self._avatar_changed_event
-
- avatar_changed_event = property(get_avatar_changed_event)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Widgets
- # -----------------------------------------------------------------------------------------------------------------
-
- def init_widget(self):
- self._widget.name.setText(self._name)
- self._widget.status_message.setText(self._status_message)
- self._widget.connection_status.update(self._status)
- self.load_avatar()
-
- # -----------------------------------------------------------------------------------------------------------------
- # Private methods
- # -----------------------------------------------------------------------------------------------------------------
-
- @staticmethod
- def _get_default_avatar_path():
- return util.join_path(util.get_images_directory(), 'avatar.png')
diff --git a/toxygen/contacts/common.py b/toxygen/contacts/common.py
deleted file mode 100644
index 27750a2..0000000
--- a/toxygen/contacts/common.py
+++ /dev/null
@@ -1,50 +0,0 @@
-from pydenticon import Generator
-import hashlib
-
-
-# -----------------------------------------------------------------------------------------------------------------
-# Typing notifications
-# -----------------------------------------------------------------------------------------------------------------
-
-class BaseTypingNotificationHandler:
-
- DEFAULT_HANDLER = None
-
- def __init__(self):
- pass
-
- def send(self, tox, is_typing):
- pass
-
-
-class FriendTypingNotificationHandler(BaseTypingNotificationHandler):
-
- def __init__(self, friend_number):
- super().__init__()
- self._friend_number = friend_number
-
- def send(self, tox, is_typing):
- tox.self_set_typing(self._friend_number, is_typing)
-
-
-BaseTypingNotificationHandler.DEFAULT_HANDLER = BaseTypingNotificationHandler()
-
-
-# -----------------------------------------------------------------------------------------------------------------
-# Identicons support
-# -----------------------------------------------------------------------------------------------------------------
-
-
-def generate_avatar(public_key):
- foreground = ['rgb(45,79,255)', 'rgb(185, 66, 244)', 'rgb(185, 66, 244)',
- 'rgb(254,180,44)', 'rgb(252, 2, 2)', 'rgb(109, 198, 0)',
- 'rgb(226,121,234)', 'rgb(130, 135, 124)',
- 'rgb(30,179,253)', 'rgb(160, 157, 0)',
- 'rgb(232,77,65)', 'rgb(102, 4, 4)',
- 'rgb(49,203,115)',
- 'rgb(141,69,170)']
- generator = Generator(5, 5, foreground=foreground, background='rgba(42,42,42,0)')
- digest = hashlib.sha256(public_key.encode('utf-8')).hexdigest()
- identicon = generator.generate(digest, 220, 220, padding=(10, 10, 10, 10))
-
- return identicon
diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py
deleted file mode 100644
index e88acf2..0000000
--- a/toxygen/contacts/contact.py
+++ /dev/null
@@ -1,333 +0,0 @@
-from history.database import *
-from contacts import basecontact, common
-from messenger.messages import *
-from contacts.contact_menu import *
-from file_transfers import file_transfers as ft
-import re
-
-
-class Contact(basecontact.BaseContact):
- """
- Class encapsulating TOX contact
- Properties: number, message getter, history etc. Base class for friend and gc classes
- """
-
- def __init__(self, profile_manager, message_getter, number, name, status_message, widget, tox_id):
- """
- :param message_getter: gets messages from db
- :param number: number of friend.
- """
- super().__init__(profile_manager, 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
-
- # -----------------------------------------------------------------------------------------------------------------
- # History support
- # -----------------------------------------------------------------------------------------------------------------
-
- def load_corr(self, first_time=True):
- """
- :param first_time: friend became active, load first part of messages
- """
- try:
- 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 p: self._get_text_message(p), data))
- self._corr = data + self._corr
- except:
- pass
- finally:
- self._history_loaded = True
-
- def load_all_corr(self):
- """
- Get all chat history from db for current friend
- """
- if self._message_getter is None:
- return
- data = list(self._message_getter.get_all())
- if data is not None and len(data):
- data.reverse()
- data = list(map(lambda p: self._get_text_message(p), 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 m: m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']), self._corr))
- return 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.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']):
- self._unsaved_messages += 1
-
- def get_last_message_text(self):
- messages = list(filter(lambda m: m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION'])
- and m.author.type != MESSAGE_AUTHOR['FRIEND'], self._corr))
- if messages:
- return messages[-1].text
- else:
- return ''
-
- def remove_messages_widgets(self):
- for message in self._corr:
- message.remove_widget()
-
- def get_message(self, _filter):
- return list(filter(lambda m: _filter(m), self._corr))[0]
-
- @staticmethod
- def _get_text_message(params):
- (message, author_type, author_name, unix_time, message_type, unique_id) = params
- author = MessageAuthor(author_name, author_type)
-
- return TextMessage(message, author, unix_time, message_type, unique_id)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Unsent messages
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_unsent_messages(self):
- """
- :return list of unsent messages
- """
- messages = filter(lambda m: m.author is not None and m.author.type == MESSAGE_AUTHOR['NOT_SENT'], self._corr)
- return list(messages)
-
- def get_unsent_messages_for_saving(self):
- """
- :return list of unsent messages for saving
- """
- messages = filter(lambda m: m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION'])
- and m.author.type == MESSAGE_AUTHOR['NOT_SENT'], self._corr)
- return list(messages)
-
- def mark_as_sent(self, tox_message_id):
- try:
- message = list(filter(lambda m: m.author is not None and m.author.type == MESSAGE_AUTHOR['NOT_SENT']
- and m.tox_message_id == tox_message_id, 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, message_id):
- elem = list(filter(lambda m: m.message_id == message_id, self._corr))[0]
- tmp = list(filter(lambda m: m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']), 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(m):
- if m.type == MESSAGE_TYPE['FILE_TRANSFER'] and (m.state not in ACTIVE_FILE_TRANSFERS):
- return True
- return m.author is not None and m.author.type == MESSAGE_AUTHOR['NOT_SENT']
-
- old = filter(save_message, self._corr[:-SAVE_MESSAGES])
- self._corr = list(old) + self._corr[-SAVE_MESSAGES:]
- text_messages = filter(lambda m: m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']), 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 m: m.type == MESSAGE_TYPE['FILE_TRANSFER'] and
- m.state in ft.ACTIVE_FILE_TRANSFERS, self._corr))
- self._unsaved_messages = 0
- else:
- self._corr = list(filter(lambda m: (m.type == MESSAGE_TYPE['FILE_TRANSFER']
- and m.state in ft.ACTIVE_FILE_TRANSFERS)
- or (m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION'])
- and m.author.type == MESSAGE_AUTHOR['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].type not in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']):
- continue
- message = self._corr[i].text
- 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].type not in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']):
- continue
- message = self._corr[i].text
- 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):
- """
- Set new name or ignore if alias exists
- :param value: new name
- """
- if not self._alias:
- super().set_name(value)
-
- def set_alias(self, alias):
- self._alias = bool(alias)
-
- def has_alias(self):
- return self._alias
-
- # -----------------------------------------------------------------------------------------------------------------
- # Visibility in friends' list
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_visibility(self):
- return self._visible
-
- def set_visibility(self, value):
- self._visible = value
-
- visibility = property(get_visibility, set_visibility)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Unread messages 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 or group'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)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Typing notifications
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_typing_notification_handler(self):
- return common.BaseTypingNotificationHandler.DEFAULT_HANDLER
-
- typing_notification_handler = property(get_typing_notification_handler)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Context menu support
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_context_menu_generator(self):
- return BaseContactMenuGenerator(self)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Filtration support
- # -----------------------------------------------------------------------------------------------------------------
-
- def set_widget(self, widget):
- self._widget = widget
- self.init_widget()
diff --git a/toxygen/contacts/contact_menu.py b/toxygen/contacts/contact_menu.py
deleted file mode 100644
index 8178d31..0000000
--- a/toxygen/contacts/contact_menu.py
+++ /dev/null
@@ -1,229 +0,0 @@
-from PyQt5 import QtWidgets
-import utils.ui as util_ui
-
-
-# -----------------------------------------------------------------------------------------------------------------
-# Builder
-# -----------------------------------------------------------------------------------------------------------------
-
-def _create_menu(menu_name, parent):
- menu_name = menu_name or ''
-
- return QtWidgets.QMenu(menu_name) if parent is None else parent.addMenu(menu_name)
-
-
-class ContactMenuBuilder:
-
- def __init__(self):
- self._actions = {}
- self._submenus = {}
- self._name = None
- self._index = 0
-
- def with_name(self, name):
- self._name = name
-
- return self
-
- def with_action(self, text, handler):
- self._add_action(text, handler)
-
- return self
-
- def with_optional_action(self, text, handler, show_action):
- if show_action:
- self._add_action(text, handler)
-
- return self
-
- def with_actions(self, actions):
- for action in actions:
- (text, handler) = action
- self._add_action(text, handler)
-
- return self
-
- def with_submenu(self, submenu_builder):
- self._add_submenu(submenu_builder)
-
- return self
-
- def with_optional_submenu(self, submenu_builder):
- if submenu_builder is not None:
- self._add_submenu(submenu_builder)
-
- return self
-
- def build(self, parent=None):
- menu = _create_menu(self._name, parent)
-
- for i in range(self._index):
- if i in self._actions:
- text, handler = self._actions[i]
- action = menu.addAction(text)
- action.triggered.connect(handler)
- else:
- submenu_builder = self._submenus[i]
- submenu = submenu_builder.build(menu)
- menu.addMenu(submenu)
-
- return menu
-
- def _add_submenu(self, submenu):
- self._submenus[self._index] = submenu
- self._index += 1
-
- def _add_action(self, text, handler):
- self._actions[self._index] = (text, handler)
- self._index += 1
-
-# -----------------------------------------------------------------------------------------------------------------
-# Generators
-# -----------------------------------------------------------------------------------------------------------------
-
-
-class BaseContactMenuGenerator:
-
- def __init__(self, contact):
- self._contact = contact
-
- def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service, history_loader):
- return ContactMenuBuilder().build()
-
- # -----------------------------------------------------------------------------------------------------------------
- # Private methods
- # -----------------------------------------------------------------------------------------------------------------
-
- def _generate_copy_menu_builder(self, main_screen):
- copy_menu_builder = ContactMenuBuilder()
- (copy_menu_builder
- .with_name(util_ui.tr('Copy'))
- .with_action(util_ui.tr('Name'), lambda: main_screen.copy_text(self._contact.name))
- .with_action(util_ui.tr('Status message'), lambda: main_screen.copy_text(self._contact.status_message))
- .with_action(util_ui.tr('Public key'), lambda: main_screen.copy_text(self._contact.tox_id))
- )
-
- return copy_menu_builder
-
- def _generate_history_menu_builder(self, history_loader, main_screen):
- history_menu_builder = ContactMenuBuilder()
- (history_menu_builder
- .with_name(util_ui.tr('Chat history'))
- .with_action(util_ui.tr('Clear history'), lambda: history_loader.clear_history(self._contact)
- or main_screen.messages.clear())
- .with_action(util_ui.tr('Export as text'), lambda: history_loader.export_history(self._contact))
- .with_action(util_ui.tr('Export as HTML'), lambda: history_loader.export_history(self._contact, False))
- )
-
- return history_menu_builder
-
-
-class FriendMenuGenerator(BaseContactMenuGenerator):
-
- def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service, history_loader):
- history_menu_builder = self._generate_history_menu_builder(history_loader, main_screen)
- copy_menu_builder = self._generate_copy_menu_builder(main_screen)
- plugins_menu_builder = self._generate_plugins_menu_builder(plugin_loader, number)
- groups_menu_builder = self._generate_groups_menu(contacts_manager, groups_service)
-
- allowed = self._contact.tox_id in settings['auto_accept_from_friends']
- auto = util_ui.tr('Disallow auto accept') if allowed else util_ui.tr('Allow auto accept')
-
- builder = ContactMenuBuilder()
- menu = (builder
- .with_action(util_ui.tr('Set alias'), lambda: main_screen.set_alias(number))
- .with_submenu(history_menu_builder)
- .with_submenu(copy_menu_builder)
- .with_action(auto, lambda: main_screen.auto_accept(number, not allowed))
- .with_action(util_ui.tr('Remove friend'), lambda: main_screen.remove_friend(number))
- .with_action(util_ui.tr('Block friend'), lambda: main_screen.block_friend(number))
- .with_action(util_ui.tr('Notes'), lambda: main_screen.show_note(self._contact))
- .with_optional_submenu(plugins_menu_builder)
- .with_optional_submenu(groups_menu_builder)
- ).build()
-
- return menu
-
- # -----------------------------------------------------------------------------------------------------------------
- # Private methods
- # -----------------------------------------------------------------------------------------------------------------
-
- @staticmethod
- def _generate_plugins_menu_builder(plugin_loader, number):
- if plugin_loader is None:
- return None
- plugins_actions = plugin_loader.get_menu(number)
- if not len(plugins_actions):
- return None
- plugins_menu_builder = ContactMenuBuilder()
- (plugins_menu_builder
- .with_name(util_ui.tr('Plugins'))
- .with_actions(plugins_actions)
- )
-
- return plugins_menu_builder
-
- def _generate_groups_menu(self, contacts_manager, groups_service):
- chats = contacts_manager.get_group_chats()
- if not len(chats) or self._contact.status is None:
- return None
- groups_menu_builder = ContactMenuBuilder()
- (groups_menu_builder
- .with_name(util_ui.tr('Invite to group'))
- .with_actions([(g.name, lambda: groups_service.invite_friend(self._contact.number, g.number)) for g in chats])
- )
-
- return groups_menu_builder
-
-
-class GroupMenuGenerator(BaseContactMenuGenerator):
-
- def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service, history_loader):
- copy_menu_builder = self._generate_copy_menu_builder(main_screen)
- history_menu_builder = self._generate_history_menu_builder(history_loader, main_screen)
-
- builder = ContactMenuBuilder()
- menu = (builder
- .with_action(util_ui.tr('Set alias'), lambda: main_screen.set_alias(number))
- .with_submenu(copy_menu_builder)
- .with_submenu(history_menu_builder)
- .with_optional_action(util_ui.tr('Manage group'),
- lambda: groups_service.show_group_management_screen(self._contact),
- self._contact.is_self_founder())
- .with_optional_action(util_ui.tr('Group settings'),
- lambda: groups_service.show_group_settings_screen(self._contact),
- not self._contact.is_self_founder())
- .with_optional_action(util_ui.tr('Set topic'),
- lambda: groups_service.set_group_topic(self._contact),
- self._contact.is_self_moderator_or_founder())
- .with_action(util_ui.tr('Bans list'),
- lambda: groups_service.show_bans_list(self._contact))
- .with_action(util_ui.tr('Reconnect to group'),
- lambda: groups_service.reconnect_to_group(self._contact.number))
- .with_optional_action(util_ui.tr('Disconnect from group'),
- lambda: groups_service.disconnect_from_group(self._contact.number),
- self._contact.status is not None)
- .with_action(util_ui.tr('Leave group'), lambda: groups_service.leave_group(self._contact.number))
- .with_action(util_ui.tr('Notes'), lambda: main_screen.show_note(self._contact))
- ).build()
-
- return menu
-
-
-class GroupPeerMenuGenerator(BaseContactMenuGenerator):
-
- def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service, history_loader):
- copy_menu_builder = self._generate_copy_menu_builder(main_screen)
- history_menu_builder = self._generate_history_menu_builder(history_loader, main_screen)
-
- builder = ContactMenuBuilder()
- menu = (builder
- .with_action(util_ui.tr('Set alias'), lambda: main_screen.set_alias(number))
- .with_submenu(copy_menu_builder)
- .with_submenu(history_menu_builder)
- .with_action(util_ui.tr('Quit chat'),
- lambda: contacts_manager.remove_group_peer(self._contact))
- .with_action(util_ui.tr('Notes'), lambda: main_screen.show_note(self._contact))
- ).build()
-
- return menu
diff --git a/toxygen/contacts/contact_provider.py b/toxygen/contacts/contact_provider.py
deleted file mode 100644
index 76e8e79..0000000
--- a/toxygen/contacts/contact_provider.py
+++ /dev/null
@@ -1,107 +0,0 @@
-import common.tox_save as tox_save
-
-
-class ContactProvider(tox_save.ToxSave):
-
- def __init__(self, tox, friend_factory, group_factory, group_peer_factory):
- super().__init__(tox)
- self._friend_factory = friend_factory
- self._group_factory = group_factory
- self._group_peer_factory = group_peer_factory
- self._cache = {} # key - contact's public key, value - contact instance
-
- # -----------------------------------------------------------------------------------------------------------------
- # Friends
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_friend_by_number(self, friend_number):
- public_key = self._tox.friend_get_public_key(friend_number)
-
- return self.get_friend_by_public_key(public_key)
-
- def get_friend_by_public_key(self, public_key):
- friend = self._get_contact_from_cache(public_key)
- if friend is not None:
- return friend
- friend = self._friend_factory.create_friend_by_public_key(public_key)
- self._add_to_cache(public_key, friend)
-
- return friend
-
- def get_all_friends(self):
- friend_numbers = self._tox.self_get_friend_list()
- friends = map(lambda n: self.get_friend_by_number(n), friend_numbers)
-
- return list(friends)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Groups
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_all_groups(self):
- group_numbers = range(self._tox.group_get_number_groups())
- groups = map(lambda n: self.get_group_by_number(n), group_numbers)
-
- return list(groups)
-
- def get_group_by_number(self, group_number):
- public_key = self._tox.group_get_chat_id(group_number)
-
- return self.get_group_by_public_key(public_key)
-
- def get_group_by_public_key(self, public_key):
- group = self._get_contact_from_cache(public_key)
- if group is not None:
- return group
- group = self._group_factory.create_group_by_public_key(public_key)
- self._add_to_cache(public_key, group)
-
- return group
-
- # -----------------------------------------------------------------------------------------------------------------
- # Group peers
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_all_group_peers(self):
- return list()
-
- def get_group_peer_by_id(self, group, peer_id):
- peer = group.get_peer_by_id(peer_id)
-
- return self._get_group_peer(group, peer)
-
- def get_group_peer_by_public_key(self, group, public_key):
- peer = group.get_peer_by_public_key(public_key)
-
- return self._get_group_peer(group, peer)
-
- # -----------------------------------------------------------------------------------------------------------------
- # All contacts
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_all(self):
- return self.get_all_friends() + self.get_all_groups() + self.get_all_group_peers()
-
- # -----------------------------------------------------------------------------------------------------------------
- # Caching
- # -----------------------------------------------------------------------------------------------------------------
-
- def clear_cache(self):
- self._cache.clear()
-
- def remove_contact_from_cache(self, contact_public_key):
- if contact_public_key in self._cache:
- del self._cache[contact_public_key]
-
- # -----------------------------------------------------------------------------------------------------------------
- # Private methods
- # -----------------------------------------------------------------------------------------------------------------
-
- def _get_contact_from_cache(self, public_key):
- return self._cache[public_key] if public_key in self._cache else None
-
- def _add_to_cache(self, public_key, contact):
- self._cache[public_key] = contact
-
- def _get_group_peer(self, group, peer):
- return self._group_peer_factory.create_group_peer(group, peer)
diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py
deleted file mode 100644
index 87a61ff..0000000
--- a/toxygen/contacts/contacts_manager.py
+++ /dev/null
@@ -1,575 +0,0 @@
-from contacts.friend import Friend
-from contacts.group_chat import GroupChat
-from messenger.messages import *
-from common.tox_save import ToxSave
-from contacts.group_peer_contact import GroupPeerContact
-
-
-class ContactsManager(ToxSave):
- """
- Represents contacts list.
- """
-
- def __init__(self, tox, settings, screen, profile_manager, contact_provider, history, tox_dns,
- messages_items_factory):
- super().__init__(tox)
- self._settings = settings
- self._screen = screen
- self._profile_manager = profile_manager
- self._contact_provider = contact_provider
- self._tox_dns = tox_dns
- self._messages_items_factory = messages_items_factory
- self._messages = screen.messages
- self._contacts, self._active_contact = [], -1
- self._active_contact_changed = Event()
- self._sorting = settings['sorting']
- self._filter_string = ''
- screen.contacts_filter.setCurrentIndex(int(self._sorting))
- self._history = history
- self._load_contacts()
-
- def get_contact(self, num):
- if num < 0 or num >= len(self._contacts):
- return None
- return self._contacts[num]
-
- def get_curr_contact(self):
- return self._contacts[self._active_contact] if self._active_contact + 1 else None
-
- def save_profile(self):
- data = self._tox.get_savedata()
- self._profile_manager.save_profile(data)
-
- def is_friend_active(self, friend_number):
- if not self.is_active_a_friend():
- return False
-
- return self.get_curr_contact().number == friend_number
-
- def is_group_active(self, group_number):
- if self.is_active_a_friend():
- return False
-
- return self.get_curr_contact().number == group_number
-
- def is_contact_active(self, contact):
- return self._contacts[self._active_contact].tox_id == contact.tox_id
-
- # -----------------------------------------------------------------------------------------------------------------
- # Reconnection support
- # -----------------------------------------------------------------------------------------------------------------
-
- def reset_contacts_statuses(self):
- for contact in self._contacts:
- contact.status = None
-
- # -----------------------------------------------------------------------------------------------------------------
- # Work with active friend
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_active(self):
- return self._active_contact
-
- def set_active(self, value):
- """
- Change current active friend or update info
- :param value: number of new active friend in friend's list
- """
- if value is None and self._active_contact == -1: # nothing to update
- return
- if value == -1: # all friends were deleted
- self._screen.account_name.setText('')
- self._screen.account_status.setText('')
- self._screen.account_status.setToolTip('')
- self._active_contact = -1
- self._screen.account_avatar.setHidden(True)
- self._messages.clear()
- self._screen.messageEdit.clear()
- return
- try:
- self._screen.typing.setVisible(False)
- current_contact = self.get_curr_contact()
- if current_contact is not None:
- # TODO: send when needed
- current_contact.typing_notification_handler.send(self._tox, False)
- current_contact.remove_messages_widgets() # TODO: if required
- self._unsubscribe_from_events(current_contact)
-
- if self._active_contact + 1 and self._active_contact != value:
- try:
- current_contact.curr_text = self._screen.messageEdit.toPlainText()
- except:
- pass
- contact = self._contacts[value]
- self._subscribe_to_events(contact)
- contact.remove_invalid_unsent_files()
- if self._active_contact != value:
- self._screen.messageEdit.setPlainText(contact.curr_text)
- self._active_contact = value
- contact.reset_messages()
- if not self._settings['save_history']:
- contact.delete_old_messages()
- self._messages.clear()
- contact.load_corr()
- corr = contact.get_corr()[-PAGE_SIZE:]
- for message in corr:
- if message.type == MESSAGE_TYPE['FILE_TRANSFER']:
- self._messages_items_factory.create_file_transfer_item(message)
- elif message.type == MESSAGE_TYPE['INLINE']:
- self._messages_items_factory.create_inline_item(message)
- else:
- self._messages_items_factory.create_message_item(message)
- self._messages.scrollToBottom()
- # if value in self._call:
- # self._screen.active_call()
- # elif value in self._incoming_calls:
- # self._screen.incoming_call()
- # else:
- # self._screen.call_finished()
- self._set_current_contact_data(contact)
- self._active_contact_changed(contact)
- except Exception as ex: # no friend found. ignore
- util.log('Friend value: ' + str(value))
- util.log('Error in set active: ' + str(ex))
- raise
-
- active_contact = property(get_active, set_active)
-
- def get_active_contact_changed(self):
- return self._active_contact_changed
-
- active_contact_changed = property(get_active_contact_changed)
-
- def update(self):
- if self._active_contact + 1:
- self.set_active(self._active_contact)
-
- def is_active_a_friend(self):
- return type(self.get_curr_contact()) is Friend
-
- def is_active_a_group(self):
- return type(self.get_curr_contact()) is GroupChat
-
- def is_active_a_group_chat_peer(self):
- return type(self.get_curr_contact()) is GroupPeerContact
-
- # -----------------------------------------------------------------------------------------------------------------
- # Filtration
- # -----------------------------------------------------------------------------------------------------------------
-
- def filtration_and_sorting(self, sorting=0, filter_str=''):
- """
- Filtration of friends list
- :param sorting: 0 - no sorting, 1 - online only, 2 - online first, 3 - by name,
- 4 - online and by name, 5 - online first and by name
- :param filter_str: show contacts which name contains this substring
- """
- filter_str = filter_str.lower()
- current_contact = self.get_curr_contact()
-
- if sorting > 5 or sorting < 0:
- sorting = 0
-
- if sorting in (1, 2, 4, 5): # online first
- self._contacts = sorted(self._contacts, key=lambda x: int(x.status is not None), reverse=True)
- sort_by_name = sorting in (4, 5)
- # save results of previous sorting
- online_friends = filter(lambda x: x.status is not None, self._contacts)
- online_friends_count = len(list(online_friends))
- part1 = self._contacts[:online_friends_count]
- part2 = self._contacts[online_friends_count:]
- key_lambda = lambda x: x.name.lower() if sort_by_name else x.number
- part1 = sorted(part1, key=key_lambda)
- part2 = sorted(part2, key=key_lambda)
- self._contacts = part1 + part2
- elif sorting == 0:
- contacts = sorted(self._contacts, key=lambda c: c.number)
- friends = filter(lambda c: type(c) is Friend, contacts)
- groups = filter(lambda c: type(c) is GroupChat, contacts)
- group_peers = filter(lambda c: type(c) is GroupPeerContact, contacts)
- self._contacts = list(friends) + list(groups) + list(group_peers)
- else:
- self._contacts = sorted(self._contacts, key=lambda x: x.name.lower())
-
- # change item widgets
- for index, contact in enumerate(self._contacts):
- list_item = self._screen.friends_list.item(index)
- item_widget = self._screen.friends_list.itemWidget(list_item)
- contact.set_widget(item_widget)
-
- for index, friend in enumerate(self._contacts):
- filtered_by_name = filter_str in friend.name.lower()
- friend.visibility = (friend.status is not None or sorting not in (1, 4)) and filtered_by_name
- # show friend even if it's hidden when there any unread messages/actions
- friend.visibility = friend.visibility or friend.messages or friend.actions
- item = self._screen.friends_list.item(index)
- item_widget = self._screen.friends_list.itemWidget(item)
- item.setSizeHint(QtCore.QSize(250, item_widget.height() if friend.visibility else 0))
-
- # save soring results
- self._sorting, self._filter_string = sorting, filter_str
- self._settings['sorting'] = self._sorting
- self._settings.save()
-
- # update active contact
- if current_contact is not None:
- index = self._contacts.index(current_contact)
- self.set_active(index)
-
- def update_filtration(self):
- """
- Update list of contacts when 1 of friends change connection status
- """
- self.filtration_and_sorting(self._sorting, self._filter_string)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Contact getters
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_friend_by_number(self, number):
- return list(filter(lambda c: c.number == number and type(c) is Friend, self._contacts))[0]
-
- def get_group_by_number(self, number):
- return list(filter(lambda c: c.number == number and type(c) is GroupChat, self._contacts))[0]
-
- def get_or_create_group_peer_contact(self, group_number, peer_id):
- group = self.get_group_by_number(group_number)
- peer = group.get_peer_by_id(peer_id)
- if not self.check_if_contact_exists(peer.public_key):
- self.add_group_peer(group, peer)
-
- return self.get_contact_by_tox_id(peer.public_key)
-
- def check_if_contact_exists(self, tox_id):
- return any(filter(lambda c: c.tox_id == tox_id, self._contacts))
-
- def get_contact_by_tox_id(self, tox_id):
- return list(filter(lambda c: c.tox_id == tox_id, self._contacts))[0]
-
- def get_active_number(self):
- return self.get_curr_contact().number if self._active_contact + 1 else -1
-
- def get_active_name(self):
- return self.get_curr_contact().name if self._active_contact + 1 else ''
-
- def is_active_online(self):
- return self._active_contact + 1 and self.get_curr_contact().status is not None
-
- # -----------------------------------------------------------------------------------------------------------------
- # Work with friends (remove, block, set alias, get public key)
- # -----------------------------------------------------------------------------------------------------------------
-
- def set_alias(self, num):
- """
- Set new alias for friend
- """
- friend = self._contacts[num]
- name = friend.name
- text = util_ui.tr("Enter new alias for friend {} or leave empty to use friend's name:").format(name)
- title = util_ui.tr('Set alias')
- text, ok = util_ui.text_dialog(text, title, name)
- if not ok:
- return
- aliases = self._settings['friends_aliases']
- if text:
- friend.name = text
- try:
- index = list(map(lambda x: x[0], aliases)).index(friend.tox_id)
- aliases[index] = (friend.tox_id, text)
- except:
- aliases.append((friend.tox_id, text))
- friend.set_alias(text)
- else: # use default name
- friend.name = self._tox.friend_get_name(friend.number)
- friend.set_alias('')
- try:
- index = list(map(lambda x: x[0], aliases)).index(friend.tox_id)
- del aliases[index]
- except:
- pass
- self._settings.save()
-
- def friend_public_key(self, num):
- return self._contacts[num].tox_id
-
- def delete_friend(self, num):
- """
- Removes friend from contact list
- :param num: number of friend in list
- """
- friend = self._contacts[num]
- self._cleanup_contact_data(friend)
- self._tox.friend_delete(friend.number)
- self._delete_contact(num)
-
- def add_friend(self, tox_id):
- """
- Adds friend to list
- """
- self._tox.friend_add_norequest(tox_id)
- self._add_friend(tox_id)
- self.update_filtration()
-
- def block_user(self, tox_id):
- """
- Block user with specified tox id (or public key) - delete from friends list and ignore friend requests
- """
- tox_id = tox_id[:TOX_PUBLIC_KEY_SIZE * 2]
- if tox_id == self._tox.self_get_address[:TOX_PUBLIC_KEY_SIZE * 2]:
- return
- if tox_id not in self._settings['blocked']:
- self._settings['blocked'].append(tox_id)
- self._settings.save()
- try:
- num = self._tox.friend_by_public_key(tox_id)
- self.delete_friend(num)
- self.save_profile()
- except: # not in friend list
- pass
-
- def unblock_user(self, tox_id, add_to_friend_list):
- """
- Unblock user
- :param tox_id: tox id of contact
- :param add_to_friend_list: add this contact to friend list or not
- """
- self._settings['blocked'].remove(tox_id)
- self._settings.save()
- if add_to_friend_list:
- self.add_friend(tox_id)
- self.save_profile()
-
- # -----------------------------------------------------------------------------------------------------------------
- # Groups support
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_group_chats(self):
- return list(filter(lambda c: type(c) is GroupChat, self._contacts))
-
- def add_group(self, group_number):
- group = self._contact_provider.get_group_by_number(group_number)
- index = len(self._contacts)
- self._contacts.append(group)
- group.reset_avatar(self._settings['identicons'])
- self._save_profile()
- self.set_active(index)
- self.update_filtration()
-
- def delete_group(self, group_number):
- group = self.get_group_by_number(group_number)
- self._cleanup_contact_data(group)
- num = self._contacts.index(group)
- self._delete_contact(num)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Groups private messaging
- # -----------------------------------------------------------------------------------------------------------------
-
- def add_group_peer(self, group, peer):
- contact = self._contact_provider.get_group_peer_by_id(group, peer.id)
- if self.check_if_contact_exists(contact.tox_id):
- return
- self._contacts.append(contact)
- contact.reset_avatar(self._settings['identicons'])
- self._save_profile()
-
- def remove_group_peer_by_id(self, group, peer_id):
- peer = group.get_peer_by_id(peer_id)
- if not self.check_if_contact_exists(peer.public_key):
- return
- contact = self.get_contact_by_tox_id(peer.public_key)
- self.remove_group_peer(contact)
-
- def remove_group_peer(self, group_peer_contact):
- contact = self.get_contact_by_tox_id(group_peer_contact.tox_id)
- self._cleanup_contact_data(contact)
- num = self._contacts.index(contact)
- self._delete_contact(num)
-
- def get_gc_peer_name(self, name):
- group = self.get_curr_contact()
-
- names = sorted(group.get_peers_names())
- if name in names: # return next nick
- index = names.index(name)
- index = (index + 1) % len(names)
-
- return names[index]
-
- suggested_names = list(filter(lambda x: x.startswith(name), names))
- if not len(suggested_names):
- return '\t'
-
- return suggested_names[0]
-
- # -----------------------------------------------------------------------------------------------------------------
- # Friend requests
- # -----------------------------------------------------------------------------------------------------------------
-
- def send_friend_request(self, tox_id, message):
- """
- Function tries to send request to contact with specified id
- :param tox_id: id of new contact or tox dns 4 value
- :param message: additional message
- :return: True on success else error string
- """
- try:
- message = message or 'Hello! Add me to your contact list please'
- if '@' in tox_id: # value like groupbot@toxme.io
- tox_id = self._tox_dns.lookup(tox_id)
- if tox_id is None:
- raise Exception('TOX DNS lookup failed')
- if len(tox_id) == TOX_PUBLIC_KEY_SIZE * 2: # public key
- self.add_friend(tox_id)
- title = util_ui.tr('Friend added')
- text = util_ui.tr('Friend added without sending friend request')
- util_ui.message_box(text, title)
- else:
- self._tox.friend_add(tox_id, message.encode('utf-8'))
- tox_id = tox_id[:TOX_PUBLIC_KEY_SIZE * 2]
- self._add_friend(tox_id)
- self.update_filtration()
- self.save_profile()
- return True
- except Exception as ex: # wrong data
- util.log('Friend request failed with ' + str(ex))
- return str(ex)
-
- def process_friend_request(self, tox_id, message):
- """
- Accept or ignore friend request
- :param tox_id: tox id of contact
- :param message: message
- """
- if tox_id in self._settings['blocked']:
- return
- try:
- text = util_ui.tr('User {} wants to add you to contact list. Message:\n{}')
- reply = util_ui.question(text.format(tox_id, message), util_ui.tr('Friend request'))
- if reply: # accepted
- self.add_friend(tox_id)
- data = self._tox.get_savedata()
- self._profile_manager.save_profile(data)
- except Exception as ex: # something is wrong
- util.log('Accept friend request failed! ' + str(ex))
-
- def can_send_typing_notification(self):
- return self._settings['typing_notifications'] and not self.is_active_a_group_chat_peer()
-
- # -----------------------------------------------------------------------------------------------------------------
- # Contacts numbers update
- # -----------------------------------------------------------------------------------------------------------------
-
- def update_friends_numbers(self):
- for friend in self._contact_provider.get_all_friends():
- friend.number = self._tox.friend_by_public_key(friend.tox_id)
- self.update_filtration()
-
- def update_groups_numbers(self):
- groups = self._contact_provider.get_all_groups()
- for i in range(len(groups)):
- chat_id = self._tox.group_get_chat_id(i)
- group = self.get_contact_by_tox_id(chat_id)
- group.number = i
- self.update_filtration()
-
- def update_groups_lists(self):
- groups = self._contact_provider.get_all_groups()
- for group in groups:
- group.remove_all_peers_except_self()
-
- # -----------------------------------------------------------------------------------------------------------------
- # Private methods
- # -----------------------------------------------------------------------------------------------------------------
-
- def _load_contacts(self):
- self._load_friends()
- self._load_groups()
- if len(self._contacts):
- self.set_active(0)
- for contact in filter(lambda c: not c.has_avatar(), self._contacts):
- contact.reset_avatar(self._settings['identicons'])
- self.update_filtration()
-
- def _load_friends(self):
- self._contacts.extend(self._contact_provider.get_all_friends())
-
- def _load_groups(self):
- self._contacts.extend(self._contact_provider.get_all_groups())
-
- # -----------------------------------------------------------------------------------------------------------------
- # Current contact subscriptions
- # -----------------------------------------------------------------------------------------------------------------
-
- def _subscribe_to_events(self, contact):
- contact.name_changed_event.add_callback(self._current_contact_name_changed)
- contact.status_changed_event.add_callback(self._current_contact_status_changed)
- contact.status_message_changed_event.add_callback(self._current_contact_status_message_changed)
- contact.avatar_changed_event.add_callback(self._current_contact_avatar_changed)
-
- def _unsubscribe_from_events(self, contact):
- contact.name_changed_event.remove_callback(self._current_contact_name_changed)
- contact.status_changed_event.remove_callback(self._current_contact_status_changed)
- contact.status_message_changed_event.remove_callback(self._current_contact_status_message_changed)
- contact.avatar_changed_event.remove_callback(self._current_contact_avatar_changed)
-
- def _current_contact_name_changed(self, name):
- self._screen.account_name.setText(name)
-
- def _current_contact_status_changed(self, status):
- pass
-
- def _current_contact_status_message_changed(self, status_message):
- self._screen.account_status.setText(status_message)
-
- def _current_contact_avatar_changed(self, avatar_path):
- self._set_current_contact_avatar(avatar_path)
-
- def _set_current_contact_data(self, contact):
- self._screen.account_name.setText(contact.name)
- self._screen.account_status.setText(contact.status_message)
- self._set_current_contact_avatar(contact.get_avatar_path())
-
- def _set_current_contact_avatar(self, avatar_path):
- width = self._screen.account_avatar.width()
- pixmap = QtGui.QPixmap(avatar_path)
- self._screen.account_avatar.setPixmap(pixmap.scaled(width, width,
- QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))
-
- def _add_friend(self, tox_id):
- self._history.add_friend_to_db(tox_id)
- friend = self._contact_provider.get_friend_by_public_key(tox_id)
- index = len(self._contacts)
- self._contacts.append(friend)
- if not friend.has_avatar():
- friend.reset_avatar(self._settings['identicons'])
- self._save_profile()
- self.set_active(index)
-
- def _save_profile(self):
- data = self._tox.get_savedata()
- self._profile_manager.save_profile(data)
-
- def _cleanup_contact_data(self, contact):
- try:
- index = list(map(lambda x: x[0], self._settings['friends_aliases'])).index(contact.tox_id)
- del self._settings['friends_aliases'][index]
- except:
- pass
- if contact.tox_id in self._settings['notes']:
- del self._settings['notes'][contact.tox_id]
- self._settings.save()
- self._history.delete_history(contact)
- if contact.has_avatar():
- avatar_path = contact.get_contact_avatar_path()
- remove(avatar_path)
-
- def _delete_contact(self, num):
- self.set_active(-1 if len(self._contacts) == 1 else 0)
-
- self._contact_provider.remove_contact_from_cache(self._contacts[num].tox_id)
- del self._contacts[num]
- self._screen.friends_list.takeItem(num)
- self._save_profile()
-
- self.update_filtration()
diff --git a/toxygen/contacts/friend.py b/toxygen/contacts/friend.py
deleted file mode 100644
index 5c8eabb..0000000
--- a/toxygen/contacts/friend.py
+++ /dev/null
@@ -1,74 +0,0 @@
-from contacts import contact, common
-from messenger.messages import *
-import os
-from contacts.contact_menu import *
-
-
-class Friend(contact.Contact):
- """
- Friend in list of friends.
- """
-
- def __init__(self, profile_manager, message_getter, number, name, status_message, widget, tox_id):
- super().__init__(profile_manager, message_getter, number, name, status_message, widget, tox_id)
- self._receipts = 0
- self._typing_notification_handler = common.FriendTypingNotificationHandler(number)
-
- # -----------------------------------------------------------------------------------------------------------------
- # File transfers support
- # -----------------------------------------------------------------------------------------------------------------
-
- def insert_inline(self, before_message_id, inline):
- """
- Update status of active transfer and load inline if needed
- """
- try:
- tr = list(filter(lambda m: m.message_id == before_message_id, self._corr))[0]
- i = self._corr.index(tr)
- if inline: # inline was loaded
- self._corr.insert(i, inline)
- return i - len(self._corr)
- except:
- pass
-
- def get_unsent_files(self):
- messages = filter(lambda m: type(m) is UnsentFileMessage, self._corr)
- return list(messages)
-
- def clear_unsent_files(self):
- self._corr = list(filter(lambda m: type(m) is not UnsentFileMessage, self._corr))
-
- def remove_invalid_unsent_files(self):
- def is_valid(message):
- if type(message) is not UnsentFileMessage:
- return True
- if message.data is not None:
- return True
- return os.path.exists(message.path)
-
- self._corr = list(filter(is_valid, self._corr))
-
- def delete_one_unsent_file(self, message_id):
- self._corr = list(filter(lambda m: not (type(m) is UnsentFileMessage and m.message_id == message_id),
- self._corr))
-
- # -----------------------------------------------------------------------------------------------------------------
- # Full status
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_full_status(self):
- return self._status_message
-
- # -----------------------------------------------------------------------------------------------------------------
- # Typing notifications
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_typing_notification_handler(self):
- return self._typing_notification_handler
-
- # -----------------------------------------------------------------------------------------------------------------
- # Context menu support
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_context_menu_generator(self):
- return FriendMenuGenerator(self)
diff --git a/toxygen/contacts/friend_factory.py b/toxygen/contacts/friend_factory.py
deleted file mode 100644
index 8ebafd6..0000000
--- a/toxygen/contacts/friend_factory.py
+++ /dev/null
@@ -1,44 +0,0 @@
-from contacts.friend import Friend
-from common.tox_save import ToxSave
-
-
-class FriendFactory(ToxSave):
-
- def __init__(self, profile_manager, settings, tox, db, items_factory):
- super().__init__(tox)
- self._profile_manager = profile_manager
- self._settings = settings
- self._db = db
- self._items_factory = items_factory
-
- def create_friend_by_public_key(self, public_key):
- friend_number = self._tox.friend_by_public_key(public_key)
-
- return self.create_friend_by_number(friend_number)
-
- def create_friend_by_number(self, friend_number):
- aliases = self._settings['friends_aliases']
- tox_id = self._tox.friend_get_public_key(friend_number)
- try:
- alias = list(filter(lambda x: x[0] == tox_id, aliases))[0][1]
- except:
- alias = ''
- item = self._create_friend_item()
- name = alias or self._tox.friend_get_name(friend_number) or tox_id
- status_message = self._tox.friend_get_status_message(friend_number)
- message_getter = self._db.messages_getter(tox_id)
- friend = Friend(self._profile_manager, message_getter, friend_number, name, status_message, item, tox_id)
- friend.set_alias(alias)
-
- return friend
-
- # -----------------------------------------------------------------------------------------------------------------
- # Private methods
- # -----------------------------------------------------------------------------------------------------------------
-
- def _create_friend_item(self):
- """
- Method-factory
- :return: new widget for friend instance
- """
- return self._items_factory.create_contact_item()
diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py
deleted file mode 100644
index 19ebc8e..0000000
--- a/toxygen/contacts/group_chat.py
+++ /dev/null
@@ -1,137 +0,0 @@
-from contacts import contact
-from contacts.contact_menu import GroupMenuGenerator
-import utils.util as util
-from groups.group_peer import GroupChatPeer
-from wrapper import toxcore_enums_and_consts as constants
-from common.tox_save import ToxSave
-from groups.group_ban import GroupBan
-
-
-class GroupChat(contact.Contact, ToxSave):
-
- def __init__(self, tox, profile_manager, message_getter, number, name, status_message, widget, tox_id, is_private):
- super().__init__(profile_manager, message_getter, number, name, status_message, widget, tox_id)
- ToxSave.__init__(self, tox)
-
- self._is_private = is_private
- self._password = str()
- self._peers_limit = 512
- self._peers = []
- self._add_self_to_gc()
-
- def remove_invalid_unsent_files(self):
- pass
-
- def get_context_menu_generator(self):
- return GroupMenuGenerator(self)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Properties
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_is_private(self):
- return self._is_private
-
- def set_is_private(self, is_private):
- self._is_private = is_private
-
- is_private = property(get_is_private, set_is_private)
-
- def get_password(self):
- return self._password
-
- def set_password(self, password):
- self._password = password
-
- password = property(get_password, set_password)
-
- def get_peers_limit(self):
- return self._peers_limit
-
- def set_peers_limit(self, peers_limit):
- self._peers_limit = peers_limit
-
- peers_limit = property(get_peers_limit, set_peers_limit)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Peers methods
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_self_peer(self):
- return self._peers[0]
-
- def get_self_name(self):
- return self._peers[0].name
-
- def get_self_role(self):
- return self._peers[0].role
-
- def is_self_moderator_or_founder(self):
- return self.get_self_role() <= constants.TOX_GROUP_ROLE['MODERATOR']
-
- def is_self_founder(self):
- return self.get_self_role() == constants.TOX_GROUP_ROLE['FOUNDER']
-
- def add_peer(self, peer_id, is_current_user=False):
- peer = GroupChatPeer(peer_id,
- self._tox.group_peer_get_name(self._number, peer_id),
- self._tox.group_peer_get_status(self._number, peer_id),
- self._tox.group_peer_get_role(self._number, peer_id),
- self._tox.group_peer_get_public_key(self._number, peer_id),
- is_current_user)
- self._peers.append(peer)
-
- def remove_peer(self, peer_id):
- if peer_id == self.get_self_peer().id: # we were kicked or banned
- self.remove_all_peers_except_self()
- else:
- peer = self.get_peer_by_id(peer_id)
- self._peers.remove(peer)
-
- def get_peer_by_id(self, peer_id):
- peers = list(filter(lambda p: p.id == peer_id, self._peers))
-
- return peers[0]
-
- def get_peer_by_public_key(self, public_key):
- peers = list(filter(lambda p: p.public_key == public_key, self._peers))
-
- return peers[0]
-
- def remove_all_peers_except_self(self):
- self._peers = self._peers[:1]
-
- def get_peers_names(self):
- peers_names = map(lambda p: p.name, self._peers)
-
- return list(peers_names)
-
- def get_peers(self):
- return self._peers[:]
-
- peers = property(get_peers)
-
- def get_bans(self):
- ban_ids = self._tox.group_ban_get_list(self._number)
- bans = []
- for ban_id in ban_ids:
- ban = GroupBan(ban_id,
- self._tox.group_ban_get_target(self._number, ban_id),
- self._tox.group_ban_get_time_set(self._number, ban_id))
- bans.append(ban)
-
- return bans
-
- bans = property(get_bans)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Private methods
- # -----------------------------------------------------------------------------------------------------------------
-
- @staticmethod
- def _get_default_avatar_path():
- return util.join_path(util.get_images_directory(), 'group.png')
-
- def _add_self_to_gc(self):
- peer_id = self._tox.group_self_get_peer_id(self._number)
- self.add_peer(peer_id, True)
diff --git a/toxygen/contacts/group_factory.py b/toxygen/contacts/group_factory.py
deleted file mode 100644
index 4083438..0000000
--- a/toxygen/contacts/group_factory.py
+++ /dev/null
@@ -1,53 +0,0 @@
-from contacts.group_chat import GroupChat
-from common.tox_save import ToxSave
-import wrapper.toxcore_enums_and_consts as constants
-
-
-class GroupFactory(ToxSave):
-
- def __init__(self, profile_manager, settings, tox, db, items_factory):
- super().__init__(tox)
- self._profile_manager = profile_manager
- self._settings = settings
- self._db = db
- self._items_factory = items_factory
-
- def create_group_by_public_key(self, public_key):
- group_number = self._get_group_number_by_chat_id(public_key)
-
- return self.create_group_by_number(group_number)
-
- def create_group_by_number(self, group_number):
- aliases = self._settings['friends_aliases']
- tox_id = self._tox.group_get_chat_id(group_number)
- try:
- alias = list(filter(lambda x: x[0] == tox_id, aliases))[0][1]
- except:
- alias = ''
- item = self._create_group_item()
- name = alias or self._tox.group_get_name(group_number) or tox_id
- status_message = self._tox.group_get_topic(group_number)
- message_getter = self._db.messages_getter(tox_id)
- is_private = self._tox.group_get_privacy_state(group_number) == constants.TOX_GROUP_PRIVACY_STATE['PRIVATE']
- group = GroupChat(self._tox, self._profile_manager, message_getter, group_number, name, status_message,
- item, tox_id, is_private)
- group.set_alias(alias)
-
- return group
-
- # -----------------------------------------------------------------------------------------------------------------
- # Private methods
- # -----------------------------------------------------------------------------------------------------------------
-
- def _create_group_item(self):
- """
- Method-factory
- :return: new widget for group instance
- """
- return self._items_factory.create_contact_item()
-
- def _get_group_number_by_chat_id(self, chat_id):
- for i in range(self._tox.group_get_number_groups()):
- if self._tox.group_get_chat_id(i) == chat_id:
- return i
- return -1
diff --git a/toxygen/contacts/group_peer_contact.py b/toxygen/contacts/group_peer_contact.py
deleted file mode 100644
index 8854198..0000000
--- a/toxygen/contacts/group_peer_contact.py
+++ /dev/null
@@ -1,20 +0,0 @@
-import contacts.contact
-from contacts.contact_menu import GroupPeerMenuGenerator
-
-
-class GroupPeerContact(contacts.contact.Contact):
-
- def __init__(self, profile_manager, message_getter, peer_number, name, widget, tox_id, group_pk):
- super().__init__(profile_manager, message_getter, peer_number, name, str(), widget, tox_id)
- self._group_pk = group_pk
-
- def get_group_pk(self):
- return self._group_pk
-
- group_pk = property(get_group_pk)
-
- def remove_invalid_unsent_files(self):
- pass
-
- def get_context_menu_generator(self):
- return GroupPeerMenuGenerator(self)
diff --git a/toxygen/contacts/group_peer_factory.py b/toxygen/contacts/group_peer_factory.py
deleted file mode 100644
index 38b3a20..0000000
--- a/toxygen/contacts/group_peer_factory.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from common.tox_save import ToxSave
-from contacts.group_peer_contact import GroupPeerContact
-
-
-class GroupPeerFactory(ToxSave):
-
- def __init__(self, tox, profile_manager, db, items_factory):
- super().__init__(tox)
- self._profile_manager = profile_manager
- self._db = db
- self._items_factory = items_factory
-
- def create_group_peer(self, group, peer):
- item = self._create_group_peer_item()
- message_getter = self._db.messages_getter(peer.public_key)
- group_peer_contact = GroupPeerContact(self._profile_manager, message_getter, peer.id, peer.name,
- item, peer.public_key, group.tox_id)
- group_peer_contact.status = peer.status
-
- return group_peer_contact
-
- def _create_group_peer_item(self):
- return self._items_factory.create_contact_item()
diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py
deleted file mode 100644
index 81220af..0000000
--- a/toxygen/contacts/profile.py
+++ /dev/null
@@ -1,87 +0,0 @@
-from contacts import basecontact
-import random
-import threading
-import common.tox_save as tox_save
-from middleware.threads import invoke_in_main_thread
-
-
-class Profile(basecontact.BaseContact, tox_save.ToxSave):
- """
- Profile of current toxygen user.
- """
- def __init__(self, profile_manager, tox, screen, contacts_provider, reset_action):
- """
- :param tox: tox instance
- :param screen: ref to main screen
- """
- basecontact.BaseContact.__init__(self,
- profile_manager,
- tox.self_get_name(),
- tox.self_get_status_message(),
- screen,
- tox.self_get_address())
- tox_save.ToxSave.__init__(self, tox)
- self._screen = screen
- self._messages = screen.messages
- self._contacts_provider = contacts_provider
- self._reset_action = reset_action
- self._waiting_for_reconnection = False
- self._timer = None
-
- # -----------------------------------------------------------------------------------------------------------------
- # Edit current user's data
- # -----------------------------------------------------------------------------------------------------------------
-
- def change_status(self):
- """
- Changes status of user (online, away, busy)
- """
- if self._status is not None:
- self.set_status((self._status + 1) % 3)
-
- def set_status(self, status):
- super().set_status(status)
- if status is not None:
- self._tox.self_set_status(status)
- elif not self._waiting_for_reconnection:
- self._waiting_for_reconnection = True
- self._timer = threading.Timer(50, self._reconnect)
- self._timer.start()
-
- def set_name(self, value):
- if self.name == value:
- return
- super().set_name(value)
- self._tox.self_set_name(self._name)
-
- def set_status_message(self, value):
- super().set_status_message(value)
- self._tox.self_set_status_message(self._status_message)
-
- def set_new_nospam(self):
- """Sets new nospam part of tox id"""
- self._tox.self_set_nospam(random.randint(0, 4294967295)) # no spam - uint32
- self._tox_id = self._tox.self_get_address()
-
- return self._tox_id
-
- # -----------------------------------------------------------------------------------------------------------------
- # Reset
- # -----------------------------------------------------------------------------------------------------------------
-
- def restart(self):
- """
- Recreate tox instance
- """
- self.status = None
- invoke_in_main_thread(self._reset_action)
-
- def _reconnect(self):
- self._waiting_for_reconnection = False
- contacts = self._contacts_provider.get_all_friends()
- all_friends_offline = all(list(map(lambda x: x.status is None, contacts)))
- if self.status is None or (all_friends_offline and len(contacts)):
- self._waiting_for_reconnection = True
- self.restart()
- self._timer = threading.Timer(50, self._reconnect)
- self._timer.start()
diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers.py
similarity index 69%
rename from toxygen/file_transfers/file_transfers.py
rename to toxygen/file_transfers.py
index 0f04e5b..7b23ffc 100644
--- a/toxygen/file_transfers/file_transfers.py
+++ b/toxygen/file_transfers.py
@@ -1,21 +1,24 @@
-from wrapper.toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL
+from toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL
from os.path import basename, getsize, exists, dirname
from os import remove, rename, chdir
-from time import time
-from wrapper.tox import Tox
-from common.event import Event
-from middleware.threads import invoke_in_main_thread
+from time import time, sleep
+from tox import Tox
+import settings
+try:
+ from PySide import QtCore
+except ImportError:
+ from PyQt4 import QtCore
+ QtCore.Signal = QtCore.pyqtSignal
-FILE_TRANSFER_STATE = {
+TOX_FILE_TRANSFER_STATE = {
'RUNNING': 0,
'PAUSED_BY_USER': 1,
'CANCELLED': 2,
'FINISHED': 3,
'PAUSED_BY_FRIEND': 4,
'INCOMING_NOT_STARTED': 5,
- 'OUTGOING_NOT_STARTED': 6,
- 'UNSENT': 7
+ 'OUTGOING_NOT_STARTED': 6
}
ACTIVE_FILE_TRANSFERS = (0, 1, 4, 5, 6)
@@ -26,105 +29,97 @@ DO_NOT_SHOW_ACCEPT_BUTTON = (2, 3, 4, 6)
SHOW_PROGRESS_BAR = (0, 1, 4)
-
-def is_inline(file_name):
- allowed_inlines = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png')
-
- return file_name in allowed_inlines or file_name.startswith('qTox_Image_')
+ALLOWED_FILES = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png')
-class FileTransfer:
+class StateSignal(QtCore.QObject):
+
+ signal = QtCore.Signal(int, float, int) # state, progress, time in sec
+
+
+class TransferFinishedSignal(QtCore.QObject):
+
+ signal = QtCore.Signal(int, int) # friend number, file number
+
+
+class FileTransfer(QtCore.QObject):
"""
Superclass for file transfers
"""
def __init__(self, path, tox, friend_number, size, file_number=None):
+ QtCore.QObject.__init__(self)
self._path = path
self._tox = tox
self._friend_number = friend_number
- self._state = FILE_TRANSFER_STATE['RUNNING']
+ self.state = TOX_FILE_TRANSFER_STATE['RUNNING']
self._file_number = file_number
self._creation_time = None
self._size = float(size)
self._done = 0
- self._state_changed_event = Event()
- self._finished_event = Event()
- self._file_id = self._file = None
+ self._state_changed = StateSignal()
+ self._finished = TransferFinishedSignal()
+ self._file_id = None
+
+ def set_tox(self, tox):
+ self._tox = tox
def set_state_changed_handler(self, handler):
- self._state_changed_event += lambda *args: invoke_in_main_thread(handler, *args)
+ self._state_changed.signal.connect(handler)
def set_transfer_finished_handler(self, handler):
- self._finished_event += lambda *args: invoke_in_main_thread(handler, *args)
+ self._finished.signal.connect(handler)
- def get_file_number(self):
- return self._file_number
-
- file_number = property(get_file_number)
-
- def get_state(self):
- return self._state
-
- def set_state(self, value):
- self._state = value
- self._signal()
-
- state = property(get_state, set_state)
-
- def get_friend_number(self):
- return self._friend_number
-
- friend_number = property(get_friend_number)
-
- def get_file_id(self):
- return self._file_id
-
- file_id = property(get_file_id)
-
- def get_path(self):
- return self._path
-
- path = property(get_path)
-
- def get_size(self):
- return self._size
-
- size = property(get_size)
-
- def cancel(self):
- self.send_control(TOX_FILE_CONTROL['CANCEL'])
- if self._file is not None:
- self._file.close()
- self._signal()
-
- def cancelled(self):
- if self._file is not None:
- self._file.close()
- self.set_state(FILE_TRANSFER_STATE['CANCELLED'])
-
- def pause(self, by_friend):
- if not by_friend:
- self.send_control(TOX_FILE_CONTROL['PAUSE'])
- else:
- self.set_state(FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'])
-
- def send_control(self, control):
- if self._tox.file_control(self._friend_number, self._file_number, control):
- self.set_state(control)
-
- def get_file_id(self):
- return self._tox.file_get_file_id(self._friend_number, self._file_number)
-
- def _signal(self):
+ def signal(self):
percentage = self._done / self._size if self._size else 0
if self._creation_time is None or not percentage:
t = -1
else:
t = ((time() - self._creation_time) / percentage) * (1 - percentage)
- self._state_changed_event(self.state, percentage, int(t))
+ self._state_changed.signal.emit(self.state, percentage, int(t))
- def _finished(self):
- self._finished_event(self._friend_number, self._file_number)
+ 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'):
+ self._file.close()
+ self.signal()
+
+ def cancelled(self):
+ if hasattr(self, '_file'):
+ sleep(0.1)
+ self._file.close()
+ self.state = TOX_FILE_TRANSFER_STATE['CANCELLED']
+ self.signal()
+
+ def pause(self, by_friend):
+ if not by_friend:
+ self.send_control(TOX_FILE_CONTROL['PAUSE'])
+ else:
+ self.state = TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND']
+ self.signal()
+
+ def send_control(self, control):
+ if self._tox.file_control(self._friend_number, self._file_number, control):
+ self.state = control
+ self.signal()
+
+ def get_file_id(self):
+ return self._tox.file_get_file_id(self._friend_number, self._file_number)
# -----------------------------------------------------------------------------------------------------------------
# Send file
@@ -135,14 +130,12 @@ class SendTransfer(FileTransfer):
def __init__(self, path, tox, friend_number, kind=TOX_FILE_KIND['DATA'], file_id=None):
if path is not None:
- fl = open(path, 'rb')
+ self._file = open(path, 'rb')
size = getsize(path)
else:
- fl = None
size = 0
- super().__init__(path, tox, friend_number, size)
- self._file = fl
- self.state = FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
+ super(SendTransfer, self).__init__(path, tox, friend_number, size)
+ self.state = TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
self._file_number = tox.file_send(friend_number, kind, size, file_id,
bytes(basename(path), 'utf-8') if path else b'')
self._file_id = self.get_file_id()
@@ -160,12 +153,12 @@ 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 self._file is not None:
+ if hasattr(self, '_file'):
self._file.close()
- self.state = FILE_TRANSFER_STATE['FINISHED']
- self._finished()
+ self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
+ self.finished()
+ self.signal()
class SendAvatar(SendTransfer):
@@ -175,11 +168,11 @@ class SendAvatar(SendTransfer):
def __init__(self, path, tox, friend_number):
if path is None:
- avatar_hash = None
+ hash = None
else:
with open(path, 'rb') as fl:
- avatar_hash = Tox.hash(fl.read())
- super().__init__(path, tox, friend_number, TOX_FILE_KIND['AVATAR'], avatar_hash)
+ hash = Tox.hash(fl.read())
+ super(SendAvatar, self).__init__(path, tox, friend_number, TOX_FILE_KIND['AVATAR'], hash)
class SendFromBuffer(FileTransfer):
@@ -188,8 +181,8 @@ class SendFromBuffer(FileTransfer):
"""
def __init__(self, tox, friend_number, data, file_name):
- super().__init__(None, tox, friend_number, len(data))
- self.state = FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
+ super(SendFromBuffer, self).__init__(None, tox, friend_number, len(data))
+ self.state = TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
self._data = data
self._file_number = tox.file_send(friend_number, TOX_FILE_KIND['DATA'],
len(data), None, bytes(file_name, 'utf-8'))
@@ -197,8 +190,6 @@ class SendFromBuffer(FileTransfer):
def get_data(self):
return self._data
- data = property(get_data)
-
def send_chunk(self, position, size):
if self._creation_time is None:
self._creation_time = time()
@@ -207,18 +198,18 @@ class SendFromBuffer(FileTransfer):
self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
self._done += size
else:
- self.state = FILE_TRANSFER_STATE['FINISHED']
- self._finished()
- self._signal()
+ self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
+ self.finished()
+ self.signal()
class SendFromFileBuffer(SendTransfer):
def __init__(self, *args):
- super().__init__(*args)
+ super(SendFromFileBuffer, self).__init__(*args)
def send_chunk(self, position, size):
- super().send_chunk(position, size)
+ super(SendFromFileBuffer, self).send_chunk(position, size)
if not size:
chdir(dirname(self._path))
remove(self._path)
@@ -231,7 +222,7 @@ class SendFromFileBuffer(SendTransfer):
class ReceiveTransfer(FileTransfer):
def __init__(self, path, tox, friend_number, size, file_number, position=0):
- super().__init__(path, tox, friend_number, size, file_number)
+ super(ReceiveTransfer, self).__init__(path, tox, friend_number, size, file_number)
self._file = open(self._path, 'wb')
self._file_size = position
self._file.truncate(position)
@@ -240,12 +231,11 @@ class ReceiveTransfer(FileTransfer):
self._done = position
def cancel(self):
- super().cancel()
+ 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):
@@ -258,8 +248,8 @@ class ReceiveTransfer(FileTransfer):
self._creation_time = time()
if data is None:
self._file.close()
- self.state = FILE_TRANSFER_STATE['FINISHED']
- self._finished()
+ self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
+ self.finished()
else:
data = bytearray(data)
if self._file_size < position:
@@ -274,7 +264,7 @@ class ReceiveTransfer(FileTransfer):
if position + l > self._file_size:
self._file_size = position + l
self._done += l
- self._signal()
+ self.signal()
class ReceiveToBuffer(FileTransfer):
@@ -283,21 +273,19 @@ class ReceiveToBuffer(FileTransfer):
"""
def __init__(self, tox, friend_number, size, file_number):
- super().__init__(None, tox, friend_number, size, file_number)
+ super(ReceiveToBuffer, self).__init__(None, tox, friend_number, size, file_number)
self._data = bytes()
self._data_size = 0
def get_data(self):
return self._data
- data = property(get_data)
-
def write_chunk(self, position, data):
if self._creation_time is None:
self._creation_time = time()
if data is None:
- self.state = FILE_TRANSFER_STATE['FINISHED']
- self._finished()
+ self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
+ self.finished()
else:
data = bytes(data)
l = len(data)
@@ -307,7 +295,7 @@ class ReceiveToBuffer(FileTransfer):
if position + l > self._data_size:
self._data_size = position + l
self._done += l
- self._signal()
+ self.signal()
class ReceiveAvatar(ReceiveTransfer):
@@ -316,17 +304,20 @@ class ReceiveAvatar(ReceiveTransfer):
"""
MAX_AVATAR_SIZE = 512 * 1024
- def __init__(self, path, tox, friend_number, size, file_number):
- full_path = path + '.tmp'
- super().__init__(full_path, tox, friend_number, size, file_number)
+ def __init__(self, tox, friend_number, size, file_number):
+ path = settings.ProfileHelper.get_path() + 'avatars/{}.png'.format(tox.friend_get_public_key(friend_number))
+ super(ReceiveAvatar, self).__init__(path + '.tmp', tox, friend_number, size, file_number)
if size > self.MAX_AVATAR_SIZE:
self.send_control(TOX_FILE_CONTROL['CANCEL'])
self._file.close()
- remove(full_path)
+ remove(path + '.tmp')
elif not size:
self.send_control(TOX_FILE_CONTROL['CANCEL'])
self._file.close()
- remove(full_path)
+ if exists(path):
+ remove(path)
+ self._file.close()
+ remove(path + '.tmp')
elif exists(path):
hash = self.get_file_id()
with open(path, 'rb') as fl:
@@ -335,17 +326,17 @@ class ReceiveAvatar(ReceiveTransfer):
if hash == existing_hash:
self.send_control(TOX_FILE_CONTROL['CANCEL'])
self._file.close()
- remove(full_path)
+ remove(path + '.tmp')
else:
self.send_control(TOX_FILE_CONTROL['RESUME'])
else:
self.send_control(TOX_FILE_CONTROL['RESUME'])
def write_chunk(self, position, data):
- if data is None:
+ super(ReceiveAvatar, self).write_chunk(position, data)
+ if self.state:
avatar_path = self._path[:-4]
if exists(avatar_path):
chdir(dirname(avatar_path))
remove(avatar_path)
rename(self._path, avatar_path)
- super().write_chunk(position, data)
diff --git a/toxygen/file_transfers/__init__.py b/toxygen/file_transfers/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py
deleted file mode 100644
index 114383b..0000000
--- a/toxygen/file_transfers/file_transfers_handler.py
+++ /dev/null
@@ -1,304 +0,0 @@
-from messenger.messages import *
-from ui.contact_items import *
-import utils.util as util
-from common.tox_save import ToxSave
-
-
-class FileTransfersHandler(ToxSave):
-
- def __init__(self, tox, settings, contact_provider, file_transfers_message_service, profile):
- super().__init__(tox)
- self._settings = settings
- self._contact_provider = contact_provider
- self._file_transfers_message_service = file_transfers_message_service
- self._file_transfers = {}
- # key = (friend number, file number), value - transfer instance
- self._paused_file_transfers = dict(settings['paused_file_transfers'])
- # key - file id, value: [path, friend number, is incoming, start position]
- self._insert_inline_before = {}
- # key = (friend number, file number), value - message id
-
- profile.avatar_changed_event.add_callback(self._send_avatar_to_contacts)
-
- def stop(self):
- self._settings['paused_file_transfers'] = self._paused_file_transfers if self._settings['resend_files'] else {}
- self._settings.save()
-
- # -----------------------------------------------------------------------------------------------------------------
- # File transfers support
- # -----------------------------------------------------------------------------------------------------------------
-
- def incoming_file_transfer(self, friend_number, file_number, size, file_name):
- """
- New transfer
- :param friend_number: number of friend who sent file
- :param file_number: file number
- :param size: file size in bytes
- :param file_name: file name without path
- """
- friend = self._get_friend_by_number(friend_number)
- auto = self._settings['allow_auto_accept'] and friend.tox_id in self._settings['auto_accept_from_friends']
- inline = is_inline(file_name) and self._settings['allow_inline']
- file_id = self._tox.file_get_file_id(friend_number, file_number)
- accepted = True
- if file_id in self._paused_file_transfers:
- (path, ft_friend_number, is_incoming, start_position) = self._paused_file_transfers[file_id]
- pos = start_position if os.path.exists(path) else 0
- if pos >= size:
- self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
- return
- self._tox.file_seek(friend_number, file_number, pos)
- self._file_transfers_message_service.add_incoming_transfer_message(
- friend, accepted, size, file_name, file_number)
- self.accept_transfer(path, friend_number, file_number, size, False, pos)
- elif inline and size < 1024 * 1024:
- self._file_transfers_message_service.add_incoming_transfer_message(
- friend, accepted, size, file_name, file_number)
- self.accept_transfer('', friend_number, file_number, size, True)
- elif auto:
- path = self._settings['auto_accept_path'] or util.curr_directory()
- self._file_transfers_message_service.add_incoming_transfer_message(
- friend, accepted, size, file_name, file_number)
- self.accept_transfer(path + '/' + file_name, friend_number, file_number, size)
- else:
- accepted = False
- self._file_transfers_message_service.add_incoming_transfer_message(
- friend, accepted, size, file_name, file_number)
-
- def cancel_transfer(self, friend_number, file_number, already_cancelled=False):
- """
- Stop transfer
- :param friend_number: number of friend
- :param file_number: file number
- :param already_cancelled: was cancelled by friend
- """
- if (friend_number, file_number) in self._file_transfers:
- tr = self._file_transfers[(friend_number, file_number)]
- if not already_cancelled:
- tr.cancel()
- else:
- tr.cancelled()
- if (friend_number, file_number) in self._file_transfers:
- del tr
- del self._file_transfers[(friend_number, file_number)]
- elif not already_cancelled:
- self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
-
- def cancel_not_started_transfer(self, friend_number, message_id):
- self._get_friend_by_number(friend_number).delete_one_unsent_file(message_id)
-
- def pause_transfer(self, friend_number, file_number, by_friend=False):
- """
- Pause transfer with specified data
- """
- tr = self._file_transfers[(friend_number, file_number)]
- tr.pause(by_friend)
-
- def resume_transfer(self, friend_number, file_number, by_friend=False):
- """
- Resume transfer with specified data
- """
- tr = self._file_transfers[(friend_number, file_number)]
- if by_friend:
- tr.state = FILE_TRANSFER_STATE['RUNNING']
- else:
- tr.send_control(TOX_FILE_CONTROL['RESUME'])
-
- def accept_transfer(self, path, friend_number, file_number, size, inline=False, from_position=0):
- """
- :param path: path for saving
- :param friend_number: friend number
- :param file_number: file number
- :param size: file size
- :param inline: is inline image
- :param from_position: position for start
- """
- path = self._generate_valid_path(path, from_position)
- friend = self._get_friend_by_number(friend_number)
- if not inline:
- rt = ReceiveTransfer(path, self._tox, friend_number, size, file_number, from_position)
- else:
- rt = ReceiveToBuffer(self._tox, friend_number, size, file_number)
- rt.set_transfer_finished_handler(self.transfer_finished)
- message = friend.get_message(lambda m: m.type == MESSAGE_TYPE['FILE_TRANSFER']
- and m.state in (FILE_TRANSFER_STATE['INCOMING_NOT_STARTED'],
- FILE_TRANSFER_STATE['RUNNING'])
- and m.file_number == file_number)
- rt.set_state_changed_handler(message.transfer_updated)
- self._file_transfers[(friend_number, file_number)] = rt
- rt.send_control(TOX_FILE_CONTROL['RESUME'])
- if inline:
- self._insert_inline_before[(friend_number, file_number)] = message.message_id
-
- def send_screenshot(self, data, friend_number):
- """
- Send screenshot
- :param data: raw data - png format
- :param friend_number: friend number
- """
- self.send_inline(data, 'toxygen_inline.png', friend_number)
-
- def send_sticker(self, path, friend_number):
- with open(path, 'rb') as fl:
- data = fl.read()
- self.send_inline(data, 'sticker.png', friend_number)
-
- def send_inline(self, data, file_name, friend_number, is_resend=False):
- friend = self._get_friend_by_number(friend_number)
- if friend.status is None and not is_resend:
- self._file_transfers_message_service.add_unsent_file_message(friend, file_name, data)
- return
- elif friend.status is None and is_resend:
- raise RuntimeError()
- st = SendFromBuffer(self._tox, friend.number, data, file_name)
- self._send_file_add_set_handlers(st, friend, file_name, True)
-
- def send_file(self, path, friend_number, is_resend=False, file_id=None):
- """
- Send file to current active friend
- :param path: file path
- :param friend_number: friend_number
- :param is_resend: is 'offline' message
- :param file_id: file id of transfer
- """
- friend = self._get_friend_by_number(friend_number)
- if friend.status is None and not is_resend:
- self._file_transfers_message_service.add_unsent_file_message(friend, path, None)
- return
- elif friend.status is None and is_resend:
- print('Error in sending')
- return
- st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id)
- file_name = os.path.basename(path)
- self._send_file_add_set_handlers(st, friend, file_name)
-
- def incoming_chunk(self, friend_number, file_number, position, data):
- """
- Incoming chunk
- """
- self._file_transfers[(friend_number, file_number)].write_chunk(position, data)
-
- def outgoing_chunk(self, friend_number, file_number, position, size):
- """
- Outgoing chunk
- """
- self._file_transfers[(friend_number, file_number)].send_chunk(position, size)
-
- def transfer_finished(self, friend_number, file_number):
- transfer = self._file_transfers[(friend_number, file_number)]
- t = type(transfer)
- if t is ReceiveAvatar:
- self._get_friend_by_number(friend_number).load_avatar()
- elif t is ReceiveToBuffer or (t is SendFromBuffer and self._settings['allow_inline']): # inline image
- print('inline')
- inline = InlineImageMessage(transfer.data)
- message_id = self._insert_inline_before[(friend_number, file_number)]
- del self._insert_inline_before[(friend_number, file_number)]
- index = self._get_friend_by_number(friend_number).insert_inline(message_id, inline)
- self._file_transfers_message_service.add_inline_message(transfer, index)
- del self._file_transfers[(friend_number, file_number)]
-
- def send_files(self, friend_number):
- friend = self._get_friend_by_number(friend_number)
- friend.remove_invalid_unsent_files()
- files = friend.get_unsent_files()
- try:
- for fl in files:
- data, path = fl.data, fl.path
- if data is not None:
- self.send_inline(data, path, friend_number, True)
- else:
- self.send_file(path, friend_number, True)
- friend.clear_unsent_files()
- for key in self._paused_file_transfers.keys():
- (path, ft_friend_number, is_incoming, start_position) = self._paused_file_transfers[key]
- if not os.path.exists(path):
- del self._paused_file_transfers[key]
- elif ft_friend_number == friend_number and not is_incoming:
- self.send_file(path, friend_number, True, key)
- del self._paused_file_transfers[key]
- except Exception as ex:
- print('Exception in file sending: ' + str(ex))
-
- def friend_exit(self, friend_number):
- for friend_num, file_num in self._file_transfers.keys():
- if friend_num != friend_number:
- continue
- ft = self._file_transfers[(friend_num, file_num)]
- if type(ft) is SendTransfer:
- self._paused_file_transfers[ft.file_id] = [ft.path, friend_num, False, -1]
- elif type(ft) is ReceiveTransfer and ft.state != FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
- self._paused_file_transfers[ft.file_id] = [ft.path, friend_num, True, ft.total_size()]
- self.cancel_transfer(friend_num, file_num, True)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Avatars support
- # -----------------------------------------------------------------------------------------------------------------
-
- def send_avatar(self, friend_number, avatar_path=None):
- """
- :param friend_number: number of friend who should get new avatar
- :param avatar_path: path to avatar or None if reset
- """
- sa = SendAvatar(avatar_path, self._tox, friend_number)
- self._file_transfers[(friend_number, sa.file_number)] = sa
-
- def incoming_avatar(self, friend_number, file_number, size):
- """
- Friend changed avatar
- :param friend_number: friend number
- :param file_number: file number
- :param size: size of avatar or 0 (default avatar)
- """
- friend = self._get_friend_by_number(friend_number)
- ra = ReceiveAvatar(friend.get_contact_avatar_path(), self._tox, friend_number, size, file_number)
- if ra.state != FILE_TRANSFER_STATE['CANCELLED']:
- self._file_transfers[(friend_number, file_number)] = ra
- ra.set_transfer_finished_handler(self.transfer_finished)
- elif not size:
- friend.reset_avatar(self._settings['identicons'])
-
- def _send_avatar_to_contacts(self, _):
- friends = self._get_all_friends()
- for friend in filter(self._is_friend_online, friends):
- self.send_avatar(friend.number)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Private methods
- # -----------------------------------------------------------------------------------------------------------------
-
- def _is_friend_online(self, friend_number):
- friend = self._get_friend_by_number(friend_number)
-
- return friend.status is not None
-
- def _get_friend_by_number(self, friend_number):
- return self._contact_provider.get_friend_by_number(friend_number)
-
- def _get_all_friends(self):
- return self._contact_provider.get_all_friends()
-
- def _send_file_add_set_handlers(self, st, friend, file_name, inline=False):
- st.set_transfer_finished_handler(self.transfer_finished)
- file_number = st.get_file_number()
- self._file_transfers[(friend.number, file_number)] = st
- tm = self._file_transfers_message_service.add_outgoing_transfer_message(friend, st.size, file_name, file_number)
- st.set_state_changed_handler(tm.transfer_updated)
- if inline:
- self._insert_inline_before[(friend.number, file_number)] = tm.message_id
-
- @staticmethod
- def _generate_valid_path(path, from_position):
- path, file_name = os.path.split(path)
- new_file_name, i = file_name, 1
- if not from_position:
- while os.path.isfile(join_path(path, new_file_name)): # file with same name already exists
- if '.' in file_name: # has extension
- d = file_name.rindex('.')
- else: # no extension
- d = len(file_name)
- new_file_name = file_name[:d] + ' ({})'.format(i) + file_name[d:]
- i += 1
- path = join_path(path, new_file_name)
-
- return path
diff --git a/toxygen/file_transfers/file_transfers_messages_service.py b/toxygen/file_transfers/file_transfers_messages_service.py
deleted file mode 100644
index 4509183..0000000
--- a/toxygen/file_transfers/file_transfers_messages_service.py
+++ /dev/null
@@ -1,78 +0,0 @@
-from messenger.messenger import *
-import utils.util as util
-from file_transfers.file_transfers import *
-
-
-class FileTransfersMessagesService:
-
- def __init__(self, contacts_manager, messages_items_factory, profile, main_screen):
- self._contacts_manager = contacts_manager
- self._messages_items_factory = messages_items_factory
- self._profile = profile
- self._messages = main_screen.messages
-
- def add_incoming_transfer_message(self, friend, accepted, size, file_name, file_number):
- author = MessageAuthor(friend.name, MESSAGE_AUTHOR['FRIEND'])
- status = FILE_TRANSFER_STATE['RUNNING'] if accepted else FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']
- tm = TransferMessage(author, util.get_unix_time(), status, size, file_name, friend.number, file_number)
-
- if self._is_friend_active(friend.number):
- self._create_file_transfer_item(tm)
- self._messages.scrollToBottom()
- else:
- friend.actions = True
-
- friend.append_message(tm)
-
- return tm
-
- def add_outgoing_transfer_message(self, friend, size, file_name, file_number):
- author = MessageAuthor(self._profile.name, MESSAGE_AUTHOR['ME'])
- status = FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
- tm = TransferMessage(author, util.get_unix_time(), status, size, file_name, friend.number, file_number)
-
- if self._is_friend_active(friend.number):
- self._create_file_transfer_item(tm)
- self._messages.scrollToBottom()
-
- friend.append_message(tm)
-
- return tm
-
- def add_inline_message(self, transfer, index):
- if not self._is_friend_active(transfer.friend_number):
- return
- count = self._messages.count()
- if count + index + 1 >= 0:
- self._create_inline_item(transfer.data, count + index + 1)
-
- def add_unsent_file_message(self, friend, file_path, data):
- author = MessageAuthor(self._profile.name, MESSAGE_AUTHOR['ME'])
- size = os.path.getsize(file_path) if data is None else len(data)
- tm = UnsentFileMessage(file_path, data, util.get_unix_time(), author, size, friend.number)
- friend.append_message(tm)
-
- if self._is_friend_active(friend.number):
- self._create_unsent_file_item(tm)
- self._messages.scrollToBottom()
-
- return tm
-
- # -----------------------------------------------------------------------------------------------------------------
- # Private methods
- # -----------------------------------------------------------------------------------------------------------------
-
- def _is_friend_active(self, friend_number):
- if not self._contacts_manager.is_active_a_friend():
- return False
-
- return friend_number == self._contacts_manager.get_active_number()
-
- def _create_file_transfer_item(self, tm):
- return self._messages_items_factory.create_file_transfer_item(tm)
-
- def _create_inline_item(self, data, position):
- return self._messages_items_factory.create_inline_item(data, False, position)
-
- def _create_unsent_file_item(self, tm):
- return self._messages_items_factory.create_unsent_file_item(tm)
diff --git a/toxygen/friend.py b/toxygen/friend.py
new file mode 100644
index 0000000..4e57f0b
--- /dev/null
+++ b/toxygen/friend.py
@@ -0,0 +1,251 @@
+import contact
+from messages import *
+from history import *
+import util
+import file_transfers as ft
+
+
+class Friend(contact.Contact):
+ """
+ Friend in list of friends. Can be hidden, properties 'has unread messages' and 'has alias' added
+ """
+
+ def __init__(self, message_getter, number, *args):
+ """
+ :param message_getter: gets messages from db
+ :param number: number of friend.
+ """
+ super(Friend, self).__init__(*args)
+ self._number = number
+ self._new_messages = False
+ self._visible = True
+ self._alias = False
+ self._message_getter = message_getter
+ self._corr = []
+ self._unsaved_messages = 0
+ self._history_loaded = self._new_actions = False
+ self._receipts = 0
+ self._curr_text = ''
+
+ def __del__(self):
+ self.set_visibility(False)
+ del self._widget
+ if hasattr(self, '_message_getter'):
+ del self._message_getter
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # History support
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def get_receipts(self):
+ return self._receipts
+
+ receipts = property(get_receipts) # read receipts
+
+ def inc_receipts(self):
+ self._receipts += 1
+
+ def dec_receipt(self):
+ if self._receipts:
+ self._receipts -= 1
+ self.mark_as_sent()
+
+ def load_corr(self, first_time=True):
+ """
+ :param first_time: friend became active, load first part of messages
+ """
+ if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')):
+ return
+ data = list(self._message_getter.get(PAGE_SIZE))
+ if data is not None and len(data):
+ data.reverse()
+ else:
+ return
+ data = list(map(lambda tupl: TextMessage(*tupl), data))
+ self._corr = data + self._corr
+ self._history_loaded = True
+
+ def load_all_corr(self):
+ 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 ''
+
+ 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
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def update_transfer_data(self, file_number, status, inline=None):
+ """
+ Update status of active transfer and load inline if needed
+ """
+ try:
+ tr = list(filter(lambda x: x.get_type() == MESSAGE_TYPE['FILE_TRANSFER'] and x.is_active(file_number),
+ self._corr))[0]
+ tr.set_status(status)
+ i = self._corr.index(tr)
+ if inline: # inline was loaded
+ self._corr.insert(i, inline)
+ return i - len(self._corr)
+ except:
+ pass
+
+ def get_unsent_files(self):
+ messages = filter(lambda x: type(x) is UnsentFile, self._corr)
+ return messages
+
+ def clear_unsent_files(self):
+ self._corr = list(filter(lambda x: type(x) is not UnsentFile, self._corr))
+
+ def delete_one_unsent_file(self, time):
+ self._corr = list(filter(lambda x: not (type(x) is UnsentFile and x.get_data()[2] == time), self._corr))
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Alias support
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def set_name(self, value):
+ """
+ Set new name or ignore if alias exists
+ :param value: new name
+ """
+ if not self._alias:
+ super(Friend, self).set_name(value)
+
+ def set_alias(self, alias):
+ self._alias = bool(alias)
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Visibility in friends' list
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def get_visibility(self):
+ return self._visible
+
+ def set_visibility(self, value):
+ self._visible = value
+
+ visibility = property(get_visibility, set_visibility)
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Unread messages from friend
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def get_actions(self):
+ return self._new_actions
+
+ def set_actions(self, value):
+ self._new_actions = value
+ self._widget.connection_status.update(self.status, value)
+
+ actions = property(get_actions, set_actions) # unread messages, incoming files, av calls
+
+ def get_messages(self):
+ return self._new_messages
+
+ def inc_messages(self):
+ self._new_messages += 1
+ self._new_actions = True
+ self._widget.connection_status.update(self.status, True)
+ self._widget.messages.update(self._new_messages)
+
+ def reset_messages(self):
+ self._new_actions = False
+ self._new_messages = 0
+ self._widget.messages.update(self._new_messages)
+ self._widget.connection_status.update(self.status, False)
+
+ messages = property(get_messages)
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Friend's number (can be used in toxcore)
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def get_number(self):
+ return self._number
+
+ def set_number(self, value):
+ self._number = value
+
+ number = property(get_number, set_number)
diff --git a/toxygen/groups/__init__.py b/toxygen/groups/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/toxygen/groups/group_ban.py b/toxygen/groups/group_ban.py
deleted file mode 100644
index 89ecc7e..0000000
--- a/toxygen/groups/group_ban.py
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-class GroupBan:
-
- def __init__(self, ban_id, ban_target, ban_time):
- self._ban_id = ban_id
- self._ban_target = ban_target
- self._ban_time = ban_time
-
- def get_ban_id(self):
- return self._ban_id
-
- ban_id = property(get_ban_id)
-
- def get_ban_target(self):
- return self._ban_target
-
- ban_target = property(get_ban_target)
-
- def get_ban_time(self):
- return self._ban_time
-
- ban_time = property(get_ban_time)
diff --git a/toxygen/groups/group_invite.py b/toxygen/groups/group_invite.py
deleted file mode 100644
index a2eed47..0000000
--- a/toxygen/groups/group_invite.py
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-class GroupInvite:
-
- def __init__(self, friend_public_key, chat_name, invite_data):
- self._friend_public_key = friend_public_key
- self._chat_name = chat_name
- self._invite_data = invite_data[:]
-
- def get_friend_public_key(self):
- return self._friend_public_key
-
- friend_public_key = property(get_friend_public_key)
-
- def get_chat_name(self):
- return self._chat_name
-
- chat_name = property(get_chat_name)
-
- def get_invite_data(self):
- return self._invite_data[:]
-
- invite_data = property(get_invite_data)
diff --git a/toxygen/groups/group_peer.py b/toxygen/groups/group_peer.py
deleted file mode 100644
index 4eaf255..0000000
--- a/toxygen/groups/group_peer.py
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-class GroupChatPeer:
- """
- Represents peer in group chat.
- """
-
- def __init__(self, peer_id, name, status, role, public_key, is_current_user=False, is_muted=False):
- self._peer_id = peer_id
- self._name = name
- self._status = status
- self._role = role
- self._public_key = public_key
- self._is_current_user = is_current_user
- self._is_muted = is_muted
-
- # -----------------------------------------------------------------------------------------------------------------
- # Readonly properties
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_id(self):
- return self._peer_id
-
- id = property(get_id)
-
- def get_public_key(self):
- return self._public_key
-
- public_key = property(get_public_key)
-
- def get_is_current_user(self):
- return self._is_current_user
-
- is_current_user = property(get_is_current_user)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Read-write properties
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_name(self):
- return self._name
-
- def set_name(self, name):
- self._name = name
-
- name = property(get_name, set_name)
-
- def get_status(self):
- return self._status
-
- def set_status(self, status):
- self._status = status
-
- status = property(get_status, set_status)
-
- def get_role(self):
- return self._role
-
- def set_role(self, role):
- self._role = role
-
- role = property(get_role, set_role)
-
- def get_is_muted(self):
- return self._is_muted
-
- def set_is_muted(self, is_muted):
- self._is_muted = is_muted
-
- is_muted = property(get_is_muted, set_is_muted)
diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py
deleted file mode 100644
index b8fc7cc..0000000
--- a/toxygen/groups/groups_service.py
+++ /dev/null
@@ -1,242 +0,0 @@
-import common.tox_save as tox_save
-import utils.ui as util_ui
-from groups.peers_list import PeersListGenerator
-from groups.group_invite import GroupInvite
-import wrapper.toxcore_enums_and_consts as constants
-
-
-class GroupsService(tox_save.ToxSave):
-
- def __init__(self, tox, contacts_manager, contacts_provider, main_screen, widgets_factory_provider):
- super().__init__(tox)
- self._contacts_manager = contacts_manager
- self._contacts_provider = contacts_provider
- self._main_screen = main_screen
- self._peers_list_widget = main_screen.peers_list
- self._widgets_factory_provider = widgets_factory_provider
- self._group_invites = []
- self._screen = None
-
- def set_tox(self, tox):
- super().set_tox(tox)
- for group in self._get_all_groups():
- group.set_tox(tox)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Groups creation
- # -----------------------------------------------------------------------------------------------------------------
-
- def create_new_gc(self, name, privacy_state, nick, status):
- group_number = self._tox.group_new(privacy_state, name, nick, status)
- if group_number == -1:
- return
-
- self._add_new_group_by_number(group_number)
- group = self._get_group_by_number(group_number)
- group.status = constants.TOX_USER_STATUS['NONE']
- self._contacts_manager.update_filtration()
-
- def join_gc_by_id(self, chat_id, password, nick, status):
- group_number = self._tox.group_join(chat_id, password, nick, status)
- self._add_new_group_by_number(group_number)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Groups reconnect and leaving
- # -----------------------------------------------------------------------------------------------------------------
-
- def leave_group(self, group_number):
- self._tox.group_leave(group_number)
- self._contacts_manager.delete_group(group_number)
-
- def disconnect_from_group(self, group_number):
- self._tox.group_disconnect(group_number)
- group = self._get_group_by_number(group_number)
- group.status = None
- self._clear_peers_list(group)
-
- def reconnect_to_group(self, group_number):
- self._tox.group_reconnect(group_number)
- group = self._get_group_by_number(group_number)
- group.status = constants.TOX_USER_STATUS['NONE']
- self._clear_peers_list(group)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Group invites
- # -----------------------------------------------------------------------------------------------------------------
-
- def invite_friend(self, friend_number, group_number):
- self._tox.group_invite_friend(group_number, friend_number)
-
- def process_group_invite(self, friend_number, group_name, invite_data):
- friend = self._get_friend_by_number(friend_number)
- invite = GroupInvite(friend.tox_id, group_name, invite_data)
- self._group_invites.append(invite)
- self._update_invites_button_state()
-
- def accept_group_invite(self, invite, name, status, password):
- pk = invite.friend_public_key
- friend = self._get_friend_by_public_key(pk)
- self._join_gc_via_invite(invite.invite_data, friend.number, name, status, password)
- self._delete_group_invite(invite)
- self._update_invites_button_state()
-
- def decline_group_invite(self, invite):
- self._delete_group_invite(invite)
- self._main_screen.update_gc_invites_button_state()
-
- def get_group_invites(self):
- return self._group_invites[:]
-
- group_invites = property(get_group_invites)
-
- def get_group_invites_count(self):
- return len(self._group_invites)
-
- group_invites_count = property(get_group_invites_count)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Group info methods
- # -----------------------------------------------------------------------------------------------------------------
-
- def update_group_info(self, group):
- group.name = self._tox.group_get_name(group.number)
- group.status_message = self._tox.group_get_topic(group.number)
-
- def set_group_topic(self, group):
- if not group.is_self_moderator_or_founder():
- return
- text = util_ui.tr('New topic for group "{}":'.format(group.name))
- title = util_ui.tr('Set group topic')
- topic, ok = util_ui.text_dialog(text, title, group.status_message)
- if not ok or not topic:
- return
- self._tox.group_set_topic(group.number, topic)
- group.status_message = topic
-
- def show_group_management_screen(self, group):
- widgets_factory = self._get_widgets_factory()
- self._screen = widgets_factory.create_group_management_screen(group)
- self._screen.show()
-
- def show_group_settings_screen(self, group):
- widgets_factory = self._get_widgets_factory()
- self._screen = widgets_factory.create_group_settings_screen(group)
- self._screen.show()
-
- def set_group_password(self, group, password):
- if group.password == password:
- return
- self._tox.group_founder_set_password(group.number, password)
- group.password = password
-
- def set_group_peers_limit(self, group, peers_limit):
- if group.peers_limit == peers_limit:
- return
- self._tox.group_founder_set_peer_limit(group.number, peers_limit)
- group.peers_limit = peers_limit
-
- def set_group_privacy_state(self, group, privacy_state):
- is_private = privacy_state == constants.TOX_GROUP_PRIVACY_STATE['PRIVATE']
- if group.is_private == is_private:
- return
- self._tox.group_founder_set_privacy_state(group.number, privacy_state)
- group.is_private = is_private
-
- # -----------------------------------------------------------------------------------------------------------------
- # Peers list
- # -----------------------------------------------------------------------------------------------------------------
-
- def generate_peers_list(self):
- if not self._contacts_manager.is_active_a_group():
- return
- group = self._contacts_manager.get_curr_contact()
- PeersListGenerator().generate(group.peers, self, self._peers_list_widget, group.tox_id)
-
- def peer_selected(self, chat_id, peer_id):
- widgets_factory = self._get_widgets_factory()
- group = self._get_group_by_public_key(chat_id)
- self_peer = group.get_self_peer()
- if self_peer.id != peer_id:
- self._screen = widgets_factory.create_peer_screen_window(group, peer_id)
- else:
- self._screen = widgets_factory.create_self_peer_screen_window(group)
- self._screen.show()
-
- # -----------------------------------------------------------------------------------------------------------------
- # Peers actions
- # -----------------------------------------------------------------------------------------------------------------
-
- def set_new_peer_role(self, group, peer, role):
- self._tox.group_mod_set_role(group.number, peer.id, role)
- peer.role = role
- self.generate_peers_list()
-
- def toggle_ignore_peer(self, group, peer, ignore):
- self._tox.group_toggle_ignore(group.number, peer.id, ignore)
- peer.is_muted = ignore
-
- def set_self_info(self, group, name, status):
- self._tox.group_self_set_name(group.number, name)
- self._tox.group_self_set_status(group.number, status)
- self_peer = group.get_self_peer()
- self_peer.name = name
- self_peer.status = status
- self.generate_peers_list()
-
- # -----------------------------------------------------------------------------------------------------------------
- # Bans support
- # -----------------------------------------------------------------------------------------------------------------
-
- def show_bans_list(self, group):
- widgets_factory = self._get_widgets_factory()
- self._screen = widgets_factory.create_groups_bans_screen(group)
- self._screen.show()
-
- def ban_peer(self, group, peer_id, ban_type):
- self._tox.group_mod_ban_peer(group.number, peer_id, ban_type)
-
- def kick_peer(self, group, peer_id):
- self._tox.group_mod_remove_peer(group.number, peer_id)
-
- def cancel_ban(self, group_number, ban_id):
- self._tox.group_mod_remove_ban(group_number, ban_id)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Private methods
- # -----------------------------------------------------------------------------------------------------------------
-
- def _add_new_group_by_number(self, group_number):
- self._contacts_manager.add_group(group_number)
-
- def _get_group_by_number(self, group_number):
- return self._contacts_provider.get_group_by_number(group_number)
-
- def _get_group_by_public_key(self, public_key):
- return self._contacts_provider.get_group_by_public_key(public_key)
-
- def _get_all_groups(self):
- return self._contacts_provider.get_all_groups()
-
- def _get_friend_by_number(self, friend_number):
- return self._contacts_provider.get_friend_by_number(friend_number)
-
- def _get_friend_by_public_key(self, public_key):
- return self._contacts_provider.get_friend_by_public_key(public_key)
-
- def _clear_peers_list(self, group):
- group.remove_all_peers_except_self()
- self.generate_peers_list()
-
- def _delete_group_invite(self, invite):
- if invite in self._group_invites:
- self._group_invites.remove(invite)
-
- def _join_gc_via_invite(self, invite_data, friend_number, nick, status, password):
- group_number = self._tox.group_invite_accept(invite_data, friend_number, nick, status, password)
- self._add_new_group_by_number(group_number)
-
- def _update_invites_button_state(self):
- self._main_screen.update_gc_invites_button_state()
-
- def _get_widgets_factory(self):
- return self._widgets_factory_provider.get_item()
diff --git a/toxygen/groups/peers_list.py b/toxygen/groups/peers_list.py
deleted file mode 100644
index 17495f5..0000000
--- a/toxygen/groups/peers_list.py
+++ /dev/null
@@ -1,104 +0,0 @@
-from ui.group_peers_list import PeerItem, PeerTypeItem
-from wrapper.toxcore_enums_and_consts import *
-from ui.widgets import *
-
-
-# -----------------------------------------------------------------------------------------------------------------
-# Builder
-# -----------------------------------------------------------------------------------------------------------------
-
-
-class PeerListBuilder:
-
- def __init__(self):
- self._peers = {}
- self._titles = {}
- self._index = 0
- self._handler = None
-
- def with_click_handler(self, handler):
- self._handler = handler
-
- return self
-
- def with_title(self, title):
- self._titles[self._index] = title
- self._index += 1
-
- return self
-
- def with_peers(self, peers):
- for peer in peers:
- self._add_peer(peer)
-
- return self
-
- def build(self, list_widget):
- list_widget.clear()
-
- for i in range(self._index):
- if i in self._peers:
- peer = self._peers[i]
- self._add_peer_item(peer, list_widget)
- else:
- title = self._titles[i]
- self._add_peer_type_item(title, list_widget)
-
- def _add_peer_item(self, peer, parent):
- item = PeerItem(peer, self._handler, parent.width(), parent)
- self._add_item(parent, item)
-
- def _add_peer_type_item(self, text, parent):
- item = PeerTypeItem(text, parent.width(), parent)
- self._add_item(parent, item)
-
- @staticmethod
- def _add_item(parent, item):
- elem = QtWidgets.QListWidgetItem(parent)
- elem.setSizeHint(QtCore.QSize(parent.width(), item.height()))
- parent.addItem(elem)
- parent.setItemWidget(elem, item)
-
- def _add_peer(self, peer):
- self._peers[self._index] = peer
- self._index += 1
-
-# -----------------------------------------------------------------------------------------------------------------
-# Generators
-# -----------------------------------------------------------------------------------------------------------------
-
-
-class PeersListGenerator:
-
- @staticmethod
- def generate(peers_list, groups_service, list_widget, chat_id):
- admin_title = util_ui.tr('Administrator')
- moderators_title = util_ui.tr('Moderators')
- users_title = util_ui.tr('Users')
- observers_title = util_ui.tr('Observers')
-
- admins = list(filter(lambda p: p.role == TOX_GROUP_ROLE['FOUNDER'], peers_list))
- moderators = list(filter(lambda p: p.role == TOX_GROUP_ROLE['MODERATOR'], peers_list))
- users = list(filter(lambda p: p.role == TOX_GROUP_ROLE['USER'], peers_list))
- observers = list(filter(lambda p: p.role == TOX_GROUP_ROLE['OBSERVER'], peers_list))
-
- builder = (PeerListBuilder()
- .with_click_handler(lambda peer_id: groups_service.peer_selected(chat_id, peer_id)))
- if len(admins):
- (builder
- .with_title(admin_title)
- .with_peers(admins))
- if len(moderators):
- (builder
- .with_title(moderators_title)
- .with_peers(moderators))
- if len(users):
- (builder
- .with_title(users_title)
- .with_peers(users))
- if len(observers):
- (builder
- .with_title(observers_title)
- .with_peers(observers))
-
- builder.build(list_widget)
diff --git a/toxygen/history.py b/toxygen/history.py
new file mode 100644
index 0000000..ad18ee5
--- /dev/null
+++ b/toxygen/history.py
@@ -0,0 +1,182 @@
+# coding=utf-8
+from sqlite3 import connect
+import settings
+from os import chdir
+import os.path
+from toxencryptsave import ToxEncryptSave
+
+
+PAGE_SIZE = 42
+
+MESSAGE_OWNER = {
+ 'ME': 0,
+ 'FRIEND': 1,
+ 'NOT_SENT': 2
+}
+
+
+class History:
+
+ def __init__(self, name):
+ self._name = name
+ chdir(settings.ProfileHelper.get_path())
+ path = settings.ProfileHelper.get_path() + self._name + '.hstr'
+ if os.path.exists(path):
+ decr = ToxEncryptSave.get_instance()
+ try:
+ with open(path, 'rb') as fin:
+ data = fin.read()
+ if decr.is_data_encrypted(data):
+ data = decr.pass_decrypt(data)
+ with open(path, 'wb') as fout:
+ fout.write(data)
+ except:
+ os.remove(path)
+ db = connect(name + '.hstr')
+ cursor = db.cursor()
+ cursor.execute('CREATE TABLE IF NOT EXISTS friends('
+ ' tox_id TEXT PRIMARY KEY'
+ ')')
+ db.close()
+
+ def save(self):
+ encr = ToxEncryptSave.get_instance()
+ if encr.has_password():
+ path = settings.ProfileHelper.get_path() + self._name + '.hstr'
+ with open(path, 'rb') as fin:
+ data = fin.read()
+ data = encr.pass_encrypt(bytes(data))
+ with open(path, 'wb') as fout:
+ fout.write(data)
+
+ def export(self, directory):
+ path = settings.ProfileHelper.get_path() + self._name + '.hstr'
+ new_path = directory + self._name + '.hstr'
+ with open(path, 'rb') as fin:
+ data = fin.read()
+ encr = ToxEncryptSave.get_instance()
+ if encr.has_password():
+ data = encr.pass_encrypt(data)
+ with open(new_path, 'wb') as fout:
+ fout.write(data)
+
+ def add_friend_to_db(self, tox_id):
+ chdir(settings.ProfileHelper.get_path())
+ db = connect(self._name + '.hstr')
+ try:
+ cursor = db.cursor()
+ cursor.execute('INSERT INTO friends VALUES (?);', (tox_id, ))
+ cursor.execute('CREATE TABLE id' + tox_id + '('
+ ' id INTEGER PRIMARY KEY,'
+ ' message TEXT,'
+ ' owner INTEGER,'
+ ' unix_time REAL,'
+ ' message_type INTEGER'
+ ')')
+ db.commit()
+ except:
+ db.rollback()
+ raise
+ finally:
+ db.close()
+
+ def delete_friend_from_db(self, tox_id):
+ chdir(settings.ProfileHelper.get_path())
+ db = connect(self._name + '.hstr')
+ try:
+ cursor = db.cursor()
+ cursor.execute('DELETE FROM friends WHERE tox_id=?;', (tox_id, ))
+ cursor.execute('DROP TABLE id' + tox_id + ';')
+ db.commit()
+ except:
+ db.rollback()
+ raise
+ finally:
+ db.close()
+
+ def friend_exists_in_db(self, tox_id):
+ chdir(settings.ProfileHelper.get_path())
+ db = connect(self._name + '.hstr')
+ cursor = db.cursor()
+ cursor.execute('SELECT 0 FROM friends WHERE tox_id=?', (tox_id, ))
+ result = cursor.fetchone()
+ db.close()
+ return result is not None
+
+ def save_messages_to_db(self, tox_id, messages_iter):
+ chdir(settings.ProfileHelper.get_path())
+ db = connect(self._name + '.hstr')
+ try:
+ cursor = db.cursor()
+ cursor.executemany('INSERT INTO id' + tox_id + '(message, owner, unix_time, message_type) '
+ 'VALUES (?, ?, ?, ?);', messages_iter)
+ db.commit()
+ except:
+ db.rollback()
+ raise
+ finally:
+ db.close()
+
+ def update_messages(self, tox_id, unsent_time):
+ chdir(settings.ProfileHelper.get_path())
+ db = connect(self._name + '.hstr')
+ try:
+ cursor = db.cursor()
+ cursor.execute('UPDATE id' + tox_id + ' SET owner = 0 '
+ 'WHERE unix_time < ' + str(unsent_time) + ' AND owner = 2;')
+ db.commit()
+ except:
+ db.rollback()
+ raise
+ finally:
+ db.close()
+ pass
+
+ def delete_message(self, tox_id, time):
+ chdir(settings.ProfileHelper.get_path())
+ db = connect(self._name + '.hstr')
+ try:
+ cursor = db.cursor()
+ cursor.execute('DELETE FROM id' + tox_id + ' WHERE unix_time = ' + str(time) + ';')
+ db.commit()
+ except:
+ db.rollback()
+ raise
+ finally:
+ db.close()
+
+ def delete_messages(self, tox_id):
+ chdir(settings.ProfileHelper.get_path())
+ db = connect(self._name + '.hstr')
+ try:
+ cursor = db.cursor()
+ cursor.execute('DELETE FROM id' + tox_id + ';')
+ db.commit()
+ except:
+ db.rollback()
+ raise
+ finally:
+ db.close()
+
+ def messages_getter(self, tox_id):
+ return History.MessageGetter(self._name, tox_id)
+
+ class MessageGetter:
+ def __init__(self, name, tox_id):
+ chdir(settings.ProfileHelper.get_path())
+ self._db = connect(name + '.hstr')
+ self._cursor = self._db.cursor()
+ self._cursor.execute('SELECT message, owner, unix_time, message_type FROM id' + tox_id +
+ ' ORDER BY unix_time DESC;')
+
+ def get_one(self):
+ return self._cursor.fetchone()
+
+ def get_all(self):
+ return self._cursor.fetchall()
+
+ def get(self, count):
+ return self._cursor.fetchmany(count)
+
+ def __del__(self):
+ self._db.close()
diff --git a/toxygen/history/__init__.py b/toxygen/history/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/toxygen/history/database.py b/toxygen/history/database.py
deleted file mode 100644
index 751c74b..0000000
--- a/toxygen/history/database.py
+++ /dev/null
@@ -1,201 +0,0 @@
-from sqlite3 import connect
-import os.path
-import utils.util as util
-
-
-TIMEOUT = 11
-
-SAVE_MESSAGES = 500
-
-MESSAGE_AUTHOR = {
- 'ME': 0,
- 'FRIEND': 1,
- 'NOT_SENT': 2,
- 'GC_PEER': 3
-}
-
-CONTACT_TYPE = {
- 'FRIEND': 0,
- 'GC_PEER': 1,
- 'GC_PEER_PRIVATE': 2
-}
-
-
-class Database:
-
- def __init__(self, path, toxes):
- self._path, self._toxes = path, toxes
- self._name = os.path.basename(path)
- if os.path.exists(path):
- try:
- with open(path, 'rb') as fin:
- data = fin.read()
- if toxes.is_data_encrypted(data):
- data = toxes.pass_decrypt(data)
- with open(path, 'wb') as fout:
- fout.write(data)
- except Exception as ex:
- util.log('Db reading error: ' + str(ex))
- os.remove(path)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Public methods
- # -----------------------------------------------------------------------------------------------------------------
-
- def save(self):
- if self._toxes.has_password():
- with open(self._path, 'rb') as fin:
- data = fin.read()
- data = self._toxes.pass_encrypt(bytes(data))
- with open(self._path, 'wb') as fout:
- fout.write(data)
-
- def export(self, directory):
- new_path = util.join_path(directory, self._name)
- with open(self._path, 'rb') as fin:
- data = fin.read()
- if self._toxes.has_password():
- data = self._toxes.pass_encrypt(data)
- with open(new_path, 'wb') as fout:
- fout.write(data)
-
- def add_friend_to_db(self, tox_id):
- db = self._connect()
- try:
- cursor = db.cursor()
- cursor.execute('CREATE TABLE IF NOT EXISTS id' + tox_id + '('
- ' id INTEGER PRIMARY KEY,'
- ' author_name TEXT,'
- ' message TEXT,'
- ' author_type INTEGER,'
- ' unix_time REAL,'
- ' message_type INTEGER'
- ')')
- db.commit()
- except:
- print('Database is locked!')
- db.rollback()
- finally:
- db.close()
-
- def delete_friend_from_db(self, tox_id):
- db = self._connect()
- try:
- cursor = db.cursor()
- cursor.execute('DROP TABLE id' + tox_id + ';')
- db.commit()
- except:
- print('Database is locked!')
- db.rollback()
- finally:
- db.close()
-
- def save_messages_to_db(self, tox_id, messages_iter):
- db = self._connect()
- try:
- cursor = db.cursor()
- cursor.executemany('INSERT INTO id' + tox_id +
- '(message, author_name, author_type, unix_time, message_type) ' +
- 'VALUES (?, ?, ?, ?, ?, ?);', messages_iter)
- db.commit()
- except:
- print('Database is locked!')
- db.rollback()
- finally:
- db.close()
-
- def update_messages(self, tox_id, message_id):
- db = self._connect()
- try:
- cursor = db.cursor()
- cursor.execute('UPDATE id' + tox_id + ' SET author = 0 '
- 'WHERE id = ' + str(message_id) + ' AND author = 2;')
- db.commit()
- except:
- print('Database is locked!')
- db.rollback()
- finally:
- db.close()
-
- def delete_message(self, tox_id, unique_id):
- db = self._connect()
- try:
- cursor = db.cursor()
- cursor.execute('DELETE FROM id' + tox_id + ' WHERE id = ' + str(unique_id) + ';')
- db.commit()
- except:
- print('Database is locked!')
- db.rollback()
- finally:
- db.close()
-
- def delete_messages(self, tox_id):
- db = self._connect()
- try:
- cursor = db.cursor()
- cursor.execute('DELETE FROM id' + tox_id + ';')
- db.commit()
- except:
- print('Database is locked!')
- db.rollback()
- finally:
- db.close()
-
- def messages_getter(self, tox_id):
- self.add_friend_to_db(tox_id)
-
- return Database.MessageGetter(self._path, tox_id)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Messages loading
- # -----------------------------------------------------------------------------------------------------------------
-
- class MessageGetter:
-
- def __init__(self, path, tox_id):
- self._count = 0
- self._path = path
- self._tox_id = tox_id
- self._db = self._cursor = None
-
- def get_one(self):
- return self.get(1)
-
- def get_all(self):
- self._connect()
- data = self._cursor.fetchall()
- self._disconnect()
- self._count = len(data)
- return data
-
- def get(self, count):
- self._connect()
- self.skip()
- data = self._cursor.fetchmany(count)
- self._disconnect()
- self._count += len(data)
- return data
-
- def skip(self):
- if self._count:
- self._cursor.fetchmany(self._count)
-
- def delete_one(self):
- if self._count:
- self._count -= 1
-
- def _connect(self):
- self._db = connect(self._path, timeout=TIMEOUT)
- self._cursor = self._db.cursor()
- self._cursor.execute('SELECT message, author_type, author_name, unix_time, message_type, id FROM id' +
- self._tox_id + ' ORDER BY unix_time DESC;')
-
- def _disconnect(self):
- self._db.close()
-
- # -----------------------------------------------------------------------------------------------------------------
- # Private methods
- # -----------------------------------------------------------------------------------------------------------------
-
- def _connect(self):
- return connect(self._path, timeout=TIMEOUT)
diff --git a/toxygen/history/history.py b/toxygen/history/history.py
deleted file mode 100644
index bd7e353..0000000
--- a/toxygen/history/history.py
+++ /dev/null
@@ -1,138 +0,0 @@
-from history.history_logs_generators import *
-
-
-class History:
-
- def __init__(self, contact_provider, db, settings, main_screen, messages_items_factory):
- self._contact_provider = contact_provider
- self._db = db
- self._settings = settings
- self._messages = main_screen.messages
- self._messages_items_factory = messages_items_factory
- self._is_loading = False
- self._contacts_manager = None
-
- def __del__(self):
- del self._db
-
- def set_contacts_manager(self, contacts_manager):
- self._contacts_manager = contacts_manager
-
- # -----------------------------------------------------------------------------------------------------------------
- # History support
- # -----------------------------------------------------------------------------------------------------------------
-
- def save_history(self):
- """
- Save history to db
- """
- if self._settings['save_db']:
- for friend in self._contact_provider.get_all_friends():
- self._db.add_friend_to_db(friend.tox_id)
- if not self._settings['save_unsent_only']:
- messages = friend.get_corr_for_saving()
- else:
- messages = friend.get_unsent_messages_for_saving()
- self._db.delete_messages(friend.tox_id)
- messages = map(lambda m: (m.text, m.author.name, m.author.type, m.time, m.type), messages)
- self._db.save_messages_to_db(friend.tox_id, messages)
-
- self._db.save()
-
- def clear_history(self, friend, save_unsent=False):
- """
- Clear chat history
- """
- friend.clear_corr(save_unsent)
- self._db.delete_friend_from_db(friend.tox_id)
-
- def export_history(self, contact, as_text=True):
- extension = 'txt' if as_text else 'html'
- file_name, _ = util_ui.save_file_dialog(util_ui.tr('Choose file name'), extension)
-
- if not file_name:
- return
-
- if not file_name.endswith('.' + extension):
- file_name += '.' + extension
-
- history = self.generate_history(contact, as_text)
- with open(file_name, 'wt') as fl:
- fl.write(history)
-
- def delete_message(self, message):
- contact = self._contacts_manager.get_curr_contact()
- if message.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']):
- if message.is_saved():
- self._db.delete_message(contact.tox_id, message.id)
- contact.delete_message(message.message_id)
-
- def load_history(self, friend):
- """
- Tries to load next part of messages
- """
- if self._is_loading:
- return
- self._is_loading = True
- friend.load_corr(False)
- messages = friend.get_corr()
- if not messages:
- self._is_loading = False
- return
- messages.reverse()
- messages = messages[self._messages.count():self._messages.count() + PAGE_SIZE]
- for message in messages:
- message_type = message.get_type()
- if message_type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']): # text message
- self._create_message_item(message)
- elif message_type == MESSAGE_TYPE['FILE_TRANSFER']: # file transfer
- if message.state == FILE_TRANSFER_STATE['UNSENT']:
- self._create_unsent_file_item(message)
- else:
- self._create_file_transfer_item(message)
- elif message_type == MESSAGE_TYPE['INLINE']: # inline image
- self._create_inline_item(message)
- else: # info message
- self._create_message_item(message)
- self._is_loading = False
-
- def get_message_getter(self, friend_public_key):
- self._db.add_friend_to_db(friend_public_key)
-
- return self._db.messages_getter(friend_public_key)
-
- def delete_history(self, friend):
- self._db.delete_friend_from_db(friend.tox_id)
-
- def add_friend_to_db(self, tox_id):
- self._db.add_friend_to_db(tox_id)
-
- @staticmethod
- def generate_history(contact, as_text=True, _range=None):
- if _range is None:
- contact.load_all_corr()
- corr = contact.get_corr()
- elif _range[1] + 1:
- corr = contact.get_corr()[_range[0]:_range[1] + 1]
- else:
- corr = contact.get_corr()[_range[0]:]
-
- generator = TextHistoryGenerator(corr, contact.name) if as_text else HtmlHistoryGenerator(corr, contact.name)
-
- return generator.generate()
-
- # -----------------------------------------------------------------------------------------------------------------
- # Items creation
- # -----------------------------------------------------------------------------------------------------------------
-
- def _create_message_item(self, message):
- return self._messages_items_factory.create_message_item(message, False)
-
- def _create_unsent_file_item(self, message):
- return self._messages_items_factory.create_unsent_file_item(message, False)
-
- def _create_file_transfer_item(self, message):
- return self._messages_items_factory.create_file_transfer_item(message, False)
-
- def _create_inline_item(self, message):
- return self._messages_items_factory.create_inline_item(message, False)
diff --git a/toxygen/history/history_logs_generators.py b/toxygen/history/history_logs_generators.py
deleted file mode 100644
index b8d0a56..0000000
--- a/toxygen/history/history_logs_generators.py
+++ /dev/null
@@ -1,48 +0,0 @@
-from messenger.messages import *
-import utils.util as util
-
-
-class HistoryLogsGenerator:
-
- def __init__(self, history, contact_name):
- self._history = history
- self._contact_name = contact_name
-
- def generate(self):
- return str()
-
- @staticmethod
- def _get_message_time(message):
- return util.convert_time(message.time) if message.author.type != MESSAGE_AUTHOR['NOT_SENT'] else 'Unsent'
-
-
-class HtmlHistoryGenerator(HistoryLogsGenerator):
-
- def __init__(self, history, contact_name):
- super().__init__(history, contact_name)
-
- def generate(self):
- arr = []
- for message in self._history:
- if type(message) is TextMessage:
- x = '[{}] {}: {}
'
- arr.append(x.format(self._get_message_time(message), message.author.name, message.text))
- s = '
'.join(arr)
- html = '
{}{}'
-
- return html.format(self._contact_name, s)
-
-
-class TextHistoryGenerator(HistoryLogsGenerator):
-
- def __init__(self, history, contact_name):
- super().__init__(history, contact_name)
-
- def generate(self):
- arr = [self._contact_name]
- for message in self._history:
- if type(message) is TextMessage:
- x = '[{}] {}: {}\n'
- arr.append(x.format(self._get_message_time(message), message.author.name, message.text))
-
- return '\n'.join(arr)
diff --git a/toxygen/images/incoming_call_video.png b/toxygen/images/audio_message.png
similarity index 63%
rename from toxygen/images/incoming_call_video.png
rename to toxygen/images/audio_message.png
index 4fe4c98..22ba2a0 100755
Binary files a/toxygen/images/incoming_call_video.png and b/toxygen/images/audio_message.png differ
diff --git a/toxygen/images/avatar.png b/toxygen/images/avatar.png
index 83ac757..91d1200 100755
Binary files a/toxygen/images/avatar.png and b/toxygen/images/avatar.png differ
diff --git a/toxygen/images/group.png b/toxygen/images/group.png
deleted file mode 100644
index 22adab0..0000000
Binary files a/toxygen/images/group.png and /dev/null differ
diff --git a/toxygen/images/finish_call_video.png b/toxygen/images/video_message.png
similarity index 73%
rename from toxygen/images/finish_call_video.png
rename to toxygen/images/video_message.png
index 8465106..37603ce 100755
Binary files a/toxygen/images/finish_call_video.png and b/toxygen/images/video_message.png differ
diff --git a/toxygen/images/call_video.png b/toxygen/images/videocall.png
similarity index 100%
rename from toxygen/images/call_video.png
rename to toxygen/images/videocall.png
diff --git a/toxygen/libtox.py b/toxygen/libtox.py
new file mode 100644
index 0000000..1c30eee
--- /dev/null
+++ b/toxygen/libtox.py
@@ -0,0 +1,53 @@
+from platform import system
+from ctypes import CDLL
+import util
+
+
+class LibToxCore:
+
+ def __init__(self):
+ if system() == 'Windows':
+ self._libtoxcore = CDLL(util.curr_directory() + '/libs/libtox.dll')
+ else:
+ # 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)
+
+
+class LibToxAV:
+
+ def __init__(self):
+ if system() == 'Windows':
+ # on Windows av api is in libtox.dll
+ self._libtoxav = CDLL(util.curr_directory() + '/libs/libtox.dll')
+ else:
+ # /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)
+
+
+class LibToxEncryptSave:
+
+ def __init__(self):
+ if system() == 'Windows':
+ # on Windows profile encryption api is in libtox.dll
+ self._lib_tox_encrypt_save = CDLL(util.curr_directory() + '/libs/libtox.dll')
+ else:
+ # /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)
diff --git a/toxygen/list_items.py b/toxygen/list_items.py
new file mode 100644
index 0000000..81469f7
--- /dev/null
+++ b/toxygen/list_items.py
@@ -0,0 +1,529 @@
+from toxcore_enums_and_consts import *
+try:
+ from PySide import QtCore, QtGui
+except ImportError:
+ from PyQt4 import QtCore, QtGui
+ QtCore.Slot = QtCore.pyqtSlot
+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
+from widgets import DataLabel, create_menu
+import html as h
+import smileys
+import settings
+
+
+class MessageEdit(QtGui.QTextBrowser):
+
+ def __init__(self, text, width, message_type, parent=None):
+ super(MessageEdit, self).__init__(parent)
+ self.urls = {}
+ self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+ self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+ self.setWordWrapMode(QtGui.QTextOption.WrapAtWordBoundaryOrAnywhere)
+ self.document().setTextWidth(width)
+ self.setOpenExternalLinks(True)
+ self.setAcceptRichText(True)
+ self.setOpenLinks(False)
+ self.setSearchPaths([smileys.SmileyLoader.get_instance().get_smileys_path()])
+ self.document().setDefaultStyleSheet('a { color: #306EFF; }')
+ text = self.decoratedText(text)
+ if message_type != TOX_MESSAGE_TYPE['NORMAL']:
+ self.setHtml('' + text + '
')
+ else:
+ self.setHtml(text)
+ font = QtGui.QFont()
+ font.setFamily(settings.Settings.get_instance()['font'])
+ font.setPixelSize(settings.Settings.get_instance()['message_font_size'])
+ font.setBold(False)
+ self.setFont(font)
+ self.resize(width, self.document().size().height())
+ self.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse | QtCore.Qt.LinksAccessibleByMouse)
+ self.anchorClicked.connect(self.on_anchor_clicked)
+
+ def contextMenuEvent(self, event):
+ menu = create_menu(self.createStandardContextMenu(event.pos()))
+ quote = menu.addAction(QtGui.QApplication.translate("MainWindow", 'Quote selected text', None, QtGui.QApplication.UnicodeUTF8))
+ 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(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8))
+ 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:'):
+ import menu
+ self.add_contact = menu.AddContact(text[4:])
+ self.add_contact.show()
+ else:
+ QtGui.QDesktopServices.openUrl(url)
+ self.clearFocus()
+
+ def addAnimation(self, url, fileName):
+ movie = QtGui.QMovie(self)
+ movie.setFileName(fileName)
+ self.urls[movie] = url
+ movie.frameChanged[int].connect(lambda x: self.animate(movie))
+ movie.start()
+
+ def animate(self, movie):
+ self.document().addResource(QtGui.QTextDocument.ImageResource,
+ self.urls[movie],
+ movie.currentPixmap())
+ self.setLineWrapColumnOrWidth(self.lineWrapColumnOrWidth())
+
+ def decoratedText(self, text):
+ text = h.escape(text) # replace < and >
+ exp = QtCore.QRegExp(
+ '('
+ '(?:\\b)((www\\.)|(http[s]?|ftp)://)'
+ '\\w+\\S+)'
+ '|(?:\\b)(file:///)([\\S| ]*)'
+ '|(?:\\b)(tox:[a-zA-Z\\d]{76}$)'
+ '|(?:\\b)(mailto:\\S+@\\S+\\.\\S+)'
+ '|(?:\\b)(tox:\\S+@\\S+)')
+ offset = exp.indexIn(text, 0)
+ while offset != -1: # add links
+ url = exp.cap()
+ if exp.cap(2) == 'www.':
+ html = '{0}'.format(url)
+ else:
+ html = '{0}'.format(url)
+ text = text[:offset] + html + text[offset + len(exp.cap()):]
+ offset += len(html)
+ offset = exp.indexIn(text, offset)
+ arr = text.split('\n')
+ for i in range(len(arr)): # quotes
+ if arr[i].startswith('>'):
+ arr[i] = '' + arr[i][4:] + ''
+ text = '
'.join(arr)
+ text = smileys.SmileyLoader.get_instance().add_smileys_to_text(text, self) # smileys
+ return text
+
+
+class MessageItem(QtGui.QWidget):
+ """
+ Message in messages list
+ """
+ def __init__(self, text, time, user='', sent=True, message_type=TOX_MESSAGE_TYPE['NORMAL'], parent=None):
+ QtGui.QWidget.__init__(self, parent)
+ self.name = DataLabel(self)
+ self.name.setGeometry(QtCore.QRect(2, 2, 95, 20))
+ self.name.setTextFormat(QtCore.Qt.PlainText)
+ font = QtGui.QFont()
+ 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.setPointSize(10)
+ font.setBold(False)
+ self.time.setFont(font)
+ self._time = time
+ if not sent:
+ movie = QtGui.QMovie(curr_directory() + '/images/spinner.gif')
+ self.time.setMovie(movie)
+ movie.start()
+ self.t = True
+ else:
+ self.time.setText(convert_time(time))
+ self.t = False
+
+ self.message = MessageEdit(text, parent.width() - 150, message_type, self)
+ if message_type != TOX_MESSAGE_TYPE['NORMAL']:
+ self.name.setStyleSheet("QLabel { color: #5CB3FF; }")
+ self.message.setAlignment(QtCore.Qt.AlignCenter)
+ self.time.setStyleSheet("QLabel { color: #5CB3FF; }")
+ self.message.setGeometry(QtCore.QRect(100, 0, parent.width() - 150, self.message.height()))
+ self.setFixedHeight(self.message.height())
+
+ def mouseReleaseEvent(self, event):
+ if event.button() == QtCore.Qt.RightButton and event.x() > self.time.x():
+ self.listMenu = QtGui.QMenu()
+ delete_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Delete message', None, QtGui.QApplication.UnicodeUTF8))
+ self.connect(delete_item, QtCore.SIGNAL("triggered()"), self.delete)
+ parent_position = self.time.mapToGlobal(QtCore.QPoint(0, 0))
+ self.listMenu.move(parent_position)
+ self.listMenu.show()
+
+ def delete(self):
+ pr = profile.Profile.get_instance()
+ pr.delete_message(self._time)
+
+ def mark_as_sent(self):
+ if self.t:
+ self.time.setText(convert_time(self._time))
+ self.t = False
+ return True
+ return False
+
+ def set_avatar(self, pixmap):
+ self.name.setAlignment(QtCore.Qt.AlignCenter)
+ self.message.setAlignment(QtCore.Qt.AlignVCenter)
+ 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, QtCore.Qt.SmoothTransformation))
+
+
+class ContactItem(QtGui.QWidget):
+ """
+ Contact in friends list
+ """
+
+ def __init__(self, parent=None):
+ QtGui.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)
+ size = 32 if mode else 64
+ self.avatar_label.setGeometry(QtCore.QRect(3, 4, size, size))
+ 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(settings.Settings.get_instance()['font'])
+ font.setPointSize(10 if mode else 12)
+ font.setBold(True)
+ self.name.setFont(font)
+ self.status_message = DataLabel(self)
+ self.status_message.setGeometry(QtCore.QRect(50 if mode else 75, 20 if mode else 30, 170, 15 if mode else 20))
+ font.setPointSize(10)
+ font.setBold(False)
+ self.status_message.setFont(font)
+ self.connection_status = StatusCircle(self)
+ self.connection_status.setGeometry(QtCore.QRect(230, -2 if mode else 5, 32, 32))
+ self.messages = UnreadMessagesCount(self)
+ self.messages.setGeometry(QtCore.QRect(20 if mode else 52, 20 if mode else 50, 30, 20))
+
+
+class StatusCircle(QtGui.QWidget):
+ """
+ Connection status
+ """
+ def __init__(self, parent):
+ QtGui.QWidget.__init__(self, parent)
+ self.setGeometry(0, 0, 32, 32)
+ self.label = QtGui.QLabel(self)
+ self.label.setGeometry(QtCore.QRect(0, 0, 32, 32))
+ self.unread = False
+
+ def update(self, status, unread_messages=None):
+ if unread_messages is None:
+ unread_messages = self.unread
+ else:
+ self.unread = unread_messages
+ if status == TOX_USER_STATUS['NONE']:
+ name = 'online'
+ elif status == TOX_USER_STATUS['AWAY']:
+ name = 'idle'
+ elif status == TOX_USER_STATUS['BUSY']:
+ name = 'busy'
+ else:
+ name = 'offline'
+ if unread_messages:
+ name += '_notification'
+ self.label.setGeometry(QtCore.QRect(0, 0, 32, 32))
+ else:
+ self.label.setGeometry(QtCore.QRect(2, 0, 32, 32))
+ pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(name))
+ self.label.setPixmap(pixmap)
+
+
+class UnreadMessagesCount(QtGui.QWidget):
+
+ def __init__(self, parent=None):
+ super(UnreadMessagesCount, self).__init__(parent)
+ self.resize(30, 20)
+ self.label = QtGui.QLabel(self)
+ self.label.setGeometry(QtCore.QRect(0, 0, 30, 20))
+ self.label.setVisible(False)
+ font = QtGui.QFont()
+ font.setFamily(settings.Settings.get_instance()['font'])
+ font.setPointSize(12)
+ font.setBold(True)
+ self.label.setFont(font)
+ self.label.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignCenter)
+ color = settings.Settings.get_instance()['unread_color']
+ self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }')
+
+ def update(self, messages_count):
+ color = settings.Settings.get_instance()['unread_color']
+ self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }')
+ if messages_count:
+ self.label.setVisible(True)
+ self.label.setText(str(messages_count))
+ else:
+ self.label.setVisible(False)
+
+
+class FileTransferItem(QtGui.QListWidget):
+
+ def __init__(self, file_name, size, time, user, friend_number, file_number, state, width, parent=None):
+
+ QtGui.QListWidget.__init__(self, parent)
+ self.resize(QtCore.QSize(width, 34))
+ if state == TOX_FILE_TRANSFER_STATE['CANCELLED']:
+ self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
+ elif state in PAUSED_FILE_TRANSFERS:
+ self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
+ else:
+ self.setStyleSheet('QListWidget { border: 1px solid green; }')
+ self.state = state
+
+ self.name = DataLabel(self)
+ self.name.setGeometry(QtCore.QRect(3, 7, 95, 20))
+ self.name.setTextFormat(QtCore.Qt.PlainText)
+ font = QtGui.QFont()
+ 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))
+ 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))
+ pixmap = QtGui.QPixmap(curr_directory() + '/images/decline.png')
+ icon = QtGui.QIcon(pixmap)
+ self.cancel.setIcon(icon)
+ self.cancel.setIconSize(QtCore.QSize(30, 30))
+ self.cancel.setVisible(state in ACTIVE_FILE_TRANSFERS)
+ self.cancel.clicked.connect(lambda: self.cancel_transfer(friend_number, file_number))
+ self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}')
+
+ self.accept_or_pause = QtGui.QPushButton(self)
+ self.accept_or_pause.setGeometry(QtCore.QRect(width - 170, 2, 30, 30))
+ if state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
+ self.accept_or_pause.setVisible(True)
+ self.button_update('accept')
+ elif state in DO_NOT_SHOW_ACCEPT_BUTTON:
+ self.accept_or_pause.setVisible(False)
+ elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: # setup for continue
+ self.accept_or_pause.setVisible(True)
+ self.button_update('resume')
+ else: # pause
+ self.accept_or_pause.setVisible(True)
+ self.button_update('pause')
+ self.accept_or_pause.clicked.connect(lambda: self.accept_or_pause_transfer(friend_number, file_number, size))
+
+ self.accept_or_pause.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none}')
+
+ self.pb = QtGui.QProgressBar(self)
+ self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20))
+ self.pb.setValue(0)
+ self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }')
+ self.pb.setVisible(state in SHOW_PROGRESS_BAR)
+
+ self.file_name = DataLabel(self)
+ self.file_name.setGeometry(QtCore.QRect(210, 7, width - 420, 20))
+ font.setPointSize(12)
+ self.file_name.setFont(font)
+ file_size = size // 1024
+ if not file_size:
+ file_size = '{}B'.format(size)
+ elif file_size >= 1024:
+ file_size = '{}MB'.format(file_size // 1024)
+ else:
+ file_size = '{}KB'.format(file_size)
+ file_data = '{} {}'.format(file_size, file_name)
+ self.file_name.setText(file_data)
+ self.file_name.setToolTip(file_name)
+ self.saved_name = file_name
+ self.time_left = QtGui.QLabel(self)
+ self.time_left.setGeometry(QtCore.QRect(width - 87, 7, 30, 20))
+ font.setPointSize(10)
+ self.time_left.setFont(font)
+ self.time_left.setVisible(state == TOX_FILE_TRANSFER_STATE['RUNNING'])
+ self.setFocusPolicy(QtCore.Qt.NoFocus)
+ self.paused = False
+
+ def cancel_transfer(self, friend_number, file_number):
+ pr = profile.Profile.get_instance()
+ pr.cancel_transfer(friend_number, file_number)
+ self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
+ self.cancel.setVisible(False)
+ self.accept_or_pause.setVisible(False)
+ self.pb.setVisible(False)
+
+ 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),
+ curr_directory(),
+ QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog)
+ self.pb.setVisible(True)
+ if directory:
+ pr = profile.Profile.get_instance()
+ pr.accept_transfer(self, directory + '/' + self.saved_name, friend_number, file_number, size)
+ self.button_update('pause')
+ elif self.state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: # resume
+ self.paused = False
+ profile.Profile.get_instance().resume_transfer(friend_number, file_number)
+ self.button_update('pause')
+ self.state = TOX_FILE_TRANSFER_STATE['RUNNING']
+ else: # pause
+ self.paused = True
+ self.state = TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']
+ profile.Profile.get_instance().pause_transfer(friend_number, file_number)
+ self.button_update('resume')
+ self.accept_or_pause.clearFocus()
+
+ def button_update(self, path):
+ pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(path))
+ icon = QtGui.QIcon(pixmap)
+ 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):
+ 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 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)
+ self.accept_or_pause.setVisible(False)
+ self.pb.setVisible(False)
+ self.state = state
+ self.time_left.setVisible(False)
+ elif state == TOX_FILE_TRANSFER_STATE['FINISHED']:
+ self.accept_or_pause.setVisible(False)
+ self.pb.setVisible(False)
+ self.cancel.setVisible(False)
+ self.setStyleSheet('QListWidget { border: 1px solid green; }')
+ self.state = state
+ self.time_left.setVisible(False)
+ elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND']:
+ self.accept_or_pause.setVisible(False)
+ self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
+ self.state = state
+ self.time_left.setVisible(False)
+ elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']:
+ self.button_update('resume') # setup button continue
+ self.setStyleSheet('QListWidget { border: 1px solid green; }')
+ self.state = state
+ self.time_left.setVisible(False)
+ elif state == TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']:
+ self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
+ self.accept_or_pause.setVisible(False)
+ self.time_left.setVisible(False)
+ self.pb.setVisible(False)
+ elif not self.paused: # active
+ self.pb.setVisible(True)
+ self.accept_or_pause.setVisible(True) # setup to pause
+ self.button_update('pause')
+ self.setStyleSheet('QListWidget { border: 1px solid green; }')
+ self.state = state
+ self.time_left.setVisible(True)
+
+ def mark_as_sent(self):
+ return False
+
+
+class UnsentFileItem(FileTransferItem):
+
+ def __init__(self, file_name, size, user, time, width, parent=None):
+ super(UnsentFileItem, self).__init__(file_name, size, time, user, -1, -1,
+ TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'], width, parent)
+ self._time = time
+ self.pb.setVisible(False)
+ movie = QtGui.QMovie(curr_directory() + '/images/spinner.gif')
+ self.time.setMovie(movie)
+ movie.start()
+
+ def cancel_transfer(self, *args):
+ pr = profile.Profile.get_instance()
+ pr.cancel_not_started_transfer(self._time)
+
+
+class InlineImageItem(QtGui.QScrollArea):
+
+ def __init__(self, data, width, elem):
+
+ QtGui.QScrollArea.__init__(self)
+ self.setFocusPolicy(QtCore.Qt.NoFocus)
+ self._elem = elem
+ self._image_label = QtGui.QLabel(self)
+ self._image_label.raise_()
+ self.setWidget(self._image_label)
+ self._image_label.setScaledContents(False)
+ self._pixmap = QtGui.QPixmap()
+ self._pixmap.loadFromData(data, 'PNG')
+ self._max_size = width - 30
+ self._resize_needed = not (self._pixmap.width() <= self._max_size)
+ self._full_size = not self._resize_needed
+ if not self._resize_needed:
+ self._image_label.setPixmap(self._pixmap)
+ self.resize(QtCore.QSize(self._max_size + 5, self._pixmap.height() + 5))
+ self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height())
+ else:
+ pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio)
+ self._image_label.setPixmap(pixmap)
+ self.resize(QtCore.QSize(self._max_size + 5, pixmap.height()))
+ self._image_label.setGeometry(5, 0, self._max_size + 5, pixmap.height())
+ self._elem.setSizeHint(QtCore.QSize(self.width(), self.height()))
+
+ def mouseReleaseEvent(self, event):
+ if event.button() == QtCore.Qt.LeftButton and self._resize_needed: # scale inline
+ if self._full_size:
+ pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio)
+ self._image_label.setPixmap(pixmap)
+ self.resize(QtCore.QSize(self._max_size, pixmap.height()))
+ self._image_label.setGeometry(5, 0, pixmap.width(), pixmap.height())
+ else:
+ self._image_label.setPixmap(self._pixmap)
+ self.resize(QtCore.QSize(self._max_size, self._pixmap.height() + 17))
+ self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height())
+ self._full_size = not self._full_size
+ self._elem.setSizeHint(QtCore.QSize(self.width(), self.height()))
+ elif event.button() == QtCore.Qt.RightButton: # save inline
+ directory = QtGui.QFileDialog.getExistingDirectory(self,
+ QtGui.QApplication.translate("MainWindow",
+ 'Choose folder', None,
+ QtGui.QApplication.UnicodeUTF8),
+ curr_directory(),
+ QtGui.QFileDialog.ShowDirsOnly | QtGui.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
+
+
+
+
diff --git a/toxygen/loginscreen.py b/toxygen/loginscreen.py
new file mode 100644
index 0000000..b6d0811
--- /dev/null
+++ b/toxygen/loginscreen.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+
+try:
+ from PySide import QtCore, QtGui
+except ImportError:
+ from PyQt4 import QtCore, QtGui
+from widgets import *
+
+
+class NickEdit(LineEdit):
+
+ def __init__(self, parent):
+ super(NickEdit, self).__init__(parent)
+ self.parent = parent
+
+ def keyPressEvent(self, event):
+ if event.key() == QtCore.Qt.Key_Return:
+ self.parent.create_profile()
+ else:
+ super(NickEdit, self).keyPressEvent(event)
+
+
+class LoginScreen(CenteredWidget):
+
+ def __init__(self):
+ super(LoginScreen, self).__init__()
+ self.initUI()
+ self.center()
+
+ def initUI(self):
+ 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.setGeometry(QtCore.QRect(20, 150, 171, 27))
+ self.new_profile.clicked.connect(self.create_profile)
+ self.label = QtGui.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.setGeometry(QtCore.QRect(220, 150, 161, 27))
+ self.load_profile.clicked.connect(self.load_ex_profile)
+ self.default = QtGui.QCheckBox(self)
+ self.default.setGeometry(QtCore.QRect(220, 110, 131, 22))
+ self.groupBox = QtGui.QGroupBox(self)
+ self.groupBox.setGeometry(QtCore.QRect(210, 40, 181, 151))
+ self.comboBox = QtGui.QComboBox(self.groupBox)
+ self.comboBox.setGeometry(QtCore.QRect(10, 30, 161, 27))
+ self.groupBox_2 = QtGui.QGroupBox(self)
+ self.groupBox_2.setGeometry(QtCore.QRect(10, 40, 191, 151))
+ self.toxygen = QtGui.QLabel(self)
+ self.groupBox.raise_()
+ self.groupBox_2.raise_()
+ self.comboBox.raise_()
+ self.default.raise_()
+ self.load_profile.raise_()
+ self.new_name.raise_()
+ self.new_profile.raise_()
+ self.toxygen.setGeometry(QtCore.QRect(160, 8, 90, 25))
+ font = QtGui.QFont()
+ font.setFamily("Impact")
+ font.setPointSize(16)
+ self.toxygen.setFont(font)
+ self.toxygen.setObjectName("toxygen")
+ self.type = 0
+ self.number = -1
+ self.load_as_default = False
+ self.name = None
+ self.retranslateUi()
+ 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))
+
+ def create_profile(self):
+ self.type = 1
+ self.name = self.new_name.text()
+ self.close()
+
+ def load_ex_profile(self):
+ if not self.create_only:
+ self.type = 2
+ self.number = self.comboBox.currentIndex()
+ self.load_as_default = self.default.isChecked()
+ self.close()
+
+ def update_select(self, data):
+ list_of_profiles = []
+ for elem in data:
+ list_of_profiles.append(elem)
+ self.comboBox.addItems(list_of_profiles)
+ self.create_only = not list_of_profiles
+
+ def update_on_close(self, func):
+ self.onclose = func
+
+ def closeEvent(self, event):
+ self.onclose(self.type, self.number, self.load_as_default, self.name)
+ event.accept()
diff --git a/toxygen/main.py b/toxygen/main.py
index eca3ac3..6f34785 100644
--- a/toxygen/main.py
+++ b/toxygen/main.py
@@ -1,49 +1,460 @@
-import app
-from user_data.settings import *
-import utils.util as util
-import argparse
+import sys
+from loginscreen import LoginScreen
+import profile
+from settings import *
+try:
+ from PySide import QtCore, QtGui
+except ImportError:
+ from PyQt4 import QtCore, QtGui
+from bootstrap import node_generator
+from mainscreen import MainWindow
+from callbacks import init_callbacks, stop, start
+from util import curr_directory, program_version
+import styles.style
+import platform
+import toxencryptsave
+from passwordscreen import PasswordScreen, UnlockAppScreen, SetProfilePasswordScreen
+from plugin_support import PluginLoader
-__maintainer__ = 'Ingvar'
-__version__ = '0.5.0'
+class Toxygen:
+
+ def __init__(self, path_or_uri=None):
+ super(Toxygen, self).__init__()
+ self.tox = self.ms = self.init = self.mainloop = self.avloop = None
+ if path_or_uri is None:
+ self.uri = self.path = None
+ elif path_or_uri.startswith('tox:'):
+ self.path = None
+ self.uri = path_or_uri[4:]
+ else:
+ self.path = path_or_uri
+ self.uri = None
+
+ def enter_pass(self, data):
+ """
+ Show password screen
+ """
+ tmp = [data]
+ p = PasswordScreen(toxencryptsave.ToxEncryptSave.get_instance(), tmp)
+ p.show()
+ self.app.connect(self.app, QtCore.SIGNAL("lastWindowClosed()"), self.app, QtCore.SLOT("quit()"))
+ self.app.exec_()
+ if tmp[0] == data:
+ raise SystemExit()
+ else:
+ return tmp[0]
+
+ def main(self):
+ """
+ Main function of app. loads login screen if needed and starts main screen
+ """
+ app = QtGui.QApplication(sys.argv)
+ app.setWindowIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
+ self.app = app
+
+ if platform.system() == 'Linux':
+ QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
+
+ # application color scheme
+ with open(curr_directory() + '/styles/style.qss') as fl:
+ dark_style = fl.read()
+ app.setStyleSheet(dark_style)
+
+ encrypt_save = toxencryptsave.ToxEncryptSave()
+
+ if self.path is not None:
+ path = os.path.dirname(self.path) + '/'
+ name = os.path.basename(self.path)[:-4]
+ data = ProfileHelper(path, name).open_profile()
+ if encrypt_save.is_data_encrypted(data):
+ data = self.enter_pass(data)
+ settings = Settings(name)
+ self.tox = profile.tox_factory(data, settings)
+ else:
+ auto_profile = Settings.get_auto_profile()
+ if not auto_profile[0]:
+ # show login screen if default profile not found
+ current_locale = QtCore.QLocale()
+ curr_lang = current_locale.languageToString(current_locale.language())
+ langs = Settings.supported_languages()
+ if curr_lang in langs:
+ lang_path = langs[curr_lang]
+ translator = QtCore.QTranslator()
+ translator.load(curr_directory() + '/translations/' + lang_path)
+ app.installTranslator(translator)
+ app.translator = translator
+ ls = LoginScreen()
+ ls.setWindowIconText("Toxygen")
+ profiles = ProfileHelper.find_profiles()
+ ls.update_select(map(lambda x: x[1], profiles))
+ _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
+ elif _login.t == 1: # create new profile
+ _login.name = _login.name.strip()
+ 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.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))
+ msgBox.setText(text)
+ msgBox.exec_()
+ return
+ self.tox = profile.tox_factory()
+ self.tox.self_set_name(bytes(_login.name, 'utf-8') if _login.name else b'Toxygen User')
+ self.tox.self_set_status_message(b'Toxing on Toxygen')
+ reply = QtGui.QMessageBox.question(None,
+ 'Profile {}'.format(name),
+ QtGui.QApplication.translate("login",
+ 'Do you want to set profile password?',
+ None,
+ QtGui.QApplication.UnicodeUTF8),
+ QtGui.QMessageBox.Yes,
+ QtGui.QMessageBox.No)
+ if reply == QtGui.QMessageBox.Yes:
+ set_pass = SetProfilePasswordScreen(encrypt_save)
+ set_pass.show()
+ self.app.connect(self.app, QtCore.SIGNAL("lastWindowClosed()"), self.app, QtCore.SLOT("quit()"))
+ self.app.exec_()
+ reply = QtGui.QMessageBox.question(None,
+ 'Profile {}'.format(name),
+ QtGui.QApplication.translate("login",
+ 'Do you want to save profile in default folder? If no, profile will be saved in program folder',
+ None,
+ QtGui.QApplication.UnicodeUTF8),
+ QtGui.QMessageBox.Yes,
+ QtGui.QMessageBox.No)
+ if reply == QtGui.QMessageBox.Yes:
+ path = Settings.get_default_path()
+ else:
+ path = curr_directory()
+ ProfileHelper(path, name).save_profile(self.tox.get_savedata())
+ path = Settings.get_default_path()
+ settings = Settings(name)
+ if curr_lang in langs:
+ settings['language'] = curr_lang
+ settings.save()
+ else: # load existing profile
+ path, name = _login.get_data()
+ if _login.default:
+ Settings.set_auto_profile(path, name)
+ data = ProfileHelper(path, name).open_profile()
+ if encrypt_save.is_data_encrypted(data):
+ data = self.enter_pass(data)
+ settings = Settings(name)
+ self.tox = profile.tox_factory(data, settings)
+ else:
+ path, name = auto_profile
+ data = ProfileHelper(path, name).open_profile()
+ if encrypt_save.is_data_encrypted(data):
+ data = self.enter_pass(data)
+ settings = Settings(name)
+ self.tox = profile.tox_factory(data, settings)
+
+ if Settings.is_active_profile(path, name): # profile is in use
+ reply = QtGui.QMessageBox.question(None,
+ 'Profile {}'.format(name),
+ QtGui.QApplication.translate("login", 'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?', None, QtGui.QApplication.UnicodeUTF8),
+ QtGui.QMessageBox.Yes,
+ QtGui.QMessageBox.No)
+ if reply != QtGui.QMessageBox.Yes:
+ return
+ else:
+ settings.set_active_profile()
+
+ lang = Settings.supported_languages()[settings['language']]
+ translator = QtCore.QTranslator()
+ translator.load(curr_directory() + '/translations/' + lang)
+ app.installTranslator(translator)
+ app.translator = translator
+
+ # tray icon
+ self.tray = QtGui.QSystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
+ self.tray.setObjectName('tray')
+
+ self.ms = MainWindow(self.tox, self.reset, self.tray)
+
+ class Menu(QtGui.QMenu):
+
+ def newStatus(self, status):
+ profile.Profile.get_instance().set_status(status)
+ self.aboutToShow()
+ self.hide()
+
+ def aboutToShow(self):
+ status = profile.Profile.get_instance().status
+ act = self.act
+ if status is None or Settings.get_instance().locked:
+ self.actions()[1].setVisible(False)
+ else:
+ self.actions()[1].setVisible(True)
+ act.actions()[0].setChecked(False)
+ act.actions()[1].setChecked(False)
+ act.actions()[2].setChecked(False)
+ act.actions()[status].setChecked(True)
+ 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))
+
+ m = Menu()
+ show = m.addAction(QtGui.QApplication.translate('tray', 'Open Toxygen', None, QtGui.QApplication.UnicodeUTF8))
+ sub = m.addMenu(QtGui.QApplication.translate('tray', 'Set status', None, QtGui.QApplication.UnicodeUTF8))
+ onl = sub.addAction(QtGui.QApplication.translate('tray', 'Online', None, QtGui.QApplication.UnicodeUTF8))
+ away = sub.addAction(QtGui.QApplication.translate('tray', 'Away', None, QtGui.QApplication.UnicodeUTF8))
+ busy = sub.addAction(QtGui.QApplication.translate('tray', 'Busy', None, QtGui.QApplication.UnicodeUTF8))
+ onl.setCheckable(True)
+ away.setCheckable(True)
+ busy.setCheckable(True)
+ m.act = sub
+ exit = m.addAction(QtGui.QApplication.translate('tray', 'Exit', None, QtGui.QApplication.UnicodeUTF8))
+
+ def show_window():
+ 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:
+ show()
+ else:
+ def correct_pass():
+ show()
+ Settings.get_instance().locked = False
+ self.p = UnlockAppScreen(toxencryptsave.ToxEncryptSave.get_instance(), correct_pass)
+ self.p.show()
+
+ def tray_activated(reason):
+ if reason == QtGui.QSystemTrayIcon.DoubleClick:
+ show_window()
+
+ def close_app():
+ settings.closing = True
+ self.ms.close()
+
+ m.connect(show, QtCore.SIGNAL("triggered()"), show_window)
+ m.connect(exit, QtCore.SIGNAL("triggered()"), close_app)
+ m.connect(m, QtCore.SIGNAL("aboutToShow()"), lambda: m.aboutToShow())
+ sub.connect(onl, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(0))
+ sub.connect(away, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(1))
+ sub.connect(busy, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(2))
+
+ self.tray.setContextMenu(m)
+ self.tray.show()
+ self.tray.activated.connect(tray_activated)
+
+ self.ms.show()
+
+ 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()
+
+ # starting threads for tox iterate and toxav iterate
+ self.mainloop = self.ToxIterateThread(self.tox)
+ self.mainloop.start()
+ self.avloop = self.ToxAVIterateThread(self.tox.AV)
+ self.avloop.start()
+
+ if self.uri is not None:
+ self.ms.add_contact(self.uri)
+
+ app.connect(app, QtCore.SIGNAL("lastWindowClosed()"), app, QtCore.SLOT("quit()"))
+ app.exec_()
+ self.init.stop = True
+ self.mainloop.stop = True
+ self.avloop.stop = True
+ plugin_helper.stop()
+ stop()
+ self.mainloop.wait()
+ self.init.wait()
+ self.avloop.wait()
+ data = self.tox.get_savedata()
+ ProfileHelper.get_instance().save_profile(data)
+ settings.close()
+ del self.tox
+
+ def reset(self):
+ """
+ Create new tox instance (new network settings)
+ :return: tox instance
+ """
+ self.mainloop.stop = True
+ self.init.stop = True
+ self.avloop.stop = True
+ self.mainloop.wait()
+ self.init.wait()
+ self.avloop.wait()
+ data = self.tox.get_savedata()
+ ProfileHelper.get_instance().save_profile(data)
+ del self.tox
+ # create new tox instance
+ self.tox = profile.tox_factory(data, Settings.get_instance())
+ # init thread
+ self.init = self.InitThread(self.tox, self.ms, self.tray)
+ self.init.start()
+
+ # starting threads for tox iterate and toxav iterate
+ self.mainloop = self.ToxIterateThread(self.tox)
+ self.mainloop.start()
+
+ self.avloop = self.ToxAVIterateThread(self.tox.AV)
+ self.avloop.start()
+
+ plugin_helper = PluginLoader.get_instance()
+ plugin_helper.set_tox(self.tox)
+
+ return self.tox
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Inner classes
+ # -----------------------------------------------------------------------------------------------------------------
+
+ class InitThread(QtCore.QThread):
+
+ def __init__(self, tox, ms, tray):
+ QtCore.QThread.__init__(self)
+ self.tox, self.ms, self.tray = tox, ms, tray
+ self.stop = False
+
+ def run(self):
+ # initializing callbacks
+ init_callbacks(self.tox, self.ms, self.tray)
+ # bootstrap
+ try:
+ for data in node_generator():
+ if self.stop:
+ return
+ self.tox.bootstrap(*data)
+ self.tox.add_tcp_relay(*data)
+ except:
+ pass
+ for _ in range(10):
+ if self.stop:
+ return
+ self.msleep(1000)
+ while not self.tox.self_get_connection_status():
+ try:
+ for data in node_generator():
+ if self.stop:
+ return
+ self.tox.bootstrap(*data)
+ self.tox.add_tcp_relay(*data)
+ except:
+ pass
+ finally:
+ self.msleep(5000)
+
+ class ToxIterateThread(QtCore.QThread):
+
+ def __init__(self, tox):
+ QtCore.QThread.__init__(self)
+ self.tox = tox
+ self.stop = False
+
+ def run(self):
+ while not self.stop:
+ self.tox.iterate()
+ self.msleep(self.tox.iteration_interval())
+
+ class ToxAVIterateThread(QtCore.QThread):
+
+ def __init__(self, toxav):
+ QtCore.QThread.__init__(self)
+ self.toxav = toxav
+ self.stop = False
+
+ def run(self):
+ while not self.stop:
+ self.toxav.iterate()
+ self.msleep(self.toxav.iteration_interval())
+
+ class Login:
+
+ def __init__(self, arr):
+ self.arr = arr
+
+ def login_screen_close(self, t, number=-1, default=False, name=None):
+ """ Function which processes data from login screen
+ :param t: 0 - window was closed, 1 - new profile was created, 2 - profile loaded
+ :param number: num of chosen profile in list (-1 by default)
+ :param default: was or not chosen profile marked as default
+ :param name: name of new profile
+ """
+ self.t = t
+ self.num = number
+ self.default = default
+ self.name = name
+
+ def get_data(self):
+ return self.arr[self.num]
def clean():
- """Removes libs folder"""
- directory = util.get_libs_directory()
- util.remove(directory)
+ """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)
-def reset():
- Settings.reset_auto_profile()
-
-
-def print_toxygen_version():
- print('Toxygen v' + __version__)
+def configure():
+ """Removes unused libs"""
+ d = curr_directory() + '/libs/'
+ is_64bits = sys.maxsize > 2 ** 32
+ if not is_64bits:
+ if os.path.exists(d + 'libtox64.dll'):
+ os.remove(d + 'libtox64.dll')
+ if os.path.exists(d + 'libsodium64.a'):
+ os.remove(d + 'libsodium64.a')
+ else:
+ if os.path.exists(d + 'libtox.dll'):
+ os.remove(d + 'libtox.dll')
+ if os.path.exists(d + 'libsodium.a'):
+ os.remove(d + 'libsodium.a')
+ try:
+ os.rename(d + 'libtox64.dll', d + 'libtox.dll')
+ os.rename(d + 'libsodium64.a', d + 'libsodium.a')
+ except:
+ pass
def main():
- parser = argparse.ArgumentParser()
- parser.add_argument('--version', action='store_true', help='Prints Toxygen version')
- parser.add_argument('--clean', action='store_true', help='Delete toxcore libs from libs folder')
- parser.add_argument('--reset', action='store_true', help='Reset default profile')
- parser.add_argument('--uri', help='Add specified Tox ID to friends')
- parser.add_argument('profile', nargs='?', default=None, help='Path to Tox profile')
- args = parser.parse_args()
-
- if args.version:
- print_toxygen_version()
- return
-
- if args.clean:
- clean()
- return
-
- if args.reset:
- reset()
- return
-
- toxygen = app.App(__version__, args.profile, args.uri)
+ if len(sys.argv) == 1:
+ toxygen = Toxygen()
+ else: # started with argument(s)
+ arg = sys.argv[1]
+ if arg == '--version':
+ print('Toxygen v' + program_version)
+ return
+ elif arg == '--help':
+ print('Usage:\ntoxygen path_to_profile\ntoxygen tox_id\ntoxygen --version')
+ return
+ elif arg == '--configure':
+ configure()
+ return
+ elif arg == '--clean':
+ clean()
+ return
+ else:
+ toxygen = Toxygen(arg)
toxygen.main()
diff --git a/toxygen/mainscreen.py b/toxygen/mainscreen.py
new file mode 100644
index 0000000..14c9f86
--- /dev/null
+++ b/toxygen/mainscreen.py
@@ -0,0 +1,651 @@
+# -*- coding: utf-8 -*-
+
+from menu import *
+from profile import *
+from list_items import *
+from widgets import MultilineEdit, LineEdit
+import plugin_support
+from mainscreen_widgets import *
+import settings
+
+
+class MainWindow(QtGui.QMainWindow, Singleton):
+
+ def __init__(self, tox, reset, tray):
+ super().__init__()
+ Singleton.__init__(self)
+ self.reset = reset
+ self.tray = tray
+ self.setAcceptDrops(True)
+ self.initUI(tox)
+ 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)
+
+ self.menuProfile = QtGui.QMenu(self.menubar)
+ self.menuProfile.setObjectName("menuProfile")
+ self.menuSettings = QtGui.QMenu(self.menubar)
+ self.menuSettings.setObjectName("menuSettings")
+ self.menuPlugins = QtGui.QMenu(self.menubar)
+ self.menuPlugins.setObjectName("menuPlugins")
+ self.menuAbout = QtGui.QMenu(self.menubar)
+ self.menuAbout.setObjectName("menuAbout")
+
+ self.actionAdd_friend = QtGui.QAction(MainWindow)
+ self.actionAdd_friend.setObjectName("actionAdd_friend")
+ self.actionprofilesettings = QtGui.QAction(MainWindow)
+ self.actionprofilesettings.setObjectName("actionprofilesettings")
+ self.actionPrivacy_settings = QtGui.QAction(MainWindow)
+ self.actionPrivacy_settings.setObjectName("actionPrivacy_settings")
+ self.actionInterface_settings = QtGui.QAction(MainWindow)
+ self.actionInterface_settings.setObjectName("actionInterface_settings")
+ self.actionNotifications = QtGui.QAction(MainWindow)
+ self.actionNotifications.setObjectName("actionNotifications")
+ self.actionNetwork = QtGui.QAction(MainWindow)
+ self.actionNetwork.setObjectName("actionNetwork")
+ self.actionAbout_program = QtGui.QAction(MainWindow)
+ self.actionAbout_program.setObjectName("actionAbout_program")
+ self.actionSettings = QtGui.QAction(MainWindow)
+ 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.menuProfile.addAction(self.actionAdd_friend)
+ self.menuProfile.addAction(self.actionSettings)
+ self.menuProfile.addAction(self.lockApp)
+ self.menuSettings.addAction(self.actionPrivacy_settings)
+ self.menuSettings.addAction(self.actionInterface_settings)
+ self.menuSettings.addAction(self.actionNotifications)
+ self.menuSettings.addAction(self.actionNetwork)
+ self.menuSettings.addAction(self.audioSettings)
+ self.menuPlugins.addAction(self.pluginData)
+ self.menuPlugins.addAction(self.importPlugin)
+ self.menuAbout.addAction(self.actionAbout_program)
+ self.menubar.addAction(self.menuProfile.menuAction())
+ self.menubar.addAction(self.menuSettings.menuAction())
+ self.menubar.addAction(self.menuPlugins.menuAction())
+ self.menubar.addAction(self.menuAbout.menuAction())
+
+ self.actionAbout_program.triggered.connect(self.about_program)
+ self.actionNetwork.triggered.connect(self.network_settings)
+ self.actionAdd_friend.triggered.connect(self.add_contact)
+ 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.pluginData.triggered.connect(self.plugins_menu)
+ self.lockApp.triggered.connect(self.lock_app)
+ self.importPlugin.triggered.connect(self.import_plugin)
+ QtCore.QMetaObject.connectSlotsByName(MainWindow)
+
+ def languageChange(self, *args, **kwargs):
+ self.retranslateUi()
+
+ def event(self, event):
+ if event.type() == QtCore.QEvent.WindowActivate:
+ self.tray.setIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
+ 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.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))
+
+ def setup_right_bottom(self, Form):
+ Form.resize(650, 60)
+ self.messageEdit = MessageArea(Form, self)
+ self.messageEdit.setGeometry(QtCore.QRect(0, 3, 450, 55))
+ self.messageEdit.setObjectName("messageEdit")
+ font = QtGui.QFont()
+ font.setPointSize(10)
+ font.setFamily(settings.Settings.get_instance()['font'])
+ self.messageEdit.setFont(font)
+
+ self.sendMessageButton = QtGui.QPushButton(Form)
+ self.sendMessageButton.setGeometry(QtCore.QRect(565, 3, 60, 55))
+ self.sendMessageButton.setObjectName("sendMessageButton")
+
+ self.menuButton = MenuButton(Form, self.show_menu)
+ self.menuButton.setGeometry(QtCore.QRect(QtCore.QRect(455, 3, 55, 55)))
+
+ pixmap = QtGui.QPixmap('send.png')
+ icon = QtGui.QIcon(pixmap)
+ self.sendMessageButton.setIcon(icon)
+ self.sendMessageButton.setIconSize(QtCore.QSize(45, 60))
+
+ pixmap = QtGui.QPixmap('menu.png')
+ icon = QtGui.QIcon(pixmap)
+ self.menuButton.setIcon(icon)
+ self.menuButton.setIconSize(QtCore.QSize(40, 40))
+
+ self.sendMessageButton.clicked.connect(self.send_message)
+
+ QtCore.QMetaObject.connectSlotsByName(Form)
+
+ def setup_left_center_menu(self, Form):
+ Form.resize(270, 25)
+ self.search_label = QtGui.QLabel(Form)
+ self.search_label.setGeometry(QtCore.QRect(3, 2, 20, 20))
+ pixmap = QtGui.QPixmap()
+ pixmap.load(curr_directory() + '/images/search.png')
+ self.search_label.setScaledContents(False)
+ self.search_label.setPixmap(pixmap)
+
+ self.contact_name = LineEdit(Form)
+ self.contact_name.setGeometry(QtCore.QRect(0, 0, 150, 25))
+ self.contact_name.setObjectName("contact_name")
+ self.contact_name.textChanged.connect(self.filtering)
+
+ self.online_contacts = QtGui.QComboBox(Form)
+ self.online_contacts.setGeometry(QtCore.QRect(150, 0, 120, 25))
+ self.online_contacts.activated[int].connect(lambda x: self.filtering())
+ self.search_label.raise_()
+
+ QtCore.QMetaObject.connectSlotsByName(Form)
+
+ def setup_left_top(self, Form):
+ Form.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(False)
+ self.avatar_label.setAlignment(QtCore.Qt.AlignCenter)
+ self.name = Form.name = DataLabel(Form)
+ Form.name.setGeometry(QtCore.QRect(75, 40, 150, 25))
+ font = QtGui.QFont()
+ 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))
+ 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.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(False)
+ self.account_name = DataLabel(Form)
+ self.account_name.setGeometry(QtCore.QRect(100, 25, 400, 25))
+ self.account_name.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse)
+ font = QtGui.QFont()
+ 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.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.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.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))
+ pixmap = QtGui.QPixmap(QtCore.QSize(50, 30))
+ pixmap.load(curr_directory() + '/images/typing.png')
+ self.typing.setScaledContents(False)
+ self.typing.setPixmap(pixmap.scaled(50, 30, QtCore.Qt.KeepAspectRatio))
+ self.typing.setVisible(False)
+ QtCore.QMetaObject.connectSlotsByName(Form)
+
+ def setup_left_center(self, widget):
+ self.friends_list = QtGui.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)
+
+ def setup_right_center(self, widget):
+ self.messages = QtGui.QListWidget(widget)
+ self.messages.setGeometry(0, 0, 620, 310)
+ self.messages.setObjectName("messages")
+ self.messages.setSpacing(1)
+ self.messages.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
+ self.messages.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+ # self.messages.setFocusPolicy(QtCore.Qt.NoFocus)
+
+ 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.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
+
+ def initUI(self, tox):
+ self.setMinimumSize(920, 500)
+ s = Settings.get_instance()
+ self.setGeometry(s['x'], s['y'], s['width'], s['height'])
+ self.setWindowTitle('Toxygen')
+ os.chdir(curr_directory() + '/images/')
+ main = QtGui.QWidget()
+ grid = QtGui.QGridLayout()
+ search = QtGui.QWidget()
+ name = QtGui.QWidget()
+ info = QtGui.QWidget()
+ main_list = QtGui.QWidget()
+ messages = QtGui.QWidget()
+ message_buttons = QtGui.QWidget()
+ self.setup_left_center_menu(search)
+ self.setup_left_top(name)
+ self.setup_right_center(messages)
+ self.setup_right_top(info)
+ self.setup_right_bottom(message_buttons)
+ self.setup_left_center(main_list)
+ if not Settings.get_instance()['mirror_mode']:
+ grid.addWidget(search, 1, 0)
+ grid.addWidget(name, 0, 0)
+ grid.addWidget(messages, 1, 1, 2, 1)
+ grid.addWidget(info, 0, 1)
+ grid.addWidget(message_buttons, 3, 1)
+ grid.addWidget(main_list, 2, 0, 2, 1)
+ grid.setColumnMinimumWidth(1, 500)
+ grid.setColumnMinimumWidth(0, 270)
+ else:
+ grid.addWidget(search, 1, 1)
+ grid.addWidget(name, 0, 1)
+ grid.addWidget(messages, 1, 0, 2, 1)
+ grid.addWidget(info, 0, 0)
+ grid.addWidget(message_buttons, 3, 0)
+ grid.addWidget(main_list, 2, 1, 2, 1)
+ grid.setColumnMinimumWidth(0, 500)
+ grid.setColumnMinimumWidth(1, 270)
+ grid.setSpacing(0)
+ grid.setContentsMargins(0, 0, 0, 0)
+ grid.setRowMinimumHeight(0, 100)
+ grid.setRowMinimumHeight(1, 25)
+ grid.setRowMinimumHeight(2, 320)
+ grid.setRowMinimumHeight(3, 55)
+ grid.setColumnStretch(1, 1)
+ grid.setRowStretch(2, 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, event):
+ self.profile.save_history()
+ self.profile.close()
+ s = Settings.get_instance()
+ if not s['close_to_tray'] or s.closing:
+ s['x'] = self.geometry().x()
+ s['y'] = self.geometry().y()
+ s['width'] = self.width()
+ s['height'] = self.height()
+ s.save()
+ QtGui.QApplication.closeAllWindows()
+ event.accept()
+ else:
+ event.ignore()
+ self.hide()
+
+ def resizeEvent(self, *args, **kwargs):
+ self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155)
+ self.friends_list.setGeometry(0, 0, 270, self.height() - 125)
+
+ self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 40, 50, 50))
+ self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 40, 50, 50))
+ self.typing.setGeometry(QtCore.QRect(self.width() - 450, 50, 50, 30))
+
+ self.messageEdit.setGeometry(QtCore.QRect(55, 0, self.width() - 395, 55))
+ self.menuButton.setGeometry(QtCore.QRect(0, 0, 55, 55))
+ self.sendMessageButton.setGeometry(QtCore.QRect(self.width() - 340, 0, 70, 55))
+
+ self.account_name.setGeometry(QtCore.QRect(100, 40, self.width() - 560, 25))
+ self.account_status.setGeometry(QtCore.QRect(100, 60, self.width() - 560, 25))
+ self.messageEdit.setFocus()
+ self.profile.update()
+
+ def keyPressEvent(self, event):
+ if event.key() == QtCore.Qt.Key_Escape:
+ self.hide()
+ elif event.key() == QtCore.Qt.Key_C and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
+ rows = list(map(lambda x: self.messages.row(x), self.messages.selectedItems()))
+ indexes = (rows[0] - self.messages.count(), rows[-1] - self.messages.count())
+ s = self.profile.export_history(self.profile.active_friend, True, indexes)
+ clipboard = QtGui.QApplication.clipboard()
+ clipboard.setText(s)
+ elif event.key() == QtCore.Qt.Key_Z and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
+ self.messages.clearSelection()
+ else:
+ super(MainWindow, self).keyPressEvent(event)
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Functions which called when user click in menu
+ # -----------------------------------------------------------------------------------------------------------------
+
+ 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: https://github.com/toxygen-project/toxygen/')
+ msgBox.exec_()
+
+ def network_settings(self):
+ self.n_s = NetworkSettings(self.reset)
+ self.n_s.show()
+
+ def plugins_menu(self):
+ self.p_s = PluginsSettings()
+ self.p_s.show()
+
+ def add_contact(self, link=''):
+ self.a_c = AddContact(link or '')
+ self.a_c.show()
+
+ def profile_settings(self, *args):
+ self.p_s = ProfileSettings()
+ self.p_s.show()
+
+ def privacy_settings(self):
+ self.priv_s = PrivacySettings()
+ self.priv_s.show()
+
+ def notification_settings(self):
+ self.notif_s = NotificationsSettings()
+ self.notif_s.show()
+
+ def interface_settings(self):
+ self.int_s = InterfaceSettings()
+ self.int_s.show()
+
+ def audio_settings(self):
+ self.audio_s = AudioSettings()
+ self.audio_s.show()
+
+ def import_plugin(self):
+ import util
+ directory = QtGui.QFileDialog.getExistingDirectory(self,
+ QtGui.QApplication.translate("MainWindow", 'Choose folder with plugin',
+ None,
+ QtGui.QApplication.UnicodeUTF8),
+ util.curr_directory(),
+ QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog)
+ if directory:
+ src = directory + '/'
+ dest = curr_directory() + '/plugins/'
+ util.copy(src, dest)
+ msgBox = QtGui.QMessageBox()
+ msgBox.setWindowTitle(
+ QtGui.QApplication.translate("MainWindow", "Restart Toxygen", None, QtGui.QApplication.UnicodeUTF8))
+ msgBox.setText(
+ QtGui.QApplication.translate("MainWindow", 'Plugin will be loaded after restart', None,
+ QtGui.QApplication.UnicodeUTF8))
+ msgBox.exec_()
+
+ def lock_app(self):
+ if toxencryptsave.ToxEncryptSave.get_instance().has_password():
+ Settings.get_instance().locked = True
+ self.hide()
+ else:
+ msgBox = QtGui.QMessageBox()
+ msgBox.setWindowTitle(
+ QtGui.QApplication.translate("MainWindow", "Cannot lock app", None, QtGui.QApplication.UnicodeUTF8))
+ msgBox.setText(
+ QtGui.QApplication.translate("MainWindow", 'Error. Profile password is not set.', None,
+ QtGui.QApplication.UnicodeUTF8))
+ msgBox.exec_()
+
+ def show_menu(self):
+ if not hasattr(self, 'menu'):
+ self.menu = DropdownMenu(self)
+ self.menu.setGeometry(QtCore.QRect(0 if Settings.get_instance()['mirror_mode'] else 270,
+ self.height() - 120,
+ 180,
+ 120))
+ self.menu.show()
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Messages, calls and file transfers
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def send_message(self):
+ text = self.messageEdit.toPlainText()
+ self.profile.send_message(text)
+
+ def send_file(self):
+ self.menu.hide()
+ if self.profile.active_friend + 1:
+ choose = QtGui.QApplication.translate("MainWindow", 'Choose file', None, QtGui.QApplication.UnicodeUTF8)
+ name = QtGui.QFileDialog.getOpenFileName(self, choose, options=QtGui.QFileDialog.DontUseNativeDialog)
+ if name[0]:
+ self.profile.send_file(name[0])
+
+ def send_screenshot(self, hide=False):
+ self.menu.hide()
+ if self.profile.active_friend + 1:
+ self.sw = ScreenShotWindow(self)
+ self.sw.show()
+ if hide:
+ self.hide()
+
+ def send_smiley(self):
+ self.menu.hide()
+ if self.profile.active_friend + 1:
+ self.smiley = SmileyWindow(self)
+ self.smiley.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(),
+ self.y() + self.height() - 200,
+ self.smiley.width(),
+ self.smiley.height()))
+ self.smiley.show()
+
+ def send_sticker(self):
+ self.menu.hide()
+ if self.profile.active_friend + 1:
+ self.sticker = StickerWindow(self)
+ self.sticker.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(),
+ self.y() + self.height() - 200,
+ self.sticker.width(),
+ self.sticker.height()))
+ self.sticker.show()
+
+ def active_call(self):
+ self.update_call_state('finish_call')
+
+ def incoming_call(self):
+ self.update_call_state('incoming_call')
+
+ def call_finished(self):
+ self.update_call_state('call')
+
+ def update_call_state(self, fl):
+ # TODO: do smth with video call button
+ os.chdir(curr_directory() + '/images/')
+ pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(fl))
+ icon = QtGui.QIcon(pixmap)
+ self.callButton.setIcon(icon)
+ self.callButton.setIconSize(QtCore.QSize(50, 50))
+ pixmap = QtGui.QPixmap(curr_directory() + '/images/videocall.png')
+ icon = QtGui.QIcon(pixmap)
+ self.videocallButton.setIcon(icon)
+ self.videocallButton.setIconSize(QtCore.QSize(35, 35))
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Functions which called when user open context menu in friends list
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def friend_right_click(self, pos):
+ item = self.friends_list.itemAt(pos)
+ num = self.friends_list.indexFromItem(item).row()
+ friend = Profile.get_instance().get_friend(num)
+ settings = Settings.get_instance()
+ allowed = friend.tox_id in settings['auto_accept_from_friends']
+ auto = QtGui.QApplication.translate("MainWindow", 'Disallow auto accept', None, QtGui.QApplication.UnicodeUTF8) if allowed else QtGui.QApplication.translate("MainWindow", 'Allow auto accept', None, QtGui.QApplication.UnicodeUTF8)
+ 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))
+
+ history_menu = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Chat history', None, QtGui.QApplication.UnicodeUTF8))
+ clear_history_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8))
+ export_to_text_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Export as text', None, QtGui.QApplication.UnicodeUTF8))
+ export_to_html_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Export as HTML', None, QtGui.QApplication.UnicodeUTF8))
+
+ 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))
+
+ auto_accept_item = self.listMenu.addAction(auto)
+ remove_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Remove friend', None, QtGui.QApplication.UnicodeUTF8))
+ notes_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Notes', None, QtGui.QApplication.UnicodeUTF8))
+
+ submenu = plugin_support.PluginLoader.get_instance().get_menu(self.listMenu, num)
+ if len(submenu):
+ plug = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8))
+ plug.addActions(submenu)
+ self.connect(set_alias_item, QtCore.SIGNAL("triggered()"), lambda: self.set_alias(num))
+ self.connect(remove_item, QtCore.SIGNAL("triggered()"), lambda: self.remove_friend(num))
+ self.connect(copy_key_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_friend_key(num))
+ self.connect(clear_history_item, QtCore.SIGNAL("triggered()"), lambda: self.clear_history(num))
+ self.connect(auto_accept_item, QtCore.SIGNAL("triggered()"), lambda: self.auto_accept(num, not allowed))
+ self.connect(notes_item, QtCore.SIGNAL("triggered()"), lambda: self.show_note(friend))
+ self.connect(copy_name_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_name(friend))
+ self.connect(copy_status_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_status(friend))
+ self.connect(export_to_text_item, QtCore.SIGNAL("triggered()"), lambda: self.export_history(num))
+ self.connect(export_to_html_item, QtCore.SIGNAL("triggered()"),
+ lambda: self.export_history(num, False))
+ parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0))
+ self.listMenu.move(parent_position + pos)
+ self.listMenu.show()
+
+ def show_note(self, friend):
+ s = Settings.get_instance()
+ note = s['notes'][friend.tox_id] if friend.tox_id in s['notes'] else ''
+ user = QtGui.QApplication.translate("MainWindow", 'Notes about user', None, QtGui.QApplication.UnicodeUTF8)
+ user = '{} {}'.format(user, friend.name)
+
+ def save_note(text):
+ if friend.tox_id in s['notes']:
+ del s['notes'][friend.tox_id]
+ if text:
+ s['notes'][friend.tox_id] = text
+ s.save()
+ self.note = MultilineEdit(user, note, save_note)
+ self.note.show()
+
+ def export_history(self, num, as_text=True):
+ s = self.profile.export_history(num, as_text)
+ directory = QtGui.QFileDialog.getExistingDirectory(None,
+ QtGui.QApplication.translate("MainWindow", 'Choose folder',
+ None,
+ QtGui.QApplication.UnicodeUTF8),
+ curr_directory(),
+ QtGui.QFileDialog.ShowDirsOnly | QtGui.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 copy_friend_key(self, num):
+ tox_id = self.profile.friend_public_key(num)
+ clipboard = QtGui.QApplication.clipboard()
+ clipboard.setText(tox_id)
+
+ def copy_name(self, friend):
+ clipboard = QtGui.QApplication.clipboard()
+ clipboard.setText(friend.name)
+
+ def copy_status(self, friend):
+ clipboard = QtGui.QApplication.clipboard()
+ clipboard.setText(friend.status_message)
+
+ def clear_history(self, num):
+ self.profile.clear_history(num)
+
+ def auto_accept(self, num, value):
+ settings = Settings.get_instance()
+ tox_id = self.profile.friend_public_key(num)
+ if value:
+ settings['auto_accept_from_friends'].append(tox_id)
+ else:
+ settings['auto_accept_from_friends'].remove(tox_id)
+ settings.save()
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Functions which called when user click somewhere else
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def friend_click(self, index):
+ num = index.row()
+ self.profile.set_active(num)
+
+ def mouseReleaseEvent(self, event):
+ pos = self.connection_status.pos()
+ x, y = pos.x() + self.user_info.pos().x(), pos.y() + self.user_info.pos().y()
+ if (x < event.x() < x + 32) and (y < event.y() < y + 32):
+ self.profile.change_status()
+ else:
+ super(MainWindow, self).mouseReleaseEvent(event)
+
+ def filtering(self):
+ self.profile.filtration(self.online_contacts.currentIndex() == 1, self.contact_name.text())
diff --git a/toxygen/mainscreen_widgets.py b/toxygen/mainscreen_widgets.py
new file mode 100644
index 0000000..09c3f36
--- /dev/null
+++ b/toxygen/mainscreen_widgets.py
@@ -0,0 +1,394 @@
+try:
+ from PySide import QtCore, QtGui
+except ImportError:
+ from PyQt4 import QtCore, QtGui
+from widgets import RubberBand, create_menu, QRightClickButton, CenteredWidget
+from profile import Profile
+import smileys
+import util
+
+
+class MessageArea(QtGui.QPlainTextEdit):
+ """User types messages here"""
+
+ def __init__(self, parent, form):
+ super(MessageArea, self).__init__(parent)
+ self.parent = form
+ self.setAcceptDrops(True)
+ self.timer = QtCore.QTimer(self)
+ self.timer.timeout.connect(lambda: self.parent.profile.send_typing(False))
+
+ def keyPressEvent(self, event):
+ if event.matches(QtGui.QKeySequence.Paste):
+ mimeData = QtGui.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()
+ if modifiers & QtCore.Qt.ControlModifier or modifiers & QtCore.Qt.ShiftModifier:
+ self.insertPlainText('\n')
+ else:
+ if self.timer.isActive():
+ self.timer.stop()
+ self.parent.profile.send_typing(False)
+ self.parent.send_message()
+ elif event.key() == QtCore.Qt.Key_Up and not self.toPlainText():
+ self.appendPlainText(Profile.get_instance().get_last_message())
+ else:
+ self.parent.profile.send_typing(True)
+ if self.timer.isActive():
+ self.timer.stop()
+ self.timer.start(5000)
+ super(MessageArea, self).keyPressEvent(event)
+
+ def contextMenuEvent(self, event):
+ menu = create_menu(self.createStandardContextMenu())
+ menu.exec_(event.globalPos())
+ del menu
+
+ def dragEnterEvent(self, e):
+ e.accept()
+
+ def dragMoveEvent(self, e):
+ e.accept()
+
+ def dropEvent(self, e):
+ if e.mimeData().hasFormat('text/plain') 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()
+ if text.startswith('file://'):
+ self.parent.profile.send_file(text[7:])
+ else:
+ self.insertPlainText(text)
+
+
+class ScreenShotWindow(QtGui.QWidget):
+
+ def __init__(self, parent):
+ super(ScreenShotWindow, self).__init__()
+ self.parent = parent
+ self.setMouseTracking(True)
+ self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
+ self.showFullScreen()
+ self.setWindowOpacity(0.5)
+ self.rubberband = RubberBand()
+
+ def closeEvent(self, *args):
+ if self.parent.isHidden():
+ self.parent.show()
+
+ def mousePressEvent(self, event):
+ self.origin = event.pos()
+ self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize()))
+ self.rubberband.show()
+ QtGui.QWidget.mousePressEvent(self, event)
+
+ def mouseMoveEvent(self, event):
+ if self.rubberband.isVisible():
+ self.rubberband.setGeometry(QtCore.QRect(self.origin, event.pos()).normalized())
+ left = QtGui.QRegion(QtCore.QRect(0, 0, self.rubberband.x(), self.height()))
+ right = QtGui.QRegion(QtCore.QRect(self.rubberband.x() + self.rubberband.width(), 0, self.width(), self.height()))
+ top = QtGui.QRegion(0, 0, self.width(), self.rubberband.y())
+ bottom = QtGui.QRegion(0, self.rubberband.y() + self.rubberband.height(), self.width(), self.height())
+ self.setMask(left + right + top + bottom)
+
+ def mouseReleaseEvent(self, event):
+ if self.rubberband.isVisible():
+ self.rubberband.hide()
+ rect = self.rubberband.geometry()
+ if rect.width() and rect.height():
+ p = QtGui.QPixmap.grabWindow(QtGui.QApplication.desktop().winId(),
+ rect.x() + 4,
+ rect.y() + 4,
+ rect.width() - 8,
+ rect.height() - 8)
+ byte_array = QtCore.QByteArray()
+ buffer = QtCore.QBuffer(byte_array)
+ buffer.open(QtCore.QIODevice.WriteOnly)
+ p.save(buffer, 'PNG')
+ Profile.get_instance().send_screenshot(bytes(byte_array.data()))
+ self.close()
+
+ def keyPressEvent(self, event):
+ if event.key() == QtCore.Qt.Key_Escape:
+ self.rubberband.setHidden(True)
+ self.close()
+ else:
+ super(ScreenShotWindow, self).keyPressEvent(event)
+
+
+class SmileyWindow(QtGui.QWidget):
+ """
+ Smiley selection window
+ """
+
+ def __init__(self, parent):
+ super(SmileyWindow, self).__init__()
+ self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
+ inst = smileys.SmileyLoader.get_instance()
+ self.data = inst.get_smileys()
+ count = len(self.data)
+ if not count:
+ self.close()
+ self.page_size = int(pow(count / 8, 0.5) + 1) * 8 # smileys per page
+ if count % self.page_size == 0:
+ self.page_count = count // self.page_size
+ else:
+ self.page_count = round(count / self.page_size + 0.5)
+ self.page = -1
+ self.radio = []
+ self.parent = parent
+ for i in range(self.page_count): # buttons with smileys
+ elem = QtGui.QRadioButton(self)
+ elem.setGeometry(QtCore.QRect(i * 20 + 5, 180, 20, 20))
+ elem.clicked.connect(lambda i=i: self.checked(i))
+ self.radio.append(elem)
+ width = max(self.page_count * 20 + 30, (self.page_size + 5) * 8 // 10)
+ self.setMaximumSize(width, 200)
+ self.setMinimumSize(width, 200)
+ self.buttons = []
+ for i in range(self.page_size): # pages - radio buttons
+ b = QtGui.QPushButton(self)
+ b.setGeometry(QtCore.QRect((i // 8) * 20 + 5, (i % 8) * 20, 20, 20))
+ b.clicked.connect(lambda i=i: self.clicked(i))
+ self.buttons.append(b)
+ self.checked(0)
+
+ def checked(self, pos): # new page opened
+ self.radio[self.page].setChecked(False)
+ self.radio[pos].setChecked(True)
+ self.page = pos
+ start = self.page * self.page_size
+ for i in range(self.page_size):
+ try:
+ self.buttons[i].setVisible(True)
+ pixmap = QtGui.QPixmap(self.data[start + i][1])
+ icon = QtGui.QIcon(pixmap)
+ self.buttons[i].setIcon(icon)
+ except:
+ self.buttons[i].setVisible(False)
+
+ def clicked(self, pos): # smiley selected
+ pos += self.page * self.page_size
+ smiley = self.data[pos][0]
+ self.parent.messageEdit.insertPlainText(smiley)
+ self.close()
+
+ def leaveEvent(self, event):
+ self.close()
+
+
+class MenuButton(QtGui.QPushButton):
+
+ def __init__(self, parent, enter):
+ super(MenuButton, self).__init__(parent)
+ self.enter = enter
+
+ def enterEvent(self, event):
+ self.enter()
+ super(MenuButton, self).enterEvent(event)
+
+
+class DropdownMenu(QtGui.QWidget):
+
+ def __init__(self, parent):
+ super(DropdownMenu, self).__init__(parent)
+ self.installEventFilter(self)
+ self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
+ self.setMaximumSize(180, 120)
+ self.setMinimumSize(180, 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.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.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.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.fileTransferButton.clicked.connect(parent.send_file)
+ self.screenshotButton.clicked.connect(parent.send_screenshot)
+ self.connect(self.screenshotButton, QtCore.SIGNAL("rightClicked()"), lambda: parent.send_screenshot(True))
+ self.smileyButton.clicked.connect(parent.send_smiley)
+ self.stickerButton.clicked.connect(parent.send_sticker)
+
+ def leaveEvent(self, event):
+ self.close()
+
+ def eventFilter(self, object, event):
+ if event.type() == QtCore.QEvent.WindowDeactivate:
+ self.close()
+ return False
+
+
+class StickerItem(QtGui.QWidget):
+
+ def __init__(self, fl):
+ super(StickerItem, self).__init__()
+ self._image_label = QtGui.QLabel(self)
+ self.path = fl
+ self.pixmap = QtGui.QPixmap()
+ self.pixmap.load(fl)
+ if self.pixmap.width() > 150:
+ self.pixmap = self.pixmap.scaled(150, 200, QtCore.Qt.KeepAspectRatio)
+ self.setFixedSize(150, self.pixmap.height())
+ self._image_label.setPixmap(self.pixmap)
+
+
+class StickerWindow(QtGui.QWidget):
+ """Sticker selection window"""
+
+ def __init__(self, parent):
+ super(StickerWindow, self).__init__()
+ self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
+ self.setMaximumSize(250, 200)
+ self.setMinimumSize(250, 200)
+ self.list = QtGui.QListWidget(self)
+ self.list.setGeometry(QtCore.QRect(0, 0, 250, 200))
+ self.arr = smileys.sticker_loader()
+ for sticker in self.arr:
+ item = StickerItem(sticker)
+ elem = QtGui.QListWidgetItem()
+ elem.setSizeHint(QtCore.QSize(250, item.height()))
+ self.list.addItem(elem)
+ self.list.setItemWidget(elem, item)
+ self.list.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
+ self.list.setSpacing(3)
+ self.list.clicked.connect(self.click)
+ self.parent = parent
+
+ def click(self, index):
+ num = index.row()
+ self.parent.profile.send_sticker(self.arr[num])
+ self.close()
+
+ def leaveEvent(self, event):
+ self.close()
+
+
+class WelcomeScreen(CenteredWidget):
+
+ def __init__(self):
+ super().__init__()
+ self.setMaximumSize(250, 200)
+ self.setMinimumSize(250, 200)
+ self.center()
+ self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
+ self.text = QtGui.QTextBrowser(self)
+ self.text.setGeometry(QtCore.QRect(0, 0, 250, 170))
+ self.text.setOpenExternalLinks(True)
+ self.checkbox = QtGui.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))
+ 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)
+ elif num == 1:
+ text = QtGui.QApplication.translate('WelcomeScreen',
+ 'Right click on screenshot button hides app to tray during screenshot.',
+ None, QtGui.QApplication.UnicodeUTF8)
+ elif num == 2:
+ text = QtGui.QApplication.translate('WelcomeScreen',
+ 'You can use Tox over Tor. For more info read this post',
+ None, QtGui.QApplication.UnicodeUTF8)
+ elif num == 3:
+ text = QtGui.QApplication.translate('WelcomeScreen',
+ 'Use Settings -> Interface to customize interface.',
+ None, QtGui.QApplication.UnicodeUTF8)
+ 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)
+ elif num == 5:
+ text = QtGui.QApplication.translate('WelcomeScreen',
+ 'Since v0.1.3 Toxygen supports plugins. Read more',
+ None, QtGui.QApplication.UnicodeUTF8)
+ elif num == 6:
+ text = QtGui.QApplication.translate('WelcomeScreen',
+ 'New in Toxygen v0.2.3:
TCS compliance
Plugins, smileys and stickers import
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)
+ 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)
+ elif num == 9:
+ text = QtGui.QApplication.translate('WelcomeScreen',
+ 'Use right click on inline image to save it',
+ None, QtGui.QApplication.UnicodeUTF8)
+ else:
+ text = QtGui.QApplication.translate('WelcomeScreen',
+ 'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.',
+ None, QtGui.QApplication.UnicodeUTF8)
+ self.text.setHtml(text)
+ self.checkbox.stateChanged.connect(self.not_show)
+ QtCore.QTimer.singleShot(1000, self.show)
+
+ def not_show(self):
+ import settings
+ s = settings.Settings.get_instance()
+ s['show_welcome_screen'] = False
+ s.save()
diff --git a/toxygen/menu.py b/toxygen/menu.py
new file mode 100644
index 0000000..1a8bc28
--- /dev/null
+++ b/toxygen/menu.py
@@ -0,0 +1,901 @@
+try:
+ from PySide import QtCore, QtGui
+except ImportError:
+ from PyQt4 import QtCore, QtGui
+from settings import *
+from profile import Profile
+from util import curr_directory, copy
+from widgets import CenteredWidget, DataLabel, LineEdit
+import pyaudio
+import toxencryptsave
+import plugin_support
+
+
+class AddContact(CenteredWidget):
+ """Add contact form"""
+
+ def __init__(self, tox_id=''):
+ super(AddContact, self).__init__()
+ self.initUI(tox_id)
+ self._adding = False
+
+ def initUI(self, tox_id):
+ self.setObjectName('AddContact')
+ self.resize(568, 306)
+ self.sendRequestButton = QtGui.QPushButton(self)
+ self.sendRequestButton.setGeometry(QtCore.QRect(50, 270, 471, 31))
+ self.sendRequestButton.setMinimumSize(QtCore.QSize(0, 0))
+ self.sendRequestButton.setBaseSize(QtCore.QSize(0, 0))
+ self.sendRequestButton.setObjectName("sendRequestButton")
+ self.sendRequestButton.clicked.connect(self.add_friend)
+ self.tox_id = LineEdit(self)
+ self.tox_id.setGeometry(QtCore.QRect(50, 40, 471, 27))
+ self.tox_id.setObjectName("lineEdit")
+ self.tox_id.setText(tox_id)
+ self.label = QtGui.QLabel(self)
+ self.label.setGeometry(QtCore.QRect(50, 10, 80, 20))
+ self.error_label = DataLabel(self)
+ self.error_label.setGeometry(QtCore.QRect(120, 10, 420, 20))
+ font = QtGui.QFont()
+ font.setFamily(Settings.get_instance()['font'])
+ font.setPointSize(10)
+ font.setWeight(30)
+ self.error_label.setFont(font)
+ self.error_label.setStyleSheet("QLabel { color: #BC1C1C; }")
+ self.label.setObjectName("label")
+ self.message_edit = QtGui.QTextEdit(self)
+ self.message_edit.setGeometry(QtCore.QRect(50, 110, 471, 151))
+ self.message_edit.setObjectName("textEdit")
+ self.message = QtGui.QLabel(self)
+ self.message.setGeometry(QtCore.QRect(50, 70, 101, 31))
+ self.message.setFont(font)
+ self.message.setObjectName("label_2")
+ self.retranslateUi()
+ self.message_edit.setText('Hello! Add me to your contact list please')
+ font.setPointSize(12)
+ font.setBold(True)
+ self.label.setFont(font)
+ self.message.setFont(font)
+ QtCore.QMetaObject.connectSlotsByName(self)
+
+ def add_friend(self):
+ if self._adding:
+ return
+ self._adding = True
+ profile = Profile.get_instance()
+ send = profile.send_friend_request(self.tox_id.text().strip(), self.message_edit.toPlainText())
+ self._adding = False
+ if send is True:
+ # request was successful
+ self.close()
+ else: # print error data
+ self.error_label.setText(send)
+
+ def retranslateUi(self):
+ self.setWindowTitle(QtGui.QApplication.translate('AddContact', "Add contact", None, QtGui.QApplication.UnicodeUTF8))
+ self.sendRequestButton.setText(QtGui.QApplication.translate("Form", "Send request", None, QtGui.QApplication.UnicodeUTF8))
+ self.label.setText(QtGui.QApplication.translate('AddContact', "TOX ID:", None, QtGui.QApplication.UnicodeUTF8))
+ self.message.setText(QtGui.QApplication.translate('AddContact', "Message:", None, QtGui.QApplication.UnicodeUTF8))
+ self.tox_id.setPlaceholderText(QtGui.QApplication.translate('AddContact', "TOX ID or public key of contact", None, QtGui.QApplication.UnicodeUTF8))
+
+
+class ProfileSettings(CenteredWidget):
+ """Form with profile settings such as name, status, TOX ID"""
+ def __init__(self):
+ super(ProfileSettings, self).__init__()
+ self.initUI()
+ self.center()
+
+ def initUI(self):
+ self.setObjectName("ProfileSettingsForm")
+ self.setMinimumSize(QtCore.QSize(700, 600))
+ self.setMaximumSize(QtCore.QSize(700, 600))
+ self.nick = LineEdit(self)
+ self.nick.setGeometry(QtCore.QRect(30, 60, 350, 27))
+ profile = Profile.get_instance()
+ self.nick.setText(profile.name)
+ self.status = QtGui.QComboBox(self)
+ self.status.setGeometry(QtCore.QRect(400, 60, 200, 27))
+ self.status_message = LineEdit(self)
+ self.status_message.setGeometry(QtCore.QRect(30, 130, 350, 27))
+ self.status_message.setText(profile.status_message)
+ self.label = QtGui.QLabel(self)
+ self.label.setGeometry(QtCore.QRect(40, 30, 91, 25))
+ font = QtGui.QFont()
+ font.setFamily(Settings.get_instance()['font'])
+ font.setPointSize(18)
+ font.setWeight(75)
+ font.setBold(True)
+ self.label.setFont(font)
+ self.label_2 = QtGui.QLabel(self)
+ self.label_2.setGeometry(QtCore.QRect(40, 100, 100, 25))
+ self.label_2.setFont(font)
+ self.label_3 = QtGui.QLabel(self)
+ self.label_3.setGeometry(QtCore.QRect(40, 180, 100, 25))
+ self.label_3.setFont(font)
+ self.tox_id = QtGui.QLabel(self)
+ self.tox_id.setGeometry(QtCore.QRect(15, 210, 685, 21))
+ font.setPointSize(10)
+ self.tox_id.setFont(font)
+ s = profile.tox_id
+ self.tox_id.setText(s)
+ self.copyId = QtGui.QPushButton(self)
+ self.copyId.setGeometry(QtCore.QRect(40, 250, 180, 30))
+ self.copyId.clicked.connect(self.copy)
+ self.export = QtGui.QPushButton(self)
+ self.export.setGeometry(QtCore.QRect(230, 250, 180, 30))
+ self.export.clicked.connect(self.export_profile)
+ self.new_nospam = QtGui.QPushButton(self)
+ self.new_nospam.setGeometry(QtCore.QRect(420, 250, 180, 30))
+ self.new_nospam.clicked.connect(self.new_no_spam)
+ self.copy_pk = QtGui.QPushButton(self)
+ self.copy_pk.setGeometry(QtCore.QRect(40, 300, 180, 30))
+ self.copy_pk.clicked.connect(self.copy_public_key)
+ self.new_avatar = QtGui.QPushButton(self)
+ self.new_avatar.setGeometry(QtCore.QRect(230, 300, 180, 30))
+ self.delete_avatar = QtGui.QPushButton(self)
+ self.delete_avatar.setGeometry(QtCore.QRect(420, 300, 180, 30))
+ self.delete_avatar.clicked.connect(self.reset_avatar)
+ self.new_avatar.clicked.connect(self.set_avatar)
+ self.profilepass = QtGui.QLabel(self)
+ self.profilepass.setGeometry(QtCore.QRect(40, 340, 300, 30))
+ font.setPointSize(18)
+ self.profilepass.setFont(font)
+ self.password = LineEdit(self)
+ self.password.setGeometry(QtCore.QRect(40, 380, 300, 30))
+ self.password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
+ self.leave_blank = QtGui.QLabel(self)
+ self.leave_blank.setGeometry(QtCore.QRect(350, 380, 300, 30))
+ self.confirm_password = LineEdit(self)
+ self.confirm_password.setGeometry(QtCore.QRect(40, 420, 300, 30))
+ self.confirm_password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
+ self.set_password = QtGui.QPushButton(self)
+ self.set_password.setGeometry(QtCore.QRect(40, 470, 300, 30))
+ self.set_password.clicked.connect(self.new_password)
+ self.not_match = QtGui.QLabel(self)
+ self.not_match.setGeometry(QtCore.QRect(350, 420, 300, 30))
+ self.not_match.setVisible(False)
+ self.not_match.setStyleSheet('QLabel { color: #BC1C1C; }')
+ self.warning = QtGui.QLabel(self)
+ self.warning.setGeometry(QtCore.QRect(40, 510, 500, 30))
+ self.warning.setStyleSheet('QLabel { color: #BC1C1C; }')
+ self.default = QtGui.QPushButton(self)
+ self.default.setGeometry(QtCore.QRect(40, 550, 620, 30))
+ path, name = Settings.get_auto_profile()
+ self.auto = path + name == ProfileHelper.get_path() + Settings.get_instance().name
+ self.default.clicked.connect(self.auto_profile)
+ self.retranslateUi()
+ if profile.status is not None:
+ self.status.setCurrentIndex(profile.status)
+ else:
+ self.status.setVisible(False)
+ QtCore.QMetaObject.connectSlotsByName(self)
+
+ def retranslateUi(self):
+ self.export.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Export profile", None, QtGui.QApplication.UnicodeUTF8))
+ self.setWindowTitle(QtGui.QApplication.translate("ProfileSettingsForm", "Profile settings", None, QtGui.QApplication.UnicodeUTF8))
+ self.label.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Name:", None, QtGui.QApplication.UnicodeUTF8))
+ self.label_2.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Status:", None, QtGui.QApplication.UnicodeUTF8))
+ self.label_3.setText(QtGui.QApplication.translate("ProfileSettingsForm", "TOX ID:", None, QtGui.QApplication.UnicodeUTF8))
+ self.copyId.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Copy TOX ID", None, QtGui.QApplication.UnicodeUTF8))
+ self.new_avatar.setText(QtGui.QApplication.translate("ProfileSettingsForm", "New avatar", None, QtGui.QApplication.UnicodeUTF8))
+ self.delete_avatar.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Reset avatar", None, QtGui.QApplication.UnicodeUTF8))
+ self.new_nospam.setText(QtGui.QApplication.translate("ProfileSettingsForm", "New NoSpam", None, QtGui.QApplication.UnicodeUTF8))
+ self.profilepass.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Profile password", None, QtGui.QApplication.UnicodeUTF8))
+ self.password.setPlaceholderText(QtGui.QApplication.translate("ProfileSettingsForm", "Password (at least 8 symbols)", None, QtGui.QApplication.UnicodeUTF8))
+ self.confirm_password.setPlaceholderText(QtGui.QApplication.translate("ProfileSettingsForm", "Confirm password", None, QtGui.QApplication.UnicodeUTF8))
+ self.set_password.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Set password", None, QtGui.QApplication.UnicodeUTF8))
+ self.not_match.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Passwords do not match", None, QtGui.QApplication.UnicodeUTF8))
+ self.leave_blank.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Leaving blank will reset current password", None, QtGui.QApplication.UnicodeUTF8))
+ self.warning.setText(QtGui.QApplication.translate("ProfileSettingsForm", "There is no way to recover lost passwords", None, QtGui.QApplication.UnicodeUTF8))
+ self.status.addItem(QtGui.QApplication.translate("ProfileSettingsForm", "Online", None, QtGui.QApplication.UnicodeUTF8))
+ self.status.addItem(QtGui.QApplication.translate("ProfileSettingsForm", "Away", None, QtGui.QApplication.UnicodeUTF8))
+ self.status.addItem(QtGui.QApplication.translate("ProfileSettingsForm", "Busy", None, QtGui.QApplication.UnicodeUTF8))
+ self.copy_pk.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Copy public key", None, QtGui.QApplication.UnicodeUTF8))
+ if self.auto:
+ self.default.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Mark as not default profile", None, QtGui.QApplication.UnicodeUTF8))
+ else:
+ self.default.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Mark as default profile", None, QtGui.QApplication.UnicodeUTF8))
+
+ def auto_profile(self):
+ if self.auto:
+ Settings.reset_auto_profile()
+ else:
+ Settings.set_auto_profile(ProfileHelper.get_path(), Settings.get_instance().name)
+ self.auto = not self.auto
+ if self.auto:
+ self.default.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Mark as not default profile", None,
+ QtGui.QApplication.UnicodeUTF8))
+ else:
+ self.default.setText(
+ QtGui.QApplication.translate("ProfileSettingsForm", "Mark as default profile", None,
+ QtGui.QApplication.UnicodeUTF8))
+
+ def new_password(self):
+ if self.password.text() == self.confirm_password.text():
+ if not len(self.password.text()) or len(self.password.text()) >= 8:
+ e = toxencryptsave.ToxEncryptSave.get_instance()
+ e.set_password(self.password.text())
+ self.close()
+ else:
+ self.not_match.setText(
+ QtGui.QApplication.translate("ProfileSettingsForm", "Password must be at least 8 symbols", None,
+ QtGui.QApplication.UnicodeUTF8))
+ self.not_match.setVisible(True)
+ else:
+ self.not_match.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Passwords do not match", None,
+ QtGui.QApplication.UnicodeUTF8))
+ self.not_match.setVisible(True)
+
+ def copy(self):
+ clipboard = QtGui.QApplication.clipboard()
+ profile = Profile.get_instance()
+ clipboard.setText(profile.tox_id)
+ pixmap = QtGui.QPixmap(curr_directory() + '/images/accept.png')
+ icon = QtGui.QIcon(pixmap)
+ self.copyId.setIcon(icon)
+ self.copyId.setIconSize(QtCore.QSize(10, 10))
+
+ def copy_public_key(self):
+ clipboard = QtGui.QApplication.clipboard()
+ profile = Profile.get_instance()
+ clipboard.setText(profile.tox_id[:64])
+ pixmap = QtGui.QPixmap(curr_directory() + '/images/accept.png')
+ icon = QtGui.QIcon(pixmap)
+ self.copy_pk.setIcon(icon)
+ self.copy_pk.setIconSize(QtCore.QSize(10, 10))
+
+ def new_no_spam(self):
+ self.tox_id.setText(Profile.get_instance().new_nospam())
+
+ def reset_avatar(self):
+ Profile.get_instance().reset_avatar()
+
+ def set_avatar(self):
+ choose = QtGui.QApplication.translate("ProfileSettingsForm", "Choose avatar", None, QtGui.QApplication.UnicodeUTF8)
+ name = QtGui.QFileDialog.getOpenFileName(self, choose, None, 'Images (*.png)',
+ options=QtGui.QFileDialog.DontUseNativeDialog)
+ if name[0]:
+ bitmap = QtGui.QPixmap(name[0])
+ bitmap.scaled(QtCore.QSize(128, 128), aspectMode=QtCore.Qt.KeepAspectRatio,
+ mode=QtCore.Qt.SmoothTransformation)
+
+ byte_array = QtCore.QByteArray()
+ buffer = QtCore.QBuffer(byte_array)
+ buffer.open(QtCore.QIODevice.WriteOnly)
+ bitmap.save(buffer, 'PNG')
+ Profile.get_instance().set_avatar(bytes(byte_array.data()))
+
+ def export_profile(self):
+ directory = QtGui.QFileDialog.getExistingDirectory(options=QtGui.QFileDialog.DontUseNativeDialog,
+ dir=curr_directory()) + '/'
+ if directory != '/':
+ reply = QtGui.QMessageBox.question(None,
+ QtGui.QApplication.translate("ProfileSettingsForm",
+ 'Use new path',
+ None,
+ QtGui.QApplication.UnicodeUTF8),
+ QtGui.QApplication.translate("ProfileSettingsForm",
+ 'Do you want to move your profile to this location?',
+ None,
+ QtGui.QApplication.UnicodeUTF8),
+ QtGui.QMessageBox.Yes,
+ QtGui.QMessageBox.No)
+ settings = Settings.get_instance()
+ settings.export(directory)
+ profile = Profile.get_instance()
+ profile.export_db(directory)
+ ProfileHelper.get_instance().export_profile(directory, reply == QtGui.QMessageBox.Yes)
+
+ def closeEvent(self, event):
+ profile = Profile.get_instance()
+ profile.set_name(self.nick.text())
+ profile.set_status_message(self.status_message.text().encode('utf-8'))
+ profile.set_status(self.status.currentIndex())
+
+
+class NetworkSettings(CenteredWidget):
+ """Network settings form: UDP, Ipv6 and proxy"""
+ def __init__(self, reset):
+ super(NetworkSettings, self).__init__()
+ self.reset = reset
+ self.initUI()
+ self.center()
+
+ def initUI(self):
+ self.setObjectName("NetworkSettings")
+ self.resize(300, 330)
+ self.setMinimumSize(QtCore.QSize(300, 330))
+ self.setMaximumSize(QtCore.QSize(300, 330))
+ self.setBaseSize(QtCore.QSize(300, 330))
+ self.ipv = QtGui.QCheckBox(self)
+ self.ipv.setGeometry(QtCore.QRect(20, 10, 97, 22))
+ self.ipv.setObjectName("ipv")
+ self.udp = QtGui.QCheckBox(self)
+ self.udp.setGeometry(QtCore.QRect(150, 10, 97, 22))
+ self.udp.setObjectName("udp")
+ self.proxy = QtGui.QCheckBox(self)
+ self.proxy.setGeometry(QtCore.QRect(20, 40, 97, 22))
+ self.http = QtGui.QCheckBox(self)
+ self.http.setGeometry(QtCore.QRect(20, 70, 97, 22))
+ self.proxy.setObjectName("proxy")
+ self.proxyip = LineEdit(self)
+ self.proxyip.setGeometry(QtCore.QRect(40, 130, 231, 27))
+ self.proxyip.setObjectName("proxyip")
+ self.proxyport = LineEdit(self)
+ self.proxyport.setGeometry(QtCore.QRect(40, 190, 231, 27))
+ self.proxyport.setObjectName("proxyport")
+ self.label = QtGui.QLabel(self)
+ self.label.setGeometry(QtCore.QRect(40, 100, 66, 17))
+ self.label_2 = QtGui.QLabel(self)
+ self.label_2.setGeometry(QtCore.QRect(40, 165, 66, 17))
+ self.reconnect = QtGui.QPushButton(self)
+ self.reconnect.setGeometry(QtCore.QRect(40, 230, 231, 30))
+ self.reconnect.clicked.connect(self.restart_core)
+ settings = Settings.get_instance()
+ self.ipv.setChecked(settings['ipv6_enabled'])
+ self.udp.setChecked(settings['udp_enabled'])
+ self.proxy.setChecked(settings['proxy_type'])
+ self.proxyip.setText(settings['proxy_host'])
+ self.proxyport.setText(str(settings['proxy_port']))
+ self.http.setChecked(settings['proxy_type'] == 1)
+ self.warning = QtGui.QLabel(self)
+ self.warning.setGeometry(QtCore.QRect(5, 270, 290, 60))
+ self.warning.setStyleSheet('QLabel { color: #BC1C1C; }')
+ self.retranslateUi()
+ self.proxy.stateChanged.connect(lambda x: self.activate())
+ self.activate()
+ QtCore.QMetaObject.connectSlotsByName(self)
+
+ def retranslateUi(self):
+ self.setWindowTitle(QtGui.QApplication.translate("NetworkSettings", "Network settings", None, QtGui.QApplication.UnicodeUTF8))
+ self.ipv.setText(QtGui.QApplication.translate("Form", "IPv6", None, QtGui.QApplication.UnicodeUTF8))
+ self.udp.setText(QtGui.QApplication.translate("Form", "UDP", None, QtGui.QApplication.UnicodeUTF8))
+ self.proxy.setText(QtGui.QApplication.translate("Form", "Proxy", None, QtGui.QApplication.UnicodeUTF8))
+ self.label.setText(QtGui.QApplication.translate("Form", "IP:", None, QtGui.QApplication.UnicodeUTF8))
+ self.label_2.setText(QtGui.QApplication.translate("Form", "Port:", None, QtGui.QApplication.UnicodeUTF8))
+ self.reconnect.setText(QtGui.QApplication.translate("NetworkSettings", "Restart TOX core", None, QtGui.QApplication.UnicodeUTF8))
+ self.http.setText(QtGui.QApplication.translate("Form", "HTTP", None, QtGui.QApplication.UnicodeUTF8))
+ self.warning.setText(QtGui.QApplication.translate("Form", "WARNING:\nusing proxy with enabled UDP\ncan produce IP leak",
+ None, QtGui.QApplication.UnicodeUTF8))
+
+ def activate(self):
+ bl = self.proxy.isChecked()
+ self.proxyip.setEnabled(bl)
+ self.http.setEnabled(bl)
+ self.proxyport.setEnabled(bl)
+
+ def restart_core(self):
+ try:
+ settings = Settings.get_instance()
+ settings['ipv6_enabled'] = self.ipv.isChecked()
+ settings['udp_enabled'] = self.udp.isChecked()
+ settings['proxy_type'] = 2 - int(self.http.isChecked()) if self.proxy.isChecked() else 0
+ settings['proxy_host'] = str(self.proxyip.text())
+ settings['proxy_port'] = int(self.proxyport.text())
+ settings.save()
+ # recreate tox instance
+ Profile.get_instance().reset(self.reset)
+ self.close()
+ except Exception as ex:
+ log('Exception in restart: ' + str(ex))
+
+
+class PrivacySettings(CenteredWidget):
+ """Privacy settings form: history, typing notifications"""
+
+ def __init__(self):
+ super(PrivacySettings, self).__init__()
+ self.initUI()
+ self.center()
+
+ def initUI(self):
+ self.setObjectName("privacySettings")
+ self.resize(370, 600)
+ self.setMinimumSize(QtCore.QSize(370, 600))
+ self.setMaximumSize(QtCore.QSize(370, 600))
+ self.saveHistory = QtGui.QCheckBox(self)
+ self.saveHistory.setGeometry(QtCore.QRect(10, 20, 350, 22))
+ self.saveUnsentOnly = QtGui.QCheckBox(self)
+ self.saveUnsentOnly.setGeometry(QtCore.QRect(10, 60, 350, 22))
+
+ self.fileautoaccept = QtGui.QCheckBox(self)
+ self.fileautoaccept.setGeometry(QtCore.QRect(10, 100, 350, 22))
+
+ self.typingNotifications = QtGui.QCheckBox(self)
+ self.typingNotifications.setGeometry(QtCore.QRect(10, 140, 350, 30))
+ self.inlines = QtGui.QCheckBox(self)
+ self.inlines.setGeometry(QtCore.QRect(10, 180, 350, 30))
+ self.auto_path = QtGui.QLabel(self)
+ self.auto_path.setGeometry(QtCore.QRect(10, 230, 350, 30))
+ self.path = QtGui.QPlainTextEdit(self)
+ self.path.setGeometry(QtCore.QRect(10, 265, 350, 45))
+ self.change_path = QtGui.QPushButton(self)
+ self.change_path.setGeometry(QtCore.QRect(10, 320, 350, 30))
+ settings = Settings.get_instance()
+ self.typingNotifications.setChecked(settings['typing_notifications'])
+ self.fileautoaccept.setChecked(settings['allow_auto_accept'])
+ self.saveHistory.setChecked(settings['save_history'])
+ self.inlines.setChecked(settings['allow_inline'])
+ self.saveUnsentOnly.setChecked(settings['save_unsent_only'])
+ self.saveUnsentOnly.setEnabled(settings['save_history'])
+ self.saveHistory.stateChanged.connect(self.update)
+ self.path.setPlainText(settings['auto_accept_path'] or curr_directory())
+ self.change_path.clicked.connect(self.new_path)
+ self.block_user_label = QtGui.QLabel(self)
+ self.block_user_label.setGeometry(QtCore.QRect(10, 360, 350, 30))
+ self.block_id = QtGui.QPlainTextEdit(self)
+ self.block_id.setGeometry(QtCore.QRect(10, 390, 350, 30))
+ self.block = QtGui.QPushButton(self)
+ self.block.setGeometry(QtCore.QRect(10, 430, 350, 30))
+ self.block.clicked.connect(lambda: Profile.get_instance().block_user(self.block_id.toPlainText()) or self.close())
+ self.blocked_users_label = QtGui.QLabel(self)
+ self.blocked_users_label.setGeometry(QtCore.QRect(10, 470, 350, 30))
+ self.comboBox = QtGui.QComboBox(self)
+ self.comboBox.setGeometry(QtCore.QRect(10, 500, 350, 30))
+ self.comboBox.addItems(settings['blocked'])
+ self.unblock = QtGui.QPushButton(self)
+ self.unblock.setGeometry(QtCore.QRect(10, 540, 350, 30))
+ self.unblock.clicked.connect(lambda: self.unblock_user())
+ self.retranslateUi()
+ QtCore.QMetaObject.connectSlotsByName(self)
+
+ def retranslateUi(self):
+ self.setWindowTitle(QtGui.QApplication.translate("privacySettings", "Privacy settings", None, QtGui.QApplication.UnicodeUTF8))
+ self.saveHistory.setText(QtGui.QApplication.translate("privacySettings", "Save chat history", None, QtGui.QApplication.UnicodeUTF8))
+ self.fileautoaccept.setText(QtGui.QApplication.translate("privacySettings", "Allow file auto accept", None, QtGui.QApplication.UnicodeUTF8))
+ self.typingNotifications.setText(QtGui.QApplication.translate("privacySettings", "Send typing notifications", None, QtGui.QApplication.UnicodeUTF8))
+ self.auto_path.setText(QtGui.QApplication.translate("privacySettings", "Auto accept default path:", None, QtGui.QApplication.UnicodeUTF8))
+ self.change_path.setText(QtGui.QApplication.translate("privacySettings", "Change", None, QtGui.QApplication.UnicodeUTF8))
+ self.inlines.setText(QtGui.QApplication.translate("privacySettings", "Allow inlines", None, QtGui.QApplication.UnicodeUTF8))
+ self.block_user_label.setText(QtGui.QApplication.translate("privacySettings", "Block by public key:", None, QtGui.QApplication.UnicodeUTF8))
+ self.blocked_users_label.setText(QtGui.QApplication.translate("privacySettings", "Blocked users:", None, QtGui.QApplication.UnicodeUTF8))
+ self.unblock.setText(QtGui.QApplication.translate("privacySettings", "Unblock", None, QtGui.QApplication.UnicodeUTF8))
+ self.block.setText(QtGui.QApplication.translate("privacySettings", "Block user", None, QtGui.QApplication.UnicodeUTF8))
+ self.saveUnsentOnly.setText(QtGui.QApplication.translate("privacySettings", "Save unsent messages only", None, QtGui.QApplication.UnicodeUTF8))
+
+ def update(self, new_state):
+ self.saveUnsentOnly.setEnabled(new_state)
+ if not new_state:
+ self.saveUnsentOnly.setChecked(False)
+
+ def unblock_user(self):
+ if not self.comboBox.count():
+ return
+ title = QtGui.QApplication.translate("privacySettings", "Add to friend list", None, QtGui.QApplication.UnicodeUTF8)
+ info = QtGui.QApplication.translate("privacySettings", "Do you want to add this user to friend list?", None, QtGui.QApplication.UnicodeUTF8)
+ reply = QtGui.QMessageBox.question(None, title, info, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
+ Profile.get_instance().unblock_user(self.comboBox.currentText(), reply == QtGui.QMessageBox.Yes)
+ self.close()
+
+ def closeEvent(self, event):
+ settings = Settings.get_instance()
+ settings['typing_notifications'] = self.typingNotifications.isChecked()
+ settings['allow_auto_accept'] = self.fileautoaccept.isChecked()
+
+ if settings['save_history'] and not self.saveHistory.isChecked(): # clear history
+ reply = QtGui.QMessageBox.question(None,
+ QtGui.QApplication.translate("privacySettings",
+ 'Chat history',
+ None, QtGui.QApplication.UnicodeUTF8),
+ QtGui.QApplication.translate("privacySettings",
+ 'History will be cleaned! Continue?',
+ None, QtGui.QApplication.UnicodeUTF8),
+ QtGui.QMessageBox.Yes,
+ QtGui.QMessageBox.No)
+ if reply == QtGui.QMessageBox.Yes:
+ Profile.get_instance().clear_history()
+ settings['save_history'] = self.saveHistory.isChecked()
+ else:
+ settings['save_history'] = self.saveHistory.isChecked()
+ if self.saveUnsentOnly.isChecked() and not settings['save_unsent_only']:
+ reply = QtGui.QMessageBox.question(None,
+ QtGui.QApplication.translate("privacySettings",
+ 'Chat history',
+ None, QtGui.QApplication.UnicodeUTF8),
+ QtGui.QApplication.translate("privacySettings",
+ 'History will be cleaned! Continue?',
+ None, QtGui.QApplication.UnicodeUTF8),
+ QtGui.QMessageBox.Yes,
+ QtGui.QMessageBox.No)
+ if reply == QtGui.QMessageBox.Yes:
+ Profile.get_instance().clear_history(None, True)
+ settings['save_unsent_only'] = self.saveUnsentOnly.isChecked()
+ else:
+ settings['save_unsent_only'] = self.saveUnsentOnly.isChecked()
+ settings['auto_accept_path'] = self.path.toPlainText()
+ settings['allow_inline'] = self.inlines.isChecked()
+ settings.save()
+
+ def new_path(self):
+ directory = QtGui.QFileDialog.getExistingDirectory(options=QtGui.QFileDialog.DontUseNativeDialog) + '/'
+ if directory != '/':
+ self.path.setPlainText(directory)
+
+
+class NotificationsSettings(CenteredWidget):
+ """Notifications settings form"""
+
+ def __init__(self):
+ super(NotificationsSettings, self).__init__()
+ self.initUI()
+ self.center()
+
+ def initUI(self):
+ self.setObjectName("notificationsForm")
+ self.resize(350, 180)
+ self.setMinimumSize(QtCore.QSize(350, 180))
+ self.setMaximumSize(QtCore.QSize(350, 180))
+ self.enableNotifications = QtGui.QCheckBox(self)
+ self.enableNotifications.setGeometry(QtCore.QRect(10, 20, 340, 18))
+ self.callsSound = QtGui.QCheckBox(self)
+ self.callsSound.setGeometry(QtCore.QRect(10, 120, 340, 18))
+ self.soundNotifications = QtGui.QCheckBox(self)
+ self.soundNotifications.setGeometry(QtCore.QRect(10, 70, 340, 18))
+ font = QtGui.QFont()
+ s = Settings.get_instance()
+ font.setFamily(s['font'])
+ font.setPointSize(12)
+ self.callsSound.setFont(font)
+ self.soundNotifications.setFont(font)
+ self.enableNotifications.setFont(font)
+ self.enableNotifications.setChecked(s['notifications'])
+ self.soundNotifications.setChecked(s['sound_notifications'])
+ self.callsSound.setChecked(s['calls_sound'])
+ self.retranslateUi()
+ QtCore.QMetaObject.connectSlotsByName(self)
+
+ def retranslateUi(self):
+ self.setWindowTitle(QtGui.QApplication.translate("notificationsForm", "Notification settings", None, QtGui.QApplication.UnicodeUTF8))
+ self.enableNotifications.setText(QtGui.QApplication.translate("notificationsForm", "Enable notifications", None, QtGui.QApplication.UnicodeUTF8))
+ self.callsSound.setText(QtGui.QApplication.translate("notificationsForm", "Enable call\'s sound", None, QtGui.QApplication.UnicodeUTF8))
+ self.soundNotifications.setText(QtGui.QApplication.translate("notificationsForm", "Enable sound notifications", None, QtGui.QApplication.UnicodeUTF8))
+
+ def closeEvent(self, *args, **kwargs):
+ settings = Settings.get_instance()
+ settings['notifications'] = self.enableNotifications.isChecked()
+ settings['sound_notifications'] = self.soundNotifications.isChecked()
+ settings['calls_sound'] = self.callsSound.isChecked()
+ settings.save()
+
+
+class InterfaceSettings(CenteredWidget):
+ """Interface settings form"""
+ def __init__(self):
+ super(InterfaceSettings, self).__init__()
+ self.initUI()
+ self.center()
+
+ def initUI(self):
+ self.setObjectName("interfaceForm")
+ self.setMinimumSize(QtCore.QSize(400, 650))
+ self.setMaximumSize(QtCore.QSize(400, 650))
+ self.label = QtGui.QLabel(self)
+ self.label.setGeometry(QtCore.QRect(30, 10, 370, 20))
+ settings = Settings.get_instance()
+ font = QtGui.QFont()
+ font.setPointSize(14)
+ font.setBold(True)
+ font.setFamily(settings['font'])
+ self.label.setFont(font)
+ self.themeSelect = QtGui.QComboBox(self)
+ self.themeSelect.setGeometry(QtCore.QRect(30, 40, 120, 30))
+ list_of_themes = ['dark']
+ self.themeSelect.addItems(list_of_themes)
+ theme = settings['theme']
+ if theme in list_of_themes:
+ index = list_of_themes.index(theme)
+ else:
+ index = 0
+ self.themeSelect.setCurrentIndex(index)
+ self.lang_choose = QtGui.QComboBox(self)
+ self.lang_choose.setGeometry(QtCore.QRect(30, 110, 120, 30))
+ supported = sorted(Settings.supported_languages().keys(), reverse=True)
+ for key in supported:
+ self.lang_choose.insertItem(0, key)
+ if settings['language'] == key:
+ self.lang_choose.setCurrentIndex(0)
+ self.lang = QtGui.QLabel(self)
+ self.lang.setGeometry(QtCore.QRect(30, 80, 370, 20))
+ self.lang.setFont(font)
+ self.mirror_mode = QtGui.QCheckBox(self)
+ self.mirror_mode.setGeometry(QtCore.QRect(30, 160, 370, 20))
+ self.mirror_mode.setChecked(settings['mirror_mode'])
+ self.smileys = QtGui.QCheckBox(self)
+ self.smileys.setGeometry(QtCore.QRect(30, 190, 120, 20))
+ self.smileys.setChecked(settings['smileys'])
+ self.smiley_pack_label = QtGui.QLabel(self)
+ self.smiley_pack_label.setGeometry(QtCore.QRect(30, 230, 370, 20))
+ self.smiley_pack_label.setFont(font)
+ self.smiley_pack = QtGui.QComboBox(self)
+ self.smiley_pack.setGeometry(QtCore.QRect(30, 260, 160, 30))
+ sm = smileys.SmileyLoader.get_instance()
+ self.smiley_pack.addItems(sm.get_packs_list())
+ try:
+ ind = sm.get_packs_list().index(settings['smiley_pack'])
+ except:
+ ind = sm.get_packs_list().index('default')
+ self.smiley_pack.setCurrentIndex(ind)
+ self.messages_font_size_label = QtGui.QLabel(self)
+ self.messages_font_size_label.setGeometry(QtCore.QRect(30, 300, 370, 20))
+ self.messages_font_size_label.setFont(font)
+ self.messages_font_size = QtGui.QComboBox(self)
+ self.messages_font_size.setGeometry(QtCore.QRect(30, 330, 160, 30))
+ self.messages_font_size.addItems([str(x) for x in range(10, 19)])
+ self.messages_font_size.setCurrentIndex(settings['message_font_size'] - 10)
+
+ self.unread = QtGui.QPushButton(self)
+ self.unread.setGeometry(QtCore.QRect(30, 470, 340, 30))
+ self.unread.clicked.connect(self.select_color)
+
+ self.compact_mode = QtGui.QCheckBox(self)
+ self.compact_mode.setGeometry(QtCore.QRect(30, 380, 370, 20))
+ self.compact_mode.setChecked(settings['compact_mode'])
+
+ self.close_to_tray = QtGui.QCheckBox(self)
+ self.close_to_tray.setGeometry(QtCore.QRect(30, 410, 370, 20))
+ self.close_to_tray.setChecked(settings['close_to_tray'])
+
+ self.show_avatars = QtGui.QCheckBox(self)
+ self.show_avatars.setGeometry(QtCore.QRect(30, 440, 370, 20))
+ self.show_avatars.setChecked(settings['show_avatars'])
+
+ self.choose_font = QtGui.QPushButton(self)
+ self.choose_font.setGeometry(QtCore.QRect(30, 510, 340, 30))
+ self.choose_font.clicked.connect(self.new_font)
+
+ self.import_smileys = QtGui.QPushButton(self)
+ self.import_smileys.setGeometry(QtCore.QRect(30, 550, 340, 30))
+ self.import_smileys.clicked.connect(self.import_sm)
+
+ self.import_stickers = QtGui.QPushButton(self)
+ self.import_stickers.setGeometry(QtCore.QRect(30, 590, 340, 30))
+ self.import_stickers.clicked.connect(self.import_st)
+
+ self.retranslateUi()
+ QtCore.QMetaObject.connectSlotsByName(self)
+
+ def retranslateUi(self):
+ self.show_avatars.setText(QtGui.QApplication.translate("interfaceForm", "Show avatars in chat", None, QtGui.QApplication.UnicodeUTF8))
+ self.setWindowTitle(QtGui.QApplication.translate("interfaceForm", "Interface settings", None, QtGui.QApplication.UnicodeUTF8))
+ self.label.setText(QtGui.QApplication.translate("interfaceForm", "Theme:", None, QtGui.QApplication.UnicodeUTF8))
+ self.lang.setText(QtGui.QApplication.translate("interfaceForm", "Language:", None, QtGui.QApplication.UnicodeUTF8))
+ self.smileys.setText(QtGui.QApplication.translate("interfaceForm", "Smileys", None, QtGui.QApplication.UnicodeUTF8))
+ self.smiley_pack_label.setText(QtGui.QApplication.translate("interfaceForm", "Smiley pack:", None, QtGui.QApplication.UnicodeUTF8))
+ self.mirror_mode.setText(QtGui.QApplication.translate("interfaceForm", "Mirror mode", None, QtGui.QApplication.UnicodeUTF8))
+ self.messages_font_size_label.setText(QtGui.QApplication.translate("interfaceForm", "Messages font size:", None, QtGui.QApplication.UnicodeUTF8))
+ self.unread.setText(QtGui.QApplication.translate("interfaceForm", "Select unread messages notification color", None, QtGui.QApplication.UnicodeUTF8))
+ self.compact_mode.setText(QtGui.QApplication.translate("interfaceForm", "Compact contact list", None, QtGui.QApplication.UnicodeUTF8))
+ self.import_smileys.setText(QtGui.QApplication.translate("interfaceForm", "Import smiley pack", None, QtGui.QApplication.UnicodeUTF8))
+ self.import_stickers.setText(QtGui.QApplication.translate("interfaceForm", "Import sticker pack", None, QtGui.QApplication.UnicodeUTF8))
+ self.close_to_tray.setText(QtGui.QApplication.translate("interfaceForm", "Close to tray", None, QtGui.QApplication.UnicodeUTF8))
+ self.choose_font.setText(QtGui.QApplication.translate("interfaceForm", "Select font", None, QtGui.QApplication.UnicodeUTF8))
+
+ def import_st(self):
+ directory = QtGui.QFileDialog.getExistingDirectory(self,
+ QtGui.QApplication.translate("MainWindow",
+ 'Choose folder with sticker pack',
+ None,
+ QtGui.QApplication.UnicodeUTF8),
+ curr_directory(),
+ QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog)
+
+ if directory:
+ src = directory + '/'
+ dest = curr_directory() + '/stickers/' + os.path.basename(directory) + '/'
+ copy(src, dest)
+
+ def import_sm(self):
+ directory = QtGui.QFileDialog.getExistingDirectory(self,
+ QtGui.QApplication.translate("MainWindow",
+ 'Choose folder with smiley pack',
+ None,
+ QtGui.QApplication.UnicodeUTF8),
+ curr_directory(),
+ QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog)
+
+ if directory:
+ src = directory + '/'
+ dest = curr_directory() + '/smileys/' + os.path.basename(directory) + '/'
+ copy(src, dest)
+
+ def new_font(self):
+ settings = Settings.get_instance()
+ font, ok = QtGui.QFontDialog.getFont(QtGui.QFont(settings['font'], 10), self)
+ if ok:
+ settings['font'] = font.family()
+ settings.save()
+ msgBox = QtGui.QMessageBox()
+ text = QtGui.QApplication.translate("interfaceForm", 'Restart app to apply settings', None,
+ QtGui.QApplication.UnicodeUTF8)
+ msgBox.setWindowTitle(QtGui.QApplication.translate("interfaceForm", 'Restart required', None,
+ QtGui.QApplication.UnicodeUTF8))
+ msgBox.setText(text)
+ msgBox.exec_()
+
+ def select_color(self):
+ col = QtGui.QColorDialog.getColor()
+
+ if col.isValid():
+ settings = Settings.get_instance()
+ name = col.name()
+ settings['unread_color'] = name
+ settings.save()
+
+ def closeEvent(self, event):
+ settings = Settings.get_instance()
+ settings['theme'] = str(self.themeSelect.currentText())
+ settings['smileys'] = self.smileys.isChecked()
+ restart = False
+ if settings['mirror_mode'] != self.mirror_mode.isChecked():
+ settings['mirror_mode'] = self.mirror_mode.isChecked()
+ restart = True
+ if settings['compact_mode'] != self.compact_mode.isChecked():
+ settings['compact_mode'] = self.compact_mode.isChecked()
+ restart = True
+ if settings['show_avatars'] != self.show_avatars.isChecked():
+ settings['show_avatars'] = self.show_avatars.isChecked()
+ restart = True
+ settings['smiley_pack'] = self.smiley_pack.currentText()
+ settings['close_to_tray'] = self.close_to_tray.isChecked()
+ smileys.SmileyLoader.get_instance().load_pack()
+ language = self.lang_choose.currentText()
+ if settings['language'] != language:
+ settings['language'] = language
+ text = self.lang_choose.currentText()
+ path = Settings.supported_languages()[text]
+ app = QtGui.QApplication.instance()
+ app.removeTranslator(app.translator)
+ app.translator.load(curr_directory() + '/translations/' + path)
+ app.installTranslator(app.translator)
+ settings['message_font_size'] = self.messages_font_size.currentIndex() + 10
+ Profile.get_instance().update()
+ settings.save()
+ if restart:
+ msgBox = QtGui.QMessageBox()
+ text = QtGui.QApplication.translate("interfaceForm", 'Restart app to apply settings', None,
+ QtGui.QApplication.UnicodeUTF8)
+ msgBox.setWindowTitle(QtGui.QApplication.translate("interfaceForm", 'Restart required', None,
+ QtGui.QApplication.UnicodeUTF8))
+ msgBox.setText(text)
+ msgBox.exec_()
+
+
+class AudioSettings(CenteredWidget):
+ """
+ Audio calls settings form
+ """
+
+ def __init__(self):
+ super(AudioSettings, self).__init__()
+ self.initUI()
+ self.retranslateUi()
+ self.center()
+
+ def initUI(self):
+ self.setObjectName("audioSettingsForm")
+ self.resize(400, 150)
+ self.setMinimumSize(QtCore.QSize(400, 150))
+ self.setMaximumSize(QtCore.QSize(400, 150))
+ self.in_label = QtGui.QLabel(self)
+ self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20))
+ self.out_label = QtGui.QLabel(self)
+ self.out_label.setGeometry(QtCore.QRect(25, 65, 350, 20))
+ settings = Settings.get_instance()
+ font = QtGui.QFont()
+ font.setPointSize(16)
+ font.setBold(True)
+ font.setFamily(settings['font'])
+ self.in_label.setFont(font)
+ self.out_label.setFont(font)
+ self.input = QtGui.QComboBox(self)
+ self.input.setGeometry(QtCore.QRect(25, 30, 350, 30))
+ self.output = QtGui.QComboBox(self)
+ self.output.setGeometry(QtCore.QRect(25, 90, 350, 30))
+ p = pyaudio.PyAudio()
+ self.in_indexes, self.out_indexes = [], []
+ for i in range(p.get_device_count()):
+ device = p.get_device_info_by_index(i)
+ if device["maxInputChannels"]:
+ self.input.addItem(str(device["name"]))
+ self.in_indexes.append(i)
+ if device["maxOutputChannels"]:
+ self.output.addItem(str(device["name"]))
+ self.out_indexes.append(i)
+ self.input.setCurrentIndex(self.in_indexes.index(settings.audio['input']))
+ self.output.setCurrentIndex(self.out_indexes.index(settings.audio['output']))
+ QtCore.QMetaObject.connectSlotsByName(self)
+
+ def retranslateUi(self):
+ self.setWindowTitle(QtGui.QApplication.translate("audioSettingsForm", "Audio settings", None, QtGui.QApplication.UnicodeUTF8))
+ self.in_label.setText(QtGui.QApplication.translate("audioSettingsForm", "Input device:", None, QtGui.QApplication.UnicodeUTF8))
+ self.out_label.setText(QtGui.QApplication.translate("audioSettingsForm", "Output device:", None, QtGui.QApplication.UnicodeUTF8))
+
+ def closeEvent(self, event):
+ settings = Settings.get_instance()
+ settings.audio['input'] = self.in_indexes[self.input.currentIndex()]
+ settings.audio['output'] = self.out_indexes[self.output.currentIndex()]
+ settings.save()
+
+
+class PluginsSettings(CenteredWidget):
+ """
+ Plugins settings form
+ """
+
+ def __init__(self):
+ super(PluginsSettings, self).__init__()
+ self.initUI()
+ self.center()
+ self.retranslateUi()
+
+ def initUI(self):
+ self.resize(400, 210)
+ self.setMinimumSize(QtCore.QSize(400, 210))
+ self.setMaximumSize(QtCore.QSize(400, 210))
+ self.comboBox = QtGui.QComboBox(self)
+ self.comboBox.setGeometry(QtCore.QRect(30, 10, 340, 30))
+ self.label = QtGui.QLabel(self)
+ self.label.setGeometry(QtCore.QRect(30, 40, 340, 90))
+ self.label.setWordWrap(True)
+ self.button = QtGui.QPushButton(self)
+ self.button.setGeometry(QtCore.QRect(30, 130, 340, 30))
+ self.button.clicked.connect(self.button_click)
+ self.open = QtGui.QPushButton(self)
+ self.open.setGeometry(QtCore.QRect(30, 170, 340, 30))
+ self.open.clicked.connect(self.open_plugin)
+ self.pl_loader = plugin_support.PluginLoader.get_instance()
+ self.update_list()
+ self.comboBox.currentIndexChanged.connect(self.show_data)
+ self.show_data()
+
+ def retranslateUi(self):
+ self.setWindowTitle(QtGui.QApplication.translate('PluginsForm', "Plugins", None, QtGui.QApplication.UnicodeUTF8))
+ self.open.setText(QtGui.QApplication.translate('PluginsForm', "Open selected plugin", None, QtGui.QApplication.UnicodeUTF8))
+
+ def open_plugin(self):
+ ind = self.comboBox.currentIndex()
+ plugin = self.data[ind]
+ window = self.pl_loader.plugin_window(plugin[-1])
+ if window is not None:
+ self.window = window
+ self.window.show()
+ else:
+ msgBox = QtGui.QMessageBox()
+ text = QtGui.QApplication.translate("PluginsForm", 'No GUI found for this plugin', None,
+ QtGui.QApplication.UnicodeUTF8)
+ msgBox.setWindowTitle(QtGui.QApplication.translate("PluginsForm", 'Error', None,
+ QtGui.QApplication.UnicodeUTF8))
+ msgBox.setText(text)
+ msgBox.exec_()
+
+ def update_list(self):
+ self.comboBox.clear()
+ data = self.pl_loader.get_plugins_list()
+ self.comboBox.addItems(list(map(lambda x: x[0], data)))
+ self.data = data
+
+ def show_data(self):
+ ind = self.comboBox.currentIndex()
+ if len(self.data):
+ plugin = self.data[ind]
+ descr = plugin[2] or QtGui.QApplication.translate("PluginsForm", "No description available", None, QtGui.QApplication.UnicodeUTF8)
+ self.label.setText(descr)
+ if plugin[1]:
+ self.button.setText(QtGui.QApplication.translate("PluginsForm", "Disable plugin", None, QtGui.QApplication.UnicodeUTF8))
+ else:
+ self.button.setText(QtGui.QApplication.translate("PluginsForm", "Enable plugin", None, QtGui.QApplication.UnicodeUTF8))
+ else:
+ self.open.setVisible(False)
+ self.button.setVisible(False)
+ self.label.setText(QtGui.QApplication.translate("PluginsForm", "No plugins found", None, QtGui.QApplication.UnicodeUTF8))
+
+ def button_click(self):
+ ind = self.comboBox.currentIndex()
+ plugin = self.data[ind]
+ self.pl_loader.toggle_plugin(plugin[-1])
+ plugin[1] = not plugin[1]
+ if plugin[1]:
+ self.button.setText(QtGui.QApplication.translate("PluginsForm", "Disable plugin", None, QtGui.QApplication.UnicodeUTF8))
+ else:
+ self.button.setText(QtGui.QApplication.translate("PluginsForm", "Enable plugin", None, QtGui.QApplication.UnicodeUTF8))
diff --git a/toxygen/messages.py b/toxygen/messages.py
new file mode 100644
index 0000000..87a1cc2
--- /dev/null
+++ b/toxygen/messages.py
@@ -0,0 +1,101 @@
+
+
+MESSAGE_TYPE = {
+ 'TEXT': 0,
+ 'ACTION': 1,
+ 'FILE_TRANSFER': 2,
+ 'INLINE': 3,
+ 'INFO_MESSAGE': 4
+}
+
+
+class Message:
+
+ def __init__(self, message_type, owner, time):
+ self._time = time
+ self._type = message_type
+ self._owner = owner
+
+ def get_type(self):
+ return self._type
+
+ def get_owner(self):
+ return self._owner
+
+ def mark_as_sent(self):
+ self._owner = 0
+
+
+class TextMessage(Message):
+ """
+ Plain text or action message
+ """
+
+ def __init__(self, message, owner, time, message_type):
+ super(TextMessage, self).__init__(message_type, owner, time)
+ self._message = message
+
+ def get_data(self):
+ return self._message, self._owner, self._time, self._type
+
+
+class TransferMessage(Message):
+ """
+ Message with info about file transfer
+ """
+
+ def __init__(self, owner, time, status, size, name, friend_number, file_number):
+ super(TransferMessage, self).__init__(MESSAGE_TYPE['FILE_TRANSFER'], owner, time)
+ self._status = status
+ self._size = size
+ self._file_name = name
+ self._friend_number, self._file_number = friend_number, file_number
+
+ def is_active(self, file_number):
+ return self._file_number == file_number and self._status not in (2, 3)
+
+ def get_friend_number(self):
+ return self._friend_number
+
+ def get_file_number(self):
+ return self._file_number
+
+ def get_status(self):
+ return self._status
+
+ def set_status(self, value):
+ self._status = value
+
+ def get_data(self):
+ return self._file_name, self._size, self._time, self._owner, self._friend_number, self._file_number, self._status
+
+
+class UnsentFile(Message):
+ def __init__(self, path, data, time):
+ super(UnsentFile, self).__init__(MESSAGE_TYPE['FILE_TRANSFER'], 0, time)
+ self._data, self._path = data, path
+
+ def get_data(self):
+ return self._path, self._data, self._time
+
+ def get_status(self):
+ return None
+
+
+class InlineImage(Message):
+ """
+ Inline image
+ """
+
+ def __init__(self, data):
+ super(InlineImage, self).__init__(MESSAGE_TYPE['INLINE'], None, None)
+ self._data = data
+
+ def get_data(self):
+ return self._data
+
+
+class InfoMessage(TextMessage):
+
+ def __init__(self, message, time):
+ super(InfoMessage, self).__init__(message, None, time, MESSAGE_TYPE['INFO_MESSAGE'])
diff --git a/toxygen/messenger/__init__.py b/toxygen/messenger/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py
deleted file mode 100644
index e777c4b..0000000
--- a/toxygen/messenger/messages.py
+++ /dev/null
@@ -1,239 +0,0 @@
-from history.database import MESSAGE_AUTHOR
-import os.path
-from ui.messages_widgets import *
-
-
-MESSAGE_TYPE = {
- 'TEXT': 0,
- 'ACTION': 1,
- 'FILE_TRANSFER': 2,
- 'INLINE': 3,
- 'INFO_MESSAGE': 4
-}
-
-PAGE_SIZE = 42
-
-
-class MessageAuthor:
-
- def __init__(self, author_name, author_type):
- self._name = author_name
- self._type = author_type
-
- def get_name(self):
- return self._name
-
- name = property(get_name)
-
- def get_type(self):
- return self._type
-
- def set_type(self, value):
- self._type = value
-
- type = property(get_type, set_type)
-
-
-class Message:
-
- MESSAGE_ID = 0
-
- def __init__(self, message_type, author, time):
- self._time = time
- self._type = message_type
- self._author = author
- self._widget = None
- self._message_id = self._get_id()
-
- def get_type(self):
- return self._type
-
- type = property(get_type)
-
- def get_author(self):
- return self._author
-
- author = property(get_author)
-
- def get_time(self):
- return self._time
-
- time = property(get_time)
-
- def get_message_id(self):
- return self._message_id
-
- message_id = property(get_message_id)
-
- def get_widget(self, *args):
- self._widget = self._create_widget(*args)
-
- return self._widget
-
- widget = property(get_widget)
-
- def remove_widget(self):
- self._widget = None
-
- def mark_as_sent(self):
- self._author.type = MESSAGE_AUTHOR['ME']
- if self._widget is not None:
- self._widget.mark_as_sent()
-
- def _create_widget(self, *args):
- pass
-
- @staticmethod
- def _get_id():
- Message.MESSAGE_ID += 1
-
- return int(Message.MESSAGE_ID)
-
-
-class TextMessage(Message):
- """
- Plain text or action message
- """
-
- def __init__(self, message, owner, time, message_type, message_id=0):
- super().__init__(message_type, owner, time)
- self._message = message
- self._id = message_id
-
- def get_text(self):
- return self._message
-
- text = property(get_text)
-
- def get_id(self):
- return self._id
-
- id = property(get_id)
-
- def is_saved(self):
- return self._id > 0
-
- def _create_widget(self, *args):
- return MessageItem(self, *args)
-
-
-class OutgoingTextMessage(TextMessage):
-
- def __init__(self, message, owner, time, message_type, tox_message_id=0):
- super().__init__(message, owner, time, message_type)
- self._tox_message_id = tox_message_id
-
- def get_tox_message_id(self):
- return self._tox_message_id
-
- def set_tox_message_id(self, tox_message_id):
- self._tox_message_id = tox_message_id
-
- tox_message_id = property(get_tox_message_id, set_tox_message_id)
-
-
-class GroupChatMessage(TextMessage):
-
- def __init__(self, id, message, owner, time, message_type, name):
- super().__init__(id, message, owner, time, message_type)
- self._user_name = name
-
-
-class TransferMessage(Message):
- """
- Message with info about file transfer
- """
-
- def __init__(self, author, time, state, size, file_name, friend_number, file_number):
- super().__init__(MESSAGE_TYPE['FILE_TRANSFER'], author, time)
- self._state = state
- self._size = size
- self._file_name = file_name
- self._friend_number, self._file_number = friend_number, file_number
-
- def is_active(self, file_number):
- if self._file_number != file_number:
- return False
-
- return self._state not in (FILE_TRANSFER_STATE['FINISHED'], FILE_TRANSFER_STATE['CANCELLED'])
-
- def get_friend_number(self):
- return self._friend_number
-
- friend_number = property(get_friend_number)
-
- def get_file_number(self):
- return self._file_number
-
- file_number = property(get_file_number)
-
- def get_state(self):
- return self._state
-
- def set_state(self, value):
- self._state = value
-
- state = property(get_state, set_state)
-
- def get_size(self):
- return self._size
-
- size = property(get_size)
-
- def get_file_name(self):
- return self._file_name
-
- file_name = property(get_file_name)
-
- def transfer_updated(self, state, percentage, time):
- self._state = state
- if self._widget is not None:
- self._widget.update_transfer_state(state, percentage, time)
-
- def _create_widget(self, *args):
- return FileTransferItem(self, *args)
-
-
-class UnsentFileMessage(TransferMessage):
-
- def __init__(self, path, data, time, author, size, friend_number):
- file_name = os.path.basename(path)
- super().__init__(author, time, FILE_TRANSFER_STATE['UNSENT'], size, file_name, friend_number, -1)
- self._data, self._path = data, path
-
- def get_data(self):
- return self._data
-
- data = property(get_data)
-
- def get_path(self):
- return self._path
-
- path = property(get_path)
-
- def _create_widget(self, *args):
- return UnsentFileItem(self, *args)
-
-
-class InlineImageMessage(Message):
- """
- Inline image
- """
-
- def __init__(self, data):
- super().__init__(MESSAGE_TYPE['INLINE'], None, None)
- self._data = data
-
- def get_data(self):
- return self._data
-
- data = property(get_data)
-
- def _create_widget(self, *args):
- return InlineImageItem(self, *args)
-
-
-class InfoMessage(TextMessage):
-
- def __init__(self, message, time):
- super().__init__(message, None, time, MESSAGE_TYPE['INFO_MESSAGE'])
diff --git a/toxygen/messenger/messenger.py b/toxygen/messenger/messenger.py
deleted file mode 100644
index e859135..0000000
--- a/toxygen/messenger/messenger.py
+++ /dev/null
@@ -1,310 +0,0 @@
-import common.tox_save as tox_save
-from messenger.messages import *
-
-
-class Messenger(tox_save.ToxSave):
-
- def __init__(self, tox, plugin_loader, screen, contacts_manager, contacts_provider, items_factory, profile,
- calls_manager):
- super().__init__(tox)
- self._plugin_loader = plugin_loader
- self._screen = screen
- self._contacts_manager = contacts_manager
- self._contacts_provider = contacts_provider
- self._items_factory = items_factory
- self._profile = profile
- self._profile_name = profile.name
-
- profile.name_changed_event.add_callback(self._on_profile_name_changed)
- calls_manager.call_started_event.add_callback(self._on_call_started)
- calls_manager.call_finished_event.add_callback(self._on_call_finished)
-
- def get_last_message(self):
- contact = self._contacts_manager.get_curr_contact()
- if contact is None:
- return str()
-
- return contact.get_last_message_text()
-
- # -----------------------------------------------------------------------------------------------------------------
- # Messaging - friends
- # -----------------------------------------------------------------------------------------------------------------
-
- def new_message(self, friend_number, message_type, message):
- """
- Current user gets new message
- :param friend_number: friend_num of friend who sent message
- :param message_type: message type - plain text or action message (/me)
- :param message: text of message
- """
- t = util.get_unix_time()
- friend = self._get_friend_by_number(friend_number)
- text_message = TextMessage(message, MessageAuthor(friend.name, MESSAGE_AUTHOR['FRIEND']), t, message_type)
- self._add_message(text_message, friend)
-
- def send_message(self):
- text = self._screen.messageEdit.toPlainText()
-
- plugin_command_prefix = '/plugin '
- if text.startswith(plugin_command_prefix):
- self._plugin_loader.command(text[len(plugin_command_prefix):])
- self._screen.messageEdit.clear()
- return
-
- action_message_prefix = '/me '
- if text.startswith(action_message_prefix):
- message_type = TOX_MESSAGE_TYPE['ACTION']
- text = text[len(action_message_prefix):]
- else:
- message_type = TOX_MESSAGE_TYPE['NORMAL']
-
- if self._contacts_manager.is_active_a_friend():
- self.send_message_to_friend(text, message_type)
- elif self._contacts_manager.is_active_a_group():
- self.send_message_to_group(text, message_type)
- elif self._contacts_manager.is_active_a_group_chat_peer():
- self.send_message_to_group_peer(text, message_type)
-
- def send_message_to_friend(self, text, message_type, friend_number=None):
- """
- Send message
- :param text: message text
- :param friend_number: number of friend
- """
- if friend_number is None:
- friend_number = self._contacts_manager.get_active_number()
-
- if not text or friend_number < 0:
- return
-
- friend = self._get_friend_by_number(friend_number)
- messages = self._split_message(text.encode('utf-8'))
- t = util.get_unix_time()
- for message in messages:
- if friend.status is not None:
- message_id = self._tox.friend_send_message(friend_number, message_type, message)
- else:
- message_id = 0
- message_author = MessageAuthor(self._profile.name, MESSAGE_AUTHOR['NOT_SENT'])
- message = OutgoingTextMessage(text, message_author, t, message_type, message_id)
- friend.append_message(message)
- if not self._contacts_manager.is_friend_active(friend_number):
- return
- self._create_message_item(message)
- self._screen.messageEdit.clear()
- self._screen.messages.scrollToBottom()
-
- def send_messages(self, friend_number):
- """
- Send 'offline' messages to friend
- """
- friend = self._get_friend_by_number(friend_number)
- friend.load_corr()
- messages = friend.get_unsent_messages()
- try:
- for message in messages:
- message_id = self._tox.friend_send_message(friend_number, message.type, message.text.encode('utf-8'))
- message.tox_message_id = message_id
- except Exception as ex:
- util.log('Sending pending messages failed with ' + str(ex))
-
- # -----------------------------------------------------------------------------------------------------------------
- # Messaging - groups
- # -----------------------------------------------------------------------------------------------------------------
-
- def send_message_to_group(self, text, message_type, group_number=None):
- if group_number is None:
- group_number = self._contacts_manager.get_active_number()
-
- if not text or group_number < 0:
- return
-
- group = self._get_group_by_number(group_number)
- messages = self._split_message(text.encode('utf-8'))
- t = util.get_unix_time()
- for message in messages:
- self._tox.group_send_message(group_number, message_type, message)
- message_author = MessageAuthor(group.get_self_name(), MESSAGE_AUTHOR['GC_PEER'])
- message = OutgoingTextMessage(text, message_author, t, message_type)
- group.append_message(message)
- if not self._contacts_manager.is_group_active(group_number):
- return
- self._create_message_item(message)
- self._screen.messageEdit.clear()
- self._screen.messages.scrollToBottom()
-
- def new_group_message(self, group_number, message_type, message, peer_id):
- """
- Current user gets new message
- :param message_type: message type - plain text or action message (/me)
- :param message: text of message
- """
- t = util.get_unix_time()
- group = self._get_group_by_number(group_number)
- peer = group.get_peer_by_id(peer_id)
- text_message = TextMessage(message, MessageAuthor(peer.name, MESSAGE_AUTHOR['GC_PEER']), t, message_type)
- self._add_message(text_message, group)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Messaging - group peers
- # -----------------------------------------------------------------------------------------------------------------
-
- def send_message_to_group_peer(self, text, message_type, group_number=None, peer_id=None):
- if group_number is None or peer_id is None:
- group_peer_contact = self._contacts_manager.get_curr_contact()
- peer_id = group_peer_contact.number
- group = self._get_group_by_public_key(group_peer_contact.group_pk)
- group_number = group.number
-
- if not text or group_number < 0 or peer_id < 0:
- return
-
- group_peer_contact = self._contacts_manager.get_or_create_group_peer_contact(group_number, peer_id)
- group = self._get_group_by_number(group_number)
- messages = self._split_message(text.encode('utf-8'))
- t = util.get_unix_time()
- for message in messages:
- self._tox.group_send_private_message(group_number, peer_id, message_type, message)
- message_author = MessageAuthor(group.get_self_name(), MESSAGE_AUTHOR['GC_PEER'])
- message = OutgoingTextMessage(text, message_author, t, message_type)
- group_peer_contact.append_message(message)
- if not self._contacts_manager.is_contact_active(group_peer_contact):
- return
- self._create_message_item(message)
- self._screen.messageEdit.clear()
- self._screen.messages.scrollToBottom()
-
- def new_group_private_message(self, group_number, message_type, message, peer_id):
- """
- Current user gets new message
- :param message: text of message
- """
- t = util.get_unix_time()
- group = self._get_group_by_number(group_number)
- peer = group.get_peer_by_id(peer_id)
- text_message = TextMessage(message, MessageAuthor(peer.name, MESSAGE_AUTHOR['GC_PEER']),
- t, message_type)
- group_peer_contact = self._contacts_manager.get_or_create_group_peer_contact(group_number, peer_id)
- self._add_message(text_message, group_peer_contact)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Message receipts
- # -----------------------------------------------------------------------------------------------------------------
-
- def receipt(self, friend_number, message_id):
- friend = self._get_friend_by_number(friend_number)
- friend.mark_as_sent(message_id)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Typing notifications
- # -----------------------------------------------------------------------------------------------------------------
-
- def send_typing(self, typing):
- """
- Send typing notification to a friend
- """
- if not self._contacts_manager.can_send_typing_notification():
- return
- contact = self._contacts_manager.get_curr_contact()
- contact.typing_notification_handler.send(self._tox, typing)
-
- def friend_typing(self, friend_number, typing):
- """
- Display incoming typing notification
- """
- if self._contacts_manager.is_friend_active(friend_number):
- self._screen.typing.setVisible(typing)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Contact info updated
- # -----------------------------------------------------------------------------------------------------------------
-
- def new_friend_name(self, friend, old_name, new_name):
- if old_name == new_name or friend.has_alias():
- return
- message = util_ui.tr('User {} is now known as {}')
- message = message.format(old_name, new_name)
- if not self._contacts_manager.is_friend_active(friend.number):
- friend.actions = True
- self._add_info_message(friend.number, message)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Private methods
- # -----------------------------------------------------------------------------------------------------------------
-
- @staticmethod
- def _split_message(message):
- messages = []
- while len(message) > TOX_MAX_MESSAGE_LENGTH:
- size = TOX_MAX_MESSAGE_LENGTH * 4 // 5
- last_part = message[size:TOX_MAX_MESSAGE_LENGTH]
- if b' ' in last_part:
- index = last_part.index(b' ')
- elif b',' in last_part:
- index = last_part.index(b',')
- elif b'.' in last_part:
- index = last_part.index(b'.')
- else:
- index = TOX_MAX_MESSAGE_LENGTH - size - 1
- index += size + 1
- messages.append(message[:index])
- message = message[index:]
- if message:
- messages.append(message)
-
- return messages
-
- def _get_friend_by_number(self, friend_number):
- return self._contacts_provider.get_friend_by_number(friend_number)
-
- def _get_group_by_number(self, group_number):
- return self._contacts_provider.get_group_by_number(group_number)
-
- def _get_group_by_public_key(self, public_key):
- return self._contacts_provider.get_group_by_public_key( public_key)
-
- def _on_profile_name_changed(self, new_name):
- if self._profile_name == new_name:
- return
- message = util_ui.tr('User {} is now known as {}')
- message = message.format(self._profile_name, new_name)
- for friend in self._contacts_provider.get_all_friends():
- self._add_info_message(friend.number, message)
- self._profile_name = new_name
-
- def _on_call_started(self, friend_number, audio, video, is_outgoing):
- if is_outgoing:
- text = util_ui.tr("Outgoing video call") if video else util_ui.tr("Outgoing audio call")
- else:
- text = util_ui.tr("Incoming video call") if video else util_ui.tr("Incoming audio call")
- self._add_info_message(friend_number, text)
-
- def _on_call_finished(self, friend_number, is_declined):
- text = util_ui.tr("Call declined") if is_declined else util_ui.tr("Call finished")
- self._add_info_message(friend_number, text)
-
- def _add_info_message(self, friend_number, text):
- friend = self._get_friend_by_number(friend_number)
- message = InfoMessage(text, util.get_unix_time())
- friend.append_message(message)
- if self._contacts_manager.is_friend_active(friend_number):
- self._create_info_message_item(message)
-
- def _create_info_message_item(self, message):
- self._items_factory.create_message_item(message)
- self._screen.messages.scrollToBottom()
-
- def _add_message(self, text_message, contact):
- if self._contacts_manager.is_contact_active(contact): # add message to list
- self._create_message_item(text_message)
- self._screen.messages.scrollToBottom()
- self._contacts_manager.get_curr_contact().append_message(text_message)
- else:
- contact.inc_messages()
- contact.append_message(text_message)
- if not contact.visibility:
- self._contacts_manager.update_filtration()
-
- def _create_message_item(self, text_message):
- # pixmap = self._contacts_manager.get_curr_contact().get_pixmap()
- self._items_factory.create_message_item(text_message)
diff --git a/toxygen/middleware/__init__.py b/toxygen/middleware/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py
deleted file mode 100644
index b9a4099..0000000
--- a/toxygen/middleware/callbacks.py
+++ /dev/null
@@ -1,605 +0,0 @@
-from PyQt5 import QtGui
-from wrapper.toxcore_enums_and_consts import *
-from wrapper.toxav_enums import *
-from wrapper.tox import bin_to_string
-import utils.ui as util_ui
-import utils.util as util
-import cv2
-import numpy as np
-from middleware.threads import invoke_in_main_thread, execute
-from notifications.tray import tray_notification
-from notifications.sound import *
-import threading
-
-# TODO: refactoring. Use contact provider instead of manager
-
-# -----------------------------------------------------------------------------------------------------------------
-# Callbacks - current user
-# -----------------------------------------------------------------------------------------------------------------
-
-
-def self_connection_status(tox, profile):
- """
- Current user changed connection status (offline, TCP, UDP)
- """
- def wrapped(tox_link, connection, user_data):
- print('Connection status: ', str(connection))
- status = tox.self_get_status() if connection != TOX_CONNECTION['NONE'] else None
- invoke_in_main_thread(profile.set_status, status)
-
- return wrapped
-
-
-# -----------------------------------------------------------------------------------------------------------------
-# Callbacks - friends
-# -----------------------------------------------------------------------------------------------------------------
-
-
-def friend_status(contacts_manager, file_transfer_handler, profile, settings):
- def wrapped(tox, friend_number, new_status, user_data):
- """
- Check friend's status (none, busy, away)
- """
- print("Friend's #{} status changed!".format(friend_number))
- friend = contacts_manager.get_friend_by_number(friend_number)
- if friend.status is None and settings['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)
-
- def set_timer():
- t = threading.Timer(5, lambda: file_transfer_handler.send_files(friend_number))
- t.start()
- invoke_in_main_thread(set_timer)
- invoke_in_main_thread(contacts_manager.update_filtration)
-
- return wrapped
-
-
-def friend_connection_status(contacts_manager, profile, settings, plugin_loader, file_transfer_handler,
- messenger, calls_manager):
- def wrapped(tox, friend_number, new_status, user_data):
- """
- Check friend's connection status (offline, udp, tcp)
- """
- print("Friend #{} connection status: {}".format(friend_number, new_status))
- friend = contacts_manager.get_friend_by_number(friend_number)
- if new_status == TOX_CONNECTION['NONE']:
- invoke_in_main_thread(friend.set_status, None)
- invoke_in_main_thread(file_transfer_handler.friend_exit, friend_number)
- invoke_in_main_thread(contacts_manager.update_filtration)
- invoke_in_main_thread(messenger.friend_typing, friend_number, False)
- invoke_in_main_thread(calls_manager.friend_exit, friend_number)
- if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
- sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
- elif friend.status is None:
- invoke_in_main_thread(file_transfer_handler.send_avatar, friend_number)
- invoke_in_main_thread(plugin_loader.friend_online, friend_number)
-
- return wrapped
-
-
-def friend_name(contacts_provider, messenger):
- def wrapped(tox, friend_number, name, size, user_data):
- """
- Friend changed his name
- """
- print('New name friend #' + str(friend_number))
- friend = contacts_provider.get_friend_by_number(friend_number)
- old_name = friend.name
- new_name = str(name, 'utf-8')
- invoke_in_main_thread(friend.set_name, new_name)
- invoke_in_main_thread(messenger.new_friend_name, friend, old_name, new_name)
-
- return wrapped
-
-
-def friend_status_message(contacts_manager, messenger):
- def wrapped(tox, friend_number, status_message, size, user_data):
- """
- :return: function for callback friend_status_message. It updates friend's status message
- and calls window repaint
- """
- friend = contacts_manager.get_friend_by_number(friend_number)
- invoke_in_main_thread(friend.set_status_message, str(status_message, 'utf-8'))
- print('User #{} has new status message'.format(friend_number))
- invoke_in_main_thread(messenger.send_messages, friend_number)
-
- return wrapped
-
-
-def friend_message(messenger, contacts_manager, profile, settings, window, tray):
- def wrapped(tox, friend_number, message_type, message, size, user_data):
- """
- New message from friend
- """
- message = str(message, 'utf-8')
- invoke_in_main_thread(messenger.new_message, friend_number, message_type, message)
- if not window.isActiveWindow():
- friend = contacts_manager.get_friend_by_number(friend_number)
- if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
- invoke_in_main_thread(tray_notification, friend.name, message, tray, window)
- if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
- sound_notification(SOUND_NOTIFICATION['MESSAGE'])
- icon = os.path.join(util.get_images_directory(), 'icon_new_messages.png')
- invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
-
- return wrapped
-
-
-def friend_request(contacts_manager):
- def wrapped(tox, public_key, message, message_size, user_data):
- """
- Called when user get new friend request
- """
- print('Friend request')
- key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE])
- tox_id = bin_to_string(key, TOX_PUBLIC_KEY_SIZE)
- invoke_in_main_thread(contacts_manager.process_friend_request, tox_id, str(message, 'utf-8'))
-
- return wrapped
-
-
-def friend_typing(messenger):
- def wrapped(tox, friend_number, typing, user_data):
- invoke_in_main_thread(messenger.friend_typing, friend_number, typing)
-
- return wrapped
-
-
-def friend_read_receipt(messenger):
- def wrapped(tox, friend_number, message_id, user_data):
- invoke_in_main_thread(messenger.receipt, friend_number, message_id)
-
- return wrapped
-
-
-# -----------------------------------------------------------------------------------------------------------------
-# Callbacks - file transfers
-# -----------------------------------------------------------------------------------------------------------------
-
-
-def tox_file_recv(window, tray, profile, file_transfer_handler, contacts_manager, settings):
- """
- New incoming file
- """
- def wrapped(tox, friend_number, file_number, file_type, size, file_name, file_name_size, user_data):
- if file_type == TOX_FILE_KIND['DATA']:
- print('File')
- try:
- file_name = str(file_name[:file_name_size], 'utf-8')
- except:
- file_name = 'toxygen_file'
- invoke_in_main_thread(file_transfer_handler.incoming_file_transfer,
- friend_number,
- file_number,
- size,
- file_name)
- if not window.isActiveWindow():
- friend = contacts_manager.get_friend_by_number(friend_number)
- if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
- file_from = util_ui.tr("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'])
- icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png')
- invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
- else: # avatar
- print('Avatar')
- invoke_in_main_thread(file_transfer_handler.incoming_avatar,
- friend_number,
- file_number,
- size)
- return wrapped
-
-
-def file_recv_chunk(file_transfer_handler):
- """
- Incoming chunk
- """
- def wrapped(tox, friend_number, file_number, position, chunk, length, user_data):
- chunk = chunk[:length] if length else None
- execute(file_transfer_handler.incoming_chunk, friend_number, file_number, position, chunk)
-
- return wrapped
-
-
-def file_chunk_request(file_transfer_handler):
- """
- Outgoing chunk
- """
- def wrapped(tox, friend_number, file_number, position, size, user_data):
- execute(file_transfer_handler.outgoing_chunk, friend_number, file_number, position, size)
-
- return wrapped
-
-
-def file_recv_control(file_transfer_handler):
- """
- Friend cancelled, paused or resumed file transfer
- """
- def wrapped(tox, friend_number, file_number, file_control, user_data):
- if file_control == TOX_FILE_CONTROL['CANCEL']:
- file_transfer_handler.cancel_transfer(friend_number, file_number, True)
- elif file_control == TOX_FILE_CONTROL['PAUSE']:
- file_transfer_handler.pause_transfer(friend_number, file_number, True)
- elif file_control == TOX_FILE_CONTROL['RESUME']:
- file_transfer_handler.resume_transfer(friend_number, file_number, True)
-
- return wrapped
-
-# -----------------------------------------------------------------------------------------------------------------
-# Callbacks - custom packets
-# -----------------------------------------------------------------------------------------------------------------
-
-
-def lossless_packet(plugin_loader):
- def wrapped(tox, friend_number, data, length, user_data):
- """
- Incoming lossless packet
- """
- data = data[:length]
- invoke_in_main_thread(plugin_loader.callback_lossless, friend_number, data)
-
- return wrapped
-
-
-def lossy_packet(plugin_loader):
- def wrapped(tox, friend_number, data, length, user_data):
- """
- Incoming lossy packet
- """
- data = data[:length]
- invoke_in_main_thread(plugin_loader.callback_lossy, friend_number, data)
-
- return wrapped
-
-
-# -----------------------------------------------------------------------------------------------------------------
-# Callbacks - audio
-# -----------------------------------------------------------------------------------------------------------------
-
-def call_state(calls_manager):
- def wrapped(toxav, friend_number, mask, user_data):
- """
- New call state
- """
- print(friend_number, mask)
- if mask == TOXAV_FRIEND_CALL_STATE['FINISHED'] or mask == TOXAV_FRIEND_CALL_STATE['ERROR']:
- invoke_in_main_thread(calls_manager.stop_call, friend_number, True)
- else:
- calls_manager.toxav_call_state_cb(friend_number, mask)
-
- return wrapped
-
-
-def call(calls_manager):
- def wrapped(toxav, friend_number, audio, video, user_data):
- """
- Incoming call from friend
- """
- print(friend_number, audio, video)
- invoke_in_main_thread(calls_manager.incoming_call, audio, video, friend_number)
-
- return wrapped
-
-
-def callback_audio(calls_manager):
- def wrapped(toxav, friend_number, samples, audio_samples_per_channel, audio_channels_count, rate, user_data):
- """
- New audio chunk
- """
- calls_manager.call.audio_chunk(
- bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]),
- audio_channels_count,
- rate)
-
- return wrapped
-
-# -----------------------------------------------------------------------------------------------------------------
-# Callbacks - video
-# -----------------------------------------------------------------------------------------------------------------
-
-
-def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, ustride, vstride, user_data):
- """
- Creates yuv frame from y, u, v and shows it using OpenCV
- For yuv => bgr we need this YUV420 frame:
-
- width
- -------------------------
- | |
- | Y | height
- | |
- -------------------------
- | | |
- | U even | U odd | height // 4
- | | |
- -------------------------
- | | |
- | V even | V odd | height // 4
- | | |
- -------------------------
-
- width // 2 width // 2
-
- It can be created from initial y, u, v using slices
- """
- try:
- y_size = abs(max(width, abs(ystride)))
- u_size = abs(max(width // 2, abs(ustride)))
- v_size = abs(max(width // 2, abs(vstride)))
-
- y = np.asarray(y[:y_size * height], dtype=np.uint8).reshape(height, y_size)
- u = np.asarray(u[:u_size * height // 2], dtype=np.uint8).reshape(height // 2, u_size)
- v = np.asarray(v[:v_size * height // 2], dtype=np.uint8).reshape(height // 2, v_size)
-
- width -= width % 4
- height -= height % 4
-
- frame = np.zeros((int(height * 1.5), width), dtype=np.uint8)
-
- frame[:height, :] = y[:height, :width]
- frame[height:height * 5 // 4, :width // 2] = u[:height // 2:2, :width // 2]
- frame[height:height * 5 // 4, width // 2:] = u[1:height // 2:2, :width // 2]
-
- frame[height * 5 // 4:, :width // 2] = v[:height // 2:2, :width // 2]
- frame[height * 5 // 4:, width // 2:] = v[1:height // 2:2, :width // 2]
-
- frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420)
-
- invoke_in_main_thread(cv2.imshow, str(friend_number), frame)
- except Exception as ex:
- print(ex)
-
-# -----------------------------------------------------------------------------------------------------------------
-# Callbacks - groups
-# -----------------------------------------------------------------------------------------------------------------
-
-
-def group_message(window, tray, tox, messenger, settings, profile):
- """
- New message in group chat
- """
- def wrapped(tox_link, group_number, peer_id, message_type, message, length, user_data):
- message = str(message[:length], 'utf-8')
- invoke_in_main_thread(messenger.new_group_message, group_number, message_type, message, peer_id)
- if window.isActiveWindow():
- return
- bl = settings['notify_all_gc'] or profile.name in message
- name = tox.group_peer_get_name(group_number, peer_id)
- if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and (not settings.locked) and bl:
- invoke_in_main_thread(tray_notification, name, message, tray, window)
- if settings['sound_notifications'] and bl and profile.status != TOX_USER_STATUS['BUSY']:
- sound_notification(SOUND_NOTIFICATION['MESSAGE'])
- icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png')
- invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
-
- return wrapped
-
-
-def group_private_message(window, tray, tox, messenger, settings, profile):
- """
- New private message in group chat
- """
- def wrapped(tox_link, group_number, peer_id, message_type, message, length, user_data):
- message = str(message[:length], 'utf-8')
- invoke_in_main_thread(messenger.new_group_private_message, group_number, message_type, message, peer_id)
- if window.isActiveWindow():
- return
- bl = settings['notify_all_gc'] or profile.name in message
- name = tox.group_peer_get_name(group_number, peer_id)
- if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and (not settings.locked) and bl:
- invoke_in_main_thread(tray_notification, name, message, tray, window)
- if settings['sound_notifications'] and bl and profile.status != TOX_USER_STATUS['BUSY']:
- sound_notification(SOUND_NOTIFICATION['MESSAGE'])
- icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png')
- invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
-
- return wrapped
-
-
-def group_invite(window, settings, tray, profile, groups_service, contacts_provider):
- def wrapped(tox, friend_number, invite_data, length, group_name, group_name_length, user_data):
- group_name = str(bytes(group_name[:group_name_length]), 'utf-8')
- invoke_in_main_thread(groups_service.process_group_invite,
- friend_number, group_name,
- bytes(invite_data[:length]))
- if window.isActiveWindow():
- return
- if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
- friend = contacts_provider.get_friend_by_number(friend_number)
- title = util_ui.tr('New invite to group chat')
- text = util_ui.tr('{} invites you to group "{}"').format(friend.name, group_name)
- invoke_in_main_thread(tray_notification, title, text, tray, window)
- icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png')
- invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
-
- return wrapped
-
-
-def group_self_join(contacts_provider, contacts_manager, groups_service):
- def wrapped(tox, group_number, user_data):
- group = contacts_provider.get_group_by_number(group_number)
- invoke_in_main_thread(group.set_status, TOX_USER_STATUS['NONE'])
- invoke_in_main_thread(groups_service.update_group_info, group)
- invoke_in_main_thread(contacts_manager.update_filtration)
-
- return wrapped
-
-
-def group_peer_join(contacts_provider, groups_service):
- def wrapped(tox, group_number, peer_id, user_data):
- group = contacts_provider.get_group_by_number(group_number)
- group.add_peer(peer_id)
- invoke_in_main_thread(groups_service.generate_peers_list)
- invoke_in_main_thread(groups_service.update_group_info, group)
-
- return wrapped
-
-
-def group_peer_exit(contacts_provider, groups_service, contacts_manager):
- def wrapped(tox, group_number, peer_id, message, length, user_data):
- group = contacts_provider.get_group_by_number(group_number)
- group.remove_peer(peer_id)
- invoke_in_main_thread(groups_service.generate_peers_list)
-
- return wrapped
-
-
-def group_peer_name(contacts_provider, groups_service):
- def wrapped(tox, group_number, peer_id, name, length, user_data):
- group = contacts_provider.get_group_by_number(group_number)
- peer = group.get_peer_by_id(peer_id)
- peer.name = str(name[:length], 'utf-8')
- invoke_in_main_thread(groups_service.generate_peers_list)
-
- return wrapped
-
-
-def group_peer_status(contacts_provider, groups_service):
- def wrapped(tox, group_number, peer_id, peer_status, user_data):
- group = contacts_provider.get_group_by_number(group_number)
- peer = group.get_peer_by_id(peer_id)
- peer.status = peer_status
- invoke_in_main_thread(groups_service.generate_peers_list)
-
- return wrapped
-
-
-def group_topic(contacts_provider):
- def wrapped(tox, group_number, peer_id, topic, length, user_data):
- group = contacts_provider.get_group_by_number(group_number)
- topic = str(topic[:length], 'utf-8')
- invoke_in_main_thread(group.set_status_message, topic)
-
- return wrapped
-
-
-def group_moderation(groups_service, contacts_provider, contacts_manager, messenger):
-
- def update_peer_role(group, mod_peer_id, peer_id, new_role):
- peer = group.get_peer_by_id(peer_id)
- peer.role = new_role
- # TODO: add info message
-
- def remove_peer(group, mod_peer_id, peer_id, is_ban):
- contacts_manager.remove_group_peer_by_id(group, peer_id)
- group.remove_peer(peer_id)
- # TODO: add info message
-
- def wrapped(tox, group_number, mod_peer_id, peer_id, event_type, user_data):
- group = contacts_provider.get_group_by_number(group_number)
-
- if event_type == TOX_GROUP_MOD_EVENT['KICK']:
- remove_peer(group, mod_peer_id, peer_id, False)
- elif event_type == TOX_GROUP_MOD_EVENT['BAN']:
- remove_peer(group, mod_peer_id, peer_id, True)
- elif event_type == TOX_GROUP_MOD_EVENT['OBSERVER']:
- update_peer_role(group, mod_peer_id, peer_id, TOX_GROUP_ROLE['OBSERVER'])
- elif event_type == TOX_GROUP_MOD_EVENT['USER']:
- update_peer_role(group, mod_peer_id, peer_id, TOX_GROUP_ROLE['USER'])
- elif event_type == TOX_GROUP_MOD_EVENT['MODERATOR']:
- update_peer_role(group, mod_peer_id, peer_id, TOX_GROUP_ROLE['MODERATOR'])
-
- invoke_in_main_thread(groups_service.generate_peers_list)
-
- return wrapped
-
-
-def group_password(contacts_provider):
-
- def wrapped(tox_link, group_number, password, length, user_data):
- password = str(password[:length], 'utf-8')
- group = contacts_provider.get_group_by_number(group_number)
- group.password = password
-
- return wrapped
-
-
-def group_peer_limit(contacts_provider):
-
- def wrapped(tox_link, group_number, peer_limit, user_data):
- group = contacts_provider.get_group_by_number(group_number)
- group.peer_limit = peer_limit
-
- return wrapped
-
-
-def group_privacy_state(contacts_provider):
-
- def wrapped(tox_link, group_number, privacy_state, user_data):
- group = contacts_provider.get_group_by_number(group_number)
- group.is_private = privacy_state == TOX_GROUP_PRIVACY_STATE['PRIVATE']
-
- return wrapped
-
-# -----------------------------------------------------------------------------------------------------------------
-# Callbacks - initialization
-# -----------------------------------------------------------------------------------------------------------------
-
-
-def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager,
- calls_manager, file_transfer_handler, main_window, tray, messenger, groups_service,
- contacts_provider):
- """
- Initialization of all callbacks.
- :param tox: Tox instance
- :param profile: Profile instance
- :param settings: Settings instance
- :param contacts_manager: ContactsManager instance
- :param contacts_manager: ContactsManager instance
- :param calls_manager: CallsManager instance
- :param file_transfer_handler: FileTransferHandler instance
- :param plugin_loader: PluginLoader instance
- :param main_window: MainWindow instance
- :param tray: tray (for notifications)
- :param messenger: Messenger instance
- :param groups_service: GroupsService instance
- :param contacts_provider: ContactsProvider instance
- """
- # self callbacks
- tox.callback_self_connection_status(self_connection_status(tox, profile))
-
- # friend callbacks
- tox.callback_friend_status(friend_status(contacts_manager, file_transfer_handler, profile, settings))
- tox.callback_friend_message(friend_message(messenger, contacts_manager, profile, settings, main_window, tray))
- tox.callback_friend_connection_status(friend_connection_status(contacts_manager, profile, settings, plugin_loader,
- file_transfer_handler, messenger, calls_manager))
- tox.callback_friend_name(friend_name(contacts_provider, messenger))
- tox.callback_friend_status_message(friend_status_message(contacts_manager, messenger))
- tox.callback_friend_request(friend_request(contacts_manager))
- tox.callback_friend_typing(friend_typing(messenger))
- tox.callback_friend_read_receipt(friend_read_receipt(messenger))
-
- # file transfer
- tox.callback_file_recv(tox_file_recv(main_window, tray, profile, file_transfer_handler,
- contacts_manager, settings))
- tox.callback_file_recv_chunk(file_recv_chunk(file_transfer_handler))
- tox.callback_file_chunk_request(file_chunk_request(file_transfer_handler))
- tox.callback_file_recv_control(file_recv_control(file_transfer_handler))
-
- # av
- toxav = tox.AV
- toxav.callback_call_state(call_state(calls_manager), 0)
- toxav.callback_call(call(calls_manager), 0)
- toxav.callback_audio_receive_frame(callback_audio(calls_manager), 0)
- toxav.callback_video_receive_frame(video_receive_frame, 0)
-
- # custom packets
- tox.callback_friend_lossless_packet(lossless_packet(plugin_loader))
- tox.callback_friend_lossy_packet(lossy_packet(plugin_loader))
-
- # gc callbacks
- tox.callback_group_message(group_message(main_window, tray, tox, messenger, settings, profile), 0)
- tox.callback_group_private_message(group_private_message(main_window, tray, tox, messenger, settings, profile), 0)
- tox.callback_group_invite(group_invite(main_window, settings, tray, profile, groups_service, contacts_provider), 0)
- tox.callback_group_self_join(group_self_join(contacts_provider, contacts_manager, groups_service), 0)
- tox.callback_group_peer_join(group_peer_join(contacts_provider, groups_service), 0)
- tox.callback_group_peer_exit(group_peer_exit(contacts_provider, groups_service, contacts_manager), 0)
- tox.callback_group_peer_name(group_peer_name(contacts_provider, groups_service), 0)
- tox.callback_group_peer_status(group_peer_status(contacts_provider, groups_service), 0)
- tox.callback_group_topic(group_topic(contacts_provider), 0)
- tox.callback_group_moderation(group_moderation(groups_service, contacts_provider, contacts_manager, messenger), 0)
- tox.callback_group_password(group_password(contacts_provider), 0)
- tox.callback_group_peer_limit(group_peer_limit(contacts_provider), 0)
- tox.callback_group_privacy_state(group_privacy_state(contacts_provider), 0)
diff --git a/toxygen/middleware/threads.py b/toxygen/middleware/threads.py
deleted file mode 100644
index 5f9404b..0000000
--- a/toxygen/middleware/threads.py
+++ /dev/null
@@ -1,172 +0,0 @@
-from bootstrap.bootstrap import *
-import threading
-import queue
-from utils import util
-import time
-from PyQt5 import QtCore
-
-
-# -----------------------------------------------------------------------------------------------------------------
-# Base threads
-# -----------------------------------------------------------------------------------------------------------------
-
-class BaseThread(threading.Thread):
-
- def __init__(self):
- super().__init__()
- self._stop_thread = False
-
- def stop_thread(self):
- self._stop_thread = True
- self.join()
-
-
-class BaseQThread(QtCore.QThread):
-
- def __init__(self):
- super().__init__()
- self._stop_thread = False
-
- def stop_thread(self):
- self._stop_thread = True
- self.wait()
-
-
-# -----------------------------------------------------------------------------------------------------------------
-# Toxcore threads
-# -----------------------------------------------------------------------------------------------------------------
-
-class InitThread(BaseThread):
-
- def __init__(self, tox, plugin_loader, settings, is_first_start):
- super().__init__()
- self._tox, self._plugin_loader, self._settings = tox, plugin_loader, settings
- self._is_first_start = is_first_start
-
- def run(self):
- if self._is_first_start:
- # download list of nodes if needed
- download_nodes_list(self._settings)
- # start plugins
- self._plugin_loader.load()
-
- # bootstrap
- try:
- for data in generate_nodes():
- if self._stop_thread:
- return
- self._tox.bootstrap(*data)
- self._tox.add_tcp_relay(*data)
- except:
- pass
-
- for _ in range(10):
- if self._stop_thread:
- return
- time.sleep(1)
-
- while not self._tox.self_get_connection_status():
- try:
- for data in generate_nodes(None):
- if self._stop_thread:
- return
- self._tox.bootstrap(*data)
- self._tox.add_tcp_relay(*data)
- except:
- pass
- finally:
- time.sleep(5)
-
-
-class ToxIterateThread(BaseQThread):
-
- def __init__(self, tox):
- super().__init__()
- self._tox = tox
-
- def run(self):
- while not self._stop_thread:
- self._tox.iterate()
- time.sleep(self._tox.iteration_interval() / 1000)
-
-
-class ToxAVIterateThread(BaseQThread):
-
- def __init__(self, toxav):
- super().__init__()
- self._toxav = toxav
-
- def run(self):
- while not self._stop_thread:
- self._toxav.iterate()
- time.sleep(self._toxav.iteration_interval() / 1000)
-
-
-# -----------------------------------------------------------------------------------------------------------------
-# File transfers thread
-# -----------------------------------------------------------------------------------------------------------------
-
-class FileTransfersThread(BaseQThread):
-
- def __init__(self):
- super().__init__()
- self._queue = queue.Queue()
- self._timeout = 0.01
-
- def execute(self, func, *args, **kwargs):
- self._queue.put((func, args, kwargs))
-
- def run(self):
- while not self._stop_thread:
- try:
- func, args, kwargs = self._queue.get(timeout=self._timeout)
- func(*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_file_transfer_thread():
- _thread.start()
-
-
-def stop_file_transfer_thread():
- _thread.stop_thread()
-
-
-def execute(func, *args, **kwargs):
- _thread.execute(func, *args, **kwargs)
-
-
-# -----------------------------------------------------------------------------------------------------------------
-# Invoking in main thread
-# -----------------------------------------------------------------------------------------------------------------
-
-class InvokeEvent(QtCore.QEvent):
- EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
-
- def __init__(self, fn, *args, **kwargs):
- QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
- self.fn = fn
- self.args = args
- self.kwargs = kwargs
-
-
-class Invoker(QtCore.QObject):
-
- def event(self, event):
- event.fn(*event.args, **event.kwargs)
- return True
-
-
-_invoker = Invoker()
-
-
-def invoke_in_main_thread(fn, *args, **kwargs):
- QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
diff --git a/toxygen/middleware/tox_factory.py b/toxygen/middleware/tox_factory.py
deleted file mode 100644
index 9ee5c01..0000000
--- a/toxygen/middleware/tox_factory.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import user_data.settings
-import wrapper.tox
-import wrapper.toxcore_enums_and_consts as enums
-import ctypes
-
-
-def tox_factory(data=None, settings=None):
- """
- :param data: user data from .tox file. None = no saved data, create new profile
- :param settings: current profile settings. None = default settings will be used
- :return: new tox instance
- """
- if settings is None:
- settings = user_data.settings.Settings.get_default_settings()
-
- tox_options = wrapper.tox.Tox.options_new()
- tox_options.contents.udp_enabled = settings['udp_enabled']
- tox_options.contents.proxy_type = settings['proxy_type']
- tox_options.contents.proxy_host = bytes(settings['proxy_host'], 'UTF-8')
- tox_options.contents.proxy_port = settings['proxy_port']
- tox_options.contents.start_port = settings['start_port']
- tox_options.contents.end_port = settings['end_port']
- tox_options.contents.tcp_port = settings['tcp_port']
- tox_options.contents.local_discovery_enabled = settings['lan_discovery']
- if data: # load existing profile
- tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['TOX_SAVE']
- tox_options.contents.savedata_data = ctypes.c_char_p(data)
- tox_options.contents.savedata_length = len(data)
- else: # create new profile
- tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['NONE']
- tox_options.contents.savedata_data = None
- tox_options.contents.savedata_length = 0
-
- return wrapper.tox.Tox(tox_options)
diff --git a/toxygen/network/__init__.py b/toxygen/network/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/toxygen/network/tox_dns.py b/toxygen/network/tox_dns.py
deleted file mode 100644
index 02e97f5..0000000
--- a/toxygen/network/tox_dns.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import json
-import urllib.request
-import utils.util as util
-from PyQt5 import QtNetwork, QtCore
-
-
-class ToxDns:
-
- def __init__(self, settings):
- self._settings = settings
-
- @staticmethod
- def _send_request(url, data):
- req = urllib.request.Request(url)
- req.add_header('Content-Type', 'application/json')
- response = urllib.request.urlopen(req, bytes(json.dumps(data), 'utf-8'))
- res = json.loads(str(response.read(), 'utf-8'))
- if not res['c']:
- return res['tox_id']
- else:
- raise LookupError()
-
- def lookup(self, email):
- """
- TOX DNS 4
- :param email: data like 'groupbot@toxme.io'
- :return: tox id on success else None
- """
- site = email.split('@')[1]
- data = {"action": 3, "name": "{}".format(email)}
- urls = ('https://{}/api'.format(site), 'http://{}/api'.format(site))
- if not self._settings['proxy_type']: # no proxy
- for url in urls:
- try:
- return self._send_request(url, data)
- except Exception as ex:
- util.log('TOX DNS ERROR: ' + str(ex))
- else: # proxy
- netman = QtNetwork.QNetworkAccessManager()
- proxy = QtNetwork.QNetworkProxy()
- if self._settings['proxy_type'] == 2:
- proxy.setType(QtNetwork.QNetworkProxy.Socks5Proxy)
- else:
- proxy.setType(QtNetwork.QNetworkProxy.HttpProxy)
- proxy.setHostName(self._settings['proxy_host'])
- proxy.setPort(self._settings['proxy_port'])
- netman.setProxy(proxy)
- for url in urls:
- try:
- 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'))
-
- while not reply.isFinished():
- QtCore.QThread.msleep(1)
- QtCore.QCoreApplication.processEvents()
- data = bytes(reply.readAll().data())
- result = json.loads(str(data, 'utf-8'))
- if not result['c']:
- return result['tox_id']
- except Exception as ex:
- util.log('TOX DNS ERROR: ' + str(ex))
-
- return None # error
diff --git a/toxygen/notifications.py b/toxygen/notifications.py
new file mode 100644
index 0000000..81a8a05
--- /dev/null
+++ b/toxygen/notifications.py
@@ -0,0 +1,75 @@
+try:
+ from PySide import QtCore, QtGui
+except ImportError:
+ from PyQt4 import QtCore, QtGui
+from util import curr_directory
+import wave
+import pyaudio
+
+
+SOUND_NOTIFICATION = {
+ 'MESSAGE': 0,
+ 'FRIEND_CONNECTION_STATUS': 1,
+ 'FILE_TRANSFER': 2
+}
+
+
+def tray_notification(title, text, tray, window):
+ """
+ Show tray notification and activate window icon
+ NOTE: different behaviour on different OS
+ :param title: Name of user who sent message or file
+ :param text: text of message or file info
+ :param tray: ref to tray icon
+ :param window: main window
+ """
+ if QtGui.QSystemTrayIcon.isSystemTrayAvailable():
+ if len(text) > 30:
+ text = text[:27] + '...'
+ tray.showMessage(title, text, QtGui.QSystemTrayIcon.NoIcon, 3000)
+ QtGui.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)
+
+
+class AudioFile:
+ chunk = 1024
+
+ def __init__(self, fl):
+ self.wf = wave.open(fl, 'rb')
+ self.p = pyaudio.PyAudio()
+ self.stream = self.p.open(
+ format=self.p.get_format_from_width(self.wf.getsampwidth()),
+ channels=self.wf.getnchannels(),
+ rate=self.wf.getframerate(),
+ output=True
+ )
+
+ def play(self):
+ data = self.wf.readframes(self.chunk)
+ while data:
+ self.stream.write(data)
+ data = self.wf.readframes(self.chunk)
+
+ def close(self):
+ self.stream.close()
+ self.p.terminate()
+
+
+def sound_notification(t):
+ """
+ Plays sound notification
+ :param t: type of notification
+ """
+ if t == SOUND_NOTIFICATION['MESSAGE']:
+ f = curr_directory() + '/sounds/message.wav'
+ elif t == SOUND_NOTIFICATION['FILE_TRANSFER']:
+ f = curr_directory() + '/sounds/file.wav'
+ else:
+ f = curr_directory() + '/sounds/contact.wav'
+ a = AudioFile(f)
+ a.play()
+ a.close()
diff --git a/toxygen/notifications/__init__.py b/toxygen/notifications/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/toxygen/notifications/sound.py b/toxygen/notifications/sound.py
deleted file mode 100644
index 361cd05..0000000
--- a/toxygen/notifications/sound.py
+++ /dev/null
@@ -1,54 +0,0 @@
-import utils.util
-import wave
-import pyaudio
-import os.path
-
-
-SOUND_NOTIFICATION = {
- 'MESSAGE': 0,
- 'FRIEND_CONNECTION_STATUS': 1,
- 'FILE_TRANSFER': 2
-}
-
-
-class AudioFile:
- chunk = 1024
-
- def __init__(self, fl):
- self.wf = wave.open(fl, 'rb')
- self.p = pyaudio.PyAudio()
- self.stream = self.p.open(
- format=self.p.get_format_from_width(self.wf.getsampwidth()),
- channels=self.wf.getnchannels(),
- rate=self.wf.getframerate(),
- output=True)
-
- def play(self):
- data = self.wf.readframes(self.chunk)
- while data:
- self.stream.write(data)
- data = self.wf.readframes(self.chunk)
-
- def close(self):
- self.stream.close()
- self.p.terminate()
-
-
-def sound_notification(t):
- """
- Plays sound notification
- :param t: type of notification
- """
- if t == SOUND_NOTIFICATION['MESSAGE']:
- f = get_file_path('message.wav')
- elif t == SOUND_NOTIFICATION['FILE_TRANSFER']:
- f = get_file_path('file.wav')
- else:
- f = get_file_path('contact.wav')
- a = AudioFile(f)
- a.play()
- a.close()
-
-
-def get_file_path(file_name):
- return os.path.join(utils.util.get_sounds_directory(), file_name)
diff --git a/toxygen/notifications/tray.py b/toxygen/notifications/tray.py
deleted file mode 100644
index 4232253..0000000
--- a/toxygen/notifications/tray.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from PyQt5 import QtCore, QtWidgets
-
-
-def tray_notification(title, text, tray, window):
- """
- Show tray notification and activate window icon
- NOTE: different behaviour on different OS
- :param title: Name of user who sent message or file
- :param text: text of message or file info
- :param tray: ref to tray icon
- :param window: main window
- """
- if QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
- if len(text) > 30:
- text = text[:27] + '...'
- 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.messageClicked.connect(message_clicked)
diff --git a/toxygen/ui/password_screen.py b/toxygen/passwordscreen.py
similarity index 56%
rename from toxygen/ui/password_screen.py
rename to toxygen/passwordscreen.py
index bbae7ff..dcd9d05 100644
--- a/toxygen/ui/password_screen.py
+++ b/toxygen/passwordscreen.py
@@ -1,27 +1,28 @@
-from ui.widgets import CenteredWidget, LineEdit, DialogWithResult
-from PyQt5 import QtCore, QtWidgets
-import utils.ui as util_ui
+from widgets import CenteredWidget, LineEdit
+try:
+ from PySide import QtCore, QtGui
+except ImportError:
+ from PyQt4 import QtCore, QtGui
class PasswordArea(LineEdit):
def __init__(self, parent):
- super().__init__(parent)
- self._parent = parent
- self.setEchoMode(QtWidgets.QLineEdit.Password)
+ super(PasswordArea, self).__init__(parent)
+ self.parent = parent
+ self.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Return:
- self._parent.button_click()
+ self.parent.button_click()
else:
- super().keyPressEvent(event)
+ super(PasswordArea, self).keyPressEvent(event)
-class PasswordScreenBase(CenteredWidget, DialogWithResult):
+class PasswordScreenBase(CenteredWidget):
def __init__(self, encrypt):
- CenteredWidget.__init__(self)
- DialogWithResult.__init__(self)
+ super(PasswordScreenBase, self).__init__()
self._encrypt = encrypt
self.initUI()
@@ -30,18 +31,18 @@ class PasswordScreenBase(CenteredWidget, DialogWithResult):
self.setMinimumSize(QtCore.QSize(360, 170))
self.setMaximumSize(QtCore.QSize(360, 170))
- self.enter_pass = QtWidgets.QLabel(self)
+ self.enter_pass = QtGui.QLabel(self)
self.enter_pass.setGeometry(QtCore.QRect(30, 10, 300, 30))
self.password = PasswordArea(self)
self.password.setGeometry(QtCore.QRect(30, 50, 300, 30))
- self.button = QtWidgets.QPushButton(self)
+ self.button = QtGui.QPushButton(self)
self.button.setGeometry(QtCore.QRect(30, 90, 300, 30))
- self.button.setText(util_ui.tr('OK'))
+ self.button.setText('OK')
self.button.clicked.connect(self.button_click)
- self.warning = QtWidgets.QLabel(self)
+ self.warning = QtGui.QLabel(self)
self.warning.setGeometry(QtCore.QRect(30, 130, 300, 30))
self.warning.setStyleSheet('QLabel { color: #F70D1A; }')
self.warning.setVisible(False)
@@ -60,27 +61,28 @@ class PasswordScreenBase(CenteredWidget, DialogWithResult):
super(PasswordScreenBase, self).keyPressEvent(event)
def retranslateUi(self):
- self.setWindowTitle(util_ui.tr('Enter password'))
- self.enter_pass.setText(util_ui.tr('Password:'))
- self.warning.setText(util_ui.tr('Incorrect password'))
+ 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))
class PasswordScreen(PasswordScreenBase):
def __init__(self, encrypt, data):
- super().__init__(encrypt)
+ super(PasswordScreen, self).__init__(encrypt)
self._data = data
def button_click(self):
if self.password.text():
try:
self._encrypt.set_password(self.password.text())
- new_data = self._encrypt.pass_decrypt(self._data)
+ new_data = self._encrypt.pass_decrypt(self._data[0])
except Exception as ex:
self.warning.setVisible(True)
print('Decryption error:', ex)
else:
- self.close_with_result(new_data)
+ self._data[0] = new_data
+ self.close()
class UnlockAppScreen(PasswordScreenBase):
@@ -114,31 +116,37 @@ 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(QtWidgets.QLineEdit.Password)
+ self.password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
self.confirm_password = LineEdit(self)
self.confirm_password.setGeometry(QtCore.QRect(40, 50, 300, 30))
- self.confirm_password.setEchoMode(QtWidgets.QLineEdit.Password)
- self.set_password = QtWidgets.QPushButton(self)
+ self.confirm_password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
+ self.set_password = QtGui.QPushButton(self)
self.set_password.setGeometry(QtCore.QRect(40, 100, 300, 30))
self.set_password.clicked.connect(self.new_password)
- self.not_match = QtWidgets.QLabel(self)
+ self.not_match = QtGui.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 = QtWidgets.QLabel(self)
+ self.warning = QtGui.QLabel(self)
self.warning.setGeometry(QtCore.QRect(40, 160, 500, 30))
self.warning.setStyleSheet('QLabel { color: #BC1C1C; }')
def retranslateUi(self):
- self.setWindowTitle(util_ui.tr('Profile password'))
+ self.setWindowTitle(QtGui.QApplication.translate("PasswordScreen", "Profile password", None,
+ QtGui.QApplication.UnicodeUTF8))
self.password.setPlaceholderText(
- util_ui.tr('Password (at least 8 symbols)'))
+ QtGui.QApplication.translate("PasswordScreen", "Password (at least 8 symbols)", None,
+ QtGui.QApplication.UnicodeUTF8))
self.confirm_password.setPlaceholderText(
- util_ui.tr('Confirm password'))
+ QtGui.QApplication.translate("PasswordScreen", "Confirm password", None,
+ QtGui.QApplication.UnicodeUTF8))
self.set_password.setText(
- util_ui.tr('Set password'))
- self.not_match.setText(util_ui.tr('Passwords do not match'))
- self.warning.setText(util_ui.tr('There is no way to recover lost passwords'))
+ 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))
+ self.warning.setText(
+ QtGui.QApplication.translate("PasswordScreen", "There is no way to recover lost passwords", None,
+ QtGui.QApplication.UnicodeUTF8))
def new_password(self):
if self.password.text() == self.confirm_password.text():
@@ -146,8 +154,11 @@ class SetProfilePasswordScreen(CenteredWidget):
self._encrypt.set_password(self.password.text())
self.close()
else:
- self.not_match.setText(util_ui.tr('Password must be at least 8 symbols'))
+ self.not_match.setText(
+ QtGui.QApplication.translate("PasswordScreen", "Password must be at least 8 symbols", None,
+ QtGui.QApplication.UnicodeUTF8))
self.not_match.setVisible(True)
else:
- self.not_match.setText(util_ui.tr('Passwords do not match'))
+ self.not_match.setText(QtGui.QApplication.translate("PasswordScreen", "Passwords do not match", None,
+ QtGui.QApplication.UnicodeUTF8))
self.not_match.setVisible(True)
diff --git a/toxygen/plugin_support.py b/toxygen/plugin_support.py
new file mode 100644
index 0000000..a4f5891
--- /dev/null
+++ b/toxygen/plugin_support.py
@@ -0,0 +1,167 @@
+import util
+import profile
+import os
+import importlib
+import inspect
+import plugins.plugin_super_class as pl
+import toxencryptsave
+import sys
+
+
+class PluginLoader(util.Singleton):
+
+ def __init__(self, tox, settings):
+ super().__init__()
+ self._profile = profile.Profile.get_instance()
+ self._settings = settings
+ self._plugins = {} # dict. key - plugin unique short name, value - tuple (plugin instance, is active)
+ self._tox = tox
+ self._encr = toxencryptsave.ToxEncryptSave.get_instance()
+
+ def set_tox(self, tox):
+ """
+ New tox instance
+ """
+ self._tox = tox
+ for value in self._plugins.values():
+ value[0].set_tox(tox)
+
+ def load(self):
+ """
+ Load all plugins in plugins folder
+ """
+ path = util.curr_directory() + '/plugins/'
+ if not os.path.exists(path):
+ util.log('Plugin dir not found')
+ return
+ else:
+ sys.path.append(path)
+ files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))]
+ for fl in files:
+ if fl in ('plugin_super_class.py', '__init__.py') or not fl.endswith('.py'):
+ continue
+ name = fl[:-3] # module name without .py
+ try:
+ module = importlib.import_module(name) # import plugin
+ except ImportError:
+ util.log('Import error in module ' + name)
+ continue
+ except Exception as ex:
+ util.log('Exception in module ' + name + ' Exception: ' + str(ex))
+ continue
+ for elem in dir(module):
+ obj = getattr(module, elem)
+ if inspect.isclass(obj) and hasattr(obj, 'is_plugin') and obj.is_plugin: # looking for plugin class in module
+ print('Plugin', elem)
+ try: # create instance of plugin class
+ inst = obj(self._tox, self._profile, self._settings, self._encr)
+ autostart = inst.get_short_name() in self._settings['plugins']
+ if autostart:
+ inst.start()
+ except Exception as ex:
+ util.log('Exception in module ' + name + ' Exception: ' + str(ex))
+ continue
+ self._plugins[inst.get_short_name()] = [inst, autostart] # (inst, is active)
+ break
+
+ def callback_lossless(self, friend_number, data, length):
+ """
+ New incoming custom lossless packet (callback)
+ """
+ l = data[0] - pl.LOSSLESS_FIRST_BYTE
+ name = ''.join(chr(x) for x in data[1:l + 1])
+ if name in self._plugins and self._plugins[name][1]:
+ self._plugins[name][0].lossless_packet(''.join(chr(x) for x in data[l + 1:length]), friend_number)
+
+ def callback_lossy(self, friend_number, data, length):
+ """
+ New incoming custom lossy packet (callback)
+ """
+ l = data[0] - pl.LOSSY_FIRST_BYTE
+ name = ''.join(chr(x) for x in data[1:l + 1])
+ if name in self._plugins and self._plugins[name][1]:
+ self._plugins[name][0].lossy_packet(''.join(chr(x) for x in data[l + 1:length]), friend_number)
+
+ def friend_online(self, friend_number):
+ """
+ Friend with specified number is online
+ """
+ for elem in self._plugins.values():
+ if elem[1]:
+ elem[0].friend_connected(friend_number)
+
+ def get_plugins_list(self):
+ """
+ Returns list of all plugins
+ """
+ result = []
+ for data in self._plugins.values():
+ result.append([data[0].get_name(), # plugin full name
+ data[1], # is enabled
+ data[0].get_description(), # plugin description
+ data[0].get_short_name()]) # key - short unique name
+ return result
+
+ def plugin_window(self, key):
+ """
+ Return window or None for specified plugin
+ """
+ return self._plugins[key][0].get_window()
+
+ def toggle_plugin(self, key):
+ """
+ Enable/disable plugin
+ :param key: plugin short name
+ """
+ plugin = self._plugins[key]
+ if plugin[1]:
+ plugin[0].stop()
+ else:
+ plugin[0].start()
+ plugin[1] = not plugin[1]
+ if plugin[1]:
+ self._settings['plugins'].append(key)
+ else:
+ self._settings['plugins'].remove(key)
+ self._settings.save()
+
+ def command(self, text):
+ """
+ New command for plugin
+ """
+ text = text.strip()
+ name = text.split()[0]
+ if name in self._plugins and self._plugins[name][1]:
+ self._plugins[name][0].command(text[len(name) + 1:])
+
+ def get_menu(self, menu, num):
+ """
+ Return list of items for menu
+ """
+ result = []
+ for elem in self._plugins.values():
+ if elem[1]:
+ try:
+ result.extend(elem[0].get_menu(menu, num))
+ except:
+ continue
+ return result
+
+ def 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
+ """
+ for key in list(self._plugins.keys()):
+ if self._plugins[key][1]:
+ self._plugins[key][0].close()
+ del self._plugins[key]
diff --git a/toxygen/plugin_support/__init__.py b/toxygen/plugin_support/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/toxygen/plugin_support/plugin_support.py b/toxygen/plugin_support/plugin_support.py
deleted file mode 100644
index ed45910..0000000
--- a/toxygen/plugin_support/plugin_support.py
+++ /dev/null
@@ -1,194 +0,0 @@
-import utils.util as util
-import os
-import importlib
-import inspect
-import plugins.plugin_super_class as pl
-import sys
-
-
-class Plugin:
-
- def __init__(self, plugin, is_active):
- self._instance = plugin
- self._is_active = is_active
-
- def get_instance(self):
- return self._instance
-
- instance = property(get_instance)
-
- def get_is_active(self):
- return self._is_active
-
- def set_is_active(self, is_active):
- self._is_active = is_active
-
- is_active = property(get_is_active, set_is_active)
-
-
-class PluginLoader:
-
- def __init__(self, settings, app):
- self._settings = settings
- self._app = app
- self._plugins = {} # dict. key - plugin unique short name, value - Plugin instance
-
- def set_tox(self, tox):
- """
- New tox instance
- """
- for plugin in self._plugins.values():
- plugin.instance.set_tox(tox)
-
- def load(self):
- """
- Load all plugins in plugins folder
- """
- path = util.get_plugins_directory()
- if not os.path.exists(path):
- util.log('Plugin dir not found')
- return
- else:
- sys.path.append(path)
- files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))]
- for fl in files:
- if fl in ('plugin_super_class.py', '__init__.py') or not fl.endswith('.py'):
- continue
- name = fl[:-3] # module name without .py
- try:
- module = importlib.import_module(name) # import plugin
- except ImportError:
- util.log('Import error in module ' + name)
- continue
- except Exception as ex:
- util.log('Exception in module ' + name + ' Exception: ' + str(ex))
- continue
- for elem in dir(module):
- obj = getattr(module, elem)
- # looking for plugin class in module
- if not inspect.isclass(obj) or not hasattr(obj, 'is_plugin') or not obj.is_plugin:
- continue
- print('Plugin', elem)
- try: # create instance of plugin class
- instance = obj(self._app)
- is_active = instance.get_short_name() in self._settings['plugins']
- if is_active:
- instance.start()
- except Exception as ex:
- util.log('Exception in module ' + name + ' Exception: ' + str(ex))
- continue
- self._plugins[instance.get_short_name()] = Plugin(instance, is_active)
- break
-
- 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].is_active:
- self._plugins[name].instance.lossless_packet(''.join(chr(x) for x in data[l + 1:]), friend_number)
-
- 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].is_active:
- self._plugins[name].instance.lossy_packet(''.join(chr(x) for x in data[l + 1:]), friend_number)
-
- def friend_online(self, friend_number):
- """
- Friend with specified number is online
- """
- for plugin in self._plugins.values():
- if plugin.is_active:
- plugin.instance.friend_connected(friend_number)
-
- def get_plugins_list(self):
- """
- Returns list of all plugins
- """
- result = []
- for plugin in self._plugins.values():
- try:
- result.append([plugin.instance.get_name(), # plugin full name
- plugin.is_active, # is enabled
- plugin.instance.get_description(), # plugin description
- plugin.instance.get_short_name()]) # key - short unique name
- except:
- continue
-
- return result
-
- def plugin_window(self, key):
- """
- Return window or None for specified plugin
- """
- return self._plugins[key].instance.get_window()
-
- def toggle_plugin(self, key):
- """
- Enable/disable plugin
- :param key: plugin short name
- """
- plugin = self._plugins[key]
- if plugin.is_active:
- plugin.instance.stop()
- else:
- plugin.instance.start()
- plugin.is_active = not plugin.is_active
- if plugin.is_active:
- self._settings['plugins'].append(key)
- else:
- self._settings['plugins'].remove(key)
- self._settings.save()
-
- def command(self, text):
- """
- New command for plugin
- """
- text = text.strip()
- name = text.split()[0]
- if name in self._plugins and self._plugins[name].is_active:
- self._plugins[name].instance.command(text[len(name) + 1:])
-
- def get_menu(self, num):
- """
- Return list of items for menu
- """
- result = []
- for plugin in self._plugins.values():
- if not plugin.is_active:
- continue
- try:
- result.extend(plugin.instance.get_menu(num))
- except:
- continue
- return result
-
- def get_message_menu(self, menu, selected_text):
- result = []
- for plugin in self._plugins.values():
- if not plugin.is_active:
- continue
- try:
- result.extend(plugin.instance.get_message_menu(menu, selected_text))
- except:
- pass
- return result
-
- def stop(self):
- """
- App is closing, stop all plugins
- """
- for key in list(self._plugins.keys()):
- if self._plugins[key].is_active:
- self._plugins[key].instance.close()
- del self._plugins[key]
-
- def reload(self):
- print('Reloading plugins')
- self.stop()
- self.load()
diff --git a/toxygen/plugins/plugin_super_class.py b/toxygen/plugins/plugin_super_class.py
index 0056d36..84c4a3e 100644
--- a/toxygen/plugins/plugin_super_class.py
+++ b/toxygen/plugins/plugin_super_class.py
@@ -1,7 +1,8 @@
import os
-from PyQt5 import QtCore, QtWidgets
-import utils.ui as util_ui
-import common.tox_save as tox_save
+try:
+ from PySide import QtCore, QtGui
+except ImportError:
+ from PyQt4 import QtCore, QtGui
MAX_SHORT_NAME_LENGTH = 5
@@ -25,25 +26,28 @@ def log(name, data):
:param data: data for saving in log
"""
with open(path_to_data(name) + 'logs.txt', 'a') as fl:
- fl.write(str(data) + '\n')
+ fl.write(bytes(data, 'utf-8') + b'\n')
-class PluginSuperClass(tox_save.ToxSave):
+class PluginSuperClass:
"""
- Superclass for all plugins. Plugin is Python3 module with at least one class derived from PluginSuperClass.
+ Superclass for all plugins. Plugin is python module with at least one class derived from PluginSuperClass.
"""
is_plugin = True
- def __init__(self, name, short_name, app):
+ def __init__(self, name, short_name, tox=None, profile=None, settings=None, encrypt_save=None):
"""
- Constructor. In plugin __init__ should take only 1 last argument
+ Constructor. In plugin __init__ should take only 4 last arguments
:param name: plugin full name
:param short_name: plugin unique short name (length of short name should not exceed MAX_SHORT_NAME_LENGTH)
- :param app: App instance
+ :param tox: tox instance
+ :param profile: profile instance
+ :param settings: profile settings
+ :param encrypt_save: LibToxEncryptSave instance.
"""
- tox = getattr(app, '_tox')
- super().__init__(tox)
- self._settings = getattr(app, '_settings')
+ self._settings = settings
+ self._profile = profile
+ self._tox = tox
name = name.strip()
short_name = short_name.strip()
if not name or not short_name:
@@ -51,6 +55,7 @@ class PluginSuperClass(tox_save.ToxSave):
self._name = name
self._short_name = short_name[:MAX_SHORT_NAME_LENGTH]
self._translator = None # translator for plugin's GUI
+ self._encrypt_save = encrypt_save
# -----------------------------------------------------------------------------------------------------------------
# Get methods
@@ -74,11 +79,12 @@ class PluginSuperClass(tox_save.ToxSave):
"""
return self.__doc__
- def get_menu(self, row_number):
+ def get_menu(self, menu, row_number):
"""
This method creates items for menu which called on right click in list of friends
+ :param menu: menu instance
:param row_number: number of selected row in list of contacts
- :return list of tuples (text, handler)
+ :return list of QAction's
"""
return []
@@ -97,6 +103,12 @@ class PluginSuperClass(tox_save.ToxSave):
"""
return None
+ def set_tox(self, tox):
+ """
+ New tox instance
+ """
+ self._tox = tox
+
# -----------------------------------------------------------------------------------------------------------------
# Plugin was stopped, started or new command received
# -----------------------------------------------------------------------------------------------------------------
@@ -117,7 +129,7 @@ class PluginSuperClass(tox_save.ToxSave):
"""
App is closing
"""
- self.stop()
+ pass
def command(self, command):
"""
@@ -125,9 +137,11 @@ class PluginSuperClass(tox_save.ToxSave):
:param command: string with command
"""
if command == 'help':
- text = util_ui.tr('No commands available')
- title = util_ui.tr('List of commands for plugin {}').format(self._name)
- util_ui.message_box(text, title)
+ msgbox = QtGui.QMessageBox()
+ title = QtGui.QApplication.translate("PluginWindow", "List of commands for plugin {}", None, QtGui.QApplication.UnicodeUTF8)
+ msgbox.setWindowTitle(title.format(self._name))
+ msgbox.setText(QtGui.QApplication.translate("PluginWindow", "No commands available", None, QtGui.QApplication.UnicodeUTF8))
+ msgbox.exec_()
# -----------------------------------------------------------------------------------------------------------------
# Translations support
@@ -137,7 +151,7 @@ class PluginSuperClass(tox_save.ToxSave):
"""
This method loads translations for GUI
"""
- app = QtWidgets.QApplication.instance()
+ app = QtGui.QApplication.instance()
langs = self._settings.supported_languages()
curr_lang = self._settings['language']
if curr_lang in langs:
@@ -155,7 +169,6 @@ class PluginSuperClass(tox_save.ToxSave):
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()
diff --git a/toxygen/profile.py b/toxygen/profile.py
new file mode 100644
index 0000000..ff271d1
--- /dev/null
+++ b/toxygen/profile.py
@@ -0,0 +1,1273 @@
+from list_items import *
+try:
+ from PySide import QtCore, QtGui
+except ImportError:
+ from PyQt4 import QtCore, QtGui
+from friend import *
+from settings import *
+from toxcore_enums_and_consts import *
+from ctypes import *
+from util import log, Singleton, curr_directory
+from tox_dns import tox_dns
+from history import *
+from file_transfers import *
+import time
+import calls
+import avwidgets
+import plugin_support
+
+
+class Profile(contact.Contact, Singleton):
+ """
+ Profile of current toxygen user. Contains friends list, tox instance
+ """
+ def __init__(self, tox, screen):
+ """
+ :param tox: tox instance
+ :param screen: ref to main screen
+ """
+ contact.Contact.__init__(self,
+ tox.self_get_name(),
+ tox.self_get_status_message(),
+ screen.user_info,
+ tox.self_get_address())
+ Singleton.__init__(self)
+ self._screen = screen
+ self._messages = screen.messages
+ self._tox = tox
+ self._file_transfers = {} # dict of file transfers. key - tuple (friend_number, file_number)
+ self._call = calls.AV(tox.AV) # object with data about calls
+ self._incoming_calls = set()
+ self._load_history = True
+ settings = Settings.get_instance()
+ self._show_online = settings['show_online_friends']
+ self._show_avatars = settings['show_avatars']
+ self._friend_item_height = 40 if settings['compact_mode'] else 70
+ self._paused_file_transfers = dict(settings['paused_file_transfers'])
+ # key - file id, value: [path, friend number, is incoming, start position]
+ screen.online_contacts.setCurrentIndex(int(self._show_online))
+ aliases = settings['friends_aliases']
+ data = tox.self_get_friend_list()
+ self._history = History(tox.self_get_public_key()) # connection to db
+ self._friends, self._active_friend = [], -1
+ for i in data: # creates list of friends
+ tox_id = tox.friend_get_public_key(i)
+ try:
+ alias = list(filter(lambda x: x[0] == tox_id, aliases))[0][1]
+ except:
+ alias = ''
+ item = self.create_friend_item()
+ name = alias or tox.friend_get_name(i) or tox_id
+ status_message = tox.friend_get_status_message(i)
+ if not self._history.friend_exists_in_db(tox_id):
+ self._history.add_friend_to_db(tox_id)
+ message_getter = self._history.messages_getter(tox_id)
+ friend = Friend(message_getter, i, name, status_message, item, tox_id)
+ friend.set_alias(alias)
+ self._friends.append(friend)
+ self.filtration(self._show_online)
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Edit current user's data
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def change_status(self):
+ """
+ Changes status of user (online, away, busy)
+ """
+ if self._status is not None:
+ self.set_status((self._status + 1) % 3)
+
+ def set_status(self, status):
+ super(Profile, self).set_status(status)
+ if status is not None:
+ self._tox.self_set_status(status)
+ else:
+ QtCore.QTimer.singleShot(30000, self.reconnect)
+
+ def set_name(self, value):
+ if self.name == value:
+ return
+ tmp = self.name
+ super(Profile, self).set_name(value.encode('utf-8'))
+ self._tox.self_set_name(self._name.encode('utf-8'))
+ message = QtGui.QApplication.translate("MainWindow", 'User {} is now known as {}', None,
+ QtGui.QApplication.UnicodeUTF8)
+ message = message.format(tmp, value)
+ for friend in self._friends:
+ friend.append_message(InfoMessage(message, time.time()))
+ if self._active_friend + 1:
+ self.create_message_item(message, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
+ self._messages.scrollToBottom()
+
+ def set_status_message(self, value):
+ super(Profile, self).set_status_message(value)
+ self._tox.self_set_status_message(self._status_message.encode('utf-8'))
+
+ def new_nospam(self):
+ """Sets new nospam part of tox id"""
+ import random
+ self._tox.self_set_nospam(random.randint(0, 4294967295)) # no spam - uint32
+ self._tox_id = self._tox.self_get_address()
+ return self._tox_id
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Filtration
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def filtration(self, show_online=True, filter_str=''):
+ """
+ Filtration of friends list
+ :param show_online: show online only contacts
+ :param filter_str: show contacts which name contains this substring
+ """
+ filter_str = filter_str.lower()
+ settings = Settings.get_instance()
+ for index, friend in enumerate(self._friends):
+ friend.visibility = (friend.status is not None or not show_online) and (filter_str in friend.name.lower())
+ friend.visibility = friend.visibility or friend.messages or friend.actions
+ if friend.visibility:
+ self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250,
+ self._friend_item_height))
+ else:
+ self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, 0))
+ self._show_online, self._filter_string = show_online, filter_str
+ settings['show_online_friends'] = self._show_online
+ settings.save()
+
+ def update_filtration(self):
+ """
+ Update list of contacts when 1 of friends change connection status
+ """
+ self.filtration(self._show_online, self._filter_string)
+
+ def get_friend_by_number(self, num):
+ return list(filter(lambda x: x.number == num, self._friends))[0]
+
+ def get_friend(self, num):
+ return self._friends[num]
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Work with active friend
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def get_active(self):
+ return self._active_friend
+
+ def set_active(self, value=None):
+ """
+ Change current active friend or update info
+ :param value: number of new active friend in friend's list or None to update active user's data
+ """
+ if value is None and self._active_friend == -1: # nothing to update
+ return
+ if value == -1: # all friends were deleted
+ self._screen.account_name.setText('')
+ self._screen.account_status.setText('')
+ self._active_friend = -1
+ self._screen.account_avatar.setHidden(True)
+ self._messages.clear()
+ self._screen.messageEdit.clear()
+ return
+ try:
+ self.send_typing(False)
+ self._screen.typing.setVisible(False)
+ if value is not None:
+ if self._active_friend + 1 and self._active_friend != value:
+ try:
+ self._friends[self._active_friend].curr_text = self._screen.messageEdit.toPlainText()
+ except:
+ pass
+ friend = self._friends[value]
+ if self._active_friend != value:
+ self._screen.messageEdit.setPlainText(friend.curr_text)
+ self._active_friend = value
+ self._friends[value].reset_messages()
+ self._messages.clear()
+ friend.load_corr()
+ messages = friend.get_corr()[-PAGE_SIZE:]
+ self._load_history = False
+ for message in messages:
+ if message.get_type() <= 1:
+ data = message.get_data()
+ self.create_message_item(data[0],
+ data[2],
+ data[1],
+ data[3])
+ elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']:
+ if message.get_status() is None:
+ self.create_unsent_file_item(message)
+ continue
+ item = self.create_file_transfer_item(message)
+ if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer
+ try:
+ ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())]
+ ft.set_state_changed_handler(item.update)
+ ft.signal()
+ except:
+ print('Incoming not started transfer - no info found')
+ elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline
+ self.create_inline_item(message.get_data())
+ else: # info message
+ data = message.get_data()
+ self.create_message_item(data[0],
+ data[2],
+ '',
+ data[3])
+ self._messages.scrollToBottom()
+ self._load_history = True
+ if value in self._call:
+ self._screen.active_call()
+ elif value in self._incoming_calls:
+ self._screen.incoming_call()
+ else:
+ self._screen.call_finished()
+ else:
+ friend = self._friends[self._active_friend]
+
+ self._screen.account_name.setText(friend.name)
+ self._screen.account_status.setText(friend.status_message)
+ avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(friend.tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
+ if not os.path.isfile(avatar_path): # load default image
+ avatar_path = curr_directory() + '/images/avatar.png'
+ os.chdir(os.path.dirname(avatar_path))
+ pixmap = QtGui.QPixmap(avatar_path)
+ self._screen.account_avatar.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio,
+ QtCore.Qt.SmoothTransformation))
+ self.update_filtration()
+ except Exception as ex: # no friend found. ignore
+ log('Friend value: ' + str(value))
+ log('Error: ' + str(ex))
+ raise
+
+ active_friend = property(get_active, set_active)
+
+ def get_last_message(self):
+ return self._friends[self._active_friend].get_last_message_text()
+
+ def get_active_number(self):
+ return self._friends[self._active_friend].number if self._active_friend + 1 else -1
+
+ def get_active_name(self):
+ return self._friends[self._active_friend].name if self._active_friend + 1 else ''
+
+ def is_active_online(self):
+ return self._active_friend + 1 and self._friends[self._active_friend].status is not None
+
+ def new_name(self, number, name):
+ friend = self.get_friend_by_number(number)
+ tmp = friend.name
+ friend.set_name(name)
+ name = str(name, 'utf-8')
+ if friend.name == name and tmp != name:
+ message = QtGui.QApplication.translate("MainWindow", 'User {} is now known as {}', None, QtGui.QApplication.UnicodeUTF8)
+ message = message.format(tmp, name)
+ friend.append_message(InfoMessage(message, time.time()))
+ friend.actions = True
+ if number == self.get_active_number():
+ self.create_message_item(message, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
+ self._messages.scrollToBottom()
+ self.set_active(None)
+
+ def update(self):
+ if self._active_friend + 1:
+ self.set_active(self._active_friend)
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Friend connection status callbacks
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def send_files(self, friend_number):
+ friend = self.get_friend_by_number(friend_number)
+ files = friend.get_unsent_files()
+ try:
+ for fl in files:
+ data = fl.get_data()
+ if data[1] is not None:
+ self.send_inline(data[1], data[0], friend_number, True)
+ else:
+ self.send_file(data[0], friend_number, True)
+ friend.clear_unsent_files()
+ for key in list(self._paused_file_transfers.keys()):
+ data = self._paused_file_transfers[key]
+ if not os.path.exists(data[0]):
+ del self._paused_file_transfers[key]
+ elif data[1] == friend_number and not data[2]:
+ self.send_file(data[0], friend_number, True, key)
+ del self._paused_file_transfers[key]
+ if friend_number == self.get_active_number():
+ self.update()
+ except Exception as ex:
+ print('Exception in file sending: ' + str(ex))
+
+ def friend_exit(self, friend_number):
+ """
+ Friend with specified number quit
+ """
+ self.get_friend_by_number(friend_number).status = None
+ self.friend_typing(friend_number, False)
+ if friend_number in self._call:
+ self._call.finish_call(friend_number, True)
+ for friend_num, file_num in list(self._file_transfers.keys()):
+ if friend_num == friend_number:
+ ft = self._file_transfers[(friend_num, file_num)]
+ if type(ft) is SendTransfer:
+ self._paused_file_transfers[ft.get_id()] = [ft.get_path(), friend_num, False, -1]
+ elif type(ft) is ReceiveTransfer:
+ self._paused_file_transfers[ft.get_id()] = [ft.get_path(), friend_num, True, ft.total_size()]
+ self.cancel_transfer(friend_num, file_num, True)
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Typing notifications
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def send_typing(self, typing):
+ """
+ Send typing notification to a friend
+ """
+ if Settings.get_instance()['typing_notifications'] and self._active_friend + 1:
+ try:
+ friend = self._friends[self._active_friend]
+ if friend.status is not None:
+ self._tox.self_set_typing(friend.number, typing)
+ except:
+ pass
+
+ def friend_typing(self, friend_number, typing):
+ """
+ Display incoming typing notification
+ """
+ if friend_number == self.get_active_number():
+ self._screen.typing.setVisible(typing)
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Private messages
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def receipt(self):
+ i = 0
+ while i < self._messages.count() and not self._messages.itemWidget(self._messages.item(i)).mark_as_sent():
+ i += 1
+
+ def send_messages(self, friend_number):
+ """
+ Send 'offline' messages to friend
+ """
+ friend = self.get_friend_by_number(friend_number)
+ friend.load_corr()
+ messages = friend.get_unsent_messages()
+ try:
+ for message in messages:
+ self.split_and_send(friend_number, message.get_data()[-1], message.get_data()[0].encode('utf-8'))
+ friend.inc_receipts()
+ except:
+ pass
+
+ def split_and_send(self, number, message_type, message):
+ """
+ Message splitting
+ :param number: friend's number
+ :param message_type: type of message
+ :param message: message text
+ """
+ while len(message) > TOX_MAX_MESSAGE_LENGTH:
+ size = TOX_MAX_MESSAGE_LENGTH * 4 / 5
+ last_part = message[size:TOX_MAX_MESSAGE_LENGTH]
+ if ' ' in last_part:
+ index = last_part.index(' ')
+ elif ',' in last_part:
+ index = last_part.index(',')
+ elif '.' in last_part:
+ index = last_part.index('.')
+ else:
+ index = TOX_MAX_MESSAGE_LENGTH - size - 1
+ index += size + 1
+ self._tox.friend_send_message(number, message_type, message[:index])
+ message = message[index:]
+ self._tox.friend_send_message(number, message_type, message)
+
+ def new_message(self, friend_num, message_type, message):
+ """
+ Current user gets new message
+ :param friend_num: friend_num of friend who sent message
+ :param message_type: message type - plain text or action message (/me)
+ :param message: text of message
+ """
+ if friend_num == self.get_active_number(): # add message to list
+ t = time.time()
+ self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type)
+ self._messages.scrollToBottom()
+ self._friends[self._active_friend].append_message(
+ TextMessage(message, MESSAGE_OWNER['FRIEND'], t, message_type))
+ else:
+ friend = self.get_friend_by_number(friend_num)
+ friend.inc_messages()
+ friend.append_message(
+ TextMessage(message, MESSAGE_OWNER['FRIEND'], time.time(), message_type))
+ if not friend.visibility:
+ self.update_filtration()
+
+ def send_message(self, text, friend_num=None):
+ """
+ Send message
+ :param text: message text
+ :param friend_num: num of friend
+ """
+ if friend_num is None:
+ friend_num = self.get_active_number()
+ if text.startswith('/plugin '):
+ plugin_support.PluginLoader.get_instance().command(text[8:])
+ self._screen.messageEdit.clear()
+ elif text and friend_num + 1:
+ if text.startswith('/me '):
+ message_type = TOX_MESSAGE_TYPE['ACTION']
+ text = text[4:]
+ else:
+ message_type = TOX_MESSAGE_TYPE['NORMAL']
+ friend = self.get_friend_by_number(friend_num)
+ friend.inc_receipts()
+ if friend.status is not None:
+ self.split_and_send(friend.number, message_type, text.encode('utf-8'))
+ if friend.number == self.get_active_number():
+ t = time.time()
+ self.create_message_item(text, t, MESSAGE_OWNER['NOT_SENT'], message_type)
+ self._screen.messageEdit.clear()
+ self._messages.scrollToBottom()
+ friend.append_message(TextMessage(text, MESSAGE_OWNER['NOT_SENT'], t, message_type))
+
+ def delete_message(self, time):
+ friend = self._friends[self._active_friend]
+ friend.delete_message(time)
+ self._history.delete_message(friend.tox_id, time)
+ self.update()
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # History support
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def save_history(self):
+ """
+ Save history to db
+ """
+ s = Settings.get_instance()
+ if hasattr(self, '_history'):
+ if s['save_history']:
+ for friend in self._friends:
+ if not self._history.friend_exists_in_db(friend.tox_id):
+ self._history.add_friend_to_db(friend.tox_id)
+ if not s['save_unsent_only']:
+ messages = friend.get_corr_for_saving()
+ else:
+ messages = friend.get_unsent_messages_for_saving()
+ self._history.delete_messages(friend.tox_id)
+ self._history.save_messages_to_db(friend.tox_id, messages)
+ unsent_messages = friend.get_unsent_messages()
+ unsent_time = unsent_messages[0].get_data()[2] if len(unsent_messages) else time.time() + 1
+ self._history.update_messages(friend.tox_id, unsent_time)
+ self._history.save()
+ del self._history
+
+ def clear_history(self, num=None, save_unsent=False):
+ """
+ Clear chat history
+ """
+ if num is not None:
+ friend = self._friends[num]
+ friend.clear_corr(save_unsent)
+ if self._history.friend_exists_in_db(friend.tox_id):
+ self._history.delete_messages(friend.tox_id)
+ self._history.delete_friend_from_db(friend.tox_id)
+ else: # clear all history
+ for number in range(len(self._friends)):
+ self.clear_history(number, save_unsent)
+ if num is None or num == self.get_active_number():
+ self.update()
+
+ def load_history(self):
+ """
+ Tries to load next part of messages
+ """
+ if not self._load_history:
+ return
+ self._load_history = False
+ friend = self._friends[self._active_friend]
+ friend.load_corr(False)
+ data = friend.get_corr()
+ if not data:
+ return
+ data.reverse()
+ data = data[self._messages.count():self._messages.count() + PAGE_SIZE]
+ for message in data:
+ if message.get_type() <= 1: # text message
+ data = message.get_data()
+ self.create_message_item(data[0],
+ data[2],
+ data[1],
+ data[3],
+ False)
+ elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: # file transfer
+ if message.get_status() is None:
+ self.create_unsent_file_item(message)
+ continue
+ item = self.create_file_transfer_item(message, False)
+ if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer
+ try:
+ ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())]
+ ft.set_state_changed_handler(item.update)
+ ft.signal()
+ except:
+ print('Incoming not started transfer - no info found')
+ elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline image
+ self.create_inline_item(message.get_data(), False)
+ else: # info message
+ data = message.get_data()
+ self.create_message_item(data[0],
+ data[2],
+ '',
+ data[3],
+ False)
+ self._load_history = True
+
+ def export_db(self, directory):
+ self._history.export(directory)
+
+ def export_history(self, num, as_text=True, _range=None):
+ friend = self._friends[num]
+ if _range is None:
+ friend.load_all_corr()
+ if _range is None:
+ corr = friend.get_corr()
+ elif _range[1] + 1:
+ corr = friend.get_corr()[_range[0]:_range[1] + 1]
+ else:
+ corr = friend.get_corr()[_range[0]:]
+ arr = []
+ new_line = '\n' if as_text else '
'
+ for message in corr:
+ if type(message) is TextMessage:
+ data = message.get_data()
+ if as_text:
+ x = '[{}] {}: {}\n'
+ else:
+ x = '[{}] {}: {}
'
+ arr.append(x.format(convert_time(data[2]) if data[1] != MESSAGE_OWNER['NOT_SENT'] else 'Unsent',
+ friend.name if data[1] == MESSAGE_OWNER['FRIEND'] else self.name,
+ data[0]))
+ s = new_line.join(arr)
+ return s
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Factories for friend, message and file transfer items
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def create_friend_item(self):
+ """
+ Method-factory
+ :return: new widget for friend instance
+ """
+ item = ContactItem()
+ elem = QtGui.QListWidgetItem(self._screen.friends_list)
+ elem.setSizeHint(QtCore.QSize(250, item.height()))
+ self._screen.friends_list.addItem(elem)
+ self._screen.friends_list.setItemWidget(elem, item)
+ return item
+
+ def create_message_item(self, text, time, owner, message_type, append=True):
+ if message_type == MESSAGE_TYPE['INFO_MESSAGE']:
+ name = ''
+ elif owner == MESSAGE_OWNER['FRIEND']:
+ name = self.get_active_name()
+ else:
+ name = self._name
+ item = MessageItem(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'], message_type, self._messages)
+ if self._show_avatars:
+ item.set_avatar(self._friends[self._active_friend].get_pixmap() if owner == MESSAGE_OWNER[
+ 'FRIEND'] else self.get_pixmap())
+ elem = QtGui.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)
+
+ def create_file_transfer_item(self, tm, append=True):
+ data = list(tm.get_data())
+ data[3] = self.get_friend_by_number(data[4]).name if data[3] else self._name
+ data.append(self._messages.width())
+ item = FileTransferItem(*data)
+ elem = QtGui.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 create_unsent_file_item(self, message, append=True):
+ data = message.get_data()
+ item = UnsentFileItem(os.path.basename(data[0]),
+ os.path.getsize(data[0]) if data[1] is None else len(data[1]),
+ self.name,
+ data[2],
+ self._messages.width())
+ elem = QtGui.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)
+
+ def create_inline_item(self, data, append=True):
+ elem = QtGui.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)
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Work with friends (remove, block, set alias, get public key)
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def set_alias(self, num):
+ """
+ Set new alias for friend
+ """
+ friend = self._friends[num]
+ name = friend.name
+ dialog = QtGui.QApplication.translate('MainWindow',
+ "Enter new alias for friend {} or leave empty to use friend's name:",
+ None, QtGui.QApplication.UnicodeUTF8)
+ dialog = dialog.format(name)
+ title = QtGui.QApplication.translate('MainWindow',
+ 'Set alias',
+ None, QtGui.QApplication.UnicodeUTF8)
+ text, ok = QtGui.QInputDialog.getText(None,
+ title,
+ dialog,
+ QtGui.QLineEdit.Normal,
+ name)
+ if ok:
+ settings = Settings.get_instance()
+ aliases = settings['friends_aliases']
+ if text:
+ friend.name = bytes(text, 'utf-8')
+ try:
+ index = list(map(lambda x: x[0], aliases)).index(friend.tox_id)
+ aliases[index] = (friend.tox_id, text)
+ except:
+ aliases.append((friend.tox_id, text))
+ friend.set_alias(text)
+ else: # use default name
+ friend.name = bytes(self._tox.friend_get_name(friend.number), 'utf-8')
+ friend.set_alias('')
+ try:
+ index = list(map(lambda x: x[0], aliases)).index(friend.tox_id)
+ del aliases[index]
+ except:
+ pass
+ settings.save()
+ if num == self.get_active_number():
+ self.update()
+
+ def friend_public_key(self, num):
+ return self._friends[num].tox_id
+
+ def delete_friend(self, num):
+ """
+ Removes friend from contact list
+ :param num: number of friend in list
+ """
+ friend = self._friends[num]
+ settings = Settings.get_instance()
+ try:
+ index = list(map(lambda x: x[0], settings['friends_aliases'])).index(friend.tox_id)
+ del settings['friends_aliases'][index]
+ except:
+ pass
+ if friend.tox_id in settings['notes']:
+ del settings['notes'][friend.tox_id]
+ settings.save()
+ self.clear_history(num)
+ if self._history.friend_exists_in_db(friend.tox_id):
+ self._history.delete_friend_from_db(friend.tox_id)
+ self._tox.friend_delete(friend.number)
+ del self._friends[num]
+ self._screen.friends_list.takeItem(num)
+ if num == self._active_friend: # active friend was deleted
+ if not len(self._friends): # last friend was deleted
+ self.set_active(-1)
+ else:
+ self.set_active(0)
+ data = self._tox.get_savedata()
+ ProfileHelper.get_instance().save_profile(data)
+
+ def add_friend(self, tox_id):
+ """
+ Adds friend to list
+ """
+ num = self._tox.friend_add_norequest(tox_id) # num - friend number
+ item = self.create_friend_item()
+ try:
+ if not self._history.friend_exists_in_db(tox_id):
+ self._history.add_friend_to_db(tox_id)
+ message_getter = self._history.messages_getter(tox_id)
+ except Exception as ex: # something is wrong
+ log('Accept friend request failed! ' + str(ex))
+ message_getter = None
+ friend = Friend(message_getter, num, tox_id, '', item, tox_id)
+ self._friends.append(friend)
+
+ def block_user(self, tox_id):
+ """
+ Block user with specified tox id (or public key) - delete from friends list and ignore friend requests
+ """
+ tox_id = tox_id[:TOX_PUBLIC_KEY_SIZE * 2]
+ if tox_id == self.tox_id[:TOX_PUBLIC_KEY_SIZE * 2]:
+ return
+ settings = Settings.get_instance()
+ if tox_id not in settings['blocked']:
+ settings['blocked'].append(tox_id)
+ settings.save()
+ try:
+ num = self._tox.friend_by_public_key(tox_id)
+ self.delete_friend(num)
+ data = self._tox.get_savedata()
+ ProfileHelper.get_instance().save_profile(data)
+ except: # not in friend list
+ pass
+
+ def unblock_user(self, tox_id, add_to_friend_list):
+ """
+ Unblock user
+ :param tox_id: tox id of contact
+ :param add_to_friend_list: add this contact to friend list or not
+ """
+ s = Settings.get_instance()
+ s['blocked'].remove(tox_id)
+ s.save()
+ if add_to_friend_list:
+ self.add_friend(tox_id)
+ data = self._tox.get_savedata()
+ ProfileHelper.get_instance().save_profile(data)
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Friend requests
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def send_friend_request(self, tox_id, message):
+ """
+ Function tries to send request to contact with specified id
+ :param tox_id: id of new contact or tox dns 4 value
+ :param message: additional message
+ :return: True on success else error string
+ """
+ try:
+ message = message or 'Hello! Add me to your contact list please'
+ if '@' in tox_id: # value like groupbot@toxme.io
+ tox_id = tox_dns(tox_id)
+ if tox_id is None:
+ raise Exception('TOX DNS lookup failed')
+ if len(tox_id) == TOX_PUBLIC_KEY_SIZE * 2: # public key
+ self.add_friend(tox_id)
+ msgBox = QtGui.QMessageBox()
+ msgBox.setWindowTitle(QtGui.QApplication.translate("MainWindow", "Friend added", None, QtGui.QApplication.UnicodeUTF8))
+ text = (QtGui.QApplication.translate("MainWindow", 'Friend added without sending friend request', None, QtGui.QApplication.UnicodeUTF8))
+ msgBox.setText(text)
+ msgBox.exec_()
+ else:
+ result = self._tox.friend_add(tox_id, message.encode('utf-8'))
+ tox_id = tox_id[:TOX_PUBLIC_KEY_SIZE * 2]
+ item = self.create_friend_item()
+ if not self._history.friend_exists_in_db(tox_id):
+ self._history.add_friend_to_db(tox_id)
+ message_getter = self._history.messages_getter(tox_id)
+ friend = Friend(message_getter, result, tox_id, '', item, tox_id)
+ self._friends.append(friend)
+ data = self._tox.get_savedata()
+ ProfileHelper.get_instance().save_profile(data)
+ return True
+ except Exception as ex: # wrong data
+ log('Friend request failed with ' + str(ex))
+ return str(ex)
+
+ def process_friend_request(self, tox_id, message):
+ """
+ Accept or ignore friend request
+ :param tox_id: tox id of contact
+ :param message: message
+ """
+ try:
+ text = QtGui.QApplication.translate('MainWindow', 'User {} wants to add you to contact list. Message:\n{}', None, QtGui.QApplication.UnicodeUTF8)
+ info = text.format(tox_id, message)
+ fr_req = QtGui.QApplication.translate('MainWindow', 'Friend request', None, QtGui.QApplication.UnicodeUTF8)
+ reply = QtGui.QMessageBox.question(None, fr_req, info, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
+ if reply == QtGui.QMessageBox.Yes: # accepted
+ self.add_friend(tox_id)
+ data = self._tox.get_savedata()
+ ProfileHelper.get_instance().save_profile(data)
+ except Exception as ex: # something is wrong
+ log('Accept friend request failed! ' + str(ex))
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Reset
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def reset(self, restart):
+ """
+ Recreate tox instance
+ :param restart: method which calls restart and returns new tox instance
+ """
+ for friend in self._friends:
+ self.friend_exit(friend.number)
+ self._call.stop()
+ del self._call
+ del self._tox
+ self._tox = restart()
+ self._call = calls.AV(self._tox.AV)
+ self.status = None
+ for friend in self._friends:
+ friend.number = self._tox.friend_by_public_key(friend.tox_id) # numbers update
+ self.update_filtration()
+
+ def reconnect(self):
+ if self.status is None or all(list(map(lambda x: x.status is None, self._friends))):
+ self.reset(self._screen.reset)
+ QtCore.QTimer.singleShot(30000, self.reconnect)
+
+ def close(self):
+ for friend in self._friends:
+ self.friend_exit(friend.number)
+ for i in range(len(self._friends)):
+ del self._friends[0]
+ if hasattr(self, '_call'):
+ self._call.stop()
+ del self._call
+ s = Settings.get_instance()
+ s['paused_file_transfers'] = dict(self._paused_file_transfers) if s['resend_files'] else {}
+ s.save()
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # File transfers support
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def incoming_file_transfer(self, friend_number, file_number, size, file_name):
+ """
+ New transfer
+ :param friend_number: number of friend who sent file
+ :param file_number: file number
+ :param size: file size in bytes
+ :param file_name: file name without path
+ """
+ settings = Settings.get_instance()
+ friend = self.get_friend_by_number(friend_number)
+ auto = settings['allow_auto_accept'] and friend.tox_id in settings['auto_accept_from_friends']
+ inline = (file_name in ALLOWED_FILES) and settings['allow_inline']
+ file_id = self._tox.file_get_file_id(friend_number, file_number)
+ accepted = True
+ if file_id in self._paused_file_transfers:
+ data = self._paused_file_transfers[file_id]
+ pos = data[-1] if os.path.exists(data[0]) else 0
+ if pos >= size:
+ self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
+ return
+ self._tox.file_seek(friend_number, file_number, pos)
+ self.accept_transfer(None, data[0], friend_number, file_number, size, False, pos)
+ tm = TransferMessage(MESSAGE_OWNER['FRIEND'],
+ time.time(),
+ TOX_FILE_TRANSFER_STATE['RUNNING'],
+ size,
+ file_name,
+ friend_number,
+ file_number)
+ elif inline and size < 1024 * 1024:
+ self.accept_transfer(None, '', friend_number, file_number, size, True)
+ tm = TransferMessage(MESSAGE_OWNER['FRIEND'],
+ time.time(),
+ TOX_FILE_TRANSFER_STATE['RUNNING'],
+ size,
+ file_name,
+ friend_number,
+ file_number)
+
+ elif auto:
+ path = settings['auto_accept_path'] or curr_directory()
+ self.accept_transfer(None, path + '/' + file_name, friend_number, file_number, size)
+ tm = TransferMessage(MESSAGE_OWNER['FRIEND'],
+ time.time(),
+ TOX_FILE_TRANSFER_STATE['RUNNING'],
+ size,
+ file_name,
+ friend_number,
+ file_number)
+ else:
+ tm = TransferMessage(MESSAGE_OWNER['FRIEND'],
+ time.time(),
+ TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED'],
+ size,
+ file_name,
+ friend_number,
+ file_number)
+ accepted = False
+ if friend_number == self.get_active_number():
+ item = self.create_file_transfer_item(tm)
+ if accepted:
+ self._file_transfers[(friend_number, file_number)].set_state_changed_handler(item.update)
+ self._messages.scrollToBottom()
+ else:
+ friend.actions = True
+
+ friend.append_message(tm)
+
+ def cancel_transfer(self, friend_number, file_number, already_cancelled=False):
+ """
+ Stop transfer
+ :param friend_number: number of friend
+ :param file_number: file number
+ :param already_cancelled: was cancelled by friend
+ """
+ i = self.get_friend_by_number(friend_number).update_transfer_data(file_number,
+ TOX_FILE_TRANSFER_STATE['CANCELLED'])
+ if (friend_number, file_number) in self._file_transfers:
+ tr = self._file_transfers[(friend_number, file_number)]
+ if not already_cancelled:
+ tr.cancel()
+ else:
+ tr.cancelled()
+ if (friend_number, file_number) in self._file_transfers:
+ del tr
+ del self._file_transfers[(friend_number, file_number)]
+ else:
+ if not already_cancelled:
+ self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
+ if friend_number == self.get_active_number():
+ tmp = self._messages.count() + i
+ if tmp >= 0:
+ self._messages.itemWidget(self._messages.item(tmp)).update(TOX_FILE_TRANSFER_STATE['CANCELLED'],
+ 0, -1)
+
+ def cancel_not_started_transfer(self, time):
+ self._friends[self._active_friend].delete_one_unsent_file(time)
+ self.update()
+
+ def pause_transfer(self, friend_number, file_number, by_friend=False):
+ """
+ Pause transfer with specified data
+ """
+ tr = self._file_transfers[(friend_number, file_number)]
+ tr.pause(by_friend)
+ t = TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'] if by_friend else TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']
+ self.get_friend_by_number(friend_number).update_transfer_data(file_number, t)
+
+ def resume_transfer(self, friend_number, file_number, by_friend=False):
+ """
+ Resume transfer with specified data
+ """
+ self.get_friend_by_number(friend_number).update_transfer_data(file_number,
+ TOX_FILE_TRANSFER_STATE['RUNNING'])
+ tr = self._file_transfers[(friend_number, file_number)]
+ if by_friend:
+ tr.state = TOX_FILE_TRANSFER_STATE['RUNNING']
+ tr.signal()
+ else:
+ tr.send_control(TOX_FILE_CONTROL['RESUME'])
+
+ def accept_transfer(self, item, path, friend_number, file_number, size, inline=False, from_position=0):
+ """
+ :param item: transfer item.
+ :param path: path for saving
+ :param friend_number: friend number
+ :param file_number: file number
+ :param size: file size
+ :param inline: is inline image
+ :param from_position: position for start
+ """
+ path, file_name = os.path.split(path)
+ new_file_name, i = file_name, 1
+ if not from_position:
+ while os.path.isfile(path + '/' + new_file_name): # file with same name already exists
+ if '.' in file_name: # has extension
+ d = file_name.rindex('.')
+ else: # no extension
+ d = len(file_name)
+ new_file_name = file_name[:d] + ' ({})'.format(i) + file_name[d:]
+ i += 1
+ path = os.path.join(path, new_file_name)
+ if not inline:
+ rt = ReceiveTransfer(path, self._tox, friend_number, size, file_number, from_position)
+ else:
+ rt = ReceiveToBuffer(self._tox, friend_number, size, file_number)
+ rt.set_transfer_finished_handler(self.transfer_finished)
+ self._file_transfers[(friend_number, file_number)] = rt
+ self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['RESUME'])
+ if item is not None:
+ rt.set_state_changed_handler(item.update)
+ self.get_friend_by_number(friend_number).update_transfer_data(file_number,
+ TOX_FILE_TRANSFER_STATE['RUNNING'])
+
+ def send_screenshot(self, data):
+ """
+ Send screenshot to current active friend
+ :param data: raw data - png
+ """
+ self.send_inline(data, 'toxygen_inline.png')
+ self._messages.repaint()
+
+ def send_sticker(self, path):
+ with open(path, 'rb') as fl:
+ data = fl.read()
+ self.send_inline(data, 'sticker.png')
+
+ def send_inline(self, data, file_name, friend_number=None, is_resend=False):
+ friend_number = friend_number or self.get_active_number()
+ friend = self.get_friend_by_number(friend_number)
+ if friend.status is None and not is_resend:
+ m = UnsentFile(file_name, data, time.time())
+ friend.append_message(m)
+ self.update()
+ return
+ elif friend.status is None and is_resend:
+ raise RuntimeError()
+ st = SendFromBuffer(self._tox, friend.number, data, file_name)
+ st.set_transfer_finished_handler(self.transfer_finished)
+ self._file_transfers[(friend.number, st.get_file_number())] = st
+ tm = TransferMessage(MESSAGE_OWNER['ME'],
+ time.time(),
+ TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'],
+ len(data),
+ file_name,
+ friend.number,
+ st.get_file_number())
+ item = self.create_file_transfer_item(tm)
+ friend.append_message(tm)
+ st.set_state_changed_handler(item.update)
+ self._messages.scrollToBottom()
+
+ def send_file(self, path, number=None, is_resend=False, file_id=None):
+ """
+ Send file to current active friend
+ :param path: file path
+ :param number: friend_number
+ :param is_resend: is 'offline' message
+ :param file_id: file id of transfer
+ """
+ friend_number = self.get_active_number() if number is None else number
+ friend = self.get_friend_by_number(friend_number)
+ if friend.status is None and not is_resend:
+ m = UnsentFile(path, None, time.time())
+ friend.append_message(m)
+ self.update()
+ return
+ elif friend.status is None and is_resend:
+ print('Error in sending')
+ raise RuntimeError()
+ st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id)
+ st.set_transfer_finished_handler(self.transfer_finished)
+ self._file_transfers[(friend_number, st.get_file_number())] = st
+ tm = TransferMessage(MESSAGE_OWNER['ME'],
+ time.time(),
+ TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'],
+ os.path.getsize(path),
+ os.path.basename(path),
+ friend_number,
+ st.get_file_number())
+ if friend_number == self.get_active_number():
+ item = self.create_file_transfer_item(tm)
+ st.set_state_changed_handler(item.update)
+ self._messages.scrollToBottom()
+ self._friends[friend_number].append_message(tm)
+
+ def incoming_chunk(self, friend_number, file_number, position, data):
+ """
+ Incoming chunk
+ """
+ self._file_transfers[(friend_number, file_number)].write_chunk(position, data)
+
+ def outgoing_chunk(self, friend_number, file_number, position, size):
+ """
+ Outgoing chunk
+ """
+ self._file_transfers[(friend_number, file_number)].send_chunk(position, size)
+
+ @QtCore.Slot(int, int)
+ def transfer_finished(self, friend_number, file_number):
+ transfer = self._file_transfers[(friend_number, file_number)]
+ t = type(transfer)
+ if t is ReceiveAvatar:
+ self.get_friend_by_number(friend_number).load_avatar()
+ self.set_active(None)
+ elif t is ReceiveToBuffer or (t is SendFromBuffer and Settings.get_instance()['allow_inline']): # inline image
+ print('inline')
+ inline = InlineImage(transfer.get_data())
+ i = self.get_friend_by_number(friend_number).update_transfer_data(file_number,
+ TOX_FILE_TRANSFER_STATE['FINISHED'],
+ inline)
+ if friend_number == self.get_active_number():
+ count = self._messages.count()
+ if count + i + 1 >= 0:
+ elem = QtGui.QListWidgetItem()
+ item = InlineImageItem(transfer.get_data(), self._messages.width(), elem)
+ elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
+ self._messages.insertItem(count + i + 1, elem)
+ self._messages.setItemWidget(elem, item)
+ elif t is not SendAvatar:
+ self.get_friend_by_number(friend_number).update_transfer_data(file_number,
+ TOX_FILE_TRANSFER_STATE['FINISHED'])
+ del self._file_transfers[(friend_number, file_number)]
+ del transfer
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # Avatars support
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def send_avatar(self, friend_number):
+ """
+ :param friend_number: number of friend who should get new avatar
+ """
+ avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
+ if not os.path.isfile(avatar_path): # reset image
+ avatar_path = None
+ sa = SendAvatar(avatar_path, self._tox, friend_number)
+ self._file_transfers[(friend_number, sa.get_file_number())] = sa
+
+ def incoming_avatar(self, friend_number, file_number, size):
+ """
+ Friend changed avatar
+ :param friend_number: friend number
+ :param file_number: file number
+ :param size: size of avatar or 0 (default avatar)
+ """
+ ra = ReceiveAvatar(self._tox, friend_number, size, file_number)
+ if ra.state != TOX_FILE_TRANSFER_STATE['CANCELLED']:
+ self._file_transfers[(friend_number, file_number)] = ra
+ else:
+ self.get_friend_by_number(friend_number).load_avatar()
+ if self.get_active_number() == friend_number:
+ self.set_active(None)
+
+ def reset_avatar(self):
+ super(Profile, self).reset_avatar()
+ for friend in filter(lambda x: x.status is not None, self._friends):
+ self.send_avatar(friend.number)
+
+ def set_avatar(self, data):
+ super(Profile, self).set_avatar(data)
+ for friend in filter(lambda x: x.status is not None, self._friends):
+ self.send_avatar(friend.number)
+
+ # -----------------------------------------------------------------------------------------------------------------
+ # AV support
+ # -----------------------------------------------------------------------------------------------------------------
+
+ def get_call(self):
+ return self._call
+
+ call = property(get_call)
+
+ def call_click(self, audio=True, video=False):
+ """User clicked audio button in main window"""
+ num = self.get_active_number()
+ if num not in self._call and self.is_active_online(): # start call
+ if not Settings.get_instance().audio['enabled']:
+ return
+ self._call(num, audio, video)
+ self._screen.active_call()
+ if video:
+ text = QtGui.QApplication.translate("incoming_call", "Outgoing video call", None,
+ QtGui.QApplication.UnicodeUTF8)
+ else:
+ text = QtGui.QApplication.translate("incoming_call", "Outgoing audio call", None,
+ QtGui.QApplication.UnicodeUTF8)
+ self._friends[self._active_friend].append_message(InfoMessage(text, time.time()))
+ self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
+ self._messages.scrollToBottom()
+ elif num in self._call: # finish or cancel call if you call with active friend
+ self.stop_call(num, False)
+
+ def incoming_call(self, audio, video, friend_number):
+ """
+ Incoming call from friend. Only audio is supported now
+ """
+ if not Settings.get_instance().audio['enabled']:
+ return
+ friend = self.get_friend_by_number(friend_number)
+ if video:
+ text = QtGui.QApplication.translate("incoming_call", "Incoming video call", None,
+ QtGui.QApplication.UnicodeUTF8)
+ else:
+ text = QtGui.QApplication.translate("incoming_call", "Incoming audio call", None,
+ QtGui.QApplication.UnicodeUTF8)
+ friend.append_message(InfoMessage(text, time.time()))
+ self._incoming_calls.add(friend_number)
+ if friend_number == self.get_active_number():
+ self._screen.incoming_call()
+ self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
+ self._messages.scrollToBottom()
+ else:
+ friend.actions = True
+ # TODO: dict of widgets
+ self._call_widget = avwidgets.IncomingCallWidget(friend_number, text, friend.name)
+ self._call_widget.set_pixmap(friend.get_pixmap())
+ self._call_widget.show()
+
+ def accept_call(self, friend_number, audio, video):
+ """
+ Accept incoming call with audio or video
+ """
+ self._call.accept_call(friend_number, audio, video)
+ self._screen.active_call()
+ self._incoming_calls.remove(friend_number)
+ if hasattr(self, '_call_widget'):
+ del self._call_widget
+
+ def stop_call(self, friend_number, by_friend):
+ """
+ Stop call with friend
+ """
+ if friend_number in self._incoming_calls:
+ self._incoming_calls.remove(friend_number)
+ text = QtGui.QApplication.translate("incoming_call", "Call declined", None, QtGui.QApplication.UnicodeUTF8)
+ else:
+ text = QtGui.QApplication.translate("incoming_call", "Call finished", None, QtGui.QApplication.UnicodeUTF8)
+ self._screen.call_finished()
+ self._call.finish_call(friend_number, by_friend) # finish or decline call
+ if hasattr(self, '_call_widget'):
+ self._call_widget.close()
+ del self._call_widget
+ friend = self.get_friend_by_number(friend_number)
+ friend.append_message(InfoMessage(text, time.time()))
+ if friend_number == self.get_active_number():
+ self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
+ self._messages.scrollToBottom()
+
+
+def tox_factory(data=None, settings=None):
+ """
+ :param data: user data from .tox file. None = no saved data, create new profile
+ :param settings: current profile settings. None = default settings will be used
+ :return: new tox instance
+ """
+ if settings is None:
+ settings = Settings.get_default_settings()
+ tox_options = Tox.options_new()
+ tox_options.contents.udp_enabled = settings['udp_enabled']
+ tox_options.contents.proxy_type = settings['proxy_type']
+ tox_options.contents.proxy_host = bytes(settings['proxy_host'], 'UTF-8')
+ tox_options.contents.proxy_port = settings['proxy_port']
+ tox_options.contents.start_port = settings['start_port']
+ tox_options.contents.end_port = settings['end_port']
+ tox_options.contents.tcp_port = settings['tcp_port']
+ if data: # load existing profile
+ tox_options.contents.savedata_type = TOX_SAVEDATA_TYPE['TOX_SAVE']
+ tox_options.contents.savedata_data = c_char_p(data)
+ tox_options.contents.savedata_length = len(data)
+ else: # create new profile
+ tox_options.contents.savedata_type = TOX_SAVEDATA_TYPE['NONE']
+ tox_options.contents.savedata_data = None
+ tox_options.contents.savedata_length = 0
+ return Tox(tox_options)
diff --git a/toxygen/settings.py b/toxygen/settings.py
new file mode 100644
index 0000000..3623d69
--- /dev/null
+++ b/toxygen/settings.py
@@ -0,0 +1,291 @@
+from platform import system
+import json
+import os
+from util import Singleton, curr_directory, log, copy
+import pyaudio
+from toxencryptsave import ToxEncryptSave
+import smileys
+
+
+class Settings(dict, Singleton):
+ """
+ Settings of current profile + global app settings
+ """
+
+ def __init__(self, name):
+ Singleton.__init__(self)
+ self.path = ProfileHelper.get_path() + str(name) + '.json'
+ self.name = name
+ if os.path.isfile(self.path):
+ with open(self.path, 'rb') as fl:
+ data = fl.read()
+ inst = ToxEncryptSave.get_instance()
+ try:
+ if inst.is_data_encrypted(data):
+ data = inst.pass_decrypt(data)
+ info = json.loads(str(data, 'utf-8'))
+ except Exception as ex:
+ info = Settings.get_default_settings()
+ log('Parsing settings error: ' + str(ex))
+ super(Settings, self).__init__(info)
+ self.upgrade()
+ else:
+ super(Settings, self).__init__(Settings.get_default_settings())
+ self.save()
+ smileys.SmileyLoader(self)
+ self.locked = False
+ self.closing = 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}
+
+ @staticmethod
+ def get_auto_profile():
+ path = Settings.get_default_path() + 'toxygen.json'
+ if os.path.isfile(path):
+ with open(path) as fl:
+ data = fl.read()
+ auto = json.loads(data)
+ if 'path' in auto and 'name' in auto:
+ return str(auto['path']), str(auto['name'])
+ return '', ''
+
+ @staticmethod
+ def set_auto_profile(path, name):
+ p = Settings.get_default_path() + 'toxygen.json'
+ with open(p) as fl:
+ data = fl.read()
+ data = json.loads(data)
+ data['path'] = str(path)
+ data['name'] = str(name)
+ with open(p, 'w') as fl:
+ fl.write(json.dumps(data))
+
+ @staticmethod
+ def reset_auto_profile():
+ p = Settings.get_default_path() + 'toxygen.json'
+ with open(p) as fl:
+ data = fl.read()
+ data = json.loads(data)
+ if 'path' in data:
+ del data['path']
+ del data['name']
+ with open(p, 'w') as fl:
+ fl.write(json.dumps(data))
+
+ @staticmethod
+ def is_active_profile(path, name):
+ path = path + name + '.tox'
+ settings = Settings.get_default_path() + 'toxygen.json'
+ if os.path.isfile(settings):
+ with open(settings) as fl:
+ data = fl.read()
+ data = json.loads(data)
+ if 'active_profile' in data:
+ return path in data['active_profile']
+ return False
+
+ @staticmethod
+ def get_default_settings():
+ """
+ Default profile settings
+ """
+ return {
+ 'theme': 'default',
+ 'ipv6_enabled': True,
+ 'udp_enabled': True,
+ 'proxy_type': 0,
+ 'proxy_host': '127.0.0.1',
+ 'proxy_port': 9050,
+ 'start_port': 0,
+ 'end_port': 0,
+ 'tcp_port': 0,
+ 'notifications': True,
+ 'sound_notifications': False,
+ 'language': 'English',
+ 'save_history': False,
+ 'allow_inline': True,
+ 'allow_auto_accept': True,
+ 'auto_accept_path': None,
+ 'show_online_friends': False,
+ 'auto_accept_from_friends': [],
+ 'paused_file_transfers': {},
+ 'resend_files': True,
+ 'friends_aliases': [],
+ 'show_avatars': False,
+ 'typing_notifications': False,
+ 'calls_sound': True,
+ 'blocked': [],
+ 'plugins': [],
+ 'notes': {},
+ 'smileys': True,
+ 'smiley_pack': 'default',
+ 'mirror_mode': False,
+ 'width': 920,
+ 'height': 500,
+ 'x': 400,
+ 'y': 400,
+ 'message_font_size': 14,
+ 'unread_color': 'red',
+ 'save_unsent_only': False,
+ 'compact_mode': False,
+ 'show_welcome_screen': True,
+ 'close_to_tray': False,
+ 'font': 'Times New Roman'
+ }
+
+ @staticmethod
+ def supported_languages():
+ return {
+ 'English': 'en_EN',
+ 'Russian': 'ru_RU',
+ 'French': 'fr_FR'
+ }
+
+ def upgrade(self):
+ default = Settings.get_default_settings()
+ for key in default:
+ if key not in self:
+ print(key)
+ self[key] = default[key]
+ self.save()
+
+ def save(self):
+ text = json.dumps(self)
+ inst = ToxEncryptSave.get_instance()
+ if inst.has_password():
+ text = bytes(inst.pass_encrypt(bytes(text, 'utf-8')))
+ else:
+ text = bytes(text, 'utf-8')
+ with open(self.path, 'wb') as fl:
+ fl.write(text)
+
+ def close(self):
+ 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)
+ try:
+ app_settings['active_profile'].remove(str(ProfileHelper.get_path() + self.name + '.tox'))
+ except:
+ pass
+ data = json.dumps(app_settings)
+ with open(path, 'w') as fl:
+ fl.write(data)
+
+ def set_active_profile(self):
+ """
+ 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)
+ with open(path, 'w') as fl:
+ fl.write(data)
+
+ 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_default_path():
+ 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):
+ """
+ Class with methods for search, load and save profiles
+ """
+ def __init__(self, path, name):
+ Singleton.__init__(self)
+ self._path = path + name + '.tox'
+ self._directory = path
+ # create /avatars if not exists:
+ directory = path + 'avatars'
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+
+ def open_profile(self):
+ with open(self._path, 'rb') as fl:
+ data = fl.read()
+ if data:
+ return data
+ else:
+ raise IOError('Save file has zero size!')
+
+ def get_dir(self):
+ return self._directory
+
+ def save_profile(self, data):
+ inst = ToxEncryptSave.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, use_new_path):
+ path = new_path + os.path.basename(self._path)
+ with open(self._path, 'rb') as fin:
+ data = fin.read()
+ 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():
+ """
+ Find available tox profiles
+ """
+ path = Settings.get_default_path()
+ result = []
+ # check default path
+ if not os.path.exists(path):
+ os.makedirs(path)
+ for fl in os.listdir(path):
+ if fl.endswith('.tox'):
+ name = fl[:-4]
+ result.append((path, name))
+ path = curr_directory()
+ # check current directory
+ for fl in os.listdir(path):
+ if fl.endswith('.tox'):
+ name = fl[:-4]
+ result.append((path + '/', name))
+ return result
+
+ @staticmethod
+ def get_path():
+ return ProfileHelper.get_instance().get_dir()
diff --git a/toxygen/smileys/smileys.py b/toxygen/smileys.py
similarity index 74%
rename from toxygen/smileys/smileys.py
rename to toxygen/smileys.py
index 0391856..9143a0b 100644
--- a/toxygen/smileys/smileys.py
+++ b/toxygen/smileys.py
@@ -1,11 +1,14 @@
-from utils import util
+import util
import json
import os
from collections import OrderedDict
-from PyQt5 import QtCore
+try:
+ from PySide import QtCore
+except ImportError:
+ from PyQt4 import QtCore
-class SmileyLoader:
+class SmileyLoader(util.Singleton):
"""
Class which loads smileys packs and insert smileys into messages
"""
@@ -25,7 +28,7 @@ class SmileyLoader:
pack_name = self._settings['smiley_pack']
if self._settings['smileys'] and self._curr_pack != pack_name:
self._curr_pack = pack_name
- path = util.join_path(self.get_smileys_path(), 'config.json')
+ path = self.get_smileys_path() + 'config.json'
try:
with open(path, encoding='utf8') as fl:
self._smileys = json.loads(fl.read())
@@ -34,7 +37,7 @@ class SmileyLoader:
print('Smiley pack {} loaded'.format(pack_name))
keys, values, self._list = [], [], []
for key, value in tmp.items():
- value = util.join_path(self.get_smileys_path(), value)
+ value = self.get_smileys_path() + value
if value not in values:
keys.append(key)
values.append(value)
@@ -45,11 +48,10 @@ class SmileyLoader:
print('Smiley pack {} was not loaded. Error: {}'.format(pack_name, ex))
def get_smileys_path(self):
- return util.join_path(util.get_smileys_directory(), self._curr_pack) if self._curr_pack is not None else None
+ return util.curr_directory() + '/smileys/' + self._curr_pack + '/'
- @staticmethod
- def get_packs_list():
- d = util.get_smileys_directory()
+ def get_packs_list(self):
+ d = util.curr_directory() + '/smileys/'
return [x[1] for x in os.walk(d)][0]
def get_smileys(self):
@@ -72,3 +74,18 @@ class SmileyLoader:
if file_name.endswith('.gif'): # animated smiley
edit.addAnimation(QtCore.QUrl(file_name), self.get_smileys_path() + file_name)
return ' '.join(arr)
+
+
+def sticker_loader():
+ """
+ :return list of stickers
+ """
+ result = []
+ d = util.curr_directory() + '/stickers/'
+ keys = [x[1] for x in os.walk(d)][0]
+ for key in keys:
+ path = d + key + '/'
+ files = filter(lambda f: f.endswith('.png'), os.listdir(path))
+ files = map(lambda f: str(path + f), files)
+ result.extend(files)
+ return result
diff --git a/toxygen/smileys/__init__.py b/toxygen/smileys/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/toxygen/stickers/__init__.py b/toxygen/stickers/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/toxygen/stickers/stickers.py b/toxygen/stickers/stickers.py
deleted file mode 100644
index 14142c7..0000000
--- a/toxygen/stickers/stickers.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import os
-import utils.util as util
-
-
-def load_stickers():
- """
- :return list of stickers
- """
- result = []
- d = util.get_stickers_directory()
- keys = [x[1] for x in os.walk(d)][0]
- for key in keys:
- path = util.join_path(d, key)
- files = filter(lambda f: f.endswith('.png'), os.listdir(path))
- files = map(lambda f: util.join_path(path, f), files)
- result.extend(files)
-
- return result
diff --git a/toxygen/styles/dark_style.qss b/toxygen/styles/dark_style.qss
deleted file mode 100644
index ece5ec3..0000000
--- a/toxygen/styles/dark_style.qss
+++ /dev/null
@@ -1,1335 +0,0 @@
-/*
- * The MIT License (MIT)
- *
- * Copyright (c) <2013-2014>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
-
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
-
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-QToolTip
-{
- border: 1px solid #3A3939;
- background-color: rgb(90, 102, 117);;
- color: white;
- padding: 1px;
- opacity: 200;
-}
-
-QWidget
-{
- color: silver;
- background-color: #302F2F;
- selection-background-color: #A9A9A9;
- selection-color: black;
- background-clip: border;
- border-image: none;
- outline: 0;
-}
-
-QWidget:item:hover
-{
- background-color: #78879b;
- color: black;
-}
-
-QWidget:item:selected
-{
- background-color: #A9A9A9;
-}
-
-QProgressBar:horizontal {
- border: 1px solid #3A3939;
- text-align: center;
- padding: 1px;
- background: #201F1F;
-}
-QProgressBar::chunk:horizontal {
- background-color: qlineargradient(spread:reflect, x1:1, y1:0.545, x2:1, y2:0, stop:0 rgba(28, 66, 111, 255), stop:1 rgba(37, 87, 146, 255));
-}
-
-QCheckBox:disabled
-{
- color: #777777;
-}
-QCheckBox::indicator,
-QGroupBox::indicator
-{
- width: 18px;
- height: 18px;
-}
-QGroupBox::indicator
-{
- margin-left: 2px;
-}
-
-QCheckBox::indicator:unchecked,
-QCheckBox::indicator:unchecked:hover,
-QGroupBox::indicator:unchecked,
-QGroupBox::indicator:unchecked:hover
-{
- image: url(:/qss_icons/rc/checkbox_unchecked.png);
-}
-
-QCheckBox::indicator:unchecked:focus,
-QCheckBox::indicator:unchecked:pressed,
-QGroupBox::indicator:unchecked:focus,
-QGroupBox::indicator:unchecked:pressed
-{
- border: none;
- image: url(:/qss_icons/rc/checkbox_unchecked_focus.png);
-}
-
-QCheckBox::indicator:checked,
-QCheckBox::indicator:checked:hover,
-QGroupBox::indicator:checked,
-QGroupBox::indicator:checked:hover
-{
- image: url(:/qss_icons/rc/checkbox_checked.png);
-}
-
-QCheckBox::indicator:checked:focus,
-QCheckBox::indicator:checked:pressed,
-QGroupBox::indicator:checked:focus,
-QGroupBox::indicator:checked:pressed
-{
- border: none;
- image: url(:/qss_icons/rc/checkbox_checked_focus.png);
-}
-
-QCheckBox::indicator:indeterminate,
-QCheckBox::indicator:indeterminate:hover,
-QCheckBox::indicator:indeterminate:pressed
-QGroupBox::indicator:indeterminate,
-QGroupBox::indicator:indeterminate:hover,
-QGroupBox::indicator:indeterminate:pressed
-{
- image: url(:/qss_icons/rc/checkbox_indeterminate.png);
-}
-
-QCheckBox::indicator:indeterminate:focus,
-QGroupBox::indicator:indeterminate:focus
-{
- image: url(:/qss_icons/rc/checkbox_indeterminate_focus.png);
-}
-
-QCheckBox::indicator:checked:disabled,
-QGroupBox::indicator:checked:disabled
-{
- image: url(:/qss_icons/rc/checkbox_checked_disabled.png);
-}
-
-QCheckBox::indicator:unchecked:disabled,
-QGroupBox::indicator:unchecked:disabled
-{
- image: url(:/qss_icons/rc/checkbox_unchecked_disabled.png);
-}
-
-QRadioButton
-{
- spacing: 5px;
- outline: none;
- color: #bbb;
- margin-bottom: 2px;
-}
-
-QRadioButton:disabled
-{
- color: #777777;
-}
-QRadioButton::indicator
-{
- width: 21px;
- height: 21px;
-}
-
-QRadioButton::indicator:unchecked,
-QRadioButton::indicator:unchecked:hover
-{
- image: url(:/qss_icons/rc/radio_unchecked.png);
-}
-
-QRadioButton::indicator:unchecked:focus,
-QRadioButton::indicator:unchecked:pressed
-{
- border: none;
- outline: none;
- image: url(:/qss_icons/rc/radio_unchecked_focus.png);
-}
-
-QRadioButton::indicator:checked,
-QRadioButton::indicator:checked:hover
-{
- border: none;
- outline: none;
- image: url(:/qss_icons/rc/radio_checked.png);
-}
-
-QRadioButton::indicator:checked:focus,
-QRadioButton::indicato::menu-arrowr:checked:pressed
-{
- border: none;
- outline: none;
- image: url(:/qss_icons/rc/radio_checked_focus.png);
-}
-
-QRadioButton::indicator:indeterminate,
-QRadioButton::indicator:indeterminate:hover,
-QRadioButton::indicator:indeterminate:pressed
-{
- image: url(:/qss_icons/rc/radio_indeterminate.png);
-}
-
-QRadioButton::indicator:checked:disabled
-{
- outline: none;
- image: url(:/qss_icons/rc/radio_checked_disabled.png);
-}
-
-QRadioButton::indicator:unchecked:disabled
-{
- image: url(:/qss_icons/rc/radio_unchecked_disabled.png);
-}
-
-
-QMenuBar
-{
- background-color: #302F2F;
- color: silver;
-}
-
-QMenuBar::item
-{
- background: transparent;
-}
-
-QMenuBar::item:selected
-{
- background: transparent;
- border: 1px solid #A9A9A9;
-}
-
-QMenuBar::item:pressed
-{
- border: 1px solid #3A3939;
- background-color: #A9A9A9;
- color: black;
- margin-bottom:-1px;
- padding-bottom:1px;
-}
-
-QMenu
-{
- border: 1px solid #3A3939;
- color: silver;
- margin: 2px;
-}
-
-QMenu::icon
-{
- margin: 5px;
-}
-
-QMenu::item
-{
- padding: 5px 30px 5px 30px;
- margin-left: 5px;
- border: 1px solid transparent; /* reserve space for selection border */
-}
-
-QMenu::item:selected
-{
- color: black;
-}
-
-QMenu::separator {
- height: 2px;
- background: lightblue;
- margin-left: 10px;
- margin-right: 5px;
-}
-
-QMenu::indicator {
- width: 18px;
- height: 18px;
-}
-
-/* non-exclusive indicator = check box style indicator
- (see QActionGroup::setExclusive) */
-QMenu::indicator:non-exclusive:unchecked {
- image: url(:/qss_icons/rc/checkbox_unchecked.png);
-}
-
-QMenu::indicator:non-exclusive:unchecked:selected {
- image: url(:/qss_icons/rc/checkbox_unchecked_disabled.png);
-}
-
-QMenu::indicator:non-exclusive:checked {
- image: url(:/qss_icons/rc/checkbox_checked.png);
-}
-
-QMenu::indicator:non-exclusive:checked:selected {
- image: url(:/qss_icons/rc/checkbox_checked_disabled.png);
-}
-
-/* exclusive indicator = radio button style indicator (see QActionGroup::setExclusive) */
-QMenu::indicator:exclusive:unchecked {
- image: url(:/qss_icons/rc/radio_unchecked.png);
-}
-
-QMenu::indicator:exclusive:unchecked:selected {
- image: url(:/qss_icons/rc/radio_unchecked_disabled.png);
-}
-
-QMenu::indicator:exclusive:checked {
- image: url(:/qss_icons/rc/radio_checked.png);
-}
-
-QMenu::indicator:exclusive:checked:selected {
- image: url(:/qss_icons/rc/radio_checked_disabled.png);
-}
-
-QMenu::right-arrow {
- margin: 5px;
- image: url(:/qss_icons/rc/right_arrow.png)
-}
-
-
-QWidget:disabled
-{
- color: #404040;
- background-color: #302F2F;
-}
-
-QAbstractItemView
-{
- alternate-background-color: #3A3939;
- color: silver;
- border: 1px solid 3A3939;
- border-radius: 2px;
- padding: 1px;
-}
-
-QWidget:focus, QMenuBar:focus
-{
- border: 1px solid #78879b;
-}
-
-QTabWidget:focus, QCheckBox:focus, QRadioButton:focus, QSlider:focus
-{
- border: none;
-}
-
-QLineEdit
-{
- background-color: #201F1F;
- padding: 2px;
- border-style: solid;
- border: 1px solid #3A3939;
- border-radius: 2px;
- color: silver;
-}
-
-QGroupBox {
- border:1px solid #3A3939;
- border-radius: 2px;
- margin-top: 20px;
-}
-
-QGroupBox::title {
- subcontrol-origin: margin;
- subcontrol-position: top center;
- padding-left: 10px;
- padding-right: 10px;
- padding-top: 10px;
-}
-
-QAbstractScrollArea
-{
- border-radius: 2px;
- border: 1px solid #3A3939;
- background-color: transparent;
-}
-
-QScrollBar:horizontal
-{
- height: 15px;
- margin: 3px 15px 3px 15px;
- border: 1px transparent #2A2929;
- border-radius: 4px;
- background-color: #2A2929;
-}
-
-QScrollBar::handle:horizontal
-{
- background-color: #605F5F;
- min-width: 5px;
- border-radius: 4px;
-}
-
-QScrollBar::add-line:horizontal
-{
- margin: 0px 3px 0px 3px;
- border-image: url(:/qss_icons/rc/right_arrow_disabled.png);
- width: 10px;
- height: 10px;
- subcontrol-position: right;
- subcontrol-origin: margin;
-}
-
-QScrollBar::sub-line:horizontal
-{
- margin: 0px 3px 0px 3px;
- border-image: url(:/qss_icons/rc/left_arrow_disabled.png);
- height: 10px;
- width: 10px;
- subcontrol-position: left;
- subcontrol-origin: margin;
-}
-
-QScrollBar::add-line:horizontal:hover,QScrollBar::add-line:horizontal:on
-{
- border-image: url(:/qss_icons/rc/right_arrow.png);
- height: 10px;
- width: 10px;
- subcontrol-position: right;
- subcontrol-origin: margin;
-}
-
-
-QScrollBar::sub-line:horizontal:hover, QScrollBar::sub-line:horizontal:on
-{
- border-image: url(:/qss_icons/rc/left_arrow.png);
- height: 10px;
- width: 10px;
- subcontrol-position: left;
- subcontrol-origin: margin;
-}
-
-QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal
-{
- background: none;
-}
-
-
-QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal
-{
- background: none;
-}
-
-QScrollBar:vertical
-{
- background-color: #2A2929;
- width: 15px;
- margin: 15px 3px 15px 3px;
- border: 1px transparent #2A2929;
- border-radius: 4px;
-}
-
-QScrollBar::handle:vertical
-{
- background-color: #605F5F;
- min-height: 5px;
- border-radius: 4px;
-}
-
-QScrollBar::sub-line:vertical
-{
- margin: 3px 0px 3px 0px;
- border-image: url(:/qss_icons/rc/up_arrow_disabled.png);
- height: 10px;
- width: 10px;
- subcontrol-position: top;
- subcontrol-origin: margin;
-}
-
-QScrollBar::add-line:vertical
-{
- margin: 3px 0px 3px 0px;
- border-image: url(:/qss_icons/rc/down_arrow_disabled.png);
- height: 10px;
- width: 10px;
- subcontrol-position: bottom;
- subcontrol-origin: margin;
-}
-
-QScrollBar::sub-line:vertical:hover,QScrollBar::sub-line:vertical:on
-{
-
- border-image: url(:/qss_icons/rc/up_arrow.png);
- height: 10px;
- width: 10px;
- subcontrol-position: top;
- subcontrol-origin: margin;
-}
-
-
-QScrollBar::add-line:vertical:hover, QScrollBar::add-line:vertical:on
-{
- border-image: url(:/qss_icons/rc/down_arrow.png);
- height: 10px;
- width: 10px;
- subcontrol-position: bottom;
- subcontrol-origin: margin;
-}
-
-QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical
-{
- background: none;
-}
-
-
-QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical
-{
- background: none;
-}
-
-QTextEdit
-{
- background-color: #201F1F;
- color: silver;
- border: 1px solid #3A3939;
-}
-
-QPlainTextEdit
-{
- background-color: #201F1F;;
- color: silver;
- border-radius: 2px;
- border: 1px solid #3A3939;
-}
-
-QHeaderView::section
-{
- background-color: #3A3939;
- color: silver;
- padding-left: 4px;
- border: 1px solid #6c6c6c;
-}
-
-QSizeGrip {
- image: url(:/qss_icons/rc/sizegrip.png);
- width: 12px;
- height: 12px;
-}
-
-
-QMainWindow::separator
-{
- background-color: #302F2F;
- color: white;
- padding-left: 4px;
- spacing: 2px;
- border: 1px dashed #3A3939;
-}
-
-QMainWindow::separator:hover
-{
-
- background-color: #787876;
- color: white;
- padding-left: 4px;
- border: 1px solid #3A3939;
- spacing: 2px;
-}
-
-
-QMenu::separator
-{
- height: 1px;
- background-color: #3A3939;
- color: white;
- padding-left: 4px;
- margin-left: 10px;
- margin-right: 5px;
-}
-
-
-QFrame
-{
- border-radius: 2px;
- border: 1px solid #444;
-}
-
-QFrame[frameShape="0"]
-{
- border-radius: 2px;
- border: 1px transparent #444;
-}
-
-QStackedWidget
-{
- border: 1px transparent black;
-}
-
-QToolBar {
- border: 1px transparent #393838;
- background: 1px solid #302F2F;
- font-weight: bold;
-}
-
-QToolBar::handle:horizontal {
- image: url(:/qss_icons/rc/Hmovetoolbar.png);
-}
-QToolBar::handle:vertical {
- image: url(:/qss_icons/rc/Vmovetoolbar.png);
-}
-QToolBar::separator:horizontal {
- image: url(:/qss_icons/rc/Hsepartoolbar.png);
-}
-QToolBar::separator:vertical {
- image: url(:/qss_icons/rc/Vsepartoolbars.png);
-}
-
-QPushButton
-{
- color: silver;
- background-color: #302F2F;
- border-width: 1px;
- border-color: #4A4949;
- border-style: solid;
- padding-top: 5px;
- padding-bottom: 5px;
- padding-left: 5px;
- padding-right: 5px;
- border-radius: 2px;
- outline: none;
-}
-
-QPushButton:focus
-{
- border-width: 1px;
- border-color: #4A4949;
- border-style: solid;
-}
-
-QPushButton:disabled
-{
- background-color: #302F2F;
- border-width: 1px;
- border-color: #3A3939;
- border-style: solid;
- padding-top: 5px;
- padding-bottom: 5px;
- padding-left: 10px;
- padding-right: 10px;
- /*border-radius: 2px;*/
- color: #454545;
-}
-
-QComboBox
-{
- selection-background-color: #A9A9A9;
- background-color: #201F1F;
- border-style: solid;
- border: 1px solid #3A3939;
- border-radius: 2px;
- padding: 2px;
- min-width: 75px;
-}
-
-QPushButton:hover
-{
- background-color: #3d8ec9;
- color: white;
-}
-
-QComboBox:hover,QAbstractSpinBox:hover,QLineEdit:hover,QTextEdit:hover,QPlainTextEdit:hover,QAbstractView:hover,QTreeView:hover
-{
- border: 1px solid #78879b;
- color: silver;
-}
-
-QComboBox:on
-{
- background-color: #626873;
- padding-top: 3px;
- padding-left: 4px;
- selection-background-color: #4a4a4a;
-}
-
-QComboBox QAbstractItemView
-{
- background-color: #201F1F;
- border-radius: 2px;
- border: 1px solid #444;
- selection-background-color: #A9A9A9;
-}
-
-QComboBox::drop-down
-{
- subcontrol-origin: padding;
- subcontrol-position: top right;
- width: 15px;
-
- border-left-width: 0px;
- border-left-color: darkgray;
- border-left-style: solid;
- border-top-right-radius: 3px;
- border-bottom-right-radius: 3px;
-}
-
-QComboBox::down-arrow
-{
- image: url(:/qss_icons/rc/down_arrow_disabled.png);
-}
-
-QComboBox::down-arrow:on, QComboBox::down-arrow:hover,
-QComboBox::down-arrow:focus
-{
- image: url(:/qss_icons/rc/down_arrow.png);
-}
-
-QAbstractSpinBox {
- padding-top: 2px;
- padding-bottom: 2px;
- border: 1px solid #3A3939;
- background-color: #201F1F;
- color: silver;
- border-radius: 2px;
- min-width: 75px;
-}
-
-QAbstractSpinBox:up-button
-{
- background-color: transparent;
- subcontrol-origin: border;
- subcontrol-position: center right;
-}
-
-QAbstractSpinBox:down-button
-{
- background-color: transparent;
- subcontrol-origin: border;
- subcontrol-position: center left;
-}
-
-QAbstractSpinBox::up-arrow,QAbstractSpinBox::up-arrow:disabled,QAbstractSpinBox::up-arrow:off {
- image: url(:/qss_icons/rc/up_arrow_disabled.png);
- width: 10px;
- height: 10px;
-}
-QAbstractSpinBox::up-arrow:hover
-{
- image: url(:/qss_icons/rc/up_arrow.png);
-}
-
-
-QAbstractSpinBox::down-arrow,QAbstractSpinBox::down-arrow:disabled,QAbstractSpinBox::down-arrow:off
-{
- image: url(:/qss_icons/rc/down_arrow_disabled.png);
- width: 10px;
- height: 10px;
-}
-QAbstractSpinBox::down-arrow:hover
-{
- image: url(:/qss_icons/rc/down_arrow.png);
-}
-
-
-QLabel
-{
- border: 0px solid black;
- background-color: transparent;
-}
-
-QTabWidget{
- border: 1px transparent black;
-}
-
-QTabWidget::pane {
- border: 1px solid #444;
- border-radius: 3px;
- padding: 3px;
-}
-
-QTabBar
-{
- qproperty-drawBase: 0;
- left: 5px; /* move to the right by 5px */
-}
-
-QTabBar:focus
-{
- border: 0px transparent black;
-}
-
-QTabBar::close-button {
- image: url(:/qss_icons/rc/close.png);
- background: transparent;
-}
-
-QTabBar::close-button:hover
-{
- image: url(:/qss_icons/rc/close-hover.png);
- background: transparent;
-}
-
-QTabBar::close-button:pressed {
- image: url(:/qss_icons/rc/close-pressed.png);
- background: transparent;
-}
-
-/* TOP TABS */
-QTabBar::tab:top {
- color: #b1b1b1;
- border: 1px solid #4A4949;
- border-bottom: 1px transparent black;
- background-color: #302F2F;
- padding: 5px;
- border-top-left-radius: 2px;
- border-top-right-radius: 2px;
-}
-
-QTabBar::tab:top:!selected
-{
- color: #b1b1b1;
- background-color: #201F1F;
- border: 1px transparent #4A4949;
- border-bottom: 1px transparent #4A4949;
- border-top-left-radius: 0px;
- border-top-right-radius: 0px;
-}
-
-QTabBar::tab:top:!selected:hover {
- background-color: #48576b;
-}
-
-/* BOTTOM TABS */
-QTabBar::tab:bottom {
- color: #b1b1b1;
- border: 1px solid #4A4949;
- border-top: 1px transparent black;
- background-color: #302F2F;
- padding: 5px;
- border-bottom-left-radius: 2px;
- border-bottom-right-radius: 2px;
-}
-
-QTabBar::tab:bottom:!selected
-{
- color: #b1b1b1;
- background-color: #201F1F;
- border: 1px transparent #4A4949;
- border-top: 1px transparent #4A4949;
- border-bottom-left-radius: 0px;
- border-bottom-right-radius: 0px;
-}
-
-QTabBar::tab:bottom:!selected:hover {
- background-color: #78879b;
-}
-
-/* LEFT TABS */
-QTabBar::tab:left {
- color: #b1b1b1;
- border: 1px solid #4A4949;
- border-left: 1px transparent black;
- background-color: #302F2F;
- padding: 5px;
- border-top-right-radius: 2px;
- border-bottom-right-radius: 2px;
-}
-
-QTabBar::tab:left:!selected
-{
- color: #b1b1b1;
- background-color: #201F1F;
- border: 1px transparent #4A4949;
- border-right: 1px transparent #4A4949;
- border-top-right-radius: 0px;
- border-bottom-right-radius: 0px;
-}
-
-QTabBar::tab:left:!selected:hover {
- background-color: #48576b;
-}
-
-
-/* RIGHT TABS */
-QTabBar::tab:right {
- color: #b1b1b1;
- border: 1px solid #4A4949;
- border-right: 1px transparent black;
- background-color: #302F2F;
- padding: 5px;
- border-top-left-radius: 2px;
- border-bottom-left-radius: 2px;
-}
-
-QTabBar::tab:right:!selected
-{
- color: #b1b1b1;
- background-color: #201F1F;
- border: 1px transparent #4A4949;
- border-right: 1px transparent #4A4949;
- border-top-left-radius: 0px;
- border-bottom-left-radius: 0px;
-}
-
-QTabBar::tab:right:!selected:hover {
- background-color: #48576b;
-}
-
-QTabBar QToolButton::right-arrow:enabled {
- image: url(:/qss_icons/rc/right_arrow.png);
- }
-
- QTabBar QToolButton::left-arrow:enabled {
- image: url(:/qss_icons/rc/left_arrow.png);
- }
-
-QTabBar QToolButton::right-arrow:disabled {
- image: url(:/qss_icons/rc/right_arrow_disabled.png);
- }
-
- QTabBar QToolButton::left-arrow:disabled {
- image: url(:/qss_icons/rc/left_arrow_disabled.png);
- }
-
-
-QDockWidget {
- border: 1px solid #403F3F;
- titlebar-close-icon: url(:/qss_icons/rc/close.png);
- titlebar-normal-icon: url(:/qss_icons/rc/undock.png);
-}
-
-QDockWidget::close-button, QDockWidget::float-button {
- border: 1px solid transparent;
- border-radius: 2px;
- background: transparent;
-}
-
-QDockWidget::close-button:hover, QDockWidget::float-button:hover {
- background: rgba(255, 255, 255, 10);
-}
-
-QDockWidget::close-button:pressed, QDockWidget::float-button:pressed {
- padding: 1px -1px -1px 1px;
- background: rgba(255, 255, 255, 10);
-}
-
-QTreeView, QListView
-{
- border: 1px solid #444;
- background-color: #201F1F;
-}
-
-QTreeView:branch:selected, QTreeView:branch:hover
-{
- background: url(:/qss_icons/rc/transparent.png);
-}
-
-QTreeView::branch:has-siblings:!adjoins-item {
- border-image: url(:/qss_icons/rc/transparent.png);
-}
-
-QTreeView::branch:has-siblings:adjoins-item {
- border-image: url(:/qss_icons/rc/transparent.png);
-}
-
-QTreeView::branch:!has-children:!has-siblings:adjoins-item {
- border-image: url(:/qss_icons/rc/transparent.png);
-}
-
-QTreeView::branch:has-children:!has-siblings:closed,
-QTreeView::branch:closed:has-children:has-siblings {
- image: url(:/qss_icons/rc/branch_closed.png);
-}
-
-QTreeView::branch:open:has-children:!has-siblings,
-QTreeView::branch:open:has-children:has-siblings {
- image: url(:/qss_icons/rc/branch_open.png);
-}
-
-QTreeView::branch:has-children:!has-siblings:closed:hover,
-QTreeView::branch:closed:has-children:has-siblings:hover {
- image: url(:/qss_icons/rc/branch_closed-on.png);
- }
-
-QTreeView::branch:open:has-children:!has-siblings:hover,
-QTreeView::branch:open:has-children:has-siblings:hover {
- image: url(:/qss_icons/rc/branch_open-on.png);
- }
-
-QListView::item:!selected:hover, QListView::item:!selected:hover, QTreeView::item:!selected:hover {
- background: rgba(0, 0, 0, 0);
- outline: 0;
- color: #FFFFFF
-}
-
-QListView::item:selected:hover, QListView::item:selected:hover, QTreeView::item:selected:hover {
- background: #3d8ec9;
- color: #FFFFFF;
-}
-
-QSlider::groove:horizontal {
- border: 1px solid #3A3939;
- height: 8px;
- background: #201F1F;
- margin: 2px 0;
- border-radius: 2px;
-}
-
-QSlider::handle:horizontal {
- background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1,
- stop: 0.0 silver, stop: 0.2 #a8a8a8, stop: 1 #727272);
- border: 1px solid #3A3939;
- width: 14px;
- height: 14px;
- margin: -4px 0;
- border-radius: 2px;
-}
-
-QSlider::groove:vertical {
- border: 1px solid #3A3939;
- width: 8px;
- background: #201F1F;
- margin: 0 0px;
- border-radius: 2px;
-}
-
-QSlider::handle:vertical {
- background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0.0 silver,
- stop: 0.2 #a8a8a8, stop: 1 #727272);
- border: 1px solid #3A3939;
- width: 14px;
- height: 14px;
- margin: 0 -4px;
- border-radius: 2px;
-}
-
-QToolButton {
- background-color: transparent;
- border: 1px transparent #4A4949;
- border-radius: 2px;
- margin: 3px;
- padding: 3px;
-}
-
-QToolButton[popupMode="1"] { /* only for MenuButtonPopup */
- padding-right: 20px; /* make way for the popup button */
- border: 1px transparent #4A4949;
- border-radius: 5px;
-}
-
-QToolButton[popupMode="2"] { /* only for InstantPopup */
- padding-right: 10px; /* make way for the popup button */
- border: 1px transparent #4A4949;
-}
-
-
-QToolButton:hover, QToolButton::menu-button:hover {
- background-color: transparent;
- border: 1px solid #78879b;
-}
-
-QToolButton:checked, QToolButton:pressed,
- QToolButton::menu-button:pressed {
- background-color: #4A4949;
- border: 1px solid #78879b;
-}
-
-/* the subcontrol below is used only in the InstantPopup or DelayedPopup mode */
-QToolButton::menu-indicator {
- image: url(:/qss_icons/rc/down_arrow.png);
- top: -7px; left: -2px; /* shift it a bit */
-}
-
-/* the subcontrols below are used only in the MenuButtonPopup mode */
-QToolButton::menu-button {
- border: 1px transparent #4A4949;
- border-top-right-radius: 6px;
- border-bottom-right-radius: 6px;
- /* 16px width + 4px for border = 20px allocated above */
- width: 16px;
- outline: none;
-}
-
-QToolButton::menu-arrow {
- image: url(:/qss_icons/rc/down_arrow.png);
-}
-
-QToolButton::menu-arrow:open {
- top: 1px; left: 1px; /* shift it a bit */
- border: 1px solid #3A3939;
-}
-
-QTableView
-{
- border: 1px solid #444;
- gridline-color: #6c6c6c;
- background-color: #201F1F;
-}
-
-
-QTableView, QHeaderView
-{
- border-radius: 0px;
-}
-
-QTableView::item:pressed, QListView::item:pressed, QTreeView::item:pressed {
- background: #78879b;
- color: #FFFFFF;
-}
-
-QTableView::item:selected:active, QTreeView::item:selected:active, QListView::item:selected:active {
- background: #3d8ec9;
- color: #FFFFFF;
-}
-
-
-QHeaderView
-{
- border: 1px transparent;
- border-radius: 2px;
- margin: 0px;
- padding: 0px;
-}
-
-QHeaderView::section {
- background-color: #3A3939;
- color: silver;
- padding: 4px;
- border: 1px solid #6c6c6c;
- border-radius: 0px;
- text-align: center;
-}
-
-QHeaderView::section::vertical::first, QHeaderView::section::vertical::only-one
-{
- border-top: 1px solid #6c6c6c;
-}
-
-QHeaderView::section::vertical
-{
- border-top: transparent;
-}
-
-QHeaderView::section::horizontal::first, QHeaderView::section::horizontal::only-one
-{
- border-left: 1px solid #6c6c6c;
-}
-
-QHeaderView::section::horizontal
-{
- border-left: transparent;
-}
-
-
-QHeaderView::section:checked
- {
- color: white;
- background-color: #5A5959;
- }
-
- /* style the sort indicator */
-QHeaderView::down-arrow {
- image: url(:/qss_icons/rc/down_arrow.png);
-}
-
-QHeaderView::up-arrow {
- image: url(:/qss_icons/rc/up_arrow.png);
-}
-
-
-QTableCornerButton::section {
- background-color: #3A3939;
- border: 1px solid #3A3939;
- border-radius: 2px;
-}
-
-QToolBox {
- padding: 3px;
- border: 1px transparent black;
-}
-
-QToolBox::tab {
- color: #b1b1b1;
- background-color: #302F2F;
- border: 1px solid #4A4949;
- border-bottom: 1px transparent #302F2F;
- border-top-left-radius: 5px;
- border-top-right-radius: 5px;
-}
-
- QToolBox::tab:selected { /* italicize selected tabs */
- font: italic;
- background-color: #302F2F;
- border-color: #3d8ec9;
- }
-
-QStatusBar::item {
- border: 1px solid #3A3939;
- border-radius: 2px;
- }
-
-
-QFrame[height="3"], QFrame[width="3"] {
- background-color: #444;
-}
-
-
-QSplitter::handle {
- border: 1px dashed #3A3939;
-}
-
-QSplitter::handle:hover {
- background-color: #787876;
- border: 1px solid #3A3939;
-}
-
-QSplitter::handle:horizontal {
- width: 1px;
-}
-
-QSplitter::handle:vertical {
- height: 1px;
-}
-
-MessageItem
-{
- border: none;
-}
-
-MessageBrowser
-{
- border: none;
-}
-
-MessageBrowser::focus
-{
- border: none;
-}
-
-MessageItem::focus
-{
- border: none;
-}
-
-MessageBrowser:hover
-{
- border: none;
-}
-
-QListWidget QPushButton
-{
- background-color: transparent;
- border: none;
-}
-
-QPushButton:hover
-{
- background-color: #4A4949;
-}
-
-#messages:item:selected
-{
- background-color: #1E90FF;
-}
-
-MessageBrowser
-{
- background-color: transparent;
-}
-
-#messages:item:selected QListWidgetItem
-{
- background-color: #1E90FF;
-}
-
-#friendsListWidget:item:selected
-{
- background-color: #333333;
-}
-
-#toxygen
-{
- color: #A9A9A9;
-}
-
-QCheckBox
-{
- spacing: 5px;
- outline: none;
- color: #bbb;
- margin-bottom: 2px;
- text-align: center;
-}
-
-QListWidget > QLabel
-{
- color: #A9A9A9;
-}
-
-#searchLineEdit
-{
- 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;
-}
-
-#warningLabel
-{
- color: #BC1C1C;
-}
-
-#groupInvitesPushButton
-{
- background-color: #009c00;
-}
-
diff --git a/toxygen/styles/style.py b/toxygen/styles/style.py
index 6e05c3e..61352b0 100644
--- a/toxygen/styles/style.py
+++ b/toxygen/styles/style.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
try:
- from PyQt5 import QtCore
+ from PySide import QtCore
except ImportError:
from PyQt4 import QtCore
diff --git a/toxygen/styles/style.qrc b/toxygen/styles/style.qrc
index 7ceed90..ac14bc5 100644
--- a/toxygen/styles/style.qrc
+++ b/toxygen/styles/style.qrc
@@ -41,9 +41,6 @@
rc/radio_unchecked.png
- dark_style.qss
-
-
style.qss
diff --git a/toxygen/styles/style.qss b/toxygen/styles/style.qss
index ff9f614..a672d38 100644
--- a/toxygen/styles/style.qss
+++ b/toxygen/styles/style.qss
@@ -1,6 +1,1216 @@
-#searchLineEdit
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) <2013-2014>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+QToolTip
{
- padding-left: 22px;
+ border: 1px solid #3A3939;
+ background-color: rgb(90, 102, 117);;
+ color: white;
+ padding: 1px;
+ opacity: 200;
+}
+
+QWidget
+{
+ color: silver;
+ background-color: #302F2F;
+ selection-background-color: #A9A9A9;
+ selection-color: black;
+ background-clip: border;
+ border-image: none;
+ outline: 0;
+}
+
+QWidget:item:hover
+{
+ background-color: #78879b;
+ color: black;
+}
+
+QWidget:item:selected
+{
+ background-color: #A9A9A9;
+}
+
+QProgressBar:horizontal {
+ border: 1px solid #3A3939;
+ text-align: center;
+ padding: 1px;
+ background: #201F1F;
+}
+QProgressBar::chunk:horizontal {
+ background-color: qlineargradient(spread:reflect, x1:1, y1:0.545, x2:1, y2:0, stop:0 rgba(28, 66, 111, 255), stop:1 rgba(37, 87, 146, 255));
+}
+
+QCheckBox:disabled
+{
+ color: #777777;
+}
+QCheckBox::indicator,
+QGroupBox::indicator
+{
+ width: 18px;
+ height: 18px;
+}
+QGroupBox::indicator
+{
+ margin-left: 2px;
+}
+
+QCheckBox::indicator:unchecked,
+QCheckBox::indicator:unchecked:hover,
+QGroupBox::indicator:unchecked,
+QGroupBox::indicator:unchecked:hover
+{
+ image: url(:/qss_icons/rc/checkbox_unchecked.png);
+}
+
+QCheckBox::indicator:unchecked:focus,
+QCheckBox::indicator:unchecked:pressed,
+QGroupBox::indicator:unchecked:focus,
+QGroupBox::indicator:unchecked:pressed
+{
+ border: none;
+ image: url(:/qss_icons/rc/checkbox_unchecked_focus.png);
+}
+
+QCheckBox::indicator:checked,
+QCheckBox::indicator:checked:hover,
+QGroupBox::indicator:checked,
+QGroupBox::indicator:checked:hover
+{
+ image: url(:/qss_icons/rc/checkbox_checked.png);
+}
+
+QCheckBox::indicator:checked:focus,
+QCheckBox::indicator:checked:pressed,
+QGroupBox::indicator:checked:focus,
+QGroupBox::indicator:checked:pressed
+{
+ border: none;
+ image: url(:/qss_icons/rc/checkbox_checked_focus.png);
+}
+
+QCheckBox::indicator:indeterminate,
+QCheckBox::indicator:indeterminate:hover,
+QCheckBox::indicator:indeterminate:pressed
+QGroupBox::indicator:indeterminate,
+QGroupBox::indicator:indeterminate:hover,
+QGroupBox::indicator:indeterminate:pressed
+{
+ image: url(:/qss_icons/rc/checkbox_indeterminate.png);
+}
+
+QCheckBox::indicator:indeterminate:focus,
+QGroupBox::indicator:indeterminate:focus
+{
+ image: url(:/qss_icons/rc/checkbox_indeterminate_focus.png);
+}
+
+QCheckBox::indicator:checked:disabled,
+QGroupBox::indicator:checked:disabled
+{
+ image: url(:/qss_icons/rc/checkbox_checked_disabled.png);
+}
+
+QCheckBox::indicator:unchecked:disabled,
+QGroupBox::indicator:unchecked:disabled
+{
+ image: url(:/qss_icons/rc/checkbox_unchecked_disabled.png);
+}
+
+QRadioButton
+{
+ spacing: 5px;
+ outline: none;
+ color: #bbb;
+ margin-bottom: 2px;
+}
+
+QRadioButton:disabled
+{
+ color: #777777;
+}
+QRadioButton::indicator
+{
+ width: 21px;
+ height: 21px;
+}
+
+QRadioButton::indicator:unchecked,
+QRadioButton::indicator:unchecked:hover
+{
+ image: url(:/qss_icons/rc/radio_unchecked.png);
+}
+
+QRadioButton::indicator:unchecked:focus,
+QRadioButton::indicator:unchecked:pressed
+{
+ border: none;
+ outline: none;
+ image: url(:/qss_icons/rc/radio_unchecked_focus.png);
+}
+
+QRadioButton::indicator:checked,
+QRadioButton::indicator:checked:hover
+{
+ border: none;
+ outline: none;
+ image: url(:/qss_icons/rc/radio_checked.png);
+}
+
+QRadioButton::indicator:checked:focus,
+QRadioButton::indicato::menu-arrowr:checked:pressed
+{
+ border: none;
+ outline: none;
+ image: url(:/qss_icons/rc/radio_checked_focus.png);
+}
+
+QRadioButton::indicator:indeterminate,
+QRadioButton::indicator:indeterminate:hover,
+QRadioButton::indicator:indeterminate:pressed
+{
+ image: url(:/qss_icons/rc/radio_indeterminate.png);
+}
+
+QRadioButton::indicator:checked:disabled
+{
+ outline: none;
+ image: url(:/qss_icons/rc/radio_checked_disabled.png);
+}
+
+QRadioButton::indicator:unchecked:disabled
+{
+ image: url(:/qss_icons/rc/radio_unchecked_disabled.png);
+}
+
+
+QMenuBar
+{
+ background-color: #302F2F;
+ color: silver;
+}
+
+QMenuBar::item
+{
+ background: transparent;
+}
+
+QMenuBar::item:selected
+{
+ background: transparent;
+ border: 1px solid #A9A9A9;
+}
+
+QMenuBar::item:pressed
+{
+ border: 1px solid #3A3939;
+ background-color: #A9A9A9;
+ color: black;
+ margin-bottom:-1px;
+ padding-bottom:1px;
+}
+
+QMenu
+{
+ border: 1px solid #3A3939;
+ color: silver;
+ margin: 2px;
+}
+
+QMenu::icon
+{
+ margin: 5px;
+}
+
+QMenu::item
+{
+ padding: 5px 30px 5px 30px;
+ margin-left: 5px;
+ border: 1px solid transparent; /* reserve space for selection border */
+}
+
+QMenu::item:selected
+{
+ color: black;
+}
+
+QMenu::separator {
+ height: 2px;
+ background: lightblue;
+ margin-left: 10px;
+ margin-right: 5px;
+}
+
+QMenu::indicator {
+ width: 18px;
+ height: 18px;
+}
+
+/* non-exclusive indicator = check box style indicator
+ (see QActionGroup::setExclusive) */
+QMenu::indicator:non-exclusive:unchecked {
+ image: url(:/qss_icons/rc/checkbox_unchecked.png);
+}
+
+QMenu::indicator:non-exclusive:unchecked:selected {
+ image: url(:/qss_icons/rc/checkbox_unchecked_disabled.png);
+}
+
+QMenu::indicator:non-exclusive:checked {
+ image: url(:/qss_icons/rc/checkbox_checked.png);
+}
+
+QMenu::indicator:non-exclusive:checked:selected {
+ image: url(:/qss_icons/rc/checkbox_checked_disabled.png);
+}
+
+/* exclusive indicator = radio button style indicator (see QActionGroup::setExclusive) */
+QMenu::indicator:exclusive:unchecked {
+ image: url(:/qss_icons/rc/radio_unchecked.png);
+}
+
+QMenu::indicator:exclusive:unchecked:selected {
+ image: url(:/qss_icons/rc/radio_unchecked_disabled.png);
+}
+
+QMenu::indicator:exclusive:checked {
+ image: url(:/qss_icons/rc/radio_checked.png);
+}
+
+QMenu::indicator:exclusive:checked:selected {
+ image: url(:/qss_icons/rc/radio_checked_disabled.png);
+}
+
+QMenu::right-arrow {
+ margin: 5px;
+ image: url(:/qss_icons/rc/right_arrow.png)
+}
+
+
+QWidget:disabled
+{
+ color: #404040;
+ background-color: #302F2F;
+}
+
+QAbstractItemView
+{
+ alternate-background-color: #3A3939;
+ color: silver;
+ border: 1px solid 3A3939;
+ border-radius: 2px;
+ padding: 1px;
+}
+
+QWidget:focus, QMenuBar:focus
+{
+ border: 1px solid #78879b;
+}
+
+QTabWidget:focus, QCheckBox:focus, QRadioButton:focus, QSlider:focus
+{
+ border: none;
+}
+
+QLineEdit
+{
+ background-color: #201F1F;
+ padding: 2px;
+ border-style: solid;
+ border: 1px solid #3A3939;
+ border-radius: 2px;
+ color: silver;
+}
+
+QGroupBox {
+ border:1px solid #3A3939;
+ border-radius: 2px;
+ margin-top: 20px;
+}
+
+QGroupBox::title {
+ subcontrol-origin: margin;
+ subcontrol-position: top center;
+ padding-left: 10px;
+ padding-right: 10px;
+ padding-top: 10px;
+}
+
+QAbstractScrollArea
+{
+ border-radius: 2px;
+ border: 1px solid #3A3939;
+ background-color: transparent;
+}
+
+QScrollBar:horizontal
+{
+ height: 15px;
+ margin: 3px 15px 3px 15px;
+ border: 1px transparent #2A2929;
+ border-radius: 4px;
+ background-color: #2A2929;
+}
+
+QScrollBar::handle:horizontal
+{
+ background-color: #605F5F;
+ min-width: 5px;
+ border-radius: 4px;
+}
+
+QScrollBar::add-line:horizontal
+{
+ margin: 0px 3px 0px 3px;
+ border-image: url(:/qss_icons/rc/right_arrow_disabled.png);
+ width: 10px;
+ height: 10px;
+ subcontrol-position: right;
+ subcontrol-origin: margin;
+}
+
+QScrollBar::sub-line:horizontal
+{
+ margin: 0px 3px 0px 3px;
+ border-image: url(:/qss_icons/rc/left_arrow_disabled.png);
+ height: 10px;
+ width: 10px;
+ subcontrol-position: left;
+ subcontrol-origin: margin;
+}
+
+QScrollBar::add-line:horizontal:hover,QScrollBar::add-line:horizontal:on
+{
+ border-image: url(:/qss_icons/rc/right_arrow.png);
+ height: 10px;
+ width: 10px;
+ subcontrol-position: right;
+ subcontrol-origin: margin;
+}
+
+
+QScrollBar::sub-line:horizontal:hover, QScrollBar::sub-line:horizontal:on
+{
+ border-image: url(:/qss_icons/rc/left_arrow.png);
+ height: 10px;
+ width: 10px;
+ subcontrol-position: left;
+ subcontrol-origin: margin;
+}
+
+QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal
+{
+ background: none;
+}
+
+
+QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal
+{
+ background: none;
+}
+
+QScrollBar:vertical
+{
+ background-color: #2A2929;
+ width: 15px;
+ margin: 15px 3px 15px 3px;
+ border: 1px transparent #2A2929;
+ border-radius: 4px;
+}
+
+QScrollBar::handle:vertical
+{
+ background-color: #605F5F;
+ min-height: 5px;
+ border-radius: 4px;
+}
+
+QScrollBar::sub-line:vertical
+{
+ margin: 3px 0px 3px 0px;
+ border-image: url(:/qss_icons/rc/up_arrow_disabled.png);
+ height: 10px;
+ width: 10px;
+ subcontrol-position: top;
+ subcontrol-origin: margin;
+}
+
+QScrollBar::add-line:vertical
+{
+ margin: 3px 0px 3px 0px;
+ border-image: url(:/qss_icons/rc/down_arrow_disabled.png);
+ height: 10px;
+ width: 10px;
+ subcontrol-position: bottom;
+ subcontrol-origin: margin;
+}
+
+QScrollBar::sub-line:vertical:hover,QScrollBar::sub-line:vertical:on
+{
+
+ border-image: url(:/qss_icons/rc/up_arrow.png);
+ height: 10px;
+ width: 10px;
+ subcontrol-position: top;
+ subcontrol-origin: margin;
+}
+
+
+QScrollBar::add-line:vertical:hover, QScrollBar::add-line:vertical:on
+{
+ border-image: url(:/qss_icons/rc/down_arrow.png);
+ height: 10px;
+ width: 10px;
+ subcontrol-position: bottom;
+ subcontrol-origin: margin;
+}
+
+QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical
+{
+ background: none;
+}
+
+
+QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical
+{
+ background: none;
+}
+
+QTextEdit
+{
+ background-color: #201F1F;
+ color: silver;
+ border: 1px solid #3A3939;
+}
+
+QPlainTextEdit
+{
+ background-color: #201F1F;;
+ color: silver;
+ border-radius: 2px;
+ border: 1px solid #3A3939;
+}
+
+QHeaderView::section
+{
+ background-color: #3A3939;
+ color: silver;
+ padding-left: 4px;
+ border: 1px solid #6c6c6c;
+}
+
+QSizeGrip {
+ image: url(:/qss_icons/rc/sizegrip.png);
+ width: 12px;
+ height: 12px;
+}
+
+
+QMainWindow::separator
+{
+ background-color: #302F2F;
+ color: white;
+ padding-left: 4px;
+ spacing: 2px;
+ border: 1px dashed #3A3939;
+}
+
+QMainWindow::separator:hover
+{
+
+ background-color: #787876;
+ color: white;
+ padding-left: 4px;
+ border: 1px solid #3A3939;
+ spacing: 2px;
+}
+
+
+QMenu::separator
+{
+ height: 1px;
+ background-color: #3A3939;
+ color: white;
+ padding-left: 4px;
+ margin-left: 10px;
+ margin-right: 5px;
+}
+
+
+QFrame
+{
+ border-radius: 2px;
+ border: 1px solid #444;
+}
+
+QFrame[frameShape="0"]
+{
+ border-radius: 2px;
+ border: 1px transparent #444;
+}
+
+QStackedWidget
+{
+ border: 1px transparent black;
+}
+
+QToolBar {
+ border: 1px transparent #393838;
+ background: 1px solid #302F2F;
+ font-weight: bold;
+}
+
+QToolBar::handle:horizontal {
+ image: url(:/qss_icons/rc/Hmovetoolbar.png);
+}
+QToolBar::handle:vertical {
+ image: url(:/qss_icons/rc/Vmovetoolbar.png);
+}
+QToolBar::separator:horizontal {
+ image: url(:/qss_icons/rc/Hsepartoolbar.png);
+}
+QToolBar::separator:vertical {
+ image: url(:/qss_icons/rc/Vsepartoolbars.png);
+}
+
+QPushButton
+{
+ color: silver;
+ background-color: #302F2F;
+ border-width: 1px;
+ border-color: #4A4949;
+ border-style: solid;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ padding-left: 5px;
+ padding-right: 5px;
+ border-radius: 2px;
+ outline: none;
+}
+
+QPushButton:focus
+{
+ border-width: 1px;
+ border-color: #4A4949;
+ border-style: solid;
+}
+
+QPushButton:disabled
+{
+ background-color: #302F2F;
+ border-width: 1px;
+ border-color: #3A3939;
+ border-style: solid;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ padding-left: 10px;
+ padding-right: 10px;
+ /*border-radius: 2px;*/
+ color: #454545;
+}
+
+QComboBox
+{
+ selection-background-color: #A9A9A9;
+ background-color: #201F1F;
+ border-style: solid;
+ border: 1px solid #3A3939;
+ border-radius: 2px;
+ padding: 2px;
+ min-width: 75px;
+}
+
+QPushButton:hover
+{
+ background-color: #3d8ec9;
+ color: white;
+}
+
+QComboBox:hover,QAbstractSpinBox:hover,QLineEdit:hover,QTextEdit:hover,QPlainTextEdit:hover,QAbstractView:hover,QTreeView:hover
+{
+ border: 1px solid #78879b;
+ color: silver;
+}
+
+QComboBox:on
+{
+ background-color: #626873;
+ padding-top: 3px;
+ padding-left: 4px;
+ selection-background-color: #4a4a4a;
+}
+
+QComboBox QAbstractItemView
+{
+ background-color: #201F1F;
+ border-radius: 2px;
+ border: 1px solid #444;
+ selection-background-color: #A9A9A9;
+}
+
+QComboBox::drop-down
+{
+ subcontrol-origin: padding;
+ subcontrol-position: top right;
+ width: 15px;
+
+ border-left-width: 0px;
+ border-left-color: darkgray;
+ border-left-style: solid;
+ border-top-right-radius: 3px;
+ border-bottom-right-radius: 3px;
+}
+
+QComboBox::down-arrow
+{
+ image: url(:/qss_icons/rc/down_arrow_disabled.png);
+}
+
+QComboBox::down-arrow:on, QComboBox::down-arrow:hover,
+QComboBox::down-arrow:focus
+{
+ image: url(:/qss_icons/rc/down_arrow.png);
+}
+
+QAbstractSpinBox {
+ padding-top: 2px;
+ padding-bottom: 2px;
+ border: 1px solid #3A3939;
+ background-color: #201F1F;
+ color: silver;
+ border-radius: 2px;
+ min-width: 75px;
+}
+
+QAbstractSpinBox:up-button
+{
+ background-color: transparent;
+ subcontrol-origin: border;
+ subcontrol-position: center right;
+}
+
+QAbstractSpinBox:down-button
+{
+ background-color: transparent;
+ subcontrol-origin: border;
+ subcontrol-position: center left;
+}
+
+QAbstractSpinBox::up-arrow,QAbstractSpinBox::up-arrow:disabled,QAbstractSpinBox::up-arrow:off {
+ image: url(:/qss_icons/rc/up_arrow_disabled.png);
+ width: 10px;
+ height: 10px;
+}
+QAbstractSpinBox::up-arrow:hover
+{
+ image: url(:/qss_icons/rc/up_arrow.png);
+}
+
+
+QAbstractSpinBox::down-arrow,QAbstractSpinBox::down-arrow:disabled,QAbstractSpinBox::down-arrow:off
+{
+ image: url(:/qss_icons/rc/down_arrow_disabled.png);
+ width: 10px;
+ height: 10px;
+}
+QAbstractSpinBox::down-arrow:hover
+{
+ image: url(:/qss_icons/rc/down_arrow.png);
+}
+
+
+QLabel
+{
+ border: 0px solid black;
+ background-color: transparent;
+}
+
+QTabWidget{
+ border: 1px transparent black;
+}
+
+QTabWidget::pane {
+ border: 1px solid #444;
+ border-radius: 3px;
+ padding: 3px;
+}
+
+QTabBar
+{
+ qproperty-drawBase: 0;
+ left: 5px; /* move to the right by 5px */
+}
+
+QTabBar:focus
+{
+ border: 0px transparent black;
+}
+
+QTabBar::close-button {
+ image: url(:/qss_icons/rc/close.png);
+ background: transparent;
+}
+
+QTabBar::close-button:hover
+{
+ image: url(:/qss_icons/rc/close-hover.png);
+ background: transparent;
+}
+
+QTabBar::close-button:pressed {
+ image: url(:/qss_icons/rc/close-pressed.png);
+ background: transparent;
+}
+
+/* TOP TABS */
+QTabBar::tab:top {
+ color: #b1b1b1;
+ border: 1px solid #4A4949;
+ border-bottom: 1px transparent black;
+ background-color: #302F2F;
+ padding: 5px;
+ border-top-left-radius: 2px;
+ border-top-right-radius: 2px;
+}
+
+QTabBar::tab:top:!selected
+{
+ color: #b1b1b1;
+ background-color: #201F1F;
+ border: 1px transparent #4A4949;
+ border-bottom: 1px transparent #4A4949;
+ border-top-left-radius: 0px;
+ border-top-right-radius: 0px;
+}
+
+QTabBar::tab:top:!selected:hover {
+ background-color: #48576b;
+}
+
+/* BOTTOM TABS */
+QTabBar::tab:bottom {
+ color: #b1b1b1;
+ border: 1px solid #4A4949;
+ border-top: 1px transparent black;
+ background-color: #302F2F;
+ padding: 5px;
+ border-bottom-left-radius: 2px;
+ border-bottom-right-radius: 2px;
+}
+
+QTabBar::tab:bottom:!selected
+{
+ color: #b1b1b1;
+ background-color: #201F1F;
+ border: 1px transparent #4A4949;
+ border-top: 1px transparent #4A4949;
+ border-bottom-left-radius: 0px;
+ border-bottom-right-radius: 0px;
+}
+
+QTabBar::tab:bottom:!selected:hover {
+ background-color: #78879b;
+}
+
+/* LEFT TABS */
+QTabBar::tab:left {
+ color: #b1b1b1;
+ border: 1px solid #4A4949;
+ border-left: 1px transparent black;
+ background-color: #302F2F;
+ padding: 5px;
+ border-top-right-radius: 2px;
+ border-bottom-right-radius: 2px;
+}
+
+QTabBar::tab:left:!selected
+{
+ color: #b1b1b1;
+ background-color: #201F1F;
+ border: 1px transparent #4A4949;
+ border-right: 1px transparent #4A4949;
+ border-top-right-radius: 0px;
+ border-bottom-right-radius: 0px;
+}
+
+QTabBar::tab:left:!selected:hover {
+ background-color: #48576b;
+}
+
+
+/* RIGHT TABS */
+QTabBar::tab:right {
+ color: #b1b1b1;
+ border: 1px solid #4A4949;
+ border-right: 1px transparent black;
+ background-color: #302F2F;
+ padding: 5px;
+ border-top-left-radius: 2px;
+ border-bottom-left-radius: 2px;
+}
+
+QTabBar::tab:right:!selected
+{
+ color: #b1b1b1;
+ background-color: #201F1F;
+ border: 1px transparent #4A4949;
+ border-right: 1px transparent #4A4949;
+ border-top-left-radius: 0px;
+ border-bottom-left-radius: 0px;
+}
+
+QTabBar::tab:right:!selected:hover {
+ background-color: #48576b;
+}
+
+QTabBar QToolButton::right-arrow:enabled {
+ image: url(:/qss_icons/rc/right_arrow.png);
+ }
+
+ QTabBar QToolButton::left-arrow:enabled {
+ image: url(:/qss_icons/rc/left_arrow.png);
+ }
+
+QTabBar QToolButton::right-arrow:disabled {
+ image: url(:/qss_icons/rc/right_arrow_disabled.png);
+ }
+
+ QTabBar QToolButton::left-arrow:disabled {
+ image: url(:/qss_icons/rc/left_arrow_disabled.png);
+ }
+
+
+QDockWidget {
+ border: 1px solid #403F3F;
+ titlebar-close-icon: url(:/qss_icons/rc/close.png);
+ titlebar-normal-icon: url(:/qss_icons/rc/undock.png);
+}
+
+QDockWidget::close-button, QDockWidget::float-button {
+ border: 1px solid transparent;
+ border-radius: 2px;
+ background: transparent;
+}
+
+QDockWidget::close-button:hover, QDockWidget::float-button:hover {
+ background: rgba(255, 255, 255, 10);
+}
+
+QDockWidget::close-button:pressed, QDockWidget::float-button:pressed {
+ padding: 1px -1px -1px 1px;
+ background: rgba(255, 255, 255, 10);
+}
+
+QTreeView, QListView
+{
+ border: 1px solid #444;
+ background-color: #201F1F;
+}
+
+QTreeView:branch:selected, QTreeView:branch:hover
+{
+ background: url(:/qss_icons/rc/transparent.png);
+}
+
+QTreeView::branch:has-siblings:!adjoins-item {
+ border-image: url(:/qss_icons/rc/transparent.png);
+}
+
+QTreeView::branch:has-siblings:adjoins-item {
+ border-image: url(:/qss_icons/rc/transparent.png);
+}
+
+QTreeView::branch:!has-children:!has-siblings:adjoins-item {
+ border-image: url(:/qss_icons/rc/transparent.png);
+}
+
+QTreeView::branch:has-children:!has-siblings:closed,
+QTreeView::branch:closed:has-children:has-siblings {
+ image: url(:/qss_icons/rc/branch_closed.png);
+}
+
+QTreeView::branch:open:has-children:!has-siblings,
+QTreeView::branch:open:has-children:has-siblings {
+ image: url(:/qss_icons/rc/branch_open.png);
+}
+
+QTreeView::branch:has-children:!has-siblings:closed:hover,
+QTreeView::branch:closed:has-children:has-siblings:hover {
+ image: url(:/qss_icons/rc/branch_closed-on.png);
+ }
+
+QTreeView::branch:open:has-children:!has-siblings:hover,
+QTreeView::branch:open:has-children:has-siblings:hover {
+ image: url(:/qss_icons/rc/branch_open-on.png);
+ }
+
+QListView::item:!selected:hover, QListView::item:!selected:hover, QTreeView::item:!selected:hover {
+ background: rgba(0, 0, 0, 0);
+ outline: 0;
+ color: #FFFFFF
+}
+
+QListView::item:selected:hover, QListView::item:selected:hover, QTreeView::item:selected:hover {
+ background: #3d8ec9;
+ color: #FFFFFF;
+}
+
+QSlider::groove:horizontal {
+ border: 1px solid #3A3939;
+ height: 8px;
+ background: #201F1F;
+ margin: 2px 0;
+ border-radius: 2px;
+}
+
+QSlider::handle:horizontal {
+ background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1,
+ stop: 0.0 silver, stop: 0.2 #a8a8a8, stop: 1 #727272);
+ border: 1px solid #3A3939;
+ width: 14px;
+ height: 14px;
+ margin: -4px 0;
+ border-radius: 2px;
+}
+
+QSlider::groove:vertical {
+ border: 1px solid #3A3939;
+ width: 8px;
+ background: #201F1F;
+ margin: 0 0px;
+ border-radius: 2px;
+}
+
+QSlider::handle:vertical {
+ background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0.0 silver,
+ stop: 0.2 #a8a8a8, stop: 1 #727272);
+ border: 1px solid #3A3939;
+ width: 14px;
+ height: 14px;
+ margin: 0 -4px;
+ border-radius: 2px;
+}
+
+QToolButton {
+ background-color: transparent;
+ border: 1px transparent #4A4949;
+ border-radius: 2px;
+ margin: 3px;
+ padding: 3px;
+}
+
+QToolButton[popupMode="1"] { /* only for MenuButtonPopup */
+ padding-right: 20px; /* make way for the popup button */
+ border: 1px transparent #4A4949;
+ border-radius: 5px;
+}
+
+QToolButton[popupMode="2"] { /* only for InstantPopup */
+ padding-right: 10px; /* make way for the popup button */
+ border: 1px transparent #4A4949;
+}
+
+
+QToolButton:hover, QToolButton::menu-button:hover {
+ background-color: transparent;
+ border: 1px solid #78879b;
+}
+
+QToolButton:checked, QToolButton:pressed,
+ QToolButton::menu-button:pressed {
+ background-color: #4A4949;
+ border: 1px solid #78879b;
+}
+
+/* the subcontrol below is used only in the InstantPopup or DelayedPopup mode */
+QToolButton::menu-indicator {
+ image: url(:/qss_icons/rc/down_arrow.png);
+ top: -7px; left: -2px; /* shift it a bit */
+}
+
+/* the subcontrols below are used only in the MenuButtonPopup mode */
+QToolButton::menu-button {
+ border: 1px transparent #4A4949;
+ border-top-right-radius: 6px;
+ border-bottom-right-radius: 6px;
+ /* 16px width + 4px for border = 20px allocated above */
+ width: 16px;
+ outline: none;
+}
+
+QToolButton::menu-arrow {
+ image: url(:/qss_icons/rc/down_arrow.png);
+}
+
+QToolButton::menu-arrow:open {
+ top: 1px; left: 1px; /* shift it a bit */
+ border: 1px solid #3A3939;
+}
+
+QPushButton::menu-indicator {
+ subcontrol-origin: padding;
+ subcontrol-position: bottom right;
+ left: 8px;
+}
+
+QTableView
+{
+ border: 1px solid #444;
+ gridline-color: #6c6c6c;
+ background-color: #201F1F;
+}
+
+
+QTableView, QHeaderView
+{
+ border-radius: 0px;
+}
+
+QTableView::item:pressed, QListView::item:pressed, QTreeView::item:pressed {
+ background: #78879b;
+ color: #FFFFFF;
+}
+
+QTableView::item:selected:active, QTreeView::item:selected:active, QListView::item:selected:active {
+ background: #3d8ec9;
+ color: #FFFFFF;
+}
+
+
+QHeaderView
+{
+ border: 1px transparent;
+ border-radius: 2px;
+ margin: 0px;
+ padding: 0px;
+}
+
+QHeaderView::section {
+ background-color: #3A3939;
+ color: silver;
+ padding: 4px;
+ border: 1px solid #6c6c6c;
+ border-radius: 0px;
+ text-align: center;
+}
+
+QHeaderView::section::vertical::first, QHeaderView::section::vertical::only-one
+{
+ border-top: 1px solid #6c6c6c;
+}
+
+QHeaderView::section::vertical
+{
+ border-top: transparent;
+}
+
+QHeaderView::section::horizontal::first, QHeaderView::section::horizontal::only-one
+{
+ border-left: 1px solid #6c6c6c;
+}
+
+QHeaderView::section::horizontal
+{
+ border-left: transparent;
+}
+
+
+QHeaderView::section:checked
+ {
+ color: white;
+ background-color: #5A5959;
+ }
+
+ /* style the sort indicator */
+QHeaderView::down-arrow {
+ image: url(:/qss_icons/rc/down_arrow.png);
+}
+
+QHeaderView::up-arrow {
+ image: url(:/qss_icons/rc/up_arrow.png);
+}
+
+
+QTableCornerButton::section {
+ background-color: #3A3939;
+ border: 1px solid #3A3939;
+ border-radius: 2px;
+}
+
+QToolBox {
+ padding: 3px;
+ border: 1px transparent black;
+}
+
+QToolBox::tab {
+ color: #b1b1b1;
+ background-color: #302F2F;
+ border: 1px solid #4A4949;
+ border-bottom: 1px transparent #302F2F;
+ border-top-left-radius: 5px;
+ border-top-right-radius: 5px;
+}
+
+ QToolBox::tab:selected { /* italicize selected tabs */
+ font: italic;
+ background-color: #302F2F;
+ border-color: #3d8ec9;
+ }
+
+QStatusBar::item {
+ border: 1px solid #3A3939;
+ border-radius: 2px;
+ }
+
+
+QFrame[height="3"], QFrame[width="3"] {
+ background-color: #444;
+}
+
+
+QSplitter::handle {
+ border: 1px dashed #3A3939;
+}
+
+QSplitter::handle:hover {
+ background-color: #787876;
+ border: 1px solid #3A3939;
+}
+
+QSplitter::handle:horizontal {
+ width: 1px;
+}
+
+QSplitter::handle:vertical {
+ height: 1px;
+}
+
+MessageItem
+{
+ border: none;
}
MessageEdit
@@ -23,18 +1233,57 @@ MessageEdit:hover
border: none;
}
-MessageEdit
+QListWidget QPushButton
+{
+ background-color: transparent;
+ border: none;
+}
+
+QPushButton:hover
+{
+ background-color: #4A4949;
+}
+
+#messages:item:selected
+{
+ background-color: #1E90FF;
+}
+
+MessageEdit
{
background-color: transparent;
}
-#warningLabel
+#messages:item:selected QListWidgetItem
{
- color: #BC1C1C;
+ background-color: #1E90FF;
}
-#groupInvitesPushButton
+#friends_list:item:selected
{
- background-color: #009c00;
+ background-color: #333333;
}
+#toxygen
+{
+ color: #A9A9A9;
+}
+
+QCheckBox
+{
+ spacing: 5px;
+ outline: none;
+ color: #bbb;
+ margin-bottom: 2px;
+ text-align: center;
+}
+
+QListWidget > QLabel
+{
+ color: #A9A9A9;
+}
+
+#contact_name
+{
+ padding-left: 22px;
+}
\ No newline at end of file
diff --git a/toxygen/wrapper/tox.py b/toxygen/tox.py
similarity index 64%
rename from toxygen/wrapper/tox.py
rename to toxygen/tox.py
index 21b0ebc..862badd 100644
--- a/toxygen/wrapper/tox.py
+++ b/toxygen/tox.py
@@ -1,35 +1,24 @@
# -*- coding: utf-8 -*-
-from ctypes import *
-from wrapper.toxcore_enums_and_consts import *
-from wrapper.toxav import ToxAV
-from wrapper.libtox import LibToxCore
+from ctypes import c_char_p, Structure, c_bool, byref, c_int, c_size_t, POINTER, c_uint16, c_void_p, c_uint64
+from ctypes import create_string_buffer, ArgumentError, CFUNCTYPE, c_uint32, sizeof, c_uint8
+from toxcore_enums_and_consts import *
+from toxav import ToxAV
+from libtox import LibToxCore
class ToxOptions(Structure):
_fields_ = [
('ipv6_enabled', c_bool),
('udp_enabled', c_bool),
- ('local_discovery_enabled', c_bool),
('proxy_type', c_int),
('proxy_host', c_char_p),
('proxy_port', c_uint16),
('start_port', c_uint16),
('end_port', c_uint16),
('tcp_port', c_uint16),
- ('hole_punching_enabled', c_bool),
('savedata_type', c_int),
('savedata_data', c_char_p),
- ('savedata_length', c_size_t),
- ('log_callback', c_void_p),
- ('log_user_data', c_void_p)
- ]
-
-
-class GroupChatSelfPeerInfo(Structure):
- _fields_ = [
- ('nick', c_char_p),
- ('nick_length', c_uint8),
- ('user_status', c_int)
+ ('savedata_length', c_size_t)
]
@@ -43,8 +32,9 @@ def bin_to_string(raw_id, length):
class Tox:
- libtoxcore = LibToxCore()
+ libtoxcore = LibToxCore()
+
def __init__(self, tox_options=None, tox_pointer=None):
"""
Creates and initialises a new Tox instance with the options passed.
@@ -59,9 +49,8 @@ class Tox:
self._tox_pointer = tox_pointer
else:
tox_err_new = c_int()
- f = Tox.libtoxcore.tox_new
- f.restype = POINTER(c_void_p)
- self._tox_pointer = f(tox_options, byref(tox_err_new))
+ Tox.libtoxcore.tox_new.restype = POINTER(c_void_p)
+ self._tox_pointer = Tox.libtoxcore.tox_new(tox_options, byref(tox_err_new))
tox_err_new = tox_err_new.value
if tox_err_new == TOX_ERR_NEW['NULL']:
raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
@@ -103,25 +92,10 @@ class Tox:
self.file_recv_chunk_cb = None
self.friend_lossy_packet_cb = None
self.friend_lossless_packet_cb = None
- self.group_moderation_cb = None
- self.group_join_fail_cb = None
- self.group_self_join_cb = None
- self.group_invite_cb = None
- self.group_custom_packet_cb = None
- self.group_private_message_cb = None
- self.group_message_cb = None
- self.group_password_cb = None
- self.group_peer_limit_cb = None
- self.group_privacy_state_cb = None
- self.group_topic_cb = None
- self.group_peer_status_cb = None
- self.group_peer_name_cb = None
- self.group_peer_exit_cb = None
- self.group_peer_join_cb = None
self.AV = ToxAV(self._tox_pointer)
- def kill(self):
+ def __del__(self):
del self.AV
Tox.libtoxcore.tox_kill(self._tox_pointer)
@@ -219,7 +193,6 @@ class Tox:
:param public_key: The long term public key of the bootstrap node (TOX_PUBLIC_KEY_SIZE bytes).
:return: True on success.
"""
- address = bytes(address, 'utf-8')
tox_err_bootstrap = c_int()
result = Tox.libtoxcore.tox_bootstrap(self._tox_pointer, c_char_p(address), c_uint16(port),
string_to_bin(public_key), byref(tox_err_bootstrap))
@@ -246,7 +219,6 @@ class Tox:
:param public_key: The long term public key of the TCP relay (TOX_PUBLIC_KEY_SIZE bytes).
:return: True on success.
"""
- address = bytes(address, 'utf-8')
tox_err_bootstrap = c_int()
result = Tox.libtoxcore.tox_add_tcp_relay(self._tox_pointer, c_char_p(address), c_uint16(port),
string_to_bin(public_key), byref(tox_err_bootstrap))
@@ -270,7 +242,7 @@ class Tox:
"""
return Tox.libtoxcore.tox_self_get_connection_status(self._tox_pointer)
- def callback_self_connection_status(self, callback):
+ def callback_self_connection_status(self, callback, user_data):
"""
Set the callback for the `self_connection_status` event. Pass None to unset.
@@ -281,11 +253,12 @@ class Tox:
:param callback: Python function. Should take pointer (c_void_p) to Tox object,
TOX_CONNECTION (c_int),
pointer (c_void_p) to user_data
+ :param user_data: pointer (c_void_p) to user data
"""
c_callback = CFUNCTYPE(None, c_void_p, c_int, c_void_p)
self.self_connection_status_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_self_connection_status(self._tox_pointer,
- self.self_connection_status_cb)
+ self.self_connection_status_cb, user_data)
def iteration_interval(self):
"""
@@ -294,13 +267,11 @@ class Tox:
"""
return Tox.libtoxcore.tox_iteration_interval(self._tox_pointer)
- def iterate(self, user_data=None):
+ def iterate(self):
"""
The main loop that needs to be run in intervals of tox_iteration_interval() milliseconds.
"""
- if user_data is not None:
- user_data = c_char_p(user_data)
- Tox.libtoxcore.tox_iterate(self._tox_pointer, user_data)
+ Tox.libtoxcore.tox_iterate(self._tox_pointer)
# -----------------------------------------------------------------------------------------------------------------
# Internal client information (Tox address/id)
@@ -376,7 +347,6 @@ class Tox:
:return: True on success.
"""
tox_err_set_info = c_int()
- name = bytes(name, 'utf-8')
result = Tox.libtoxcore.tox_self_set_name(self._tox_pointer, c_char_p(name),
c_size_t(len(name)), byref(tox_err_set_info))
tox_err_set_info = tox_err_set_info.value
@@ -425,7 +395,6 @@ class Tox:
:return: True on success.
"""
tox_err_set_info = c_int()
- status_message = bytes(status_message, 'utf-8')
result = Tox.libtoxcore.tox_self_set_status_message(self._tox_pointer, c_char_p(status_message),
c_size_t(len(status_message)), byref(tox_err_set_info))
tox_err_set_info = tox_err_set_info.value
@@ -727,7 +696,7 @@ class Tox:
elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']:
raise ArgumentError('The friend_number did not designate a valid friend.')
- def callback_friend_name(self, callback):
+ def callback_friend_name(self, callback, user_data):
"""
Set the callback for the `friend_name` event. Pass None to unset.
@@ -738,10 +707,11 @@ class Tox:
A byte array (c_char_p) containing the same data as tox_friend_get_name would write to its `name` parameter,
A value (c_size_t) equal to the return value of tox_friend_get_name_size,
pointer (c_void_p) to user_data
+ :param user_data: pointer (c_void_p) to user data
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_char_p, c_size_t, c_void_p)
self.friend_name_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_friend_name(self._tox_pointer, self.friend_name_cb)
+ Tox.libtoxcore.tox_callback_friend_name(self._tox_pointer, self.friend_name_cb, user_data)
def friend_get_status_message_size(self, friend_number):
"""
@@ -790,7 +760,7 @@ class Tox:
elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']:
raise ArgumentError('The friend_number did not designate a valid friend.')
- def callback_friend_status_message(self, callback):
+ def callback_friend_status_message(self, callback, user_data):
"""
Set the callback for the `friend_status_message` event. Pass NULL to unset.
@@ -802,11 +772,12 @@ class Tox:
`status_message` parameter,
A value (c_size_t) equal to the return value of tox_friend_get_status_message_size,
pointer (c_void_p) to user_data
+ :param user_data: pointer (c_void_p) to user data
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_char_p, c_size_t, c_void_p)
self.friend_status_message_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_friend_status_message(self._tox_pointer,
- self.friend_status_message_cb)
+ self.friend_status_message_cb, c_void_p(user_data))
def friend_get_status(self, friend_number):
"""
@@ -830,7 +801,7 @@ class Tox:
elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']:
raise ArgumentError('The friend_number did not designate a valid friend.')
- def callback_friend_status(self, callback):
+ def callback_friend_status(self, callback, user_data):
"""
Set the callback for the `friend_status` event. Pass None to unset.
@@ -844,7 +815,7 @@ class Tox:
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_int, c_void_p)
self.friend_status_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_friend_status(self._tox_pointer, self.friend_status_cb)
+ Tox.libtoxcore.tox_callback_friend_status(self._tox_pointer, self.friend_status_cb, c_void_p(user_data))
def friend_get_connection_status(self, friend_number):
"""
@@ -869,7 +840,7 @@ class Tox:
elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']:
raise ArgumentError('The friend_number did not designate a valid friend.')
- def callback_friend_connection_status(self, callback):
+ def callback_friend_connection_status(self, callback, user_data):
"""
Set the callback for the `friend_connection_status` event. Pass NULL to unset.
@@ -882,11 +853,12 @@ class Tox:
The friend number (c_uint32) of the friend whose connection status changed,
The result of calling tox_friend_get_connection_status (TOX_CONNECTION) on the passed friend_number,
pointer (c_void_p) to user_data
+ :param user_data: pointer (c_void_p) to user data
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_int, c_void_p)
self.friend_connection_status_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_friend_connection_status(self._tox_pointer,
- self.friend_connection_status_cb)
+ self.friend_connection_status_cb, c_void_p(user_data))
def friend_get_typing(self, friend_number):
"""
@@ -908,7 +880,7 @@ class Tox:
elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']:
raise ArgumentError('The friend_number did not designate a valid friend.')
- def callback_friend_typing(self, callback):
+ def callback_friend_typing(self, callback, user_data):
"""
Set the callback for the `friend_typing` event. Pass NULL to unset.
@@ -918,10 +890,11 @@ class Tox:
The friend number (c_uint32) of the friend who started or stopped typing,
The result of calling tox_friend_get_typing (c_bool) on the passed friend_number,
pointer (c_void_p) to user_data
+ :param user_data: pointer (c_void_p) to user data
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_bool, c_void_p)
self.friend_typing_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_friend_typing(self._tox_pointer, self.friend_typing_cb)
+ Tox.libtoxcore.tox_callback_friend_typing(self._tox_pointer, self.friend_typing_cb, c_void_p(user_data))
# -----------------------------------------------------------------------------------------------------------------
# Sending private messages
@@ -986,7 +959,7 @@ class Tox:
elif tox_err_friend_send_message == TOX_ERR_FRIEND_SEND_MESSAGE['EMPTY']:
raise ArgumentError('Attempted to send a zero-length message.')
- def callback_friend_read_receipt(self, callback):
+ def callback_friend_read_receipt(self, callback, user_data):
"""
Set the callback for the `friend_read_receipt` event. Pass None to unset.
@@ -1002,13 +975,13 @@ class Tox:
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_void_p)
self.friend_read_receipt_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_friend_read_receipt(self._tox_pointer,
- self.friend_read_receipt_cb)
+ self.friend_read_receipt_cb, c_void_p(user_data))
# -----------------------------------------------------------------------------------------------------------------
# Receiving private messages and friend requests
# -----------------------------------------------------------------------------------------------------------------
- def callback_friend_request(self, callback):
+ def callback_friend_request(self, callback, user_data):
"""
Set the callback for the `friend_request` event. Pass None to unset.
@@ -1023,9 +996,9 @@ class Tox:
"""
c_callback = CFUNCTYPE(None, c_void_p, POINTER(c_uint8), c_char_p, c_size_t, c_void_p)
self.friend_request_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_friend_request(self._tox_pointer, self.friend_request_cb)
+ Tox.libtoxcore.tox_callback_friend_request(self._tox_pointer, self.friend_request_cb, c_void_p(user_data))
- def callback_friend_message(self, callback):
+ def callback_friend_message(self, callback, user_data):
"""
Set the callback for the `friend_message` event. Pass None to unset.
@@ -1037,10 +1010,11 @@ class Tox:
The message data (c_char_p) they sent,
The size (c_size_t) of the message byte array.
pointer (c_void_p) to user_data
+ :param user_data: pointer (c_void_p) to user data
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_int, c_char_p, c_size_t, c_void_p)
self.friend_message_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_friend_message(self._tox_pointer, self.friend_message_cb)
+ Tox.libtoxcore.tox_callback_friend_message(self._tox_pointer, self.friend_message_cb, c_void_p(user_data))
# -----------------------------------------------------------------------------------------------------------------
# File transmission: common between sending and receiving
@@ -1098,7 +1072,7 @@ class Tox:
elif tox_err_file_control == TOX_ERR_FILE_CONTROL['SENDQ']:
raise RuntimeError('Packet queue is full.')
- def callback_file_recv_control(self, callback):
+ def callback_file_recv_control(self, callback, user_data):
"""
Set the callback for the `file_recv_control` event. Pass NULL to unset.
@@ -1113,11 +1087,12 @@ class Tox:
The friend-specific file number (c_uint32) the data received is associated with.
The file control (TOX_FILE_CONTROL) command received.
pointer (c_void_p) to user_data
+ :param user_data: pointer (c_void_p) to user data
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_int, c_void_p)
self.file_recv_control_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_file_recv_control(self._tox_pointer,
- self.file_recv_control_cb)
+ self.file_recv_control_cb, user_data)
def file_seek(self, friend_number, file_number, position):
"""
@@ -1287,7 +1262,7 @@ class Tox:
elif tox_err_file_send_chunk == TOX_ERR_FILE_SEND_CHUNK['WRONG_POSITION']:
raise ArgumentError('Position parameter was wrong.')
- def callback_file_chunk_request(self, callback):
+ def callback_file_chunk_request(self, callback, user_data):
"""
Set the callback for the `file_chunk_request` event. Pass None to unset.
@@ -1313,16 +1288,17 @@ class Tox:
The file or stream position (c_uint64) from which to continue reading.
The number of bytes (c_size_t) requested for the current chunk.
pointer (c_void_p) to user_data
+ :param user_data: pointer (c_void_p) to user data
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint64, c_size_t, c_void_p)
self.file_chunk_request_cb = c_callback(callback)
- self.libtoxcore.tox_callback_file_chunk_request(self._tox_pointer, self.file_chunk_request_cb)
+ self.libtoxcore.tox_callback_file_chunk_request(self._tox_pointer, self.file_chunk_request_cb, user_data)
# -----------------------------------------------------------------------------------------------------------------
# File transmission: receiving
# -----------------------------------------------------------------------------------------------------------------
- def callback_file_recv(self, callback):
+ def callback_file_recv(self, callback, user_data):
"""
Set the callback for the `file_recv` event. Pass None to unset.
@@ -1342,12 +1318,13 @@ class Tox:
send request.
Size in bytes (c_size_t) of the filename.
pointer (c_void_p) to user_data
+ :param user_data: pointer (c_void_p) to user data
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint32, c_uint64, c_char_p, c_size_t, c_void_p)
self.file_recv_cb = c_callback(callback)
- self.libtoxcore.tox_callback_file_recv(self._tox_pointer, self.file_recv_cb)
+ self.libtoxcore.tox_callback_file_recv(self._tox_pointer, self.file_recv_cb, user_data)
- def callback_file_recv_chunk(self, callback):
+ def callback_file_recv_chunk(self, callback, user_data):
"""
Set the callback for the `file_recv_chunk` event. Pass NULL to unset.
@@ -1368,10 +1345,11 @@ class Tox:
A byte array (c_char_p) containing the received chunk.
The length (c_size_t) of the received chunk.
pointer (c_void_p) to user_data
+ :param user_data: pointer (c_void_p) to user data
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint64, POINTER(c_uint8), c_size_t, c_void_p)
self.file_recv_chunk_cb = c_callback(callback)
- self.libtoxcore.tox_callback_file_recv_chunk(self._tox_pointer, self.file_recv_chunk_cb)
+ self.libtoxcore.tox_callback_file_recv_chunk(self._tox_pointer, self.file_recv_chunk_cb, user_data)
# -----------------------------------------------------------------------------------------------------------------
# Low-level custom packet sending and receiving
@@ -1452,7 +1430,7 @@ class Tox:
elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['SENDQ']:
raise RuntimeError('Packet queue is full.')
- def callback_friend_lossy_packet(self, callback):
+ def callback_friend_lossy_packet(self, callback, user_data):
"""
Set the callback for the `friend_lossy_packet` event. Pass NULL to unset.
@@ -1462,12 +1440,13 @@ class Tox:
A byte array (c_uint8 array) containing the received packet data,
length (c_size_t) - The length of the packet data byte array,
pointer (c_void_p) to user_data
+ :param user_data: pointer (c_void_p) to user data
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t, c_void_p)
self.friend_lossy_packet_cb = c_callback(callback)
- self.libtoxcore.tox_callback_friend_lossy_packet(self._tox_pointer, self.friend_lossy_packet_cb)
+ self.libtoxcore.tox_callback_friend_lossy_packet(self._tox_pointer, self.friend_lossy_packet_cb, user_data)
- def callback_friend_lossless_packet(self, callback):
+ def callback_friend_lossless_packet(self, callback, user_data):
"""
Set the callback for the `friend_lossless_packet` event. Pass NULL to unset.
@@ -1477,10 +1456,12 @@ class Tox:
A byte array (c_uint8 array) containing the received packet data,
length (c_size_t) - The length of the packet data byte array,
pointer (c_void_p) to user_data
+ :param user_data: pointer (c_void_p) to user data
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t, c_void_p)
self.friend_lossless_packet_cb = c_callback(callback)
- self.libtoxcore.tox_callback_friend_lossless_packet(self._tox_pointer, self.friend_lossless_packet_cb)
+ self.libtoxcore.tox_callback_friend_lossless_packet(self._tox_pointer, self.friend_lossless_packet_cb,
+ user_data)
# -----------------------------------------------------------------------------------------------------------------
# Low-level network information
@@ -1529,1004 +1510,3 @@ class Tox:
return result
elif tox_err_get_port == TOX_ERR_GET_PORT['NOT_BOUND']:
raise RuntimeError('The instance was not bound to any port.')
-
- # -----------------------------------------------------------------------------------------------------------------
- # Group chat instance management
- # -----------------------------------------------------------------------------------------------------------------
-
- def group_new(self, privacy_state, group_name, nick, status):
- """
- Creates a new group chat.
-
- This function creates a new group chat object and adds it to the chats array.
-
- The client should initiate its peer list with self info after calling this function, as
- the peer_join callback will not be triggered.
-
- :param privacy_state: The privacy state of the group. If this is set to TOX_GROUP_PRIVACY_STATE_PUBLIC,
- the group will attempt to announce itself to the DHT and anyone with the Chat ID may join.
- Otherwise a friend invite will be required to join the group.
- :param group_name: The name of the group. The name must be non-NULL.
-
- :return group number on success, UINT32_MAX on failure.
- """
-
- error = c_int()
- peer_info = self.group_self_peer_info_new()
- nick = bytes(nick, 'utf-8')
- group_name = group_name.encode('utf-8')
- peer_info.contents.nick = c_char_p(nick)
- peer_info.contents.nick_length = len(nick)
- peer_info.contents.user_status = status
- result = Tox.libtoxcore.tox_group_new(self._tox_pointer, privacy_state, group_name,
- len(group_name), peer_info, byref(error))
- return result
-
- def group_join(self, chat_id, password, nick, status):
- """
- Joins a group chat with specified Chat ID.
-
- This function creates a new group chat object, adds it to the chats array, and sends
- a DHT announcement to find peers in the group associated with chat_id. Once a peer has been
- found a join attempt will be initiated.
-
- :param chat_id: The Chat ID of the group you wish to join. This must be TOX_GROUP_CHAT_ID_SIZE bytes.
- :param password: The password required to join the group. Set to NULL if no password is required.
-
- :return group_number on success, UINT32_MAX on failure.
- """
-
- error = c_int()
- peer_info = self.group_self_peer_info_new()
- nick = bytes(nick, 'utf-8')
- peer_info.contents.nick = c_char_p(nick)
- peer_info.contents.nick_length = len(nick)
- peer_info.contents.user_status = status
- result = Tox.libtoxcore.tox_group_join(self._tox_pointer, string_to_bin(chat_id),
- password,
- len(password) if password is not None else 0,
- peer_info,
- byref(error))
- return result
-
- def group_reconnect(self, group_number):
- """
- Reconnects to a group.
-
- This function disconnects from all peers in the group, then attempts to reconnect with the group.
- The caller's state is not changed (i.e. name, status, role, chat public key etc.)
-
- :param group_number: The group number of the group we wish to reconnect to.
- :return True on success.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_reconnect(self._tox_pointer, group_number, byref(error))
- return result
-
- def group_is_connected(self, group_number):
- error = c_int()
- result = Tox.libtoxcore.tox_group_is_connected(self._tox_pointer, group_number, byref(error))
- return result
-
- def group_disconnect(self, group_number):
- error = c_int()
- result = Tox.libtoxcore.tox_group_disconnect(self._tox_pointer, group_number, byref(error))
- return result
-
- def group_leave(self, group_number, message=''):
- """
- Leaves a group.
-
- This function sends a parting packet containing a custom (non-obligatory) message to all
- peers in a group, and deletes the group from the chat array. All group state information is permanently
- lost, including keys and role credentials.
-
- :param group_number: The group number of the group we wish to leave.
- :param message: The parting message to be sent to all the peers. Set to NULL if we do not wish to
- send a parting message.
-
- :return True if the group chat instance was successfully deleted.
- """
-
- error = c_int()
- f = Tox.libtoxcore.tox_group_leave
- f.restype = c_bool
- result = f(self._tox_pointer, group_number, message,
- len(message) if message is not None else 0, byref(error))
- return result
-
- # -----------------------------------------------------------------------------------------------------------------
- # Group user-visible client information (nickname/status/role/public key)
- # -----------------------------------------------------------------------------------------------------------------
-
- def group_self_set_name(self, group_number, name):
- """
- Set the client's nickname for the group instance designated by the given group number.
-
- Nickname length cannot exceed TOX_MAX_NAME_LENGTH. If length is equal to zero or name is a NULL
- pointer, the function call will fail.
-
- :param name: A byte array containing the new nickname.
-
- :return True on success.
- """
-
- error = c_int()
- name = bytes(name, 'utf-8')
- result = Tox.libtoxcore.tox_group_self_set_name(self._tox_pointer, group_number, name, len(name), byref(error))
- return result
-
- def group_self_get_name_size(self, group_number):
- """
- Return the length of the client's current nickname for the group instance designated
- by group_number as passed to tox_group_self_set_name.
-
- If no nickname was set before calling this function, the name is empty,
- and this function returns 0.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_self_get_name_size(self._tox_pointer, group_number, byref(error))
- return result
-
- def group_self_get_name(self, group_number):
- """
- Write the nickname set by tox_group_self_set_name to a byte array.
-
- If no nickname was set before calling this function, the name is empty,
- and this function has no effect.
-
- Call tox_group_self_get_name_size to find out how much memory to allocate for the result.
- :return nickname
- """
-
- error = c_int()
- size = self.group_self_get_name_size(group_number)
- name = create_string_buffer(size)
- result = Tox.libtoxcore.tox_group_self_get_name(self._tox_pointer, group_number, name, byref(error))
- return str(name[:size], 'utf-8')
-
- def group_self_set_status(self, group_number, status):
-
- """
- Set the client's status for the group instance. Status must be a TOX_USER_STATUS.
- :return True on success.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_self_set_status(self._tox_pointer, group_number, status, byref(error))
- return result
-
- def group_self_get_status(self, group_number):
- """
- returns the client's status for the group instance on success.
- return value is unspecified on failure.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_self_get_status(self._tox_pointer, group_number, byref(error))
- return result
-
- def group_self_get_role(self, group_number):
- """
- returns the client's role for the group instance on success.
- return value is unspecified on failure.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_self_get_role(self._tox_pointer, group_number, byref(error))
- return result
-
- def group_self_get_peer_id(self, group_number):
- """
- returns the client's peer id for the group instance on success.
- return value is unspecified on failure.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_self_get_peer_id(self._tox_pointer, group_number, byref(error))
- return result
-
- def group_self_get_public_key(self, group_number):
- """
- Write the client's group public key designated by the given group number to a byte array.
-
- This key will be permanently tied to the client's identity for this particular group until
- the client explicitly leaves the group or gets kicked/banned. This key is the only way for
- other peers to reliably identify the client across client restarts.
-
- `public_key` should have room for at least TOX_GROUP_PEER_PUBLIC_KEY_SIZE bytes.
-
- :return public key
- """
-
- error = c_int()
- key = create_string_buffer(TOX_GROUP_PEER_PUBLIC_KEY_SIZE)
- result = Tox.libtoxcore.tox_group_self_get_public_key(self._tox_pointer, group_number,
- key, byref(error))
- return bin_to_string(key, TOX_GROUP_PEER_PUBLIC_KEY_SIZE)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Peer-specific group state queries.
- # -----------------------------------------------------------------------------------------------------------------
-
- def group_peer_get_name_size(self, group_number, peer_id):
- """
- Return the length of the peer's name. If the group number or ID is invalid, the
- return value is unspecified.
-
- The return value is equal to the `length` argument received by the last
- `group_peer_name` callback.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_peer_get_name_size(self._tox_pointer, group_number, peer_id, byref(error))
- return result
-
- def group_peer_get_name(self, group_number, peer_id):
- """
- Write the name of the peer designated by the given ID to a byte
- array.
-
- Call tox_group_peer_get_name_size to determine the allocation size for the `name` parameter.
-
- The data written to `name` is equal to the data received by the last
- `group_peer_name` callback.
-
- :param group_number: The group number of the group we wish to query.
- :param peer_id: The ID of the peer whose name we want to retrieve.
-
- :return name.
- """
- error = c_int()
- size = self.group_peer_get_name_size(group_number, peer_id)
- name = create_string_buffer(size)
- result = Tox.libtoxcore.tox_group_peer_get_name(self._tox_pointer, group_number, peer_id, name, byref(error))
- return str(name[:], 'utf-8')
-
- def group_peer_get_status(self, group_number, peer_id):
- """
- Return the peer's user status (away/busy/...). If the ID or group number is
- invalid, the return value is unspecified.
-
- The status returned is equal to the last status received through the
- `group_peer_status` callback.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_peer_get_status(self._tox_pointer, group_number, peer_id, byref(error))
- return result
-
- def group_peer_get_role(self, group_number, peer_id):
- """
- Return the peer's role (user/moderator/founder...). If the ID or group number is
- invalid, the return value is unspecified.
-
- The role returned is equal to the last role received through the
- `group_moderation` callback.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_peer_get_role(self._tox_pointer, group_number, peer_id, byref(error))
- return result
-
- def group_peer_get_public_key(self, group_number, peer_id):
- """
- Write the group public key with the designated peer_id for the designated group number to public_key.
-
- This key will be permanently tied to a particular peer until they explicitly leave the group or
- get kicked/banned, and is the only way to reliably identify the same peer across client restarts.
-
- `public_key` should have room for at least TOX_GROUP_PEER_PUBLIC_KEY_SIZE bytes.
-
- :return public key
- """
-
- error = c_int()
- key = create_string_buffer(TOX_GROUP_PEER_PUBLIC_KEY_SIZE)
- result = Tox.libtoxcore.tox_group_peer_get_public_key(self._tox_pointer, group_number, peer_id,
- key, byref(error))
- return bin_to_string(key, TOX_GROUP_PEER_PUBLIC_KEY_SIZE)
-
- def callback_group_peer_name(self, callback, user_data):
- """
- Set the callback for the `group_peer_name` event. Pass NULL to unset.
- This event is triggered when a peer changes their nickname.
- """
-
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_char_p, c_size_t, c_void_p)
- self.group_peer_name_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_group_peer_name(self._tox_pointer, self.group_peer_name_cb, user_data)
-
- def callback_group_peer_status(self, callback, user_data):
- """
- Set the callback for the `group_peer_status` event. Pass NULL to unset.
- This event is triggered when a peer changes their status.
- """
-
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_int, c_void_p)
- self.group_peer_status_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_group_peer_status(self._tox_pointer, self.group_peer_status_cb, user_data)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Group chat state queries and events.
- # -----------------------------------------------------------------------------------------------------------------
-
- def group_set_topic(self, group_number, topic):
- """
- Set the group topic and broadcast it to the rest of the group.
-
- topic length cannot be longer than TOX_GROUP_MAX_TOPIC_LENGTH. If length is equal to zero or
- topic is set to NULL, the topic will be unset.
-
- :return True on success.
- """
-
- error = c_int()
- topic = bytes(topic, 'utf-8')
- result = Tox.libtoxcore.tox_group_set_topic(self._tox_pointer, group_number, topic, len(topic), byref(error))
- return result
-
- def group_get_topic_size(self, group_number):
- """
- Return the length of the group topic. If the group number is invalid, the
- return value is unspecified.
-
- The return value is equal to the `length` argument received by the last
- `group_topic` callback.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_get_topic_size(self._tox_pointer, group_number, byref(error))
- return result
-
- def group_get_topic(self, group_number):
- """
- Write the topic designated by the given group number to a byte array.
- Call tox_group_get_topic_size to determine the allocation size for the `topic` parameter.
- The data written to `topic` is equal to the data received by the last
- `group_topic` callback.
-
- :return topic
- """
-
- error = c_int()
- size = self.group_get_topic_size(group_number)
- topic = create_string_buffer(size)
- result = Tox.libtoxcore.tox_group_get_topic(self._tox_pointer, group_number, topic, byref(error))
- return str(topic[:size], 'utf-8')
-
- def group_get_name_size(self, group_number):
- """
- Return the length of the group name. If the group number is invalid, the
- return value is unspecified.
- """
- error = c_int()
- result = Tox.libtoxcore.tox_group_get_name_size(self._tox_pointer, group_number, byref(error))
- return int(result)
-
- def group_get_name(self, group_number):
- """
- Write the name of the group designated by the given group number to a byte array.
- Call tox_group_get_name_size to determine the allocation size for the `name` parameter.
- :return true on success.
- """
-
- error = c_int()
- size = self.group_get_name_size(group_number)
- name = create_string_buffer(size)
- result = Tox.libtoxcore.tox_group_get_name(self._tox_pointer, group_number,
- name, byref(error))
- return str(name[:size], 'utf-8')
-
- def group_get_chat_id(self, group_number):
- """
- Write the Chat ID designated by the given group number to a byte array.
- `chat_id` should have room for at least TOX_GROUP_CHAT_ID_SIZE bytes.
- :return chat id.
- """
-
- error = c_int()
- buff = create_string_buffer(TOX_GROUP_CHAT_ID_SIZE)
- result = Tox.libtoxcore.tox_group_get_chat_id(self._tox_pointer, group_number,
- buff, byref(error))
- return bin_to_string(buff, TOX_GROUP_CHAT_ID_SIZE)
-
- def group_get_number_groups(self):
- """
- Return the number of groups in the Tox chats array.
- """
-
- result = Tox.libtoxcore.tox_group_get_number_groups(self._tox_pointer)
- return result
-
- def groups_get_list(self):
- groups_list_size = self.group_get_number_groups()
- groups_list = create_string_buffer(sizeof(c_uint32) * groups_list_size)
- groups_list = POINTER(c_uint32)(groups_list)
- Tox.libtoxcore.tox_groups_get_list(self._tox_pointer, groups_list)
- return groups_list[0:groups_list_size]
-
- def group_get_privacy_state(self, group_number):
- """
- Return the privacy state of the group designated by the given group number. If group number
- is invalid, the return value is unspecified.
-
- The value returned is equal to the data received by the last
- `group_privacy_state` callback.
-
- see the `Group chat founder controls` section for the respective set function.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_get_privacy_state(self._tox_pointer, group_number, byref(error))
- return result
-
- def group_get_peer_limit(self, group_number):
- """
- Return the maximum number of peers allowed for the group designated by the given group number.
- If the group number is invalid, the return value is unspecified.
-
- The value returned is equal to the data received by the last
- `group_peer_limit` callback.
-
- see the `Group chat founder controls` section for the respective set function.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_get_peer_limit(self._tox_pointer, group_number, byref(error))
- return result
-
- def group_get_password_size(self, group_number):
- """
- Return the length of the group password. If the group number is invalid, the
- return value is unspecified.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_get_password_size(self._tox_pointer, group_number, byref(error))
- return result
-
- def group_get_password(self, group_number):
- """
- Write the password for the group designated by the given group number to a byte array.
-
- Call tox_group_get_password_size to determine the allocation size for the `password` parameter.
-
- The data received is equal to the data received by the last
- `group_password` callback.
-
- see the `Group chat founder controls` section for the respective set function.
-
- :return password
- """
-
- error = c_int()
- size = self.group_get_password_size(group_number)
- password = create_string_buffer(size)
- result = Tox.libtoxcore.tox_group_get_password(self._tox_pointer, group_number,
- password, byref(error))
- return str(password[:size], 'utf-8')
-
- def callback_group_topic(self, callback, user_data):
- """
- Set the callback for the `group_topic` event. Pass NULL to unset.
- This event is triggered when a peer changes the group topic.
- """
-
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_char_p, c_size_t, c_void_p)
- self.group_topic_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_group_topic(self._tox_pointer, self.group_topic_cb, user_data)
-
- def callback_group_privacy_state(self, callback, user_data):
- """
- Set the callback for the `group_privacy_state` event. Pass NULL to unset.
- This event is triggered when the group founder changes the privacy state.
- """
-
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_int, c_void_p)
- self.group_privacy_state_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_group_privacy_state(self._tox_pointer, self.group_privacy_state_cb, user_data)
-
- def callback_group_peer_limit(self, callback, user_data):
- """
- Set the callback for the `group_peer_limit` event. Pass NULL to unset.
- This event is triggered when the group founder changes the maximum peer limit.
- """
-
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_void_p)
- self.group_peer_limit_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_group_peer_limit(self._tox_pointer, self.group_peer_limit_cb, user_data)
-
- def callback_group_password(self, callback, user_data):
- """
- Set the callback for the `group_password` event. Pass NULL to unset.
- This event is triggered when the group founder changes the group password.
- """
-
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_char_p, c_size_t, c_void_p)
- self.group_password_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_group_password(self._tox_pointer, self.group_password_cb, user_data)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Group message sending
- # -----------------------------------------------------------------------------------------------------------------
-
- def group_send_custom_packet(self, group_number, lossless, data):
- """
- Send a custom packet to the group.
-
- If lossless is true the packet will be lossless. Lossless packet behaviour is comparable
- to TCP (reliability, arrive in order) but with packets instead of a stream.
-
- If lossless is false, the packet will be lossy. Lossy packets behave like UDP packets,
- meaning they might never reach the other side or might arrive more than once (if someone
- is messing with the connection) or might arrive in the wrong order.
-
- Unless latency is an issue or message reliability is not important, it is recommended that you use
- lossless custom packets.
-
- :param group_number: The group number of the group the message is intended for.
- :param lossless: True if the packet should be lossless.
- :param data A byte array containing the packet data.
- :return True on success.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_send_custom_packet(self._tox_pointer, group_number, lossless, data,
- len(data), byref(error))
- return result
-
- def group_send_private_message(self, group_number, peer_id, message_type, message):
- """
- Send a text chat message to the specified peer in the specified group.
-
- This function creates a group private message packet and pushes it into the send
- queue.
-
- The message length may not exceed TOX_MAX_MESSAGE_LENGTH. Larger messages
- must be split by the client and sent as separate messages. Other clients can
- then reassemble the fragments. Messages may not be empty.
-
- :param group_number: The group number of the group the message is intended for.
- :param peer_id: The ID of the peer the message is intended for.
- :param message: A non-NULL pointer to the first element of a byte array containing the message text.
-
- :return True on success.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_send_private_message(self._tox_pointer, group_number, peer_id,
- message_type, message,
- len(message), byref(error))
- return result
-
- def group_send_message(self, group_number, type, message):
- """
- Send a text chat message to the group.
-
- This function creates a group message packet and pushes it into the send
- queue.
-
- The message length may not exceed TOX_MAX_MESSAGE_LENGTH. Larger messages
- must be split by the client and sent as separate messages. Other clients can
- then reassemble the fragments. Messages may not be empty.
-
- :param group_number: The group number of the group the message is intended for.
- :param type: Message type (normal, action, ...).
- :param message: A non-NULL pointer to the first element of a byte array containing the message text.
-
- :return True on success.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_send_message(self._tox_pointer, group_number, type, message, len(message),
- byref(error))
- return result
-
- # -----------------------------------------------------------------------------------------------------------------
- # Group message receiving
- # -----------------------------------------------------------------------------------------------------------------
-
- def callback_group_message(self, callback, user_data):
- """
- Set the callback for the `group_message` event. Pass NULL to unset.
- This event is triggered when the client receives a group message.
-
- Callback: python function with params:
- tox Tox* instance
- group_number The group number of the group the message is intended for.
- peer_id The ID of the peer who sent the message.
- type The type of message (normal, action, ...).
- message The message data.
- length The length of the message.
- user_data - user data
- """
-
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_int, c_char_p, c_size_t, c_void_p)
- self.group_message_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_group_message(self._tox_pointer, self.group_message_cb, user_data)
-
- def callback_group_private_message(self, callback, user_data):
- """
- Set the callback for the `group_private_message` event. Pass NULL to unset.
- This event is triggered when the client receives a private message.
- """
-
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint8, c_char_p, c_size_t, c_void_p)
- self.group_private_message_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_group_private_message(self._tox_pointer, self.group_private_message_cb, user_data)
-
- def callback_group_custom_packet(self, callback, user_data):
- """
- Set the callback for the `group_custom_packet` event. Pass NULL to unset.
-
- This event is triggered when the client receives a custom packet.
- """
-
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, POINTER(c_uint8), c_void_p)
- self.group_custom_packet_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_group_custom_packet(self._tox_pointer, self.group_custom_packet_cb, user_data)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Group chat inviting and join/part events
- # -----------------------------------------------------------------------------------------------------------------
-
- def group_invite_friend(self, group_number, friend_number):
- """
- Invite a friend to a group.
-
- This function creates an invite request packet and pushes it to the send queue.
-
- :param group_number: The group number of the group the message is intended for.
- :param friend_number: The friend number of the friend the invite is intended for.
-
- :return True on success.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_invite_friend(self._tox_pointer, group_number, friend_number, byref(error))
- return result
-
- @staticmethod
- def group_self_peer_info_new():
- error = c_int()
- f = Tox.libtoxcore.tox_group_self_peer_info_new
- f.restype = POINTER(GroupChatSelfPeerInfo)
- result = f(byref(error))
-
- return result
-
- def group_invite_accept(self, invite_data, friend_number, nick, status, password=None):
- """
- Accept an invite to a group chat that the client previously received from a friend. The invite
- is only valid while the inviter is present in the group.
-
- :param invite_data: The invite data received from the `group_invite` event.
- :param password: The password required to join the group. Set to NULL if no password is required.
- :return the group_number on success, UINT32_MAX on failure.
- """
-
- error = c_int()
- f = Tox.libtoxcore.tox_group_invite_accept
- f.restype = c_uint32
- peer_info = self.group_self_peer_info_new()
- nick = bytes(nick, 'utf-8')
- peer_info.contents.nick = c_char_p(nick)
- peer_info.contents.nick_length = len(nick)
- peer_info.contents.user_status = status
- result = f(self._tox_pointer, friend_number, invite_data, len(invite_data), password,
- len(password) if password is not None else 0, peer_info, byref(error))
- print('Invite accept. Result:', result, 'Error:', error.value)
- return result
-
- def callback_group_invite(self, callback, user_data):
- """
- Set the callback for the `group_invite` event. Pass NULL to unset.
-
- This event is triggered when the client receives a group invite from a friend. The client must store
- invite_data which is used to join the group via tox_group_invite_accept.
-
- Callback: python function with params:
- tox - Tox*
- friend_number The friend number of the contact who sent the invite.
- invite_data The invite data.
- length The length of invite_data.
- user_data - user data
- """
-
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t,
- POINTER(c_uint8), c_size_t, c_void_p)
- self.group_invite_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_group_invite(self._tox_pointer, self.group_invite_cb, user_data)
-
- def callback_group_peer_join(self, callback, user_data):
- """
- Set the callback for the `group_peer_join` event. Pass NULL to unset.
-
- This event is triggered when a peer other than self joins the group.
- Callback: python function with params:
- tox - Tox*
- group_number - group number
- peer_id - peer id
- user_data - user data
- """
-
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_void_p)
- self.group_peer_join_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_group_peer_join(self._tox_pointer, self.group_peer_join_cb, user_data)
-
- def callback_group_peer_exit(self, callback, user_data):
- """
- Set the callback for the `group_peer_exit` event. Pass NULL to unset.
-
- This event is triggered when a peer other than self exits the group.
- """
-
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_char_p, c_size_t, c_void_p)
- self.group_peer_exit_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_group_peer_exit(self._tox_pointer, self.group_peer_exit_cb, user_data)
-
- def callback_group_self_join(self, callback, user_data):
- """
- Set the callback for the `group_self_join` event. Pass NULL to unset.
-
- This event is triggered when the client has successfully joined a group. Use this to initialize
- any group information the client may need.
- Callback: python fucntion with params:
- tox - *Tox
- group_number - group number
- user_data - user data
- """
-
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_void_p)
- self.group_self_join_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_group_self_join(self._tox_pointer, self.group_self_join_cb, user_data)
-
- def callback_group_join_fail(self, callback, user_data):
- """
- Set the callback for the `group_join_fail` event. Pass NULL to unset.
-
- This event is triggered when the client fails to join a group.
- """
-
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_int, c_void_p)
- self.group_join_fail_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_group_join_fail(self._tox_pointer, self.group_join_fail_cb, user_data)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Group chat founder controls (these only work for the group founder)
- # -----------------------------------------------------------------------------------------------------------------
-
- def group_founder_set_password(self, group_number, password):
- """
- Set or unset the group password.
-
- This function sets the groups password, creates a new group shared state including the change,
- and distributes it to the rest of the group.
-
- :param group_number: The group number of the group for which we wish to set the password.
- :param password: The password we want to set. Set password to NULL to unset the password.
-
- :return True on success.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_founder_set_password(self._tox_pointer, group_number, password,
- len(password), byref(error))
- return result
-
- def group_founder_set_privacy_state(self, group_number, privacy_state):
- """
- Set the group privacy state.
-
- This function sets the group's privacy state, creates a new group shared state
- including the change, and distributes it to the rest of the group.
-
- If an attempt is made to set the privacy state to the same state that the group is already
- in, the function call will be successful and no action will be taken.
-
- :param group_number: The group number of the group for which we wish to change the privacy state.
- :param privacy_state: The privacy state we wish to set the group to.
-
- :return true on success.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_founder_set_privacy_state(self._tox_pointer, group_number, privacy_state,
- byref(error))
- return result
-
- def group_founder_set_peer_limit(self, group_number, max_peers):
- """
- Set the group peer limit.
-
- This function sets a limit for the number of peers who may be in the group, creates a new
- group shared state including the change, and distributes it to the rest of the group.
-
- :param group_number: The group number of the group for which we wish to set the peer limit.
- :param max_peers: The maximum number of peers to allow in the group.
-
- :return True on success.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_founder_set_peer_limit(self._tox_pointer, group_number,
- max_peers, byref(error))
- return result
-
- # -----------------------------------------------------------------------------------------------------------------
- # Group chat moderation
- # -----------------------------------------------------------------------------------------------------------------
-
- def group_toggle_ignore(self, group_number, peer_id, ignore):
- """
- Ignore or unignore a peer.
-
- :param group_number: The group number of the group the in which you wish to ignore a peer.
- :param peer_id: The ID of the peer who shall be ignored or unignored.
- :param ignore: True to ignore the peer, false to unignore the peer.
-
- :return True on success.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_toggle_ignore(self._tox_pointer, group_number, peer_id, ignore, byref(error))
- return result
-
- def group_mod_set_role(self, group_number, peer_id, role):
- """
- Set a peer's role.
-
- This function will first remove the peer's previous role and then assign them a new role.
- It will also send a packet to the rest of the group, requesting that they perform
- the role reassignment. Note: peers cannot be set to the founder role.
-
- :param group_number: The group number of the group the in which you wish set the peer's role.
- :param peer_id: The ID of the peer whose role you wish to set.
- :param role: The role you wish to set the peer to.
-
- :return True on success.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_mod_set_role(self._tox_pointer, group_number, peer_id, role, byref(error))
- return result
-
- def group_mod_remove_peer(self, group_number, peer_id):
- """
- Kick/ban a peer.
-
- This function will remove a peer from the caller's peer list and optionally add their IP address
- to the ban list. It will also send a packet to all group members requesting them
- to do the same.
-
- :param group_number: The group number of the group the ban is intended for.
- :param peer_id: The ID of the peer who will be kicked and/or added to the ban list.
-
- :return True on success.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_mod_remove_peer(self._tox_pointer, group_number, peer_id,
- byref(error))
- return result
-
- def group_mod_ban_peer(self, group_number, peer_id, ban_type):
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_mod_ban_peer(self._tox_pointer, group_number, peer_id,
- ban_type, byref(error))
- return result
-
- def group_mod_remove_ban(self, group_number, ban_id):
- """
- Removes a ban.
-
- This function removes a ban entry from the ban list, and sends a packet to the rest of
- the group requesting that they do the same.
-
- :param group_number: The group number of the group in which the ban is to be removed.
- :param ban_id: The ID of the ban entry that shall be removed.
-
- :return True on success
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_mod_remove_ban(self._tox_pointer, group_number, ban_id, byref(error))
- return result
-
- def callback_group_moderation(self, callback, user_data):
- """
- Set the callback for the `group_moderation` event. Pass NULL to unset.
-
- This event is triggered when a moderator or founder executes a moderation event.
- """
-
- c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint32, c_int, c_void_p)
- self.group_moderation_cb = c_callback(callback)
- Tox.libtoxcore.tox_callback_group_moderation(self._tox_pointer, self.group_moderation_cb, user_data)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Group chat ban list queries
- # -----------------------------------------------------------------------------------------------------------------
-
- def group_ban_get_list_size(self, group_number):
- """
- Return the number of entries in the ban list for the group designated by
- the given group number. If the group number is invalid, the return value is unspecified.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_ban_get_list_size(self._tox_pointer, group_number, byref(error))
- return result
-
- def group_ban_get_list(self, group_number):
- """
- Copy a list of valid ban list ID's into an array.
-
- Call tox_group_ban_get_list_size to determine the number of elements to allocate.
- return true on success.
- """
-
- error = c_int()
- bans_list_size = self.group_ban_get_list_size(group_number)
- bans_list = create_string_buffer(sizeof(c_uint32) * bans_list_size)
- bans_list = POINTER(c_uint32)(bans_list)
- result = Tox.libtoxcore.tox_group_ban_get_list(self._tox_pointer, group_number, bans_list, byref(error))
- return bans_list[:bans_list_size]
-
- def group_ban_get_type(self, group_number, ban_id):
- """
- Return the type for the ban list entry designated by ban_id, in the
- group designated by the given group number. If either group_number or ban_id is invalid,
- the return value is unspecified.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_ban_get_type(self._tox_pointer, group_number, ban_id, byref(error))
- return result
-
- def group_ban_get_target_size(self, group_number, ban_id):
- """
- Return the length of the name for the ban list entry designated by ban_id, in the
- group designated by the given group number. If either group_number or ban_id is invalid,
- the return value is unspecified.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_ban_get_target_size(self._tox_pointer, group_number, ban_id, byref(error))
- return result
-
- def group_ban_get_target(self, group_number, ban_id):
- """
- Write the name of the ban entry designated by ban_id in the group designated by the
- given group number to a byte array.
-
- Call tox_group_ban_get_name_size to find out how much memory to allocate for the result.
-
- :return name
- """
-
- error = c_int()
- size = self.group_ban_get_target_size(group_number, ban_id)
- target = create_string_buffer(size)
- target_type = self.group_ban_get_type(group_number, ban_id)
-
- result = Tox.libtoxcore.tox_group_ban_get_target(self._tox_pointer, group_number, ban_id,
- target, byref(error))
- if target_type == TOX_GROUP_BAN_TYPE['PUBLIC_KEY']:
- return bin_to_string(target, size)
- return str(target[:size], 'utf-8')
-
- def group_ban_get_time_set(self, group_number, ban_id):
- """
- Return a time stamp indicating the time the ban was set, for the ban list entry
- designated by ban_id, in the group designated by the given group number.
- If either group_number or ban_id is invalid, the return value is unspecified.
- """
-
- error = c_int()
- result = Tox.libtoxcore.tox_group_ban_get_time_set(self._tox_pointer, group_number, ban_id, byref(error))
- return result
diff --git a/toxygen/tox_dns.py b/toxygen/tox_dns.py
new file mode 100644
index 0000000..ec8582f
--- /dev/null
+++ b/toxygen/tox_dns.py
@@ -0,0 +1,61 @@
+import json
+import urllib.request
+from util import log
+import settings
+try:
+ from PySide import QtNetwork, QtCore
+except:
+ from PyQt4 import QtNetwork, QtCore
+
+
+def tox_dns(email):
+ """
+ TOX DNS 4
+ :param email: data like 'groupbot@toxme.io'
+ :return: tox id on success else None
+ """
+ site = email.split('@')[1]
+ data = {"action": 3, "name": "{}".format(email)}
+ urls = ('https://{}/api'.format(site), 'http://{}/api'.format(site))
+ s = settings.Settings.get_instance()
+ if not s['proxy_type']: # no proxy
+ for url in urls:
+ try:
+ return send_request(url, data)
+ except Exception as ex:
+ log('TOX DNS ERROR: ' + str(ex))
+ else: # proxy
+ netman = QtNetwork.QNetworkAccessManager()
+ proxy = QtNetwork.QNetworkProxy()
+ proxy.setType(QtNetwork.QNetworkProxy.Socks5Proxy if s['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy)
+ proxy.setHostName(s['proxy_host'])
+ proxy.setPort(s['proxy_port'])
+ netman.setProxy(proxy)
+ for url in urls:
+ try:
+ request = QtNetwork.QNetworkRequest(url)
+ request.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, "application/json")
+ reply = netman.post(request, bytes(json.dumps(data), 'utf-8'))
+
+ while not reply.isFinished():
+ QtCore.QThread.msleep(1)
+ QtCore.QCoreApplication.processEvents()
+ data = bytes(reply.readAll().data())
+ result = json.loads(str(data, 'utf-8'))
+ if not result['c']:
+ return result['tox_id']
+ except Exception as ex:
+ log('TOX DNS ERROR: ' + str(ex))
+
+ return None # error
+
+
+def send_request(url, data):
+ req = urllib.request.Request(url)
+ req.add_header('Content-Type', 'application/json')
+ response = urllib.request.urlopen(req, bytes(json.dumps(data), 'utf-8'))
+ res = json.loads(str(response.read(), 'utf-8'))
+ if not res['c']:
+ return res['tox_id']
+ else:
+ raise LookupError()
diff --git a/toxygen/wrapper/toxav.py b/toxygen/toxav.py
similarity index 98%
rename from toxygen/wrapper/toxav.py
rename to toxygen/toxav.py
index 98e1c73..0ab891c 100644
--- a/toxygen/wrapper/toxav.py
+++ b/toxygen/toxav.py
@@ -1,7 +1,7 @@
from ctypes import c_int, POINTER, c_void_p, byref, ArgumentError, c_uint32, CFUNCTYPE, c_size_t, c_uint8, c_uint16
from ctypes import c_char_p, c_int32, c_bool, cast
-from wrapper.libtox import LibToxAV
-from wrapper.toxav_enums import *
+from libtox import LibToxAV
+from toxav_enums import *
class ToxAV:
@@ -24,9 +24,8 @@ class ToxAV:
"""
self.libtoxav = LibToxAV()
toxav_err_new = c_int()
- f = self.libtoxav.toxav_new
- f.restype = POINTER(c_void_p)
- self._toxav_pointer = f(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.')
@@ -41,7 +40,7 @@ class ToxAV:
self.video_receive_frame_cb = None
self.call_cb = None
- def kill(self):
+ def __del__(self):
"""
Releases all resources associated with the A/V session.
diff --git a/toxygen/wrapper/toxav_enums.py b/toxygen/toxav_enums.py
similarity index 100%
rename from toxygen/wrapper/toxav_enums.py
rename to toxygen/toxav_enums.py
diff --git a/toxygen/toxcore_enums_and_consts.py b/toxygen/toxcore_enums_and_consts.py
new file mode 100644
index 0000000..4d52837
--- /dev/null
+++ b/toxygen/toxcore_enums_and_consts.py
@@ -0,0 +1,209 @@
+TOX_USER_STATUS = {
+ 'NONE': 0,
+ 'AWAY': 1,
+ 'BUSY': 2,
+}
+
+TOX_MESSAGE_TYPE = {
+ 'NORMAL': 0,
+ 'ACTION': 1,
+}
+
+TOX_PROXY_TYPE = {
+ 'NONE': 0,
+ 'HTTP': 1,
+ 'SOCKS5': 2,
+}
+
+TOX_SAVEDATA_TYPE = {
+ 'NONE': 0,
+ 'TOX_SAVE': 1,
+ 'SECRET_KEY': 2,
+}
+
+TOX_ERR_OPTIONS_NEW = {
+ 'OK': 0,
+ 'MALLOC': 1,
+}
+
+TOX_ERR_NEW = {
+ 'OK': 0,
+ 'NULL': 1,
+ 'MALLOC': 2,
+ 'PORT_ALLOC': 3,
+ 'PROXY_BAD_TYPE': 4,
+ 'PROXY_BAD_HOST': 5,
+ 'PROXY_BAD_PORT': 6,
+ 'PROXY_NOT_FOUND': 7,
+ 'LOAD_ENCRYPTED': 8,
+ 'LOAD_BAD_FORMAT': 9,
+}
+
+TOX_ERR_BOOTSTRAP = {
+ 'OK': 0,
+ 'NULL': 1,
+ 'BAD_HOST': 2,
+ 'BAD_PORT': 3,
+}
+
+TOX_CONNECTION = {
+ 'NONE': 0,
+ 'TCP': 1,
+ 'UDP': 2,
+}
+
+TOX_ERR_SET_INFO = {
+ 'OK': 0,
+ 'NULL': 1,
+ 'TOO_LONG': 2,
+}
+
+TOX_ERR_FRIEND_ADD = {
+ 'OK': 0,
+ 'NULL': 1,
+ 'TOO_LONG': 2,
+ 'NO_MESSAGE': 3,
+ 'OWN_KEY': 4,
+ 'ALREADY_SENT': 5,
+ 'BAD_CHECKSUM': 6,
+ 'SET_NEW_NOSPAM': 7,
+ 'MALLOC': 8,
+}
+
+TOX_ERR_FRIEND_DELETE = {
+ 'OK': 0,
+ 'FRIEND_NOT_FOUND': 1,
+}
+
+TOX_ERR_FRIEND_BY_PUBLIC_KEY = {
+ 'OK': 0,
+ 'NULL': 1,
+ 'NOT_FOUND': 2,
+}
+
+TOX_ERR_FRIEND_GET_PUBLIC_KEY = {
+ 'OK': 0,
+ 'FRIEND_NOT_FOUND': 1,
+}
+
+TOX_ERR_FRIEND_GET_LAST_ONLINE = {
+ 'OK': 0,
+ 'FRIEND_NOT_FOUND': 1,
+}
+
+TOX_ERR_FRIEND_QUERY = {
+ 'OK': 0,
+ 'NULL': 1,
+ 'FRIEND_NOT_FOUND': 2,
+}
+
+TOX_ERR_SET_TYPING = {
+ 'OK': 0,
+ 'FRIEND_NOT_FOUND': 1,
+}
+
+TOX_ERR_FRIEND_SEND_MESSAGE = {
+ 'OK': 0,
+ 'NULL': 1,
+ 'FRIEND_NOT_FOUND': 2,
+ 'FRIEND_NOT_CONNECTED': 3,
+ 'SENDQ': 4,
+ 'TOO_LONG': 5,
+ 'EMPTY': 6,
+}
+
+TOX_FILE_KIND = {
+ 'DATA': 0,
+ 'AVATAR': 1,
+}
+
+TOX_FILE_CONTROL = {
+ 'RESUME': 0,
+ 'PAUSE': 1,
+ 'CANCEL': 2,
+}
+
+TOX_ERR_FILE_CONTROL = {
+ 'OK': 0,
+ 'FRIEND_NOT_FOUND': 1,
+ 'FRIEND_NOT_CONNECTED': 2,
+ 'NOT_FOUND': 3,
+ 'NOT_PAUSED': 4,
+ 'DENIED': 5,
+ 'ALREADY_PAUSED': 6,
+ 'SENDQ': 7,
+}
+
+TOX_ERR_FILE_SEEK = {
+ 'OK': 0,
+ 'FRIEND_NOT_FOUND': 1,
+ 'FRIEND_NOT_CONNECTED': 2,
+ 'NOT_FOUND': 3,
+ 'DENIED': 4,
+ 'INVALID_POSITION': 5,
+ 'SENDQ': 6,
+}
+
+TOX_ERR_FILE_GET = {
+ 'OK': 0,
+ 'NULL': 1,
+ 'FRIEND_NOT_FOUND': 2,
+ 'NOT_FOUND': 3,
+}
+
+TOX_ERR_FILE_SEND = {
+ 'OK': 0,
+ 'NULL': 1,
+ 'FRIEND_NOT_FOUND': 2,
+ 'FRIEND_NOT_CONNECTED': 3,
+ 'NAME_TOO_LONG': 4,
+ 'TOO_MANY': 5,
+}
+
+TOX_ERR_FILE_SEND_CHUNK = {
+ 'OK': 0,
+ 'NULL': 1,
+ 'FRIEND_NOT_FOUND': 2,
+ 'FRIEND_NOT_CONNECTED': 3,
+ 'NOT_FOUND': 4,
+ 'NOT_TRANSFERRING': 5,
+ 'INVALID_LENGTH': 6,
+ 'SENDQ': 7,
+ 'WRONG_POSITION': 8,
+}
+
+TOX_ERR_FRIEND_CUSTOM_PACKET = {
+ 'OK': 0,
+ 'NULL': 1,
+ 'FRIEND_NOT_FOUND': 2,
+ 'FRIEND_NOT_CONNECTED': 3,
+ 'INVALID': 4,
+ 'EMPTY': 5,
+ 'TOO_LONG': 6,
+ 'SENDQ': 7,
+}
+
+TOX_ERR_GET_PORT = {
+ 'OK': 0,
+ 'NOT_BOUND': 1,
+}
+
+TOX_PUBLIC_KEY_SIZE = 32
+
+TOX_ADDRESS_SIZE = TOX_PUBLIC_KEY_SIZE + 6
+
+TOX_MAX_FRIEND_REQUEST_LENGTH = 1016
+
+TOX_MAX_MESSAGE_LENGTH = 1372
+
+TOX_MAX_NAME_LENGTH = 128
+
+TOX_MAX_STATUS_MESSAGE_LENGTH = 1007
+
+TOX_SECRET_KEY_SIZE = 32
+
+TOX_FILE_ID_LENGTH = 32
+
+TOX_HASH_LENGTH = 32
+
+TOX_MAX_CUSTOM_PACKET_SIZE = 1373
diff --git a/toxygen/wrapper/toxencryptsave.py b/toxygen/toxencryptsave.py
similarity index 61%
rename from toxygen/wrapper/toxencryptsave.py
rename to toxygen/toxencryptsave.py
index 31de085..e420ecf 100644
--- a/toxygen/wrapper/toxencryptsave.py
+++ b/toxygen/toxencryptsave.py
@@ -1,25 +1,64 @@
-from wrapper import libtox
+import libtox
+import util
from ctypes import c_size_t, create_string_buffer, byref, c_int, ArgumentError, c_char_p, c_bool
-from wrapper.toxencryptsave_enums_and_consts import *
-class ToxEncryptSave:
+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):
def __init__(self):
+ super().__init__()
self.libtoxencryptsave = libtox.LibToxEncryptSave()
+ 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):
- """
- 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, password):
+ def pass_encrypt(self, data):
"""
- Encrypts the given data with the given password.
+ Encrypts the given data with the given passphrase.
:return: output array
"""
@@ -27,8 +66,8 @@ class ToxEncryptSave:
tox_err_encryption = c_int()
self.libtoxencryptsave.tox_pass_encrypt(c_char_p(data),
c_size_t(len(data)),
- c_char_p(bytes(password, 'utf-8')),
- c_size_t(len(password)),
+ c_char_p(bytes(self._passphrase, 'utf-8')),
+ c_size_t(len(self._passphrase)),
out,
byref(tox_err_encryption))
tox_err_encryption = tox_err_encryption.value
@@ -42,9 +81,9 @@ class ToxEncryptSave:
elif tox_err_encryption == TOX_ERR_ENCRYPTION['FAILED']:
raise RuntimeError('The encryption itself failed.')
- def pass_decrypt(self, data, password):
+ def pass_decrypt(self, data):
"""
- Decrypts the given data with the given password.
+ Decrypts the given data with the given passphrase.
:return: output array
"""
@@ -52,8 +91,8 @@ class ToxEncryptSave:
tox_err_decryption = c_int()
self.libtoxencryptsave.tox_pass_decrypt(c_char_p(bytes(data)),
c_size_t(len(data)),
- c_char_p(bytes(password, 'utf-8')),
- c_size_t(len(password)),
+ c_char_p(bytes(self._passphrase, 'utf-8')),
+ c_size_t(len(self._passphrase)),
out,
byref(tox_err_decryption))
tox_err_decryption = tox_err_decryption.value
diff --git a/toxygen/toxygen.pro b/toxygen/toxygen.pro
index 9643c8b..aeafe07 100644
--- a/toxygen/toxygen.pro
+++ b/toxygen/toxygen.pro
@@ -1,2 +1,2 @@
-SOURCES = main.py profile.py menu.py list_items.py loginscreen.py mainscreen.py plugins/plugin_super_class.py callbacks.py widgets.py avwidgets.py mainscreen_widgets.py passwordscreen.py
-TRANSLATIONS = translations/en_GB.ts translations/ru_RU.ts translations/fr_FR.ts translations/uk_UA.ts
+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
+TRANSLATIONS = translations/en_GB.ts translations/ru_RU.ts translations/fr_FR.ts
diff --git a/toxygen/translations/en_GB.ts b/toxygen/translations/en_GB.ts
index 7186005..a50fd4c 100644
--- a/toxygen/translations/en_GB.ts
+++ b/toxygen/translations/en_GB.ts
@@ -3,22 +3,22 @@
AddContact
-
+
Add contact
Add contact
-
+
TOX ID:
TOX ID:
-
+
Message:
Message:
-
+
TOX ID or public key of contact
@@ -26,7 +26,7 @@
Callback
-
+
File from
@@ -34,32 +34,32 @@
Form
-
+
Send request
Send request
-
+
IPv6
IPv6
-
+
UDP
UDP
-
+
Proxy
Proxy
-
+
IP:
IP:
-
+
Port:
Port:
@@ -69,118 +69,113 @@
Online contacts
-
+
HTTP
HTTP
-
+
WARNING:
using proxy with enabled UDP
can produce IP leak
-
-
- Download nodes list from tox.chat
-
-
MainWindow
-
+
Profile
-
+
Settings
-
+
About
-
+
Add contact
-
+
Privacy
-
+
Interface
-
+
Notifications
-
+
Network
-
+
About program
-
+
User {} wants to add you to contact list. Message:
{}
-
+
Friend request
-
+
Choose file
Choose file
-
+
Disallow auto accept
-
+
Allow auto accept
-
+
Set alias
-
+
Clear history
-
+
Remove friend
-
+
Enter new alias for friend {} or leave empty to use friend's name:
Enter new alias for friend {} or leave empty to use friend's name:
-
+
Audio
Audio
@@ -190,24 +185,24 @@ can produce IP leak
Find contact
-
+
Friend added
Friend added
-
+
Toxygen is Tox client written on Python.
Version:
Toxygen is Tox client written on Python.
Version:
-
+
Friend added without sending friend request
Friend added without sending friend request
-
+
Choose folder
Choose folder
@@ -222,300 +217,220 @@ Version:
Send file
-
+
Send message
Send message
-
+
Start audio call with friend
Start audio call with friend
-
+
Plugins
-
+
List of plugins
-
+
Search
-
+
All
-
+
Online
-
+
Notes
-
+
Notes about user
-
+
Copy link location
-
+
Copy
-
+
Select all
-
+
Delete
-
+
Paste
-
+
Cut
-
+
Undo
-
+
Redo
-
+
Save
-
+
User {} is now known as {}
-
+
Delete message
-
+
Lock
-
+
Cannot lock app
-
+
Error. Profile password is not set.
-
+
Name
-
+
Status message
-
+
Public key
-
+
Error
-
+
Profile with this name already exists
-
+
Choose folder with sticker pack
-
+
Choose folder with smiley pack
-
+
Import plugin
-
+
Choose folder with plugin
-
+
Restart Toxygen
-
+
Plugin will be loaded after restart
-
-
- Quote selected text
-
-
-
-
- Chat history
-
-
-
-
- Export as text
-
-
-
-
- Export as HTML
-
-
-
-
- Updates
-
-
-
-
- Online first
-
-
-
-
- Online and by name
-
-
-
-
- Online first and by name
-
-
-
-
- Block friend
-
-
-
-
- Not found
-
-
-
-
- Text "{}" was not found
-
-
-
-
- Reload plugins
-
-
-
-
- Video
-
-
-
-
- User {} invites you to group chat. Accept?
-
-
-
-
- Group chat invite
-
-
-
-
- {} users in chat
-
-
-
-
- Enter new title for group {}:
-
-
-
-
- Set title
-
-
-
-
- Create group chat
-
-
-
-
- Invite to group chat
-
-
-
-
- Leave chat
-
-
MenuWindow
-
+
+ Send audio message to friend {}
+
+
+
+
+ Start recording
+
+
+
+
+ Stop recording
+
+
+
+
Send screenshot
Send screenshot
-
+
Send file
Send file
-
+
+ Send audio message
+
+
+
+
+ Send video message
+
+
+
+
Add smiley
-
+
Send sticker
@@ -523,63 +438,25 @@ Version:
NetworkSettings
-
+
Network settings
Network settings
-
+
Restart TOX core
Restart Tox core
-
- PasswordScreen
-
-
- Profile password
-
-
-
-
- Password (at least 8 symbols)
-
-
-
-
- Confirm password
-
-
-
-
- Set password
-
-
-
-
- Passwords do not match
-
-
-
-
- There is no way to recover lost passwords
-
-
-
-
- Password must be at least 8 symbols
-
-
-
PluginWindow
-
+
List of commands for plugin {}
-
+
No commands available
@@ -587,42 +464,42 @@ Version:
PluginsForm
-
+
Plugins
-
+
Open selected plugin
-
+
No GUI found for this plugin
-
+
No description available
-
+
Disable plugin
-
+
Enable plugin
-
+
No plugins found
-
+
Error
@@ -630,218 +507,208 @@ Version:
ProfileSettingsForm
-
+
Export profile
-
+
Profile settings
-
+
Name:
-
+
Status:
-
+
TOX ID:
-
+
Copy TOX ID
-
+
New avatar
-
+
Reset avatar
-
+
New NoSpam
New NoSpam
-
+
Profile password
-
+
Password (at least 8 symbols)
-
+
Confirm password
-
+
Set password
-
+
Passwords do not match
-
+
Leaving blank will reset current password
-
+
There is no way to recover lost passwords
-
+
Password must be at least 8 symbols
-
+
Choose avatar
-
+
Online
-
+
Away
-
+
Busy
-
+
Mark as not default profile
-
+
Mark as default profile
-
+
Copy public key
-
-
- Use new path
-
-
-
-
- Do you want to move your profile to this location?
-
-
WelcomeScreen
-
+
Don't show again
-
+
Tip of the day
-
+
Press Esc if you want hide app to tray.
-
+
You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a>
-
+
Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.
-
+
+ Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/xveduk/toxygen/blob/master/docs/plugins.md">Read more</a>
+
+
+
+
Right click on screenshot button hides app to tray during screenshot.
-
+
Use Settings -> Interface to customize interface.
-
+
Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.
-
+
Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.
-
+
+ New in Toxygen v0.2.3:<br>TCS compliance<br>Plugins, smileys and stickers import<br>Bug fixes
+
+
+
+
Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu
-
+
Use right click on inline image to save it
-
-
- Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a>
-
-
-
-
- New in Toxygen 0.4.1:<br>Downloading nodes from tox.chat<br>Bug fixes
-
-
audioSettingsForm
-
+
Audio settings
Audio settings
-
+
Input device:
Input device:
-
+
Output device:
Output device:
@@ -849,32 +716,32 @@ Version:
incoming_call
-
+
Incoming video call
Incoming video call
-
+
Incoming audio call
Incoming audio call
-
+
Outgoing video call
-
+
Outgoing audio call
-
+
Call declined
-
+
Call finished
@@ -882,274 +749,226 @@ Version:
interfaceForm
-
+
Interface settings
-
+
Theme:
-
+
Language:
-
+
Smileys
-
+
Smiley pack:
-
+
Mirror mode
-
+
Messages font size:
-
+
Restart app to apply settings
-
+
Restart required
-
+
Select unread messages notification color
-
+
Compact contact list
-
+
Import smiley pack
-
+
Import sticker pack
-
+
Show avatars in chat
-
-
- Close to tray
-
-
-
-
- Select font
-
-
login
-
+
Log in
-
+
Create
-
+
Profile name:
-
+
Load profile
-
+
Use as default
-
+
Load existing profile
-
+
Create new profile
-
+
toxygen
-
+
Profile name
-
+
Other instance of Toxygen uses this profile or profile was not properly closed. Continue?
-
+
Do you want to set profile password?
-
-
- Do you want to save profile in default folder? If no, profile will be saved in program folder
-
-
-
-
- Profile saving error! Does Toxygen have permission to write to this directory?
-
-
-
-
- Update for Toxygen was found. Download and install it?
-
-
notificationsForm
-
+
Notification settings
-
+
Enable notifications
-
+
Enable call's sound
-
+
Enable sound notifications
-
-
- Notify about all messages in groups
-
-
-
-
- pass
-
-
- Enter password
-
-
-
-
- Password:
-
-
-
-
- Incorrect password
-
-
privacySettings
-
+
Privacy settings
-
+
Save chat history
-
+
Allow file auto accept
-
+
Send typing notifications
-
+
Auto accept default path:
-
+
Change
-
+
Allow inlines
-
+
Chat history
-
+
History will be cleaned! Continue?
-
+
Blocked users:
Blocked users:
-
+
Unblock
Unblock
-
+
Block user
Block user
-
+
Add to friend list
Add to friend list
-
+
Do you want to add this user to friend list?
Do you want to add this user to friend list?
@@ -1159,12 +978,12 @@ Version:
Block by TOX ID:
-
+
Block by public key:
-
+
Save unsent messages only
@@ -1172,115 +991,34 @@ Version:
tray
-
+
Open Toxygen
-
+
Exit
-
+
Set status
-
+
Online
-
+
Away
-
+
Busy
-
- updateSettingsForm
-
-
- Update settings
-
-
-
-
- Select update mode:
-
-
-
-
- Update Toxygen
-
-
-
-
- Disabled
-
-
-
-
- Manual
-
-
-
-
- Auto
-
-
-
-
- Error
-
-
-
-
- Problems with internet connection
-
-
-
-
- Updater not found
-
-
-
-
- No updates found
-
-
-
-
- Toxygen is up to date
-
-
-
-
- videoSettingsForm
-
-
- Video settings
-
-
-
-
- Device:
-
-
-
-
- Desktop
-
-
-
-
- Select region
-
-
-
diff --git a/toxygen/translations/fr_FR.qm b/toxygen/translations/fr_FR.qm
index 33b0cbc..e7ab531 100644
Binary files a/toxygen/translations/fr_FR.qm and b/toxygen/translations/fr_FR.qm differ
diff --git a/toxygen/translations/fr_FR.ts b/toxygen/translations/fr_FR.ts
index 1931a26..a547576 100644
--- a/toxygen/translations/fr_FR.ts
+++ b/toxygen/translations/fr_FR.ts
@@ -2,64 +2,64 @@
AddContact
-
-
- Add contact
- Ajouter un contact
-
- TOX ID:
- ID Tox :
+ Add contact
+ Rajouter un contact
-
+
+ TOX ID:
+ ID TOX :
+
+
+
Message:
Message :
-
+
TOX ID or public key of contact
- ID Tox ou clé publique de contact
+
Callback
-
+
File from
- Fichier de
+
Form
-
+
Send request
Envoyer une demande
-
+
IPv6
IPv6
-
+
UDP
UDP
-
+
Proxy
Proxy
-
+
IP:
IP :
-
+
Port:
Port :
@@ -69,82 +69,75 @@
Contacts connectés
-
+
HTTP
HTTP
-
+
WARNING:
using proxy with enabled UDP
can produce IP leak
- ATTENTION :
-Utiliser un proxy avec UDP
-peut entrainer une fuite d'IP
-
-
-
- Download nodes list from tox.chat
MainWindow
-
+
Profile
- Profil
+ Profile
-
+
Settings
- Paramètres
+ Paramêtres
-
+
About
À Propos
-
+
Add contact
- Ajouter un contact
+ Rajouter un contact
-
+
Privacy
Confidentialité
-
+
Interface
Interface
-
+
Notifications
Notifications
-
+
Network
Réseau
-
+
About program
- À propos de toxygen
+ À propos du programme
-
+
User {} wants to add you to contact list. Message:
{}
- L'Utilisateur {} veut vous ajouter à sa liste de contacts. Message : {}
+ L'Utilisateur {} veut vout rajouter à sa liste de contacts. Message : {}
-
+
Friend request
- Demande de contact
+ Demande d'amis
@@ -152,27 +145,27 @@ peut entrainer une fuite d'IP
Toxygen est un client Tox écris en Python 2.7. Version :
-
+
Choose file
- Sélectionner un fichier
+ Choisir un fichier
-
+
Disallow auto accept
Désactiver l'auto-réception
-
+
Allow auto accept
Activer l'auto-réception
-
+
Set alias
Définir un alias
-
+
Clear history
Vider l'historique
@@ -182,17 +175,17 @@ peut entrainer une fuite d'IP
Copier la clé publique
-
+
Remove friend
- Retirer ce contact
+ Retirer un ami
-
+
Enter new alias for friend {} or leave empty to use friend's name:
- Entrez un nouvel alias pour le contact {} ou laissez vide pour garder son nom de base :
+ Entrez un nouvel alias pour l'ami {} ou laissez vide pour garder son nom de base :
-
+
Audio
Audio
@@ -202,26 +195,26 @@ peut entrainer une fuite d'IP
Trouver le contact
-
+
Friend added
- Contact ajouté
+ Ami rajouté
-
+
Toxygen is Tox client written on Python.
Version:
Toxygen est un client Tox écrit en Python.
Version :
-
+
Friend added without sending friend request
- Contact ajouté sans envoi de demande
+ Ami rajouté sans avoir envoyé de demande
-
+
Choose folder
- Sélectionner un dossier
+ Choisir le dossier
@@ -234,631 +227,498 @@ Version :
Envoyer le fichier
-
+
Send message
Envoyer le message
-
+
Start audio call with friend
- Démarrer un appel audio avec un ami
+ Lancer un appel audio avec un ami
-
+
Plugins
- Plugins
+
-
+
List of plugins
- Liste de plugins
+
-
+
Search
- Chercher
+
-
+
All
- Tous
+
-
+
Online
- En ligne
+
-
+
Notes
- Notes
+
-
+
Notes about user
- Notes sur l'utilisateur
+
-
+
Copy link location
- Copier l'emplacement du lien
+
-
+
Copy
- Copier
+
-
+
Select all
- Tout sélectionner
+
-
+
Delete
- Supprimer
+
-
+
Paste
- Coller
+
-
+
Cut
- Couper
+
-
+
Undo
- Annuler
+
-
+
Redo
- Refaire
+
-
+
Save
- Sauvegarder
+
-
+
User {} is now known as {}
- L'utilisateur {} s'appelle désormais {}
+
-
+
Delete message
- Supprimer ce message
+
-
+
Lock
- Verrouiller
+
-
+
Cannot lock app
- Impossible de verrouiller l'application
+
-
+
Error. Profile password is not set.
- Erreur. Le profil n'a pas de mot de passe.
+
-
+
Name
- Nom
+
-
+
Status message
- Status
+
-
+
Public key
- Clé publique
+
-
+
Error
- Erreur
+
-
+
Profile with this name already exists
- Un profil ayant ce nom existe déjà
+
-
+
Choose folder with sticker pack
- Sélectionner le dossier contenant le pack de stickers
+
-
+
Choose folder with smiley pack
- Sélectionner le dossier contenant le pack de smileys
+
-
+
Import plugin
- Importer un plugin
+
-
+
Choose folder with plugin
- Sélectionner un dossier avec des plugins
+
-
+
Restart Toxygen
- Redémarrer Toxyger
+
-
+
Plugin will be loaded after restart
- Le plugin sera chargé après le redémarrage
-
-
-
- Quote selected text
- Citer le texte sélectionné
-
-
-
- Chat history
- Historique de la conversation
-
-
-
- Export as text
- Exporter comme texte
-
-
-
- Export as HTML
- Exporter comme HTML
-
-
-
- Updates
- Mises à jour
-
-
-
- Online first
- En ligne d'abord
-
-
-
- Online and by name
- En ligne et par nom
-
-
-
- Online first and by name
- En ligne d'abord puis par nom
-
-
-
- Block friend
- Bloquer le contact
-
-
-
- Not found
- Non trouvé
-
-
-
- Text "{}" was not found
- Le texte "{}" n'a pas été trouvé
-
-
-
- Reload plugins
- Recharger les plugins
-
-
-
- Video
- Vidéo
-
-
-
- User {} invites you to group chat. Accept?
-
-
-
-
- Group chat invite
-
-
-
-
- {} users in chat
-
-
-
-
- Enter new title for group {}:
-
-
-
-
- Set title
-
-
-
-
- Create group chat
-
-
-
-
- Invite to group chat
-
-
-
-
- Leave chat
MenuWindow
-
+
+ Send audio message to friend {}
+
+
+
+
+ Start recording
+
+
+
+
+ Stop recording
+
+
+
+
Send screenshot
- Envoyer une capture d'écran
+ Envoyer une capture d'écran
-
+
Send file
- Envoyer un fichier
+ Envoyer le fichier
-
+
+ Send audio message
+
+
+
+
+ Send video message
+
+
+
+
Add smiley
- Ajouter un smiley
+
-
+
Send sticker
- Ajouter un sticker
+
NetworkSettings
-
+
Network settings
Paramètres réseaux
-
+
Restart TOX core
- Relancer le noyau Tox
-
-
-
- PasswordScreen
-
-
- Profile password
- Mot de passe du profil
-
-
-
- Password (at least 8 symbols)
- Mot de passe (8 symboles minimum)
-
-
-
- Confirm password
- Confirmation
-
-
-
- Set password
- Enregistrer le mot de passe
-
-
-
- Passwords do not match
- Les mots de passes sont différents
-
-
-
- There is no way to recover lost passwords
- Il est impossible de récuperer un mot de passe perdu
-
-
-
- Password must be at least 8 symbols
- Un mot de passe doit faire 8 symboles minimum
+ Relancer le noyau TOX
PluginWindow
-
+
List of commands for plugin {}
- Liste de commandes du plugin {}
+
-
+
No commands available
- Pas de commandes disponibles
+
PluginsForm
-
+
Plugins
- Plugins
+
-
+
Open selected plugin
- Ouvrir le plugin sélectionné
+
-
+
No GUI found for this plugin
- Pas d'interface pour ce plugin
+
-
+
No description available
- Pas de description
+
-
+
Disable plugin
- Désactiver le plugin
+
-
+
Enable plugin
- Activer le plugin
+
-
+
No plugins found
- Pas de plugin trouvé
+
-
+
Error
- Erreur
+
ProfileSettingsForm
-
-
- Export profile
- Exporter le profil
-
- Profile settings
- Paramètres du profil
+ Export profile
+ Exporter le profile
+ Profile settings
+ Paramêtres du profil
+
+
+
Name:
Nom :
-
+
Status:
Status :
-
+
TOX ID:
ID TOX :
-
+
Copy TOX ID
- Copier l'ID Tox
+ Copier l'ID TOX
-
+
New avatar
Nouvel avatar
-
+
Reset avatar
Réinitialiser l'avatar
-
+
New NoSpam
Nouveau NoSpam
-
-
- Profile password
- Mot de passe du profil
-
- Password (at least 8 symbols)
- Mot de passe (8 symboles minimum)
+ Profile password
+
- Confirm password
- Confirmation
+ Password (at least 8 symbols)
+
+ Confirm password
+
+
+
+
Set password
- Sauvegarder le mot de passe
+
-
+
Passwords do not match
- Les mots de passe sont différents
-
-
-
- Leaving blank will reset current password
- Laisser vide réinitialisera le mot de passe actuel
+
- There is no way to recover lost passwords
- Il est impossible de récupérer un mot de passe perdu
-
-
-
- Password must be at least 8 symbols
- Le mot de passe doit faire 8 symboles minimum
-
-
-
- Choose avatar
- Choisir l'avatar
+ Leaving blank will reset current password
+
- Online
- En ligne
+ There is no way to recover lost passwords
+
+
+
+
+ Password must be at least 8 symbols
+
+
+
+
+ Choose avatar
+
- Away
- Absent
+ Online
+
- Busy
- Occupé
-
-
-
- Mark as not default profile
- Ne plus en faire le profil par défaut
-
-
-
- Mark as default profile
- En faire le profil par défaut
+ Away
+
+ Busy
+
+
+
+
+ Mark as not default profile
+
+
+
+
+ Mark as default profile
+
+
+
+
Copy public key
- Copier la clé publique
-
-
-
- Use new path
- Utiliser un nouveau chemin
-
-
-
- Do you want to move your profile to this location?
- Déplacer le profil dans ce dossier ?
+ Copier la clé publique
WelcomeScreen
-
+
Don't show again
- Ne plus montrer
-
-
-
- Tip of the day
- Astuce du jou
-
-
-
- Press Esc if you want hide app to tray.
- Appuyez sur échap pour réduire l'application.
-
-
-
- You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a>
- Vous pouvez utiliser Tox avec Tor. Pour plus d'informations, voir <a href="https://wiki.tox.chat/users/tox_over_tor_tot">cet article</a>
-
-
-
- Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.
- Vous pouvez mettre un mot de passe dans Profil -> Paramètres -> Mot de passe pour que toxygen encrypte votre historique et vos paramètres.
-
-
-
- Right click on screenshot button hides app to tray during screenshot.
- Faire un clic droit sur le bouton de capture d'écran réduit l'application avant de capturer l'écran.
-
-
-
- Use Settings -> Interface to customize interface.
- Vous pouvez customizer votre interface dans Paramètres -> Interface.
-
-
-
- Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.
- Toxygen permet d'envoyer des messages et fichiers en différé. Envoyez des messages ou fichiers à un contact hors ligne et il le recevra plus tard.
+
+ Tip of the day
+
+
+
+
+ Press Esc if you want hide app to tray.
+
+
+
+
+ You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a>
+
+
+
+
+ Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.
+
+
+
+
+ Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/xveduk/toxygen/blob/master/docs/plugins.md">Read more</a>
+
+
+
+
+ Right click on screenshot button hides app to tray during screenshot.
+
+
+
+
+ Use Settings -> Interface to customize interface.
+
+
+
+
+ Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.
+
+
+
+
Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.
- Vous pouvez empecher le spam dans les demandes de contact avec Profil -> Paramètres -> Nouveau NoSpam.
+
-
+
+ New in Toxygen v0.2.3:<br>TCS compliance<br>Plugins, smileys and stickers import<br>Bug fixes
+
+
+
+
Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu
- Pour supprimer un seul message dans une conversation, faites un clic droit sur l'heure du message et sélectionnez "Supprimer ce message" dans le menu
+
-
+
Use right click on inline image to save it
- Pour sauvegarder une image intégrée, faites un clic droit dessus
-
-
-
- Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a>
- Depuis la version 0.1.3 Toxygen supporte les plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">En savoir plus</a>
-
-
-
- New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5
- Nouveau dans Toxygen 0.3.0 : <br>Appels vidéo<br>Support de Python3.6<br>Migration vers PyQt5
-
-
-
- New in Toxygen 0.4.1:<br>Downloading nodes from tox.chat<br>Bug fixes
audioSettingsForm
-
+
Audio settings
Paramètres audio
-
+
Input device:
Péripherique d'entrée :
-
+
Output device:
Péripherique de sortie :
@@ -866,158 +726,148 @@ Version :
incoming_call
-
+
Incoming video call
Appel vidéo entrant
-
+
Incoming audio call
Appel audio entrant
-
+
Outgoing video call
- Appel vidéo sortant
+
-
+
Outgoing audio call
- Appel audio sortant
+
-
+
Call declined
- Appel refusé
+
-
+
Call finished
- Appel terminé
+
interfaceForm
-
+
Interface settings
- Paramètres de l'interface
+ Paramêtres de l'interface
-
+
Theme:
Thème :
-
+
Language:
Langue :
-
+
Smileys
- Smileys
+
-
+
Smiley pack:
- Pack de smileys :
+
-
+
Mirror mode
- Mode miroir
+
-
+
Messages font size:
- Taille des messages :
+
-
+
Restart app to apply settings
- Redémarrer toxygen pour appliquer les paramètres
+
-
+
Restart required
- Redémarrage nécessaire
+
-
+
Select unread messages notification color
- Sélectionner la couleur des messages non-lus
+
-
+
Compact contact list
- Liste de contacts compacte
+
-
+
Import smiley pack
- Importer un pack de smileys
+
-
+
Import sticker pack
- Importer un pack de stickers
+
-
+
Show avatars in chat
- Montrer les avatars dans la conversation
-
-
-
- Close to tray
- Réduire
-
-
-
- Select font
- Sélectionner la police
+
login
-
+
Log in
Se connecter
-
+
Create
Créer
-
+
Profile name:
Nom du profil :
-
+
Load profile
Charger le profil
-
+
Use as default
Utiliser par défaut
-
+
Load existing profile
Charger un profil existant
-
+
Create new profile
Créer un nouveau profil
-
+
toxygen
toxygen
@@ -1027,153 +877,115 @@ Version :
Il semble qu'une autre instance de Toxygen utilise ce profil ! Continuer ?
-
+
Profile name
- Nom de profil
+
-
+
Other instance of Toxygen uses this profile or profile was not properly closed. Continue?
- Ce profil semble être utilisé par une autre instance de toxygen ou avoir été incorrectement fermé . Continuer ?
+
-
+
Do you want to set profile password?
- Souhaitez vous protéger le profil par un mot de passe ?
-
-
-
- Do you want to save profile in default folder? If no, profile will be saved in program folder
- Souhaitez vous conserver le profil dans le dossier par défaut ? Si non, il sera conservé dans le dossier du programme
-
-
-
- Profile saving error! Does Toxygen have permission to write to this directory?
- Un problème est survenu lors de la sauvegarde du profil ! Toxygen as t'il le droit d'écrire dans ce dossier ?
-
-
-
- Update for Toxygen was found. Download and install it?
- Une mise à jour est disponible. La télécharger et l'installer ?
+
notificationsForm
-
+
Notification settings
- Paramètres de notification
+ Paramêtres de notification
-
+
Enable notifications
Activer les notifications
-
+
Enable call's sound
Activer les sons d'appel
-
+
Enable sound notifications
Activer les sons de notifications
-
-
- Notify about all messages in groups
-
-
-
-
- pass
-
-
- Enter password
- Entrer le mot de passe
-
-
-
- Password:
- Mot de passe :
-
-
-
- Incorrect password
- Mot de passe incorrect
-
privacySettings
-
+
Privacy settings
- Paramètres de confidentialité
+ Paramêtres de confidentialité
-
+
Save chat history
- Sauvegarder l'historique de conversation
+ Sauvegarder l'historique de chat
-
+
Allow file auto accept
Autoriser les fichier automatiquement
-
+
Send typing notifications
- Informer de la frappe
+ Notifier la frappe
-
+
Auto accept default path:
- Chemin par défaut des fichiers acceptés automatiquement :
+ Chemin d'accès des fichiers acceptés automatiquement :
-
+
Change
Modifier
-
+
Allow inlines
- Activer l'affichage integré
+ Activer l'auto-réception
-
+
Chat history
- Historique de conversation
+ Historique de chat
-
+
History will be cleaned! Continue?
- L'Historique va être vidé ! Confirmer ?
+ L'Historique va être nettoyé ! Confirmer ?
-
+
Blocked users:
Utilisateurs bloqués :
-
+
Unblock
Débloquer
-
+
Block user
Bloquer l'utilisateur
-
+
Add to friend list
- Ajouter à la liste de contacts
+ Ajouter à la liste des amis
-
+
Do you want to add this user to friend list?
- Voulez vous aajouter cet utilisateur à votre liste de contacts ?
+ Voulez vous rajouter cet utilisateur à votre liste d'amis ?
@@ -1181,127 +993,46 @@ Version :
Bloquer l'ID TOX :
-
+
Block by public key:
- Bloquer par clé publique :
+
-
+
Save unsent messages only
- Sauvegarder les messages non envoyés uniquement
+
tray
-
+
Open Toxygen
Ouvrir Toxygen
-
+
Exit
Quitter
-
+
Set status
- Changer le status
-
-
-
- Online
- En ligne
-
-
-
- Away
- Absent
-
-
-
- Busy
- Occupé
-
-
-
- updateSettingsForm
-
-
- Update settings
- Paramètres de mise à jour
-
-
-
- Select update mode:
- Sélectionner le mode de mise à jour :
-
-
-
- Update Toxygen
- Mettre à jour toxygen
-
-
-
- Disabled
- Désactivé
-
-
-
- Manual
- Manuel
-
-
-
- Auto
- Automatique
-
-
-
- Error
- Erreur
-
-
-
- Problems with internet connection
- Il y à des problèmes avec votre connexion internet
-
-
-
- Updater not found
- Updater non trouvé
-
-
-
- No updates found
- Pas de mises à jour trouvés
-
-
-
- Toxygen is up to date
- Toxygen est à jour
-
-
-
- videoSettingsForm
-
-
- Video settings
- Paramètres vidéo
-
-
-
- Device:
- Périphérique :
-
-
-
- Desktop
-
- Select region
+
+ Online
+
+
+
+
+ Away
+
+
+
+
+ Busy
diff --git a/toxygen/translations/ru_RU.qm b/toxygen/translations/ru_RU.qm
index 1231884..f8a1019 100644
Binary files a/toxygen/translations/ru_RU.qm and b/toxygen/translations/ru_RU.qm differ
diff --git a/toxygen/translations/ru_RU.ts b/toxygen/translations/ru_RU.ts
index 8d6c63c..5664f77 100644
--- a/toxygen/translations/ru_RU.ts
+++ b/toxygen/translations/ru_RU.ts
@@ -3,22 +3,22 @@
AddContact
-
+
Add contact
Добавить контакт
-
+
TOX ID:
TOX ID:
-
+
Message:
Сообщение:
-
+
TOX ID or public key of contact
TOX ID или публичный ключ контакта
@@ -26,7 +26,7 @@
Callback
-
+
File from
Файл от
@@ -34,32 +34,32 @@
Form
-
+
Send request
Отправить запрос
-
+
IPv6
IPv6
-
+
UDP
UDP
-
+
Proxy
Прокси
-
+
IP:
IP:
-
+
Port:
Порт:
@@ -69,12 +69,12 @@
Контакты в сети
-
+
HTTP
HTTP
-
+
WARNING:
using proxy with enabled UDP
can produce IP leak
@@ -82,93 +82,88 @@ can produce IP leak
использование прокси с UDP
может привести к утечке IP
-
-
- Download nodes list from tox.chat
-
-
MainWindow
-
+
Profile
Профиль
-
+
Settings
Настройки
-
+
About
О программе
-
+
Add contact
Добавить контакт
-
+
Privacy
Приватность
-
+
Interface
Интерфейс
-
+
Notifications
Уведомления
-
+
Network
Сеть
-
+
About program
О программе
-
+
User {} wants to add you to contact list. Message:
{}
Пользователь {} хочет добавить Вас в список контактов. Сообщение:
{}
-
+
Friend request
Запрос на добавление в друзья
-
+
Choose file
Выберите файл
-
+
Disallow auto accept
Запретить автоматическое получение файлов
-
+
Allow auto accept
Разрешить автоматическое получение файлов
-
+
Set alias
Изменить псевдоним
-
+
Clear history
Очистить историю
@@ -178,17 +173,17 @@ can produce IP leak
Копировать публичный ключ
-
+
Remove friend
Удалить друга
-
+
Enter new alias for friend {} or leave empty to use friend's name:
Введите новый псевдоним для друга {} или оставьте пустым для использования его имени:
-
+
Audio
Аудио
@@ -198,23 +193,23 @@ can produce IP leak
Найти контакт
-
+
Friend added
Друг добавлен
-
+
Toxygen is Tox client written on Python.
Version:
Toxygen - клиент для мессенджера Tox, написанный на Python. Версия:
-
+
Friend added without sending friend request
Друг добавлен без отправки запроса на добавление в друзья
-
+
Choose folder
Выбрать папку
@@ -229,325 +224,220 @@ Version:
Отправить файл
-
+
Send message
Отправить сообщение
-
+
Start audio call with friend
Начать аудиозвонок с другом
-
+
Plugins
Плагины
-
+
List of plugins
Список плагинов
-
+
Search
Поиск
-
+
All
Все
-
+
Online
Онлайн
-
+
Notes
Заметки
-
+
Notes about user
Заметки о пользователе
-
+
Copy link location
Копировать адрес ссылки
-
+
Copy
Копировать
-
+
Select all
Выделить всё
-
+
Delete
Удалить
-
+
Paste
Вставить
-
+
Cut
Вырезать
-
+
Undo
Отменить
-
+
Redo
Повторить
-
+
Save
Сохранить
-
+
User {} is now known as {}
Пользователь {} сейчас известен как {}
-
+
Delete message
Удалить сообщение
-
+
Lock
Заблокировать
-
+
Cannot lock app
Невозможно заблокировать приложение
-
+
Error. Profile password is not set.
Ошибка. Пароль профиля не установлен.
-
+
Name
Имя
-
+
Status message
Статус
-
+
Public key
Публичный ключ
-
+
Error
Ошибка
-
+
Profile with this name already exists
Профиль с данным именем уже существует
-
+
Choose folder with sticker pack
Выберите папку в паком стикеров
-
+
Choose folder with smiley pack
Выберите папку с паком смайлов
-
+
Import plugin
Импортировать плагин
-
+
Choose folder with plugin
Выберите папку с плагином
-
+
Restart Toxygen
Перезапустите Toxygen
-
+
Plugin will be loaded after restart
Плагин будет загружен после перезапуска
-
-
- Quote selected text
- Цитировать выбранный текст
-
-
-
- Chat history
- История чата
-
-
-
- Export as text
- Экспортировать как текст
-
-
-
- Export as HTML
- Экспортировать как HTML
-
-
-
- Updates
- Обновления
-
-
-
- Online first
- Сначала онлайн
-
-
-
- Online and by name
- Онлайн и по имени
-
-
-
- Online first and by name
- Сначала онлайн и по имени
-
-
-
- Block friend
- Заблокировать друга
-
-
-
- Not found
- Не найдено
-
-
-
- Text "{}" was not found
- Текст "{}" не был найден
-
-
-
- Reload plugins
- Перезагрузить плагины
-
-
-
- Video
- Видео
-
-
-
- User {} invites you to group chat. Accept?
- Пользователь {} приглашает Вас в групповой чат. Принять приглашение?
-
-
-
- Group chat invite
- Приглашение в групповой чат
-
-
-
- {} users in chat
- {} пользователей в чате
-
-
-
- Enter new title for group {}:
- Введите название для группы {}:
-
-
-
- Set title
- Изменить название
-
-
-
- Create group chat
- Создать групповой чат
-
-
-
- Invite to group chat
- Пригласить в групповой чат
-
-
-
- Leave chat
- Покинуть чат
-
MenuWindow
Send audio message to friend {}
- Отправить аудиосообщение другу
+ Отправить аудиосообщение другу
Start recording
- Начать запись
+ Начать запись
Stop recording
- Остановить запись
+ Остановить запись
-
+
Send screenshot
Отправить снимок экрана
-
+
Send file
Отправить файл
-
+
Send audio message
- Отправить аудиосообщение
+ Отправить аудиосообщение
-
+
Send video message
- Отправить видеосообщение
+ Отправить видеосообщение
-
+
Add smiley
Добавить смайлик
-
+
Send sticker
Отправить стикер
@@ -555,63 +445,25 @@ Version:
NetworkSettings
-
+
Network settings
Настройки сети
-
+
Restart TOX core
Перезапустить ядро TOX
-
- PasswordScreen
-
-
- Profile password
- Пароль профиля
-
-
-
- Password (at least 8 symbols)
- Пароль (минимум 8 символов)
-
-
-
- Confirm password
- Подтверждение пароля
-
-
-
- Set password
- Изменить пароль
-
-
-
- Passwords do not match
- Пароли не совпадают
-
-
-
- There is no way to recover lost passwords
- Восстановление забытых паролей не поддерживается
-
-
-
- Password must be at least 8 symbols
- Пароль должен быть длиной не менее 8 символов
-
-
PluginWindow
-
+
List of commands for plugin {}
Список команд для плагина {}
-
+
No commands available
Команды не найдены
@@ -619,42 +471,42 @@ Version:
PluginsForm
-
+
Plugins
Плагины
-
+
Open selected plugin
Открыть выбранный плагин
-
+
No GUI found for this plugin
GUI для данного плагина не найден
-
+
No description available
Описание недоступно
-
+
Disable plugin
Отключить плагин
-
+
Enable plugin
Включить плагин
-
+
No plugins found
Плагины не найдены
-
+
Error
Ошибка
@@ -662,32 +514,32 @@ Version:
ProfileSettingsForm
-
+
Export profile
Экспорт профиля
-
+
Profile settings
Настройки профиля
-
+
Name:
Имя:
-
+
Status:
Статус:
-
+
TOX ID:
TOX ID:
-
+
Copy TOX ID
Копировать TOX ID
@@ -697,120 +549,110 @@ Version:
Язык:
-
+
New avatar
Новый аватар
-
+
Reset avatar
Сбросить аватар
-
+
New NoSpam
Новый NoSpam
-
+
Profile password
Пароль профиля
-
+
Password (at least 8 symbols)
Пароль (минимум 8 символов)
-
+
Confirm password
Подтверждение пароля
-
+
Set password
Изменить пароль
-
+
Passwords do not match
Пароли не совпадают
-
+
Leaving blank will reset current password
Пустое поле сбросит текущий пароль
-
+
There is no way to recover lost passwords
Восстановление забытых паролей не поддерживается
-
+
Password must be at least 8 symbols
Пароль должен быть длиной не менее 8 символов
-
+
Choose avatar
Выбрать аватар
-
+
Online
Онлайн
-
+
Away
Нет на месте
-
+
Busy
Занят
-
+
Mark as not default profile
Отключить автозагрузку профиля
-
+
Mark as default profile
Сделать профилем по умолчанию
-
+
Copy public key
Копировать публичный ключ
-
-
- Use new path
- Использовать новый путь
-
-
-
- Do you want to move your profile to this location?
- Вы хотите переместить ваш профиль в эту папку?
-
WelcomeScreen
-
+
Don't show again
Не показывать снова
-
+
Tip of the day
Подсказка дня
-
+
Press Esc if you want hide app to tray.
Нажатие Esc сворачивает приложение в трей.
@@ -820,7 +662,7 @@ Version:
Правый клик на кнопке скриншота сворачивает приложение в трей на время скриншота
-
+
You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a>
Вы можете использовать Tox через Tor. Дополнительная информация <a href="https://wiki.tox.chat/users/tox_over_tor_tot">тут</a>
@@ -830,14 +672,14 @@ Version:
Используйте Настройки -> Интерфейс для настройки интерфейса
-
+
Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.
Установите пароль профиля: Профиль -> Настройки. Пароль позволяет шифровать историю переписки и настройки.
-
+
Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/xveduk/toxygen/blob/master/docs/plugins.md">Read more</a>
- С версии 0.1.3 Toxygen поддерживает плагины. <a href="https://github.com/xveduk/toxygen/blob/master/docs/plugins.md">Узнать больше.</a>
+ С версии 0.1.3 Toxygen поддерживает плагины. <a href="https://github.com/xveduk/toxygen/blob/master/docs/plugins.md">Узнать больше.</a>
@@ -855,80 +697,55 @@ Version:
Установите новый NoSpam, чтобы избежать спам запросов в друзья: Профиль->Настройки->Новый NoSpam
-
+
Right click on screenshot button hides app to tray during screenshot.
Правый клик на кнопке скриншота сворачивает приложение в трей на время скриншота.
-
+
Use Settings -> Interface to customize interface.
Используйте Настройки -> Интерфейс для настройки интерфейса.
-
+
Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.
Toxygen поддерживает псевдооффлайн сообщения и файл трансферы.
-
+
Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.
Установите новый NoSpam, чтобы избежать спам запросов в друзья: Профиль->Настройки->Новый NoSpam.
New in Toxygen v0.2.3:<br>TCS compliance<br>Plugins, smileys and stickers import<br>Bug fixes
- Новое в Toxygen 0.2.3:<br>Соответствие TCS<br>Импорт плагинов, смайлов и стикеров<br>Исправления ошибок
+ Новое в Toxygen 0.2.3:<br>Соответствие TCS<br>Импорт плагинов, смайлов и стикеров<br>Исправления ошибок
-
+
Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu
Чтобы удалить отдельное сообщение в чате сделайте правый клик на спиннер или время сообщения и выберите "Удалить" в меню
-
+
Use right click on inline image to save it
Правый клик на инлайн изображении позволит сохранить его
-
-
- New in Toxygen v0.2.4:<br>File transfers update<br>Autoreconnection<br>Improvements<br>Bug fixes
- Новое в Toxygen v0.2.4:<br>Передача файлов обновлена<br>Автопереподключение<br>Улучшения<br>Исправления ошибок
-
-
-
- New in Toxygen v0.2.6:<br>Updater<br>Better contact sorting<br>Plugins improvements
- Новое в Toxygen v0.2.6:<br>Поддержка обновлений<br>Улучшенная сортировка контактов<br>Улучшения в работе плагинов
-
-
-
- Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a>
- С версии 0.1.3 Toxygen поддерживает плагины. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Узнать больше.</a>
-
-
-
- New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5
- Новое в Toxygen 0.3.0:<br>Видеозвонки<br>Поддержка Python3.6<br>Миграция на PyQt5
-
-
-
- New in Toxygen 0.4.1:<br>Downloading nodes from tox.chat<br>Bug fixes
-
-
audioSettingsForm
-
+
Audio settings
Настройки аудио
-
+
Input device:
Устройство ввода:
-
+
Output device:
Устройство вывода:
@@ -936,32 +753,32 @@ Version:
incoming_call
-
+
Incoming video call
Входящий видеозвонок
-
+
Incoming audio call
Входящий аудиозвонок
-
+
Outgoing video call
Исходящий видеозвонок
-
+
Outgoing audio call
Исходящий аудиозвонок
-
+
Call declined
Звонок отменен
-
+
Call finished
Звонок завершен
@@ -969,125 +786,115 @@ Version:
interfaceForm
-
+
Interface settings
Настройки интерфейса
-
+
Theme:
Тема:
-
+
Language:
Язык:
-
+
Smileys
Смайлики
-
+
Smiley pack:
Набор смайликов:
-
+
Mirror mode
Зеркальный режим
-
+
Messages font size:
Размер шрифта сообщений:
-
+
Restart app to apply settings
Для применения настроек необходимо перезапустить приложение
-
+
Restart required
Требуется перезапуск
-
+
Select unread messages notification color
Цвет уведомления о сообщении
-
+
Compact contact list
Компактный список контактов
-
+
Import smiley pack
Импортировать смайлы
-
+
Import sticker pack
Импортировать стикеры
-
+
Show avatars in chat
Показывать аватары в чате
-
-
- Close to tray
- Сворачивать в трей
-
-
-
- Select font
- Выбрать шрифт
-
login
-
+
Log in
Вход
-
+
Create
Создать
-
+
Profile name:
Имя профиля:
-
+
Load profile
Загрузить профиль
-
+
Use as default
По умолчанию
-
+
Load existing profile
Загрузить профиль
-
+
Create new profile
Создать новый профиль
-
+
toxygen
toxygen
@@ -1097,152 +904,114 @@ Version:
Похоже, что этот профиль используется другим экземпляром Toxygen! Продолжить?
-
+
Profile name
Имя профиля
-
+
Other instance of Toxygen uses this profile or profile was not properly closed. Continue?
Этот профиль используется другим экземпляром Toxygen или не был правильно закрыт. Продолжить?
-
+
Do you want to set profile password?
Хотите ли вы установить пароль профиля?
-
-
- Do you want to save profile in default folder? If no, profile will be saved in program folder
- Вы хотите сохранить профиль в папку по умолчанию? Если нет, профиль будет сохранен в папке с программой
-
-
-
- Profile saving error! Does Toxygen have permission to write to this directory?
- Ошибка сохранения профиля! Toxygen имеет разрешение на запись в данную папку?
-
-
-
- Update for Toxygen was found. Download and install it?
- Обновление для Toxygen было найдено. Загрузить и установить его?
-
notificationsForm
-
+
Notification settings
Настройки уведомлений
-
+
Enable notifications
Включить уведомления
-
+
Enable call's sound
Включить звук звонка
-
+
Enable sound notifications
Включить звуковые уведомления
-
-
- Notify about all messages in groups
- Уведомлять обо всех сообщениях в группах
-
-
-
- pass
-
-
- Enter password
- Введите пароль
-
-
-
- Password:
- Пароль:
-
-
-
- Incorrect password
- Неверный пароль
-
privacySettings
-
+
Privacy settings
Настройки приватности
-
+
Save chat history
Сохранять историю переписки
-
+
Allow file auto accept
Разрешить автополучение файлов
-
+
Send typing notifications
Посылать уведомления о наборе текста
-
+
Auto accept default path:
Путь автоприема файлов:
-
+
Change
Изменить
-
+
Allow inlines
Разрешать инлайны
-
+
Chat history
История чата
-
+
History will be cleaned! Continue?
История переписки будет очищена! Продолжить?
-
+
Blocked users:
Заблокированные пользователи:
-
+
Unblock
Разблокировать
-
+
Block user
Заблокировать пользователя
-
+
Add to friend list
Добавить в список друзей
-
+
Do you want to add this user to friend list?
Добавить этого пользователя в список друзей?
@@ -1252,12 +1021,12 @@ Version:
Блокировать по TOX ID:
-
+
Block by public key:
Блокировать по публичному ключу:
-
+
Save unsent messages only
Сохранять только неотправленные сообщения
@@ -1265,115 +1034,34 @@ Version:
tray
-
+
Open Toxygen
Открыть Toxygen
-
+
Exit
Выход
-
+
Set status
Изменить статус
-
+
Online
Онлайн
-
+
Away
Нет на месте
-
+
Busy
Занят
-
- updateSettingsForm
-
-
- Update settings
- Обновить настройки
-
-
-
- Select update mode:
- Выбрать режим обновлений:
-
-
-
- Update Toxygen
- Обновить Toxygen
-
-
-
- Disabled
- Отключены
-
-
-
- Manual
- Вручную
-
-
-
- Auto
- Автоматически
-
-
-
- Error
- Ошибка
-
-
-
- Problems with internet connection
- Проблемы с соединением
-
-
-
- Updater not found
- Апдейтер не был найден
-
-
-
- No updates found
- Обновления не найдены
-
-
-
- Toxygen is up to date
- Toxygen уже обновлен
-
-
-
- videoSettingsForm
-
-
- Video settings
- Настройки видео
-
-
-
- Device:
- Устройство:
-
-
-
- Desktop
- Рабочий стол
-
-
-
- Select region
- Выберите область
-
-
diff --git a/toxygen/translations/uk_UA.qm b/toxygen/translations/uk_UA.qm
deleted file mode 100644
index a4082ef..0000000
Binary files a/toxygen/translations/uk_UA.qm and /dev/null differ
diff --git a/toxygen/translations/uk_UA.ts b/toxygen/translations/uk_UA.ts
deleted file mode 100644
index c36ecf0..0000000
--- a/toxygen/translations/uk_UA.ts
+++ /dev/null
@@ -1,1297 +0,0 @@
-
-
-
- AddContact
-
-
- TOX ID:
- TOX ID:
-
-
-
- Add contact
- Додати контакт
-
-
-
- Message:
- Повідомлення:
-
-
-
- TOX ID or public key of contact
-
-
-
-
- Callback
-
-
- File from
-
-
-
-
- Form
-
-
- IP:
- IP:
-
-
-
- UDP
- UDP
-
-
-
- HTTP
- HTTP
-
-
-
- IPv6
- IPv6
-
-
-
- Port:
- Порт:
-
-
-
- Proxy
- Проксі
-
-
-
- Online contacts
- Контактів онлайн
-
-
-
- Send request
- Відправити запит
-
-
-
- WARNING:
-using proxy with enabled UDP
-can produce IP leak
-
-
-
-
- Download nodes list from tox.chat
-
-
-
-
- MainWindow
-
-
- About program
- Про проґраму
-
-
-
- Friend request
- Запит дружби
-
-
-
- About
- Про
-
-
-
- Audio
- Звук
-
-
-
- Friend added
- Друга додано
-
-
-
- Send file
- Надіслати файл
-
-
-
- User {} wants to add you to contact list. Message:
-{}
- Користувач {} хоче додати вас до списку контактів. Повідомлення
-{}
-
-
-
- Network
- Мережа
-
-
-
- Clear history
- Очистити журнал
-
-
-
- Copy public key
- Копіювати публічний ключ
-
-
-
- Send message
- Надіслати повідомлення
-
-
-
- Set alias
- Встановити скорочення
-
-
-
- Privacy
- Приватність
-
-
-
- Profile
- Профіль
-
-
-
- Toxygen is Tox client written on Python.
-Version:
- Toxygen — це клієнт Tox написаний на Python.
-Версія:
-
-
-
- Choose file
- Обрати файл
-
-
-
- Enter new alias for friend {} or leave empty to use friend's name:
- Введіть нове скорочення для друга {} або залишіть порожнім, щоб використовувати його псевдо:
-
-
-
- Add contact
- Додати контакт
-
-
-
- Friend added without sending friend request
- Друга додано без надсилання запиту дружби
-
-
-
- Interface
- Зовнішній вигляд
-
-
-
- Settings
- Налаштування
-
-
-
- Notifications
- Сповіщення
-
-
-
- Remove friend
- Вилучити друга
-
-
-
- Find contact
- Знайти контакт
-
-
-
- Choose folder
- Обрати теку
-
-
-
- Allow auto accept
- Дозволити автоприймання
-
-
-
- Disallow auto accept
- Заборонити автоприймання
-
-
-
- Start audio call with friend
- Почати звуковий дзвінок
-
-
-
- Send screenshot
- Надіслати знімок екрану
-
-
-
- Error
-
-
-
-
- Profile with this name already exists
-
-
-
-
- User {} is now known as {}
-
-
-
-
- Choose folder with sticker pack
-
-
-
-
- Choose folder with smiley pack
-
-
-
-
- Quote selected text
-
-
-
-
- Plugins
-
-
-
-
- Delete message
-
-
-
-
- Lock
-
-
-
-
- List of plugins
-
-
-
-
- Video
-
-
-
-
- Updates
-
-
-
-
- Search
-
-
-
-
- All
-
-
-
-
- Online
-
-
-
-
- Online first
-
-
-
-
- Name
-
-
-
-
- Online and by name
-
-
-
-
- Online first and by name
-
-
-
-
- Import plugin
-
-
-
-
- Reload plugins
-
-
-
-
- Choose folder with plugin
-
-
-
-
- Restart Toxygen
-
-
-
-
- Plugin will be loaded after restart
-
-
-
-
- Cannot lock app
-
-
-
-
- Error. Profile password is not set.
-
-
-
-
- Chat history
- Журнал бесіди
-
-
-
- Export as text
-
-
-
-
- Export as HTML
-
-
-
-
- Copy
-
-
-
-
- Status message
-
-
-
-
- Public key
-
-
-
-
- Block friend
-
-
-
-
- Notes
-
-
-
-
- Notes about user
-
-
-
-
- Copy link location
-
-
-
-
- Select all
-
-
-
-
- Delete
-
-
-
-
- Paste
-
-
-
-
- Cut
-
-
-
-
- Undo
-
-
-
-
- Redo
-
-
-
-
- Save
-
-
-
-
- Text "{}" was not found
-
-
-
-
- Not found
-
-
-
-
- User {} invites you to group chat. Accept?
-
-
-
-
- Group chat invite
-
-
-
-
- {} users in chat
-
-
-
-
- Enter new title for group {}:
-
-
-
-
- Set title
-
-
-
-
- Create group chat
-
-
-
-
- Invite to group chat
-
-
-
-
- Leave chat
-
-
-
-
- MenuWindow
-
-
- Send screenshot
- Надіслати знімок екрану
-
-
-
- Send file
- Надіслати файл
-
-
-
- Add smiley
-
-
-
-
- Send sticker
-
-
-
-
- NetworkSettings
-
-
- Network settings
- Налаштування мережі
-
-
-
- Restart TOX core
- Перезапустити ядро Tox
-
-
-
- PasswordScreen
-
-
- Profile password
-
-
-
-
- Password (at least 8 symbols)
-
-
-
-
- Confirm password
-
-
-
-
- Set password
-
-
-
-
- Passwords do not match
-
-
-
-
- There is no way to recover lost passwords
-
-
-
-
- Password must be at least 8 symbols
-
-
-
-
- PluginWindow
-
-
- List of commands for plugin {}
-
-
-
-
- No commands available
-
-
-
-
- PluginsForm
-
-
- Plugins
-
-
-
-
- Open selected plugin
-
-
-
-
- No GUI found for this plugin
-
-
-
-
- Error
-
-
-
-
- No description available
-
-
-
-
- Disable plugin
-
-
-
-
- Enable plugin
-
-
-
-
- No plugins found
-
-
-
-
- ProfileSettingsForm
-
-
- Name:
- Псевдо:
-
-
-
- Profile settings
- Налаштування профілю
-
-
-
- Reset avatar
- Скинути аватар
-
-
-
- New NoSpam
- Новий NoSpam
-
-
-
- Copy TOX ID
- Копіювати TOX ID
-
-
-
- New avatar
- Новий аватар
-
-
-
- Export profile
- Експортувати профіль
-
-
-
- TOX ID:
- TOX ID:
-
-
-
- Status:
- Статус:
-
-
-
- Profile password
-
-
-
-
- Password (at least 8 symbols)
-
-
-
-
- Confirm password
-
-
-
-
- Set password
-
-
-
-
- Passwords do not match
-
-
-
-
- Leaving blank will reset current password
-
-
-
-
- There is no way to recover lost passwords
-
-
-
-
- Online
-
-
-
-
- Away
-
-
-
-
- Busy
-
-
-
-
- Copy public key
- Копіювати публічний ключ
-
-
-
- Mark as not default profile
-
-
-
-
- Mark as default profile
-
-
-
-
- Password must be at least 8 symbols
-
-
-
-
- Choose avatar
-
-
-
-
- Use new path
-
-
-
-
- Do you want to move your profile to this location?
-
-
-
-
- WelcomeScreen
-
-
- Don't show again
-
-
-
-
- Tip of the day
-
-
-
-
- Press Esc if you want hide app to tray.
-
-
-
-
- Right click on screenshot button hides app to tray during screenshot.
-
-
-
-
- You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a>
-
-
-
-
- Use Settings -> Interface to customize interface.
-
-
-
-
- Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.
-
-
-
-
- Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a>
-
-
-
-
- Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.
-
-
-
-
- Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu
-
-
-
-
- Use right click on inline image to save it
-
-
-
-
- Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.
-
-
-
-
- New in Toxygen 0.4.1:<br>Downloading nodes from tox.chat<br>Bug fixes
-
-
-
-
- audioSettingsForm
-
-
- Output device:
- Пристрій виводу:
-
-
-
- Audio settings
- Налаштування звуку
-
-
-
- Input device:
- Пристрій вводу:
-
-
-
- incoming_call
-
-
- Incoming video call
- Вхідний відеодзвінок
-
-
-
- Incoming audio call
- Вхідний аудіодзвінок
-
-
-
- Outgoing video call
-
-
-
-
- Outgoing audio call
-
-
-
-
- Call declined
-
-
-
-
- Call finished
-
-
-
-
- interfaceForm
-
-
- Language:
- Мова:
-
-
-
- Theme:
- Тема:
-
-
-
- Interface settings
- Налаштування зовнішнього вигляду
-
-
-
- Show avatars in chat
-
-
-
-
- Smileys
-
-
-
-
- Smiley pack:
-
-
-
-
- Mirror mode
-
-
-
-
- Messages font size:
-
-
-
-
- Select unread messages notification color
-
-
-
-
- Compact contact list
-
-
-
-
- Import smiley pack
-
-
-
-
- Import sticker pack
-
-
-
-
- Close to tray
-
-
-
-
- Select font
-
-
-
-
- Restart app to apply settings
-
-
-
-
- Restart required
-
-
-
-
- login
-
-
- Profile name:
- Псевдо профілю:
-
-
-
- Load profile
- Завантажити профіль
-
-
-
- Use as default
- За замовчуванням
-
-
-
- Create new profile
- Створити новий профіль
-
-
-
- Create
- Створити
-
-
-
- Log in
- Увійти
-
-
-
- Load existing profile
- Завантажити існуючий
-
-
-
- toxygen
- toxygen
-
-
-
- Looks like other instance of Toxygen uses this profile! Continue?
- Схоже, що інша копія Toxygenʼу використовує цей профіль! Продовжити?
-
-
-
- Do you want to set profile password?
-
-
-
-
- Do you want to save profile in default folder? If no, profile will be saved in program folder
-
-
-
-
- Profile saving error! Does Toxygen have permission to write to this directory?
-
-
-
-
- Other instance of Toxygen uses this profile or profile was not properly closed. Continue?
-
-
-
-
- Update for Toxygen was found. Download and install it?
-
-
-
-
- Profile name
-
-
-
-
- notificationsForm
-
-
- Enable sound notifications
- Увімкнути звукові сповіщення
-
-
-
- Enable notifications
- Увімкнути сповіщення
-
-
-
- Notification settings
- Налаштування сповіщень
-
-
-
- Enable call's sound
- Увімкнути звук дзвінка
-
-
-
- Notify about all messages in groups
-
-
-
-
- pass
-
-
- Enter password
-
-
-
-
- Password:
-
-
-
-
- Incorrect password
-
-
-
-
- privacySettings
-
-
- Privacy settings
- Налаштування приватності
-
-
-
- Add to friend list
- Додати до списку друзів
-
-
-
- Block by TOX ID:
- Блокувати по TOX ID:
-
-
-
- Blocked users:
- Блоковані користувачі:
-
-
-
- Change
- Змінити
-
-
-
- Send typing notifications
- Надсилати сповіщення про те, що я друкую
-
-
-
- Allow file auto accept
- Дозволити автоприймання файлів
-
-
-
- Allow inlines
- Дозволити інлайни
-
-
-
- Save chat history
- Зберігати журнал бесіди
-
-
-
- Block user
- Блокувати користувача
-
-
-
- Chat history
- Журнал бесіди
-
-
-
- Unblock
- Розблокувати
-
-
-
- History will be cleaned! Continue?
- Журнал буде очищено! Продовжити?
-
-
-
- Auto accept default path:
- Шлях за замовчуванням для автоприймання:
-
-
-
- Do you want to add this user to friend list?
- Ви хочете додати цього користувача у список друзів?
-
-
-
- Block by public key:
-
-
-
-
- Save unsent messages only
-
-
-
-
- tray
-
-
- Exit
- Вихід
-
-
-
- Open Toxygen
- Відкрити Toxygen
-
-
-
- Set status
-
-
-
-
- Online
-
-
-
-
- Away
-
-
-
-
- Busy
-
-
-
-
- updateSettingsForm
-
-
- Update settings
-
-
-
-
- Select update mode:
-
-
-
-
- Update Toxygen
-
-
-
-
- Disabled
-
-
-
-
- Manual
-
-
-
-
- Auto
-
-
-
-
- Error
-
-
-
-
- Problems with internet connection
-
-
-
-
- Updater not found
-
-
-
-
- No updates found
-
-
-
-
- Toxygen is up to date
-
-
-
-
- videoSettingsForm
-
-
- Video settings
-
-
-
-
- Device:
-
-
-
-
- Desktop
-
-
-
-
- Select region
-
-
-
-
diff --git a/toxygen/ui/__init__.py b/toxygen/ui/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/toxygen/ui/contact_items.py b/toxygen/ui/contact_items.py
deleted file mode 100644
index 7a32284..0000000
--- a/toxygen/ui/contact_items.py
+++ /dev/null
@@ -1,97 +0,0 @@
-from wrapper.toxcore_enums_and_consts import *
-from PyQt5 import QtCore, QtGui, QtWidgets
-from utils.util import *
-from ui.widgets import DataLabel
-
-
-class ContactItem(QtWidgets.QWidget):
- """
- Contact in friends list
- """
-
- def __init__(self, settings, parent=None):
- QtWidgets.QWidget.__init__(self, parent)
- mode = settings['compact_mode']
- self.setBaseSize(QtCore.QSize(250, 40 if mode else 70))
- self.avatar_label = QtWidgets.QLabel(self)
- size = 32 if mode else 64
- self.avatar_label.setGeometry(QtCore.QRect(3, 4, size, size))
- self.avatar_label.setScaledContents(False)
- 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(settings['font'])
- font.setPointSize(10 if mode else 12)
- font.setBold(True)
- self.name.setFont(font)
- self.status_message = DataLabel(self)
- self.status_message.setGeometry(QtCore.QRect(50 if mode else 75, 20 if mode else 30, 170, 15 if mode else 20))
- font.setPointSize(10)
- font.setBold(False)
- self.status_message.setFont(font)
- self.connection_status = StatusCircle(self)
- self.connection_status.setGeometry(QtCore.QRect(230, -2 if mode else 5, 32, 32))
- self.messages = UnreadMessagesCount(settings, self)
- self.messages.setGeometry(QtCore.QRect(20 if mode else 52, 20 if mode else 50, 30, 20))
-
-
-class StatusCircle(QtWidgets.QWidget):
- """
- Connection status
- """
- def __init__(self, parent):
- QtWidgets.QWidget.__init__(self, parent)
- self.setGeometry(0, 0, 32, 32)
- self.label = QtWidgets.QLabel(self)
- self.label.setGeometry(QtCore.QRect(0, 0, 32, 32))
- self.unread = False
-
- def update(self, status, unread_messages=None):
- if unread_messages is None:
- unread_messages = self.unread
- else:
- self.unread = unread_messages
- if status == TOX_USER_STATUS['NONE']:
- name = 'online'
- elif status == TOX_USER_STATUS['AWAY']:
- name = 'idle'
- elif status == TOX_USER_STATUS['BUSY']:
- name = 'busy'
- else:
- name = 'offline'
- if unread_messages:
- name += '_notification'
- self.label.setGeometry(QtCore.QRect(0, 0, 32, 32))
- else:
- self.label.setGeometry(QtCore.QRect(2, 0, 32, 32))
- pixmap = QtGui.QPixmap(join_path(get_images_directory(), '{}.png'.format(name)))
- self.label.setPixmap(pixmap)
-
-
-class UnreadMessagesCount(QtWidgets.QWidget):
-
- def __init__(self, settings, parent=None):
- super().__init__(parent)
- self._settings = settings
- self.resize(30, 20)
- self.label = QtWidgets.QLabel(self)
- self.label.setGeometry(QtCore.QRect(0, 0, 30, 20))
- self.label.setVisible(False)
- font = QtGui.QFont()
- font.setFamily(settings['font'])
- font.setPointSize(12)
- font.setBold(True)
- self.label.setFont(font)
- self.label.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignCenter)
- color = settings['unread_color']
- self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }')
-
- def update(self, messages_count):
- color = self._settings['unread_color']
- self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }')
- if messages_count:
- self.label.setVisible(True)
- self.label.setText(str(messages_count))
- else:
- self.label.setVisible(False)
diff --git a/toxygen/ui/create_profile_screen.py b/toxygen/ui/create_profile_screen.py
deleted file mode 100644
index 512c141..0000000
--- a/toxygen/ui/create_profile_screen.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from ui.widgets import *
-from PyQt5 import uic
-import utils.util as util
-import utils.ui as util_ui
-
-
-class CreateProfileScreenResult:
-
- def __init__(self, save_into_default_folder, password):
- self._save_into_default_folder = save_into_default_folder
- self._password = password
-
- def get_save_into_default_folder(self):
- return self._save_into_default_folder
-
- save_into_default_folder = property(get_save_into_default_folder)
-
- def get_password(self):
- return self._password
-
- password = property(get_password)
-
-
-class CreateProfileScreen(CenteredWidget, DialogWithResult):
-
- def __init__(self):
- CenteredWidget.__init__(self)
- DialogWithResult.__init__(self)
- uic.loadUi(util.get_views_path('create_profile_screen'), self)
- self.center()
- self.createProfile.clicked.connect(self._create_profile)
- self._retranslate_ui()
-
- def _retranslate_ui(self):
- self.setWindowTitle(util_ui.tr('New profile settings'))
- self.defaultFolder.setText(util_ui.tr('Save in default folder'))
- self.programFolder.setText(util_ui.tr('Save in program folder'))
- self.password.setPlaceholderText(util_ui.tr('Password'))
- self.confirmPassword.setPlaceholderText(util_ui.tr('Confirm password'))
- self.createProfile.setText(util_ui.tr('Create profile'))
- self.passwordLabel.setText(util_ui.tr('Password (at least 8 symbols):'))
-
- def _create_profile(self):
- password = self.password.text()
- if password != self.confirmPassword.text():
- self.errorLabel.setText(util_ui.tr('Passwords do not match'))
- return
- if 0 < len(password) < 8:
- self.errorLabel.setText(util_ui.tr('Password must be at least 8 symbols'))
- return
- result = CreateProfileScreenResult(self.defaultFolder.isChecked(), password)
- self.close_with_result(result)
diff --git a/toxygen/ui/group_bans_widgets.py b/toxygen/ui/group_bans_widgets.py
deleted file mode 100644
index b2758c7..0000000
--- a/toxygen/ui/group_bans_widgets.py
+++ /dev/null
@@ -1,68 +0,0 @@
-from ui.widgets import CenteredWidget
-from PyQt5 import uic, QtWidgets, QtCore
-import utils.util as util
-import utils.ui as util_ui
-
-
-class GroupBanItem(QtWidgets.QWidget):
-
- def __init__(self, ban, cancel_ban, can_cancel_ban, parent=None):
- super().__init__(parent)
- self._ban = ban
- self._cancel_ban = cancel_ban
- self._can_cancel_ban = can_cancel_ban
-
- uic.loadUi(util.get_views_path('gc_ban_item'), self)
- self._update_ui()
-
- def _update_ui(self):
- self._retranslate_ui()
-
- self.banTargetLabel.setText(self._ban.ban_target)
- ban_time = self._ban.ban_time
- self.banTimeLabel.setText(util.unix_time_to_long_str(ban_time))
-
- self.cancelPushButton.clicked.connect(self._cancel_ban)
- self.cancelPushButton.setEnabled(self._can_cancel_ban)
-
- def _retranslate_ui(self):
- self.cancelPushButton.setText(util_ui.tr('Cancel ban'))
-
- def _cancel_ban(self):
- self._cancel_ban(self._ban.ban_id)
-
-
-class GroupBansScreen(CenteredWidget):
-
- def __init__(self, groups_service, group):
- super().__init__()
- self._groups_service = groups_service
- self._group = group
-
- uic.loadUi(util.get_views_path('bans_list_screen'), self)
- self._update_ui()
-
- def _update_ui(self):
- self._retranslate_ui()
-
- self._refresh_bans_list()
-
- def _retranslate_ui(self):
- self.setWindowTitle(util_ui.tr('Bans list for group "{}"').format(self._group.name))
-
- def _refresh_bans_list(self):
- self.bansListWidget.clear()
- can_cancel_ban = self._group.is_self_moderator_or_founder()
- for ban in self._group.bans:
- self._create_ban_item(ban, can_cancel_ban)
-
- def _create_ban_item(self, ban, can_cancel_ban):
- item = GroupBanItem(ban, self._on_ban_cancelled, can_cancel_ban, self.bansListWidget)
- elem = QtWidgets.QListWidgetItem()
- elem.setSizeHint(QtCore.QSize(item.width(), item.height()))
- self.bansListWidget.addItem(elem)
- self.bansListWidget.setItemWidget(elem, item)
-
- def _on_ban_cancelled(self, ban_id):
- self._groups_service.cancel_ban(self._group.number, ban_id)
- self._refresh_bans_list()
diff --git a/toxygen/ui/group_invites_widgets.py b/toxygen/ui/group_invites_widgets.py
deleted file mode 100644
index d35aca1..0000000
--- a/toxygen/ui/group_invites_widgets.py
+++ /dev/null
@@ -1,127 +0,0 @@
-from PyQt5 import uic, QtWidgets
-import utils.util as util
-from ui.widgets import *
-
-
-class GroupInviteItem(QtWidgets.QWidget):
-
- def __init__(self, parent, chat_name, avatar, friend_name):
- super().__init__(parent)
- uic.loadUi(util.get_views_path('gc_invite_item'), self)
-
- self.groupNameLabel.setText(chat_name)
- self.friendNameLabel.setText(friend_name)
- self.friendAvatarLabel.setPixmap(avatar)
-
- def is_selected(self):
- return self.selectCheckBox.isChecked()
-
- def subscribe_checked_event(self, callback):
- self.selectCheckBox.clicked.connect(callback)
-
-
-class GroupInvitesScreen(CenteredWidget):
-
- def __init__(self, groups_service, profile, contacts_provider):
- super().__init__()
- self._groups_service = groups_service
- self._profile = profile
- self._contacts_provider = contacts_provider
-
- uic.loadUi(util.get_views_path('group_invites_screen'), self)
-
- self._update_ui()
-
- def _update_ui(self):
- self._retranslate_ui()
-
- self._refresh_invites_list()
-
- self.nickLineEdit.setText(self._profile.name)
- self.statusComboBox.setCurrentIndex(self._profile.status or 0)
-
- self.nickLineEdit.textChanged.connect(self._nick_changed)
- self.acceptPushButton.clicked.connect(self._accept_invites)
- self.declinePushButton.clicked.connect(self._decline_invites)
-
- self.invitesListWidget.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
- self.invitesListWidget.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
-
- self._update_buttons_state()
-
- def _retranslate_ui(self):
- self.setWindowTitle(util_ui.tr('Group chat invites'))
- self.noInvitesLabel.setText(util_ui.tr('No group invites found'))
- self.acceptPushButton.setText(util_ui.tr('Accept'))
- self.declinePushButton.setText(util_ui.tr('Decline'))
- self.statusComboBox.addItem(util_ui.tr('Online'))
- self.statusComboBox.addItem(util_ui.tr('Away'))
- self.statusComboBox.addItem(util_ui.tr('Busy'))
- self.nickLineEdit.setPlaceholderText(util_ui.tr('Your nick in chat'))
- self.passwordLineEdit.setPlaceholderText(util_ui.tr('Optional password'))
-
- def _get_friend(self, public_key):
- return self._contacts_provider.get_friend_by_public_key(public_key)
-
- def _accept_invites(self):
- nick = self.nickLineEdit.text()
- password = self.passwordLineEdit.text()
- status = self.statusComboBox.currentIndex()
-
- selected_invites = self._get_selected_invites()
- for invite in selected_invites:
- self._groups_service.accept_group_invite(invite, nick, status, password)
-
- self._refresh_invites_list()
- self._close_window_if_needed()
-
- def _decline_invites(self):
- selected_invites = self._get_selected_invites()
- for invite in selected_invites:
- self._groups_service.decline_group_invite(invite)
-
- self._refresh_invites_list()
- self._close_window_if_needed()
-
- def _get_selected_invites(self):
- all_invites = self._groups_service.get_group_invites()
- selected = []
- items_count = len(all_invites)
- for index in range(items_count):
- list_item = self.invitesListWidget.item(index)
- item_widget = self.invitesListWidget.itemWidget(list_item)
- if item_widget.is_selected():
- selected.append(all_invites[index])
-
- return selected
-
- def _refresh_invites_list(self):
- self.invitesListWidget.clear()
- invites = self._groups_service.get_group_invites()
- for invite in invites:
- self._create_invite_item(invite)
-
- def _create_invite_item(self, invite):
- friend = self._get_friend(invite.friend_public_key)
- item = GroupInviteItem(self.invitesListWidget, invite.chat_name, friend.get_pixmap(), friend.name)
- item.subscribe_checked_event(self._item_selected)
- elem = QtWidgets.QListWidgetItem()
- elem.setSizeHint(QtCore.QSize(item.width(), item.height()))
- self.invitesListWidget.addItem(elem)
- self.invitesListWidget.setItemWidget(elem, item)
-
- def _item_selected(self):
- self._update_buttons_state()
-
- def _nick_changed(self):
- self._update_buttons_state()
-
- def _update_buttons_state(self):
- nick = self.nickLineEdit.text()
- selected_items = self._get_selected_invites()
- self.acceptPushButton.setEnabled(bool(nick) and len(selected_items))
- self.declinePushButton.setEnabled(len(selected_items) > 0)
-
- def _close_window_if_needed(self):
- if self._groups_service.group_invites_count == 0:
- self.close()
diff --git a/toxygen/ui/group_peers_list.py b/toxygen/ui/group_peers_list.py
deleted file mode 100644
index 9d2632d..0000000
--- a/toxygen/ui/group_peers_list.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from ui.widgets import *
-from wrapper.toxcore_enums_and_consts import *
-
-
-class PeerItem(QtWidgets.QWidget):
-
- def __init__(self, peer, handler, width, parent=None):
- super().__init__(parent)
- self.resize(QtCore.QSize(width, 34))
- self.nameLabel = DataLabel(self)
- self.nameLabel.setGeometry(5, 0, width - 5, 34)
- name = peer.name
- if peer.is_current_user:
- name += util_ui.tr(' (You)')
- self.nameLabel.setText(name)
- if peer.status == TOX_USER_STATUS['NONE']:
- style = 'QLabel {color: green}'
- elif peer.status == TOX_USER_STATUS['AWAY']:
- style = 'QLabel {color: yellow}'
- else:
- style = 'QLabel {color: red}'
- self.nameLabel.setStyleSheet(style)
- self.nameLabel.mousePressEvent = lambda x: handler(peer.id)
-
-
-class PeerTypeItem(QtWidgets.QWidget):
-
- def __init__(self, text, width, parent=None):
- super().__init__(parent)
- self.resize(QtCore.QSize(width, 34))
- self.nameLabel = DataLabel(self)
- self.nameLabel.setGeometry(5, 0, width - 5, 34)
- self.nameLabel.setText(text)
diff --git a/toxygen/ui/group_settings_widgets.py b/toxygen/ui/group_settings_widgets.py
deleted file mode 100644
index c32168b..0000000
--- a/toxygen/ui/group_settings_widgets.py
+++ /dev/null
@@ -1,77 +0,0 @@
-from ui.widgets import CenteredWidget
-from PyQt5 import uic
-import utils.util as util
-import utils.ui as util_ui
-
-
-class GroupManagementScreen(CenteredWidget):
-
- def __init__(self, groups_service, group):
- super().__init__()
- self._groups_service = groups_service
- self._group = group
-
- uic.loadUi(util.get_views_path('group_management_screen'), self)
- self._update_ui()
-
- def _update_ui(self):
- self._retranslate_ui()
-
- self.passwordLineEdit.setText(self._group.password)
- self.privacyStateComboBox.setCurrentIndex(1 if self._group.is_private else 0)
- self.peersLimitSpinBox.setValue(self._group.peers_limit)
-
- self.savePushButton.clicked.connect(self._save)
-
- def _retranslate_ui(self):
- self.setWindowTitle(util_ui.tr('Group "{}"').format(self._group.name))
- self.passwordLabel.setText(util_ui.tr('Password:'))
- self.peerLimitLabel.setText(util_ui.tr('Peer limit:'))
- self.privacyStateLabel.setText(util_ui.tr('Privacy state:'))
- self.savePushButton.setText(util_ui.tr('Save'))
-
- self.privacyStateComboBox.clear()
- self.privacyStateComboBox.addItem(util_ui.tr('Public'))
- self.privacyStateComboBox.addItem(util_ui.tr('Private'))
-
- def _save(self):
- password = self.passwordLineEdit.text()
- privacy_state = self.privacyStateComboBox.currentIndex()
- peers_limit = self.peersLimitSpinBox.value()
-
- self._groups_service.set_group_password(self._group, password)
- self._groups_service.set_group_privacy_state(self._group, privacy_state)
- self._groups_service.set_group_peers_limit(self._group, peers_limit)
-
- self.close()
-
-
-class GroupSettingsScreen(CenteredWidget):
-
- def __init__(self, group):
- super().__init__()
- self._group = group
-
- uic.loadUi(util.get_views_path('gc_settings_screen'), self)
- self._update_ui()
-
- def _update_ui(self):
- self._retranslate_ui()
-
- self.copyPasswordPushButton.clicked.connect(self._copy_password)
- self.copyPasswordPushButton.setEnabled(bool(self._group.password))
-
- def _retranslate_ui(self):
- self.setWindowTitle(util_ui.tr('Group "{}"').format(self._group.name))
- if self._group.password:
- password_label_text = '{} {}'.format(util_ui.tr('Password:'), self._group.password)
- else:
- password_label_text = util_ui.tr('Password is not set')
- self.passwordLabel.setText(password_label_text)
- self.peerLimitLabel.setText('{} {}'.format(util_ui.tr('Peer limit:'), self._group.peers_limit))
- privacy_state = util_ui.tr('Private') if self._group.is_private else util_ui.tr('Public')
- self.privacyStateLabel.setText('{} {}'.format(util_ui.tr('Privacy state:'), privacy_state))
- self.copyPasswordPushButton.setText(util_ui.tr('Copy password'))
-
- def _copy_password(self):
- util_ui.copy_to_clipboard(self._group.password)
diff --git a/toxygen/ui/groups_widgets.py b/toxygen/ui/groups_widgets.py
deleted file mode 100644
index ad4b703..0000000
--- a/toxygen/ui/groups_widgets.py
+++ /dev/null
@@ -1,123 +0,0 @@
-from PyQt5 import uic
-import utils.util as util
-from ui.widgets import *
-from wrapper.toxcore_enums_and_consts import *
-
-
-class BaseGroupScreen(CenteredWidget):
-
- def __init__(self, groups_service, profile):
- super().__init__()
- self._groups_service = groups_service
- self._profile = profile
-
- def _retranslate_ui(self):
- self.nickLineEdit.setPlaceholderText(util_ui.tr('Your nick in chat'))
- self.nickLabel.setText(util_ui.tr('Nickname:'))
- self.statusLabel.setText(util_ui.tr('Status:'))
- self.statusComboBox.addItem(util_ui.tr('Online'))
- self.statusComboBox.addItem(util_ui.tr('Away'))
- self.statusComboBox.addItem(util_ui.tr('Busy'))
-
-
-class CreateGroupScreen(BaseGroupScreen):
-
- def __init__(self, groups_service, profile):
- super().__init__(groups_service, profile)
- uic.loadUi(util.get_views_path('create_group_screen'), self)
- self.center()
- self._update_ui()
-
- def _update_ui(self):
- self._retranslate_ui()
-
- self.statusComboBox.setCurrentIndex(self._profile.status or 0)
- self.nickLineEdit.setText(self._profile.name)
-
- self.addGroupButton.clicked.connect(self._create_group)
- self.groupNameLineEdit.textChanged.connect(self._group_name_changed)
- self.nickLineEdit.textChanged.connect(self._nick_changed)
-
- def _retranslate_ui(self):
- super()._retranslate_ui()
- self.setWindowTitle(util_ui.tr('Create new group chat'))
- self.groupNameLabel.setText(util_ui.tr('Group name:'))
- self.groupTypeLabel.setText(util_ui.tr('Group type:'))
- self.groupNameLineEdit.setPlaceholderText(util_ui.tr('Group\'s persistent name'))
- self.addGroupButton.setText(util_ui.tr('Create group'))
- self.groupTypeComboBox.addItem(util_ui.tr('Public'))
- self.groupTypeComboBox.addItem(util_ui.tr('Private'))
- self.groupTypeComboBox.setCurrentIndex(1)
-
- def _create_group(self):
- group_name = self.groupNameLineEdit.text()
- privacy_state = self.groupTypeComboBox.currentIndex()
- nick = self.nickLineEdit.text()
- status = self.statusComboBox.currentIndex()
- self._groups_service.create_new_gc(group_name, privacy_state, nick, status)
- self.close()
-
- def _nick_changed(self):
- self._update_button_state()
-
- def _group_name_changed(self):
- self._update_button_state()
-
- def _update_button_state(self):
- is_nick_set = bool(self.nickLineEdit.text())
- is_group_name_set = bool(self.groupNameLineEdit.text())
- self.addGroupButton.setEnabled(is_nick_set and is_group_name_set)
-
-
-class JoinGroupScreen(BaseGroupScreen):
-
- def __init__(self, groups_service, profile):
- super().__init__(groups_service, profile)
- uic.loadUi(util.get_views_path('join_group_screen'), self)
- self.center()
- self._update_ui()
-
- def _update_ui(self):
- self._retranslate_ui()
-
- self.statusComboBox.setCurrentIndex(self._profile.status or 0)
- self.nickLineEdit.setText(self._profile.name)
-
- self.chatIdLineEdit.textChanged.connect(self._chat_id_changed)
- self.joinGroupButton.clicked.connect(self._join_group)
- self.nickLineEdit.textChanged.connect(self._nick_changed)
-
- def _retranslate_ui(self):
- super()._retranslate_ui()
- self.setWindowTitle(util_ui.tr('Join public group chat'))
- self.chatIdLabel.setText(util_ui.tr('Group ID:'))
- self.passwordLabel.setText(util_ui.tr('Password:'))
- self.chatIdLineEdit.setPlaceholderText(util_ui.tr('Group\'s chat ID'))
- self.joinGroupButton.setText(util_ui.tr('Join group'))
- self.passwordLineEdit.setPlaceholderText(util_ui.tr('Optional password'))
-
- def _chat_id_changed(self):
- self._update_button_state()
-
- def _nick_changed(self):
- self._update_button_state()
-
- def _update_button_state(self):
- chat_id = self._get_chat_id()
- is_nick_set = bool(self.nickLineEdit.text())
- self.joinGroupButton.setEnabled(len(chat_id) == TOX_GROUP_CHAT_ID_SIZE * 2 and is_nick_set)
-
- def _join_group(self):
- chat_id = self._get_chat_id()
- password = self.passwordLineEdit.text()
- nick = self.nickLineEdit.text()
- status = self.statusComboBox.currentIndex()
- self._groups_service.join_gc_by_id(chat_id, password, nick, status)
- self.close()
-
- def _get_chat_id(self):
- chat_id = self.chatIdLineEdit.text().strip()
- if chat_id.startswith('tox:'):
- chat_id = chat_id[4:]
-
- return chat_id
diff --git a/toxygen/ui/items_factories.py b/toxygen/ui/items_factories.py
deleted file mode 100644
index 7346f8f..0000000
--- a/toxygen/ui/items_factories.py
+++ /dev/null
@@ -1,90 +0,0 @@
-from ui.contact_items import *
-from ui.messages_widgets import *
-
-
-class ContactItemsFactory:
-
- def __init__(self, settings, main_screen):
- self._settings = settings
- self._friends_list = main_screen.friends_list
-
- def create_contact_item(self):
- item = ContactItem(self._settings)
- elem = QtWidgets.QListWidgetItem(self._friends_list)
- elem.setSizeHint(QtCore.QSize(250, 40 if self._settings['compact_mode'] else 70))
- self._friends_list.addItem(elem)
- self._friends_list.setItemWidget(elem, item)
-
- return item
-
-
-class MessagesItemsFactory:
-
- def __init__(self, settings, plugin_loader, smiley_loader, main_screen, delete_action):
- self._file_transfers_handler = None
- self._settings, self._plugin_loader = settings, plugin_loader
- self._smiley_loader, self._delete_action = smiley_loader, delete_action
- self._messages = main_screen.messages
- self._message_edit = main_screen.messageEdit
-
- def set_file_transfers_handler(self, file_transfers_handler):
- self._file_transfers_handler = file_transfers_handler
-
- def create_message_item(self, message, append=True, pixmap=None):
- item = message.get_widget(self._settings, self._create_message_browser,
- self._delete_action, 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 create_inline_item(self, message, append=True, position=0):
- elem = QtWidgets.QListWidgetItem()
- item = InlineImageItem(message.data, self._messages.width(), elem, self._messages)
- elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
- if append:
- self._messages.addItem(elem)
- else:
- self._messages.insertItem(position, elem)
- self._messages.setItemWidget(elem, item)
-
- return item
-
- def create_unsent_file_item(self, message, append=True):
- item = message.get_widget(self._file_transfers_handler, self._settings, self._messages.width(), self._messages)
- 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 create_file_transfer_item(self, message, append=True):
- item = message.get_widget(self._file_transfers_handler, self._settings, self._messages.width(), self._messages)
- 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
-
- # -----------------------------------------------------------------------------------------------------------------
- # Private methods
- # -----------------------------------------------------------------------------------------------------------------
-
- def _create_message_browser(self, text, width, message_type, parent=None):
- return MessageBrowser(self._settings, self._message_edit, self._smiley_loader, self._plugin_loader,
- text, width, message_type, parent)
diff --git a/toxygen/ui/login_screen.py b/toxygen/ui/login_screen.py
deleted file mode 100644
index 35e33b5..0000000
--- a/toxygen/ui/login_screen.py
+++ /dev/null
@@ -1,77 +0,0 @@
-from ui.widgets import *
-from PyQt5 import uic
-import utils.util as util
-import utils.ui as util_ui
-import os.path
-
-
-class LoginScreenResult:
-
- def __init__(self, profile_path, load_as_default, password=None):
- self._profile_path = profile_path
- self._load_as_default = load_as_default
- self._password = password
-
- def get_profile_path(self):
- return self._profile_path
-
- profile_path = property(get_profile_path)
-
- def get_load_as_default(self):
- return self._load_as_default
-
- load_as_default = property(get_load_as_default)
-
- def get_password(self):
- return self._password
-
- password = property(get_password)
-
- def is_new_profile(self):
- return not os.path.isfile(self._profile_path)
-
-
-class LoginScreen(CenteredWidget, DialogWithResult):
-
- def __init__(self):
- CenteredWidget.__init__(self)
- DialogWithResult.__init__(self)
- uic.loadUi(util.get_views_path('login_screen'), self)
- self.center()
- self._profiles = []
- self._update_ui()
-
- def update_select(self, profiles):
- profiles = sorted(profiles, key=lambda p: p[1])
- self._profiles = list(profiles)
- self.profilesComboBox.addItems(list(map(lambda p: p[1], profiles)))
- self.loadProfilePushButton.setEnabled(len(profiles) > 0)
-
- def _update_ui(self):
- self.profileNameLineEdit = LineEditWithEnterSupport(self._create_profile, self)
- self.profileNameLineEdit.setGeometry(QtCore.QRect(20, 100, 160, 30))
- self._retranslate_ui()
- self.createProfilePushButton.clicked.connect(self._create_profile)
- self.loadProfilePushButton.clicked.connect(self._load_existing_profile)
-
- def _create_profile(self):
- path = self.profileNameLineEdit.text()
- load_as_default = self.defaultProfileCheckBox.isChecked()
- result = LoginScreenResult(path, load_as_default)
- self.close_with_result(result)
-
- def _load_existing_profile(self):
- index = self.profilesComboBox.currentIndex()
- load_as_default = self.defaultProfileCheckBox.isChecked()
- path = util.join_path(self._profiles[index][0], self._profiles[index][1] + '.tox')
- result = LoginScreenResult(path, load_as_default)
- self.close_with_result(result)
-
- def _retranslate_ui(self):
- self.setWindowTitle(util_ui.tr('Log in'))
- self.profileNameLineEdit.setPlaceholderText(util_ui.tr('Profile name'))
- self.createProfilePushButton.setText(util_ui.tr('Create'))
- self.loadProfilePushButton.setText(util_ui.tr('Load profile'))
- self.defaultProfileCheckBox.setText(util_ui.tr('Use as default'))
- self.existingProfileGroupBox.setTitle(util_ui.tr('Load existing profile'))
- self.newProfileGroupBox.setTitle(util_ui.tr('Create new profile'))
diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py
deleted file mode 100644
index 5a510a5..0000000
--- a/toxygen/ui/main_screen.py
+++ /dev/null
@@ -1,718 +0,0 @@
-from ui.contact_items import *
-from ui.widgets import MultilineEdit
-from ui.main_screen_widgets import *
-import utils.util as util
-import utils.ui as util_ui
-from PyQt5 import uic
-
-
-class MainWindow(QtWidgets.QMainWindow):
-
- def __init__(self, settings, tray):
- super().__init__()
- self._settings = settings
- self._contacts_manager = None
- self._tray = tray
- self._widget_factory = None
- self._modal_window = None
- self._plugins_loader = None
- self.setAcceptDrops(True)
- self._saved = False
- self._smiley_window = None
- self._profile = self._toxes = self._messenger = None
- self._file_transfer_handler = self._history_loader = self._groups_service = self._calls_manager = None
- self._should_show_group_peers_list = False
- self.initUI()
-
- def set_dependencies(self, widget_factory, tray, contacts_manager, messenger, profile, plugins_loader,
- file_transfer_handler, history_loader, calls_manager, groups_service, toxes):
- self._widget_factory = widget_factory
- self._tray = tray
- self._contacts_manager = contacts_manager
- self._profile = profile
- self._plugins_loader = plugins_loader
- self._file_transfer_handler = file_transfer_handler
- self._history_loader = history_loader
- self._calls_manager = calls_manager
- self._groups_service = groups_service
- self._toxes = toxes
- self._messenger = messenger
- self._contacts_manager.active_contact_changed.add_callback(self._new_contact_selected)
- self.messageEdit.set_dependencies(messenger, contacts_manager, file_transfer_handler)
-
- self.update_gc_invites_button_state()
-
- def show(self):
- super().show()
- self._contacts_manager.update()
- if self._settings['show_welcome_screen']:
- self._modal_window = self._widget_factory.create_welcome_window()
-
- def setup_menu(self, window):
- self.menubar = QtWidgets.QMenuBar(window)
- self.menubar.setObjectName("menubar")
- self.menubar.setNativeMenuBar(False)
- self.menubar.setMinimumSize(self.width(), 25)
- self.menubar.setMaximumSize(self.width(), 25)
- self.menubar.setBaseSize(self.width(), 25)
- self.menuProfile = QtWidgets.QMenu(self.menubar)
-
- self.menuProfile = QtWidgets.QMenu(self.menubar)
- self.menuProfile.setObjectName("menuProfile")
- self.menuGC = QtWidgets.QMenu(self.menubar)
- self.menuSettings = QtWidgets.QMenu(self.menubar)
- self.menuSettings.setObjectName("menuSettings")
- self.menuPlugins = QtWidgets.QMenu(self.menubar)
- self.menuPlugins.setObjectName("menuPlugins")
- self.menuAbout = QtWidgets.QMenu(self.menubar)
- self.menuAbout.setObjectName("menuAbout")
-
- self.actionAdd_friend = QtWidgets.QAction(window)
- self.actionAdd_friend.setObjectName("actionAdd_friend")
- self.actionprofilesettings = QtWidgets.QAction(window)
- self.actionprofilesettings.setObjectName("actionprofilesettings")
- self.actionPrivacy_settings = QtWidgets.QAction(window)
- self.actionPrivacy_settings.setObjectName("actionPrivacy_settings")
- self.actionInterface_settings = QtWidgets.QAction(window)
- self.actionInterface_settings.setObjectName("actionInterface_settings")
- self.actionNotifications = QtWidgets.QAction(window)
- self.actionNotifications.setObjectName("actionNotifications")
- self.actionNetwork = QtWidgets.QAction(window)
- self.actionNetwork.setObjectName("actionNetwork")
- self.actionAbout_program = QtWidgets.QAction(window)
- self.actionAbout_program.setObjectName("actionAbout_program")
- self.updateSettings = QtWidgets.QAction(window)
- self.actionSettings = QtWidgets.QAction(window)
- self.actionSettings.setObjectName("actionSettings")
- self.audioSettings = QtWidgets.QAction(window)
- self.videoSettings = QtWidgets.QAction(window)
- self.pluginData = QtWidgets.QAction(window)
- self.importPlugin = QtWidgets.QAction(window)
- self.reloadPlugins = QtWidgets.QAction(window)
- self.lockApp = QtWidgets.QAction(window)
- self.createGC = QtWidgets.QAction(window)
- self.joinGC = QtWidgets.QAction(window)
- self.gc_invites = QtWidgets.QAction(window)
-
- self.menuProfile.addAction(self.actionAdd_friend)
- self.menuProfile.addAction(self.actionSettings)
- self.menuProfile.addAction(self.lockApp)
- self.menuGC.addAction(self.createGC)
- self.menuGC.addAction(self.joinGC)
- self.menuGC.addAction(self.gc_invites)
- self.menuSettings.addAction(self.actionPrivacy_settings)
- self.menuSettings.addAction(self.actionInterface_settings)
- 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.menuGC.menuAction())
- self.menubar.addAction(self.menuSettings.menuAction())
- self.menubar.addAction(self.menuPlugins.menuAction())
- self.menubar.addAction(self.menuAbout.menuAction())
-
- self.actionAbout_program.triggered.connect(self.about_program)
- self.actionNetwork.triggered.connect(self.network_settings)
- self.actionAdd_friend.triggered.connect(self.add_contact_triggered)
- self.createGC.triggered.connect(self.create_gc)
- self.joinGC.triggered.connect(self.join_gc)
- self.actionSettings.triggered.connect(self.profile_settings)
- self.actionPrivacy_settings.triggered.connect(self.privacy_settings)
- self.actionInterface_settings.triggered.connect(self.interface_settings)
- self.actionNotifications.triggered.connect(self.notification_settings)
- self.audioSettings.triggered.connect(self.audio_settings)
- self.videoSettings.triggered.connect(self.video_settings)
- self.updateSettings.triggered.connect(self.update_settings)
- self.pluginData.triggered.connect(self.plugins_menu)
- self.lockApp.triggered.connect(self.lock_app)
- self.importPlugin.triggered.connect(self.import_plugin)
- self.reloadPlugins.triggered.connect(self.reload_plugins)
- self.gc_invites.triggered.connect(self._open_gc_invites_list)
-
- def languageChange(self, *args, **kwargs):
- self.retranslateUi()
-
- def event(self, event):
- if event.type() == QtCore.QEvent.WindowActivate:
- self._tray.setIcon(QtGui.QIcon(util.join_path(util.get_images_directory(), 'icon.png')))
- self.messages.repaint()
- return super().event(event)
-
- def retranslateUi(self):
- self.lockApp.setText(util_ui.tr("Lock"))
- self.menuPlugins.setTitle(util_ui.tr("Plugins"))
- self.menuGC.setTitle(util_ui.tr("Group chats"))
- self.pluginData.setText(util_ui.tr("List of plugins"))
- self.menuProfile.setTitle(util_ui.tr("Profile"))
- self.menuSettings.setTitle(util_ui.tr("Settings"))
- self.menuAbout.setTitle(util_ui.tr("About"))
- self.actionAdd_friend.setText(util_ui.tr("Add contact"))
- self.createGC.setText(util_ui.tr("Create group chat"))
- self.joinGC.setText(util_ui.tr("Join group chat"))
- self.gc_invites.setText(util_ui.tr("Group invites"))
- self.actionprofilesettings.setText(util_ui.tr("Profile"))
- self.actionPrivacy_settings.setText(util_ui.tr("Privacy"))
- self.actionInterface_settings.setText(util_ui.tr("Interface"))
- self.actionNotifications.setText(util_ui.tr("Notifications"))
- self.actionNetwork.setText(util_ui.tr("Network"))
- self.actionAbout_program.setText(util_ui.tr("About program"))
- self.actionSettings.setText(util_ui.tr("Settings"))
- self.audioSettings.setText(util_ui.tr("Audio"))
- self.videoSettings.setText(util_ui.tr("Video"))
- self.updateSettings.setText(util_ui.tr("Updates"))
- self.importPlugin.setText(util_ui.tr("Import plugin"))
- self.reloadPlugins.setText(util_ui.tr("Reload plugins"))
-
- self.searchLineEdit.setPlaceholderText(util_ui.tr("Search"))
- self.sendMessageButton.setToolTip(util_ui.tr("Send message"))
- self.callButton.setToolTip(util_ui.tr("Start audio call with friend"))
- self.contactsFilterComboBox.clear()
- self.contactsFilterComboBox.addItem(util_ui.tr("All"))
- self.contactsFilterComboBox.addItem(util_ui.tr("Online"))
- self.contactsFilterComboBox.addItem(util_ui.tr("Online first"))
- self.contactsFilterComboBox.addItem(util_ui.tr("Name"))
- self.contactsFilterComboBox.addItem(util_ui.tr("Online and by name"))
- self.contactsFilterComboBox.addItem(util_ui.tr("Online first and by name"))
-
- def setup_right_bottom(self, Form):
- Form.resize(650, 60)
- self.messageEdit = MessageArea(Form, self)
- self.messageEdit.setGeometry(QtCore.QRect(0, 3, 450, 55))
- font = QtGui.QFont()
- font.setPointSize(11)
- font.setFamily(self._settings['font'])
- self.messageEdit.setFont(font)
-
- self.sendMessageButton = QtWidgets.QPushButton(Form)
- self.sendMessageButton.setGeometry(QtCore.QRect(565, 3, 60, 55))
-
- self.menuButton = MenuButton(Form, self.show_menu)
- self.menuButton.setGeometry(QtCore.QRect(QtCore.QRect(455, 3, 55, 55)))
-
- pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'send.png'))
- icon = QtGui.QIcon(pixmap)
- self.sendMessageButton.setIcon(icon)
- self.sendMessageButton.setIconSize(QtCore.QSize(45, 60))
-
- pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'menu.png'))
- icon = QtGui.QIcon(pixmap)
- self.menuButton.setIcon(icon)
- self.menuButton.setIconSize(QtCore.QSize(40, 40))
-
- self.sendMessageButton.clicked.connect(self.send_message)
-
- QtCore.QMetaObject.connectSlotsByName(Form)
-
- def setup_left_column(self, left_column):
- uic.loadUi(util.get_views_path('ms_left_column'), left_column)
-
- pixmap = QtGui.QPixmap()
- pixmap.load(util.join_path(util.get_images_directory(), 'search.png'))
- left_column.searchLabel.setPixmap(pixmap)
-
- self.name = DataLabel(left_column)
- self.name.setGeometry(QtCore.QRect(75, 15, 150, 25))
- font = QtGui.QFont()
- font.setFamily(self._settings['font'])
- font.setPointSize(14)
- font.setBold(True)
- self.name.setFont(font)
-
- self.status_message = DataLabel(left_column)
- self.status_message.setGeometry(QtCore.QRect(75, 35, 170, 25))
-
- self.connection_status = StatusCircle(left_column)
- self.connection_status.setGeometry(QtCore.QRect(230, 10, 32, 32))
-
- left_column.contactsFilterComboBox.activated[int].connect(lambda x: self._filtering())
-
- self.avatar_label = left_column.avatarLabel
- self.searchLineEdit = left_column.searchLineEdit
- self.contacts_filter = self.contactsFilterComboBox = left_column.contactsFilterComboBox
-
- self.groupInvitesPushButton = left_column.groupInvitesPushButton
-
- self.groupInvitesPushButton.clicked.connect(self._open_gc_invites_list)
- self.avatar_label.mouseReleaseEvent = self.profile_settings
- self.status_message.mouseReleaseEvent = self.profile_settings
- self.name.mouseReleaseEvent = self.profile_settings
-
- self.friends_list = left_column.friendsListWidget
- self.friends_list.itemSelectionChanged.connect(self._selected_contact_changed)
- self.friends_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
- 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_top(self, Form):
- 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, 0, 400, 25))
- self.account_name.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse)
- font = QtGui.QFont()
- font.setFamily(self._settings['font'])
- font.setPointSize(14)
- font.setBold(True)
- self.account_name.setFont(font)
- self.account_status = DataLabel(Form)
- 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 = QtWidgets.QPushButton(Form)
- self.callButton.setGeometry(QtCore.QRect(550, 5, 50, 50))
- self.callButton.setObjectName("callButton")
- self.callButton.clicked.connect(lambda: self._calls_manager.call_click(True))
- self.videocallButton = QtWidgets.QPushButton(Form)
- self.videocallButton.setGeometry(QtCore.QRect(550, 5, 50, 50))
- self.videocallButton.setObjectName("videocallButton")
- self.videocallButton.clicked.connect(lambda: self._calls_manager.call_click(True, True))
- self.groupMenuButton = QtWidgets.QPushButton(Form)
- self.groupMenuButton.setGeometry(QtCore.QRect(470, 10, 50, 50))
- self.groupMenuButton.clicked.connect(self._toggle_gc_peers_list)
- self.groupMenuButton.setVisible(False)
- pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'menu.png'))
- icon = QtGui.QIcon(pixmap)
- self.groupMenuButton.setIcon(icon)
- self.groupMenuButton.setIconSize(QtCore.QSize(45, 60))
- self.update_call_state('call')
- self.typing = QtWidgets.QLabel(Form)
- self.typing.setGeometry(QtCore.QRect(500, 25, 50, 30))
- pixmap = QtGui.QPixmap(QtCore.QSize(50, 30))
- pixmap.load(util.join_path(util.get_images_directory(), 'typing.png'))
- self.typing.setScaledContents(False)
- self.typing.setPixmap(pixmap.scaled(50, 30, QtCore.Qt.KeepAspectRatio))
- self.typing.setVisible(False)
- QtCore.QMetaObject.connectSlotsByName(Form)
-
- def setup_right_center(self, 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.focusOutEvent = lambda event: self.messages.clearSelection()
- self.messages.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu)
-
- def load(pos):
- if not pos:
- contact = self._contacts_manager.get_curr_contact()
- self._history_loader.load_history(contact)
- self.messages.verticalScrollBar().setValue(1)
- self.messages.verticalScrollBar().valueChanged.connect(load)
- self.messages.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
- self.messages.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
-
- self.peers_list = QtWidgets.QListWidget(widget)
- self.peers_list.setGeometry(0, 0, 0, 0)
- self.peers_list.setObjectName("peersList")
- self.peers_list.setSpacing(1)
- self.peers_list.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
- self.peers_list.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
- self.peers_list.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu)
- self.peers_list.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
-
- def initUI(self):
- self.setMinimumSize(920, 500)
- s = self._settings
- self.setGeometry(s['x'], s['y'], s['width'], s['height'])
- self.setWindowTitle('Toxygen')
- menu = QtWidgets.QWidget()
- main = QtWidgets.QWidget()
- grid = QtWidgets.QGridLayout()
- info = QtWidgets.QWidget()
- left_column = QtWidgets.QWidget()
- messages = QtWidgets.QWidget()
- message_buttons = QtWidgets.QWidget()
- self.setup_right_center(messages)
- self.setup_right_top(info)
- self.setup_right_bottom(message_buttons)
- self.setup_left_column(left_column)
- self.setup_menu(menu)
- if not s['mirror_mode']:
- grid.addWidget(left_column, 1, 0, 4, 1)
- grid.addWidget(messages, 2, 1, 2, 1)
- grid.addWidget(info, 1, 1)
- grid.addWidget(message_buttons, 4, 1)
- grid.setColumnMinimumWidth(1, 500)
- grid.setColumnMinimumWidth(0, 270)
- else:
- grid.addWidget(left_column, 1, 1, 4, 1)
- grid.addWidget(messages, 2, 0, 2, 1)
- grid.addWidget(info, 1, 0)
- grid.addWidget(message_buttons, 4, 0)
- 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, 25)
- grid.setRowMinimumHeight(1, 75)
- grid.setRowMinimumHeight(2, 25)
- grid.setRowMinimumHeight(3, 320)
- grid.setRowMinimumHeight(4, 55)
- grid.setColumnStretch(1, 1)
- grid.setRowStretch(3, 1)
- main.setLayout(grid)
- self.setCentralWidget(main)
- self.messageEdit.setFocus()
- self.friend_info = info
- self.retranslateUi()
-
- def closeEvent(self, event):
- close_setting = self._settings['close_app']
- if close_setting == 0 or self._settings.closing:
- if self._saved:
- return
- self._saved = True
- self._settings['x'] = self.geometry().x()
- self._settings['y'] = self.geometry().y()
- self._settings['width'] = self.width()
- self._settings['height'] = self.height()
- self._settings.save()
- util_ui.close_all_windows()
- event.accept()
- elif close_setting == 2 and QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
- event.ignore()
- self.hide()
- else:
- event.ignore()
- self.showMinimized()
-
- def close_window(self):
- self._settings.closing = True
- self.close()
-
- def resizeEvent(self, *args, **kwargs):
- width = self.width() - 270
- if not self._should_show_group_peers_list:
- self.messages.setGeometry(0, 0, width, self.height() - 155)
- self.peers_list.setGeometry(0, 0, 0, 0)
- else:
- self.messages.setGeometry(0, 0, width * 3 // 4, self.height() - 155)
- self.peers_list.setGeometry(width * 3 // 4, 0, width - width * 3 // 4, self.height() - 155)
-
- invites_button_visible = self.groupInvitesPushButton.isVisible()
- self.friends_list.setGeometry(0, 125 if invites_button_visible else 100,
- 270, self.height() - 150 if invites_button_visible else self.height() - 125)
-
- self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 10, 50, 50))
- self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 10, 50, 50))
- self.groupMenuButton.setGeometry(QtCore.QRect(self.width() - 450, 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, 15, self.width() - 560, 25))
- self.account_status.setGeometry(QtCore.QRect(100, 35, self.width() - 560, 25))
- self.messageEdit.setFocus()
-
- def keyPressEvent(self, event):
- key, modifiers = event.key(), event.modifiers()
- if key == QtCore.Qt.Key_Escape and QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
- self.hide()
- elif key == QtCore.Qt.Key_C and 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._history_loader.export_history(self._contacts_manager.get_curr_friend(), True, indexes)
- self.copy_text(s)
- elif key == QtCore.Qt.Key_Z and modifiers & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
- self.messages.clearSelection()
- elif key == QtCore.Qt.Key_F and modifiers & QtCore.Qt.ControlModifier:
- self.show_search_field()
- else:
- super().keyPressEvent(event)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Functions which called when user click in menu
- # -----------------------------------------------------------------------------------------------------------------
-
- def about_program(self):
- # TODO: replace with window
- text = util_ui.tr('Toxygen is Tox client written on Python.\nVersion: ')
- text += '' + '\nGitHub: https://github.com/toxygen-project/toxygen/'
- title = util_ui.tr('About')
- util_ui.message_box(text, title)
-
- def network_settings(self):
- self._modal_window = self._widget_factory.create_network_settings_window()
- self._modal_window.show()
-
- def plugins_menu(self):
- self._modal_window = self._widget_factory.create_plugins_settings_window()
- self._modal_window.show()
-
- def add_contact_triggered(self, _):
- self.add_contact()
-
- def add_contact(self, link=''):
- self._modal_window = self._widget_factory.create_add_contact_window(link)
- self._modal_window.show()
-
- def create_gc(self):
- self._modal_window = self._widget_factory.create_group_screen_window()
- self._modal_window.show()
-
- def join_gc(self):
- self._modal_window = self._widget_factory.create_join_group_screen_window()
- self._modal_window.show()
-
- def profile_settings(self, _):
- self._modal_window = self._widget_factory.create_profile_settings_window()
- self._modal_window.show()
-
- def privacy_settings(self):
- self._modal_window = self._widget_factory.create_privacy_settings_window()
- self._modal_window.show()
-
- def notification_settings(self):
- self._modal_window = self._widget_factory.create_notification_settings_window()
- self._modal_window.show()
-
- def interface_settings(self):
- self._modal_window = self._widget_factory.create_interface_settings_window()
- self._modal_window.show()
-
- def audio_settings(self):
- self._modal_window = self._widget_factory.create_audio_settings_window()
- self._modal_window.show()
-
- def video_settings(self):
- self._modal_window = self._widget_factory.create_video_settings_window()
- self._modal_window.show()
-
- def update_settings(self):
- self._modal_window = self._widget_factory.create_update_settings_window()
- self._modal_window.show()
-
- def reload_plugins(self):
- if self._plugin_loader is not None:
- self._plugin_loader.reload()
-
- @staticmethod
- def import_plugin():
- directory = util_ui.directory_dialog(util_ui.tr('Choose folder with plugin'))
- if directory:
- src = directory + '/'
- dest = util.get_plugins_directory()
- util.copy(src, dest)
- util_ui.message_box(util_ui.tr('Plugin will be loaded after restart'), util_ui.tr("Restart Toxygen"))
-
- def lock_app(self):
- if self._toxes.has_password():
- self._settings.locked = True
- self.hide()
- else:
- util_ui.message_box(util_ui.tr('Error. Profile password is not set.'), util_ui.tr("Cannot lock app"))
-
- def show_menu(self):
- if not hasattr(self, 'menu'):
- self.menu = DropdownMenu(self)
- self.menu.setGeometry(QtCore.QRect(0 if self._settings['mirror_mode'] else 270,
- self.height() - 120,
- 180,
- 120))
- self.menu.show()
-
- # -----------------------------------------------------------------------------------------------------------------
- # Messages, calls and file transfers
- # -----------------------------------------------------------------------------------------------------------------
-
- def send_message(self):
- self._messenger.send_message()
-
- def send_file(self):
- self.menu.hide()
- if self._contacts_manager.is_active_a_friend():
- caption = util_ui.tr('Choose file')
- name = util_ui.file_dialog(caption)
- if name[0]:
- self._file_transfer_handler.send_file(name[0], self._contacts_manager.get_active_number())
-
- def send_screenshot(self, hide=False):
- self.menu.hide()
- if self._contacts_manager.is_active_a_friend():
- self.sw = self._widget_factory.create_screenshot_window(self)
- self.sw.show()
- if hide:
- self.hide()
-
- def send_smiley(self):
- self.menu.hide()
- if self._contacts_manager.get_curr_contact() is None:
- return
- self._smiley_window = self._widget_factory.create_smiley_window(self)
- rect = QtCore.QRect(self.menu.x(),
- self.menu.y() - self.menu.height(),
- self._smiley_window.width(),
- self._smiley_window.height())
- self._smiley_window.setGeometry(rect)
- self._smiley_window.show()
-
- def send_sticker(self):
- self.menu.hide()
- if self._contacts_manager.is_active_a_friend():
- self.sticker = self._widget_factory.create_sticker_window()
- self.sticker.setGeometry(QtCore.QRect(self.x() if self._settings['mirror_mode'] else 270 + self.x(),
- self.y() + self.height() - 200,
- self.sticker.width(),
- self.sticker.height()))
- self.sticker.show()
-
- def active_call(self):
- self.update_call_state('finish_call')
-
- def incoming_call(self):
- self.update_call_state('incoming_call')
-
- def call_finished(self):
- self.update_call_state('call')
-
- def update_call_state(self, state):
- pixmap = QtGui.QPixmap(os.path.join(util.get_images_directory(), '{}.png'.format(state)))
- icon = QtGui.QIcon(pixmap)
- self.callButton.setIcon(icon)
- self.callButton.setIconSize(QtCore.QSize(50, 50))
-
- pixmap = QtGui.QPixmap(os.path.join(util.get_images_directory(), '{}_video.png'.format(state)))
- icon = QtGui.QIcon(pixmap)
- self.videocallButton.setIcon(icon)
- self.videocallButton.setIconSize(QtCore.QSize(35, 35))
-
- # -----------------------------------------------------------------------------------------------------------------
- # Functions which called when user open context menu in friends list
- # -----------------------------------------------------------------------------------------------------------------
-
- def _friend_right_click(self, pos):
- item = self.friends_list.itemAt(pos)
- number = self.friends_list.indexFromItem(item).row()
- contact = self._contacts_manager.get_contact(number)
- if contact is None or item is None:
- return
- generator = contact.get_context_menu_generator()
- self.listMenu = generator.generate(self._plugins_loader, self._contacts_manager, self, self._settings, number,
- self._groups_service, self._history_loader)
- parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0))
- self.listMenu.move(parent_position + pos)
- self.listMenu.show()
-
- def show_note(self, friend):
- note = self._settings['notes'][friend.tox_id] if friend.tox_id in self._settings['notes'] else ''
- user = util_ui.tr('Notes about user')
- user = '{} {}'.format(user, friend.name)
-
- def save_note(text):
- if friend.tox_id in self._settings['notes']:
- del self._settings['notes'][friend.tox_id]
- if text:
- self._settings['notes'][friend.tox_id] = text
- self._settings.save()
- self.note = MultilineEdit(user, note, save_note)
- self.note.show()
-
- def set_alias(self, num):
- self._contacts_manager.set_alias(num)
-
- def remove_friend(self, num):
- self._contacts_manager.delete_friend(num)
-
- def block_friend(self, num):
- friend = self._contacts_manager.get_contact(num)
- self._contacts_manager.block_user(friend.tox_id)
-
- @staticmethod
- def copy_text(text):
- util_ui.copy_to_clipboard(text)
-
- def auto_accept(self, num, value):
- tox_id = self._contacts_manager.friend_public_key(num)
- if value:
- self._settings['auto_accept_from_friends'].append(tox_id)
- else:
- self._settings['auto_accept_from_friends'].remove(tox_id)
- self._settings.save()
-
- def invite_friend_to_gc(self, friend_number, group_number):
- self._contacts_manager.invite_friend(friend_number, group_number)
-
- def select_contact_row(self, row_index):
- self.friends_list.setCurrentRow(row_index)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Functions which called when user click somewhere else
- # -----------------------------------------------------------------------------------------------------------------
-
- def _selected_contact_changed(self):
- num = self.friends_list.currentRow()
- if self._contacts_manager.active_contact != num:
- self._contacts_manager.active_contact = num
- self.groupMenuButton.setVisible(self._contacts_manager.is_active_a_group())
-
- def mouseReleaseEvent(self, event):
- pos = self.connection_status.pos()
- x, y = pos.x(), pos.y() + 25
- if (x < event.x() < x + 32) and (y < event.y() < y + 32):
- self._profile.change_status()
- else:
- super().mouseReleaseEvent(event)
-
- def _filtering(self):
- index = self.contactsFilterComboBox.currentIndex()
- search_text = self.searchLineEdit.text()
- self._contacts_manager.filtration_and_sorting(index, search_text)
-
- def show_search_field(self):
- if hasattr(self, 'search_field') and self.search_field.isVisible():
- return
- if self._contacts_manager.get_curr_friend() is None:
- return
- self.search_field = self._widget_factory.create_search_screen(self.messages)
- 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)
- if self._should_show_group_peers_list:
- self.peers_list.setFixedHeight(self.peers_list.height() - 40)
- self.search_field.show()
-
- def _toggle_gc_peers_list(self):
- self._should_show_group_peers_list = not self._should_show_group_peers_list
- self.resizeEvent()
- if self._should_show_group_peers_list:
- self._groups_service.generate_peers_list()
-
- def _new_contact_selected(self, _):
- if self._should_show_group_peers_list:
- self._toggle_gc_peers_list()
- index = self.friends_list.currentRow()
- if self._contacts_manager.active_contact != index:
- self.friends_list.setCurrentRow(self._contacts_manager.active_contact)
- self.resizeEvent()
-
- def _open_gc_invites_list(self):
- self._modal_window = self._widget_factory.create_group_invites_window()
- self._modal_window.show()
-
- def update_gc_invites_button_state(self):
- invites_count = self._groups_service.group_invites_count
- self.groupInvitesPushButton.setVisible(invites_count > 0)
- text = util_ui.tr('{} new invites to group chats').format(invites_count)
- self.groupInvitesPushButton.setText(text)
- self.resizeEvent()
diff --git a/toxygen/ui/main_screen_widgets.py b/toxygen/ui/main_screen_widgets.py
deleted file mode 100644
index 122561b..0000000
--- a/toxygen/ui/main_screen_widgets.py
+++ /dev/null
@@ -1,496 +0,0 @@
-from PyQt5 import QtCore, QtGui, QtWidgets
-from ui.widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit
-import urllib
-import re
-import utils.util as util
-import utils.ui as util_ui
-from stickers.stickers import load_stickers
-
-
-class MessageArea(QtWidgets.QPlainTextEdit):
- """User types messages here"""
-
- def __init__(self, parent, form):
- super().__init__(parent)
- self._messenger = self._contacts_manager = self._file_transfer_handler = None
- self.parent = form
- self.setAcceptDrops(True)
- self._timer = QtCore.QTimer(self)
- self._timer.timeout.connect(lambda: self._messenger.send_typing(False))
-
- def set_dependencies(self, messenger, contacts_manager, file_transfer_handler):
- self._messenger = messenger
- self._contacts_manager = contacts_manager
- self._file_transfer_handler = file_transfer_handler
-
- 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()
- if modifiers & QtCore.Qt.ControlModifier or modifiers & QtCore.Qt.ShiftModifier:
- self.insertPlainText('\n')
- else:
- if self._timer.isActive():
- self._timer.stop()
- self._messenger.send_typing(False)
- self._messenger.send_message()
- elif event.key() == QtCore.Qt.Key_Up and not self.toPlainText():
- self.appendPlainText(self._messenger.get_last_message())
- elif event.key() == QtCore.Qt.Key_Tab and self._contacts_manager.is_active_a_group():
- text = self.toPlainText()
- text_cursor = self.textCursor()
- pos = text_cursor.position()
- current_word = re.split("\s+", text[:pos])[-1]
- start_index = text.rindex(current_word, 0, pos)
- peer_name = self._contacts_manager.get_gc_peer_name(current_word)
- self.setPlainText(text[:start_index] + peer_name + text[pos:])
- new_pos = start_index + len(peer_name)
- text_cursor.setPosition(new_pos, QtGui.QTextCursor.MoveAnchor)
- self.setTextCursor(text_cursor)
- else:
- self._messenger.send_typing(True)
- if self._timer.isActive():
- self._timer.stop()
- self._timer.start(5000)
- super().keyPressEvent(event)
-
- def contextMenuEvent(self, event):
- menu = create_menu(self.createStandardContextMenu())
- menu.exec_(event.globalPos())
- del menu
-
- def dragEnterEvent(self, e):
- e.accept()
-
- def dragMoveEvent(self, e):
- e.accept()
-
- def dropEvent(self, e):
- if e.mimeData().hasFormat('text/plain') 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 QtWidgets.QApplication.clipboard().text()
- if text.startswith('file://'):
- if not self._contacts_manager.is_active_a_friend():
- return
- friend_number = self._contacts_manager.get_active_number()
- file_path = self._parse_file_path(text)
- self._file_transfer_handler.send_file(file_path, friend_number)
- else:
- self.insertPlainText(text)
-
- @staticmethod
- def _parse_file_path(file_name):
- if file_name.endswith('\r\n'):
- file_name = file_name[:-2]
- file_name = urllib.parse.unquote(file_name)
-
- return file_name[8 if util.get_platform() == 'Windows' else 7:]
-
-
-class ScreenShotWindow(RubberBandWindow):
-
- def __init__(self, file_transfer_handler, contacts_manager, *args):
- super().__init__(*args)
- self._file_transfer_handler = file_transfer_handler
- self._contacts_manager = contacts_manager
-
- def closeEvent(self, *args):
- if self.parent.isHidden():
- self.parent.show()
-
- def mouseReleaseEvent(self, event):
- if self.rubberband.isVisible():
- self.rubberband.hide()
- rect = self.rubberband.geometry()
- if rect.width() and rect.height():
- screen = QtWidgets.QApplication.primaryScreen()
- p = screen.grabWindow(0,
- rect.x() + 4,
- rect.y() + 4,
- rect.width() - 8,
- rect.height() - 8)
- byte_array = QtCore.QByteArray()
- buffer = QtCore.QBuffer(byte_array)
- buffer.open(QtCore.QIODevice.WriteOnly)
- p.save(buffer, 'PNG')
- friend = self._contacts_manager.get_curr_contact()
- self._file_transfer_handler.send_screenshot(bytes(byte_array.data()), friend.number)
- self.close()
-
-
-class SmileyWindow(QtWidgets.QWidget):
- """
- Smiley selection window
- """
-
- def __init__(self, parent, smiley_loader):
- super().__init__(parent)
- self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
- self._parent = parent
- self._data = smiley_loader.get_smileys()
-
- count = len(self._data)
- if not count:
- self.close()
-
- self._page_size = int(pow(count / 8, 0.5) + 1) * 8 # smileys per page
- if count % self._page_size == 0:
- self._page_count = count // self._page_size
- else:
- self._page_count = round(count / self._page_size + 0.5)
- self._page = -1
- self._radio = []
-
- for i in range(self._page_count): # pages - radio buttons
- elem = QtWidgets.QRadioButton(self)
- elem.setGeometry(QtCore.QRect(i * 20 + 5, 160, 20, 20))
- elem.clicked.connect(lambda c, t=i: self._checked(t))
- self._radio.append(elem)
-
- width = max(self._page_count * 20 + 30, (self._page_size + 5) * 8 // 10)
- self.setMaximumSize(width, 200)
- self.setMinimumSize(width, 200)
- self._buttons = []
-
- for i in range(self._page_size): # buttons with smileys
- b = QtWidgets.QPushButton(self)
- b.setGeometry(QtCore.QRect((i // 8) * 20 + 5, (i % 8) * 20, 20, 20))
- b.clicked.connect(lambda c, t=i: self._clicked(t))
- self._buttons.append(b)
- self._checked(0)
-
- def leaveEvent(self, event):
- self.close()
-
- def _checked(self, pos): # new page opened
- self._radio[self._page].setChecked(False)
- self._radio[pos].setChecked(True)
- self._page = pos
- start = self._page * self._page_size
- for i in range(self._page_size):
- try:
- self._buttons[i].setVisible(True)
- pixmap = QtGui.QPixmap(self._data[start + i][1])
- icon = QtGui.QIcon(pixmap)
- self._buttons[i].setIcon(icon)
- except:
- self._buttons[i].setVisible(False)
-
- def _clicked(self, pos): # smiley selected
- pos += self._page * self._page_size
- smiley = self._data[pos][0]
- self._parent.messageEdit.insertPlainText(smiley)
- self.close()
-
-
-class MenuButton(QtWidgets.QPushButton):
-
- def __init__(self, parent, enter):
- super().__init__(parent)
- self.enter = enter
-
- def enterEvent(self, event):
- self.enter()
- super().enterEvent(event)
-
-
-class DropdownMenu(QtWidgets.QWidget):
-
- def __init__(self, parent):
- super().__init__(parent)
- self.installEventFilter(self)
- self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
- 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 = QtWidgets.QPushButton(self)
- self.fileTransferButton.setGeometry(QtCore.QRect(60, 60, 60, 60))
- self.fileTransferButton.setObjectName("fileTransferButton")
-
- self.smileyButton = QtWidgets.QPushButton(self)
- self.smileyButton.setGeometry(QtCore.QRect(0, 0, 60, 60))
-
- self.stickerButton = QtWidgets.QPushButton(self)
- self.stickerButton.setGeometry(QtCore.QRect(60, 0, 60, 60))
-
- pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'file.png'))
- icon = QtGui.QIcon(pixmap)
- self.fileTransferButton.setIcon(icon)
- self.fileTransferButton.setIconSize(QtCore.QSize(50, 50))
-
- pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'screenshot.png'))
- icon = QtGui.QIcon(pixmap)
- self.screenshotButton.setIcon(icon)
- self.screenshotButton.setIconSize(QtCore.QSize(50, 60))
-
- pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'smiley.png'))
- icon = QtGui.QIcon(pixmap)
- self.smileyButton.setIcon(icon)
- self.smileyButton.setIconSize(QtCore.QSize(50, 50))
-
- pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'sticker.png'))
- icon = QtGui.QIcon(pixmap)
- self.stickerButton.setIcon(icon)
- self.stickerButton.setIconSize(QtCore.QSize(55, 55))
-
- self.screenshotButton.setToolTip(util_ui.tr("Send screenshot"))
- self.fileTransferButton.setToolTip(util_ui.tr("Send file"))
- self.smileyButton.setToolTip(util_ui.tr("Add smiley"))
- self.stickerButton.setToolTip(util_ui.tr("Send sticker"))
-
- self.fileTransferButton.clicked.connect(parent.send_file)
- self.screenshotButton.clicked.connect(parent.send_screenshot)
- 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, obj, event):
- if event.type() == QtCore.QEvent.WindowDeactivate:
- self.close()
- return False
-
-
-class StickerItem(QtWidgets.QWidget):
-
- def __init__(self, fl):
- super().__init__()
- self._image_label = QtWidgets.QLabel(self)
- self.path = fl
- self.pixmap = QtGui.QPixmap()
- self.pixmap.load(fl)
- if self.pixmap.width() > 150:
- self.pixmap = self.pixmap.scaled(150, 200, QtCore.Qt.KeepAspectRatio)
- self.setFixedSize(150, self.pixmap.height())
- self._image_label.setPixmap(self.pixmap)
-
-
-class StickerWindow(QtWidgets.QWidget):
- """Sticker selection window"""
-
- def __init__(self, file_transfer_handler, contacts_manager):
- super().__init__()
- self._file_transfer_handler = file_transfer_handler
- self._contacts_manager = contacts_manager
- self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
- self.setMaximumSize(250, 200)
- self.setMinimumSize(250, 200)
- self.list = QtWidgets.QListWidget(self)
- self.list.setGeometry(QtCore.QRect(0, 0, 250, 200))
- self._stickers = load_stickers()
- for sticker in self._stickers:
- item = StickerItem(sticker)
- elem = QtWidgets.QListWidgetItem()
- elem.setSizeHint(QtCore.QSize(250, item.height()))
- self.list.addItem(elem)
- self.list.setItemWidget(elem, item)
- self.list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
- self.list.setSpacing(3)
- self.list.clicked.connect(self.click)
-
- def click(self, index):
- num = index.row()
- friend = self._contacts_manager.get_curr_contact()
- self._file_transfer_handler.send_sticker(self._stickers[num], friend.number)
- self.close()
-
- def leaveEvent(self, event):
- self.close()
-
-
-class WelcomeScreen(CenteredWidget):
-
- def __init__(self, settings):
- super().__init__()
- self._settings = settings
- self.setMaximumSize(250, 200)
- self.setMinimumSize(250, 200)
- self.center()
- self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
- self.text = QtWidgets.QTextBrowser(self)
- self.text.setGeometry(QtCore.QRect(0, 0, 250, 170))
- self.text.setOpenExternalLinks(True)
- self.checkbox = QtWidgets.QCheckBox(self)
- self.checkbox.setGeometry(QtCore.QRect(5, 170, 240, 30))
- self.checkbox.setText(util_ui.tr( "Don't show again"))
- self.setWindowTitle(util_ui.tr( 'Tip of the day'))
- import random
- num = random.randint(0, 10)
- if num == 0:
- text = util_ui.tr('Press Esc if you want hide app to tray.')
- elif num == 1:
- text = util_ui.tr('Right click on screenshot button hides app to tray during screenshot.')
- elif num == 2:
- text = util_ui.tr('You can use Tox over Tor. For more info read this post')
- elif num == 3:
- text = util_ui.tr('Use Settings -> Interface to customize interface.')
- elif num == 4:
- text = util_ui.tr('Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.')
- elif num == 5:
- text = util_ui.tr('Since v0.1.3 Toxygen supports plugins. Read more')
- elif num == 6:
- text = util_ui.tr('Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.')
- elif num == 7:
- text = util_ui.tr('New in Toxygen 0.4.1:
Downloading nodes from tox.chat
Bug fixes')
- elif num == 8:
- text = util_ui.tr('Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu')
- elif num == 9:
- text = util_ui.tr( 'Use right click on inline image to save it')
- else:
- text = util_ui.tr('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)
-
- def not_show(self):
- self._settings['show_welcome_screen'] = False
- self._settings.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, contacts_manager, history_loader, messages, width, *args):
- super().__init__(*args)
- self._contacts_manager = contacts_manager
- self._history_loader = history_loader
- 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.join_path(util.get_images_directory(), '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(util_ui.tr('Search'))
-
- def show(self):
- super().show()
- self.search_text.setFocus()
-
- def search(self):
- self._contacts_manager.update()
- text = self.search_text.text()
- contact = self._contacts_manager.get_curr_contact()
- if text and contact and util.is_re_valid(text):
- index = contact.search_string(text)
- self.load_messages(index)
-
- def prev(self):
- contact = self._contacts_manager.get_curr_contact()
- if contact is not None:
- index = contact.search_prev()
- self.load_messages(index)
-
- def next(self):
- contact = self._contacts_manager.get_curr_contact()
- text = self.search_text.text()
- if contact is not None:
- index = contact.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:
- count = self._messages.count()
- while count + index < 0:
- self._history_loader.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):
- self._messages.setGeometry(0, 0, self._messages.width(), self._messages.height() + 40)
- super().closeEvent(*args)
-
- @staticmethod
- def not_found(text):
- util_ui.message_box(util_ui.tr('Text "{}" was not found').format(text), util_ui.tr('Not found'))
diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py
deleted file mode 100644
index 8aec578..0000000
--- a/toxygen/ui/menu.py
+++ /dev/null
@@ -1,680 +0,0 @@
-from PyQt5 import QtCore, QtGui, QtWidgets, uic
-from user_data.settings import *
-from utils.util import *
-from ui.widgets import CenteredWidget, DataLabel, LineEdit, RubberBandWindow
-import pyaudio
-import updater.updater as updater
-import utils.ui as util_ui
-import cv2
-
-
-class AddContact(CenteredWidget):
- """Add contact form"""
-
- def __init__(self, settings, contacts_manager, tox_id=''):
- super().__init__()
- self._settings = settings
- self._contacts_manager = contacts_manager
- uic.loadUi(get_views_path('add_contact_screen'), self)
- self._update_ui(tox_id)
- self._adding = False
-
- def _update_ui(self, tox_id):
- self.toxIdLineEdit = LineEdit(self)
- self.toxIdLineEdit.setGeometry(QtCore.QRect(50, 40, 460, 30))
- self.toxIdLineEdit.setText(tox_id)
-
- self.messagePlainTextEdit.document().setPlainText(util_ui.tr('Hello! Please add me to your contact list.'))
- self.addContactPushButton.clicked.connect(self._add_friend)
- self._retranslate_ui()
-
- def _add_friend(self):
- if self._adding:
- return
- self._adding = True
- tox_id = self.toxIdLineEdit.text().strip()
- if tox_id.startswith('tox:'):
- tox_id = tox_id[4:]
- message = self.messagePlainTextEdit.toPlainText()
- send = self._contacts_manager.send_friend_request(tox_id, message)
- self._adding = False
- if send is True:
- # request was successful
- self.close()
- else: # print error data
- self.errorLabel.setText(send)
-
- def _retranslate_ui(self):
- self.setWindowTitle(util_ui.tr('Add contact'))
- self.addContactPushButton.setText(util_ui.tr('Send request'))
- self.toxIdLabel.setText(util_ui.tr('TOX ID:'))
- self.messageLabel.setText(util_ui.tr('Message:'))
- self.toxIdLineEdit.setPlaceholderText(util_ui.tr('TOX ID or public key of contact'))
-
-
-class NetworkSettings(CenteredWidget):
- """Network settings form: UDP, Ipv6 and proxy"""
- def __init__(self, settings, reset):
- super().__init__()
- self._settings = settings
- self._reset = reset
- uic.loadUi(get_views_path('network_settings_screen'), self)
- self._update_ui()
-
- def _update_ui(self):
- self.ipLineEdit = LineEdit(self)
- self.ipLineEdit.setGeometry(100, 280, 270, 30)
-
- self.portLineEdit = LineEdit(self)
- self.portLineEdit.setGeometry(100, 325, 270, 30)
-
- self.restartCorePushButton.clicked.connect(self._restart_core)
- self.ipv6CheckBox.setChecked(self._settings['ipv6_enabled'])
- self.udpCheckBox.setChecked(self._settings['udp_enabled'])
- self.proxyCheckBox.setChecked(self._settings['proxy_type'])
- self.ipLineEdit.setText(self._settings['proxy_host'])
- self.portLineEdit.setText(str(self._settings['proxy_port']))
- self.httpProxyRadioButton.setChecked(self._settings['proxy_type'] == 1)
- self.socksProxyRadioButton.setChecked(self._settings['proxy_type'] != 1)
- self.downloadNodesCheckBox.setChecked(self._settings['download_nodes_list'])
- self.lanCheckBox.setChecked(self._settings['lan_discovery'])
- self._retranslate_ui()
- self.proxyCheckBox.stateChanged.connect(lambda x: self._activate_proxy())
- self._activate_proxy()
-
- def _retranslate_ui(self):
- self.setWindowTitle(util_ui.tr("Network settings"))
- self.ipv6CheckBox.setText(util_ui.tr("IPv6"))
- self.udpCheckBox.setText(util_ui.tr("UDP"))
- self.lanCheckBox.setText(util_ui.tr("LAN"))
- self.proxyCheckBox.setText(util_ui.tr("Proxy"))
- self.ipLabel.setText(util_ui.tr("IP:"))
- self.portLabel.setText(util_ui.tr("Port:"))
- self.restartCorePushButton.setText(util_ui.tr("Restart TOX core"))
- self.httpProxyRadioButton.setText(util_ui.tr("HTTP"))
- self.socksProxyRadioButton.setText(util_ui.tr("Socks 5"))
- self.downloadNodesCheckBox.setText(util_ui.tr("Download nodes list from tox.chat"))
- self.warningLabel.setText(util_ui.tr("WARNING:\nusing proxy with enabled UDP\ncan produce IP leak"))
-
- def _activate_proxy(self):
- bl = self.proxyCheckBox.isChecked()
- self.ipLineEdit.setEnabled(bl)
- self.portLineEdit.setEnabled(bl)
- self.httpProxyRadioButton.setEnabled(bl)
- self.socksProxyRadioButton.setEnabled(bl)
- self.ipLabel.setEnabled(bl)
- self.portLabel.setEnabled(bl)
-
- def _restart_core(self):
- try:
- self._settings['ipv6_enabled'] = self.ipv6CheckBox.isChecked()
- self._settings['udp_enabled'] = self.udpCheckBox.isChecked()
- proxy_enabled = self.proxyCheckBox.isChecked()
- self._settings['proxy_type'] = 2 - int(self.httpProxyRadioButton.isChecked()) if proxy_enabled else 0
- self._settings['proxy_host'] = str(self.ipLineEdit.text())
- self._settings['proxy_port'] = int(self.portLineEdit.text())
- self._settings['download_nodes_list'] = self.downloadNodesCheckBox.isChecked()
- self._settings['lan_discovery'] = self.lanCheckBox.isChecked()
- self._settings.save()
- # recreate tox instance
- self._reset()
- self.close()
- except Exception as ex:
- log('Exception in restart: ' + str(ex))
-
-
-class PrivacySettings(CenteredWidget):
- """Privacy settings form: history, typing notifications"""
-
- def __init__(self, contacts_manager, settings):
- """
- :type contacts_manager: ContactsManager
- """
- super().__init__()
- self._contacts_manager = contacts_manager
- self._settings = settings
- self.initUI()
- self.center()
-
- def initUI(self):
- self.setObjectName("privacySettings")
- self.resize(370, 600)
- self.setMinimumSize(QtCore.QSize(370, 600))
- self.setMaximumSize(QtCore.QSize(370, 600))
- self.saveHistory = QtWidgets.QCheckBox(self)
- self.saveHistory.setGeometry(QtCore.QRect(10, 20, 350, 22))
- self.saveUnsentOnly = QtWidgets.QCheckBox(self)
- self.saveUnsentOnly.setGeometry(QtCore.QRect(10, 60, 350, 22))
-
- self.fileautoaccept = QtWidgets.QCheckBox(self)
- self.fileautoaccept.setGeometry(QtCore.QRect(10, 100, 350, 22))
-
- self.typingNotifications = QtWidgets.QCheckBox(self)
- self.typingNotifications.setGeometry(QtCore.QRect(10, 140, 350, 30))
- self.inlines = QtWidgets.QCheckBox(self)
- self.inlines.setGeometry(QtCore.QRect(10, 180, 350, 30))
- self.auto_path = QtWidgets.QLabel(self)
- self.auto_path.setGeometry(QtCore.QRect(10, 230, 350, 30))
- self.path = QtWidgets.QPlainTextEdit(self)
- self.path.setGeometry(QtCore.QRect(10, 265, 350, 45))
- self.change_path = QtWidgets.QPushButton(self)
- self.change_path.setGeometry(QtCore.QRect(10, 320, 350, 30))
- self.typingNotifications.setChecked(self._settings['typing_notifications'])
- self.fileautoaccept.setChecked(self._settings['allow_auto_accept'])
- self.saveHistory.setChecked(self._settings['save_history'])
- self.inlines.setChecked(self._settings['allow_inline'])
- self.saveUnsentOnly.setChecked(self._settings['save_unsent_only'])
- self.saveUnsentOnly.setEnabled(self._settings['save_history'])
- self.saveHistory.stateChanged.connect(self.update)
- self.path.setPlainText(self._settings['auto_accept_path'] or curr_directory())
- self.change_path.clicked.connect(self.new_path)
- self.block_user_label = QtWidgets.QLabel(self)
- self.block_user_label.setGeometry(QtCore.QRect(10, 360, 350, 30))
- self.block_id = QtWidgets.QPlainTextEdit(self)
- self.block_id.setGeometry(QtCore.QRect(10, 390, 350, 30))
- self.block = QtWidgets.QPushButton(self)
- self.block.setGeometry(QtCore.QRect(10, 430, 350, 30))
- self.block.clicked.connect(lambda: self._contacts_manager.block_user(self.block_id.toPlainText()) or self.close())
- self.blocked_users_label = QtWidgets.QLabel(self)
- self.blocked_users_label.setGeometry(QtCore.QRect(10, 470, 350, 30))
- self.comboBox = QtWidgets.QComboBox(self)
- self.comboBox.setGeometry(QtCore.QRect(10, 500, 350, 30))
- self.comboBox.addItems(self._settings['blocked'])
- self.unblock = QtWidgets.QPushButton(self)
- self.unblock.setGeometry(QtCore.QRect(10, 540, 350, 30))
- self.unblock.clicked.connect(lambda: self.unblock_user())
- self.retranslateUi()
- QtCore.QMetaObject.connectSlotsByName(self)
-
- def retranslateUi(self):
- self.setWindowTitle(util_ui.tr("Privacy settings"))
- self.saveHistory.setText(util_ui.tr("Save chat history"))
- self.fileautoaccept.setText(util_ui.tr("Allow file auto accept"))
- self.typingNotifications.setText(util_ui.tr("Send typing notifications"))
- self.auto_path.setText(util_ui.tr("Auto accept default path:"))
- self.change_path.setText(util_ui.tr("Change"))
- self.inlines.setText(util_ui.tr("Allow inlines"))
- self.block_user_label.setText(util_ui.tr("Block by public key:"))
- self.blocked_users_label.setText(util_ui.tr("Blocked users:"))
- self.unblock.setText(util_ui.tr("Unblock"))
- self.block.setText(util_ui.tr("Block user"))
- self.saveUnsentOnly.setText(util_ui.tr("Save unsent messages only"))
-
- def update(self, new_state):
- self.saveUnsentOnly.setEnabled(new_state)
- if not new_state:
- self.saveUnsentOnly.setChecked(False)
-
- def unblock_user(self):
- if not self.comboBox.count():
- return
- title = util_ui.tr("Add to friend list")
- info = util_ui.tr("Do you want to add this user to friend list?")
- reply = util_ui.question(info, title)
- self._contacts_manager.unblock_user(self.comboBox.currentText(), reply)
- self.close()
-
- def closeEvent(self, event):
- self._settings['typing_notifications'] = self.typingNotifications.isChecked()
- self._settings['allow_auto_accept'] = self.fileautoaccept.isChecked()
- text = util_ui.tr('History will be cleaned! Continue?')
- title = util_ui.tr('Chat history')
-
- if self._settings['save_history'] and not self.saveHistory.isChecked(): # clear history
- reply = util_ui.question(text, title)
- if reply:
- self._history_loader.clear_history()
- self._settings['save_history'] = self.saveHistory.isChecked()
- else:
- self._settings['save_history'] = self.saveHistory.isChecked()
- if self.saveUnsentOnly.isChecked() and not self._settings['save_unsent_only']:
- reply = util_ui.question(text, title)
- if reply:
- self._history_loader.clear_history(None, True)
- self._settings['save_unsent_only'] = self.saveUnsentOnly.isChecked()
- else:
- self._settings['save_unsent_only'] = self.saveUnsentOnly.isChecked()
- self._settings['auto_accept_path'] = self.path.toPlainText()
- self._settings['allow_inline'] = self.inlines.isChecked()
- self._settings.save()
-
- def new_path(self):
- directory = util_ui.directory_dialog()
- if directory:
- self.path.setPlainText(directory)
-
-
-class NotificationsSettings(CenteredWidget):
- """Notifications settings form"""
-
- def __init__(self, setttings):
- super().__init__()
- self._settings = setttings
- uic.loadUi(get_views_path('notifications_settings_screen'), self)
- self._update_ui()
- self.center()
-
- def closeEvent(self, *args, **kwargs):
- self._settings['notifications'] = self.notificationsCheckBox.isChecked()
- self._settings['sound_notifications'] = self.soundNotificationsCheckBox.isChecked()
- self._settings['group_notifications'] = self.groupNotificationsCheckBox.isChecked()
- self._settings['calls_sound'] = self.callsSoundCheckBox.isChecked()
- self._settings.save()
-
- def _update_ui(self):
- self.notificationsCheckBox.setChecked(self._settings['notifications'])
- self.soundNotificationsCheckBox.setChecked(self._settings['sound_notifications'])
- self.groupNotificationsCheckBox.setChecked(self._settings['group_notifications'])
- self.callsSoundCheckBox.setChecked(self._settings['calls_sound'])
- self._retranslate_ui()
-
- def _retranslate_ui(self):
- self.setWindowTitle(util_ui.tr("Notifications settings"))
- self.notificationsCheckBox.setText(util_ui.tr("Enable notifications"))
- self.groupNotificationsCheckBox.setText(util_ui.tr("Notify about all messages in groups"))
- self.callsSoundCheckBox.setText(util_ui.tr("Enable call\'s sound"))
- self.soundNotificationsCheckBox.setText(util_ui.tr("Enable sound notifications"))
-
-
-class InterfaceSettings(CenteredWidget):
- """Interface settings form"""
-
- def __init__(self, settings, smiley_loader):
- super().__init__()
- self._settings = settings
- self._smiley_loader = smiley_loader
-
- uic.loadUi(get_views_path('interface_settings_screen'), self)
- self._update_ui()
- self.center()
-
- def _update_ui(self):
- themes = list(self._settings.built_in_themes().keys())
- self.themeComboBox.addItems(themes)
- theme = self._settings['theme']
- if theme in self._settings.built_in_themes().keys():
- index = themes.index(theme)
- else:
- index = 0
- self.themeComboBox.setCurrentIndex(index)
-
- supported_languages = sorted(Settings.supported_languages().keys(), reverse=True)
- for key in supported_languages:
- self.languageComboBox.insertItem(0, key)
- if self._settings['language'] == key:
- self.languageComboBox.setCurrentIndex(0)
-
- smiley_packs = self._smiley_loader.get_packs_list()
- self.smileysPackComboBox.addItems(smiley_packs)
- try:
- index = smiley_packs.index(self._settings['smiley_pack'])
- except:
- index = smiley_packs.index('default')
- self.smileysPackComboBox.setCurrentIndex(index)
-
- app_closing_setting = self._settings['close_app']
- self.closeRadioButton.setChecked(app_closing_setting == 0)
- self.hideRadioButton.setChecked(app_closing_setting == 1)
- self.closeToTrayRadioButton.setChecked(app_closing_setting == 2)
-
- self.compactModeCheckBox.setChecked(self._settings['compact_mode'])
- self.showAvatarsCheckBox.setChecked(self._settings['show_avatars'])
- self.smileysCheckBox.setChecked(self._settings['smileys'])
-
- self.importSmileysPushButton.clicked.connect(self._import_smileys)
- self.importStickersPushButton.clicked.connect(self._import_stickers)
-
- self._retranslate_ui()
-
- def _retranslate_ui(self):
- self.setWindowTitle(util_ui.tr("Interface settings"))
- self.showAvatarsCheckBox.setText(util_ui.tr("Show avatars in chat"))
- self.themeLabel.setText(util_ui.tr("Theme:"))
- self.languageLabel.setText(util_ui.tr("Language:"))
- self.smileysGroupBox.setTitle(util_ui.tr("Smileys settings"))
- self.smileysPackLabel.setText(util_ui.tr("Smiley pack:"))
- self.smileysCheckBox.setText(util_ui.tr("Smileys"))
- self.closeRadioButton.setText(util_ui.tr("Close app"))
- self.hideRadioButton.setText(util_ui.tr("Hide app"))
- self.closeToTrayRadioButton.setText(util_ui.tr("Close to tray"))
- self.mirrorModeCheckBox.setText(util_ui.tr("Mirror mode"))
- self.compactModeCheckBox.setText(util_ui.tr("Compact contact list"))
- self.importSmileysPushButton.setText(util_ui.tr("Import smiley pack"))
- self.importStickersPushButton.setText(util_ui.tr("Import sticker pack"))
- self.appClosingGroupBox.setTitle(util_ui.tr("App closing settings"))
-
- @staticmethod
- def _import_stickers():
- directory = util_ui.directory_dialog(util_ui.tr('Choose folder with sticker pack'))
- if directory:
- dest = join_path(get_stickers_directory(), os.path.basename(directory))
- copy(directory, dest)
-
- @staticmethod
- def _import_smileys():
- directory = util_ui.directory_dialog(util_ui.tr('Choose folder with smiley pack'))
- if not directory:
- return
- src = directory + '/'
- dest = join_path(get_smileys_directory(), os.path.basename(directory))
- copy(src, dest)
-
- def closeEvent(self, event):
- app = QtWidgets.QApplication.instance()
-
- self._settings['theme'] = str(self.themeComboBox.currentText())
- try:
- theme = self._settings['theme']
- styles_path = join_path(get_styles_directory(), self._settings.built_in_themes()[theme])
- with open(styles_path) as fl:
- style = fl.read()
- app.setStyleSheet(style)
- except IsADirectoryError:
- pass
-
- self._settings['smileys'] = self.smileysCheckBox.isChecked()
-
- restart = False
- if self._settings['mirror_mode'] != self.mirrorModeCheckBox.isChecked():
- self._settings['mirror_mode'] = self.mirrorModeCheckBox.isChecked()
- restart = True
-
- if self._settings['compact_mode'] != self.compactModeCheckBox.isChecked():
- self._settings['compact_mode'] = self.compactModeCheckBox.isChecked()
- restart = True
-
- if self._settings['show_avatars'] != self.showAvatarsCheckBox.isChecked():
- self._settings['show_avatars'] = self.showAvatarsCheckBox.isChecked()
- restart = True
-
- self._settings['smiley_pack'] = self.smileysPackComboBox.currentText()
- self._smiley_loader.load_pack()
-
- language = self.languageComboBox.currentText()
- if self._settings['language'] != language:
- self._settings['language'] = language
- path = Settings.supported_languages()[language]
- app.removeTranslator(app.translator)
- app.translator.load(join_path(get_translations_directory(), path))
- app.installTranslator(app.translator)
-
- app_closing_setting = 0
- if self.hideRadioButton.isChecked():
- app_closing_setting = 1
- elif self.closeToTrayRadioButton.isChecked():
- app_closing_setting = 2
- self._settings['close_app'] = app_closing_setting
- self._settings.save()
-
- if restart:
- util_ui.message_box(util_ui.tr('Restart app to apply settings'), util_ui.tr('Restart required'))
-
-
-class AudioSettings(CenteredWidget):
- """
- Audio calls settings form
- """
-
- def __init__(self, settings):
- super().__init__()
- self._settings = settings
- self._in_indexes = self._out_indexes = None
- uic.loadUi(get_views_path('audio_settings_screen'), self)
- self._update_ui()
- self.center()
-
- def closeEvent(self, event):
- self._settings.audio['input'] = self._in_indexes[self.inputDeviceComboBox.currentIndex()]
- self._settings.audio['output'] = self._out_indexes[self.outputDeviceComboBox.currentIndex()]
- self._settings.save()
-
- def _update_ui(self):
- p = pyaudio.PyAudio()
- self._in_indexes, self._out_indexes = [], []
- for i in range(p.get_device_count()):
- device = p.get_device_info_by_index(i)
- if device["maxInputChannels"]:
- self.inputDeviceComboBox.addItem(str(device["name"]))
- self._in_indexes.append(i)
- if device["maxOutputChannels"]:
- self.outputDeviceComboBox.addItem(str(device["name"]))
- self._out_indexes.append(i)
- self.inputDeviceComboBox.setCurrentIndex(self._in_indexes.index(self._settings.audio['input']))
- self.outputDeviceComboBox.setCurrentIndex(self._out_indexes.index(self._settings.audio['output']))
- self._retranslate_ui()
-
- def _retranslate_ui(self):
- self.setWindowTitle(util_ui.tr("Audio settings"))
- self.inputDeviceLabel.setText(util_ui.tr("Input device:"))
- self.outputDeviceLabel.setText(util_ui.tr("Output device:"))
-
-
-class DesktopAreaSelectionWindow(RubberBandWindow):
-
- def mouseReleaseEvent(self, event):
- if self.rubberband.isVisible():
- self.rubberband.hide()
- rect = self.rubberband.geometry()
- width, height = rect.width(), rect.height()
- if width >= 8 and height >= 8:
- self.parent.save(rect.x(), rect.y(), width - (width % 4), height - (height % 4))
- self.close()
-
-
-class VideoSettings(CenteredWidget):
- """
- Audio calls settings form
- """
-
- def __init__(self, settings):
- super().__init__()
- self._settings = settings
- uic.loadUi(get_views_path('video_settings_screen'), self)
- self._devices = self._frame_max_sizes = None
- self._update_ui()
- self.center()
- self.desktopAreaSelection = None
-
- def closeEvent(self, event):
- if self.deviceComboBox.currentIndex() == 0:
- return
- try:
- self._settings.video['device'] = self.devices[self.input.currentIndex()]
- text = self.resolutionComboBox.currentText()
- self._settings.video['width'] = int(text.split(' ')[0])
- self._settings.video['height'] = int(text.split(' ')[-1])
- self._settings.save()
- except Exception as ex:
- print('Saving video settings error: ' + str(ex))
-
- def save(self, x, y, width, height):
- self.desktopAreaSelection = None
- self._settings.video['device'] = -1
- self._settings.video['width'] = width
- self._settings.video['height'] = height
- self._settings.video['x'] = x
- self._settings.video['y'] = y
- self._settings.save()
-
- def _update_ui(self):
- self.deviceComboBox.currentIndexChanged.connect(self._device_changed)
- self.selectRegionPushButton.clicked.connect(self._button_clicked)
- self._devices = [-1]
- screen = QtWidgets.QApplication.primaryScreen()
- size = screen.size()
- self._frame_max_sizes = [(size.width(), size.height())]
- desktop = util_ui.tr("Desktop")
- self.deviceComboBox.addItem(desktop)
- for i in range(10):
- v = cv2.VideoCapture(i)
- if v.isOpened():
- v.set(cv2.CAP_PROP_FRAME_WIDTH, 10000)
- v.set(cv2.CAP_PROP_FRAME_HEIGHT, 10000)
-
- width = int(v.get(cv2.CAP_PROP_FRAME_WIDTH))
- height = int(v.get(cv2.CAP_PROP_FRAME_HEIGHT))
- del v
- self._devices.append(i)
- self._frame_max_sizes.append((width, height))
- self.deviceComboBox.addItem(util_ui.tr('Device #') + str(i))
- try:
- index = self._devices.index(self._settings.video['device'])
- self.deviceComboBox.setCurrentIndex(index)
- except:
- print('Video devices error!')
- self._retranslate_ui()
-
- def _retranslate_ui(self):
- self.setWindowTitle(util_ui.tr("Video settings"))
- self.deviceLabel.setText(util_ui.tr("Device:"))
- self.selectRegionPushButton.setText(util_ui.tr("Select region"))
-
- def _button_clicked(self):
- self.desktopAreaSelection = DesktopAreaSelectionWindow(self)
-
- def _device_changed(self):
- index = self.deviceComboBox.currentIndex()
- self.selectRegionPushButton.setVisible(index == 0)
- self.resolutionComboBox.setVisible(index != 0)
- width, height = self._frame_max_sizes[index]
- self.resolutionComboBox.clear()
- dims = [
- (320, 240),
- (640, 360),
- (640, 480),
- (720, 480),
- (1280, 720),
- (1920, 1080),
- (2560, 1440)
- ]
- for w, h in dims:
- if w <= width and h <= height:
- self.resolutionComboBox.addItem(str(w) + ' * ' + str(h))
-
-
-class PluginsSettings(CenteredWidget):
- """
- Plugins settings form
- """
-
- def __init__(self, plugin_loader):
- super().__init__()
- self._plugin_loader = plugin_loader
- self._window = None
- self.initUI()
- self.center()
- self.retranslateUi()
-
- def initUI(self):
- self.resize(400, 210)
- self.setMinimumSize(QtCore.QSize(400, 210))
- self.setMaximumSize(QtCore.QSize(400, 210))
- self.comboBox = QtWidgets.QComboBox(self)
- self.comboBox.setGeometry(QtCore.QRect(30, 10, 340, 30))
- self.label = QtWidgets.QLabel(self)
- self.label.setGeometry(QtCore.QRect(30, 40, 340, 90))
- self.label.setWordWrap(True)
- self.button = QtWidgets.QPushButton(self)
- self.button.setGeometry(QtCore.QRect(30, 130, 340, 30))
- self.button.clicked.connect(self.button_click)
- self.open = QtWidgets.QPushButton(self)
- self.open.setGeometry(QtCore.QRect(30, 170, 340, 30))
- self.open.clicked.connect(self.open_plugin)
- self.update_list()
- self.comboBox.currentIndexChanged.connect(self.show_data)
- self.show_data()
-
- def retranslateUi(self):
- self.setWindowTitle(util_ui.tr("Plugins"))
- self.open.setText(util_ui.tr("Open selected plugin"))
-
- def open_plugin(self):
- ind = self.comboBox.currentIndex()
- plugin = self.data[ind]
- window = self.pl_loader.plugin_window(plugin[-1])
- if window is not None:
- self._window = window
- self._window.show()
- else:
- util_ui.message_box(util_ui.tr('No GUI found for this plugin'), util_ui.tr('Error'))
-
- def update_list(self):
- self.comboBox.clear()
- data = self._plugin_loader.get_plugins_list()
- self.comboBox.addItems(list(map(lambda x: x[0], data)))
- self.data = data
-
- def show_data(self):
- ind = self.comboBox.currentIndex()
- if len(self.data):
- plugin = self.data[ind]
- descr = plugin[2] or util_ui.tr("No description available")
- self.label.setText(descr)
- if plugin[1]:
- self.button.setText(util_ui.tr("Disable plugin"))
- else:
- self.button.setText(util_ui.tr("Enable plugin"))
- else:
- self.open.setVisible(False)
- self.button.setVisible(False)
- self.label.setText(util_ui.tr("No plugins found"))
-
- def button_click(self):
- ind = self.comboBox.currentIndex()
- plugin = self.data[ind]
- self._plugin_loader.toggle_plugin(plugin[-1])
- plugin[1] = not plugin[1]
- if plugin[1]:
- self.button.setText(util_ui.tr("Disable plugin"))
- else:
- self.button.setText(util_ui.tr("Enable plugin"))
-
-
-class UpdateSettings(CenteredWidget):
- """
- Updates settings form
- """
-
- def __init__(self, settings, version):
- super().__init__()
- self._settings = settings
- self._version = version
- uic.loadUi(get_views_path('update_settings_screen'), self)
- self._update_ui()
- self.center()
-
- def closeEvent(self, event):
- self._settings['update'] = self.updateModeComboBox.currentIndex()
- self._settings.save()
-
- def _update_ui(self):
- self.updatePushButton.clicked.connect(self._update_client)
- self.updateModeComboBox.currentIndexChanged.connect(self._update_mode_changed)
- self._retranslate_ui()
- self.updateModeComboBox.setCurrentIndex(self._settings['update'])
-
- def _update_mode_changed(self):
- index = self.updateModeComboBox.currentIndex()
- self.updatePushButton.setEnabled(index > 0)
-
- def _retranslate_ui(self):
- self.setWindowTitle(util_ui.tr("Update settings"))
- self.updateModeLabel.setText(util_ui.tr("Select update mode:"))
- self.updatePushButton.setText(util_ui.tr("Update Toxygen"))
- self.updateModeComboBox.addItem(util_ui.tr("Disabled"))
- self.updateModeComboBox.addItem(util_ui.tr("Manual"))
- self.updateModeComboBox.addItem(util_ui.tr("Auto"))
-
- def _update_client(self):
- if not updater.connection_available():
- util_ui.message_box(util_ui.tr('Problems with internet connection'), util_ui.tr("Error"))
- return
- if not updater.updater_available():
- util_ui.message_box(util_ui.tr('Updater not found'), util_ui.tr("Error"))
- return
- version = updater.check_for_updates(self._version, self._settings)
- if version is not None:
- updater.download(version)
- util_ui.close_all_windows()
- else:
- util_ui.message_box(util_ui.tr('Toxygen is up to date'), util_ui.tr("No updates found"))
diff --git a/toxygen/ui/messages_widgets.py b/toxygen/ui/messages_widgets.py
deleted file mode 100644
index 8a46fd0..0000000
--- a/toxygen/ui/messages_widgets.py
+++ /dev/null
@@ -1,449 +0,0 @@
-from wrapper.toxcore_enums_and_consts import *
-import ui.widgets as widgets
-import utils.util as util
-import ui.menu as menu
-import html as h
-import re
-from ui.widgets import *
-from messenger.messages import MESSAGE_AUTHOR
-from file_transfers.file_transfers import *
-
-
-class MessageBrowser(QtWidgets.QTextBrowser):
-
- def __init__(self, settings, message_edit, smileys_loader, plugin_loader, text, width, message_type, parent=None):
- super().__init__(parent)
- self.urls = {}
- self._message_edit = message_edit
- self._smileys_loader = smileys_loader
- self._plugin_loader = plugin_loader
- self._add_contact = None
- self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
- self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
- self.setWordWrapMode(QtGui.QTextOption.WrapAtWordBoundaryOrAnywhere)
- self.document().setTextWidth(width)
- self.setOpenExternalLinks(True)
- self.setAcceptRichText(True)
- self.setOpenLinks(False)
- path = smileys_loader.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']:
- self.setHtml('' + text + '
')
- else:
- self.setHtml(text)
- font = QtGui.QFont()
- font.setFamily(settings['font'])
- font.setPixelSize(settings['message_font_size'])
- font.setBold(False)
- self.setFont(font)
- self.resize(width, self.document().size().height())
- self.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse | QtCore.Qt.LinksAccessibleByMouse)
- self.anchorClicked.connect(self.on_anchor_clicked)
-
- def contextMenuEvent(self, event):
- menu = widgets.create_menu(self.createStandardContextMenu(event.pos()))
- quote = menu.addAction(util_ui.tr('Quote selected text'))
- quote.triggered.connect(self.quote_text)
- text = self.textCursor().selection().toPlainText()
- if not text:
- quote.setEnabled(False)
- else:
- sub_menu = self._plugin_loader.get_message_menu(menu, text)
- if len(sub_menu):
- plugins_menu = menu.addMenu(util_ui.tr('Plugins'))
- plugins_menu.addActions(sub_menu)
- menu.popup(event.globalPos())
- menu.exec_(event.globalPos())
- del menu
-
- def quote_text(self):
- text = self.textCursor().selection().toPlainText()
- if not text:
- return
- text = '>' + '\n>'.join(text.split('\n'))
- if self._message_edit.toPlainText():
- text = '\n' + text
- self._message_edit.appendPlainText(text)
-
- def on_anchor_clicked(self, url):
- text = str(url.toString())
- if text.startswith('tox:'):
- self._add_contact = menu.AddContact(text[4:])
- self._add_contact.show()
- else:
- QtGui.QDesktopServices.openUrl(url)
- self.clearFocus()
-
- def addAnimation(self, url, file_name):
- movie = QtGui.QMovie(self)
- movie.setFileName(file_name)
- self.urls[movie] = url
- movie.frameChanged[int].connect(lambda x: self.animate(movie))
- movie.start()
-
- def animate(self, movie):
- self.document().addResource(QtGui.QTextDocument.ImageResource,
- self.urls[movie],
- movie.currentPixmap())
- self.setLineWrapColumnOrWidth(self.lineWrapColumnOrWidth())
-
- def decoratedText(self, text):
- text = h.escape(text) # replace < and >
- exp = QtCore.QRegExp(
- '('
- '(?:\\b)((www\\.)|(http[s]?|ftp)://)'
- '\\w+\\S+)'
- '|(?:\\b)(file:///)([\\S| ]*)'
- '|(?:\\b)(tox:[a-zA-Z\\d]{76}$)'
- '|(?:\\b)(mailto:\\S+@\\S+\\.\\S+)'
- '|(?:\\b)(tox:\\S+@\\S+)')
- offset = exp.indexIn(text, 0)
- while offset != -1: # add links
- url = exp.cap()
- if exp.cap(2) == 'www.':
- html = '{0}'.format(url)
- else:
- html = '{0}'.format(url)
- text = text[:offset] + html + text[offset + len(exp.cap()):]
- offset += len(html)
- offset = exp.indexIn(text, offset)
- arr = text.split('\n')
- for i in range(len(arr)): # quotes
- if arr[i].startswith('>'):
- arr[i] = '' + arr[i][4:] + ''
- text = '
'.join(arr)
- text = self._smileys_loader.add_smileys_to_text(text, self)
- return text
-
-
-class MessageItem(QtWidgets.QWidget):
- """
- Message in messages list
- """
- def __init__(self, text_message, settings, message_browser_factory_method, delete_action, parent=None):
- QtWidgets.QWidget.__init__(self, parent)
- self._message = text_message
- self._delete_action = delete_action
- self.name = widgets.DataLabel(self)
- self.name.setGeometry(QtCore.QRect(2, 2, 95, 23))
- self.name.setTextFormat(QtCore.Qt.PlainText)
- font = QtGui.QFont()
- font.setFamily(settings['font'])
- font.setPointSize(11)
- font.setBold(True)
- if text_message.author is not None:
- self.name.setFont(font)
- self.name.setText(text_message.author.name)
-
- 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)
- self._time = text_message.time
- if text_message.author and text_message.author.type == MESSAGE_AUTHOR['NOT_SENT']:
- movie = QtGui.QMovie(util.join_path(util.get_images_directory(), 'spinner.gif'))
- self.time.setMovie(movie)
- movie.start()
- self.t = True
- else:
- self.time.setText(util.convert_time(text_message.time))
- self.t = False
-
- self.message = message_browser_factory_method(text_message.text, parent.width() - 160,
- text_message.type, self)
- if text_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() - 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 = QtWidgets.QMenu()
- delete_item = self.listMenu.addAction(util_ui.tr('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()
-
- def delete(self):
- self._delete_action(self._message)
-
- def mark_as_sent(self):
- if self.t:
- self.time.setText(util.convert_time(self._time))
- self.t = False
- return True
- return False
-
- def set_avatar(self, pixmap):
- self.name.setAlignment(QtCore.Qt.AlignCenter)
- self.message.setAlignment(QtCore.Qt.AlignVCenter)
- 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, 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 = '{}'.format(substring)
- text = text[:i] + sub + text[i + l:]
- i += len(sub)
- return text
-
-
-class FileTransferItem(QtWidgets.QListWidget):
-
- def __init__(self, transfer_message, file_transfer_handler, settings, width, parent=None):
-
- QtWidgets.QListWidget.__init__(self, parent)
- self._file_transfer_handler = file_transfer_handler
- self.resize(QtCore.QSize(width, 34))
- if transfer_message.state == FILE_TRANSFER_STATE['CANCELLED']:
- self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
- elif transfer_message.state in PAUSED_FILE_TRANSFERS:
- self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
- else:
- self.setStyleSheet('QListWidget { border: 1px solid green; }')
- self.state = transfer_message.state
-
- self.name = DataLabel(self)
- self.name.setGeometry(QtCore.QRect(3, 7, 95, 25))
- self.name.setTextFormat(QtCore.Qt.PlainText)
- font = QtGui.QFont()
- font.setFamily(settings['font'])
- font.setPointSize(11)
- font.setBold(True)
- self.name.setFont(font)
- self.name.setText(transfer_message.author.name)
-
- 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(util.convert_time(transfer_message.time))
-
- self.cancel = QtWidgets.QPushButton(self)
- self.cancel.setGeometry(QtCore.QRect(width - 125, 2, 30, 30))
- pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'decline.png'))
- icon = QtGui.QIcon(pixmap)
- self.cancel.setIcon(icon)
- self.cancel.setIconSize(QtCore.QSize(30, 30))
- self.cancel.setVisible(transfer_message.state in ACTIVE_FILE_TRANSFERS or
- transfer_message.state == FILE_TRANSFER_STATE['UNSENT'])
- self.cancel.clicked.connect(
- lambda: self.cancel_transfer(transfer_message.friend_number, transfer_message.file_number))
- self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}')
-
- self.accept_or_pause = QtWidgets.QPushButton(self)
- self.accept_or_pause.setGeometry(QtCore.QRect(width - 170, 2, 30, 30))
- if transfer_message.state == FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
- self.accept_or_pause.setVisible(True)
- self.button_update('accept')
- elif transfer_message.state in DO_NOT_SHOW_ACCEPT_BUTTON:
- self.accept_or_pause.setVisible(False)
- elif transfer_message.state == FILE_TRANSFER_STATE['PAUSED_BY_USER']: # setup for continue
- self.accept_or_pause.setVisible(True)
- self.button_update('resume')
- elif transfer_message.state == FILE_TRANSFER_STATE['UNSENT']:
- self.accept_or_pause.setVisible(False)
- self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
- else: # pause
- self.accept_or_pause.setVisible(True)
- self.button_update('pause')
- self.accept_or_pause.clicked.connect(
- lambda: self.accept_or_pause_transfer(transfer_message.friend_number, transfer_message.file_number,
- transfer_message.size))
-
- self.accept_or_pause.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none}')
-
- 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; }')
- self.pb.setVisible(transfer_message.state in SHOW_PROGRESS_BAR)
-
- self.file_name = DataLabel(self)
- self.file_name.setGeometry(QtCore.QRect(210, 7, width - 420, 20))
- font.setPointSize(12)
- self.file_name.setFont(font)
- file_size = transfer_message.size // 1024
- if not file_size:
- file_size = '{}B'.format(transfer_message.size)
- elif file_size >= 1024:
- file_size = '{}MB'.format(file_size // 1024)
- else:
- file_size = '{}KB'.format(file_size)
- file_data = '{} {}'.format(file_size, transfer_message.file_name)
- self.file_name.setText(file_data)
- self.file_name.setToolTip(transfer_message.file_name)
- self.saved_name = transfer_message.file_name
- 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(transfer_message.state == FILE_TRANSFER_STATE['RUNNING'])
- self.setFocusPolicy(QtCore.Qt.NoFocus)
- self.paused = False
-
- def cancel_transfer(self, friend_number, file_number):
- self._file_transfer_handler.cancel_transfer(friend_number, file_number)
- self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
- self.cancel.setVisible(False)
- self.accept_or_pause.setVisible(False)
- self.pb.setVisible(False)
-
- def accept_or_pause_transfer(self, friend_number, file_number, size):
- if self.state == FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
- directory = util_ui.directory_dialog(util_ui.tr('Choose folder'))
- self.pb.setVisible(True)
- if directory:
- self._file_transfer_handler.accept_transfer(directory + '/' + self.saved_name,
- friend_number, file_number, size)
- self.button_update('pause')
- elif self.state == FILE_TRANSFER_STATE['PAUSED_BY_USER']: # resume
- self.paused = False
- self._file_transfer_handler.resume_transfer(friend_number, file_number)
- self.button_update('pause')
- self.state = FILE_TRANSFER_STATE['RUNNING']
- else: # pause
- self.paused = True
- self.state = FILE_TRANSFER_STATE['PAUSED_BY_USER']
- self._file_transfer_handler.pause_transfer(friend_number, file_number)
- self.button_update('resume')
- self.accept_or_pause.clearFocus()
-
- def button_update(self, path):
- pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), '{}.png'.format(path)))
- icon = QtGui.QIcon(pixmap)
- self.accept_or_pause.setIcon(icon)
- self.accept_or_pause.setIconSize(QtCore.QSize(30, 30))
-
- 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 and self.state in ACTIVE_FILE_TRANSFERS:
- if state == FILE_TRANSFER_STATE['CANCELLED']:
- self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
- self.cancel.setVisible(False)
- self.accept_or_pause.setVisible(False)
- self.pb.setVisible(False)
- self.state = state
- self.time_left.setVisible(False)
- elif state == FILE_TRANSFER_STATE['FINISHED']:
- self.accept_or_pause.setVisible(False)
- self.pb.setVisible(False)
- self.cancel.setVisible(False)
- self.setStyleSheet('QListWidget { border: 1px solid green; }')
- self.state = state
- self.time_left.setVisible(False)
- elif state == FILE_TRANSFER_STATE['PAUSED_BY_FRIEND']:
- self.accept_or_pause.setVisible(False)
- self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
- self.state = state
- self.time_left.setVisible(False)
- elif state == FILE_TRANSFER_STATE['PAUSED_BY_USER']:
- self.button_update('resume') # setup button continue
- self.setStyleSheet('QListWidget { border: 1px solid green; }')
- self.state = state
- self.time_left.setVisible(False)
- elif state == FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']:
- self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
- self.accept_or_pause.setVisible(False)
- self.time_left.setVisible(False)
- self.pb.setVisible(False)
- elif not self.paused: # active
- self.pb.setVisible(True)
- self.accept_or_pause.setVisible(True) # setup to pause
- self.button_update('pause')
- self.setStyleSheet('QListWidget { border: 1px solid green; }')
- self.state = state
- self.time_left.setVisible(True)
-
-
-class UnsentFileItem(FileTransferItem):
-
- def __init__(self, transfer_message, file_transfer_handler, settings, width, parent=None):
- super().__init__(transfer_message, file_transfer_handler, settings, width, parent)
- self._time = time
- movie = QtGui.QMovie(util.join_path(util.get_images_directory(), 'spinner.gif'))
- self.time.setMovie(movie)
- movie.start()
- self._message_id = transfer_message.message_id
- self._friend_number = transfer_message.friend_number
-
- def cancel_transfer(self, *args):
- self._file_transfer_handler.cancel_not_started_transfer(self._friend_number, self._message_id)
-
-
-class InlineImageItem(QtWidgets.QScrollArea):
-
- def __init__(self, data, width, elem, parent=None):
-
- QtWidgets.QScrollArea.__init__(self, parent)
- self.setFocusPolicy(QtCore.Qt.NoFocus)
- self._elem = elem
- self._image_label = QtWidgets.QLabel(self)
- self._image_label.raise_()
- self.setWidget(self._image_label)
- self._image_label.setScaledContents(False)
- self._pixmap = QtGui.QPixmap()
- self._pixmap.loadFromData(data, 'PNG')
- self._max_size = width - 30
- self._resize_needed = not (self._pixmap.width() <= self._max_size)
- self._full_size = not self._resize_needed
- if not self._resize_needed:
- self._image_label.setPixmap(self._pixmap)
- self.resize(QtCore.QSize(self._max_size + 5, self._pixmap.height() + 5))
- self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height())
- else:
- pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio)
- self._image_label.setPixmap(pixmap)
- self.resize(QtCore.QSize(self._max_size + 5, pixmap.height()))
- self._image_label.setGeometry(5, 0, self._max_size + 5, pixmap.height())
- self._elem.setSizeHint(QtCore.QSize(self.width(), self.height()))
-
- def mouseReleaseEvent(self, event):
- if event.button() == QtCore.Qt.LeftButton and self._resize_needed: # scale inline
- if self._full_size:
- pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio)
- self._image_label.setPixmap(pixmap)
- self.resize(QtCore.QSize(self._max_size, pixmap.height()))
- self._image_label.setGeometry(5, 0, pixmap.width(), pixmap.height())
- else:
- self._image_label.setPixmap(self._pixmap)
- self.resize(QtCore.QSize(self._max_size, self._pixmap.height() + 17))
- self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height())
- self._full_size = not self._full_size
- self._elem.setSizeHint(QtCore.QSize(self.width(), self.height()))
- elif event.button() == QtCore.Qt.RightButton: # save inline
- directory = util_ui.directory_dialog(util_ui.tr('Choose folder'))
- if directory:
- fl = QtCore.QFile(directory + '/toxygen_inline_' + util.curr_time().replace(':', '_') + '.png')
- self._pixmap.save(fl, 'PNG')
diff --git a/toxygen/ui/peer_screen.py b/toxygen/ui/peer_screen.py
deleted file mode 100644
index 8f2d5ba..0000000
--- a/toxygen/ui/peer_screen.py
+++ /dev/null
@@ -1,111 +0,0 @@
-from ui.widgets import CenteredWidget
-from PyQt5 import uic
-import utils.util as util
-import utils.ui as util_ui
-from ui.contact_items import *
-import wrapper.toxcore_enums_and_consts as consts
-
-
-class PeerScreen(CenteredWidget):
-
- def __init__(self, contacts_manager, groups_service, group, peer_id):
- super().__init__()
- self._contacts_manager = contacts_manager
- self._groups_service = groups_service
- self._group = group
- self._peer = group.get_peer_by_id(peer_id)
-
- self._roles = {
- TOX_GROUP_ROLE['FOUNDER']: util_ui.tr('Administrator'),
- TOX_GROUP_ROLE['MODERATOR']: util_ui.tr('Moderator'),
- TOX_GROUP_ROLE['USER']: util_ui.tr('User'),
- TOX_GROUP_ROLE['OBSERVER']: util_ui.tr('Observer')
- }
-
- uic.loadUi(util.get_views_path('peer_screen'), self)
- self._update_ui()
-
- def _update_ui(self):
- self.statusCircle = StatusCircle(self)
- self.statusCircle.setGeometry(50, 15, 30, 30)
-
- self.statusCircle.update(self._peer.status)
- self.peerNameLabel.setText(self._peer.name)
- self.ignorePeerCheckBox.setChecked(self._peer.is_muted)
- self.ignorePeerCheckBox.clicked.connect(self._toggle_ignore)
- self.sendPrivateMessagePushButton.clicked.connect(self._send_private_message)
- self.copyPublicKeyPushButton.clicked.connect(self._copy_public_key)
- self.roleNameLabel.setText(self._get_role_name())
- can_change_role_or_ban = self._can_change_role_or_ban()
- self.rolesComboBox.setVisible(can_change_role_or_ban)
- self.roleNameLabel.setVisible(not can_change_role_or_ban)
- self.banGroupBox.setEnabled(can_change_role_or_ban)
- self.banPushButton.clicked.connect(self._ban_peer)
- self.kickPushButton.clicked.connect(self._kick_peer)
-
- self._retranslate_ui()
-
- self.rolesComboBox.currentIndexChanged.connect(self._role_set)
-
- def _retranslate_ui(self):
- self.setWindowTitle(util_ui.tr('Peer details'))
- self.ignorePeerCheckBox.setText(util_ui.tr('Ignore peer'))
- self.roleLabel.setText(util_ui.tr('Role:'))
- self.copyPublicKeyPushButton.setText(util_ui.tr('Copy public key'))
- self.sendPrivateMessagePushButton.setText(util_ui.tr('Send private message'))
- self.banPushButton.setText(util_ui.tr('Ban peer'))
- self.kickPushButton.setText(util_ui.tr('Kick peer'))
- self.banGroupBox.setTitle(util_ui.tr('Ban peer'))
- self.ipBanRadioButton.setText(util_ui.tr('IP'))
- self.nickBanRadioButton.setText(util_ui.tr('Nickname'))
- self.pkBanRadioButton.setText(util_ui.tr('Public key'))
-
- self.rolesComboBox.clear()
- index = self._group.get_self_peer().role
- roles = list(self._roles.values())
- for role in roles[index + 1:]:
- self.rolesComboBox.addItem(role)
- self.rolesComboBox.setCurrentIndex(self._peer.role - index - 1)
-
- def _can_change_role_or_ban(self):
- self_peer = self._group.get_self_peer()
- if self_peer.role > TOX_GROUP_ROLE['MODERATOR']:
- return False
-
- return self_peer.role < self._peer.role
-
- def _role_set(self):
- index = self.rolesComboBox.currentIndex()
- all_roles_count = len(self._roles)
- diff = all_roles_count - self.rolesComboBox.count()
- self._groups_service.set_new_peer_role(self._group, self._peer, index + diff)
-
- def _get_role_name(self):
- return self._roles[self._peer.role]
-
- def _toggle_ignore(self):
- ignore = self.ignorePeerCheckBox.isChecked()
- self._groups_service.toggle_ignore_peer(self._group, self._peer, ignore)
-
- def _send_private_message(self):
- self._contacts_manager.add_group_peer(self._group, self._peer)
- self.close()
-
- def _copy_public_key(self):
- util_ui.copy_to_clipboard(self._peer.public_key)
-
- def _ban_peer(self):
- ban_type = self._get_ban_type()
- self._groups_service.ban_peer(self._group, self._peer.id, ban_type)
- self.close()
-
- def _kick_peer(self):
- self._groups_service.kick_peer(self._group, self._peer.id)
- self.close()
-
- def _get_ban_type(self):
- if self.ipBanRadioButton.isChecked():
- return consts.TOX_GROUP_BAN_TYPE['IP_PORT']
- elif self.nickBanRadioButton.isChecked():
- return consts.TOX_GROUP_BAN_TYPE['NICK']
- return consts.TOX_GROUP_BAN_TYPE['PUBLIC_KEY']
diff --git a/toxygen/ui/profile_settings_screen.py b/toxygen/ui/profile_settings_screen.py
deleted file mode 100644
index 2e55d3d..0000000
--- a/toxygen/ui/profile_settings_screen.py
+++ /dev/null
@@ -1,157 +0,0 @@
-from ui.widgets import CenteredWidget
-import utils.ui as util_ui
-from utils.util import join_path, get_images_directory, get_views_path
-from user_data.settings import Settings
-from PyQt5 import QtGui, QtCore, uic
-
-
-class ProfileSettings(CenteredWidget):
- """Form with profile settings such as name, status, TOX ID"""
- def __init__(self, profile, profile_manager, settings, toxes):
- super().__init__()
- self._profile = profile
- self._profile_manager = profile_manager
- self._settings = settings
- self._toxes = toxes
- self._auto = False
-
- uic.loadUi(get_views_path('profile_settings_screen'), self)
-
- self._init_ui()
- self.center()
-
- def closeEvent(self, event):
- self._profile.set_name(self.nameLineEdit.text())
- self._profile.set_status_message(self.statusMessageLineEdit.text())
- self._profile.set_status(self.statusComboBox.currentIndex())
-
- def _init_ui(self):
- self._auto = Settings.get_auto_profile() == self._profile_manager.get_path()
- self.toxIdLabel.setText(self._profile.tox_id)
- self.nameLineEdit.setText(self._profile.name)
- self.statusMessageLineEdit.setText(self._profile.status_message)
- self.defaultProfilePushButton.clicked.connect(self._toggle_auto_profile)
- self.copyToxIdPushButton.clicked.connect(self._copy_tox_id)
- self.copyPublicKeyPushButton.clicked.connect(self._copy_public_key)
- self.changePasswordPushButton.clicked.connect(self._save_password)
- self.exportProfilePushButton.clicked.connect(self._export_profile)
- self.newNoSpamPushButton.clicked.connect(self._set_new_no_spam)
- self.newAvatarPushButton.clicked.connect(self._set_avatar)
- self.resetAvatarPushButton.clicked.connect(self._reset_avatar)
-
- self.invalidPasswordsLabel.setVisible(False)
-
- self._retranslate_ui()
-
- if self._profile.status is not None:
- self.statusComboBox.setCurrentIndex(self._profile.status)
- else:
- self.statusComboBox.setVisible(False)
-
- def _retranslate_ui(self):
- self.setWindowTitle(util_ui.tr("Profile settings"))
-
- self.exportProfilePushButton.setText(util_ui.tr("Export profile"))
- self.nameLabel.setText(util_ui.tr("Name:"))
- self.statusLabel.setText(util_ui.tr("Status:"))
- self.toxIdTitleLabel.setText(util_ui.tr("TOX ID:"))
- self.copyToxIdPushButton.setText(util_ui.tr("Copy TOX ID"))
- self.newAvatarPushButton.setText(util_ui.tr("New avatar"))
- self.resetAvatarPushButton.setText(util_ui.tr("Reset avatar"))
- self.newNoSpamPushButton.setText(util_ui.tr("New NoSpam"))
- self.profilePasswordLabel.setText(util_ui.tr("Profile password"))
- self.passwordLineEdit.setPlaceholderText(util_ui.tr("Password (at least 8 symbols)"))
- self.confirmPasswordLineEdit.setPlaceholderText(util_ui.tr("Confirm password"))
- self.changePasswordPushButton.setText(util_ui.tr("Set password"))
- self.invalidPasswordsLabel.setText(util_ui.tr("Passwords do not match"))
- self.emptyPasswordLabel.setText(util_ui.tr("Leaving blank will reset current password"))
- self.warningLabel.setText(util_ui.tr("There is no way to recover lost passwords"))
- self.statusComboBox.addItem(util_ui.tr("Online"))
- self.statusComboBox.addItem(util_ui.tr("Away"))
- self.statusComboBox.addItem(util_ui.tr("Busy"))
- self.copyPublicKeyPushButton.setText(util_ui.tr("Copy public key"))
-
- self._set_default_profile_button_text()
-
- def _toggle_auto_profile(self):
- if self._auto:
- Settings.reset_auto_profile()
- else:
- Settings.set_auto_profile(self._profile_manager.get_path())
- self._auto = not self._auto
- self._set_default_profile_button_text()
-
- def _set_default_profile_button_text(self):
- if self._auto:
- self.defaultProfilePushButton.setText(util_ui.tr("Mark as not default profile"))
- else:
- self.defaultProfilePushButton.setText(util_ui.tr("Mark as default profile"))
-
- def _save_password(self):
- password = self.passwordLineEdit.text()
- confirm_password = self.confirmPasswordLineEdit.text()
- if password == confirm_password:
- if not len(password) or len(password) >= 8:
- self._toxes.set_password(password)
- self.close()
- else:
- self.invalidPasswordsLabel.setText(
- util_ui.tr("Password must be at least 8 symbols"))
- self.invalidPasswordsLabel.setVisible(True)
- else:
- self.invalidPasswordsLabel.setText(util_ui.tr("Passwords do not match"))
- self.invalidPasswordsLabel.setVisible(True)
-
- def _copy_tox_id(self):
- util_ui.copy_to_clipboard(self._profile.tox_id)
-
- icon = self._get_accept_icon()
- self.copyToxIdPushButton.setIcon(icon)
- self.copyToxIdPushButton.setIconSize(QtCore.QSize(10, 10))
-
- def _copy_public_key(self):
- util_ui.copy_to_clipboard(self._profile.tox_id[:64])
-
- icon = self._get_accept_icon()
- self.copyPublicKeyPushButton.setIcon(icon)
- self.copyPublicKeyPushButton.setIconSize(QtCore.QSize(10, 10))
-
- def _set_new_no_spam(self):
- self.toxIdLabel.setText(self._profile.set_new_nospam())
-
- def _reset_avatar(self):
- self._profile.reset_avatar(self._settings['identicons'])
-
- def _set_avatar(self):
- choose = util_ui.tr("Choose avatar")
- name = util_ui.file_dialog(choose, 'Images (*.png)')
- if not name[0]:
- return
- bitmap = QtGui.QPixmap(name[0])
- bitmap.scaled(QtCore.QSize(128, 128), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
-
- byte_array = QtCore.QByteArray()
- buffer = QtCore.QBuffer(byte_array)
- buffer.open(QtCore.QIODevice.WriteOnly)
- bitmap.save(buffer, 'PNG')
-
- self._profile.set_avatar(bytes(byte_array.data()))
-
- def _export_profile(self):
- directory = util_ui.directory_dialog()
- if not directory:
- return
-
- reply = util_ui.question(util_ui.tr('Do you want to move your profile to this location?'),
- util_ui.tr('Use new path'))
-
- self._settings.export(directory)
- self._profile.export_db(directory)
- self._profile_manager.export_profile(self._settings, directory, reply)
-
- @staticmethod
- def _get_accept_icon():
- pixmap = QtGui.QPixmap(join_path(get_images_directory(), 'accept.png'))
-
- return QtGui.QIcon(pixmap)
-
diff --git a/toxygen/ui/self_peer_screen.py b/toxygen/ui/self_peer_screen.py
deleted file mode 100644
index cf252d3..0000000
--- a/toxygen/ui/self_peer_screen.py
+++ /dev/null
@@ -1,66 +0,0 @@
-from ui.widgets import CenteredWidget, LineEdit
-from PyQt5 import uic
-import utils.util as util
-import utils.ui as util_ui
-from ui.contact_items import *
-
-
-class SelfPeerScreen(CenteredWidget):
-
- def __init__(self, contacts_manager, groups_service, group):
- super().__init__()
- self._contacts_manager = contacts_manager
- self._groups_service = groups_service
- self._group = group
- self._peer = group.get_self_peer()
- self._roles = {
- TOX_GROUP_ROLE['FOUNDER']: util_ui.tr('Administrator'),
- TOX_GROUP_ROLE['MODERATOR']: util_ui.tr('Moderator'),
- TOX_GROUP_ROLE['USER']: util_ui.tr('User'),
- TOX_GROUP_ROLE['OBSERVER']: util_ui.tr('Observer')
- }
-
- uic.loadUi(util.get_views_path('self_peer_screen'), self)
- self._update_ui()
-
- def _update_ui(self):
- self.lineEdit = LineEdit(self)
- self.lineEdit.setGeometry(140, 40, 400, 30)
- self.lineEdit.setText(self._peer.name)
- self.lineEdit.textChanged.connect(self._nick_changed)
-
- self.savePushButton.clicked.connect(self._save)
- self.copyPublicKeyPushButton.clicked.connect(self._copy_public_key)
-
- self._retranslate_ui()
-
- self.statusComboBox.setCurrentIndex(self._peer.status)
-
- def _retranslate_ui(self):
- self.setWindowTitle(util_ui.tr('Change credentials in group'))
- self.lineEdit.setPlaceholderText(util_ui.tr('Your nickname in group'))
- self.nameLabel.setText(util_ui.tr('Name:'))
- self.roleLabel.setText(util_ui.tr('Role:'))
- self.statusLabel.setText(util_ui.tr('Status:'))
- self.copyPublicKeyPushButton.setText(util_ui.tr('Copy public key'))
- self.savePushButton.setText(util_ui.tr('Save'))
- self.roleNameLabel.setText(self._get_role_name())
- self.statusComboBox.addItem(util_ui.tr('Online'))
- self.statusComboBox.addItem(util_ui.tr('Away'))
- self.statusComboBox.addItem(util_ui.tr('Busy'))
-
- def _get_role_name(self):
- return self._roles[self._peer.role]
-
- def _nick_changed(self):
- nick = self.lineEdit.text()
- self.savePushButton.setEnabled(bool(nick))
-
- def _save(self):
- nick = self.lineEdit.text()
- status = self.statusComboBox.currentIndex()
- self._groups_service.set_self_info(self._group, nick, status)
- self.close()
-
- def _copy_public_key(self):
- util_ui.copy_to_clipboard(self._peer.public_key)
diff --git a/toxygen/ui/tray.py b/toxygen/ui/tray.py
deleted file mode 100644
index 3bfc7f3..0000000
--- a/toxygen/ui/tray.py
+++ /dev/null
@@ -1,111 +0,0 @@
-from PyQt5 import QtWidgets, QtGui, QtCore
-from utils.ui import tr
-from utils.util import *
-from ui.password_screen import UnlockAppScreen
-import os.path
-
-
-class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
-
- leftClicked = QtCore.pyqtSignal()
-
- def __init__(self, icon, parent=None):
- super().__init__(icon, parent)
- self.activated.connect(self.icon_activated)
-
- def icon_activated(self, reason):
- if reason == QtWidgets.QSystemTrayIcon.Trigger:
- self.leftClicked.emit()
-
-
-class Menu(QtWidgets.QMenu):
-
- def __init__(self, settings, profile, *args):
- super().__init__(*args)
- self._settings = settings
- self._profile = profile
-
- def new_status(self, status):
- if not self._settings.locked:
- self._profile.set_status(status)
- self.about_to_show_handler()
- self.hide()
-
- def about_to_show_handler(self):
- status = self._profile.status
- act = self.act
- if status is None or self._settings.locked:
- self.actions()[1].setVisible(False)
- else:
- self.actions()[1].setVisible(True)
- act.actions()[0].setChecked(False)
- act.actions()[1].setChecked(False)
- act.actions()[2].setChecked(False)
- act.actions()[status].setChecked(True)
- self.actions()[2].setVisible(not self._settings.locked)
-
- def languageChange(self, *args, **kwargs):
- self.actions()[0].setText(tr('Open Toxygen'))
- self.actions()[1].setText(tr('Set status'))
- self.actions()[2].setText(tr('Exit'))
- self.act.actions()[0].setText(tr('Online'))
- self.act.actions()[1].setText(tr('Away'))
- self.act.actions()[2].setText(tr('Busy'))
-
-
-def init_tray(profile, settings, main_screen, toxes):
- icon = os.path.join(get_images_directory(), 'icon.png')
- tray = SystemTrayIcon(QtGui.QIcon(icon))
-
- menu = Menu(settings, profile)
- show = menu.addAction(tr('Open Toxygen'))
- sub = menu.addMenu(tr('Set status'))
- online = sub.addAction(tr('Online'))
- away = sub.addAction(tr('Away'))
- busy = sub.addAction(tr('Busy'))
- online.setCheckable(True)
- away.setCheckable(True)
- busy.setCheckable(True)
- menu.act = sub
- exit = menu.addAction(tr('Exit'))
-
- def show_window():
- def show():
- if not main_screen.isActiveWindow():
- main_screen.setWindowState(
- main_screen.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
- main_screen.activateWindow()
- main_screen.show()
- if not settings.locked:
- show()
- else:
- def correct_pass():
- show()
- settings.locked = False
- settings.unlockScreen = False
- if not settings.unlockScreen:
- settings.unlockScreen = True
- show_window.screen = UnlockAppScreen(toxes, correct_pass)
- show_window.screen.show()
-
- def tray_activated(reason):
- if reason == QtWidgets.QSystemTrayIcon.DoubleClick:
- show_window()
-
- def close_app():
- if not settings.locked:
- settings.closing = True
- main_screen.close()
-
- show.triggered.connect(show_window)
- exit.triggered.connect(close_app)
- menu.aboutToShow.connect(menu.about_to_show_handler)
- online.triggered.connect(lambda: menu.new_status(0))
- away.triggered.connect(lambda: menu.new_status(1))
- busy.triggered.connect(lambda: menu.new_status(2))
-
- tray.setContextMenu(menu)
- tray.show()
- tray.activated.connect(tray_activated)
-
- return tray
diff --git a/toxygen/ui/views/add_contact_screen.ui b/toxygen/ui/views/add_contact_screen.ui
deleted file mode 100644
index 0f26a25..0000000
--- a/toxygen/ui/views/add_contact_screen.ui
+++ /dev/null
@@ -1,99 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 560
- 320
-
-
-
-
- 560
- 320
-
-
-
-
- 560
- 320
-
-
-
- Form
-
-
-
-
- 50
- 10
- 150
- 20
-
-
-
- TextLabel
-
-
-
-
-
- 50
- 70
- 150
- 30
-
-
-
- TextLabel
-
-
-
-
-
- 50
- 110
- 460
- 150
-
-
-
-
-
-
- 50
- 270
- 460
- 30
-
-
-
- PushButton
-
-
-
-
- true
-
-
-
- 220
- 10
- 321
- 31
-
-
-
- Qt::NoContextMenu
-
-
-
-
-
-
-
-
-
diff --git a/toxygen/ui/views/audio_settings_screen.ui b/toxygen/ui/views/audio_settings_screen.ui
deleted file mode 100644
index a404592..0000000
--- a/toxygen/ui/views/audio_settings_screen.ui
+++ /dev/null
@@ -1,87 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 315
- 218
-
-
-
-
- 315
- 218
-
-
-
-
- 315
- 218
-
-
-
- Form
-
-
-
-
- 30
- 10
- 261
- 30
-
-
-
-
- 16
-
-
-
- TextLabel
-
-
-
-
-
- 30
- 100
- 261
- 30
-
-
-
-
- 16
-
-
-
- TextLabel
-
-
-
-
-
- 30
- 50
- 255
- 41
-
-
-
-
-
-
- 30
- 140
- 255
- 41
-
-
-
-
-
-
-
diff --git a/toxygen/ui/views/bans_list_screen.ui b/toxygen/ui/views/bans_list_screen.ui
deleted file mode 100644
index 16339d8..0000000
--- a/toxygen/ui/views/bans_list_screen.ui
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 500
- 375
-
-
-
- Form
-
-
-
-
- 0
- 0
- 500
- 375
-
-
-
-
-
-
-
diff --git a/toxygen/ui/views/create_group_screen.ui b/toxygen/ui/views/create_group_screen.ui
deleted file mode 100644
index 3a3358a..0000000
--- a/toxygen/ui/views/create_group_screen.ui
+++ /dev/null
@@ -1,127 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 640
- 300
-
-
-
- Form
-
-
-
- false
-
-
-
- 20
- 250
- 601
- 41
-
-
-
-
-
-
-
-
-
- 150
- 20
- 470
- 35
-
-
-
-
-
-
- 150
- 80
- 470
- 35
-
-
-
-
-
-
- 20
- 20
- 121
- 31
-
-
-
- TextLabel
-
-
-
-
-
- 20
- 80
- 121
- 31
-
-
-
- TextLabel
-
-
-
-
-
- 20
- 200
- 111
- 17
-
-
-
- TextLabel
-
-
-
-
-
- 20
- 150
- 111
- 17
-
-
-
- TextLabel
-
-
-
-
-
- 150
- 140
- 470
- 35
-
-
-
-
-
-
- 150
- 190
- 470
- 35
-
-
-
-
-
-
-
diff --git a/toxygen/ui/views/create_profile_screen.ui b/toxygen/ui/views/create_profile_screen.ui
deleted file mode 100644
index bfffee5..0000000
--- a/toxygen/ui/views/create_profile_screen.ui
+++ /dev/null
@@ -1,128 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 400
- 340
-
-
-
-
- 400
- 340
-
-
-
-
- 400
- 340
-
-
-
- Form
-
-
-
-
- 30
- 270
- 341
- 51
-
-
-
- PushButton
-
-
-
-
-
- 30
- 170
- 341
- 41
-
-
-
- QLineEdit::Password
-
-
-
-
-
- 30
- 120
- 341
- 41
-
-
-
- QLineEdit::Password
-
-
-
-
-
- 30
- 80
- 330
- 20
-
-
-
- TextLabel
-
-
-
-
-
- 30
- 10
- 330
- 23
-
-
-
- RadioButton
-
-
- true
-
-
-
-
-
- 30
- 40
- 330
- 23
-
-
-
- RadioButton
-
-
-
-
-
- 30
- 220
- 341
- 30
-
-
-
-
-
-
- Qt::AlignCenter
-
-
-
-
-
-
diff --git a/toxygen/ui/views/gc_ban_item.ui b/toxygen/ui/views/gc_ban_item.ui
deleted file mode 100644
index a57d0e1..0000000
--- a/toxygen/ui/views/gc_ban_item.ui
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 500
- 100
-
-
-
- Form
-
-
-
-
- 330
- 30
- 161
- 41
-
-
-
- PushButton
-
-
-
-
-
- 15
- 20
- 305
- 20
-
-
-
- TextLabel
-
-
-
-
-
- 15
- 50
- 305
- 20
-
-
-
- TextLabel
-
-
-
-
-
-
diff --git a/toxygen/ui/views/gc_invite_item.ui b/toxygen/ui/views/gc_invite_item.ui
deleted file mode 100644
index 6eddbeb..0000000
--- a/toxygen/ui/views/gc_invite_item.ui
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 600
- 150
-
-
-
- Form
-
-
-
-
- 250
- 30
- 300
- 21
-
-
-
- TextLabel
-
-
-
-
-
- 250
- 70
- 300
- 21
-
-
-
- TextLabel
-
-
-
-
-
- 140
- 30
- 60
- 60
-
-
-
- TextLabel
-
-
-
-
-
- 40
- 50
- 20
- 23
-
-
-
-
-
-
-
-
-
-
diff --git a/toxygen/ui/views/gc_settings_screen.ui b/toxygen/ui/views/gc_settings_screen.ui
deleted file mode 100644
index 526c156..0000000
--- a/toxygen/ui/views/gc_settings_screen.ui
+++ /dev/null
@@ -1,83 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 400
- 220
-
-
-
-
- 400
- 220
-
-
-
-
- 400
- 220
-
-
-
- Form
-
-
-
-
- 10
- 20
- 380
- 20
-
-
-
- TextLabel
-
-
-
-
-
- 10
- 60
- 380
- 40
-
-
-
- PushButton
-
-
-
-
-
- 10
- 120
- 380
- 20
-
-
-
- TextLabel
-
-
-
-
-
- 10
- 160
- 380
- 20
-
-
-
- TextLabel
-
-
-
-
-
-
diff --git a/toxygen/ui/views/group_invites_screen.ui b/toxygen/ui/views/group_invites_screen.ui
deleted file mode 100644
index 183f801..0000000
--- a/toxygen/ui/views/group_invites_screen.ui
+++ /dev/null
@@ -1,113 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 600
- 500
-
-
-
-
- 600
- 500
-
-
-
-
- 600
- 500
-
-
-
- Form
-
-
-
-
- 0
- 150
- 600
- 25
-
-
-
- TextLabel
-
-
- Qt::AlignCenter
-
-
-
-
-
- 0
- 0
- 600
- 341
-
-
-
-
-
-
- 10
- 360
- 350
- 35
-
-
-
-
-
-
- 10
- 410
- 350
- 35
-
-
-
-
-
-
- 390
- 390
- 200
- 35
-
-
-
-
-
-
- 40
- 460
- 201
- 31
-
-
-
- PushButton
-
-
-
-
-
- 360
- 460
- 201
- 31
-
-
-
- PushButton
-
-
-
-
-
-
diff --git a/toxygen/ui/views/group_management_screen.ui b/toxygen/ui/views/group_management_screen.ui
deleted file mode 100644
index 859754b..0000000
--- a/toxygen/ui/views/group_management_screen.ui
+++ /dev/null
@@ -1,110 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 658
- 238
-
-
-
- Form
-
-
-
-
- 180
- 20
- 450
- 41
-
-
-
-
-
-
- 20
- 30
- 145
- 20
-
-
-
- TextLabel
-
-
-
-
-
- 20
- 80
- 145
- 20
-
-
-
- TextLabel
-
-
-
-
-
- 180
- 70
- 450
- 40
-
-
-
- 2
-
-
- 9999
-
-
- 512
-
-
-
-
-
- 20
- 130
- 145
- 20
-
-
-
- TextLabel
-
-
-
-
-
- 180
- 120
- 450
- 40
-
-
-
-
-
-
- 20
- 180
- 611
- 41
-
-
-
- PushButton
-
-
-
-
-
-
diff --git a/toxygen/ui/views/interface_settings_screen.ui b/toxygen/ui/views/interface_settings_screen.ui
deleted file mode 100644
index fb0bcf1..0000000
--- a/toxygen/ui/views/interface_settings_screen.ui
+++ /dev/null
@@ -1,253 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 552
- 847
-
-
-
- Form
-
-
- -
-
-
- Qt::ScrollBarAsNeeded
-
-
- true
-
-
-
-
- 0
- 0
- 532
- 827
-
-
-
-
-
- 30
- 140
- 67
- 17
-
-
-
- TextLabel
-
-
-
-
-
- 20
- 180
- 471
- 31
-
-
-
-
-
-
- 20
- 60
- 471
- 31
-
-
-
-
-
-
- 30
- 20
- 67
- 17
-
-
-
- TextLabel
-
-
-
-
-
- 30
- 220
- 461
- 23
-
-
-
- CheckBox
-
-
-
-
-
- 30
- 280
- 461
- 221
-
-
-
- GroupBox
-
-
-
-
- 30
- 40
- 92
- 23
-
-
-
- CheckBox
-
-
-
-
-
- 30
- 80
- 411
- 17
-
-
-
- TextLabel
-
-
-
-
-
- 30
- 120
- 411
- 31
-
-
-
-
-
-
-
- 30
- 250
- 461
- 23
-
-
-
- CheckBox
-
-
-
-
-
- 30
- 750
- 471
- 40
-
-
-
- PushButton
-
-
-
-
-
- 30
- 690
- 471
- 40
-
-
-
- PushButton
-
-
-
-
-
- 30
- 520
- 461
- 23
-
-
-
- CheckBox
-
-
-
-
-
- 30
- 550
- 471
- 131
-
-
-
- GroupBox
-
-
-
-
- 30
- 30
- 421
- 23
-
-
-
- RadioButton
-
-
-
-
-
- 30
- 60
- 431
- 23
-
-
-
- RadioButton
-
-
-
-
-
- 30
- 90
- 421
- 23
-
-
-
- RadioButton
-
-
-
-
-
-
-
-
-
-
-
diff --git a/toxygen/ui/views/join_group_screen.ui b/toxygen/ui/views/join_group_screen.ui
deleted file mode 100644
index 077a332..0000000
--- a/toxygen/ui/views/join_group_screen.ui
+++ /dev/null
@@ -1,139 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 740
- 320
-
-
-
-
- 740
- 320
-
-
-
-
- 740
- 320
-
-
-
- Form
-
-
-
-
- 30
- 30
- 67
- 17
-
-
-
- TextLabel
-
-
-
-
-
- 30
- 90
- 67
- 17
-
-
-
- TextLabel
-
-
-
-
- false
-
-
-
- 30
- 260
- 680
- 51
-
-
-
-
-
-
-
-
-
- 190
- 20
- 520
- 41
-
-
-
-
-
-
- 190
- 80
- 520
- 41
-
-
-
-
-
-
- 30
- 150
- 67
- 17
-
-
-
- TextLabel
-
-
-
-
-
- 30
- 210
- 67
- 17
-
-
-
- TextLabel
-
-
-
-
-
- 190
- 140
- 520
- 41
-
-
-
-
-
-
- 190
- 200
- 520
- 41
-
-
-
-
-
-
-
diff --git a/toxygen/ui/views/login_screen.ui b/toxygen/ui/views/login_screen.ui
deleted file mode 100644
index 50ca1e0..0000000
--- a/toxygen/ui/views/login_screen.ui
+++ /dev/null
@@ -1,136 +0,0 @@
-
-
- loginScreen
-
-
-
- 0
- 0
- 400
- 200
-
-
-
-
- 400
- 200
-
-
-
-
- 400
- 200
-
-
-
- Form
-
-
-
-
- 0
- 5
- 401
- 30
-
-
-
-
- Garuda
- 16
- 75
- true
-
-
-
- Toxygen
-
-
- Qt::AlignCenter
-
-
-
-
-
- 10
- 40
- 180
- 150
-
-
-
- GroupBox
-
-
- Qt::AlignCenter
-
-
-
-
- 10
- 110
- 160
- 27
-
-
-
- PushButton
-
-
-
-
-
-
- 210
- 40
- 180
- 150
-
-
-
- GroupBox
-
-
- Qt::AlignCenter
-
-
-
-
- 10
- 40
- 160
- 27
-
-
-
-
-
-
- 10
- 75
- 160
- 27
-
-
-
- CheckBox
-
-
-
-
-
- 10
- 110
- 160
- 27
-
-
-
- PushButton
-
-
-
-
-
-
-
diff --git a/toxygen/ui/views/ms_left_column.ui b/toxygen/ui/views/ms_left_column.ui
deleted file mode 100644
index ffbff71..0000000
--- a/toxygen/ui/views/ms_left_column.ui
+++ /dev/null
@@ -1,94 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 270
- 500
-
-
-
- PointingHandCursor
-
-
- Form
-
-
-
-
- 5
- 5
- 64
- 64
-
-
-
- PointingHandCursor
-
-
- TextLabel
-
-
-
-
-
- 0
- 75
- 150
- 25
-
-
-
-
-
-
- 150
- 75
- 120
- 25
-
-
-
-
-
-
- 0
- 77
- 20
- 20
-
-
-
- TextLabel
-
-
-
-
-
- 0
- 100
- 270
- 400
-
-
-
-
-
-
- 0
- 100
- 270
- 30
-
-
-
- PushButton
-
-
-
-
-
-
diff --git a/toxygen/ui/views/network_settings_screen.ui b/toxygen/ui/views/network_settings_screen.ui
deleted file mode 100644
index aacf1e0..0000000
--- a/toxygen/ui/views/network_settings_screen.ui
+++ /dev/null
@@ -1,180 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 400
- 500
-
-
-
-
- 400
- 500
-
-
-
-
- 400
- 500
-
-
-
- Form
-
-
-
-
- 30
- 20
- 150
- 30
-
-
-
- CheckBox
-
-
-
-
-
- 210
- 20
- 150
- 30
-
-
-
- CheckBox
-
-
-
-
-
- 30
- 140
- 150
- 30
-
-
-
- CheckBox
-
-
-
-
-
- 30
- 190
- 150
- 25
-
-
-
- RadioButton
-
-
-
-
-
- 30
- 230
- 150
- 25
-
-
-
- RadioButton
-
-
-
-
-
- 30
- 100
- 150
- 30
-
-
-
- CheckBox
-
-
-
-
-
- 30
- 280
- 60
- 20
-
-
-
- TextLabel
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
-
-
- 30
- 330
- 60
- 20
-
-
-
- TextLabel
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
-
-
- 30
- 370
- 340
- 40
-
-
-
- PushButton
-
-
-
-
-
- 30
- 60
- 340
- 30
-
-
-
- CheckBox
-
-
-
-
-
- 30
- 420
- 340
- 65
-
-
-
- TextLabel
-
-
-
-
-
-
diff --git a/toxygen/ui/views/notifications_settings_screen.ui b/toxygen/ui/views/notifications_settings_screen.ui
deleted file mode 100644
index 67e2dc6..0000000
--- a/toxygen/ui/views/notifications_settings_screen.ui
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 320
- 201
-
-
-
- Form
-
-
-
-
- 20
- 20
- 271
- 41
-
-
-
- CheckBox
-
-
-
-
-
- 20
- 60
- 271
- 41
-
-
-
- CheckBox
-
-
-
-
-
- 20
- 100
- 271
- 41
-
-
-
- CheckBox
-
-
-
-
-
- 20
- 140
- 271
- 41
-
-
-
- CheckBox
-
-
-
-
-
-
diff --git a/toxygen/ui/views/peer_screen.ui b/toxygen/ui/views/peer_screen.ui
deleted file mode 100644
index e8e9e31..0000000
--- a/toxygen/ui/views/peer_screen.ui
+++ /dev/null
@@ -1,200 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 600
- 500
-
-
-
-
- 600
- 500
-
-
-
-
- 600
- 500
-
-
-
- Form
-
-
-
-
- 110
- 10
- 431
- 40
-
-
-
- TextLabel
-
-
-
-
-
- 50
- 140
- 500
- 50
-
-
-
- PushButton
-
-
-
-
-
- 50
- 100
- 500
- 23
-
-
-
- CheckBox
-
-
-
-
-
- 50
- 300
- 500
- 161
-
-
-
- GroupBox
-
-
-
-
- 380
- 50
- 101
- 41
-
-
-
- PushButton
-
-
-
-
-
- 40
- 40
- 251
- 23
-
-
-
- RadioButton
-
-
- true
-
-
-
-
-
- 40
- 80
- 251
- 23
-
-
-
- RadioButton
-
-
-
-
-
- 40
- 120
- 251
- 23
-
-
-
- RadioButton
-
-
-
-
-
- 380
- 100
- 101
- 41
-
-
-
- PushButton
-
-
-
-
-
-
- 50
- 60
- 67
- 20
-
-
-
- TextLabel
-
-
-
-
-
- 130
- 60
- 411
- 20
-
-
-
- TextLabel
-
-
-
-
-
- 50
- 210
- 500
- 50
-
-
-
- PushButton
-
-
-
-
-
- 130
- 55
- 291
- 30
-
-
-
-
-
-
-
diff --git a/toxygen/ui/views/profile_settings_screen.ui b/toxygen/ui/views/profile_settings_screen.ui
deleted file mode 100644
index ece0083..0000000
--- a/toxygen/ui/views/profile_settings_screen.ui
+++ /dev/null
@@ -1,280 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 900
- 702
-
-
-
- Form
-
-
-
-
- 30
- 10
- 161
- 31
-
-
-
- TextLabel
-
-
-
-
-
- 30
- 90
- 161
- 31
-
-
-
- TextLabel
-
-
-
-
-
- 30
- 50
- 421
- 31
-
-
-
-
-
-
- 30
- 130
- 421
- 31
-
-
-
-
-
-
- 520
- 30
- 311
- 31
-
-
-
-
-
-
- 40
- 180
- 131
- 21
-
-
-
- TextLabel
-
-
-
-
-
- 40
- 210
- 831
- 61
-
-
-
- TextLabel
-
-
- true
-
-
-
-
-
- 40
- 280
- 371
- 31
-
-
-
- PushButton
-
-
-
-
-
- 440
- 280
- 371
- 31
-
-
-
- PushButton
-
-
-
-
-
- 520
- 80
- 321
- 35
-
-
-
- PushButton
-
-
-
-
-
- 520
- 130
- 321
- 35
-
-
-
- PushButton
-
-
-
-
-
- 60
- 380
- 161
- 31
-
-
-
- TextLabel
-
-
-
-
-
- 50
- 420
- 421
- 31
-
-
-
-
-
-
- 50
- 470
- 421
- 31
-
-
-
-
-
-
- 500
- 420
- 381
- 21
-
-
-
- TextLabel
-
-
-
-
-
- 60
- 580
- 381
- 21
-
-
-
- TextLabel
-
-
-
-
-
- 40
- 630
- 831
- 35
-
-
-
- PushButton
-
-
-
-
-
- 50
- 520
- 421
- 35
-
-
-
- PushButton
-
-
-
-
-
- 500
- 470
- 381
- 21
-
-
-
- TextLabel
-
-
-
-
-
- 40
- 330
- 371
- 35
-
-
-
- PushButton
-
-
-
-
-
- 440
- 330
- 371
- 35
-
-
-
- PushButton
-
-
-
-
-
-
diff --git a/toxygen/ui/views/self_peer_screen.ui b/toxygen/ui/views/self_peer_screen.ui
deleted file mode 100644
index 38e1f88..0000000
--- a/toxygen/ui/views/self_peer_screen.ui
+++ /dev/null
@@ -1,119 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 600
- 500
-
-
-
-
- 600
- 500
-
-
-
-
- 600
- 500
-
-
-
- Form
-
-
-
-
- 50
- 120
- 67
- 20
-
-
-
- TextLabel
-
-
-
-
-
- 50
- 250
- 500
- 50
-
-
-
- PushButton
-
-
-
-
-
- 140
- 110
- 400
- 40
-
-
-
-
-
-
- 50
- 40
- 67
- 20
-
-
-
- TextLabel
-
-
-
-
-
- 50
- 190
- 67
- 20
-
-
-
- TextLabel
-
-
-
-
-
- 140
- 190
- 411
- 20
-
-
-
- TextLabel
-
-
-
-
-
- 50
- 330
- 500
- 50
-
-
-
- PushButton
-
-
-
-
-
-
diff --git a/toxygen/ui/views/update_settings_screen.ui b/toxygen/ui/views/update_settings_screen.ui
deleted file mode 100644
index 76e7c57..0000000
--- a/toxygen/ui/views/update_settings_screen.ui
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 400
- 120
-
-
-
-
- 400
- 120
-
-
-
-
- 400
- 120
-
-
-
- Form
-
-
-
-
- 25
- 5
- 350
- 20
-
-
-
- TextLabel
-
-
-
-
-
- 25
- 30
- 350
- 30
-
-
-
-
-
-
- 25
- 70
- 350
- 30
-
-
-
- PushButton
-
-
-
-
-
-
diff --git a/toxygen/ui/views/video_settings_screen.ui b/toxygen/ui/views/video_settings_screen.ui
deleted file mode 100644
index cfa36fb..0000000
--- a/toxygen/ui/views/video_settings_screen.ui
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
- Form
-
-
-
- 0
- 0
- 400
- 120
-
-
-
-
- 400
- 120
-
-
-
-
- 400
- 120
-
-
-
- Form
-
-
-
-
- 25
- 5
- 350
- 20
-
-
-
- TextLabel
-
-
-
-
-
- 25
- 30
- 350
- 30
-
-
-
-
-
-
- 25
- 70
- 350
- 30
-
-
-
- PushButton
-
-
-
-
-
- 25
- 70
- 350
- 30
-
-
-
-
-
-
-
diff --git a/toxygen/ui/widgets.py b/toxygen/ui/widgets.py
deleted file mode 100644
index e7fe623..0000000
--- a/toxygen/ui/widgets.py
+++ /dev/null
@@ -1,197 +0,0 @@
-from PyQt5 import QtCore, QtGui, QtWidgets
-import utils.ui as util_ui
-
-
-class DataLabel(QtWidgets.QLabel):
- """
- Label with elided text
- """
- def setText(self, 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 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().__init__()
- self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
- self.center()
-
- def center(self):
- qr = self.frameGeometry()
- cp = QtWidgets.QDesktopWidget().availableGeometry().center()
- qr.moveCenter(cp)
- self.move(qr.topLeft())
-
-
-class DialogWithResult(QtWidgets.QWidget):
-
- def __init__(self, parent=None):
- super().__init__(parent)
- self._result = None
-
- def get_result(self):
- return self._result
-
- result = property(get_result)
-
- def close_with_result(self, result):
- self._result = result
- self.close()
-
-
-class LineEdit(QtWidgets.QLineEdit):
-
- def __init__(self, parent=None):
- super().__init__(parent)
-
- def contextMenuEvent(self, event):
- menu = create_menu(self.createStandardContextMenu())
- menu.exec_(event.globalPos())
- del menu
-
-
-class QRightClickButton(QtWidgets.QPushButton):
- """
- Button with right click support
- """
-
- rightClicked = QtCore.pyqtSignal()
-
- def __init__(self, parent=None):
- super().__init__(parent)
-
- def mousePressEvent(self, event):
- if event.button() == QtCore.Qt.RightButton:
- self.rightClicked.emit()
- else:
- super().mousePressEvent(event)
-
-
-class RubberBand(QtWidgets.QRubberBand):
-
- def __init__(self):
- super().__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)
- self.painter = QtGui.QPainter()
-
- def paintEvent(self, event):
-
- self.painter.begin(self)
- self.painter.setPen(self.pen)
- self.painter.drawRect(event.rect())
- self.painter.end()
-
-
-class RubberBandWindow(QtWidgets.QWidget):
-
- def __init__(self, parent):
- super().__init__()
- self.parent = parent
- self.setMouseTracking(True)
- self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
- self.showFullScreen()
- self.setWindowOpacity(0.5)
- self.rubberband = RubberBand()
- self.rubberband.setWindowFlags(self.rubberband.windowFlags() | QtCore.Qt.FramelessWindowHint)
- self.rubberband.setAttribute(QtCore.Qt.WA_TranslucentBackground)
-
- def mousePressEvent(self, event):
- self.origin = event.pos()
- self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize()))
- self.rubberband.show()
- QtWidgets.QWidget.mousePressEvent(self, event)
-
- def mouseMoveEvent(self, event):
- if self.rubberband.isVisible():
- self.rubberband.setGeometry(QtCore.QRect(self.origin, event.pos()).normalized())
- left = QtGui.QRegion(QtCore.QRect(0, 0, self.rubberband.x(), self.height()))
- right = QtGui.QRegion(QtCore.QRect(self.rubberband.x() + self.rubberband.width(), 0, self.width(), self.height()))
- top = QtGui.QRegion(0, 0, self.width(), self.rubberband.y())
- bottom = QtGui.QRegion(0, self.rubberband.y() + self.rubberband.height(), self.width(), self.height())
- self.setMask(left + right + top + bottom)
-
- def keyPressEvent(self, event):
- if event.key() == QtCore.Qt.Key_Escape:
- self.rubberband.setHidden(True)
- self.close()
- else:
- super().keyPressEvent(event)
-
-
-def create_menu(menu):
- """
- :return translated menu
- """
- for action in menu.actions():
- text = action.text()
- if 'Link Location' in text:
- text = text.replace('Copy &Link Location',
- util_ui.tr("Copy link location"))
- elif '&Copy' in text:
- text = text.replace('&Copy', util_ui.tr("Copy"))
- elif 'All' in text:
- text = text.replace('Select All', util_ui.tr("Select all"))
- elif 'Delete' in text:
- text = text.replace('Delete', util_ui.tr("Delete"))
- elif '&Paste' in text:
- text = text.replace('&Paste', util_ui.tr("Paste"))
- elif 'Cu&t' in text:
- text = text.replace('Cu&t', util_ui.tr("Cut"))
- elif '&Undo' in text:
- text = text.replace('&Undo', util_ui.tr("Undo"))
- elif '&Redo' in text:
- text = text.replace('&Redo', util_ui.tr("Redo"))
- else:
- menu.removeAction(action)
- continue
- action.setText(text)
- return menu
-
-
-class MultilineEdit(CenteredWidget):
-
- def __init__(self, title, text, save):
- super(MultilineEdit, self).__init__()
- self.resize(350, 200)
- self.setMinimumSize(QtCore.QSize(350, 200))
- self.setMaximumSize(QtCore.QSize(350, 200))
- self.setWindowTitle(title)
- self.edit = QtWidgets.QTextEdit(self)
- self.edit.setGeometry(QtCore.QRect(0, 0, 350, 150))
- self.edit.setText(text)
- self.button = QtWidgets.QPushButton(self)
- self.button.setGeometry(QtCore.QRect(0, 150, 350, 50))
- self.button.setText(util_ui.tr("Save"))
- self.button.clicked.connect(self.button_click)
- self.center()
- self.save = save
-
- def button_click(self):
- self.save(self.edit.toPlainText())
- self.close()
-
-
-class LineEditWithEnterSupport(LineEdit):
-
- def __init__(self, enter_action, parent=None):
- super().__init__(parent)
- self._action = enter_action
-
- def keyPressEvent(self, event):
- if event.key() == QtCore.Qt.Key_Return:
- self._action()
- else:
- super().keyPressEvent(event)
diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py
deleted file mode 100644
index 128e85e..0000000
--- a/toxygen/ui/widgets_factory.py
+++ /dev/null
@@ -1,97 +0,0 @@
-from ui.main_screen_widgets import *
-from ui.menu import *
-from ui.groups_widgets import *
-from ui.peer_screen import *
-from ui.self_peer_screen import *
-from ui.group_invites_widgets import *
-from ui.group_settings_widgets import *
-from ui.group_bans_widgets import *
-from ui.profile_settings_screen import ProfileSettings
-
-
-class WidgetsFactory:
-
- def __init__(self, settings, profile, profile_manager, contacts_manager, file_transfer_handler, smiley_loader,
- plugin_loader, toxes, version, groups_service, history, contacts_provider):
- self._settings = settings
- self._profile = profile
- self._profile_manager = profile_manager
- self._contacts_manager = contacts_manager
- self._file_transfer_handler = file_transfer_handler
- self._smiley_loader = smiley_loader
- self._plugin_loader = plugin_loader
- self._toxes = toxes
- self._version = version
- self._groups_service = groups_service
- self._history = history
- self._contacts_provider = contacts_provider
-
- def create_screenshot_window(self, *args):
- return ScreenShotWindow(self._file_transfer_handler, self._contacts_manager, *args)
-
- def create_welcome_window(self):
- return WelcomeScreen(self._settings)
-
- def create_profile_settings_window(self):
- return ProfileSettings(self._profile, self._profile_manager, self._settings, self._toxes)
-
- def create_network_settings_window(self):
- return NetworkSettings(self._settings, self._profile.restart)
-
- def create_audio_settings_window(self):
- return AudioSettings(self._settings)
-
- def create_video_settings_window(self):
- return VideoSettings(self._settings)
-
- def create_update_settings_window(self):
- return UpdateSettings(self._settings, self._version)
-
- def create_plugins_settings_window(self):
- return PluginsSettings(self._plugin_loader)
-
- def create_add_contact_window(self, tox_id):
- return AddContact(self._settings, self._contacts_manager, tox_id)
-
- def create_privacy_settings_window(self):
- return PrivacySettings(self._contacts_manager, self._settings)
-
- def create_interface_settings_window(self):
- return InterfaceSettings(self._settings, self._smiley_loader)
-
- def create_notification_settings_window(self):
- return NotificationsSettings(self._settings)
-
- def create_smiley_window(self, parent):
- return SmileyWindow(parent, self._smiley_loader)
-
- def create_sticker_window(self):
- return StickerWindow(self._file_transfer_handler, self._contacts_manager)
-
- def create_group_screen_window(self):
- return CreateGroupScreen(self._groups_service, self._profile)
-
- def create_join_group_screen_window(self):
- return JoinGroupScreen(self._groups_service, self._profile)
-
- def create_search_screen(self, messages):
- return SearchScreen(self._contacts_manager, self._history, messages, messages.parent())
-
- def create_peer_screen_window(self, group, peer_id):
- return PeerScreen(self._contacts_manager, self._groups_service, group, peer_id)
-
- def create_self_peer_screen_window(self, group):
- return SelfPeerScreen(self._contacts_manager, self._groups_service, group)
-
- def create_group_invites_window(self):
- return GroupInvitesScreen(self._groups_service, self._profile, self._contacts_provider)
-
- def create_group_management_screen(self, group):
- return GroupManagementScreen(self._groups_service, group)
-
- @staticmethod
- def create_group_settings_screen(group):
- return GroupSettingsScreen(group)
-
- def create_groups_bans_screen(self, group):
- return GroupBansScreen(self._groups_service, group)
diff --git a/toxygen/updater/__init__.py b/toxygen/updater/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/toxygen/updater/updater.py b/toxygen/updater/updater.py
deleted file mode 100644
index 329353c..0000000
--- a/toxygen/updater/updater.py
+++ /dev/null
@@ -1,124 +0,0 @@
-import utils.util as util
-import utils.ui as util_ui
-import os
-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, settings):
- 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, settings):
- 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():
- if platform.system() == 'Windows':
- return ['python', 'toxygen_updater.py', url, version]
- else:
- return ['python3', 'toxygen_updater.py', url, version]
- elif platform.system() == 'Windows':
- return [util.curr_directory() + '/toxygen_updater.exe', url, version]
- else:
- 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, settings):
- netman = QtNetwork.QNetworkAccessManager()
- proxy = QtNetwork.QNetworkProxy()
- if settings['proxy_type']:
- proxy.setType(QtNetwork.QNetworkProxy.Socks5Proxy if settings['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy)
- proxy.setHostName(settings['proxy_host'])
- proxy.setPort(settings['proxy_port'])
- netman.setProxy(proxy)
- url = test_url(version)
- try:
- request = QtNetwork.QNetworkRequest()
- request.setUrl(QtCore.QUrl(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
-
-
-def start_update_if_needed(version, settings):
- updating = False
- if settings['update'] and updater_available() and connection_available(): # auto update
- version = check_for_updates(version, settings)
- if version is not None:
- if settings['update'] == 2:
- download(version)
- updating = True
- else:
- reply = util_ui.question(util_ui.tr('Update for Toxygen was found. Download and install it?'))
- if reply:
- download(version)
- updating = True
- return updating
diff --git a/toxygen/user_data/__init__.py b/toxygen/user_data/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/toxygen/user_data/backup_service.py b/toxygen/user_data/backup_service.py
deleted file mode 100644
index bb0cef9..0000000
--- a/toxygen/user_data/backup_service.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import os.path
-from utils.util import get_profile_name_from_path, join_path
-
-
-class BackupService:
-
- def __init__(self, settings, profile_manager):
- self._settings = settings
- self._profile_name = get_profile_name_from_path(profile_manager.get_path())
-
- settings.settings_saved_event.add_callback(self._settings_saved)
- profile_manager.profile_saved_event.add_callback(self._profile_saved)
-
- def _settings_saved(self, data):
- if not self._check_if_should_save_backup():
- return
-
- file_path = join_path(self._get_backup_directory(), self._profile_name + '.json')
-
- with open(file_path, 'wt') as fl:
- fl.write(data)
-
- def _profile_saved(self, data):
- if not self._check_if_should_save_backup():
- return
-
- file_path = join_path(self._get_backup_directory(), self._profile_name + '.tox')
-
- with open(file_path, 'wb') as fl:
- fl.write(data)
-
- def _check_if_should_save_backup(self):
- backup_directory = self._get_backup_directory()
- if backup_directory is None:
- return False
-
- return os.path.exists(backup_directory) and os.path.isdir(backup_directory)
-
- def _get_backup_directory(self):
- return self._settings['backup_directory']
diff --git a/toxygen/user_data/profile_manager.py b/toxygen/user_data/profile_manager.py
deleted file mode 100644
index 05e2f2d..0000000
--- a/toxygen/user_data/profile_manager.py
+++ /dev/null
@@ -1,90 +0,0 @@
-import utils.util as util
-import os
-from user_data.settings import Settings
-from common.event import Event
-
-
-class ProfileManager:
- """
- Class with methods for search, load and save profiles
- """
- def __init__(self, toxes, path):
- self._toxes = toxes
- self._path = path
- self._directory = os.path.dirname(path)
- self._profile_saved_event = Event()
- # create /avatars if not exists:
- avatars_directory = util.join_path(self._directory, 'avatars')
- if not os.path.exists(avatars_directory):
- os.makedirs(avatars_directory)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Properties
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_profile_saved_event(self):
- return self._profile_saved_event
-
- profile_saved_event = property(get_profile_saved_event)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Public methods
- # -----------------------------------------------------------------------------------------------------------------
-
- def open_profile(self):
- with open(self._path, 'rb') as fl:
- data = fl.read()
- if data:
- return data
- else:
- raise IOError('Save file has zero size!')
-
- def get_dir(self):
- return self._directory
-
- def get_path(self):
- return self._path
-
- def save_profile(self, data):
- if self._toxes.has_password():
- data = self._toxes.pass_encrypt(data)
- with open(self._path, 'wb') as fl:
- fl.write(data)
- print('Profile saved successfully')
-
- self._profile_saved_event(data)
-
- def export_profile(self, settings, 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(path, 'wb') as fout:
- fout.write(data)
- print('Profile exported successfully')
- util.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.update_path(new_path)
-
- @staticmethod
- def find_profiles():
- """
- Find available tox profiles
- """
- path = Settings.get_default_path()
- result = []
- # check default path
- if not os.path.exists(path):
- os.makedirs(path)
- for fl in os.listdir(path):
- if fl.endswith('.tox'):
- name = fl[:-4]
- result.append((path, name))
- path = util.get_base_directory(__file__)
- # check current directory
- for fl in os.listdir(path):
- if fl.endswith('.tox'):
- name = fl[:-4]
- result.append((path + '/', name))
- return result
diff --git a/toxygen/user_data/settings.py b/toxygen/user_data/settings.py
deleted file mode 100644
index 71422c2..0000000
--- a/toxygen/user_data/settings.py
+++ /dev/null
@@ -1,244 +0,0 @@
-import json
-from utils.util import *
-import pyaudio
-from common.event import Event
-
-
-class Settings(dict):
- """
- Settings of current profile + global app settings
- """
-
- def __init__(self, toxes, path):
- self._path = path
- self._profile_path = path.replace('.json', '.tox')
- self._toxes = toxes
- self._settings_saved_event = Event()
- if os.path.isfile(path):
- with open(path, 'rb') as fl:
- data = fl.read()
- try:
- if toxes.is_data_encrypted(data):
- data = toxes.pass_decrypt(data)
- info = json.loads(str(data, 'utf-8'))
- except Exception as ex:
- info = Settings.get_default_settings()
- log('Parsing settings error: ' + str(ex))
- super().__init__(info)
- self._upgrade()
- else:
- super().__init__(Settings.get_default_settings())
- self.save()
- self.locked = False
- self.closing = False
- self.unlockScreen = False
- p = pyaudio.PyAudio()
- input_devices = output_devices = 0
- for i in range(p.get_device_count()):
- 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': -1, 'width': 640, 'height': 480, 'x': 0, 'y': 0}
-
- # -----------------------------------------------------------------------------------------------------------------
- # Properties
- # -----------------------------------------------------------------------------------------------------------------
-
- def get_settings_saved_event(self):
- return self._settings_saved_event
-
- settings_saved_event = property(get_settings_saved_event)
-
- # -----------------------------------------------------------------------------------------------------------------
- # Public methods
- # -----------------------------------------------------------------------------------------------------------------
-
- def save(self):
- text = json.dumps(self)
- if self._toxes.has_password():
- text = bytes(self._toxes.pass_encrypt(bytes(text, 'utf-8')))
- else:
- text = bytes(text, 'utf-8')
- with open(self._path, 'wb') as fl:
- fl.write(text)
-
- self._settings_saved_event(text)
-
- def close(self):
- path = self._profile_path + '.lock'
- if os.path.isfile(path):
- os.remove(path)
-
- def set_active_profile(self):
- """
- Mark current profile as active
- """
- path = self._profile_path + '.lock'
- with open(path, 'w') as fl:
- fl.write('active')
-
- def export(self, path):
- text = json.dumps(self)
- name = os.path.basename(self._path)
- with open(join_path(path, str(name)), 'w') as fl:
- fl.write(text)
-
- def update_path(self, new_path):
- self._path = new_path
- self.save()
-
- # -----------------------------------------------------------------------------------------------------------------
- # Static methods
- # -----------------------------------------------------------------------------------------------------------------
-
- @staticmethod
- def get_auto_profile():
- p = Settings.get_global_settings_path()
- if not os.path.isfile(p):
- return None
- with open(p) as fl:
- data = fl.read()
- try:
- auto = json.loads(data)
- except Exception as ex:
- log(str(ex))
- auto = {}
- if 'profile_path' in auto:
- path = str(auto['profile_path'])
- if not os.path.isabs(path):
- path = join_path(path, curr_directory(__file__))
- if os.path.isfile(path):
- return path
-
- @staticmethod
- def set_auto_profile(path):
- 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['profile_path'] = str(path)
- with open(p, 'w') as fl:
- fl.write(json.dumps(data))
-
- @staticmethod
- def reset_auto_profile():
- 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 'profile_path' in data:
- del data['profile_path']
- with open(p, 'w') as fl:
- fl.write(json.dumps(data))
-
- @staticmethod
- def is_active_profile(profile_path):
- return os.path.isfile(profile_path + '.lock')
-
- @staticmethod
- def get_default_settings():
- """
- Default profile settings
- """
- return {
- 'theme': 'dark',
- 'ipv6_enabled': False,
- 'udp_enabled': True,
- 'proxy_type': 0,
- 'proxy_host': '127.0.0.1',
- 'proxy_port': 9050,
- 'start_port': 0,
- 'end_port': 0,
- 'tcp_port': 0,
- 'notifications': True,
- 'sound_notifications': False,
- 'language': 'English',
- 'save_history': False,
- 'allow_inline': True,
- 'allow_auto_accept': True,
- 'auto_accept_path': None,
- 'sorting': 0,
- 'auto_accept_from_friends': [],
- 'paused_file_transfers': {},
- 'resend_files': True,
- 'friends_aliases': [],
- 'show_avatars': False,
- 'typing_notifications': False,
- 'calls_sound': True,
- 'blocked': [],
- 'plugins': [],
- 'notes': {},
- 'smileys': True,
- 'smiley_pack': 'default',
- 'mirror_mode': False,
- 'width': 920,
- 'height': 500,
- 'x': 400,
- 'y': 400,
- 'message_font_size': 14,
- 'unread_color': 'red',
- 'save_unsent_only': False,
- 'compact_mode': False,
- 'identicons': True,
- 'show_welcome_screen': True,
- 'close_app': 0,
- 'font': 'Times New Roman',
- 'update': 1,
- 'group_notifications': True,
- 'download_nodes_list': False,
- 'notify_all_gc': False,
- 'lan_discovery': True,
- 'backup_directory': None
- }
-
- @staticmethod
- def supported_languages():
- return {
- 'English': 'en_EN',
- 'French': 'fr_FR',
- 'Russian': 'ru_RU',
- 'Ukrainian': 'uk_UA'
- }
-
- @staticmethod
- def built_in_themes():
- return {
- 'dark': 'dark_style.qss',
- 'default': 'style.qss'
- }
-
- @staticmethod
- def get_global_settings_path():
- return os.path.join(get_base_directory(), 'toxygen.json')
-
- @staticmethod
- def get_default_path():
- system = get_platform()
- 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/'
-
- # -----------------------------------------------------------------------------------------------------------------
- # Private methods
- # -----------------------------------------------------------------------------------------------------------------
-
- def _upgrade(self):
- default = Settings.get_default_settings()
- for key in default:
- if key not in self:
- print(key)
- self[key] = default[key]
diff --git a/toxygen/user_data/toxes.py b/toxygen/user_data/toxes.py
deleted file mode 100644
index 982f287..0000000
--- a/toxygen/user_data/toxes.py
+++ /dev/null
@@ -1,24 +0,0 @@
-
-class ToxES:
-
- def __init__(self, tox_encrypt_save):
- self._tox_encrypt_save = tox_encrypt_save
- self._password = None
-
- def set_password(self, password):
- self._password = password
-
- def has_password(self):
- return bool(self._password)
-
- def is_password(self, password):
- return self._password == password
-
- def is_data_encrypted(self, data):
- return len(data) > 0 and self._tox_encrypt_save.is_data_encrypted(data)
-
- def pass_encrypt(self, data):
- return self._tox_encrypt_save.pass_encrypt(data, self._password)
-
- def pass_decrypt(self, data):
- return self._tox_encrypt_save.pass_decrypt(data, self._password)
diff --git a/toxygen/util.py b/toxygen/util.py
new file mode 100644
index 0000000..5b078fb
--- /dev/null
+++ b/toxygen/util.py
@@ -0,0 +1,49 @@
+import os
+import time
+import shutil
+
+program_version = '0.2.3'
+
+
+def log(data):
+ with open(curr_directory() + '/logs.log', 'a') as fl:
+ fl.write(str(data) + '\n')
+
+
+def curr_directory():
+ return os.path.dirname(os.path.realpath(__file__))
+
+
+def curr_time():
+ return time.strftime('%H:%M')
+
+
+def copy(src, dest):
+ if not os.path.exists(dest):
+ os.makedirs(dest)
+ src_files = os.listdir(src)
+ for file_name in src_files:
+ full_file_name = os.path.join(src, file_name)
+ if os.path.isfile(full_file_name):
+ shutil.copy(full_file_name, dest)
+ else:
+ copy(full_file_name, os.path.join(dest, file_name))
+
+
+def convert_time(t):
+ sec = int(t) - time.timezone
+ m, s = divmod(sec, 60)
+ h, m = divmod(m, 60)
+ d, h = divmod(h, 24)
+ return '%02d:%02d' % (h, m)
+
+
+class Singleton:
+ _instance = None
+
+ def __init__(self):
+ self.__class__._instance = self
+
+ @classmethod
+ def get_instance(cls):
+ return cls._instance
diff --git a/toxygen/utils/__init__.py b/toxygen/utils/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/toxygen/utils/ui.py b/toxygen/utils/ui.py
deleted file mode 100644
index d2d7122..0000000
--- a/toxygen/utils/ui.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from PyQt5 import QtWidgets
-import utils.util as util
-
-
-def tr(s):
- return QtWidgets.QApplication.translate('Toxygen', s)
-
-
-def question(text, title=None):
- reply = QtWidgets.QMessageBox.question(None, title or 'Toxygen', text,
- QtWidgets.QMessageBox.Yes,
- QtWidgets.QMessageBox.No)
- return reply == QtWidgets.QMessageBox.Yes
-
-
-def message_box(text, title=None):
- m_box = QtWidgets.QMessageBox()
- m_box.setText(tr(text))
- m_box.setWindowTitle(title or 'Toxygen')
- m_box.exec_()
-
-
-def text_dialog(text, title='', default_value=''):
- text, ok = QtWidgets.QInputDialog.getText(None, title, text, QtWidgets.QLineEdit.Normal, default_value)
-
- return text, ok
-
-
-def directory_dialog(caption=''):
- return QtWidgets.QFileDialog.getExistingDirectory(None, caption, util.curr_directory(),
- QtWidgets.QFileDialog.DontUseNativeDialog)
-
-
-def file_dialog(caption, file_filter=None):
- return QtWidgets.QFileDialog.getOpenFileName(None, caption, util.curr_directory(), file_filter,
- options=QtWidgets.QFileDialog.DontUseNativeDialog)
-
-
-def save_file_dialog(caption, filter=None):
- return QtWidgets.QFileDialog.getSaveFileName(None, caption, util.curr_directory(),
- filter=filter,
- options=QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
-
-
-def close_all_windows():
- QtWidgets.QApplication.closeAllWindows()
-
-
-def copy_to_clipboard(text):
- clipboard = QtWidgets.QApplication.clipboard()
- clipboard.setText(text)
-
-
-# TODO: all dialogs
diff --git a/toxygen/utils/util.py b/toxygen/utils/util.py
deleted file mode 100644
index 5bd5c3a..0000000
--- a/toxygen/utils/util.py
+++ /dev/null
@@ -1,170 +0,0 @@
-import os
-import time
-import shutil
-import sys
-import re
-import platform
-import datetime
-
-
-def cached(func):
- saved_result = None
-
- def wrapped_func():
- nonlocal saved_result
- if saved_result is None:
- saved_result = func()
-
- return saved_result
-
- return wrapped_func
-
-
-def log(data):
- try:
- with open(join_path(curr_directory(), 'logs.log'), 'a') as fl:
- fl.write(str(data) + '\n')
- except Exception as ex:
- print(ex)
-
-
-def curr_directory(current_file=None):
- return os.path.dirname(os.path.realpath(current_file or __file__))
-
-
-def get_base_directory(current_file=None):
- return os.path.dirname(curr_directory(current_file or __file__))
-
-
-@cached
-def get_images_directory():
- return get_app_directory('images')
-
-
-@cached
-def get_styles_directory():
- return get_app_directory('styles')
-
-
-@cached
-def get_sounds_directory():
- return get_app_directory('sounds')
-
-
-@cached
-def get_stickers_directory():
- return get_app_directory('stickers')
-
-
-@cached
-def get_smileys_directory():
- return get_app_directory('smileys')
-
-
-@cached
-def get_translations_directory():
- return get_app_directory('translations')
-
-
-@cached
-def get_plugins_directory():
- return get_app_directory('plugins')
-
-
-@cached
-def get_libs_directory():
- return get_app_directory('libs')
-
-
-def get_app_directory(directory_name):
- return os.path.join(get_base_directory(), directory_name)
-
-
-def get_profile_name_from_path(path):
- return os.path.basename(path)[:-4]
-
-
-def get_views_path(view_name):
- ui_folder = os.path.join(get_base_directory(), 'ui')
- views_folder = os.path.join(ui_folder, 'views')
-
- return os.path.join(views_folder, view_name + '.ui')
-
-
-def curr_time():
- return time.strftime('%H:%M')
-
-
-def get_unix_time():
- return int(time.time())
-
-
-def join_path(a, b):
- return os.path.join(a, b)
-
-
-def file_exists(file_path):
- return os.path.exists(file_path)
-
-
-def copy(src, dest):
- if not os.path.exists(dest):
- os.makedirs(dest)
- src_files = os.listdir(src)
- for file_name in src_files:
- full_file_name = os.path.join(src, file_name)
- if os.path.isfile(full_file_name):
- shutil.copy(full_file_name, dest)
- else:
- 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):
- 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)
-
-
-@cached
-def time_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
- return result
-
-
-def unix_time_to_long_str(unix_time):
- date_time = datetime.datetime.utcfromtimestamp(unix_time)
-
- return date_time.strftime('%Y-%m-%d %H:%M:%S')
-
-
-@cached
-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
-
-
-@cached
-def get_platform():
- return platform.system()
diff --git a/toxygen/widgets.py b/toxygen/widgets.py
new file mode 100644
index 0000000..2abc7b7
--- /dev/null
+++ b/toxygen/widgets.py
@@ -0,0 +1,133 @@
+try:
+ from PySide import QtCore, QtGui
+except ImportError:
+ from PyQt4 import QtCore, QtGui
+
+
+class DataLabel(QtGui.QLabel):
+ """
+ Label with elided text
+ """
+ def setText(self, text):
+ text = ''.join(c if c <= '\U0010FFFF' else '\u25AF' 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):
+
+ def __init__(self):
+ super(CenteredWidget, self).__init__()
+ self.center()
+
+ def center(self):
+ qr = self.frameGeometry()
+ cp = QtGui.QDesktopWidget().availableGeometry().center()
+ qr.moveCenter(cp)
+ self.move(qr.topLeft())
+
+
+class LineEdit(QtGui.QLineEdit):
+
+ def __init__(self, parent=None):
+ super(LineEdit, self).__init__(parent)
+
+ def contextMenuEvent(self, event):
+ menu = create_menu(self.createStandardContextMenu())
+ menu.exec_(event.globalPos())
+ del menu
+
+
+class QRightClickButton(QtGui.QPushButton):
+ """
+ Button with right click support
+ """
+
+ def __init__(self, parent):
+ super(QRightClickButton, self).__init__(parent)
+
+ def mousePressEvent(self, event):
+ if event.button() == QtCore.Qt.RightButton:
+ self.emit(QtCore.SIGNAL("rightClicked()"))
+ else:
+ super(QRightClickButton, self).mousePressEvent(event)
+
+
+class RubberBand(QtGui.QRubberBand):
+
+ def __init__(self):
+ super(RubberBand, self).__init__(QtGui.QRubberBand.Rectangle, None)
+ self.setPalette(QtGui.QPalette(QtCore.Qt.transparent))
+ self.pen = QtGui.QPen(QtCore.Qt.blue, 4)
+ self.pen.setStyle(QtCore.Qt.SolidLine)
+ self.painter = QtGui.QPainter()
+
+ def paintEvent(self, event):
+
+ self.painter.begin(self)
+ self.painter.setPen(self.pen)
+ self.painter.drawRect(event.rect())
+ self.painter.end()
+
+
+def create_menu(menu):
+ """
+ :return translated menu
+ """
+ for action in menu.actions():
+ 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))
+ elif '&Copy' in text:
+ text = text.replace('&Copy', QtGui.QApplication.translate("MainWindow", "Copy", None,
+ QtGui.QApplication.UnicodeUTF8))
+ elif 'All' in text:
+ text = text.replace('Select All', QtGui.QApplication.translate("MainWindow", "Select all", None,
+ QtGui.QApplication.UnicodeUTF8))
+ elif 'Delete' in text:
+ text = text.replace('Delete', QtGui.QApplication.translate("MainWindow", "Delete", None,
+ QtGui.QApplication.UnicodeUTF8))
+ elif '&Paste' in text:
+ text = text.replace('&Paste', QtGui.QApplication.translate("MainWindow", "Paste", None,
+ QtGui.QApplication.UnicodeUTF8))
+ elif 'Cu&t' in text:
+ text = text.replace('Cu&t', QtGui.QApplication.translate("MainWindow", "Cut", None,
+ QtGui.QApplication.UnicodeUTF8))
+ elif '&Undo' in text:
+ text = text.replace('&Undo', QtGui.QApplication.translate("MainWindow", "Undo", None,
+ QtGui.QApplication.UnicodeUTF8))
+ elif '&Redo' in text:
+ text = text.replace('&Redo', QtGui.QApplication.translate("MainWindow", "Redo", None,
+ QtGui.QApplication.UnicodeUTF8))
+ else:
+ menu.removeAction(action)
+ continue
+ action.setText(text)
+ return menu
+
+
+class MultilineEdit(CenteredWidget):
+
+ def __init__(self, title, text, save):
+ super(MultilineEdit, self).__init__()
+ self.resize(350, 200)
+ self.setMinimumSize(QtCore.QSize(350, 200))
+ self.setMaximumSize(QtCore.QSize(350, 200))
+ self.setWindowTitle(title)
+ self.edit = QtGui.QTextEdit(self)
+ self.edit.setGeometry(QtCore.QRect(0, 0, 350, 150))
+ self.edit.setText(text)
+ self.button = QtGui.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.clicked.connect(self.button_click)
+ self.center()
+ self.save = save
+
+ def button_click(self):
+ self.save(self.edit.toPlainText())
+ self.close()
+
diff --git a/toxygen/wrapper/__init__.py b/toxygen/wrapper/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/toxygen/wrapper/libtox.py b/toxygen/wrapper/libtox.py
deleted file mode 100644
index 01d41f1..0000000
--- a/toxygen/wrapper/libtox.py
+++ /dev/null
@@ -1,61 +0,0 @@
-from ctypes import CDLL
-import utils.util as util
-
-
-class LibToxCore:
-
- def __init__(self):
- platform = util.get_platform()
- if platform == 'Windows':
- self._libtoxcore = CDLL(util.join_path(util.get_libs_directory(), 'libtox.dll'))
- elif platform == 'Darwin':
- self._libtoxcore = CDLL('libtoxcore.dylib')
- else:
- # libtoxcore and libsodium must be installed in your os
- try:
- self._libtoxcore = CDLL('libtoxcore.so')
- except:
- self._libtoxcore = CDLL(util.join_path(util.get_libs_directory(), 'libtoxcore.so'))
-
- def __getattr__(self, item):
- return self._libtoxcore.__getattr__(item)
-
-
-class LibToxAV:
-
- def __init__(self):
- platform = util.get_platform()
- if platform == 'Windows':
- # on Windows av api is in libtox.dll
- self._libtoxav = CDLL(util.join_path(util.get_libs_directory(), 'libtox.dll'))
- elif platform == 'Darwin':
- self._libtoxav = CDLL('libtoxcore.dylib')
- else:
- # /usr/lib/libtoxcore.so must exists
- try:
- self._libtoxav = CDLL('libtoxcore.so')
- except:
- self._libtoxav = CDLL(util.join_path(util.get_libs_directory(), 'libtoxcore.so'))
-
- def __getattr__(self, item):
- return self._libtoxav.__getattr__(item)
-
-
-class LibToxEncryptSave:
-
- def __init__(self):
- platform = util.get_platform()
- if platform == 'Windows':
- # on Windows profile encryption api is in libtox.dll
- self._lib_tox_encrypt_save = CDLL(util.join_path(util.get_libs_directory(), 'libtox.dll'))
- elif platform == 'Darwin':
- self._lib_tox_encrypt_save = CDLL('libtoxcore.dylib')
- else:
- # /usr/lib/libtoxcore.so must exists
- try:
- self._lib_tox_encrypt_save = CDLL('libtoxcore.so')
- except:
- self._lib_tox_encrypt_save = CDLL(util.join_path(util.get_libs_directory(), 'libtoxcore.so'))
-
- def __getattr__(self, item):
- return self._lib_tox_encrypt_save.__getattr__(item)
diff --git a/toxygen/wrapper/toxcore_enums_and_consts.py b/toxygen/wrapper/toxcore_enums_and_consts.py
deleted file mode 100644
index b34e272..0000000
--- a/toxygen/wrapper/toxcore_enums_and_consts.py
+++ /dev/null
@@ -1,954 +0,0 @@
-TOX_USER_STATUS = {
- 'NONE': 0,
- 'AWAY': 1,
- 'BUSY': 2,
-}
-
-TOX_MESSAGE_TYPE = {
- 'NORMAL': 0,
- 'ACTION': 1,
-}
-
-TOX_PROXY_TYPE = {
- 'NONE': 0,
- 'HTTP': 1,
- 'SOCKS5': 2,
-}
-
-TOX_SAVEDATA_TYPE = {
- 'NONE': 0,
- 'TOX_SAVE': 1,
- 'SECRET_KEY': 2,
-}
-
-TOX_ERR_OPTIONS_NEW = {
- 'OK': 0,
- 'MALLOC': 1,
-}
-
-TOX_ERR_NEW = {
- 'OK': 0,
- 'NULL': 1,
- 'MALLOC': 2,
- 'PORT_ALLOC': 3,
- 'PROXY_BAD_TYPE': 4,
- 'PROXY_BAD_HOST': 5,
- 'PROXY_BAD_PORT': 6,
- 'PROXY_NOT_FOUND': 7,
- 'LOAD_ENCRYPTED': 8,
- 'LOAD_BAD_FORMAT': 9,
-}
-
-TOX_ERR_BOOTSTRAP = {
- 'OK': 0,
- 'NULL': 1,
- 'BAD_HOST': 2,
- 'BAD_PORT': 3,
-}
-
-TOX_CONNECTION = {
- 'NONE': 0,
- 'TCP': 1,
- 'UDP': 2,
-}
-
-TOX_ERR_SET_INFO = {
- 'OK': 0,
- 'NULL': 1,
- 'TOO_LONG': 2,
-}
-
-TOX_ERR_FRIEND_ADD = {
- 'OK': 0,
- 'NULL': 1,
- 'TOO_LONG': 2,
- 'NO_MESSAGE': 3,
- 'OWN_KEY': 4,
- 'ALREADY_SENT': 5,
- 'BAD_CHECKSUM': 6,
- 'SET_NEW_NOSPAM': 7,
- 'MALLOC': 8,
-}
-
-TOX_ERR_FRIEND_DELETE = {
- 'OK': 0,
- 'FRIEND_NOT_FOUND': 1,
-}
-
-TOX_ERR_FRIEND_BY_PUBLIC_KEY = {
- 'OK': 0,
- 'NULL': 1,
- 'NOT_FOUND': 2,
-}
-
-TOX_ERR_FRIEND_GET_PUBLIC_KEY = {
- 'OK': 0,
- 'FRIEND_NOT_FOUND': 1,
-}
-
-TOX_ERR_FRIEND_GET_LAST_ONLINE = {
- 'OK': 0,
- 'FRIEND_NOT_FOUND': 1,
-}
-
-TOX_ERR_FRIEND_QUERY = {
- 'OK': 0,
- 'NULL': 1,
- 'FRIEND_NOT_FOUND': 2,
-}
-
-TOX_ERR_SET_TYPING = {
- 'OK': 0,
- 'FRIEND_NOT_FOUND': 1,
-}
-
-TOX_ERR_FRIEND_SEND_MESSAGE = {
- 'OK': 0,
- 'NULL': 1,
- 'FRIEND_NOT_FOUND': 2,
- 'FRIEND_NOT_CONNECTED': 3,
- 'SENDQ': 4,
- 'TOO_LONG': 5,
- 'EMPTY': 6,
-}
-
-TOX_FILE_KIND = {
- 'DATA': 0,
- 'AVATAR': 1,
-}
-
-TOX_FILE_CONTROL = {
- 'RESUME': 0,
- 'PAUSE': 1,
- 'CANCEL': 2,
-}
-
-TOX_ERR_FILE_CONTROL = {
- 'OK': 0,
- 'FRIEND_NOT_FOUND': 1,
- 'FRIEND_NOT_CONNECTED': 2,
- 'NOT_FOUND': 3,
- 'NOT_PAUSED': 4,
- 'DENIED': 5,
- 'ALREADY_PAUSED': 6,
- 'SENDQ': 7,
-}
-
-TOX_ERR_FILE_SEEK = {
- 'OK': 0,
- 'FRIEND_NOT_FOUND': 1,
- 'FRIEND_NOT_CONNECTED': 2,
- 'NOT_FOUND': 3,
- 'DENIED': 4,
- 'INVALID_POSITION': 5,
- 'SENDQ': 6,
-}
-
-TOX_ERR_FILE_GET = {
- 'OK': 0,
- 'NULL': 1,
- 'FRIEND_NOT_FOUND': 2,
- 'NOT_FOUND': 3,
-}
-
-TOX_ERR_FILE_SEND = {
- 'OK': 0,
- 'NULL': 1,
- 'FRIEND_NOT_FOUND': 2,
- 'FRIEND_NOT_CONNECTED': 3,
- 'NAME_TOO_LONG': 4,
- 'TOO_MANY': 5,
-}
-
-TOX_ERR_FILE_SEND_CHUNK = {
- 'OK': 0,
- 'NULL': 1,
- 'FRIEND_NOT_FOUND': 2,
- 'FRIEND_NOT_CONNECTED': 3,
- 'NOT_FOUND': 4,
- 'NOT_TRANSFERRING': 5,
- 'INVALID_LENGTH': 6,
- 'SENDQ': 7,
- 'WRONG_POSITION': 8,
-}
-
-TOX_ERR_FRIEND_CUSTOM_PACKET = {
- 'OK': 0,
- 'NULL': 1,
- 'FRIEND_NOT_FOUND': 2,
- 'FRIEND_NOT_CONNECTED': 3,
- 'INVALID': 4,
- 'EMPTY': 5,
- 'TOO_LONG': 6,
- 'SENDQ': 7,
-}
-
-TOX_ERR_GET_PORT = {
- 'OK': 0,
- 'NOT_BOUND': 1,
-}
-
-TOX_GROUP_PRIVACY_STATE = {
-
- #
- # The group is considered to be public. Anyone may join the group using the Chat ID.
- #
- # If the group is in this state, even if the Chat ID is never explicitly shared
- # with someone outside of the group, information including the Chat ID, IP addresses,
- # and peer ID's (but not Tox ID's) is visible to anyone with access to a node
- # storing a DHT entry for the given group.
- #
- 'PUBLIC': 0,
-
- #
- # The group is considered to be private. The only way to join the group is by having
- # someone in your contact list send you an invite.
- #
- # If the group is in this state, no group information (mentioned above) is present in the DHT;
- # the DHT is not used for any purpose at all. If a public group is set to private,
- # all DHT information related to the group will expire shortly.
- #
- 'PRIVATE': 1
-}
-
-TOX_GROUP_ROLE = {
-
- #
- # May kick and ban all other peers as well as set their role to anything (except founder).
- # Founders may also set the group password, toggle the privacy state, and set the peer limit.
- #
- 'FOUNDER': 0,
-
- #
- # May kick, ban and set the user and observer roles for peers below this role.
- # May also set the group topic.
- #
- 'MODERATOR': 1,
-
- #
- # May communicate with other peers normally.
- #
- 'USER': 2,
-
- #
- # May observe the group and ignore peers; may not communicate with other peers or with the group.
- #
- 'OBSERVER': 3
-}
-
-TOX_ERR_GROUP_NEW = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_NEW_OK': 0,
-
- #
- # The group name exceeded TOX_GROUP_MAX_GROUP_NAME_LENGTH.
- #
- 'TOX_ERR_GROUP_NEW_TOO_LONG': 1,
-
- #
- # group_name is NULL or length is zero.
- #
- 'TOX_ERR_GROUP_NEW_EMPTY': 2,
-
- #
- # TOX_GROUP_PRIVACY_STATE is an invalid type.
- #
- 'TOX_ERR_GROUP_NEW_PRIVACY': 3,
-
- #
- # The group instance failed to initialize.
- #
- 'TOX_ERR_GROUP_NEW_INIT': 4,
-
- #
- # The group state failed to initialize. This usually indicates that something went wrong
- # related to cryptographic signing.
- #
- 'TOX_ERR_GROUP_NEW_STATE': 5,
-
- #
- # The group failed to announce to the DHT. This indicates a network related error.
- #
- 'TOX_ERR_GROUP_NEW_ANNOUNCE': 6,
-}
-
-TOX_ERR_GROUP_JOIN = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_JOIN_OK': 0,
-
- #
- # The group instance failed to initialize.
- #
- 'TOX_ERR_GROUP_JOIN_INIT': 1,
-
- #
- # The chat_id pointer is set to NULL or a group with chat_id already exists. This usually
- # happens if the client attempts to create multiple sessions for the same group.
- #
- 'TOX_ERR_GROUP_JOIN_BAD_CHAT_ID': 2,
-
- #
- # Password length exceeded TOX_GROUP_MAX_PASSWORD_SIZE.
- #
- 'TOX_ERR_GROUP_JOIN_TOO_LONG': 3,
-}
-
-TOX_ERR_GROUP_RECONNECT = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_RECONNECT_OK': 0,
-
- #
- # The group number passed did not designate a valid group.
- #
- 'TOX_ERR_GROUP_RECONNECT_GROUP_NOT_FOUND': 1,
-}
-
-TOX_ERR_GROUP_LEAVE = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_LEAVE_OK': 0,
-
- #
- # The group number passed did not designate a valid group.
- #
- 'TOX_ERR_GROUP_LEAVE_GROUP_NOT_FOUND': 1,
-
- #
- # Message length exceeded 'TOX_GROUP_MAX_PART_LENGTH.
- #
- 'TOX_ERR_GROUP_LEAVE_TOO_LONG': 2,
-
- #
- # The parting packet failed to send.
- #
- 'TOX_ERR_GROUP_LEAVE_FAIL_SEND': 3,
-
- #
- # The group chat instance failed to be deleted. This may occur due to memory related errors.
- #
- 'TOX_ERR_GROUP_LEAVE_DELETE_FAIL': 4,
-}
-
-TOX_ERR_GROUP_SELF_QUERY = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_SELF_QUERY_OK': 0,
-
- #
- # The group number passed did not designate a valid group.
- #
- 'TOX_ERR_GROUP_SELF_QUERY_GROUP_NOT_FOUND': 1,
-}
-
-
-TOX_ERR_GROUP_SELF_NAME_SET = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_SELF_NAME_SET_OK': 0,
-
- #
- # The group number passed did not designate a valid group.
- #
- 'TOX_ERR_GROUP_SELF_NAME_SET_GROUP_NOT_FOUND': 1,
-
- #
- # Name length exceeded 'TOX_MAX_NAME_LENGTH.
- #
- 'TOX_ERR_GROUP_SELF_NAME_SET_TOO_LONG': 2,
-
- #
- # The length given to the set function is zero or name is a NULL pointer.
- #
- 'TOX_ERR_GROUP_SELF_NAME_SET_INVALID': 3,
-
- #
- # The name is already taken by another peer in the group.
- #
- 'TOX_ERR_GROUP_SELF_NAME_SET_TAKEN': 4,
-
- #
- # The packet failed to send.
- #
- 'TOX_ERR_GROUP_SELF_NAME_SET_FAIL_SEND': 5
-}
-
-TOX_ERR_GROUP_SELF_STATUS_SET = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_SELF_STATUS_SET_OK': 0,
-
- #
- # The group number passed did not designate a valid group.
- #
- 'TOX_ERR_GROUP_SELF_STATUS_SET_GROUP_NOT_FOUND': 1,
-
- #
- # An invalid type was passed to the set function.
- #
- 'TOX_ERR_GROUP_SELF_STATUS_SET_INVALID': 2,
-
- #
- # The packet failed to send.
- #
- 'TOX_ERR_GROUP_SELF_STATUS_SET_FAIL_SEND': 3
-}
-
-TOX_ERR_GROUP_PEER_QUERY = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_PEER_QUERY_OK': 0,
-
- #
- # The group number passed did not designate a valid group.
- #
- 'TOX_ERR_GROUP_PEER_QUERY_GROUP_NOT_FOUND': 1,
-
- #
- # The ID passed did not designate a valid peer.
- #
- 'TOX_ERR_GROUP_PEER_QUERY_PEER_NOT_FOUND': 2
-}
-
-TOX_ERR_GROUP_STATE_QUERIES = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_STATE_QUERIES_OK': 0,
-
- #
- # The group number passed did not designate a valid group.
- #
- 'TOX_ERR_GROUP_STATE_QUERIES_GROUP_NOT_FOUND': 1
-}
-
-
-TOX_ERR_GROUP_TOPIC_SET = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_TOPIC_SET_OK': 0,
-
- #
- # The group number passed did not designate a valid group.
- #
- 'TOX_ERR_GROUP_TOPIC_SET_GROUP_NOT_FOUND': 1,
-
- #
- # Topic length exceeded 'TOX_GROUP_MAX_TOPIC_LENGTH.
- #
- 'TOX_ERR_GROUP_TOPIC_SET_TOO_LONG': 2,
-
- #
- # The caller does not have the required permissions to set the topic.
- #
- 'TOX_ERR_GROUP_TOPIC_SET_PERMISSIONS': 3,
-
- #
- # The packet could not be created. This error is usually related to cryptographic signing.
- #
- 'TOX_ERR_GROUP_TOPIC_SET_FAIL_CREATE': 4,
-
- #
- # The packet failed to send.
- #
- 'TOX_ERR_GROUP_TOPIC_SET_FAIL_SEND': 5
-}
-
-TOX_ERR_GROUP_SEND_MESSAGE = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_SEND_MESSAGE_OK': 0,
-
- #
- # The group number passed did not designate a valid group.
- #
- 'TOX_ERR_GROUP_SEND_MESSAGE_GROUP_NOT_FOUND': 1,
-
- #
- # Message length exceeded 'TOX_MAX_MESSAGE_LENGTH.
- #
- 'TOX_ERR_GROUP_SEND_MESSAGE_TOO_LONG': 2,
-
- #
- # The message pointer is null or length is zero.
- #
- 'TOX_ERR_GROUP_SEND_MESSAGE_EMPTY': 3,
-
- #
- # The message type is invalid.
- #
- 'TOX_ERR_GROUP_SEND_MESSAGE_BAD_TYPE': 4,
-
- #
- # The caller does not have the required permissions to send group messages.
- #
- 'TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS': 5,
-
- #
- # Packet failed to send.
- #
- 'TOX_ERR_GROUP_SEND_MESSAGE_FAIL_SEND': 6
-}
-
-TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_OK': 0,
-
- #
- # The group number passed did not designate a valid group.
- #
- 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_GROUP_NOT_FOUND': 1,
-
- #
- # The ID passed did not designate a valid peer.
- #
- 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_PEER_NOT_FOUND': 2,
-
- #
- # Message length exceeded 'TOX_MAX_MESSAGE_LENGTH.
- #
- 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_TOO_LONG': 3,
-
- #
- # The message pointer is null or length is zero.
- #
- 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_EMPTY': 4,
-
- #
- # The caller does not have the required permissions to send group messages.
- #
- 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_PERMISSIONS': 5,
-
- #
- # Packet failed to send.
- #
- 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_FAIL_SEND': 6
-}
-
-TOX_ERR_GROUP_SEND_CUSTOM_PACKET = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_OK': 0,
-
- #
- # The group number passed did not designate a valid group.
- #
- 'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_GROUP_NOT_FOUND': 1,
-
- #
- # Message length exceeded 'TOX_MAX_MESSAGE_LENGTH.
- #
- 'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_TOO_LONG': 2,
-
- #
- # The message pointer is null or length is zero.
- #
- 'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_EMPTY': 3,
-
- #
- # The caller does not have the required permissions to send group messages.
- #
- 'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_PERMISSIONS': 4
-}
-
-TOX_ERR_GROUP_INVITE_FRIEND = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_INVITE_FRIEND_OK': 0,
-
- #
- # The group number passed did not designate a valid group.
- #
- 'TOX_ERR_GROUP_INVITE_FRIEND_GROUP_NOT_FOUND': 1,
-
- #
- # The friend number passed did not designate a valid friend.
- #
- 'TOX_ERR_GROUP_INVITE_FRIEND_FRIEND_NOT_FOUND': 2,
-
- #
- # Creation of the invite packet failed. This indicates a network related error.
- #
- 'TOX_ERR_GROUP_INVITE_FRIEND_INVITE_FAIL': 3,
-
- #
- # Packet failed to send.
- #
- 'TOX_ERR_GROUP_INVITE_FRIEND_FAIL_SEND': 4
-}
-
-TOX_ERR_GROUP_INVITE_ACCEPT = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_INVITE_ACCEPT_OK': 0,
-
- #
- # The invite data is not in the expected format.
- #
- 'TOX_ERR_GROUP_INVITE_ACCEPT_BAD_INVITE': 1,
-
- #
- # The group instance failed to initialize.
- #
- 'TOX_ERR_GROUP_INVITE_ACCEPT_INIT_FAILED': 2,
-
- #
- # Password length exceeded 'TOX_GROUP_MAX_PASSWORD_SIZE.
- #
- 'TOX_ERR_GROUP_INVITE_ACCEPT_TOO_LONG': 3
-}
-
-TOX_GROUP_JOIN_FAIL = {
-
- #
- # You are using the same nickname as someone who is already in the group.
- #
- 'TOX_GROUP_JOIN_FAIL_NAME_TAKEN': 0,
-
- #
- # The group peer limit has been reached.
- #
- 'TOX_GROUP_JOIN_FAIL_PEER_LIMIT': 1,
-
- #
- # You have supplied an invalid password.
- #
- 'TOX_GROUP_JOIN_FAIL_INVALID_PASSWORD': 2,
-
- #
- # The join attempt failed due to an unspecified error. This often occurs when the group is
- # not found in the DHT.
- #
- 'TOX_GROUP_JOIN_FAIL_UNKNOWN': 3
-}
-
-TOX_ERR_GROUP_FOUNDER_SET_PASSWORD = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_OK': 0,
-
- #
- # The group number passed did not designate a valid group.
- #
- 'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_GROUP_NOT_FOUND': 1,
-
- #
- # The caller does not have the required permissions to set the password.
- #
- 'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_PERMISSIONS': 2,
-
- #
- # Password length exceeded 'TOX_GROUP_MAX_PASSWORD_SIZE.
- #
- 'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_TOO_LONG': 3,
-
- #
- # The packet failed to send.
- #
- 'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_FAIL_SEND': 4
-}
-
-TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_OK': 0,
-
- #
- # The group number passed did not designate a valid group.
- #
- 'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_GROUP_NOT_FOUND': 1,
-
- #
- # 'TOX_GROUP_PRIVACY_STATE is an invalid type.
- #
- 'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_INVALID': 2,
-
- #
- # The caller does not have the required permissions to set the privacy state.
- #
- 'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_PERMISSIONS': 3,
-
- #
- # The privacy state could not be set. This may occur due to an error related to
- # cryptographic signing of the new shared state.
- #
- 'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_FAIL_SET': 4,
-
- #
- # The packet failed to send.
- #
- 'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_FAIL_SEND': 5
-}
-
-TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_OK': 0,
-
- #
- # The group number passed did not designate a valid group.
- #
- 'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_GROUP_NOT_FOUND': 1,
-
- #
- # The caller does not have the required permissions to set the peer limit.
- #
- 'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_PERMISSIONS': 2,
-
- #
- # The peer limit could not be set. This may occur due to an error related to
- # cryptographic signing of the new shared state.
- #
- 'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_FAIL_SET': 3,
-
- #
- # The packet failed to send.
- #
- 'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_FAIL_SEND': 4
-}
-
-TOX_ERR_GROUP_TOGGLE_IGNORE = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_TOGGLE_IGNORE_OK': 0,
-
- #
- # The group number passed did not designate a valid group.
- #
- 'TOX_ERR_GROUP_TOGGLE_IGNORE_GROUP_NOT_FOUND': 1,
-
- #
- # The ID passed did not designate a valid peer.
- #
- 'TOX_ERR_GROUP_TOGGLE_IGNORE_PEER_NOT_FOUND': 2
-}
-
-TOX_ERR_GROUP_MOD_SET_ROLE = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_MOD_SET_ROLE_OK': 0,
-
- #
- # The group number passed did not designate a valid group.
- #
- 'TOX_ERR_GROUP_MOD_SET_ROLE_GROUP_NOT_FOUND': 1,
-
- #
- # The ID passed did not designate a valid peer. Note: you cannot set your own role.
- #
- 'TOX_ERR_GROUP_MOD_SET_ROLE_PEER_NOT_FOUND': 2,
-
- #
- # The caller does not have the required permissions for this action.
- #
- 'TOX_ERR_GROUP_MOD_SET_ROLE_PERMISSIONS': 3,
-
- #
- # The role assignment is invalid. This will occur if you try to set a peer's role to
- # the role they already have.
- #
- 'TOX_ERR_GROUP_MOD_SET_ROLE_ASSIGNMENT': 4,
-
- #
- # The role was not successfully set. This may occur if something goes wrong with role setting': ,
- # or if the packet fails to send.
- #
- 'TOX_ERR_GROUP_MOD_SET_ROLE_FAIL_ACTION': 5
-}
-
-TOX_ERR_GROUP_MOD_REMOVE_PEER = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_MOD_REMOVE_PEER_OK': 0,
-
- #
- # The group number passed did not designate a valid group.
- #
- 'TOX_ERR_GROUP_MOD_REMOVE_PEER_GROUP_NOT_FOUND': 1,
-
- #
- # The ID passed did not designate a valid peer.
- #
- 'TOX_ERR_GROUP_MOD_REMOVE_PEER_PEER_NOT_FOUND': 2,
-
- #
- # The caller does not have the required permissions for this action.
- #
- 'TOX_ERR_GROUP_MOD_REMOVE_PEER_PERMISSIONS': 3,
-
- #
- # The peer could not be removed from the group.
- #
- # If a ban was set': , this error indicates that the ban entry could not be created.
- # This is usually due to the peer's IP address already occurring in the ban list. It may also
- # be due to the entry containing invalid peer information': , or a failure to cryptographically
- # authenticate the entry.
- #
- 'TOX_ERR_GROUP_MOD_REMOVE_PEER_FAIL_ACTION': 4,
-
- #
- # The packet failed to send.
- #
- 'TOX_ERR_GROUP_MOD_REMOVE_PEER_FAIL_SEND': 5
-}
-
-TOX_ERR_GROUP_MOD_REMOVE_BAN = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_MOD_REMOVE_BAN_OK': 0,
-
- #
- # The group number passed did not designate a valid group.
- #
- 'TOX_ERR_GROUP_MOD_REMOVE_BAN_GROUP_NOT_FOUND': 1,
-
- #
- # The caller does not have the required permissions for this action.
- #
- 'TOX_ERR_GROUP_MOD_REMOVE_BAN_PERMISSIONS': 2,
-
- #
- # The ban entry could not be removed. This may occur if ban_id does not designate
- # a valid ban entry.
- #
- 'TOX_ERR_GROUP_MOD_REMOVE_BAN_FAIL_ACTION': 3,
-
- #
- # The packet failed to send.
- #
- 'TOX_ERR_GROUP_MOD_REMOVE_BAN_FAIL_SEND': 4
-}
-
-TOX_GROUP_MOD_EVENT = {
-
- #
- # A peer has been kicked from the group.
- #
- 'KICK': 0,
-
- #
- # A peer has been banned from the group.
- #
- 'BAN': 1,
-
- #
- # A peer as been given the observer role.
- #
- 'OBSERVER': 2,
-
- #
- # A peer has been given the user role.
- #
- 'USER': 3,
-
- #
- # A peer has been given the moderator role.
- #
- 'MODERATOR': 4,
-}
-
-TOX_ERR_GROUP_BAN_QUERY = {
-
- #
- # The function returned successfully.
- #
- 'TOX_ERR_GROUP_BAN_QUERY_OK': 0,
-
- #
- # The group number passed did not designate a valid group.
- #
- 'TOX_ERR_GROUP_BAN_QUERY_GROUP_NOT_FOUND': 1,
-
- #
- # The ban_id does not designate a valid ban list entry.
- #
- 'TOX_ERR_GROUP_BAN_QUERY_BAD_ID': 2,
-}
-
-
-TOX_GROUP_BAN_TYPE = {
-
- 'IP_PORT': 0,
-
- 'PUBLIC_KEY': 1,
-
- 'NICK': 2
-}
-
-TOX_PUBLIC_KEY_SIZE = 32
-
-TOX_ADDRESS_SIZE = TOX_PUBLIC_KEY_SIZE + 6
-
-TOX_MAX_FRIEND_REQUEST_LENGTH = 1016
-
-TOX_MAX_MESSAGE_LENGTH = 1372
-
-TOX_GROUP_MAX_TOPIC_LENGTH = 512
-
-TOX_GROUP_MAX_PART_LENGTH = 128
-
-TOX_GROUP_MAX_GROUP_NAME_LENGTH = 48
-
-TOX_GROUP_MAX_PASSWORD_SIZE = 32
-
-TOX_GROUP_CHAT_ID_SIZE = 32
-
-TOX_GROUP_PEER_PUBLIC_KEY_SIZE = 32
-
-TOX_MAX_NAME_LENGTH = 128
-
-TOX_MAX_STATUS_MESSAGE_LENGTH = 1007
-
-TOX_SECRET_KEY_SIZE = 32
-
-TOX_FILE_ID_LENGTH = 32
-
-TOX_HASH_LENGTH = 32
-
-TOX_MAX_CUSTOM_PACKET_SIZE = 1373
diff --git a/toxygen/wrapper/toxencryptsave_enums_and_consts.py b/toxygen/wrapper/toxencryptsave_enums_and_consts.py
deleted file mode 100644
index cf795f8..0000000
--- a/toxygen/wrapper/toxencryptsave_enums_and_consts.py
+++ /dev/null
@@ -1,29 +0,0 @@
-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