73 Commits

Author SHA1 Message Date
adcc32fc49 resuming fix 2016-08-05 16:40:16 +03:00
61e7aad847 bug fixes 2016-08-05 16:40:16 +03:00
742d853b11 threads fix and update 2016-08-05 16:40:16 +03:00
39fe859fe5 travis fix 2016-08-05 16:40:16 +03:00
2a0895018a thread for incoming file transfers 2016-08-05 16:40:16 +03:00
d1437b3445 file resuming part 3 2016-08-05 16:40:16 +03:00
59154d081f file resuming (part 2) 2016-08-05 16:40:16 +03:00
99e8691f0b file resuming across restarts (partial) 2016-08-05 16:40:15 +03:00
b0e82dfd08 font and closing to tray update 2016-08-05 15:58:25 +03:00
3a64121d72 avatars fix 2016-08-04 18:23:47 +03:00
99f31cc302 utf-8 fix 2016-08-04 16:11:04 +03:00
19de605b79 docs update 2016-08-01 22:07:01 +03:00
9e410254bf messages selection 2016-07-29 15:27:46 +03:00
e970fbed80 todo 2016-07-28 23:49:43 +03:00
9ed62d4414 travis 2016-07-28 23:21:57 +03:00
9516723c7f chat history export 2016-07-28 23:00:04 +03:00
52e6ace847 bug fixes 2016-07-28 18:04:02 +03:00
c7f50af25c docs fixes 2016-07-27 17:59:35 +03:00
7d8646b432 docs update 2016-07-27 17:53:50 +03:00
883a30f806 portability updates 2016-07-27 17:13:57 +03:00
3bd7655203 setup.py update 2016-07-27 15:45:34 +03:00
fdfc74521b some updates and fixes 2016-07-27 14:50:36 +03:00
3db10ead6a pyqt4 fixes 2016-07-26 21:03:18 +03:00
546eb9f042 fix #6 2016-07-26 20:36:50 +03:00
08ef8294df os x and freebsd support (untested) 2016-07-26 19:09:57 +03:00
af5db43248 pyqtSlot fix 2016-07-21 08:03:32 +03:00
697a9efb51 message menu update - plugins, quotes 2016-07-19 23:19:42 +03:00
6297da1c69 drag n drop (windows) 2016-07-19 20:18:29 +03:00
e78ba3942b autoreconnect and pyaudio fix 2016-07-19 15:14:30 +03:00
28cedae342 v0.2.3 2016-07-14 12:32:21 +03:00
3f9a35e164 avatars in chat 2016-07-13 23:09:34 +03:00
babeeb969c smileys, stickers, avatars update 2016-07-13 21:30:51 +03:00
01e6d45232 bug fixes 2016-07-13 17:16:15 +03:00
c865ae4df6 translations and bug fix 2016-07-12 19:40:26 +03:00
59452aa525 docs update 2016-07-12 18:05:33 +03:00
47c0a451c4 docs && setup.py update 2016-07-11 17:24:39 +03:00
5b9cce4155 docs update 2016-07-11 00:38:25 +03:00
d6b6327545 stickers and smileys import 2016-07-10 22:32:35 +03:00
7c2a2f16df tcs: part 1 2016-07-10 17:51:33 +03:00
e004838013 plugins improvements 2016-07-07 13:54:02 +03:00
8672c5fb56 pip3 version 2016-07-06 16:25:04 +03:00
1c788a73c6 setup.py and fixes 2016-07-05 21:43:51 +03:00
88e9317a41 friend menu update 2016-07-05 00:24:44 +03:00
27cf1a7348 welcome screen 2016-07-04 23:30:45 +03:00
326ebc155c tox dns update 2016-07-04 12:45:51 +03:00
5df82c6b3c translations update 2016-07-04 00:41:37 +03:00
8c6caab299 gnome file dialog fix 2016-07-04 00:13:41 +03:00
daa0053e4b short ui update 2016-07-02 22:19:04 +03:00
3990487701 messages loading fix 2016-07-02 17:34:14 +03:00
a6f47c6248 profile lock 2016-07-02 15:40:06 +03:00
18935c5b10 tests update, bug fix 2016-07-01 23:15:00 +03:00
5204cba58d compact contact list 2016-07-01 16:25:46 +03:00
c9358db883 db bug fix 2016-07-01 00:16:59 +03:00
2ad78bc56d translation update and bug fixes 2016-06-29 15:39:44 +03:00
ff196d8d92 docs update 2016-06-28 21:47:49 +03:00
7a77b56560 bug fixes with messages saving 2016-06-28 13:51:11 +03:00
b982df70ea messages update - deleting, unsent messages saving, bug fixes 2016-06-28 12:44:27 +03:00
a56693547c new smiley pack + fixes 2016-06-27 19:28:43 +03:00
699fe59706 smileys - last dups 2016-06-26 18:20:11 +03:00
e78bc846b8 smileys fix 2016-06-26 18:02:05 +03:00
fc2b53d4a5 Merge branch 'python3' into develop 2016-06-25 22:29:43 +03:00
82184b3df1 bug with status fixed 2016-06-25 15:40:01 +03:00
5cfa484a67 name && status bug fixes 2016-06-24 15:00:13 +03:00
27a5735dc1 bug fixes 2016-06-23 22:50:17 +03:00
a714c1045d profile loading/creation update, bug fixes 2016-06-23 15:04:36 +03:00
a4da3a7afa docs update 2016-06-23 12:46:48 +03:00
190877f5b9 aliases 2016-06-23 12:21:26 +03:00
55f13cbfd1 audio, smileys fixes 2016-06-23 11:18:18 +03:00
b9cbf809b5 windows bug fixes 2016-06-22 16:09:44 +03:00
5a0843d98b plugins fixes, improvements 2016-06-22 14:35:22 +03:00
fb74ea4455 inlines fix 2016-06-22 11:48:00 +03:00
42aa102d1d encrypt save support, smileys fix, bug fixes 2016-06-21 23:43:43 +03:00
dd53c6a842 first commit 2016-06-21 14:58:11 +03:00
1372 changed files with 2899 additions and 1260 deletions

14
.gitignore vendored
View File

@ -1,17 +1,23 @@
*.pyc
*.pyo
*.ui
src/toxcore
toxygen/toxcore
tests/tests
tests/libs
tests/.cache
tests/__pycache__
src/libs
toxygen/libs
.idea
*~
*.iml
*.so
*.log
src/build
src/dist
toxygen/build
toxygen/dist
*.spec
dist/
toxygen/avatars
toxygen/__pycache__
/*.egg-info
/*.egg

31
.travis.yml Normal file
View File

@ -0,0 +1,31 @@
language: python
python:
- "3.4"
before_install:
- sudo apt-get install -y checkinstall build-essential
- sudo apt-get install portaudio19-dev
install:
- pip install PySide --no-index --find-links https://parkin.github.io/python-wheelhouse/;
- python ~/virtualenv/python${TRAVIS_PYTHON_VERSION}/bin/pyside_postinstall.py -install
- pip install pyaudio
before_script:
# Libsodium
- git clone git://github.com/jedisct1/libsodium.git
- cd libsodium
- git checkout tags/1.0.3
- ./autogen.sh
- ./configure && make -j$(nproc)
- sudo checkinstall --install --pkgname libsodium --pkgversion 1.0.0 --nodoc -y
- sudo ldconfig
- cd ..
# Toxcore
- git clone https://github.com/irungentoo/toxcore.git
- cd toxcore
- autoreconf -if
- ./configure
- make -j$(nproc)
- sudo make install
- echo '/usr/local/lib/' | sudo tee -a /etc/ld.so.conf.d/locallib.conf
- sudo ldconfig
- cd ..
script: py.test tests/travis.py

20
MANIFEST.in Normal file
View File

@ -0,0 +1,20 @@
include toxygen/images/*.png
include toxygen/images/*.ico
include toxygen/images/*.gif
include toxygen/sounds/*.wav
include toxygen/stickers/tox/*.png
include toxygen/smileys/default/*.png
include toxygen/smileys/default/config.json
include toxygen/smileys/animated/*.gif
include toxygen/smileys/animated/config.json
include toxygen/smileys/starwars/*.gif
include toxygen/smileys/starwars/*.png
include toxygen/smileys/starwars/config.json
include toxygen/smileys/ksk/*.png
include toxygen/smileys/ksk/config.json
include toxygen/styles/style.qss
include toxygen/translations/*.qm
include toxygen/libs/libtox.dll
include toxygen/libs/libsodium.a
include toxygen/libs/libtox64.dll
include toxygen/libs/libsodium64.a

View File

@ -1,24 +1,35 @@
# Toxygen
Toxygen is cross-platform [Tox](https://tox.chat/) client written on Python
[![Release](https://img.shields.io/github/release/xveduk/toxygen.svg?style=flat)](https://github.com/xveduk/toxygen/releases/latest)
[![Open issues](https://img.shields.io/github/issues/xveduk/toxygen.svg?style=flat)](https://github.com/xveduk/toxygen/issues)
[![License](https://img.shields.io/badge/license-GPLv3-blue.svg?style=flat)](https://raw.githubusercontent.com/xveduk/toxygen/master/LICENSE.md)
Toxygen is cross-platform [Tox](https://tox.chat/) client written in pure Python3
### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md)
[![Release](https://img.shields.io/github/release/xveduk/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/releases/latest)
[![Stars](https://img.shields.io/github/stars/xveduk/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/stargazers)
[![Open issues](https://img.shields.io/github/issues/xveduk/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/issues)
[![License](https://img.shields.io/badge/license-GPLv3-blue.svg?style=flat)](https://raw.githubusercontent.com/toxygen-project/toxygen/master/LICENSE.md)
### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md) - [Compile](/docs/compile.md) - [Contact](/docs/contact.md)
### Supported OS:
- Windows
- Linux
- OS X
### Features
###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
@ -31,27 +42,22 @@ Toxygen is cross-platform [Tox](https://tox.chat/) client written on Python
- [x] Typing notifications
- [x] Changing nospam
- [x] File resuming
- [x] Save file encryption
- [x] Plugins support
- [x] Read receipts
- [x] Faux offline messaging
- [ ] Video
- [ ] Group chats
- [ ] Desktop sharing
- [ ] Group chats
###Downloads
[Releases](https://github.com/xveduk/toxygen/releases)
### Downloads
[Releases](https://github.com/toxygen-project/toxygen/releases)
[Download last stable version](https://github.com/xveduk/toxygen/archive/master.zip)
[Download last stable version](https://github.com/toxygen-project/toxygen/archive/master.zip)
[Download develop version](https://github.com/xveduk/toxygen/archive/develop.zip)
[Download develop version](https://github.com/toxygen-project/toxygen/archive/develop.zip)
###Screenshots
### Screenshots
*Toxygen on Ubuntu and Windows*
![Ubuntu](/docs/ubuntu.png)
![Windows](/docs/windows.png)
###Docs
### Docs
[Check /docs/ for more info](/docs/)

View File

@ -1,16 +1,10 @@
#Compile Toxygen
# Compile Toxygen
You can compile Toxygen using [PyInstaller](http://www.pyinstaller.org/)
Install PyInstaller:
``pip install pyinstaller``
On Linux:
``pyinstaller --windowed main.py``
On Windows:
``pip3 install pyinstaller``
``pyinstaller --windowed --icon images/icon.ico main.py``
Don't forget to copy /images/, /sounds/, /translations/, /styles/, to /dist/main/
Don't forget to copy /images/, /sounds/, /translations/, /styles/, /smileys/, /stickers/, /plugins/ (and /libs/libtox.dll, /libs/libsodium.a on Windows) to /dist/main/

5
docs/contact.md Normal file
View File

@ -0,0 +1,5 @@
# Contact us:
1) Using GitHub - open issue
2) Use Toxygen Tox Group - add bot kalina@toxme.io (or 12EDB939AA529641CE53830B518D6EB30241868EE0E5023C46A372363CAEC91C2C948AEFE4EB)

View File

@ -1,5 +1,3 @@
#Issues
Help us find all bugs in Toxygen! Please provide following info:
@ -15,8 +13,8 @@ Want to see new feature in Toxygen? [Ask for it!](https://github.com/xveduk/toxy
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 and [here](/README.md). Also you can implement plugins for Toxygen.
You can find our TODO's in code, issues list and [here](/README.md). Also you can implement [plugins](/docs/plugins.md) for Toxygen.
#Translations
Help us translate Toxygen! Translate can be created using pyside-lupdate and QT Linguist.
Help us translate Toxygen! Translation can be created using pyside-lupdate (``pyside-lupdate toxygen.pro``) and QT Linguist.

View File

@ -3,17 +3,50 @@
## 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 [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system (install in /usr/lib/)
2. Install PortAudio:
``sudo apt-get install portaudio19-dev``
3. Install PySide: ``sudo apt-get install python3-pyside``
4. Install toxygen:
``sudo pip3.4 install toxygen``
5. Run toxygen using ``toxygen`` command.
### OS X
1. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system
2. Install PortAudio:
``brew install portaudio``
3. Install toxygen:
``pip3 install toxygen``
4. Run toxygen using ``toxygen`` command.
## Packages
Coming soon.
## From source code (recommended for developers)
### Windows
1. [Download and install latest Python 2.7](https://www.python.org/downloads/windows/)
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
3. Install PyAudio: ``python -m pip install pyaudio``
1. [Download and install latest Python 3.4](https://www.python.org/downloads/windows/)
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4#installing-pyside-on-a-windows-system) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
3. Install PyAudio: ``pip3.4 install pyaudio``
4. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
5. Unpack archive
6. Download latest libtox.dll build, download latest libsodium.a build, put it into \src\libs\
7. Run \src\main.py
7. Run \toxygen\main.py.
Optional: install toxygen using setup.py: ``python3.4 setup.py install``
[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)
@ -23,26 +56,31 @@
[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
Dependencies:
1. Install Python2.7:
``sudo apt-get install python2.7``
2. [Install PySide](https://wiki.qt.io/PySide_Binaries_Linux) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
1. Install latest Python3:
``sudo apt-get install python3``
2. Install PySide: ``sudo apt-get install python3-pyside`` or install [PyQt4](https://riverbankcomputing.com/software/pyqt/download) (``sudo apt-get install python3-pyqt4``).
3. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system (install in /usr/lib/)
4. Install PyAudio:
```bash
sudo apt-get install portaudio19-dev
sudo pip install pyaudio
```
Toxygen:
``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``
1. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
2. Unpack archive
3. Run app:
``python main.py``
Optional: install toxygen using setup.py: ``python3.4 setup.py install``
## Compile Toxygen
Check [compile.md](/docs/compile.md) for more info
### OS X
1. [Download and install latest Python 3.4](https://www.python.org/downloads/mac-osx/)
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4#installing-pyside-on-a-mac-os-x-system) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
3. Install PortAudio:
``brew install portaudio``
4. Install PyAudio: ``pip3 install pyaudio``
5. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system
6. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
7. Unpack archive
8. Run \toxygen\main.py.
Optional: install toxygen using setup.py: ``python3 setup.py install``

View File

@ -1,9 +1,9 @@
#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 it's own full name and unique short name (1-5 symbols). Main app can get it using special methods.
Every plugin has its own full name and unique short name (1-5 symbols). Main app can get it using special methods.
All plugin's data should be stored in following structure:
@ -12,40 +12,44 @@ All plugin's data should be stored in following structure:
|---plugin_short_name.py
|---/plugin_short_name/
|---settings.json
|---readme.txt
|---logs.txt
|---other_files
```
Plugin MUST override:
- __init__ with params: tox (Tox instance), profile (Profile instance), settings (Settings instance), encrypt_save (ToxEncryptSave instance). Call super().__init__ with params plugin_full_name, plugin_short_name, tox, profile, settings, encrypt_save.
Plugin can override following methods:
- get_description - this method should return plugin description.
- get_menu - plugins allowed to add items in friend menu. You can open this menu making right click on friend in friends list. This method should return list of QAction's. Plugin must connect to QAction's triggered() signal.
- get_menu - plugins allowed to add items in friend menu. User can open this menu making right click on friend in friends list. This method should return list of QAction's. Plugin must connect to QAction's triggered() signal.
- get_window - plugins can have GUI, this method should return window instance or None for plugins without GUI.
- start - plugin was started.
- stop - plugin was stopped.
- close - app is closing, stop plugin.
- command - new command to plugin. Command can be entered in message field in format '/plugin <plugin_short_name> <command>'. Command 'help' should show user list of supported commands.
- command - new command to plugin. Command can be entered in message field in format '/plugin <plugin_short_name> <command>'. Command 'help' should show list of supported commands.
- lossless_packet - callback - incoming lossless packet from friend.
- lossy_packet - callback - incoming lossy packet from friend.
- friend_connected - callback - friend became online.
- friend_connected - callback - friend became online. Note that it called from friend_connection_status callback so friend is not really connected and ready for sending packets.
Other methods:
- send_lossless - this method send custom lossless packet. Plugins MUST send lossless packets using this method.
- send_lossy - this method send custom lossy packet. Plugins MUST send lossy packets using this method.
- send_lossless - this method sends custom lossless packet. Plugins MUST send lossless packets using this method.
- send_lossy - this method sends custom lossy packet. Plugins MUST send lossy packets using this method.
- load_settings - loads settings stored in default location.
- save_settings - saves settings to default location.
- load_translator - loads translations. Translations must be stored in directory with plugin's data. Files with translations must have the same name as in main app.
- load_translator - loads translations. Translations must be stored in directory with plugin's data. Files with translations must have the same name as in main app (example: ru_RU.qm).
About import:
import statement will not work in case you import module that wasn't previously imported by main program and user use precompiled binary. It's recommended to use imp module and dynamic import instead.
Import statement will not work in case you import module that wasn't previously imported by main program and user uses precompiled binary. It's recommended to use importlib module instead: importlib.import_module(module_name)
About GUI:
It's strictly recommended to support both PySide and PyQt4 in GUI.
It's strictly recommended to support both PySide and PyQt4 in GUI. Plugin can have no GUI at all.
Exceptions:
Plugin's methods should not raise exceptions.
Plugin's methods MUST NOT raise exceptions.
#Examples

View File

@ -1,6 +1,6 @@
#Plugins
Toxygen is the first [Tox](https://tox.chat/) client with plugins support. Plugin is Python 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
@ -10,9 +10,11 @@ Check [Plugin API](/docs/plugin_api.md) for more info
Toxygen comes without preinstalled plugins.
1. Put plugin and directory with it's data into /src/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
##Note: /src/plugins/ should contain plugin_super_class.py and __init__.py
#Plugins list
WARNING: It is unsecure to install plugin not from this list!

View File

@ -1,6 +1,6 @@
#Smileys
Toxygen support smileys. Smiley is small picture which replaces some combination of symbols. If you want to create your own smiley pack, create directory in src/smileys/. This directory must contain images with smileys and config.json. Example of config.json:
Toxygen support smileys. Smiley is small picture which replaces some symbol or combination of symbols. If you want to create your own smiley pack, create directory in src/smileys/. This directory must contain images with smileys and config.json. Example of config.json:
{":)": "one.png", ":(": "two.png"}
@ -9,3 +9,5 @@ Animated smileys (.gif) are supported too.
#Stickers
Sticker is inline image. If you want to create your own smiley pack, create directory in src/stickers/ and place your stickers there.
Users can import plugins and stickers packs using menu: Settings -> Interface

71
setup.py Normal file
View File

@ -0,0 +1,71 @@
from setuptools import setup
from setuptools.command.install import install
from platform import system
from subprocess import call
from toxygen.util import program_version
import sys
version = program_version + '.0'
MODULES = []
if system() in ('Windows', 'Darwin'):
MODULES = ['PyAudio', 'PySide']
else:
try:
import pyaudio
except ImportError:
MODULES = ['PyAudio']
class InstallScript(install):
"""This class configures Toxygen after installation"""
def run(self):
install.run(self)
try:
if system() == 'Windows':
call(["toxygen", "--configure"])
else:
call(["toxygen", "--clean"])
except:
try:
params = list(filter(lambda x: x.startswith('--prefix='), sys.argv))
if params:
path = params[0][len('--prefix='):]
if path[-1] not in ('/', '\\'):
path += '/'
path += 'bin/toxygen'
if system() == 'Windows':
call([path, "--configure"])
else:
call([path, "--clean"])
except:
pass
setup(name='Toxygen',
version=version,
description='Toxygen - Tox client',
long_description='Toxygen is powerful Tox client written in Python3',
url='https://github.com/xveduk/toxygen/',
keywords='toxygen tox messenger',
author='Ingvar',
maintainer='Ingvar',
license='GPL3',
packages=['toxygen', 'toxygen.plugins', 'toxygen.styles'],
install_requires=MODULES,
include_package_data=True,
classifiers=[
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
],
entry_points={
'console_scripts': ['toxygen=toxygen.main:main'],
},
cmdclass={
'install': InstallScript,
},
)

View File

@ -1,49 +0,0 @@
from platform import system
from ctypes import CDLL
class LibToxCore(object):
def __init__(self):
if system() == 'Linux':
# libtoxcore and libsodium must be installed in your os
self._libtoxcore = CDLL('libtoxcore.so')
elif system() == 'Windows':
self._libtoxcore = CDLL('libs/libtox.dll')
else:
raise OSError('Unknown system.')
def __getattr__(self, item):
return self._libtoxcore.__getattr__(item)
class LibToxAV(object):
def __init__(self):
if system() == 'Linux':
# that /usr/lib/libtoxav.so must exists
self._libtoxav = CDLL('libtoxav.so')
elif system() == 'Windows':
# on Windows av api is in libtox.dll
self._libtoxav = CDLL('libs/libtox.dll')
else:
raise OSError('Unknown system.')
def __getattr__(self, item):
return self._libtoxav.__getattr__(item)
class LibToxEncryptSave(object):
def __init__(self):
if system() == 'Linux':
# /usr/lib/libtoxencryptsave.so must exists
self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.so')
elif system() == 'Windows':
# on Windows profile encryption api is in libtox.dll
self._lib_tox_encrypt_save = CDLL('libs/libtox.dll')
else:
raise OSError('Unknown system.')
def __getattr__(self, item):
return self._lib_tox_encrypt_save.__getattr__(item)

View File

@ -1,76 +0,0 @@
from widgets import CenteredWidget, LineEdit
try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
class PasswordArea(LineEdit):
def __init__(self, parent):
super(PasswordArea, self).__init__(parent)
self.parent = parent
self.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Return:
self.parent.button_click()
else:
super(PasswordArea, self).keyPressEvent(event)
class PasswordScreen(CenteredWidget):
def __init__(self, encrypt, data):
super(PasswordScreen, self).__init__()
self._encrypt = encrypt
self._data = data
self.initUI()
def initUI(self):
self.resize(360, 170)
self.setMinimumSize(QtCore.QSize(360, 170))
self.setMaximumSize(QtCore.QSize(360, 170))
self.enter_pass = QtGui.QLabel(self)
self.enter_pass.setGeometry(QtCore.QRect(30, 10, 300, 30))
self.password = PasswordArea(self)
self.password.setGeometry(QtCore.QRect(30, 50, 300, 30))
self.button = QtGui.QPushButton(self)
self.button.setGeometry(QtCore.QRect(30, 90, 300, 30))
self.button.setText('OK')
self.button.clicked.connect(self.button_click)
self.warning = QtGui.QLabel(self)
self.warning.setGeometry(QtCore.QRect(30, 130, 300, 30))
self.warning.setStyleSheet('QLabel { color: #F70D1A; }')
self.warning.setVisible(False)
self.retranslateUi()
self.center()
QtCore.QMetaObject.connectSlotsByName(self)
def button_click(self):
if self.password.text():
try:
self._encrypt.set_password(self.password.text())
new_data = self._encrypt.pass_decrypt(self._data[0])
except Exception:
self.warning.setVisible(True)
else:
self._data[0] = new_data
self.close()
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Enter:
self.button_click()
else:
super(PasswordScreen, self).keyPressEvent(event)
def retranslateUi(self):
self.setWindowTitle(QtGui.QApplication.translate("pass", "Enter password", None, QtGui.QApplication.UnicodeUTF8))
self.enter_pass.setText(QtGui.QApplication.translate("pass", "Password:", None, QtGui.QApplication.UnicodeUTF8))
self.warning.setText(QtGui.QApplication.translate("pass", "Incorrect password", None, QtGui.QApplication.UnicodeUTF8))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

@ -1,30 +0,0 @@
import json
import urllib2
from util import log
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)}
for url in ('https://{}/api'.format(site), 'http://{}/api'.format(site)):
try:
return send_request(url, data)
except Exception as ex: # try http
log('TOX DNS ERROR: ' + str(ex))
return None # error
def send_request(url, data):
req = urllib2.Request(url)
req.add_header('Content-Type', 'application/json')
response = urllib2.urlopen(req, json.dumps(data))
res = json.loads(response.read())
if not res['c']:
return res['tox_id']
else:
raise LookupError()

Binary file not shown.

View File

@ -1,40 +1,22 @@
from src.bootstrap import node_generator
from src.profile import *
from src.settings import ProfileHelper
from src.tox_dns import tox_dns
from src.toxencryptsave import LibToxEncryptSave
from toxygen.bootstrap import node_generator
from toxygen.profile import *
from toxygen.settings import ProfileHelper
from toxygen.tox_dns import tox_dns
import toxygen.toxencryptsave as encr
class TestProfile():
class TestProfile:
def test_search(self):
arr = ProfileHelper.find_profiles()
assert arr
assert len(arr) >= 2
def test_open(self):
data = ProfileHelper(Settings.get_default_path(), 'alice').open_profile()
assert data
def test_open_save(self):
data = ProfileHelper(Settings.get_default_path(), 'alice').open_profile()
ProfileHelper.get_instance().save_profile(data)
new_data = ProfileHelper(Settings.get_default_path(), 'alice').open_profile()
assert new_data == data
class TestNodeGen():
def test_generator(self):
for elem in node_generator():
assert len(elem) == 3
def test_ports(self):
for elem in node_generator():
assert elem[1] in [33445, 443, 5190, 2306, 1813]
class TestTox():
class TestTox:
def test_loading(self):
data = ProfileHelper(Settings.get_default_path(), 'alice').open_profile()
@ -45,16 +27,16 @@ class TestTox():
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()
@ -67,7 +49,7 @@ class TestTox():
del tox
class TestDNS():
class TestDNS:
def test_dns(self):
bot_id = '56A1ADE4B65B86BCD51CC73E2CD4E542179F47959FE3E0E21B4B0ACDADE51855D34D34D37CB5'
@ -75,12 +57,13 @@ class TestDNS():
assert tox_id == bot_id
class TestEncryption():
class TestEncryption:
def test_encr_decr(self):
with open(settings.Settings.get_default_path() + '/alice.tox') as fl:
with open(settings.Settings.get_default_path() + '/alice.tox', 'rb') as fl:
data = fl.read()
lib = LibToxEncryptSave('easypassword')
lib = encr.ToxEncryptSave()
lib.set_password('easypassword')
copy_data = data[:]
data = lib.pass_encrypt(data)
data = lib.pass_decrypt(data)

4
tests/travis.py Normal file
View File

@ -0,0 +1,4 @@
class TestToxygen:
def test_main(self):
import toxygen.main

8
toxygen/__init__.py Normal file
View File

@ -0,0 +1,8 @@
import os
import sys
path = os.path.dirname(os.path.realpath(__file__)) # curr dir
sys.path.insert(0, os.path.join(path, 'styles'))
sys.path.insert(0, os.path.join(path, 'plugins'))
sys.path.insert(0, path)

View File

@ -23,7 +23,7 @@ class IncomingCallWidget(widgets.CenteredWidget):
self.name = widgets.DataLabel(self)
self.name.setGeometry(QtCore.QRect(90, 20, 300, 25))
font = QtGui.QFont()
font.setFamily("Times New Roman")
font.setFamily(settings.Settings.get_instance['font'])
font.setPointSize(16)
font.setBold(True)
self.name.setFont(font)
@ -65,7 +65,7 @@ class IncomingCallWidget(widgets.CenteredWidget):
QtCore.QThread.__init__(self)
def run(self):
class AudioFile(object):
class AudioFile:
chunk = 1024
def __init__(self, fl):

View File

@ -1,12 +1,12 @@
import random
class Node(object):
class Node:
def __init__(self, ip, port, tox_key, rand):
self._ip, self._port, self._tox_key, self.rand = ip, port, tox_key, rand
def get_data(self):
return self._ip, self._port, self._tox_key
return bytes(self._ip, 'utf-8'), self._port, self._tox_key
def node_generator():
@ -75,13 +75,9 @@ def node_generator():
"5625A62618CB4FCA70E147A71B29695F38CC65FF0CBD68AD46254585BE564802",
"31910C0497D347FF160D6F3A6C0E317BAFA71E8E03BC4CBB2A185C9D4FB8B31E"
]
for i in xrange(len(ips)):
for i in range(len(ips)):
nodes.append(Node(ips[i], ports[i], ids[i], random.randint(0, 1000000)))
arr = sorted(nodes, key=lambda x: x.rand)[:4]
for elem in arr:
yield elem.get_data()
if __name__ == "__main__":
for elem in node_generator():
print str(elem)

View File

@ -9,6 +9,14 @@ 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):
@ -33,6 +41,44 @@ _invoker = Invoker()
def invoke_in_main_thread(fn, *args, **kwargs):
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
class FileTransfersThread(threading.Thread):
def __init__(self):
self._queue = queue.Queue()
self._timeout = 0.01
self._continue = True
super().__init__()
def execute(self, function, *args, **kwargs):
self._queue.put((function, args, kwargs))
def stop(self):
self._continue = False
def run(self):
while self._continue:
try:
function, args, kwargs = self._queue.get(timeout=self._timeout)
function(*args, **kwargs)
except queue.Empty:
pass
except queue.Full:
util.log('Queue is Full in _thread')
except Exception as ex:
util.log('Exception in _thread: ' + str(ex))
_thread = FileTransfersThread()
def start():
_thread.start()
def stop():
_thread.stop()
_thread.join()
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - current user
# -----------------------------------------------------------------------------------------------------------------
@ -43,7 +89,7 @@ def self_connection_status(tox_link):
Current user changed connection status (offline, UDP, TCP)
"""
def wrapped(tox, connection, user_data):
print 'Connection status: ', str(connection)
print('Connection status: ', str(connection))
profile = Profile.get_instance()
if profile.status is None:
status = tox_link.self_get_status()
@ -62,13 +108,13 @@ def friend_status(tox, friend_num, new_status, user_data):
"""
Check friend's status (none, busy, away)
"""
print "Friend's #{} status changed! New status: {}".format(friend_num, new_status)
print("Friend's #{} status changed!".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(profile.send_files, friend_num)
invoke_in_main_thread(QtCore.QTimer.singleShot, 5000, lambda: profile.send_files(friend_num))
invoke_in_main_thread(profile.update_filtration)
@ -76,7 +122,7 @@ 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)
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']:
@ -94,7 +140,7 @@ def friend_name(tox, friend_num, name, size, user_data):
Friend changed his name
"""
profile = Profile.get_instance()
print 'New name: ', friend_num, name
print('New name friend #' + str(friend_num))
invoke_in_main_thread(profile.new_name, friend_num, name)
@ -106,7 +152,7 @@ def friend_status_message(tox, friend_num, status_message, size, user_data):
profile = Profile.get_instance()
friend = profile.get_friend_by_number(friend_num)
invoke_in_main_thread(friend.set_status_message, status_message)
print 'User #{} has new status: {}'.format(friend_num, status_message)
print('User #{} has new status'.format(friend_num))
invoke_in_main_thread(profile.send_messages, friend_num)
if profile.get_active_number() == friend_num:
invoke_in_main_thread(profile.set_active)
@ -119,11 +165,12 @@ def friend_message(window, tray):
def wrapped(tox, friend_number, message_type, message, size, user_data):
profile = Profile.get_instance()
settings = Settings.get_instance()
message = str(message, 'utf-8')
invoke_in_main_thread(profile.new_message, friend_number, message_type, message)
if not window.isActiveWindow():
friend = profile.get_friend_by_number(friend_number)
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
invoke_in_main_thread(tray_notification, friend.name, message.decode('utf8'), tray, window)
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'))
@ -134,12 +181,12 @@ def friend_request(tox, public_key, message, message_size, user_data):
"""
Called when user get new friend request
"""
print 'Friend request'
print('Friend request')
profile = Profile.get_instance()
key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE])
tox_id = bin_to_string(key, TOX_PUBLIC_KEY_SIZE)
if tox_id not in Settings.get_instance()['blocked']:
invoke_in_main_thread(profile.process_friend_request, tox_id, message.decode('utf-8'))
invoke_in_main_thread(profile.process_friend_request, tox_id, str(message, 'utf-8'))
def friend_typing(tox, friend_number, typing, user_data):
@ -165,11 +212,11 @@ def tox_file_recv(window, tray):
profile = Profile.get_instance()
settings = Settings.get_instance()
if file_type == TOX_FILE_KIND['DATA']:
print 'file'
print('File')
try:
file_name = unicode(file_name[:file_name_size].decode('utf-8'))
file_name = str(file_name[:file_name_size], 'utf-8')
except:
file_name = u'toxygen_file'
file_name = 'toxygen_file'
invoke_in_main_thread(profile.incoming_file_transfer,
friend_number,
file_number,
@ -177,14 +224,14 @@ def tox_file_recv(window, tray):
file_name)
if not window.isActiveWindow():
friend = profile.get_friend_by_number(friend_number)
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
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'
print('Avatar')
invoke_in_main_thread(profile.incoming_avatar,
friend_number,
file_number,
@ -196,28 +243,15 @@ def file_recv_chunk(tox, friend_number, file_number, position, chunk, length, us
"""
Incoming chunk
"""
if not length:
invoke_in_main_thread(Profile.get_instance().incoming_chunk,
friend_number,
file_number,
position,
None)
else:
Profile.get_instance().incoming_chunk(friend_number, file_number, position, chunk[:length])
_thread.execute(Profile.get_instance().incoming_chunk, friend_number, file_number, position,
chunk[:length] if length else None)
def file_chunk_request(tox, friend_number, file_number, position, size, user_data):
"""
Outgoing chunk
"""
if size:
Profile.get_instance().outgoing_chunk(friend_number, file_number, position, size)
else:
invoke_in_main_thread(Profile.get_instance().outgoing_chunk,
friend_number,
file_number,
position,
size)
Profile.get_instance().outgoing_chunk(friend_number, file_number, position, size)
def file_recv_control(tox, friend_number, file_number, file_control, user_data):
@ -260,7 +294,7 @@ def call_state(toxav, friend_number, mask, user_data):
"""
New call state
"""
print friend_number, mask
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:
@ -271,7 +305,7 @@ def call(toxav, friend_number, audio, video, user_data):
"""
Incoming call from friend
"""
print friend_number, audio, video
print(friend_number, audio, video)
invoke_in_main_thread(Profile.get_instance().incoming_call, audio, video, friend_number)
@ -279,9 +313,8 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud
"""
New audio chunk
"""
# print audio_samples_per_channel, audio_channels_count, rate
Profile.get_instance().call.chunk(
''.join(chr(x) for x in samples[:audio_samples_per_channel * 2 * audio_channels_count]),
bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]),
audio_channels_count,
rate)

View File

@ -13,7 +13,7 @@ CALL_TYPE = {
}
class AV(object):
class AV:
def __init__(self, toxav):
self._toxav = toxav
@ -30,7 +30,7 @@ class AV(object):
self._audio_rate = 8000
self._audio_channels = 1
self._audio_duration = 60
self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration / 1000
self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration // 1000
def __contains__(self, friend_number):
return friend_number in self._calls

View File

@ -7,7 +7,7 @@ except ImportError:
from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
class Contact(object):
class Contact:
"""
Class encapsulating TOX contact
Properties: name (alias of contact or name), status_message, status (connection status)
@ -36,7 +36,7 @@ class Contact(object):
return self._name
def set_name(self, value):
self._name = value.decode('utf-8')
self._name = str(value, 'utf-8')
self._widget.name.setText(self._name)
self._widget.name.repaint()
@ -50,7 +50,7 @@ class Contact(object):
return self._status_message
def set_status_message(self, value):
self._status_message = value.decode('utf-8')
self._status_message = str(value, 'utf-8')
self._widget.status_message.setText(self._status_message)
self._widget.status_message.repaint()
@ -91,10 +91,10 @@ class Contact(object):
if not os.path.isfile(avatar_path): # load default image
avatar_path = 'avatar.png'
os.chdir(curr_directory() + '/images/')
pixmap = QtGui.QPixmap(QtCore.QSize(64, 64))
pixmap.load(avatar_path)
self._widget.avatar_label.setScaledContents(False)
self._widget.avatar_label.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio))
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):

View File

@ -8,8 +8,7 @@ try:
from PySide import QtCore
except ImportError:
from PyQt4 import QtCore
# TODO: threads!
QtCore.Signal = QtCore.pyqtSignal
TOX_FILE_TRANSFER_STATE = {
@ -34,10 +33,13 @@ ALLOWED_FILES = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png')
class StateSignal(QtCore.QObject):
try:
signal = QtCore.Signal(int, float, int) # state and progress
except:
signal = QtCore.pyqtSignal(int, float, int) # state and progress - pyqt4
signal = QtCore.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):
@ -56,6 +58,8 @@ class FileTransfer(QtCore.QObject):
self._size = float(size)
self._done = 0
self._state_changed = StateSignal()
self._finished = TransferFinishedSignal()
self._file_id = None
def set_tox(self, tox):
self._tox = tox
@ -63,6 +67,9 @@ class FileTransfer(QtCore.QObject):
def set_state_changed_handler(self, handler):
self._state_changed.signal.connect(handler)
def set_transfer_finished_handler(self, handler):
self._finished.signal.connect(handler)
def signal(self):
percentage = self._done / self._size if self._size else 0
if self._creation_time is None or not percentage:
@ -71,12 +78,21 @@ class FileTransfer(QtCore.QObject):
t = ((time() - self._creation_time) / percentage) * (1 - percentage)
self._state_changed.signal.emit(self.state, percentage, int(t))
def finished(self):
self._finished.signal.emit(self._friend_number, self._file_number)
def get_file_number(self):
return self._file_number
def get_friend_number(self):
return self._friend_number
def get_id(self):
return self._file_id
def get_path(self):
return self._path
def cancel(self):
self.send_control(TOX_FILE_CONTROL['CANCEL'])
if hasattr(self, '_file'):
@ -121,7 +137,8 @@ class SendTransfer(FileTransfer):
super(SendTransfer, self).__init__(path, tox, friend_number, size)
self.state = TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
self._file_number = tox.file_send(friend_number, kind, size, file_id,
basename(path).encode('utf-8') if path else '')
bytes(basename(path), 'utf-8') if path else b'')
self._file_id = self.get_file_id()
def send_chunk(self, position, size):
"""
@ -136,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 hasattr(self, '_file'):
self._file.close()
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
self.signal()
self.finished()
self.signal()
class SendAvatar(SendTransfer):
@ -167,7 +184,8 @@ class SendFromBuffer(FileTransfer):
super(SendFromBuffer, self).__init__(None, tox, friend_number, len(data))
self.state = TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
self._data = data
self._file_number = tox.file_send(friend_number, TOX_FILE_KIND['DATA'], len(data), None, file_name)
self._file_number = tox.file_send(friend_number, TOX_FILE_KIND['DATA'],
len(data), None, bytes(file_name, 'utf-8'))
def get_data(self):
return self._data
@ -179,10 +197,10 @@ class SendFromBuffer(FileTransfer):
data = self._data[position:position + size]
self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
self._done += size
self.signal()
else:
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
self.signal()
self.finished()
self.signal()
class SendFromFileBuffer(SendTransfer):
@ -203,16 +221,23 @@ class SendFromFileBuffer(SendTransfer):
class ReceiveTransfer(FileTransfer):
def __init__(self, path, tox, friend_number, size, file_number):
def __init__(self, path, tox, friend_number, size, file_number, position=0):
super(ReceiveTransfer, self).__init__(path, tox, friend_number, size, file_number)
self._file = open(self._path, 'wb')
self._file.truncate(0)
self._file_size = 0
self._file_size = position
self._file.truncate(position)
self._missed = set()
self._file_id = self.get_file_id()
self._done = position
def cancel(self):
super(ReceiveTransfer, self).cancel()
remove(self._path)
def total_size(self):
self._missed.add(self._file_size)
return min(self._missed)
def write_chunk(self, position, data):
"""
Incoming chunk
@ -224,19 +249,22 @@ class ReceiveTransfer(FileTransfer):
if data is None:
self._file.close()
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
self.signal()
self.finished()
else:
data = bytearray(data)
if self._file_size < position:
self._file.seek(0, 2)
self._file.write('\0' * (position - self._file_size))
self._file.write(b'\0' * (position - self._file_size))
self._missed.add(self._file_size)
else:
self._missed.discard(position)
self._file.seek(position)
self._file.write(data)
l = len(data)
if position + l > self._file_size:
self._file_size = position + l
self._done += l
self.signal()
self.signal()
class ReceiveToBuffer(FileTransfer):
@ -246,7 +274,7 @@ class ReceiveToBuffer(FileTransfer):
def __init__(self, tox, friend_number, size, file_number):
super(ReceiveToBuffer, self).__init__(None, tox, friend_number, size, file_number)
self._data = ''
self._data = bytes()
self._data_size = 0
def get_data(self):
@ -257,17 +285,17 @@ class ReceiveToBuffer(FileTransfer):
self._creation_time = time()
if data is None:
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
self.signal()
self.finished()
else:
data = ''.join(chr(x) for x in data)
data = bytes(data)
l = len(data)
if self._data_size < position:
self._data += ('\0' * (position - self._data_size))
self._data += (b'\0' * (position - self._data_size))
self._data = self._data[:position] + data + self._data[position + l:]
if position + l > self._data_size:
self._data_size = position + l
self._done += l
self.signal()
self.signal()
class ReceiveAvatar(ReceiveTransfer):
@ -281,20 +309,23 @@ class ReceiveAvatar(ReceiveTransfer):
super(ReceiveAvatar, self).__init__(path + '.tmp', tox, friend_number, size, file_number)
if size > self.MAX_AVATAR_SIZE:
self.send_control(TOX_FILE_CONTROL['CANCEL'])
self._file.close()
remove(path + '.tmp')
elif not size:
self.send_control(TOX_FILE_CONTROL['CANCEL'])
self._file.close()
if exists(path):
remove(path)
self._file.close()
remove(path + '.tmp')
elif exists(path):
hash = self.get_file_id()
with open(path) as fl:
with open(path, 'rb') as fl:
data = fl.read()
existing_hash = Tox.hash(data)
if hash == existing_hash:
self.send_control(TOX_FILE_CONTROL['CANCEL'])
self._file.close()
remove(path + '.tmp')
else:
self.send_control(TOX_FILE_CONTROL['RESUME'])
@ -309,5 +340,3 @@ class ReceiveAvatar(ReceiveTransfer):
chdir(dirname(avatar_path))
remove(avatar_path)
rename(self._path, avatar_path)

View File

@ -2,6 +2,7 @@ import contact
from messages import *
from history import *
import util
import file_transfers as ft
class Friend(contact.Contact):
@ -55,24 +56,30 @@ class Friend(contact.Contact):
"""
if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')):
return
data = self._message_getter.get(PAGE_SIZE)
data = list(self._message_getter.get(PAGE_SIZE))
if data is not None and len(data):
data.reverse()
else:
return
data = map(lambda tupl: TextMessage(*tupl), data)
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 []
"""
if hasattr(self, '_message_getter'):
del self._message_getter
messages = filter(lambda x: x.get_type() <= 1, self._corr)
return map(lambda x: x.get_data(), messages[-self._unsaved_messages:]) if self._unsaved_messages else []
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[:]
@ -86,35 +93,56 @@ class Friend(contact.Contact):
self._unsaved_messages += 1
def get_last_message_text(self):
messages = filter(lambda x: x.get_type() <= 1 and x.get_owner() != MESSAGE_OWNER['FRIEND'], self._corr)
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 unsent_messages(self):
def get_unsent_messages(self):
"""
:return list of unsent messages
"""
messages = filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
return messages
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 = filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)[0]
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):
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
self._corr = filter(lambda x: x.get_type() in (2, 3) and x.get_status() >= 2, self._corr)
self._unsaved_messages = 0
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
@ -133,8 +161,8 @@ class Friend(contact.Contact):
Update status of active transfer and load inline if needed
"""
try:
tr = filter(lambda x: x.get_type() == MESSAGE_TYPE['FILE_TRANSFER'] and x.is_active(file_number),
self._corr)[0]
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
@ -148,10 +176,10 @@ class Friend(contact.Contact):
return messages
def clear_unsent_files(self):
self._corr = filter(lambda x: type(x) is not UnsentFile, self._corr)
self._corr = list(filter(lambda x: type(x) is not UnsentFile, self._corr))
def delete_one_unsent_file(self, time):
self._corr = filter(lambda x: not (type(x) is UnsentFile and x.get_data()[2] == time), self._corr)
self._corr = list(filter(lambda x: not (type(x) is UnsentFile and x.get_data()[2] == time), self._corr))
# -----------------------------------------------------------------------------------------------------------------
# Alias support

View File

@ -3,7 +3,7 @@ from sqlite3 import connect
import settings
from os import chdir
import os.path
from toxencryptsave import LibToxEncryptSave
from toxencryptsave import ToxEncryptSave
PAGE_SIZE = 42
@ -15,14 +15,14 @@ MESSAGE_OWNER = {
}
class History(object):
class History:
def __init__(self, name):
self._name = name
chdir(settings.ProfileHelper.get_path())
path = settings.ProfileHelper.get_path() + self._name + '.hstr'
if os.path.exists(path):
decr = LibToxEncryptSave.get_instance()
decr = ToxEncryptSave.get_instance()
try:
with open(path, 'rb') as fin:
data = fin.read()
@ -40,12 +40,12 @@ class History(object):
db.close()
def save(self):
encr = LibToxEncryptSave.get_instance()
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(data)
data = encr.pass_encrypt(bytes(data))
with open(path, 'wb') as fout:
fout.write(data)
@ -54,7 +54,7 @@ class History(object):
new_path = directory + self._name + '.hstr'
with open(path, 'rb') as fin:
data = fin.read()
encr = LibToxEncryptSave.get_instance()
encr = ToxEncryptSave.get_instance()
if encr.has_password():
data = encr.pass_encrypt(data)
with open(new_path, 'wb') as fout:
@ -132,6 +132,19 @@ class History(object):
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')
@ -148,7 +161,7 @@ class History(object):
def messages_getter(self, tox_id):
return History.MessageGetter(self._name, tox_id)
class MessageGetter(object):
class MessageGetter:
def __init__(self, name, tox_id):
chdir(settings.ProfileHelper.get_path())
self._db = connect(name + '.hstr')

View File

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 329 B

After

Width:  |  Height:  |  Size: 329 B

View File

Before

Width:  |  Height:  |  Size: 609 B

After

Width:  |  Height:  |  Size: 609 B

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 118 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 231 B

After

Width:  |  Height:  |  Size: 231 B

View File

Before

Width:  |  Height:  |  Size: 405 B

After

Width:  |  Height:  |  Size: 405 B

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 159 B

After

Width:  |  Height:  |  Size: 159 B

View File

Before

Width:  |  Height:  |  Size: 445 B

After

Width:  |  Height:  |  Size: 445 B

View File

Before

Width:  |  Height:  |  Size: 201 B

After

Width:  |  Height:  |  Size: 201 B

View File

Before

Width:  |  Height:  |  Size: 351 B

After

Width:  |  Height:  |  Size: 351 B

View File

Before

Width:  |  Height:  |  Size: 306 B

After

Width:  |  Height:  |  Size: 306 B

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 481 B

After

Width:  |  Height:  |  Size: 481 B

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

View File

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

53
toxygen/libtox.py Normal file
View File

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

View File

@ -3,11 +3,12 @@ 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 cgi
import html as h
import smileys
import settings
@ -32,7 +33,7 @@ class MessageEdit(QtGui.QTextBrowser):
else:
self.setHtml(text)
font = QtGui.QFont()
font.setFamily("Times New Roman")
font.setFamily(settings.Settings.get_instance()['font'])
font.setPixelSize(settings.Settings.get_instance()['message_font_size'])
font.setBold(False)
self.setFont(font)
@ -42,10 +43,31 @@ class MessageEdit(QtGui.QTextBrowser):
def contextMenuEvent(self, event):
menu = create_menu(self.createStandardContextMenu(event.pos()))
quote = menu.addAction(QtGui.QApplication.translate("MainWindow", 'Quote selected text', None, QtGui.QApplication.UnicodeUTF8))
quote.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:'):
@ -70,7 +92,7 @@ class MessageEdit(QtGui.QTextBrowser):
self.setLineWrapColumnOrWidth(self.lineWrapColumnOrWidth())
def decoratedText(self, text):
text = cgi.escape(text) # replace < and >
text = h.escape(text) # replace < and >
exp = QtCore.QRegExp(
'('
'(?:\\b)((www\\.)|(http[s]?|ftp)://)'
@ -108,7 +130,7 @@ class MessageItem(QtGui.QWidget):
self.name.setGeometry(QtCore.QRect(2, 2, 95, 20))
self.name.setTextFormat(QtCore.Qt.PlainText)
font = QtGui.QFont()
font.setFamily("Times New Roman")
font.setFamily(settings.Settings.get_instance()['font'])
font.setPointSize(11)
font.setBold(True)
self.name.setFont(font)
@ -116,19 +138,18 @@ class MessageItem(QtGui.QWidget):
self.time = QtGui.QLabel(self)
self.time.setGeometry(QtCore.QRect(parent.width() - 50, 0, 50, 20))
font = QtGui.QFont()
font.setFamily("Times New Roman")
font.setPointSize(10)
font.setBold(False)
self.time.setFont(font)
self._time = time
if not sent:
movie = QtGui.QMovie(curr_directory() + '/images/spinner.gif')
self.time.setMovie(movie)
movie.start()
self.t = time
self.t = True
else:
self.time.setText(time)
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']:
@ -138,13 +159,34 @@ class MessageItem(QtGui.QWidget):
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 hasattr(self, 't'):
self.time.setText(self.t)
del self.t
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):
"""
@ -153,26 +195,29 @@ class ContactItem(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setBaseSize(QtCore.QSize(250, 70))
mode = settings.Settings.get_instance()['compact_mode']
self.setBaseSize(QtCore.QSize(250, 40 if mode else 70))
self.avatar_label = QtGui.QLabel(self)
self.avatar_label.setGeometry(QtCore.QRect(3, 3, 64, 64))
self.avatar_label.setScaledContents(True)
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(75, 10, 150, 25))
self.name.setGeometry(QtCore.QRect(50 if mode else 75, 3 if mode else 10, 150, 15 if mode else 25))
font = QtGui.QFont()
font.setFamily("Times New Roman")
font.setPointSize(12)
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(75, 30, 170, 20))
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, 5, 32, 32))
self.connection_status.setGeometry(QtCore.QRect(230, -2 if mode else 5, 32, 32))
self.messages = UnreadMessagesCount(self)
self.messages.setGeometry(QtCore.QRect(52, 50, 30, 20))
self.messages.setGeometry(QtCore.QRect(20 if mode else 52, 20 if mode else 50, 30, 20))
class StatusCircle(QtGui.QWidget):
@ -217,7 +262,7 @@ class UnreadMessagesCount(QtGui.QWidget):
self.label.setGeometry(QtCore.QRect(0, 0, 30, 20))
self.label.setVisible(False)
font = QtGui.QFont()
font.setFamily("Times New Roman")
font.setFamily(settings.Settings.get_instance()['font'])
font.setPointSize(12)
font.setBold(True)
self.label.setFont(font)
@ -253,7 +298,7 @@ class FileTransferItem(QtGui.QListWidget):
self.name.setGeometry(QtCore.QRect(3, 7, 95, 20))
self.name.setTextFormat(QtCore.Qt.PlainText)
font = QtGui.QFont()
font.setFamily("Times New Roman")
font.setFamily(settings.Settings.get_instance()['font'])
font.setPointSize(11)
font.setBold(True)
self.name.setFont(font)
@ -303,14 +348,14 @@ class FileTransferItem(QtGui.QListWidget):
self.file_name.setGeometry(QtCore.QRect(210, 7, width - 420, 20))
font.setPointSize(12)
self.file_name.setFont(font)
file_size = size / 1024
file_size = size // 1024
if not file_size:
file_size = '{}B'.format(size)
elif file_size >= 1024:
file_size = '{}MB'.format(file_size / 1024)
file_size = '{}MB'.format(file_size // 1024)
else:
file_size = '{}KB'.format(file_size)
file_data = u'{} {}'.format(file_size, file_name)
file_data = '{} {}'.format(file_size, file_name)
self.file_name.setText(file_data)
self.file_name.setToolTip(file_name)
self.saved_name = file_name
@ -335,7 +380,7 @@ class FileTransferItem(QtGui.QListWidget):
directory = QtGui.QFileDialog.getExistingDirectory(self,
QtGui.QApplication.translate("MainWindow", 'Choose folder', None, QtGui.QApplication.UnicodeUTF8),
curr_directory(),
QtGui.QFileDialog.ShowDirsOnly)
QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog)
self.pb.setVisible(True)
if directory:
pr = profile.Profile.get_instance()
@ -365,7 +410,7 @@ class FileTransferItem(QtGui.QListWidget):
if time + 1:
m, s = divmod(time, 60)
self.time_left.setText('{0:02d}:{1:02d}'.format(m, s))
if self.state != state:
if self.state != state and self.state in ACTIVE_FILE_TRANSFERS:
if state == TOX_FILE_TRANSFER_STATE['CANCELLED']:
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
self.cancel.setVisible(False)
@ -435,7 +480,7 @@ class InlineImageItem(QtGui.QScrollArea):
self.setWidget(self._image_label)
self._image_label.setScaledContents(False)
self._pixmap = QtGui.QPixmap()
self._pixmap.loadFromData(QtCore.QByteArray(data), "PNG")
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
@ -469,7 +514,7 @@ class InlineImageItem(QtGui.QScrollArea):
'Choose folder', None,
QtGui.QApplication.UnicodeUTF8),
curr_directory(),
QtGui.QFileDialog.ShowDirsOnly)
QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog)
if directory:
fl = QtCore.QFile(directory + '/toxygen_inline_' + curr_time().replace(':', '_') + '.png')
self._pixmap.save(fl, 'PNG')

View File

@ -57,7 +57,7 @@ class LoginScreen(CenteredWidget):
self.load_profile.raise_()
self.new_name.raise_()
self.new_profile.raise_()
self.toxygen.setGeometry(QtCore.QRect(160, 10, 90, 21))
self.toxygen.setGeometry(QtCore.QRect(160, 8, 90, 25))
font = QtGui.QFont()
font.setFamily("Impact")
font.setPointSize(16)
@ -83,7 +83,7 @@ class LoginScreen(CenteredWidget):
def create_profile(self):
self.type = 1
self.name = self.new_name.toPlainText()
self.name = self.new_name.text()
self.close()
def load_ex_profile(self):

View File

@ -1,5 +1,6 @@
import sys
from loginscreen import LoginScreen
import profile
from settings import *
try:
from PySide import QtCore, QtGui
@ -7,18 +8,16 @@ except ImportError:
from PyQt4 import QtCore, QtGui
from bootstrap import node_generator
from mainscreen import MainWindow
from profile import tox_factory
from callbacks import init_callbacks
from util import curr_directory
from callbacks import init_callbacks, stop, start
from util import curr_directory, program_version
import styles.style
import locale
import platform
import toxencryptsave
from passwordscreen import PasswordScreen
import profile
from passwordscreen import PasswordScreen, UnlockAppScreen, SetProfilePasswordScreen
from plugin_support import PluginLoader
class Toxygen(object):
class Toxygen:
def __init__(self, path_or_uri=None):
super(Toxygen, self).__init__()
@ -37,7 +36,7 @@ class Toxygen(object):
Show password screen
"""
tmp = [data]
p = PasswordScreen(toxencryptsave.LibToxEncryptSave.get_instance(), tmp)
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_()
@ -54,21 +53,24 @@ class Toxygen(object):
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.LibToxEncryptSave()
encrypt_save = toxencryptsave.ToxEncryptSave()
if self.path is not None:
path = os.path.dirname(self.path.encode(locale.getpreferredencoding())) + '/'
name = os.path.basename(self.path.encode(locale.getpreferredencoding()))[:-4]
path = os.path.dirname(self.path) + '/'
name = os.path.basename(self.path)[:-4]
data = ProfileHelper(path, name).open_profile()
if encrypt_save.is_data_encrypted(data):
data = self.enter_pass(data)
settings = Settings(name)
self.tox = tox_factory(data, settings)
self.tox = profile.tox_factory(data, settings)
else:
auto_profile = Settings.get_auto_profile()
if not auto_profile[0]:
@ -94,13 +96,53 @@ class Toxygen(object):
if not _login.t:
return
elif _login.t == 1: # create new profile
_login.name = _login.name.strip()
name = _login.name if _login.name else 'toxygen_user'
self.tox = tox_factory()
self.tox.self_set_name(_login.name if _login.name else 'Toxygen User')
self.tox.self_set_status_message('Toxing on Toxygen')
ProfileHelper(Settings.get_default_path(), name).save_profile(self.tox.get_savedata())
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:
@ -109,20 +151,19 @@ class Toxygen(object):
if encrypt_save.is_data_encrypted(data):
data = self.enter_pass(data)
settings = Settings(name)
self.tox = tox_factory(data, settings)
self.tox = profile.tox_factory(data, settings)
else:
path, name = auto_profile
path = path.encode(locale.getpreferredencoding())
data = ProfileHelper(path, name).open_profile()
if encrypt_save.is_data_encrypted(data):
data = self.enter_pass(data)
settings = Settings(name)
self.tox = tox_factory(data, settings)
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", 'Looks like other instance of Toxygen uses this profile! Continue?', None, QtGui.QApplication.UnicodeUTF8),
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:
@ -152,7 +193,7 @@ class Toxygen(object):
def aboutToShow(self):
status = profile.Profile.get_instance().status
act = self.act
if status is None:
if status is None or Settings.get_instance().locked:
self.actions()[1].setVisible(False)
else:
self.actions()[1].setVisible(True)
@ -160,6 +201,7 @@ class Toxygen(object):
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))
@ -182,13 +224,30 @@ class Toxygen(object):
exit = m.addAction(QtGui.QApplication.translate('tray', 'Exit', None, QtGui.QApplication.UnicodeUTF8))
def show_window():
if not self.ms.isActiveWindow():
self.ms.setWindowState(self.ms.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
self.ms.activateWindow()
self.ms.show()
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()"), lambda: app.exit())
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))
@ -196,12 +255,14 @@ class Toxygen(object):
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()
@ -221,6 +282,7 @@ class Toxygen(object):
self.mainloop.stop = True
self.avloop.stop = True
plugin_helper.stop()
stop()
self.mainloop.wait()
self.init.wait()
self.avloop.wait()
@ -244,7 +306,7 @@ class Toxygen(object):
ProfileHelper.get_instance().save_profile(data)
del self.tox
# create new tox instance
self.tox = tox_factory(data, Settings.get_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()
@ -281,9 +343,10 @@ class Toxygen(object):
if self.stop:
return
self.tox.bootstrap(*data)
self.tox.add_tcp_relay(*data)
except:
pass
for _ in xrange(10):
for _ in range(10):
if self.stop:
return
self.msleep(1000)
@ -293,6 +356,7 @@ class Toxygen(object):
if self.stop:
return
self.tox.bootstrap(*data)
self.tox.add_tcp_relay(*data)
except:
pass
finally:
@ -322,7 +386,7 @@ class Toxygen(object):
self.toxav.iterate()
self.msleep(self.toxav.iteration_interval())
class Login(object):
class Login:
def __init__(self, arr):
self.arr = arr
@ -343,9 +407,56 @@ class Toxygen(object):
return self.arr[self.num]
if __name__ == '__main__':
def clean():
"""Removes all windows libs from libs folder"""
d = curr_directory() + '/libs/'
for fl in ('libtox64.dll', 'libtox.dll', 'libsodium64.a', 'libsodium.a'):
if os.path.exists(d + fl):
os.remove(d + fl)
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():
if len(sys.argv) == 1:
toxygen = Toxygen()
else: # path to profile or tox: uri
toxygen = Toxygen(sys.argv[1])
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()
if __name__ == '__main__':
main()

View File

@ -6,16 +6,20 @@ from list_items import *
from widgets import MultilineEdit, LineEdit
import plugin_support
from mainscreen_widgets import *
import settings
class MainWindow(QtGui.QMainWindow):
class MainWindow(QtGui.QMainWindow, Singleton):
def __init__(self, tox, reset, tray):
super(MainWindow, self).__init__()
super().__init__()
Singleton.__init__(self)
self.reset = reset
self.tray = tray
self.setAcceptDrops(True)
self.initUI(tox)
if settings.Settings.get_instance()['show_welcome_screen']:
self.ws = WelcomeScreen()
def setup_menu(self, MainWindow):
self.menubar = QtGui.QMenuBar(MainWindow)
@ -36,8 +40,8 @@ class MainWindow(QtGui.QMainWindow):
self.actionAdd_friend = QtGui.QAction(MainWindow)
self.actionAdd_friend.setObjectName("actionAdd_friend")
self.actionProfile_settings = QtGui.QAction(MainWindow)
self.actionProfile_settings.setObjectName("actionProfile_settings")
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)
@ -52,14 +56,18 @@ class MainWindow(QtGui.QMainWindow):
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())
@ -75,6 +83,8 @@ class MainWindow(QtGui.QMainWindow):
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):
@ -83,16 +93,18 @@ class MainWindow(QtGui.QMainWindow):
def event(self, event):
if event.type() == QtCore.QEvent.WindowActivate:
self.tray.setIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
self.messages.repaint()
return super(MainWindow, self).event(event)
def retranslateUi(self):
self.lockApp.setText(QtGui.QApplication.translate("MainWindow", "Lock", None, QtGui.QApplication.UnicodeUTF8))
self.menuPlugins.setTitle(QtGui.QApplication.translate("MainWindow", "Plugins", None, QtGui.QApplication.UnicodeUTF8))
self.pluginData.setText(QtGui.QApplication.translate("MainWindow", "List of plugins", None, QtGui.QApplication.UnicodeUTF8))
self.menuProfile.setTitle(QtGui.QApplication.translate("MainWindow", "Profile", None, QtGui.QApplication.UnicodeUTF8))
self.menuSettings.setTitle(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8))
self.menuAbout.setTitle(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8))
self.actionAdd_friend.setText(QtGui.QApplication.translate("MainWindow", "Add contact", None, QtGui.QApplication.UnicodeUTF8))
self.actionProfile_settings.setText(QtGui.QApplication.translate("MainWindow", "Profile", 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))
@ -107,6 +119,7 @@ class MainWindow(QtGui.QMainWindow):
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)
@ -115,6 +128,7 @@ class MainWindow(QtGui.QMainWindow):
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)
@ -166,11 +180,12 @@ class MainWindow(QtGui.QMainWindow):
Form.setBaseSize(QtCore.QSize(270, 100))
self.avatar_label = Form.avatar_label = QtGui.QLabel(Form)
self.avatar_label.setGeometry(QtCore.QRect(5, 30, 64, 64))
self.avatar_label.setScaledContents(True)
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("Times New Roman")
font.setFamily(settings.Settings.get_instance()['font'])
font.setPointSize(14)
font.setBold(True)
Form.name.setFont(font)
@ -193,12 +208,12 @@ class MainWindow(QtGui.QMainWindow):
Form.resize(650, 100)
self.account_avatar = QtGui.QLabel(Form)
self.account_avatar.setGeometry(QtCore.QRect(10, 30, 64, 64))
self.account_avatar.setScaledContents(True)
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("Times New Roman")
font.setFamily(settings.Settings.get_instance()['font'])
font.setPointSize(14)
font.setBold(True)
self.account_name.setFont(font)
@ -245,7 +260,7 @@ class MainWindow(QtGui.QMainWindow):
self.messages.setSpacing(1)
self.messages.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.messages.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.messages.setFocusPolicy(QtCore.Qt.NoFocus)
# self.messages.setFocusPolicy(QtCore.Qt.NoFocus)
def load(pos):
if not pos:
@ -253,6 +268,7 @@ class MainWindow(QtGui.QMainWindow):
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)
@ -309,16 +325,21 @@ class MainWindow(QtGui.QMainWindow):
self.retranslateUi()
self.profile = Profile(tox, self)
def closeEvent(self, *args, **kwargs):
def closeEvent(self, event):
self.profile.save_history()
self.profile.close()
s = Settings.get_instance()
s['x'] = self.pos().x()
s['y'] = self.pos().y()
s['width'] = self.width()
s['height'] = self.height()
s.save()
QtGui.QApplication.closeAllWindows()
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)
@ -340,6 +361,14 @@ class MainWindow(QtGui.QMainWindow):
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)
@ -352,7 +381,7 @@ class MainWindow(QtGui.QMainWindow):
msgBox = QtGui.QMessageBox()
msgBox.setWindowTitle(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8))
text = (QtGui.QApplication.translate("MainWindow", 'Toxygen is Tox client written on Python.\nVersion: ', None, QtGui.QApplication.UnicodeUTF8))
msgBox.setText(text + util.program_version + '\nGitHub: github.com/xveduk/toxygen/')
msgBox.setText(text + util.program_version + '\nGitHub: https://github.com/toxygen-project/toxygen/')
msgBox.exec_()
def network_settings(self):
@ -364,7 +393,7 @@ class MainWindow(QtGui.QMainWindow):
self.p_s.show()
def add_contact(self, link=''):
self.a_c = AddContact(link)
self.a_c = AddContact(link or '')
self.a_c.show()
def profile_settings(self, *args):
@ -387,13 +416,46 @@ class MainWindow(QtGui.QMainWindow):
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() - 100,
150,
100))
self.height() - 120,
180,
120))
self.menu.show()
# -----------------------------------------------------------------------------------------------------------------
@ -408,7 +470,7 @@ class MainWindow(QtGui.QMainWindow):
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)
name = QtGui.QFileDialog.getOpenFileName(self, choose, options=QtGui.QFileDialog.DontUseNativeDialog)
if name[0]:
self.profile.send_file(name[0])
@ -475,8 +537,17 @@ class MainWindow(QtGui.QMainWindow):
if item is not None:
self.listMenu = QtGui.QMenu()
set_alias_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Set alias', None, QtGui.QApplication.UnicodeUTF8))
clear_history_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8))
copy_key_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Copy public key', 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))
@ -491,6 +562,11 @@ class MainWindow(QtGui.QMainWindow):
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()
@ -499,7 +575,7 @@ class MainWindow(QtGui.QMainWindow):
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 = u'{} {}'.format(user, friend.name)
user = '{} {}'.format(user, friend.name)
def save_note(text):
if friend.tox_id in s['notes']:
@ -510,6 +586,20 @@ class MainWindow(QtGui.QMainWindow):
self.note = MultilineEdit(user, note, save_note)
self.note.show()
def export_history(self, num, as_text=True):
s = self.profile.export_history(num, as_text)
directory = 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)
@ -521,6 +611,14 @@ class MainWindow(QtGui.QMainWindow):
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)
@ -551,4 +649,3 @@ class MainWindow(QtGui.QMainWindow):
def filtering(self):
self.profile.filtration(self.online_contacts.currentIndex() == 1, self.contact_name.text())

View File

@ -2,7 +2,7 @@ try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
from widgets import RubberBand, create_menu, QRightClickButton
from widgets import RubberBand, create_menu, QRightClickButton, CenteredWidget
from profile import Profile
import smileys
import util
@ -20,7 +20,12 @@ class MessageArea(QtGui.QPlainTextEdit):
def keyPressEvent(self, event):
if event.matches(QtGui.QKeySequence.Paste):
self.pasteEvent()
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:
@ -51,9 +56,13 @@ class MessageArea(QtGui.QPlainTextEdit):
e.accept()
def dropEvent(self, e):
if e.mimeData().hasFormat('text/plain'):
if e.mimeData().hasFormat('text/plain') or e.mimeData().hasFormat('text/html'):
e.accept()
self.pasteEvent(e.mimeData().text())
elif e.mimeData().hasUrls():
for url in e.mimeData().urls():
self.pasteEvent(url.toString())
e.accept()
else:
e.ignore()
@ -99,7 +108,6 @@ class ScreenShotWindow(QtGui.QWidget):
if self.rubberband.isVisible():
self.rubberband.hide()
rect = self.rubberband.geometry()
print rect
if rect.width() and rect.height():
p = QtGui.QPixmap.grabWindow(QtGui.QApplication.desktop().winId(),
rect.x() + 4,
@ -110,7 +118,7 @@ class ScreenShotWindow(QtGui.QWidget):
buffer = QtCore.QBuffer(byte_array)
buffer.open(QtCore.QIODevice.WriteOnly)
p.save(buffer, 'PNG')
Profile.get_instance().send_screenshot(str(byte_array.data()))
Profile.get_instance().send_screenshot(bytes(byte_array.data()))
self.close()
def keyPressEvent(self, event):
@ -132,12 +140,14 @@ class SmileyWindow(QtGui.QWidget):
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
self.page_count = count // self.page_size
else:
self.page_count = int(count / float(self.page_size) + 0.5)
self.page = 0
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
@ -145,13 +155,13 @@ class SmileyWindow(QtGui.QWidget):
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)
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.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)
@ -197,52 +207,52 @@ class DropdownMenu(QtGui.QWidget):
super(DropdownMenu, self).__init__(parent)
self.installEventFilter(self)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.setMaximumSize(150, 100)
self.setMinimumSize(150, 100)
self.setMaximumSize(180, 120)
self.setMinimumSize(180, 120)
self.screenshotButton = QRightClickButton(self)
self.screenshotButton.setGeometry(QtCore.QRect(0, 50, 50, 50))
self.screenshotButton.setGeometry(QtCore.QRect(0, 60, 60, 60))
self.screenshotButton.setObjectName("screenshotButton")
self.fileTransferButton = QtGui.QPushButton(self)
self.fileTransferButton.setGeometry(QtCore.QRect(50, 50, 50, 50))
self.fileTransferButton.setGeometry(QtCore.QRect(60, 60, 60, 60))
self.fileTransferButton.setObjectName("fileTransferButton")
self.audioMessageButton = QtGui.QPushButton(self)
self.audioMessageButton.setGeometry(QtCore.QRect(100, 50, 50, 50))
self.audioMessageButton.setGeometry(QtCore.QRect(120, 60, 60, 60))
self.smileyButton = QtGui.QPushButton(self)
self.smileyButton.setGeometry(QtCore.QRect(0, 0, 50, 50))
self.smileyButton.setGeometry(QtCore.QRect(0, 0, 60, 60))
self.videoMessageButton = QtGui.QPushButton(self)
self.videoMessageButton.setGeometry(QtCore.QRect(100, 0, 50, 50))
self.videoMessageButton.setGeometry(QtCore.QRect(120, 0, 60, 60))
self.stickerButton = QtGui.QPushButton(self)
self.stickerButton.setGeometry(QtCore.QRect(50, 0, 50, 50))
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(40, 40))
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(40, 50))
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(40, 40))
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(40, 40))
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(45, 45))
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(45, 45))
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))
@ -311,3 +321,74 @@ class StickerWindow(QtGui.QWidget):
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 <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a>',
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. <a href="https://github.com/xveduk/toxygen/blob/master/docs/plugins.md">Read more</a>',
None, QtGui.QApplication.UnicodeUTF8)
elif num == 6:
text = QtGui.QApplication.translate('WelcomeScreen',
'New in Toxygen v0.2.3:<br>TCS compliance<br>Plugins, smileys and stickers import<br>Bug fixes',
None, QtGui.QApplication.UnicodeUTF8)
elif num == 7:
text = QtGui.QApplication.translate('WelcomeScreen',
'Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.',
None, QtGui.QApplication.UnicodeUTF8)
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()

View File

@ -4,7 +4,7 @@ except ImportError:
from PyQt4 import QtCore, QtGui
from settings import *
from profile import Profile
from util import curr_directory
from util import curr_directory, copy
from widgets import CenteredWidget, DataLabel, LineEdit
import pyaudio
import toxencryptsave
@ -17,6 +17,7 @@ class AddContact(CenteredWidget):
def __init__(self, tox_id=''):
super(AddContact, self).__init__()
self.initUI(tox_id)
self._adding = False
def initUI(self, tox_id):
self.setObjectName('AddContact')
@ -36,6 +37,7 @@ class AddContact(CenteredWidget):
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)
@ -50,7 +52,6 @@ class AddContact(CenteredWidget):
self.message.setObjectName("label_2")
self.retranslateUi()
self.message_edit.setText('Hello! Add me to your contact list please')
font = QtGui.QFont()
font.setPointSize(12)
font.setBold(True)
self.label.setFont(font)
@ -58,8 +59,12 @@ class AddContact(CenteredWidget):
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(), self.message_edit.toPlainText())
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()
@ -97,6 +102,7 @@ class ProfileSettings(CenteredWidget):
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)
@ -122,17 +128,19 @@ class ProfileSettings(CenteredWidget):
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(40, 300, 180, 30))
self.new_avatar.setGeometry(QtCore.QRect(230, 300, 180, 30))
self.delete_avatar = QtGui.QPushButton(self)
self.delete_avatar.setGeometry(QtCore.QRect(230, 300, 180, 30))
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.profile_pass = QtGui.QLabel(self)
self.profile_pass.setGeometry(QtCore.QRect(40, 340, 300, 30))
self.profilepass = QtGui.QLabel(self)
self.profilepass.setGeometry(QtCore.QRect(40, 340, 300, 30))
font.setPointSize(18)
self.profile_pass.setFont(font)
self.profilepass.setFont(font)
self.password = LineEdit(self)
self.password.setGeometry(QtCore.QRect(40, 380, 300, 30))
self.password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
@ -173,7 +181,7 @@ class ProfileSettings(CenteredWidget):
self.new_avatar.setText(QtGui.QApplication.translate("ProfileSettingsForm", "New avatar", None, QtGui.QApplication.UnicodeUTF8))
self.delete_avatar.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Reset avatar", None, QtGui.QApplication.UnicodeUTF8))
self.new_nospam.setText(QtGui.QApplication.translate("ProfileSettingsForm", "New NoSpam", None, QtGui.QApplication.UnicodeUTF8))
self.profile_pass.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Profile password", None, QtGui.QApplication.UnicodeUTF8))
self.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))
@ -183,6 +191,7 @@ class ProfileSettings(CenteredWidget):
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:
@ -205,7 +214,7 @@ class ProfileSettings(CenteredWidget):
def new_password(self):
if self.password.text() == self.confirm_password.text():
if not len(self.password.text()) or len(self.password.text()) >= 8:
e = toxencryptsave.LibToxEncryptSave.get_instance()
e = toxencryptsave.ToxEncryptSave.get_instance()
e.set_password(self.password.text())
self.close()
else:
@ -227,6 +236,15 @@ class ProfileSettings(CenteredWidget):
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())
@ -235,7 +253,8 @@ class ProfileSettings(CenteredWidget):
def set_avatar(self):
choose = QtGui.QApplication.translate("ProfileSettingsForm", "Choose avatar", None, QtGui.QApplication.UnicodeUTF8)
name = QtGui.QFileDialog.getOpenFileName(self, choose, None, 'Images (*.png)')
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,
@ -245,20 +264,32 @@ class ProfileSettings(CenteredWidget):
buffer = QtCore.QBuffer(byte_array)
buffer.open(QtCore.QIODevice.WriteOnly)
bitmap.save(buffer, 'PNG')
Profile.get_instance().set_avatar(str(byte_array.data()))
Profile.get_instance().set_avatar(bytes(byte_array.data()))
def export_profile(self):
directory = QtGui.QFileDialog.getExistingDirectory() + '/'
directory = QtGui.QFileDialog.getExistingDirectory(options=QtGui.QFileDialog.DontUseNativeDialog,
dir=curr_directory()) + '/'
if directory != '/':
ProfileHelper.get_instance().export_profile(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_history(directory)
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().encode('utf-8'))
profile.set_name(self.nick.text())
profile.set_status_message(self.status_message.text().encode('utf-8'))
profile.set_status(self.status.currentIndex())
@ -306,7 +337,7 @@ class NetworkSettings(CenteredWidget):
self.udp.setChecked(settings['udp_enabled'])
self.proxy.setChecked(settings['proxy_type'])
self.proxyip.setText(settings['proxy_host'])
self.proxyport.setText(unicode(settings['proxy_port']))
self.proxyport.setText(str(settings['proxy_port']))
self.http.setChecked(settings['proxy_type'] == 1)
self.warning = QtGui.QLabel(self)
self.warning.setGeometry(QtCore.QRect(5, 270, 290, 60))
@ -360,45 +391,51 @@ class PrivacySettings(CenteredWidget):
def initUI(self):
self.setObjectName("privacySettings")
self.resize(350, 550)
self.setMinimumSize(QtCore.QSize(350, 550))
self.setMaximumSize(QtCore.QSize(350, 550))
self.resize(370, 600)
self.setMinimumSize(QtCore.QSize(370, 600))
self.setMaximumSize(QtCore.QSize(370, 600))
self.saveHistory = QtGui.QCheckBox(self)
self.saveHistory.setGeometry(QtCore.QRect(10, 20, 291, 22))
self.saveHistory.setGeometry(QtCore.QRect(10, 20, 350, 22))
self.saveUnsentOnly = QtGui.QCheckBox(self)
self.saveUnsentOnly.setGeometry(QtCore.QRect(10, 60, 350, 22))
self.fileautoaccept = QtGui.QCheckBox(self)
self.fileautoaccept.setGeometry(QtCore.QRect(10, 60, 271, 22))
self.fileautoaccept.setGeometry(QtCore.QRect(10, 100, 350, 22))
self.typingNotifications = QtGui.QCheckBox(self)
self.typingNotifications.setGeometry(QtCore.QRect(10, 100, 350, 30))
self.typingNotifications.setGeometry(QtCore.QRect(10, 140, 350, 30))
self.inlines = QtGui.QCheckBox(self)
self.inlines.setGeometry(QtCore.QRect(10, 140, 350, 30))
self.inlines.setGeometry(QtCore.QRect(10, 180, 350, 30))
self.auto_path = QtGui.QLabel(self)
self.auto_path.setGeometry(QtCore.QRect(10, 190, 350, 30))
self.auto_path.setGeometry(QtCore.QRect(10, 230, 350, 30))
self.path = QtGui.QPlainTextEdit(self)
self.path.setGeometry(QtCore.QRect(10, 225, 330, 45))
self.path.setGeometry(QtCore.QRect(10, 265, 350, 45))
self.change_path = QtGui.QPushButton(self)
self.change_path.setGeometry(QtCore.QRect(10, 280, 330, 30))
self.change_path.setGeometry(QtCore.QRect(10, 320, 350, 30))
settings = Settings.get_instance()
self.typingNotifications.setChecked(settings['typing_notifications'])
self.fileautoaccept.setChecked(settings['allow_auto_accept'])
self.saveHistory.setChecked(settings['save_history'])
self.inlines.setChecked(settings['allow_inline'])
self.saveUnsentOnly.setChecked(settings['save_unsent_only'])
self.saveUnsentOnly.setEnabled(settings['save_history'])
self.saveHistory.stateChanged.connect(self.update)
self.path.setPlainText(settings['auto_accept_path'] or curr_directory())
self.change_path.clicked.connect(self.new_path)
self.block_user_label = QtGui.QLabel(self)
self.block_user_label.setGeometry(QtCore.QRect(10, 320, 330, 30))
self.block_user_label.setGeometry(QtCore.QRect(10, 360, 350, 30))
self.block_id = QtGui.QPlainTextEdit(self)
self.block_id.setGeometry(QtCore.QRect(10, 350, 330, 30))
self.block_id.setGeometry(QtCore.QRect(10, 390, 350, 30))
self.block = QtGui.QPushButton(self)
self.block.setGeometry(QtCore.QRect(10, 390, 330, 30))
self.block.setGeometry(QtCore.QRect(10, 430, 350, 30))
self.block.clicked.connect(lambda: Profile.get_instance().block_user(self.block_id.toPlainText()) or self.close())
self.blocked_users_label = QtGui.QLabel(self)
self.blocked_users_label.setGeometry(QtCore.QRect(10, 430, 330, 30))
self.blocked_users_label.setGeometry(QtCore.QRect(10, 470, 350, 30))
self.comboBox = QtGui.QComboBox(self)
self.comboBox.setGeometry(QtCore.QRect(10, 460, 330, 30))
self.comboBox.setGeometry(QtCore.QRect(10, 500, 350, 30))
self.comboBox.addItems(settings['blocked'])
self.unblock = QtGui.QPushButton(self)
self.unblock.setGeometry(QtCore.QRect(10, 500, 330, 30))
self.unblock.setGeometry(QtCore.QRect(10, 540, 350, 30))
self.unblock.clicked.connect(lambda: self.unblock_user())
self.retranslateUi()
QtCore.QMetaObject.connectSlotsByName(self)
@ -415,6 +452,12 @@ class PrivacySettings(CenteredWidget):
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():
@ -429,6 +472,7 @@ class PrivacySettings(CenteredWidget):
settings = Settings.get_instance()
settings['typing_notifications'] = self.typingNotifications.isChecked()
settings['allow_auto_accept'] = self.fileautoaccept.isChecked()
if settings['save_history'] and not self.saveHistory.isChecked(): # clear history
reply = QtGui.QMessageBox.question(None,
QtGui.QApplication.translate("privacySettings",
@ -444,12 +488,27 @@ class PrivacySettings(CenteredWidget):
settings['save_history'] = self.saveHistory.isChecked()
else:
settings['save_history'] = self.saveHistory.isChecked()
if self.saveUnsentOnly.isChecked() and not settings['save_unsent_only']:
reply = QtGui.QMessageBox.question(None,
QtGui.QApplication.translate("privacySettings",
'Chat history',
None, QtGui.QApplication.UnicodeUTF8),
QtGui.QApplication.translate("privacySettings",
'History will be cleaned! Continue?',
None, QtGui.QApplication.UnicodeUTF8),
QtGui.QMessageBox.Yes,
QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
Profile.get_instance().clear_history(None, True)
settings['save_unsent_only'] = self.saveUnsentOnly.isChecked()
else:
settings['save_unsent_only'] = self.saveUnsentOnly.isChecked()
settings['auto_accept_path'] = self.path.toPlainText()
settings['allow_inline'] = self.inlines.isChecked()
settings.save()
def new_path(self):
directory = QtGui.QFileDialog.getExistingDirectory() + '/'
directory = QtGui.QFileDialog.getExistingDirectory(options=QtGui.QFileDialog.DontUseNativeDialog) + '/'
if directory != '/':
self.path.setPlainText(directory)
@ -474,11 +533,12 @@ class NotificationsSettings(CenteredWidget):
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)
s = Settings.get_instance()
self.enableNotifications.setChecked(s['notifications'])
self.soundNotifications.setChecked(s['sound_notifications'])
self.callsSound.setChecked(s['calls_sound'])
@ -508,19 +568,20 @@ class InterfaceSettings(CenteredWidget):
def initUI(self):
self.setObjectName("interfaceForm")
self.setMinimumSize(QtCore.QSize(400, 450))
self.setMaximumSize(QtCore.QSize(400, 450))
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)
settings = Settings.get_instance()
theme = settings['theme']
if theme in list_of_themes:
index = list_of_themes.index(theme)
@ -529,7 +590,7 @@ class InterfaceSettings(CenteredWidget):
self.themeSelect.setCurrentIndex(index)
self.lang_choose = QtGui.QComboBox(self)
self.lang_choose.setGeometry(QtCore.QRect(30, 110, 120, 30))
supported = Settings.supported_languages()
supported = sorted(Settings.supported_languages().keys(), reverse=True)
for key in supported:
self.lang_choose.insertItem(0, key)
if settings['language'] == key:
@ -564,13 +625,38 @@ class InterfaceSettings(CenteredWidget):
self.messages_font_size.setCurrentIndex(settings['message_font_size'] - 10)
self.unread = QtGui.QPushButton(self)
self.unread.setGeometry(QtCore.QRect(30, 380, 340, 40))
self.unread.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))
@ -579,6 +665,53 @@ class InterfaceSettings(CenteredWidget):
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()
@ -593,16 +726,18 @@ class InterfaceSettings(CenteredWidget):
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()
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_()
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:
@ -616,6 +751,14 @@ class InterfaceSettings(CenteredWidget):
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):
@ -638,9 +781,11 @@ class AudioSettings(CenteredWidget):
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)
@ -648,15 +793,14 @@ class AudioSettings(CenteredWidget):
self.output = QtGui.QComboBox(self)
self.output.setGeometry(QtCore.QRect(25, 90, 350, 30))
p = pyaudio.PyAudio()
settings = Settings.get_instance()
self.in_indexes, self.out_indexes = [], []
for i in xrange(p.get_device_count()):
for i in range(p.get_device_count()):
device = p.get_device_info_by_index(i)
if device["maxInputChannels"]:
self.input.addItem(unicode(device["name"]))
self.input.addItem(str(device["name"]))
self.in_indexes.append(i)
if device["maxOutputChannels"]:
self.output.addItem(unicode(device["name"]))
self.output.addItem(str(device["name"]))
self.out_indexes.append(i)
self.input.setCurrentIndex(self.in_indexes.index(settings.audio['input']))
self.output.setCurrentIndex(self.out_indexes.index(settings.audio['output']))
@ -728,7 +872,7 @@ class PluginsSettings(CenteredWidget):
def update_list(self):
self.comboBox.clear()
data = self.pl_loader.get_plugins_list()
self.comboBox.addItems(map(lambda x: x[0], data))
self.comboBox.addItems(list(map(lambda x: x[0], data)))
self.data = data
def show_data(self):

View File

@ -9,7 +9,7 @@ MESSAGE_TYPE = {
}
class Message(object):
class Message:
def __init__(self, message_type, owner, time):
self._time = time

View File

@ -35,7 +35,7 @@ def tray_notification(title, text, tray, window):
tray.connect(tray, QtCore.SIGNAL("messageClicked()"), message_clicked)
class AudioFile(object):
class AudioFile:
chunk = 1024
def __init__(self, fl):

164
toxygen/passwordscreen.py Normal file
View File

@ -0,0 +1,164 @@
from widgets import CenteredWidget, LineEdit
try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
class PasswordArea(LineEdit):
def __init__(self, parent):
super(PasswordArea, self).__init__(parent)
self.parent = parent
self.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Return:
self.parent.button_click()
else:
super(PasswordArea, self).keyPressEvent(event)
class PasswordScreenBase(CenteredWidget):
def __init__(self, encrypt):
super(PasswordScreenBase, self).__init__()
self._encrypt = encrypt
self.initUI()
def initUI(self):
self.resize(360, 170)
self.setMinimumSize(QtCore.QSize(360, 170))
self.setMaximumSize(QtCore.QSize(360, 170))
self.enter_pass = QtGui.QLabel(self)
self.enter_pass.setGeometry(QtCore.QRect(30, 10, 300, 30))
self.password = PasswordArea(self)
self.password.setGeometry(QtCore.QRect(30, 50, 300, 30))
self.button = QtGui.QPushButton(self)
self.button.setGeometry(QtCore.QRect(30, 90, 300, 30))
self.button.setText('OK')
self.button.clicked.connect(self.button_click)
self.warning = QtGui.QLabel(self)
self.warning.setGeometry(QtCore.QRect(30, 130, 300, 30))
self.warning.setStyleSheet('QLabel { color: #F70D1A; }')
self.warning.setVisible(False)
self.retranslateUi()
self.center()
QtCore.QMetaObject.connectSlotsByName(self)
def button_click(self):
pass
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Enter:
self.button_click()
else:
super(PasswordScreenBase, self).keyPressEvent(event)
def retranslateUi(self):
self.setWindowTitle(QtGui.QApplication.translate("pass", "Enter password", None, QtGui.QApplication.UnicodeUTF8))
self.enter_pass.setText(QtGui.QApplication.translate("pass", "Password:", None, QtGui.QApplication.UnicodeUTF8))
self.warning.setText(QtGui.QApplication.translate("pass", "Incorrect password", None, QtGui.QApplication.UnicodeUTF8))
class PasswordScreen(PasswordScreenBase):
def __init__(self, encrypt, data):
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[0])
except Exception as ex:
self.warning.setVisible(True)
print('Decryption error:', ex)
else:
self._data[0] = new_data
self.close()
class UnlockAppScreen(PasswordScreenBase):
def __init__(self, encrypt, callback):
super(UnlockAppScreen, self).__init__(encrypt)
self._callback = callback
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
def button_click(self):
if self.password.text():
if self._encrypt.is_password(self.password.text()):
self._callback()
self.close()
else:
self.warning.setVisible(True)
print('Wrong password!')
class SetProfilePasswordScreen(CenteredWidget):
def __init__(self, encrypt):
super(SetProfilePasswordScreen, self).__init__()
self._encrypt = encrypt
self.initUI()
self.retranslateUi()
self.center()
def initUI(self):
self.setMinimumSize(QtCore.QSize(700, 200))
self.setMaximumSize(QtCore.QSize(700, 200))
self.password = LineEdit(self)
self.password.setGeometry(QtCore.QRect(40, 10, 300, 30))
self.password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
self.confirm_password = LineEdit(self)
self.confirm_password.setGeometry(QtCore.QRect(40, 50, 300, 30))
self.confirm_password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
self.set_password = QtGui.QPushButton(self)
self.set_password.setGeometry(QtCore.QRect(40, 100, 300, 30))
self.set_password.clicked.connect(self.new_password)
self.not_match = QtGui.QLabel(self)
self.not_match.setGeometry(QtCore.QRect(350, 50, 300, 30))
self.not_match.setVisible(False)
self.not_match.setStyleSheet('QLabel { color: #BC1C1C; }')
self.warning = QtGui.QLabel(self)
self.warning.setGeometry(QtCore.QRect(40, 160, 500, 30))
self.warning.setStyleSheet('QLabel { color: #BC1C1C; }')
def retranslateUi(self):
self.setWindowTitle(QtGui.QApplication.translate("PasswordScreen", "Profile password", None,
QtGui.QApplication.UnicodeUTF8))
self.password.setPlaceholderText(
QtGui.QApplication.translate("PasswordScreen", "Password (at least 8 symbols)", None,
QtGui.QApplication.UnicodeUTF8))
self.confirm_password.setPlaceholderText(
QtGui.QApplication.translate("PasswordScreen", "Confirm password", None,
QtGui.QApplication.UnicodeUTF8))
self.set_password.setText(
QtGui.QApplication.translate("PasswordScreen", "Set password", None, QtGui.QApplication.UnicodeUTF8))
self.not_match.setText(QtGui.QApplication.translate("PasswordScreen", "Passwords do not match", None,
QtGui.QApplication.UnicodeUTF8))
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():
if len(self.password.text()) >= 8:
self._encrypt.set_password(self.password.text())
self.close()
else:
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(QtGui.QApplication.translate("PasswordScreen", "Passwords do not match", None,
QtGui.QApplication.UnicodeUTF8))
self.not_match.setVisible(True)

View File

@ -1,20 +1,22 @@
import util
import profile
import os
import imp
import importlib
import inspect
import plugins.plugin_super_class as pl
import toxencryptsave
import sys
class PluginLoader(util.Singleton):
def __init__(self, tox, settings):
super().__init__()
self._profile = profile.Profile.get_instance()
self._settings = settings
self._plugins = {} # dict. key - plugin unique short name, value - tuple (plugin instance, is active)
self._tox = tox
self._encr = toxencryptsave.LibToxEncryptSave.get_instance()
self._encr = toxencryptsave.ToxEncryptSave.get_instance()
def set_tox(self, tox):
"""
@ -32,13 +34,15 @@ class PluginLoader(util.Singleton):
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 = imp.load_source('plugins.' + name, path + fl) # import plugin
module = importlib.import_module(name) # import plugin
except ImportError:
util.log('Import error in module ' + name)
continue
@ -47,8 +51,8 @@ class PluginLoader(util.Singleton):
continue
for elem in dir(module):
obj = getattr(module, elem)
if inspect.isclass(obj) and issubclass(obj, pl.PluginSuperClass): # looking for plugin class in module
print 'Plugin', 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']
@ -143,10 +147,21 @@ class PluginLoader(util.Singleton):
continue
return result
def get_message_menu(self, menu, selected_text):
result = []
for elem in self._plugins.values():
if elem[1]:
try:
result.extend(elem[0].get_message_menu(menu, selected_text))
except:
continue
return result
def stop(self):
"""
App is closing, stop all plugins
"""
for key in self._plugins.keys():
self._plugins[key][0].close()
for key in list(self._plugins.keys()):
if self._plugins[key][1]:
self._plugins[key][0].close()
del self._plugins[key]

View File

@ -26,16 +26,18 @@ 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(object):
class PluginSuperClass:
"""
Superclass for all plugins. Plugin is python module with at least one class derived from PluginSuperClass.
"""
is_plugin = True
def __init__(self, name, short_name, tox=None, profile=None, settings=None, encrypt_save=None):
"""
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 tox: tox instance
@ -86,6 +88,15 @@ class PluginSuperClass(object):
"""
return []
def get_message_menu(self, menu, text):
"""
This method creates items for menu which called on right click in message
:param menu: menu instance
:param text: selected text
:return list of QAction's
"""
return []
def get_window(self):
"""
This method should return window for plugins with GUI or None
@ -159,9 +170,9 @@ class PluginSuperClass(object):
"""
This method loads settings of plugin and returns raw data
"""
with open(path_to_data(self._short_name) + 'settings.json') as fl:
with open(path_to_data(self._short_name) + 'settings.json', 'rb') as fl:
data = fl.read()
return data
return str(data, 'utf-8')
def save_settings(self, data):
"""
@ -169,7 +180,7 @@ class PluginSuperClass(object):
:param data: string with data
"""
with open(path_to_data(self._short_name) + 'settings.json', 'wb') as fl:
fl.write(data)
fl.write(bytes(data, 'utf-8'))
# -----------------------------------------------------------------------------------------------------------------
# Callbacks
@ -212,8 +223,10 @@ class PluginSuperClass(object):
data = ''
try:
return self._tox.friend_send_lossless_packet(friend_number,
chr(len(self._short_name) + LOSSLESS_FIRST_BYTE) +
self._short_name + str(data))
bytes([ord(x) for x in
chr(len(self._short_name) + LOSSLESS_FIRST_BYTE) +
self._short_name + str(data)
]))
except:
return False
@ -228,7 +241,9 @@ class PluginSuperClass(object):
data = ''
try:
return self._tox.friend_send_lossy_packet(friend_number,
chr(len(self._short_name) + LOSSY_FIRST_BYTE) +
self._short_name + str(data))
bytes([ord(x) for x in
chr(len(self._short_name) + LOSSY_FIRST_BYTE) +
self._short_name + str(data)
]))
except:
return False

View File

@ -7,7 +7,7 @@ from friend import *
from settings import *
from toxcore_enums_and_consts import *
from ctypes import *
from util import curr_time, log, Singleton, curr_directory, convert_time
from util import log, Singleton, curr_directory
from tox_dns import tox_dns
from history import *
from file_transfers import *
@ -26,18 +26,25 @@ class Profile(contact.Contact, Singleton):
:param tox: tox instance
:param screen: ref to main screen
"""
super(Profile, self).__init__(tox.self_get_name(),
tox.self_get_status_message(),
screen.user_info,
tox.self_get_address())
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()
@ -45,15 +52,15 @@ class Profile(contact.Contact, Singleton):
self._friends, self._active_friend = [], -1
for i in data: # creates list of friends
tox_id = tox.friend_get_public_key(i)
if not self._history.friend_exists_in_db(tox_id):
self._history.add_friend_to_db(tox_id)
try:
alias = filter(lambda x: x[0] == tox_id, aliases)[0][1]
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)
@ -75,20 +82,23 @@ class Profile(contact.Contact, Singleton):
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)
super(Profile, self).set_name(value.encode('utf-8'))
self._tox.self_set_name(self._name.encode('utf-8'))
if tmp != value:
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, curr_time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
self._messages.scrollToBottom()
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)
@ -112,15 +122,16 @@ class Profile(contact.Contact, Singleton):
: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, 70))
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 = Settings.get_instance()
settings['show_online_friends'] = self._show_online
settings.save()
@ -131,7 +142,7 @@ class Profile(contact.Contact, Singleton):
self.filtration(self._show_online, self._filter_string)
def get_friend_by_number(self, num):
return filter(lambda x: x.number == num, self._friends)[0]
return list(filter(lambda x: x.number == num, self._friends))[0]
def get_friend(self, num):
return self._friends[num]
@ -162,23 +173,25 @@ class Profile(contact.Contact, Singleton):
self.send_typing(False)
self._screen.typing.setVisible(False)
if value is not None:
if self._active_friend + 1:
if self._active_friend + 1 and self._active_friend != value:
try:
self._friends[self._active_friend].curr_text = self._screen.messageEdit.toPlainText()
except:
pass
self._active_friend = value
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._screen.messageEdit.setPlainText(friend.curr_text)
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],
convert_time(data[2]),
data[2],
data[1],
data[3])
elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']:
@ -192,16 +205,17 @@ class Profile(contact.Contact, Singleton):
ft.set_state_changed_handler(item.update)
ft.signal()
except:
print 'Incoming not started transfer - no info found'
print('Incoming not started transfer - no info found')
elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline
self.create_inline_item(message.get_data())
else: # info message
data = message.get_data()
self.create_message_item(data[0],
convert_time(data[2]),
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:
@ -217,13 +231,13 @@ class Profile(contact.Contact, Singleton):
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(QtCore.QSize(64, 64))
pixmap.load(avatar_path)
self._screen.account_avatar.setScaledContents(False)
self._screen.account_avatar.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio))
self._screen.account_avatar.repaint() # comment?
except: # no friend found. ignore
log('Incorrect friend value: ' + str(value))
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)
@ -244,14 +258,14 @@ class Profile(contact.Contact, Singleton):
friend = self.get_friend_by_number(number)
tmp = friend.name
friend.set_name(name)
name = name.decode('utf-8')
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, curr_time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
self.create_message_item(message, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
self._messages.scrollToBottom()
self.set_active(None)
@ -274,20 +288,34 @@ class Profile(contact.Contact, Singleton):
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)
print('Exception in file sending: ' + str(ex))
def friend_exit(self, friend_number):
"""
Friend with specified number quit
"""
# TODO: fix and add full file resuming support
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
@ -298,9 +326,12 @@ class Profile(contact.Contact, Singleton):
Send typing notification to a friend
"""
if Settings.get_instance()['typing_notifications'] and self._active_friend + 1:
friend = self._friends[self._active_friend]
if friend.status is not None:
self._tox.self_set_typing(friend.number, typing)
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):
"""
@ -324,10 +355,11 @@ class Profile(contact.Contact, Singleton):
"""
friend = self.get_friend_by_number(friend_number)
friend.load_corr()
messages = friend.unsent_messages()
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
@ -362,40 +394,52 @@ class Profile(contact.Contact, Singleton):
:param message: text of message
"""
if friend_num == self.get_active_number(): # add message to list
self.create_message_item(message.decode('utf-8'), curr_time(), MESSAGE_OWNER['FRIEND'], message_type)
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.decode('utf-8'), MESSAGE_OWNER['FRIEND'], time.time(), message_type))
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.decode('utf-8'), MESSAGE_OWNER['FRIEND'], time.time(), message_type))
TextMessage(message, MESSAGE_OWNER['FRIEND'], time.time(), message_type))
if not friend.visibility:
self.update_filtration()
def send_message(self, text):
def send_message(self, text, friend_num=None):
"""
Send message to active friend
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 self._active_friend + 1:
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._friends[self._active_friend]
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'))
self.create_message_item(text, curr_time(), MESSAGE_OWNER['NOT_SENT'], message_type)
self._screen.messageEdit.clear()
self._messages.scrollToBottom()
friend.append_message(TextMessage(text, MESSAGE_OWNER['NOT_SENT'], time.time(), message_type))
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
@ -405,40 +449,47 @@ class Profile(contact.Contact, Singleton):
"""
Save history to db
"""
s = Settings.get_instance()
if hasattr(self, '_history'):
if Settings.get_instance()['save_history']:
if s['save_history']:
for friend in self._friends:
messages = friend.get_corr_for_saving()
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.unsent_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):
def clear_history(self, num=None, save_unsent=False):
"""
Clear chat history
"""
if num is not None:
friend = self._friends[num]
friend.clear_corr()
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 xrange(len(self._friends)):
self.clear_history(number)
for number in range(len(self._friends)):
self.clear_history(number, save_unsent)
if num is None or num == self.get_active_number():
self._messages.clear()
self._messages.repaint()
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()
@ -450,11 +501,11 @@ class Profile(contact.Contact, Singleton):
if message.get_type() <= 1: # text message
data = message.get_data()
self.create_message_item(data[0],
convert_time(data[2]),
data[2],
data[1],
data[3],
False)
elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']:
elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: # file transfer
if message.get_status() is None:
self.create_unsent_file_item(message)
continue
@ -465,19 +516,46 @@ class Profile(contact.Contact, Singleton):
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())
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],
convert_time(data[2]),
data[2],
'',
data[3])
data[3],
False)
self._load_history = True
def export_history(self, directory):
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 '<br>'
for message in corr:
if type(message) is TextMessage:
data = message.get_data()
if as_text:
x = '[{}] {}: {}\n'
else:
x = '[{}] <b>{}:</b> {}<br>'
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
# -----------------------------------------------------------------------------------------------------------------
@ -489,7 +567,7 @@ class Profile(contact.Contact, Singleton):
"""
item = ContactItem()
elem = QtGui.QListWidgetItem(self._screen.friends_list)
elem.setSizeHint(QtCore.QSize(250, 70))
elem.setSizeHint(QtCore.QSize(250, item.height()))
self._screen.friends_list.addItem(elem)
self._screen.friends_list.setItemWidget(elem, item)
return item
@ -502,6 +580,9 @@ class Profile(contact.Contact, Singleton):
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:
@ -558,11 +639,11 @@ class Profile(contact.Contact, Singleton):
Set new alias for friend
"""
friend = self._friends[num]
name = friend.name.encode('utf-8')
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.decode('utf-8'))
dialog = dialog.format(name)
title = QtGui.QApplication.translate('MainWindow',
'Set alias',
None, QtGui.QApplication.UnicodeUTF8)
@ -570,28 +651,29 @@ class Profile(contact.Contact, Singleton):
title,
dialog,
QtGui.QLineEdit.Normal,
name.decode('utf-8'))
name)
if ok:
settings = Settings.get_instance()
aliases = settings['friends_aliases']
if text:
friend.name = text.encode('utf-8')
friend.name = bytes(text, 'utf-8')
try:
index = map(lambda x: x[0], aliases).index(friend.tox_id)
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).encode('utf-8')
friend.name = bytes(self._tox.friend_get_name(friend.number), 'utf-8')
friend.set_alias('')
try:
index = map(lambda x: x[0], aliases).index(friend.tox_id)
index = list(map(lambda x: x[0], aliases)).index(friend.tox_id)
del aliases[index]
except:
pass
settings.save()
self.set_active()
if num == self.get_active_number():
self.update()
def friend_public_key(self, num):
return self._friends[num].tox_id
@ -604,7 +686,7 @@ class Profile(contact.Contact, Singleton):
friend = self._friends[num]
settings = Settings.get_instance()
try:
index = map(lambda x: x[0], settings['friends_aliases']).index(friend.tox_id)
index = list(map(lambda x: x[0], settings['friends_aliases'])).index(friend.tox_id)
del settings['friends_aliases'][index]
except:
pass
@ -741,20 +823,34 @@ class Profile(contact.Contact, Singleton):
Recreate tox instance
:param restart: method which calls restart and returns new tox instance
"""
for key in self._file_transfers.keys():
self._file_transfers[key].cancel()
del self._file_transfers[key]
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.status = None
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
@ -772,7 +868,24 @@ class Profile(contact.Contact, Singleton):
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']
if inline and size < 1024 * 1024:
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(),
@ -784,22 +897,12 @@ class Profile(contact.Contact, Singleton):
elif auto:
path = settings['auto_accept_path'] or curr_directory()
if not os.path.isdir(path):
path = curr_directory()
new_file_name, i = file_name, 1
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
self.accept_transfer(None, path + '/' + new_file_name, friend_number, file_number, size)
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,
new_file_name,
file_name,
friend_number,
file_number)
else:
@ -810,9 +913,10 @@ class Profile(contact.Contact, Singleton):
file_name,
friend_number,
file_number)
accepted = False
if friend_number == self.get_active_number():
item = self.create_file_transfer_item(tm)
if (inline and size < 1024 * 1024) or auto:
if accepted:
self._file_transfers[(friend_number, file_number)].set_state_changed_handler(item.update)
self._messages.scrollToBottom()
else:
@ -866,18 +970,14 @@ class Profile(contact.Contact, Singleton):
"""
self.get_friend_by_number(friend_number).update_transfer_data(file_number,
TOX_FILE_TRANSFER_STATE['RUNNING'])
# if (friend_number, file_number) not in self._file_transfers:
# print self._file_transfers
# print (friend_number, file_number)
# return
tr = self._file_transfers[(friend_number, file_number)]
if by_friend:
tr.state = TOX_FILE_TRANSFER_STATE['RUNNING']
tr.signal()
else: # send seek control?
else:
tr.send_control(TOX_FILE_CONTROL['RESUME'])
def accept_transfer(self, item, path, friend_number, file_number, size, inline=False):
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
@ -885,11 +985,24 @@ class Profile(contact.Contact, Singleton):
: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)
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:
@ -903,9 +1016,10 @@ class Profile(contact.Contact, Singleton):
:param data: raw data - png
"""
self.send_inline(data, 'toxygen_inline.png')
self._messages.repaint()
def send_sticker(self, path):
with open(path) as fl:
with open(path, 'rb') as fl:
data = fl.read()
self.send_inline(data, 'sticker.png')
@ -920,6 +1034,7 @@ class Profile(contact.Contact, Singleton):
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(),
@ -933,14 +1048,15 @@ class Profile(contact.Contact, Singleton):
st.set_state_changed_handler(item.update)
self._messages.scrollToBottom()
def send_file(self, path, number=None, is_resend=False):
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 = number or self.get_active_number()
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())
@ -948,9 +1064,10 @@ class Profile(contact.Contact, Singleton):
self.update()
return
elif friend.status is None and is_resend:
print 'Error in sending'
print('Error in sending')
raise RuntimeError()
st = SendTransfer(path, self._tox, friend_number)
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(),
@ -959,67 +1076,50 @@ class Profile(contact.Contact, Singleton):
os.path.basename(path),
friend_number,
st.get_file_number())
item = self.create_file_transfer_item(tm)
st.set_state_changed_handler(item.update)
self._friends[self._active_friend].append_message(tm)
self._messages.scrollToBottom()
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
"""
if (friend_number, file_number) in self._file_transfers:
transfer = self._file_transfers[(friend_number, file_number)]
transfer.write_chunk(position, data)
if transfer.state not in ACTIVE_FILE_TRANSFERS: # finished or cancelled
if type(transfer) is ReceiveAvatar:
self.get_friend_by_number(friend_number).load_avatar()
self.set_active(None)
elif type(transfer) is ReceiveToBuffer: # inline image
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)
else:
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)]
self._file_transfers[(friend_number, file_number)].write_chunk(position, data)
def outgoing_chunk(self, friend_number, file_number, position, size):
"""
Outgoing chunk
"""
if (friend_number, file_number) in self._file_transfers:
transfer = self._file_transfers[(friend_number, file_number)]
transfer.send_chunk(position, size)
if transfer.state not in ACTIVE_FILE_TRANSFERS: # finished or cancelled
del self._file_transfers[(friend_number, file_number)]
if type(transfer) is not SendAvatar:
if type(transfer) is SendFromBuffer and Settings.get_instance()['allow_inline']: # 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)
else:
self.get_friend_by_number(friend_number).update_transfer_data(file_number,
TOX_FILE_TRANSFER_STATE['FINISHED'])
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
@ -1073,6 +1173,8 @@ class Profile(contact.Contact, Singleton):
"""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:
@ -1082,7 +1184,7 @@ class Profile(contact.Contact, Singleton):
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, curr_time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
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)
@ -1091,6 +1193,8 @@ class Profile(contact.Contact, Singleton):
"""
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,
@ -1102,10 +1206,11 @@ class Profile(contact.Contact, Singleton):
self._incoming_calls.add(friend_number)
if friend_number == self.get_active_number():
self._screen.incoming_call()
self.create_message_item(text, curr_time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
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()
@ -1132,11 +1237,12 @@ class Profile(contact.Contact, Singleton):
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, curr_time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
self._messages.scrollToBottom()
@ -1151,7 +1257,7 @@ def tox_factory(data=None, settings=None):
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 = settings['proxy_host']
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']

View File

@ -1,29 +1,29 @@
from platform import system
import json
import os
import locale
from util import Singleton, curr_directory, log
from util import Singleton, curr_directory, log, copy
import pyaudio
from toxencryptsave import LibToxEncryptSave
from toxencryptsave import ToxEncryptSave
import smileys
class Settings(Singleton, dict):
class Settings(dict, Singleton):
"""
Settings of current profile + global app settings
"""
def __init__(self, name):
self.path = ProfileHelper.get_path() + unicode(name) + '.json'
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 = LibToxEncryptSave.get_instance()
inst = ToxEncryptSave.get_instance()
try:
if inst.is_data_encrypted(data):
data = inst.pass_decrypt(data)
info = json.loads(data)
info = json.loads(str(data, 'utf-8'))
except Exception as ex:
info = Settings.get_default_settings()
log('Parsing settings error: ' + str(ex))
@ -33,9 +33,19 @@ class Settings(Singleton, dict):
super(Settings, self).__init__(Settings.get_default_settings())
self.save()
smileys.SmileyLoader(self)
self.locked = False
self.closing = False
p = pyaudio.PyAudio()
self.audio = {'input': p.get_default_input_device_info()['index'],
'output': p.get_default_output_device_info()['index']}
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():
@ -45,7 +55,7 @@ class Settings(Singleton, dict):
data = fl.read()
auto = json.loads(data)
if 'path' in auto and 'name' in auto:
return unicode(auto['path']), unicode(auto['name'])
return str(auto['path']), str(auto['name'])
return '', ''
@staticmethod
@ -54,8 +64,8 @@ class Settings(Singleton, dict):
with open(p) as fl:
data = fl.read()
data = json.loads(data)
data['path'] = unicode(path.decode(locale.getpreferredencoding()))
data['name'] = unicode(name)
data['path'] = str(path)
data['name'] = str(name)
with open(p, 'w') as fl:
fl.write(json.dumps(data))
@ -73,7 +83,7 @@ class Settings(Singleton, dict):
@staticmethod
def is_active_profile(path, name):
path = path.decode(locale.getpreferredencoding()) + name + '.tox'
path = path + name + '.tox'
settings = Settings.get_default_path() + 'toxygen.json'
if os.path.isfile(settings):
with open(settings) as fl:
@ -93,8 +103,8 @@ class Settings(Singleton, dict):
'ipv6_enabled': True,
'udp_enabled': True,
'proxy_type': 0,
'proxy_host': '0',
'proxy_port': 0,
'proxy_host': '127.0.0.1',
'proxy_port': 9050,
'start_port': 0,
'end_port': 0,
'tcp_port': 0,
@ -107,7 +117,10 @@ class Settings(Singleton, dict):
'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': [],
@ -121,7 +134,12 @@ class Settings(Singleton, dict):
'x': 400,
'y': 400,
'message_font_size': 14,
'unread_color': 'red'
'unread_color': 'red',
'save_unsent_only': False,
'compact_mode': False,
'show_welcome_screen': True,
'close_to_tray': False,
'font': 'Times New Roman'
}
@staticmethod
@ -136,15 +154,17 @@ class Settings(Singleton, dict):
default = Settings.get_default_settings()
for key in default:
if key not in self:
print key
print(key)
self[key] = default[key]
self.save()
def save(self):
text = json.dumps(self)
inst = LibToxEncryptSave.get_instance()
inst = ToxEncryptSave.get_instance()
if inst.has_password():
text = inst.pass_encrypt(text)
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)
@ -155,7 +175,7 @@ class Settings(Singleton, dict):
data = fl.read()
app_settings = json.loads(data)
try:
app_settings['active_profile'].remove(unicode(ProfileHelper.get_path() + self.name + '.tox'))
app_settings['active_profile'].remove(str(ProfileHelper.get_path() + self.name + '.tox'))
except:
pass
data = json.dumps(app_settings)
@ -175,8 +195,8 @@ class Settings(Singleton, dict):
app_settings = {}
if 'active_profile' not in app_settings:
app_settings['active_profile'] = []
profile_path = ProfileHelper.get_path()
app_settings['active_profile'].append(unicode(profile_path + str(self.name) + '.tox'))
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)
@ -186,12 +206,17 @@ class Settings(Singleton, dict):
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() == 'Linux':
return os.getenv('HOME') + '/.config/tox/'
elif system() == 'Windows':
if system() == 'Windows':
return os.getenv('APPDATA') + '/Tox/'
elif system() == 'Darwin':
return os.getenv('HOME') + '/Library/Application Support/Tox/'
else:
return os.getenv('HOME') + '/.config/tox/'
class ProfileHelper(Singleton):
@ -199,7 +224,7 @@ class ProfileHelper(Singleton):
Class with methods for search, load and save profiles
"""
def __init__(self, path, name):
path = path.decode(locale.getpreferredencoding())
Singleton.__init__(self)
self._path = path + name + '.tox'
self._directory = path
# create /avatars if not exists:
@ -219,20 +244,25 @@ class ProfileHelper(Singleton):
return self._directory
def save_profile(self, data):
inst = LibToxEncryptSave.get_instance()
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'
print('Profile saved successfully')
def export_profile(self, new_path):
new_path += os.path.basename(self._path)
def export_profile(self, new_path, use_new_path):
path = new_path + os.path.basename(self._path)
with open(self._path, 'rb') as fin:
data = fin.read()
with open(new_path, 'wb') as fout:
with open(path, 'wb') as fout:
fout.write(data)
print 'Profile exported successfully'
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():

View File

@ -14,6 +14,7 @@ class SmileyLoader(util.Singleton):
"""
def __init__(self, settings):
super().__init__()
self._settings = settings
self._curr_pack = None # current pack name
self._smileys = {} # smileys dict. key - smiley (str), value - path to image (str)
@ -29,22 +30,22 @@ class SmileyLoader(util.Singleton):
self._curr_pack = pack_name
path = self.get_smileys_path() + 'config.json'
try:
with open(path) as fl:
with open(path, encoding='utf8') as fl:
self._smileys = json.loads(fl.read())
fl.seek(0)
tmp = json.loads(fl.read(), object_pairs_hook=OrderedDict)
print 'Smiley pack {} loaded'.format(pack_name)
print('Smiley pack {} loaded'.format(pack_name))
keys, values, self._list = [], [], []
for key, value in tmp.items():
value = self.get_smileys_path() + value
if value not in values:
keys.append(key)
values.append(value)
self._list = zip(keys, values)
self._list = list(zip(keys, values))
except Exception as ex:
self._smileys = {}
self._list = []
print 'Smiley pack {} was not loaded. Error: {}'.format(pack_name, ex)
print('Smiley pack {} was not loaded. Error: {}'.format(pack_name, ex))
def get_smileys_path(self):
return util.curr_directory() + '/smileys/' + self._curr_pack + '/'
@ -54,7 +55,7 @@ class SmileyLoader(util.Singleton):
return [x[1] for x in os.walk(d)][0]
def get_smileys(self):
return self._list[:]
return list(self._list)
def add_smileys_to_text(self, text, edit):
"""
@ -69,7 +70,7 @@ class SmileyLoader(util.Singleton):
for i in range(len(arr)):
if arr[i] in self._smileys:
file_name = self._smileys[arr[i]] # image name
arr[i] = u'<img title=\"{}\" src=\"{}\" />'.format(arr[i], file_name)
arr[i] = '<img title=\"{}\" src=\"{}\" />'.format(arr[i], file_name)
if file_name.endswith('.gif'): # animated smiley
edit.addAnimation(QtCore.QUrl(file_name), self.get_smileys_path() + file_name)
return ' '.join(arr)
@ -85,6 +86,6 @@ def sticker_loader():
for key in keys:
path = d + key + '/'
files = filter(lambda f: f.endswith('.png'), os.listdir(path))
files = map(lambda f: unicode(path + f), files)
files = map(lambda f: str(path + f), files)
result.extend(files)
return result

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Some files were not shown because too many files have changed in this diff Show More