55 Commits

Author SHA1 Message Date
4d4fd21fe9 removed audio/video messages functionality. tox dns fixed 2017-06-20 19:41:08 +03:00
6cbacef95b travis.yml fix 2017-06-18 17:48:32 +03:00
7a817eb82a icons fixes 2017-06-18 13:05:26 +03:00
49fc253c19 video call icons 2017-06-18 12:43:11 +03:00
df5a1a901a video settings 2017-06-18 00:50:42 +03:00
54a2da4670 video setting - selector 2017-06-18 00:30:08 +03:00
361f1f0e29 call timeout 2017-06-16 18:47:00 +03:00
9031a4a3e3 incoming call widget update 2017-06-15 21:34:43 +03:00
0a378c1682 ui fixes for video 2017-06-15 00:25:16 +03:00
8bc4613407 device selection in settings 2017-06-13 22:42:05 +03:00
ec6c04a7df video settings to main menu 2017-06-13 22:37:01 +03:00
769119c795 video settings (untested) 2017-06-13 22:32:32 +03:00
d1e90c6aef fixed bug with video sending 2017-06-13 21:50:00 +03:00
6d705deb55 calls.py rewriting 2017-06-13 21:40:54 +03:00
464fba23c5 incoming video yuv => bgr fix 2017-06-13 21:14:05 +03:00
c60808a7da cleanup and few todo's 2017-06-13 00:36:45 +03:00
a2273e8c27 video sending and playing, temporary hardcoded size 2017-06-13 00:26:21 +03:00
d0e2f61d03 merge with pyqt5 branch and video sending 2017-06-11 15:35:52 +03:00
f8a7087779 video recording and thread 2017-04-11 23:18:59 +03:00
01546f0047 dict on incoming call widgets, calls.py update 2017-04-11 23:18:59 +03:00
d8dd16e865 init commit 2017-04-11 23:18:58 +03:00
19fb905554 some fixes 2017-04-06 21:28:34 +03:00
3ef581bc5d some messages improvements 2017-04-05 23:46:32 +03:00
f897c7ce8d unlock screen crash fixed 2017-03-27 00:04:32 +03:00
ba390eda91 message splitting bug fix 2017-03-26 18:28:30 +03:00
5fea3e918d plugin reloading refactoring 2017-03-25 18:21:25 +03:00
7cc404ce52 plugins improvements 2017-03-15 23:17:38 +03:00
8a502b4082 block user option in friend menu and translations update 2017-03-08 13:37:19 +03:00
b83ea6be18 reconnection bug fix 2017-03-08 13:19:41 +03:00
85554eacd1 docs updates 2017-03-04 23:35:46 +03:00
8bbefff6c7 history - travis tests fix 2017-03-04 23:18:26 +03:00
019165aeac unsent files fix 2017-03-04 22:15:42 +03:00
0cfb8efefa reconnection fixes 2017-03-03 22:09:45 +03:00
b227ed627a more tests 2017-02-27 21:44:35 +03:00
f41b5e5c97 history test 2017-02-25 23:53:33 +03:00
bc9ec04171 version number fix 2017-02-20 23:47:55 +03:00
05e4184c5d travis fix 2017-02-20 22:03:35 +03:00
3194099f59 default config file moved to app dir 2017-02-20 21:33:04 +03:00
1a9db79ca2 text not found message box 2017-02-13 00:00:41 +03:00
21cc5837cf bug fixes with regex 2017-02-12 23:15:33 +03:00
150942446d fixed bug with html in search and focus 2017-02-12 22:49:08 +03:00
508db0acea ui update for search 2017-02-12 19:46:53 +03:00
de7f3359b8 search in history with regex support 2017-02-12 19:27:38 +03:00
8b56184510 search in history by ctrl + F - initial commit 2017-02-12 17:58:23 +03:00
1d33d298c3 added check 2017-02-11 22:04:32 +03:00
704344fae2 toxes tests fix 2017-02-11 20:23:08 +03:00
3511031aff toxes refactoring 2017-02-11 20:07:28 +03:00
481e48f495 typos fix and todo added 2017-02-07 00:18:57 +03:00
889d3d8f9c 2 minor bug fixes 2017-01-22 00:19:56 +03:00
9b4965d591 bug fixes 2017-01-13 21:08:54 +03:00
5bdbb28e31 reset default profile via --reset 2017-01-04 19:46:23 +03:00
6cafd14883 qtox screenshots support 2016-12-24 22:05:29 +03:00
9d939e7439 avatars handler fix 2016-12-24 14:36:49 +03:00
9e7e9b9012 incorrect contacts list update fixed 2016-12-24 14:20:58 +03:00
2c4301e4f0 bug fixes 2016-11-20 14:12:27 +03:00
48 changed files with 1970 additions and 1336 deletions

View File

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

View File

@ -2,13 +2,13 @@
Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pure Python3. Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pure Python3.
[![Release](https://img.shields.io/github/release/xveduk/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/releases/latest) [![Release](https://img.shields.io/github/release/toxygen-project/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/releases/latest)
[![Stars](https://img.shields.io/github/stars/xveduk/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/stargazers) [![Stars](https://img.shields.io/github/stars/toxygen-project/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/stargazers)
[![Open issues](https://img.shields.io/github/issues/xveduk/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/issues) [![Open issues](https://img.shields.io/github/issues/toxygen-project/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/issues)
[![License](https://img.shields.io/badge/license-GPLv3-blue.svg?style=flat)](https://raw.githubusercontent.com/toxygen-project/toxygen/master/LICENSE.md) [![License](https://img.shields.io/badge/license-GPLv3-blue.svg?style=flat)](https://raw.githubusercontent.com/toxygen-project/toxygen/master/LICENSE.md)
[![Build Status](https://travis-ci.org/toxygen-project/toxygen.svg?branch=master)](https://travis-ci.org/toxygen-project/toxygen) [![Build Status](https://travis-ci.org/toxygen-project/toxygen.svg?branch=master)](https://travis-ci.org/toxygen-project/toxygen)
### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md) - [Compile](/docs/compile.md) - [Contact](/docs/contact.md) ### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md) - [Compile](/docs/compile.md) - [Contact](/docs/contact.md) - [Updater](https://github.com/toxygen-project/toxygen_updater)
### Supported OS: ### Supported OS:
@ -18,7 +18,7 @@ Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pu
- [x] 1v1 messages - [x] 1v1 messages
- [x] File transfers - [x] File transfers
- [x] Audio - [x] Audio calls
- [x] Plugins support - [x] Plugins support
- [x] Chat history - [x] Chat history
- [x] Emoticons - [x] Emoticons
@ -42,7 +42,7 @@ Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pu
- [x] Changing nospam - [x] Changing nospam
- [x] File resuming - [x] File resuming
- [x] Read receipts - [x] Read receipts
- [ ] Video - [ ] Video calls
- [ ] Desktop sharing - [ ] Desktop sharing
- [ ] Group chats - [ ] Group chats

View File

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

View File

@ -4,15 +4,15 @@ Help us find all bugs in Toxygen! Please provide following info:
- OS - OS
- Toxygen version - Toxygen version
- Toxygen executable info - .py or precompiled binary - Toxygen executable info - .py or precompiled binary, how was it installed in system
- Steps to reproduce the bug - Steps to reproduce the bug
Want to see new feature in Toxygen? [Ask for it!](https://github.com/xveduk/toxygen/issues) Want to see new feature in Toxygen? [Ask for it!](https://github.com/toxygen-project/toxygen/issues)
#Pull requests #Pull requests
Developer? Feel free to open pull request. Our dev team is small so we glad to get help. Developer? Feel free to open pull request. Our dev team is small so we glad to get help.
Don't know what to do? Improve UI, fix [issues](https://github.com/xveduk/toxygen/issues) or implement features from our TODO list. Don't know what to do? Improve UI, fix [issues](https://github.com/toxygen-project/toxygen/issues) or implement features from our TODO list.
You can find our TODO's in code, issues list and [here](/README.md). Also you can implement [plugins](/docs/plugins.md) for Toxygen. You can find our TODO's in code, issues list and [here](/README.md). Also you can implement [plugins](/docs/plugins.md) for Toxygen.
#Translations #Translations

View File

@ -1,7 +1,7 @@
# How to install Toxygen # How to install Toxygen
## Use precompiled binary: ## Use precompiled binary:
[Check our releases page](https://github.com/xveduk/toxygen/releases) [Check our releases page](https://github.com/toxygen-project/toxygen/releases)
## Using pip3 ## Using pip3

View File

@ -1,4 +1,4 @@
#Plugins API # Plugins API
In Toxygen plugin is single python (supported Python 3.0 - 3.4) module (.py file) and directory with data associated with it. 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 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.
@ -18,7 +18,7 @@ All plugin's data should be stored in following structure:
``` ```
Plugin MUST override: Plugin MUST override:
- __init__ with params: tox (Tox instance), profile (Profile instance), settings (Settings instance), encrypt_save (ToxEncryptSave instance). Call super().__init__ with params plugin_full_name, plugin_short_name, tox, profile, settings, encrypt_save. - __init__ with params: tox (Tox instance), profile (Profile instance), settings (Settings instance), encrypt_save (ToxES instance). Call super().__init__ with params plugin_full_name, plugin_short_name, tox, profile, settings, encrypt_save.
Plugin can override following methods: Plugin can override following methods:
- get_description - this method should return plugin description. - get_description - this method should return plugin description.
@ -51,7 +51,7 @@ Exceptions:
Plugin's methods MUST NOT raise exceptions. Plugin's methods MUST NOT raise exceptions.
#Examples # Examples
You can find examples in [official repo](https://github.com/ingvar1995/toxygen_plugins) You can find examples in [official repo](https://github.com/toxygen-project/toxygen_plugins)

View File

@ -1,22 +1,22 @@
#Plugins # Plugins
Toxygen is the first [Tox](https://tox.chat/) client with plugins support. Plugin is Python 3.4 module (.py file) and directory with plugin's data which provide some additional functionality. Toxygen is the first [Tox](https://tox.chat/) client with plugins support. Plugin is Python 3.4 module (.py file) and directory with plugin's data which provide some additional functionality.
#How to write plugin # How to write plugin
Check [Plugin API](/docs/plugin_api.md) for more info Check [Plugin API](/docs/plugin_api.md) for more info
#How to install plugin # How to install plugin
Toxygen comes without preinstalled plugins. Toxygen comes without preinstalled plugins.
1. Put plugin and directory with its data into /src/plugins/ or import it via GUI (In menu: Plugins -> Import plugin) 1. Put plugin and directory with its data into /src/plugins/ or import it via GUI (In menu: Plugins => Import plugin)
2. Restart Toxygen 2. Restart Toxygen or choose Plugins => Reload plugins in menu.
##Note: /src/plugins/ should contain plugin_super_class.py and __init__.py ## Note: /src/plugins/ should contain plugin_super_class.py and __init__.py
#Plugins list # Plugins list
WARNING: It is unsecure to install plugin not from this list! WARNING: It is unsecure to install plugin not from this list!
[Main repo](https://github.com/ingvar1995/toxygen_plugins) [Main repo](https://github.com/toxygen-project/toxygen_plugins)

View File

@ -10,4 +10,4 @@ Animated smileys (.gif) are supported too.
Sticker is inline image. If you want to create your own smiley pack, create directory in src/stickers/ and place your stickers there. Sticker is inline image. If you want to create your own smiley pack, create directory in src/stickers/ and place your stickers there.
Users can import plugins and stickers packs using menu: Settings -> Interface Users can import smileys and stickers using menu: Settings -> Interface

View File

@ -8,15 +8,20 @@ import sys
version = program_version + '.0' version = program_version + '.0'
MODULES = [] MODULES = ['numpy', 'PyQt5']
if system() in ('Windows', 'Darwin'): if system() in ('Windows', 'Darwin'):
MODULES = ['PyAudio', 'PySide'] MODULES.append('PyAudio')
else: else:
try: try:
import pyaudio import pyaudio
except ImportError: except ImportError:
MODULES = ['PyAudio'] MODULES.append('PyAudio')
DEP_LINKS = []
if system() == 'Windows':
DEP_LINKS = [] # TODO: add opencv.whl
class InstallScript(install): class InstallScript(install):
@ -55,10 +60,12 @@ setup(name='Toxygen',
license='GPL3', license='GPL3',
packages=['toxygen', 'toxygen.plugins', 'toxygen.styles'], packages=['toxygen', 'toxygen.plugins', 'toxygen.styles'],
install_requires=MODULES, install_requires=MODULES,
dependency_links=DEP_LINKS,
include_package_data=True, include_package_data=True,
classifiers=[ classifiers=[
'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
], ],
entry_points={ entry_points={
'console_scripts': ['toxygen=toxygen.main:main'], 'console_scripts': ['toxygen=toxygen.main:main'],

View File

@ -1,7 +1,10 @@
from toxygen.profile import * from toxygen.profile import *
from toxygen.tox_dns import tox_dns from toxygen.tox_dns import tox_dns
import toxygen.toxencryptsave as encr from toxygen.history import History
import toxygen.messages as m from toxygen.smileys import SmileyLoader
from toxygen.messages import *
import toxygen.toxes as encr
import toxygen.util as util
import time import time
@ -53,61 +56,122 @@ class TestEncryption:
def test_encr_decr(self): def test_encr_decr(self):
tox = tox_factory() tox = tox_factory()
data = tox.get_savedata() data = tox.get_savedata()
lib = encr.ToxEncryptSave() lib = encr.ToxES()
for password in ('easypassword', 'njvnjfnvaGGV6', 'toxygen'): for password in ('easypassword', 'njvnFjfn7vaGGV6', 'toxygen'):
lib.set_password(password) lib.set_password(password)
copy_data = data[:] copy_data = data[:]
new_data = lib.pass_encrypt(data) new_data = lib.pass_encrypt(data)
assert lib.is_data_encrypted(new_data)
new_data = lib.pass_decrypt(new_data) new_data = lib.pass_decrypt(new_data)
assert copy_data == new_data assert copy_data == new_data
class TestSmileys:
def test_loading(self):
settings = {'smiley_pack': 'default', 'smileys': True}
sm = SmileyLoader(settings)
assert sm.get_smileys_path() is not None
l = sm.get_packs_list()
assert len(l) == 4
def create_singletons():
folder = util.curr_directory() + '/abc'
Settings._instance = Settings.get_default_settings()
if not os.path.exists(folder):
os.makedirs(folder)
ProfileHelper(folder, 'test')
def create_friend(name, status_message, number, tox_id):
friend = Friend(None, number, name, status_message, None, tox_id)
return friend
def create_random_friend():
name, status_message, number = 'Friend', 'I am friend!', 0
tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6'
friend = create_friend(name, status_message, number, tox_id)
return friend
class TestFriend: class TestFriend:
def create_singletons(self):
Settings._instance = Settings.get_default_settings()
ProfileHelper('abc', 'test')
def create_friend(self, name, status_message, number, tox_id):
friend = Friend(None, number, name, status_message, None, tox_id)
return friend
def test_friend_creation(self): def test_friend_creation(self):
self.create_singletons() create_singletons()
name, status_message, number = 'Friend', 'I am friend!', 0 name, status_message, number = 'Friend', 'I am friend!', 0
tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6' tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6'
friend = self.create_friend(name, status_message, number, tox_id) friend = create_friend(name, status_message, number, tox_id)
assert friend.name == name assert friend.name == name
assert friend.tox_id == tox_id assert friend.tox_id == tox_id
assert friend.status_message == status_message assert friend.status_message == status_message
assert friend.number == number assert friend.number == number
def test_friend_corr(self): def test_friend_corr(self):
self.create_singletons() create_singletons()
name, status_message, number = 'Friend', 'I am friend!', 0 friend = create_random_friend()
tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6'
friend = self.create_friend(name, status_message, number, tox_id)
t = time.time() t = time.time()
friend.append_message(m.InfoMessage('Info message', t)) friend.append_message(InfoMessage('Info message', t))
friend.append_message(m.TextMessage('Hello! It is test!', MESSAGE_OWNER['ME'], t + 0.001, 0)) friend.append_message(TextMessage('Hello! It is test!', MESSAGE_OWNER['ME'], t + 0.001, 0))
friend.append_message(m.TextMessage('Hello!', MESSAGE_OWNER['FRIEND'], t + 0.002, 0)) friend.append_message(TextMessage('Hello!', MESSAGE_OWNER['FRIEND'], t + 0.002, 0))
assert friend.get_last_message_text() == 'Hello! It is test!' assert friend.get_last_message_text() == 'Hello! It is test!'
assert len(friend.get_corr()) == 3 assert len(friend.get_corr()) == 3
assert len(friend.get_corr_for_saving()) == 2 assert len(friend.get_corr_for_saving()) == 2
friend.append_message(m.TextMessage('Not sent', MESSAGE_OWNER['NOT_SENT'], t + 0.002, 0)) friend.append_message(TextMessage('Not sent', MESSAGE_OWNER['NOT_SENT'], t + 0.002, 0))
arr = friend.get_unsent_messages_for_saving() arr = friend.get_unsent_messages_for_saving()
assert len(arr) == 1 assert len(arr) == 1
assert arr[0][0] == 'Not sent' assert arr[0][0] == 'Not sent'
tm = m.TransferMessage(MESSAGE_OWNER['FRIEND'], tm = TransferMessage(MESSAGE_OWNER['FRIEND'],
time.time(), time.time(),
TOX_FILE_TRANSFER_STATE['RUNNING'], TOX_FILE_TRANSFER_STATE['RUNNING'],
100, 'file_name', friend.number, 0) 100, 'file_name', friend.number, 0)
friend.append_message(tm) friend.append_message(tm)
friend.clear_corr() friend.clear_corr()
assert len(friend.get_corr()) == 1 assert len(friend.get_corr()) == 1
assert len(friend.get_corr_for_saving()) == 0 assert len(friend.get_corr_for_saving()) == 0
friend.append_message(m.TextMessage('Hello! It is test!', MESSAGE_OWNER['ME'], t, 0)) friend.append_message(TextMessage('Hello! It is test!', MESSAGE_OWNER['ME'], t, 0))
assert len(friend.get_corr()) == 2 assert len(friend.get_corr()) == 2
assert len(friend.get_corr_for_saving()) == 1 assert len(friend.get_corr_for_saving()) == 1
# TODO: more friend tests and history test def test_history_search(self):
create_singletons()
friend = create_random_friend()
message = 'Hello! It is test!'
friend.append_message(TextMessage(message, MESSAGE_OWNER['ME'], time.time(), 0))
last_message = friend.get_last_message_text()
assert last_message == message
result = friend.search_string('e[m|s]')
assert result is not None
result = friend.search_string('tox')
assert result is None
class TestHistory:
def test_history(self):
create_singletons()
db_name = 'my_name'
name, status_message, number = 'Friend', 'I am friend!', 0
tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6'
friend = create_friend(name, status_message, number, tox_id)
history = History(db_name)
history.add_friend_to_db(friend.tox_id)
assert history.friend_exists_in_db(friend.tox_id)
text_message = 'Test!'
t = time.time()
friend.append_message(TextMessage(text_message, MESSAGE_OWNER['ME'], t, 0))
messages = friend.get_corr_for_saving()
history.save_messages_to_db(friend.tox_id, messages)
getter = history.messages_getter(friend.tox_id)
messages = getter.get_all()
assert len(messages) == 1
assert messages[0][0] == text_message
assert messages[0][1] == MESSAGE_OWNER['ME']
assert messages[0][-1] == 0
history.delete_message(friend.tox_id, t)
getter = history.messages_getter(friend.tox_id)
messages = getter.get_all()
assert len(messages) == 0
history.delete_friend_from_db(friend.tox_id)
assert not history.friend_exists_in_db(friend.tox_id)

View File

@ -1,7 +1,4 @@
try: from PyQt5 import QtCore, QtGui, QtWidgets
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
import widgets import widgets
import profile import profile
import util import util
@ -17,11 +14,12 @@ class IncomingCallWidget(widgets.CenteredWidget):
super(IncomingCallWidget, self).__init__() super(IncomingCallWidget, self).__init__()
self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowStaysOnTopHint) self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowStaysOnTopHint)
self.resize(QtCore.QSize(500, 270)) self.resize(QtCore.QSize(500, 270))
self.avatar_label = QtGui.QLabel(self) self.avatar_label = QtWidgets.QLabel(self)
self.avatar_label.setGeometry(QtCore.QRect(10, 20, 64, 64)) self.avatar_label.setGeometry(QtCore.QRect(10, 20, 64, 64))
self.avatar_label.setScaledContents(False) self.avatar_label.setScaledContents(False)
self.name = widgets.DataLabel(self) self.name = widgets.DataLabel(self)
self.name.setGeometry(QtCore.QRect(90, 20, 300, 25)) self.name.setGeometry(QtCore.QRect(90, 20, 300, 25))
self._friend_number = friend_number
font = QtGui.QFont() font = QtGui.QFont()
font.setFamily(settings.Settings.get_instance()['font']) font.setFamily(settings.Settings.get_instance()['font'])
font.setPointSize(16) font.setPointSize(16)
@ -30,11 +28,11 @@ class IncomingCallWidget(widgets.CenteredWidget):
self.call_type = widgets.DataLabel(self) self.call_type = widgets.DataLabel(self)
self.call_type.setGeometry(QtCore.QRect(90, 55, 300, 25)) self.call_type.setGeometry(QtCore.QRect(90, 55, 300, 25))
self.call_type.setFont(font) self.call_type.setFont(font)
self.accept_audio = QtGui.QPushButton(self) self.accept_audio = QtWidgets.QPushButton(self)
self.accept_audio.setGeometry(QtCore.QRect(20, 100, 150, 150)) self.accept_audio.setGeometry(QtCore.QRect(20, 100, 150, 150))
self.accept_video = QtGui.QPushButton(self) self.accept_video = QtWidgets.QPushButton(self)
self.accept_video.setGeometry(QtCore.QRect(170, 100, 150, 150)) self.accept_video.setGeometry(QtCore.QRect(170, 100, 150, 150))
self.decline = QtGui.QPushButton(self) self.decline = QtWidgets.QPushButton(self)
self.decline.setGeometry(QtCore.QRect(320, 100, 150, 150)) self.decline.setGeometry(QtCore.QRect(320, 100, 150, 150))
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/accept_audio.png') pixmap = QtGui.QPixmap(util.curr_directory() + '/images/accept_audio.png')
icon = QtGui.QIcon(pixmap) icon = QtGui.QIcon(pixmap)
@ -54,15 +52,16 @@ class IncomingCallWidget(widgets.CenteredWidget):
self.setWindowTitle(text) self.setWindowTitle(text)
self.name.setText(name) self.name.setText(name)
self.call_type.setText(text) self.call_type.setText(text)
pr = profile.Profile.get_instance() self._processing = False
self.accept_audio.clicked.connect(lambda: pr.accept_call(friend_number, True, False) or self.stop()) self.accept_audio.clicked.connect(self.accept_call_with_audio)
# self.accept_video.clicked.connect(lambda: pr.start_call(friend_number, True, True)) self.accept_video.clicked.connect(self.accept_call_with_video)
self.decline.clicked.connect(lambda: pr.stop_call(friend_number, False) or self.stop()) self.decline.clicked.connect(self.decline_call)
class SoundPlay(QtCore.QThread): class SoundPlay(QtCore.QThread):
def __init__(self): def __init__(self):
QtCore.QThread.__init__(self) QtCore.QThread.__init__(self)
self.a = None
def run(self): def run(self):
class AudioFile: class AudioFile:
@ -107,33 +106,29 @@ class IncomingCallWidget(widgets.CenteredWidget):
self.thread.wait() self.thread.wait()
self.close() self.close()
def accept_call_with_audio(self):
if self._processing:
return
self._processing = True
pr = profile.Profile.get_instance()
pr.accept_call(self._friend_number, True, False)
self.stop()
def accept_call_with_video(self):
if self._processing:
return
self._processing = True
pr = profile.Profile.get_instance()
pr.accept_call(self._friend_number, True, True)
self.stop()
def decline_call(self):
if self._processing:
return
self._processing = True
pr = profile.Profile.get_instance()
pr.stop_call(self._friend_number, False)
self.stop()
def set_pixmap(self, pixmap): def set_pixmap(self, pixmap):
self.avatar_label.setPixmap(pixmap) self.avatar_label.setPixmap(pixmap)
class AudioMessageRecorder(widgets.CenteredWidget):
def __init__(self, friend_number, name):
super(AudioMessageRecorder, self).__init__()
self.label = QtGui.QLabel(self)
self.label.setGeometry(QtCore.QRect(10, 20, 250, 20))
text = QtGui.QApplication.translate("MenuWindow", "Send audio message to friend {}", None, QtGui.QApplication.UnicodeUTF8)
self.label.setText(text.format(name))
self.record = QtGui.QPushButton(self)
self.record.setGeometry(QtCore.QRect(20, 100, 150, 150))
self.record.setText(QtGui.QApplication.translate("MenuWindow", "Start recording", None,
QtGui.QApplication.UnicodeUTF8))
self.record.clicked.connect(self.start_or_stop_recording)
self.recording = False
self.friend_num = friend_number
def start_or_stop_recording(self):
if not self.recording:
self.recording = True
self.record.setText(QtGui.QApplication.translate("MenuWindow", "Stop recording", None,
QtGui.QApplication.UnicodeUTF8))
else:
self.close()

View File

@ -1,8 +1,5 @@
from settings import * from settings import *
try: from PyQt5 import QtCore, QtGui
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
@ -84,11 +81,10 @@ class BaseContact:
""" """
Tries to load avatar of contact or uses default avatar Tries to load avatar of contact or uses default avatar
""" """
avatar_path = '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) prefix = ProfileHelper.get_path() + 'avatars/'
os.chdir(ProfileHelper.get_path() + 'avatars/') avatar_path = prefix + '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
if not os.path.isfile(avatar_path): # load default image if not os.path.isfile(avatar_path) or not os.path.getsize(avatar_path): # load default image
avatar_path = 'avatar.png' avatar_path = curr_directory() + '/images/avatar.png'
os.chdir(curr_directory() + '/images/')
width = self._widget.avatar_label.width() width = self._widget.avatar_label.width()
pixmap = QtGui.QPixmap(avatar_path) pixmap = QtGui.QPixmap(avatar_path)
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio, self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
@ -110,6 +106,10 @@ class BaseContact:
def get_pixmap(self): def get_pixmap(self):
return self._widget.avatar_label.pixmap() return self._widget.avatar_label.pixmap()
# -----------------------------------------------------------------------------------------------------------------
# Widgets
# -----------------------------------------------------------------------------------------------------------------
def init_widget(self): def init_widget(self):
if self._widget is not None: if self._widget is not None:
self._widget.name.setText(self._name) self._widget.name.setText(self._name)

View File

@ -1,7 +1,4 @@
try: from PyQt5 import QtCore, QtGui, QtWidgets
from PySide import QtCore
except ImportError:
from PyQt4 import QtCore
from notifications import * from notifications import *
from settings import Settings from settings import Settings
from profile import Profile from profile import Profile
@ -12,7 +9,8 @@ from plugin_support import PluginLoader
import queue import queue
import threading import threading
import util import util
import cv2
import numpy as np
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
# Threads # Threads
@ -225,7 +223,7 @@ def tox_file_recv(window, tray):
if not window.isActiveWindow(): if not window.isActiveWindow():
friend = profile.get_friend_by_number(friend_number) friend = profile.get_friend_by_number(friend_number)
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked: if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
file_from = QtGui.QApplication.translate("Callback", "File from", None, QtGui.QApplication.UnicodeUTF8) file_from = QtWidgets.QApplication.translate("Callback", "File from")
invoke_in_main_thread(tray_notification, file_from + ' ' + friend.name, file_name, tray, window) invoke_in_main_thread(tray_notification, file_from + ' ' + friend.name, file_name, tray, window)
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']: if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
sound_notification(SOUND_NOTIFICATION['FILE_TRANSFER']) sound_notification(SOUND_NOTIFICATION['FILE_TRANSFER'])
@ -315,11 +313,67 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud
""" """
New audio chunk New audio chunk
""" """
Profile.get_instance().call.chunk( Profile.get_instance().call.audio_chunk(
bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]), bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]),
audio_channels_count, audio_channels_count,
rate) rate)
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - video
# -----------------------------------------------------------------------------------------------------------------
def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, ustride, vstride, user_data):
"""
Creates yuv frame from y, u, v and shows it using OpenCV
For yuv => bgr we need this YUV420 frame:
width
-------------------------
| |
| Y | height
| |
-------------------------
| | |
| U even | U odd | height // 4
| | |
-------------------------
| | |
| V even | V odd | height // 4
| | |
-------------------------
width // 2 width // 2
It can be created from initial y, u, v using slices
For more info see callback_video_receive_frame docs
"""
try:
y_size = abs(max(width, abs(ystride)))
u_size = abs(max(width // 2, abs(ustride)))
v_size = abs(max(width // 2, abs(vstride)))
y = np.asarray(y[:y_size * height], dtype=np.uint8).reshape(height, y_size)
u = np.asarray(u[:u_size * height // 2], dtype=np.uint8).reshape(height // 2, u_size)
v = np.asarray(v[:v_size * height // 2], dtype=np.uint8).reshape(height // 2, v_size)
width -= width % 4
height -= height % 4
frame = np.zeros((int(height * 1.5), width), dtype=np.uint8)
frame[:height, :] = y[:height, :width]
frame[height:height * 5 // 4, :width // 2] = u[:height // 2:2, :width // 2]
frame[height:height * 5 // 4, width // 2:] = u[1:height // 2:2, :width // 2]
frame[height * 5 // 4:, :width // 2] = v[:height // 2:2, :width // 2]
frame[height * 5 // 4:, width // 2:] = v[1:height // 2:2, :width // 2]
frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420)
invoke_in_main_thread(cv2.imshow, str(friend_number), frame)
except Exception as ex:
print(ex)
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
# Callbacks - initialization # Callbacks - initialization
@ -353,7 +407,7 @@ def init_callbacks(tox, window, tray):
toxav.callback_call_state(call_state, 0) toxav.callback_call_state(call_state, 0)
toxav.callback_call(call, 0) toxav.callback_call(call, 0)
toxav.callback_audio_receive_frame(callback_audio, 0) toxav.callback_audio_receive_frame(callback_audio, 0)
toxav.callback_video_receive_frame(video_receive_frame, 0)
tox.callback_friend_lossless_packet(lossless_packet, 0) tox.callback_friend_lossless_packet(lossless_packet, 0)
tox.callback_friend_lossy_packet(lossy_packet, 0) tox.callback_friend_lossy_packet(lossy_packet, 0)

View File

@ -3,14 +3,68 @@ import time
import threading import threading
import settings import settings
from toxav_enums import * from toxav_enums import *
# TODO: play sound until outgoing call will be started or cancelled and add timeout import cv2
# TODO: add widget for call import itertools
import numpy as np
# TODO: play sound until outgoing call will be started or cancelled
CALL_TYPE = {
'NONE': 0, class Call:
'AUDIO': 1,
'VIDEO': 2 def __init__(self, out_audio, out_video, in_audio=False, in_video=False):
} self._in_audio = in_audio
self._in_video = in_video
self._out_audio = out_audio
self._out_video = out_video
self._is_active = False
def get_is_active(self):
return self._is_active
def set_is_active(self, value):
self._is_active = value
is_active = property(get_is_active, set_is_active)
# -----------------------------------------------------------------------------------------------------------------
# Audio
# -----------------------------------------------------------------------------------------------------------------
def get_in_audio(self):
return self._in_audio
def set_in_audio(self, value):
self._in_audio = value
in_audio = property(get_in_audio, set_in_audio)
def get_out_audio(self):
return self._out_audio
def set_out_audio(self, value):
self._out_audio = value
out_audio = property(get_out_audio, set_out_audio)
# -----------------------------------------------------------------------------------------------------------------
# Video
# -----------------------------------------------------------------------------------------------------------------
def get_in_video(self):
return self._in_video
def set_in_video(self, value):
self._in_video = value
in_video = property(get_in_video, set_in_video)
def get_out_video(self):
return self._out_video
def set_out_video(self, value):
self._in_video = value
out_video = property(get_out_video, set_out_video)
class AV: class AV:
@ -19,7 +73,7 @@ class AV:
self._toxav = toxav self._toxav = toxav
self._running = True self._running = True
self._calls = {} # dict: key - friend number, value - call type self._calls = {} # dict: key - friend number, value - Call instance
self._audio = None self._audio = None
self._audio_stream = None self._audio_stream = None
@ -32,27 +86,75 @@ class AV:
self._audio_duration = 60 self._audio_duration = 60
self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration // 1000 self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration // 1000
def __contains__(self, friend_number): self._video = None
return friend_number in self._calls self._video_thread = None
self._video_running = False
def __call__(self, friend_number, audio, video): self._video_width = 640
"""Call friend with specified number""" self._video_height = 480
self._toxav.call(friend_number, 32 if audio else 0, 5000 if video else 0)
self._calls[friend_number] = CALL_TYPE['AUDIO']
self.start_audio_thread()
def finish_call(self, friend_number, by_friend=False):
if not by_friend:
self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL'])
if friend_number in self._calls:
del self._calls[friend_number]
if not len(self._calls):
self.stop_audio_thread()
def stop(self): def stop(self):
self._running = False self._running = False
self.stop_audio_thread() self.stop_audio_thread()
self.stop_video_thread()
def __contains__(self, friend_number):
return friend_number in self._calls
# -----------------------------------------------------------------------------------------------------------------
# Calls
# -----------------------------------------------------------------------------------------------------------------
def __call__(self, friend_number, audio, video):
"""Call friend with specified number"""
self._toxav.call(friend_number, 32 if audio else 0, 5000 if video else 0)
self._calls[friend_number] = Call(audio, video)
threading.Timer(30.0, lambda: self.finish_not_started_call(friend_number)).start()
def accept_call(self, friend_number, audio_enabled, video_enabled):
if self._running:
self._calls[friend_number] = Call(audio_enabled, video_enabled)
self._toxav.answer(friend_number, 32 if audio_enabled else 0, 5000 if video_enabled else 0)
if audio_enabled:
self.start_audio_thread()
if video_enabled:
self.start_video_thread()
def finish_call(self, friend_number, by_friend=False):
if not by_friend:
self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL'])
if friend_number in self._calls:
del self._calls[friend_number]
if not len(list(filter(lambda c: c.out_audio, self._calls))):
self.stop_audio_thread()
if not len(list(filter(lambda c: c.out_video, self._calls))):
self.stop_video_thread()
def finish_not_started_call(self, friend_number):
if friend_number in self:
call = self._calls[friend_number]
if not call.is_active:
self.finish_call(friend_number)
def toxav_call_state_cb(self, friend_number, state):
"""
New call state
"""
call = self._calls[friend_number]
call.is_active = True
call.in_audio = state | TOXAV_FRIEND_CALL_STATE['SENDING_A']
call.in_video = state | TOXAV_FRIEND_CALL_STATE['SENDING_V']
if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_A'] and call.out_audio:
self.start_audio_thread()
if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_V'] and call.out_video:
self.start_video_thread()
# -----------------------------------------------------------------------------------------------------------------
# Threads
# -----------------------------------------------------------------------------------------------------------------
def start_audio_thread(self): def start_audio_thread(self):
""" """
@ -92,7 +194,37 @@ class AV:
self._out_stream.close() self._out_stream.close()
self._out_stream = None self._out_stream = None
def chunk(self, samples, channels_count, rate): def start_video_thread(self):
if self._video_thread is not None:
return
self._video_running = True
s = settings.Settings.get_instance()
self._video_width = s.video['width']
self._video_height = s.video['height']
self._video = cv2.VideoCapture(s.video['device'])
self._video.set(cv2.CAP_PROP_FPS, 25)
self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width)
self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._video_height)
self._video_thread = threading.Thread(target=self.send_video)
self._video_thread.start()
def stop_video_thread(self):
if self._video_thread is None:
return
self._video_running = False
self._video_thread.join()
self._video_thread = None
self._video = None
# -----------------------------------------------------------------------------------------------------------------
# Incoming chunks
# -----------------------------------------------------------------------------------------------------------------
def audio_chunk(self, samples, channels_count, rate):
""" """
Incoming chunk Incoming chunk
""" """
@ -105,6 +237,10 @@ class AV:
output=True) output=True)
self._out_stream.write(samples) self._out_stream.write(samples)
# -----------------------------------------------------------------------------------------------------------------
# AV sending
# -----------------------------------------------------------------------------------------------------------------
def send_audio(self): def send_audio(self):
""" """
This method sends audio to friends This method sends audio to friends
@ -114,10 +250,10 @@ class AV:
try: try:
pcm = self._audio_stream.read(self._audio_sample_count) pcm = self._audio_stream.read(self._audio_sample_count)
if pcm: if pcm:
for friend in self._calls: for friend_num in self._calls:
if self._calls[friend] & 1: if self._calls[friend_num].out_audio:
try: try:
self._toxav.audio_send_frame(friend, pcm, self._audio_sample_count, self._toxav.audio_send_frame(friend_num, pcm, self._audio_sample_count,
self._audio_channels, self._audio_rate) self._audio_channels, self._audio_rate)
except: except:
pass pass
@ -126,19 +262,71 @@ class AV:
time.sleep(0.01) time.sleep(0.01)
def accept_call(self, friend_number, audio_enabled, video_enabled): def send_video(self):
if self._running:
self._calls[friend_number] = int(video_enabled) * 2 + int(audio_enabled)
self._toxav.answer(friend_number, 32 if audio_enabled else 0, 5000 if video_enabled else 0)
self.start_audio_thread()
def toxav_call_state_cb(self, friend_number, state):
""" """
New call state This method sends video to friends
""" """
if self._running: while self._video_running:
try:
result, frame = self._video.read()
if result:
height, width, channels = frame.shape
for friend_num in self._calls:
if self._calls[friend_num].out_video:
try:
y, u, v = self.convert_bgr_to_yuv(frame)
self._toxav.video_send_frame(friend_num, width, height, y, u, v)
except Exception as e:
print(e)
except Exception as e:
print(e)
if state & TOXAV_FRIEND_CALL_STATE['ACCEPTING_A']: time.sleep(0.01)
self._calls[friend_number] |= 1
def convert_bgr_to_yuv(self, frame):
"""
:param frame: input bgr frame
:return y, u, v: y, u, v values of frame
How this function works:
OpenCV creates YUV420 frame from BGR
This frame has following structure and size:
width, height - dim of input frame
width, height * 1.5 - dim of output frame
width
-------------------------
| |
| Y | height
| |
-------------------------
| | |
| U even | U odd | height // 4
| | |
-------------------------
| | |
| V even | V odd | height // 4
| | |
-------------------------
width // 2 width // 2
Y, U, V can be extracted using slices and joined in one list using itertools.chain.from_iterable()
Function returns bytes(y), bytes(u), bytes(v), because it is required for ctypes
"""
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2YUV_I420)
y = frame[:self._video_height, :]
y = list(itertools.chain.from_iterable(y))
u = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int)
u[::2, :] = frame[self._video_height:self._video_height * 5 // 4, :self._video_width // 2]
u[1::2, :] = frame[self._video_height:self._video_height * 5 // 4, self._video_width // 2:]
u = list(itertools.chain.from_iterable(u))
v = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int)
v[::2, :] = frame[self._video_height * 5 // 4:, :self._video_width // 2]
v[1::2, :] = frame[self._video_height * 5 // 4:, self._video_width // 2:]
v = list(itertools.chain.from_iterable(v))
return bytes(y), bytes(u), bytes(v)

View File

@ -1,12 +1,10 @@
try: from PyQt5 import QtCore, QtGui
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
from history import * from history import *
import basecontact import basecontact
import util import util
from messages import * from messages import *
import file_transfers as ft import file_transfers as ft
import re
class Contact(basecontact.BaseContact): class Contact(basecontact.BaseContact):
@ -29,8 +27,8 @@ class Contact(basecontact.BaseContact):
self._corr = [] self._corr = []
self._unsaved_messages = 0 self._unsaved_messages = 0
self._history_loaded = self._new_actions = False self._history_loaded = self._new_actions = False
self._receipts = 0 self._curr_text = self._search_string = ''
self._curr_text = '' self._search_index = 0
def __del__(self): def __del__(self):
self.set_visibility(False) self.set_visibility(False)
@ -48,6 +46,8 @@ class Contact(basecontact.BaseContact):
""" """
if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')): if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')):
return return
if self._message_getter is None:
return
data = list(self._message_getter.get(PAGE_SIZE)) data = list(self._message_getter.get(PAGE_SIZE))
if data is not None and len(data): if data is not None and len(data):
data.reverse() data.reverse()
@ -94,6 +94,10 @@ class Contact(basecontact.BaseContact):
else: else:
return '' return ''
# -----------------------------------------------------------------------------------------------------------------
# Unsent messages
# -----------------------------------------------------------------------------------------------------------------
def get_unsent_messages(self): def get_unsent_messages(self):
""" """
:return list of unsent messages :return list of unsent messages
@ -108,25 +112,6 @@ class Contact(basecontact.BaseContact):
messages = filter(lambda x: x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr) 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)) 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:] and self._unsaved_messages:
self._unsaved_messages -= 1
self._corr.remove(elem)
self._message_getter.delete_one()
def delete_old_messages(self):
"""
Delete old messages (reduces RAM if messages saving is not enabled)
"""
old = filter(lambda x: x.get_type() == 2 and (x.get_status() >= 2 or x.get_status() is None),
self._corr[:-SAVE_MESSAGES])
old = list(old)
l = max(len(self._corr) - SAVE_MESSAGES, 0) - len(old)
self._unsaved_messages -= l
self._corr = old + self._corr[-SAVE_MESSAGES:]
def mark_as_sent(self): def mark_as_sent(self):
try: try:
message = list(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]
@ -134,12 +119,41 @@ class Contact(basecontact.BaseContact):
except Exception as ex: except Exception as ex:
util.log('Mark as sent ex: ' + str(ex)) util.log('Mark as sent ex: ' + str(ex))
# -----------------------------------------------------------------------------------------------------------------
# Message deletion
# -----------------------------------------------------------------------------------------------------------------
def delete_message(self, time):
elem = list(filter(lambda x: type(x) is TextMessage and x.get_data()[2] == time, self._corr))[0]
tmp = list(filter(lambda x: x.get_type() <= 1, self._corr))
if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages:
self._unsaved_messages -= 1
self._corr.remove(elem)
self._message_getter.delete_one()
self._search_index = 0
def delete_old_messages(self):
"""
Delete old messages (reduces RAM usage if messages saving is not enabled)
"""
def save_message(x):
if x.get_type() == 2 and (x.get_status() >= 2 or x.get_status() is None):
return True
return x.get_owner() == MESSAGE_OWNER['NOT_SENT']
old = filter(save_message, self._corr[:-SAVE_MESSAGES])
self._corr = list(old) + self._corr[-SAVE_MESSAGES:]
text_messages = filter(lambda x: x.get_type() <= 1, self._corr)
self._unsaved_messages = min(self._unsaved_messages, len(list(text_messages)))
self._search_index = 0
def clear_corr(self, save_unsent=False): def clear_corr(self, save_unsent=False):
""" """
Clear messages list Clear messages list
""" """
if hasattr(self, '_message_getter'): if hasattr(self, '_message_getter'):
del self._message_getter del self._message_getter
self._search_index = 0
# don't delete data about active file transfer # don't delete data about active file transfer
if not save_unsent: if not save_unsent:
self._corr = list(filter(lambda x: x.get_type() == 2 and self._corr = list(filter(lambda x: x.get_type() == 2 and
@ -151,6 +165,45 @@ class Contact(basecontact.BaseContact):
self._corr)) self._corr))
self._unsaved_messages = len(self.get_unsent_messages()) self._unsaved_messages = len(self.get_unsent_messages())
# -----------------------------------------------------------------------------------------------------------------
# Chat history search
# -----------------------------------------------------------------------------------------------------------------
def search_string(self, search_string):
self._search_string, self._search_index = search_string, 0
return self.search_prev()
def search_prev(self):
while True:
l = len(self._corr)
for i in range(self._search_index - 1, -l - 1, -1):
if self._corr[i].get_type() > 1:
continue
message = self._corr[i].get_data()[0]
if re.search(self._search_string, message, re.IGNORECASE) is not None:
self._search_index = i
return i
self._search_index = -l
self.load_corr(False)
if len(self._corr) == l:
return None # not found
def search_next(self):
if not self._search_index:
return None
for i in range(self._search_index + 1, 0):
if self._corr[i].get_type() > 1:
continue
message = self._corr[i].get_data()[0]
if re.search(self._search_string, message, re.IGNORECASE) is not None:
self._search_index = i
return i
return None # not found
# -----------------------------------------------------------------------------------------------------------------
# Current text - text from message area
# -----------------------------------------------------------------------------------------------------------------
def get_curr_text(self): def get_curr_text(self):
return self._curr_text return self._curr_text

View File

@ -4,11 +4,7 @@ from os import remove, rename, chdir
from time import time, sleep from time import time, sleep
from tox import Tox from tox import Tox
import settings import settings
try: from PyQt5 import QtCore
from PySide import QtCore
except ImportError:
from PyQt4 import QtCore
QtCore.Signal = QtCore.pyqtSignal
TOX_FILE_TRANSFER_STATE = { TOX_FILE_TRANSFER_STATE = {
@ -32,14 +28,18 @@ SHOW_PROGRESS_BAR = (0, 1, 4)
ALLOWED_FILES = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png') ALLOWED_FILES = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png')
def is_inline(file_name):
return file_name in ALLOWED_FILES or file_name.startswith('qTox_Screenshot_')
class StateSignal(QtCore.QObject): class StateSignal(QtCore.QObject):
signal = QtCore.Signal(int, float, int) # state, progress, time in sec signal = QtCore.pyqtSignal(int, float, int) # state, progress, time in sec
class TransferFinishedSignal(QtCore.QObject): class TransferFinishedSignal(QtCore.QObject):
signal = QtCore.Signal(int, int) # friend number, file number signal = QtCore.pyqtSignal(int, int) # friend number, file number
class FileTransfer(QtCore.QObject): class FileTransfer(QtCore.QObject):

View File

@ -1,5 +1,6 @@
import contact import contact
from messages import * from messages import *
import os
class Friend(contact.Contact): class Friend(contact.Contact):
@ -37,6 +38,15 @@ class Friend(contact.Contact):
def clear_unsent_files(self): def clear_unsent_files(self):
self._corr = list(filter(lambda x: type(x) is not UnsentFile, self._corr)) self._corr = list(filter(lambda x: type(x) is not UnsentFile, self._corr))
def remove_invalid_unsent_files(self):
def is_valid(message):
if type(message) is not UnsentFile:
return True
if message.get_data()[1] is not None:
return True
return os.path.exists(message.get_data()[0])
self._corr = list(filter(is_valid, self._corr))
def delete_one_unsent_file(self, time): def delete_one_unsent_file(self, time):
self._corr = list(filter(lambda x: not (type(x) is UnsentFile and x.get_data()[2] == time), self._corr)) self._corr = list(filter(lambda x: not (type(x) is UnsentFile and x.get_data()[2] == time), self._corr))

View File

@ -2,14 +2,14 @@ from sqlite3 import connect
import settings import settings
from os import chdir from os import chdir
import os.path import os.path
from toxencryptsave import ToxEncryptSave from toxes import ToxES
PAGE_SIZE = 42 PAGE_SIZE = 42
TIMEOUT = 11 TIMEOUT = 11
SAVE_MESSAGES = 150 SAVE_MESSAGES = 250
MESSAGE_OWNER = { MESSAGE_OWNER = {
'ME': 0, 'ME': 0,
@ -25,7 +25,7 @@ class History:
chdir(settings.ProfileHelper.get_path()) chdir(settings.ProfileHelper.get_path())
path = settings.ProfileHelper.get_path() + self._name + '.hstr' path = settings.ProfileHelper.get_path() + self._name + '.hstr'
if os.path.exists(path): if os.path.exists(path):
decr = ToxEncryptSave.get_instance() decr = ToxES.get_instance()
try: try:
with open(path, 'rb') as fin: with open(path, 'rb') as fin:
data = fin.read() data = fin.read()
@ -43,7 +43,7 @@ class History:
db.close() db.close()
def save(self): def save(self):
encr = ToxEncryptSave.get_instance() encr = ToxES.get_instance()
if encr.has_password(): if encr.has_password():
path = settings.ProfileHelper.get_path() + self._name + '.hstr' path = settings.ProfileHelper.get_path() + self._name + '.hstr'
with open(path, 'rb') as fin: with open(path, 'rb') as fin:
@ -57,7 +57,7 @@ class History:
new_path = directory + self._name + '.hstr' new_path = directory + self._name + '.hstr'
with open(path, 'rb') as fin: with open(path, 'rb') as fin:
data = fin.read() data = fin.read()
encr = ToxEncryptSave.get_instance() encr = ToxES.get_instance()
if encr.has_password(): if encr.has_password():
data = encr.pass_encrypt(data) data = encr.pass_encrypt(data)
with open(new_path, 'wb') as fout: with open(new_path, 'wb') as fout:
@ -133,14 +133,15 @@ class History:
db.rollback() db.rollback()
finally: finally:
db.close() db.close()
pass
def delete_message(self, tox_id, time): def delete_message(self, tox_id, time):
start, end = str(time - 0.01), str(time + 0.01)
chdir(settings.ProfileHelper.get_path()) chdir(settings.ProfileHelper.get_path())
db = connect(self._name + '.hstr', timeout=TIMEOUT) db = connect(self._name + '.hstr', timeout=TIMEOUT)
try: try:
cursor = db.cursor() cursor = db.cursor()
cursor.execute('DELETE FROM id' + tox_id + ' WHERE unix_time = ' + str(time) + ';') cursor.execute('DELETE FROM id' + tox_id + ' WHERE unix_time < ' + end + ' AND unix_time > ' +
start + ';')
db.commit() db.commit()
except: except:
print('Database is locked!') print('Database is locked!')

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

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

View File

@ -1,9 +1,5 @@
from toxcore_enums_and_consts import * from toxcore_enums_and_consts import *
try: from PyQt5 import QtCore, QtGui, QtWidgets
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
QtCore.Slot = QtCore.pyqtSlot
import profile import profile
from file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR from file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR
from util import curr_directory, convert_time, curr_time from util import curr_directory, convert_time, curr_time
@ -11,9 +7,10 @@ from widgets import DataLabel, create_menu
import html as h import html as h
import smileys import smileys
import settings import settings
import re
class MessageEdit(QtGui.QTextBrowser): class MessageEdit(QtWidgets.QTextBrowser):
def __init__(self, text, width, message_type, parent=None): def __init__(self, text, width, message_type, parent=None):
super(MessageEdit, self).__init__(parent) super(MessageEdit, self).__init__(parent)
@ -45,7 +42,7 @@ class MessageEdit(QtGui.QTextBrowser):
def contextMenuEvent(self, event): def contextMenuEvent(self, event):
menu = create_menu(self.createStandardContextMenu(event.pos())) menu = create_menu(self.createStandardContextMenu(event.pos()))
quote = menu.addAction(QtGui.QApplication.translate("MainWindow", 'Quote selected text', None, QtGui.QApplication.UnicodeUTF8)) quote = menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Quote selected text'))
quote.triggered.connect(self.quote_text) quote.triggered.connect(self.quote_text)
text = self.textCursor().selection().toPlainText() text = self.textCursor().selection().toPlainText()
if not text: if not text:
@ -54,7 +51,7 @@ class MessageEdit(QtGui.QTextBrowser):
import plugin_support import plugin_support
submenu = plugin_support.PluginLoader.get_instance().get_message_menu(menu, text) submenu = plugin_support.PluginLoader.get_instance().get_message_menu(menu, text)
if len(submenu): if len(submenu):
plug = menu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8)) plug = menu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins'))
plug.addActions(submenu) plug.addActions(submenu)
menu.popup(event.globalPos()) menu.popup(event.globalPos())
menu.exec_(event.globalPos()) menu.exec_(event.globalPos())
@ -122,12 +119,12 @@ class MessageEdit(QtGui.QTextBrowser):
return text return text
class MessageItem(QtGui.QWidget): class MessageItem(QtWidgets.QWidget):
""" """
Message in messages list Message in messages list
""" """
def __init__(self, text, time, user='', sent=True, message_type=TOX_MESSAGE_TYPE['NORMAL'], parent=None): def __init__(self, text, time, user='', sent=True, message_type=TOX_MESSAGE_TYPE['NORMAL'], parent=None):
QtGui.QWidget.__init__(self, parent) QtWidgets.QWidget.__init__(self, parent)
self.name = DataLabel(self) self.name = DataLabel(self)
self.name.setGeometry(QtCore.QRect(2, 2, 95, 23)) self.name.setGeometry(QtCore.QRect(2, 2, 95, 23))
self.name.setTextFormat(QtCore.Qt.PlainText) self.name.setTextFormat(QtCore.Qt.PlainText)
@ -138,7 +135,7 @@ class MessageItem(QtGui.QWidget):
self.name.setFont(font) self.name.setFont(font)
self.name.setText(user) self.name.setText(user)
self.time = QtGui.QLabel(self) self.time = QtWidgets.QLabel(self)
self.time.setGeometry(QtCore.QRect(parent.width() - 60, 0, 50, 25)) self.time.setGeometry(QtCore.QRect(parent.width() - 60, 0, 50, 25))
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
@ -163,9 +160,9 @@ class MessageItem(QtGui.QWidget):
def mouseReleaseEvent(self, event): def mouseReleaseEvent(self, event):
if event.button() == QtCore.Qt.RightButton and event.x() > self.time.x(): if event.button() == QtCore.Qt.RightButton and event.x() > self.time.x():
self.listMenu = QtGui.QMenu() self.listMenu = QtWidgets.QMenu()
delete_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Delete message', None, QtGui.QApplication.UnicodeUTF8)) delete_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Delete message'))
self.connect(delete_item, QtCore.SIGNAL("triggered()"), self.delete) delete_item.triggered.connect(self.delete)
parent_position = self.time.mapToGlobal(QtCore.QPoint(0, 0)) parent_position = self.time.mapToGlobal(QtCore.QPoint(0, 0))
self.listMenu.move(parent_position) self.listMenu.move(parent_position)
self.listMenu.show() self.listMenu.show()
@ -189,17 +186,42 @@ class MessageItem(QtGui.QWidget):
self.message.setFixedHeight(self.height()) self.message.setFixedHeight(self.height())
self.name.setPixmap(pixmap.scaled(30, 30, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)) self.name.setPixmap(pixmap.scaled(30, 30, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))
def select_text(self, text):
tmp = self.message.toHtml()
text = h.escape(text)
strings = re.findall(text, tmp, flags=re.IGNORECASE)
for s in strings:
tmp = self.replace_all(tmp, s)
self.message.setHtml(tmp)
class ContactItem(QtGui.QWidget): @staticmethod
def replace_all(text, substring):
i, l = 0, len(substring)
while i < len(text) - l + 1:
index = text[i:].find(substring)
if index == -1:
break
i += index
lgt, rgt = text[i:].find('<'), text[i:].find('>')
if rgt < lgt:
i += rgt + 1
continue
sub = '<font color="red"><b>{}</b></font>'.format(substring)
text = text[:i] + sub + text[i + l:]
i += len(sub)
return text
class ContactItem(QtWidgets.QWidget):
""" """
Contact in friends list Contact in friends list
""" """
def __init__(self, parent=None): def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent) QtWidgets.QWidget.__init__(self, parent)
mode = settings.Settings.get_instance()['compact_mode'] mode = settings.Settings.get_instance()['compact_mode']
self.setBaseSize(QtCore.QSize(250, 40 if mode else 70)) self.setBaseSize(QtCore.QSize(250, 40 if mode else 70))
self.avatar_label = QtGui.QLabel(self) self.avatar_label = QtWidgets.QLabel(self)
size = 32 if mode else 64 size = 32 if mode else 64
self.avatar_label.setGeometry(QtCore.QRect(3, 4, size, size)) self.avatar_label.setGeometry(QtCore.QRect(3, 4, size, size))
self.avatar_label.setScaledContents(False) self.avatar_label.setScaledContents(False)
@ -222,14 +244,14 @@ class ContactItem(QtGui.QWidget):
self.messages.setGeometry(QtCore.QRect(20 if mode else 52, 20 if mode else 50, 30, 20)) self.messages.setGeometry(QtCore.QRect(20 if mode else 52, 20 if mode else 50, 30, 20))
class StatusCircle(QtGui.QWidget): class StatusCircle(QtWidgets.QWidget):
""" """
Connection status Connection status
""" """
def __init__(self, parent): def __init__(self, parent):
QtGui.QWidget.__init__(self, parent) QtWidgets.QWidget.__init__(self, parent)
self.setGeometry(0, 0, 32, 32) self.setGeometry(0, 0, 32, 32)
self.label = QtGui.QLabel(self) self.label = QtWidgets.QLabel(self)
self.label.setGeometry(QtCore.QRect(0, 0, 32, 32)) self.label.setGeometry(QtCore.QRect(0, 0, 32, 32))
self.unread = False self.unread = False
@ -255,12 +277,12 @@ class StatusCircle(QtGui.QWidget):
self.label.setPixmap(pixmap) self.label.setPixmap(pixmap)
class UnreadMessagesCount(QtGui.QWidget): class UnreadMessagesCount(QtWidgets.QWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
super(UnreadMessagesCount, self).__init__(parent) super(UnreadMessagesCount, self).__init__(parent)
self.resize(30, 20) self.resize(30, 20)
self.label = QtGui.QLabel(self) self.label = QtWidgets.QLabel(self)
self.label.setGeometry(QtCore.QRect(0, 0, 30, 20)) self.label.setGeometry(QtCore.QRect(0, 0, 30, 20))
self.label.setVisible(False) self.label.setVisible(False)
font = QtGui.QFont() font = QtGui.QFont()
@ -282,11 +304,11 @@ class UnreadMessagesCount(QtGui.QWidget):
self.label.setVisible(False) self.label.setVisible(False)
class FileTransferItem(QtGui.QListWidget): class FileTransferItem(QtWidgets.QListWidget):
def __init__(self, file_name, size, time, user, friend_number, file_number, state, width, parent=None): def __init__(self, file_name, size, time, user, friend_number, file_number, state, width, parent=None):
QtGui.QListWidget.__init__(self, parent) QtWidgets.QListWidget.__init__(self, parent)
self.resize(QtCore.QSize(width, 34)) self.resize(QtCore.QSize(width, 34))
if state == TOX_FILE_TRANSFER_STATE['CANCELLED']: if state == TOX_FILE_TRANSFER_STATE['CANCELLED']:
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
@ -306,14 +328,14 @@ class FileTransferItem(QtGui.QListWidget):
self.name.setFont(font) self.name.setFont(font)
self.name.setText(user) self.name.setText(user)
self.time = QtGui.QLabel(self) self.time = QtWidgets.QLabel(self)
self.time.setGeometry(QtCore.QRect(width - 60, 7, 50, 25)) self.time.setGeometry(QtCore.QRect(width - 60, 7, 50, 25))
font.setPointSize(10) font.setPointSize(10)
font.setBold(False) font.setBold(False)
self.time.setFont(font) self.time.setFont(font)
self.time.setText(convert_time(time)) self.time.setText(convert_time(time))
self.cancel = QtGui.QPushButton(self) self.cancel = QtWidgets.QPushButton(self)
self.cancel.setGeometry(QtCore.QRect(width - 125, 2, 30, 30)) self.cancel.setGeometry(QtCore.QRect(width - 125, 2, 30, 30))
pixmap = QtGui.QPixmap(curr_directory() + '/images/decline.png') pixmap = QtGui.QPixmap(curr_directory() + '/images/decline.png')
icon = QtGui.QIcon(pixmap) icon = QtGui.QIcon(pixmap)
@ -323,7 +345,7 @@ class FileTransferItem(QtGui.QListWidget):
self.cancel.clicked.connect(lambda: self.cancel_transfer(friend_number, file_number)) self.cancel.clicked.connect(lambda: self.cancel_transfer(friend_number, file_number))
self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}') self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}')
self.accept_or_pause = QtGui.QPushButton(self) self.accept_or_pause = QtWidgets.QPushButton(self)
self.accept_or_pause.setGeometry(QtCore.QRect(width - 170, 2, 30, 30)) self.accept_or_pause.setGeometry(QtCore.QRect(width - 170, 2, 30, 30))
if state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: if state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
self.accept_or_pause.setVisible(True) self.accept_or_pause.setVisible(True)
@ -340,7 +362,7 @@ class FileTransferItem(QtGui.QListWidget):
self.accept_or_pause.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none}') self.accept_or_pause.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none}')
self.pb = QtGui.QProgressBar(self) self.pb = QtWidgets.QProgressBar(self)
self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20)) self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20))
self.pb.setValue(0) self.pb.setValue(0)
self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }') self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }')
@ -361,7 +383,7 @@ class FileTransferItem(QtGui.QListWidget):
self.file_name.setText(file_data) self.file_name.setText(file_data)
self.file_name.setToolTip(file_name) self.file_name.setToolTip(file_name)
self.saved_name = file_name self.saved_name = file_name
self.time_left = QtGui.QLabel(self) self.time_left = QtWidgets.QLabel(self)
self.time_left.setGeometry(QtCore.QRect(width - 92, 7, 30, 20)) self.time_left.setGeometry(QtCore.QRect(width - 92, 7, 30, 20))
font.setPointSize(10) font.setPointSize(10)
self.time_left.setFont(font) self.time_left.setFont(font)
@ -379,10 +401,10 @@ class FileTransferItem(QtGui.QListWidget):
def accept_or_pause_transfer(self, friend_number, file_number, size): def accept_or_pause_transfer(self, friend_number, file_number, size):
if self.state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: if self.state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
directory = QtGui.QFileDialog.getExistingDirectory(self, directory = QtWidgets.QFileDialog.getExistingDirectory(self,
QtGui.QApplication.translate("MainWindow", 'Choose folder', None, QtGui.QApplication.UnicodeUTF8), QtWidgets.QApplication.translate("MainWindow", 'Choose folder'),
curr_directory(), curr_directory(),
QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog) QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
self.pb.setVisible(True) self.pb.setVisible(True)
if directory: if directory:
pr = profile.Profile.get_instance() pr = profile.Profile.get_instance()
@ -406,8 +428,7 @@ class FileTransferItem(QtGui.QListWidget):
self.accept_or_pause.setIcon(icon) self.accept_or_pause.setIcon(icon)
self.accept_or_pause.setIconSize(QtCore.QSize(30, 30)) self.accept_or_pause.setIconSize(QtCore.QSize(30, 30))
@QtCore.Slot(int, float, int) def update_transfer_state(self, state, progress, time):
def update(self, state, progress, time):
self.pb.setValue(int(progress * 100)) self.pb.setValue(int(progress * 100))
if time + 1: if time + 1:
m, s = divmod(time, 60) m, s = divmod(time, 60)
@ -470,14 +491,14 @@ class UnsentFileItem(FileTransferItem):
pr.cancel_not_started_transfer(self._time) pr.cancel_not_started_transfer(self._time)
class InlineImageItem(QtGui.QScrollArea): class InlineImageItem(QtWidgets.QScrollArea):
def __init__(self, data, width, elem): def __init__(self, data, width, elem):
QtGui.QScrollArea.__init__(self) QtWidgets.QScrollArea.__init__(self)
self.setFocusPolicy(QtCore.Qt.NoFocus) self.setFocusPolicy(QtCore.Qt.NoFocus)
self._elem = elem self._elem = elem
self._image_label = QtGui.QLabel(self) self._image_label = QtWidgets.QLabel(self)
self._image_label.raise_() self._image_label.raise_()
self.setWidget(self._image_label) self.setWidget(self._image_label)
self._image_label.setScaledContents(False) self._image_label.setScaledContents(False)
@ -511,18 +532,15 @@ class InlineImageItem(QtGui.QScrollArea):
self._full_size = not self._full_size self._full_size = not self._full_size
self._elem.setSizeHint(QtCore.QSize(self.width(), self.height())) self._elem.setSizeHint(QtCore.QSize(self.width(), self.height()))
elif event.button() == QtCore.Qt.RightButton: # save inline elif event.button() == QtCore.Qt.RightButton: # save inline
directory = QtGui.QFileDialog.getExistingDirectory(self, directory = QtWidgets.QFileDialog.getExistingDirectory(self,
QtGui.QApplication.translate("MainWindow", QtWidgets.QApplication.translate("MainWindow",
'Choose folder', None, 'Choose folder'),
QtGui.QApplication.UnicodeUTF8), curr_directory(),
curr_directory(), QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog)
if directory: if directory:
fl = QtCore.QFile(directory + '/toxygen_inline_' + curr_time().replace(':', '_') + '.png') fl = QtCore.QFile(directory + '/toxygen_inline_' + curr_time().replace(':', '_') + '.png')
self._pixmap.save(fl, 'PNG') self._pixmap.save(fl, 'PNG')
return False
def mark_as_sent(self): def mark_as_sent(self):
return False return False

View File

@ -1,9 +1,4 @@
# -*- coding: utf-8 -*- from PyQt5 import QtWidgets, QtCore
try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
from widgets import * from widgets import *
@ -31,25 +26,25 @@ class LoginScreen(CenteredWidget):
self.resize(400, 200) self.resize(400, 200)
self.setMinimumSize(QtCore.QSize(400, 200)) self.setMinimumSize(QtCore.QSize(400, 200))
self.setMaximumSize(QtCore.QSize(400, 200)) self.setMaximumSize(QtCore.QSize(400, 200))
self.new_profile = QtGui.QPushButton(self) self.new_profile = QtWidgets.QPushButton(self)
self.new_profile.setGeometry(QtCore.QRect(20, 150, 171, 27)) self.new_profile.setGeometry(QtCore.QRect(20, 150, 171, 27))
self.new_profile.clicked.connect(self.create_profile) self.new_profile.clicked.connect(self.create_profile)
self.label = QtGui.QLabel(self) self.label = QtWidgets.QLabel(self)
self.label.setGeometry(QtCore.QRect(20, 70, 101, 17)) self.label.setGeometry(QtCore.QRect(20, 70, 101, 17))
self.new_name = NickEdit(self) self.new_name = NickEdit(self)
self.new_name.setGeometry(QtCore.QRect(20, 100, 171, 31)) self.new_name.setGeometry(QtCore.QRect(20, 100, 171, 31))
self.load_profile = QtGui.QPushButton(self) self.load_profile = QtWidgets.QPushButton(self)
self.load_profile.setGeometry(QtCore.QRect(220, 150, 161, 27)) self.load_profile.setGeometry(QtCore.QRect(220, 150, 161, 27))
self.load_profile.clicked.connect(self.load_ex_profile) self.load_profile.clicked.connect(self.load_ex_profile)
self.default = QtGui.QCheckBox(self) self.default = QtWidgets.QCheckBox(self)
self.default.setGeometry(QtCore.QRect(220, 110, 131, 22)) self.default.setGeometry(QtCore.QRect(220, 110, 131, 22))
self.groupBox = QtGui.QGroupBox(self) self.groupBox = QtWidgets.QGroupBox(self)
self.groupBox.setGeometry(QtCore.QRect(210, 40, 181, 151)) self.groupBox.setGeometry(QtCore.QRect(210, 40, 181, 151))
self.comboBox = QtGui.QComboBox(self.groupBox) self.comboBox = QtWidgets.QComboBox(self.groupBox)
self.comboBox.setGeometry(QtCore.QRect(10, 30, 161, 27)) self.comboBox.setGeometry(QtCore.QRect(10, 30, 161, 27))
self.groupBox_2 = QtGui.QGroupBox(self) self.groupBox_2 = QtWidgets.QGroupBox(self)
self.groupBox_2.setGeometry(QtCore.QRect(10, 40, 191, 151)) self.groupBox_2.setGeometry(QtCore.QRect(10, 40, 191, 151))
self.toxygen = QtGui.QLabel(self) self.toxygen = QtWidgets.QLabel(self)
self.groupBox.raise_() self.groupBox.raise_()
self.groupBox_2.raise_() self.groupBox_2.raise_()
self.comboBox.raise_() self.comboBox.raise_()
@ -71,15 +66,15 @@ class LoginScreen(CenteredWidget):
QtCore.QMetaObject.connectSlotsByName(self) QtCore.QMetaObject.connectSlotsByName(self)
def retranslateUi(self): def retranslateUi(self):
self.new_name.setPlaceholderText(QtGui.QApplication.translate("login", "Profile name", None, QtGui.QApplication.UnicodeUTF8)) self.new_name.setPlaceholderText(QtWidgets.QApplication.translate("login", "Profile name"))
self.setWindowTitle(QtGui.QApplication.translate("login", "Log in", None, QtGui.QApplication.UnicodeUTF8)) self.setWindowTitle(QtWidgets.QApplication.translate("login", "Log in"))
self.new_profile.setText(QtGui.QApplication.translate("login", "Create", None, QtGui.QApplication.UnicodeUTF8)) self.new_profile.setText(QtWidgets.QApplication.translate("login", "Create"))
self.label.setText(QtGui.QApplication.translate("login", "Profile name:", None, QtGui.QApplication.UnicodeUTF8)) self.label.setText(QtWidgets.QApplication.translate("login", "Profile name:"))
self.load_profile.setText(QtGui.QApplication.translate("login", "Load profile", None, QtGui.QApplication.UnicodeUTF8)) self.load_profile.setText(QtWidgets.QApplication.translate("login", "Load profile"))
self.default.setText(QtGui.QApplication.translate("login", "Use as default", None, QtGui.QApplication.UnicodeUTF8)) self.default.setText(QtWidgets.QApplication.translate("login", "Use as default"))
self.groupBox.setTitle(QtGui.QApplication.translate("login", "Load existing profile", None, QtGui.QApplication.UnicodeUTF8)) self.groupBox.setTitle(QtWidgets.QApplication.translate("login", "Load existing profile"))
self.groupBox_2.setTitle(QtGui.QApplication.translate("login", "Create new profile", None, QtGui.QApplication.UnicodeUTF8)) self.groupBox_2.setTitle(QtWidgets.QApplication.translate("login", "Create new profile"))
self.toxygen.setText(QtGui.QApplication.translate("login", "toxygen", None, QtGui.QApplication.UnicodeUTF8)) self.toxygen.setText(QtWidgets.QApplication.translate("login", "toxygen"))
def create_profile(self): def create_profile(self):
self.type = 1 self.type = 1

View File

@ -2,17 +2,14 @@ import sys
from loginscreen import LoginScreen from loginscreen import LoginScreen
import profile import profile
from settings import * from settings import *
try: from PyQt5 import QtCore, QtGui, QtWidgets
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
from bootstrap import node_generator from bootstrap import node_generator
from mainscreen import MainWindow from mainscreen import MainWindow
from callbacks import init_callbacks, stop, start from callbacks import init_callbacks, stop, start
from util import curr_directory, program_version, remove, is_64_bit from util import curr_directory, program_version, remove, is_64_bit
import styles.style import styles.style
import platform import platform
import toxencryptsave import toxes
from passwordscreen import PasswordScreen, UnlockAppScreen, SetProfilePasswordScreen from passwordscreen import PasswordScreen, UnlockAppScreen, SetProfilePasswordScreen
from plugin_support import PluginLoader from plugin_support import PluginLoader
import updater import updater
@ -37,9 +34,9 @@ class Toxygen:
Show password screen Show password screen
""" """
tmp = [data] tmp = [data]
p = PasswordScreen(toxencryptsave.ToxEncryptSave.get_instance(), tmp) p = PasswordScreen(toxes.ToxES.get_instance(), tmp)
p.show() p.show()
self.app.connect(self.app, QtCore.SIGNAL("lastWindowClosed()"), self.app, QtCore.SLOT("quit()")) self.app.lastWindowClosed.connect(self.app.quit)
self.app.exec_() self.app.exec_()
if tmp[0] == data: if tmp[0] == data:
raise SystemExit() raise SystemExit()
@ -50,7 +47,7 @@ class Toxygen:
""" """
Main function of app. loads login screen if needed and starts main screen Main function of app. loads login screen if needed and starts main screen
""" """
app = QtGui.QApplication(sys.argv) app = QtWidgets.QApplication(sys.argv)
app.setWindowIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) app.setWindowIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
self.app = app self.app = app
@ -62,7 +59,7 @@ class Toxygen:
dark_style = fl.read() dark_style = fl.read()
app.setStyleSheet(dark_style) app.setStyleSheet(dark_style)
encrypt_save = toxencryptsave.ToxEncryptSave() encrypt_save = toxes.ToxES()
if self.path is not None: if self.path is not None:
path = os.path.dirname(self.path) + '/' path = os.path.dirname(self.path) + '/'
@ -92,7 +89,6 @@ class Toxygen:
_login = self.Login(profiles) _login = self.Login(profiles)
ls.update_on_close(_login.login_screen_close) ls.update_on_close(_login.login_screen_close)
ls.show() ls.show()
app.connect(app, QtCore.SIGNAL("lastWindowClosed()"), app, QtCore.SLOT("quit()"))
app.exec_() app.exec_()
if not _login.t: if not _login.t:
return return
@ -101,40 +97,35 @@ class Toxygen:
name = _login.name if _login.name else 'toxygen_user' name = _login.name if _login.name else 'toxygen_user'
pr = map(lambda x: x[1], ProfileHelper.find_profiles()) pr = map(lambda x: x[1], ProfileHelper.find_profiles())
if name in list(pr): if name in list(pr):
msgBox = QtGui.QMessageBox() msgBox = QtWidgets.QMessageBox()
msgBox.setWindowTitle( msgBox.setWindowTitle(
QtGui.QApplication.translate("MainWindow", "Error", None, QtGui.QApplication.UnicodeUTF8)) QtWidgets.QApplication.translate("MainWindow", "Error"))
text = (QtGui.QApplication.translate("MainWindow", text = (QtWidgets.QApplication.translate("MainWindow",
'Profile with this name already exists', 'Profile with this name already exists'))
None, QtGui.QApplication.UnicodeUTF8))
msgBox.setText(text) msgBox.setText(text)
msgBox.exec_() msgBox.exec_()
return return
self.tox = profile.tox_factory() self.tox = profile.tox_factory()
self.tox.self_set_name(bytes(_login.name, 'utf-8') if _login.name else b'Toxygen User') self.tox.self_set_name(bytes(_login.name, 'utf-8') if _login.name else b'Toxygen User')
self.tox.self_set_status_message(b'Toxing on Toxygen') self.tox.self_set_status_message(b'Toxing on Toxygen')
reply = QtGui.QMessageBox.question(None, reply = QtWidgets.QMessageBox.question(None,
'Profile {}'.format(name), 'Profile {}'.format(name),
QtGui.QApplication.translate("login", QtWidgets.QApplication.translate("login",
'Do you want to set profile password?', 'Do you want to set profile password?'),
None, QtWidgets.QMessageBox.Yes,
QtGui.QApplication.UnicodeUTF8), QtWidgets.QMessageBox.No)
QtGui.QMessageBox.Yes, if reply == QtWidgets.QMessageBox.Yes:
QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
set_pass = SetProfilePasswordScreen(encrypt_save) set_pass = SetProfilePasswordScreen(encrypt_save)
set_pass.show() set_pass.show()
self.app.connect(self.app, QtCore.SIGNAL("lastWindowClosed()"), self.app, QtCore.SLOT("quit()")) self.app.lastWindowClosed.connect(self.app.quit)
self.app.exec_() self.app.exec_()
reply = QtGui.QMessageBox.question(None, reply = QtWidgets.QMessageBox.question(None,
'Profile {}'.format(name), 'Profile {}'.format(name),
QtGui.QApplication.translate("login", QtWidgets.QApplication.translate("login",
'Do you want to save profile in default folder? If no, profile will be saved in program folder', 'Do you want to save profile in default folder? If no, profile will be saved in program folder'),
None, QtWidgets.QMessageBox.Yes,
QtGui.QApplication.UnicodeUTF8), QtWidgets.QMessageBox.No)
QtGui.QMessageBox.Yes, if reply == QtWidgets.QMessageBox.Yes:
QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
path = Settings.get_default_path() path = Settings.get_default_path()
else: else:
path = curr_directory() + '/' path = curr_directory() + '/'
@ -143,11 +134,9 @@ class Toxygen:
except Exception as ex: except Exception as ex:
print(str(ex)) print(str(ex))
log('Profile creation exception: ' + str(ex)) log('Profile creation exception: ' + str(ex))
msgBox = QtGui.QMessageBox() msgBox = QtWidgets.QMessageBox()
msgBox.setText(QtGui.QApplication.translate("login", msgBox.setText(QtWidgets.QApplication.translate("login",
'Profile saving error! Does Toxygen have permission to write to this directory?', 'Profile saving error! Does Toxygen have permission to write to this directory?'))
None,
QtGui.QApplication.UnicodeUTF8))
msgBox.exec_() msgBox.exec_()
return return
path = Settings.get_default_path() path = Settings.get_default_path()
@ -173,12 +162,12 @@ class Toxygen:
self.tox = profile.tox_factory(data, settings) self.tox = profile.tox_factory(data, settings)
if Settings.is_active_profile(path, name): # profile is in use if Settings.is_active_profile(path, name): # profile is in use
reply = QtGui.QMessageBox.question(None, reply = QtWidgets.QMessageBox.question(None,
'Profile {}'.format(name), 'Profile {}'.format(name),
QtGui.QApplication.translate("login", 'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?', None, QtGui.QApplication.UnicodeUTF8), QtWidgets.QApplication.translate("login", 'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?'),
QtGui.QMessageBox.Yes, QtWidgets.QMessageBox.Yes,
QtGui.QMessageBox.No) QtWidgets.QMessageBox.No)
if reply != QtGui.QMessageBox.Yes: if reply != QtWidgets.QMessageBox.Yes:
return return
else: else:
settings.set_active_profile() settings.set_active_profile()
@ -190,20 +179,21 @@ class Toxygen:
app.translator = translator app.translator = translator
# tray icon # tray icon
self.tray = QtGui.QSystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) self.tray = QtWidgets.QSystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
self.tray.setObjectName('tray') self.tray.setObjectName('tray')
self.ms = MainWindow(self.tox, self.reset, self.tray) self.ms = MainWindow(self.tox, self.reset, self.tray)
app.aboutToQuit.connect(self.ms.close_window) app.aboutToQuit.connect(self.ms.close_window)
class Menu(QtGui.QMenu): class Menu(QtWidgets.QMenu):
def newStatus(self, status): def newStatus(self, status):
profile.Profile.get_instance().set_status(status) if not Settings.get_instance().locked:
self.aboutToShow() profile.Profile.get_instance().set_status(status)
self.hide() self.aboutToShowHandler()
self.hide()
def aboutToShow(self): def aboutToShowHandler(self):
status = profile.Profile.get_instance().status status = profile.Profile.get_instance().status
act = self.act act = self.act
if status is None or Settings.get_instance().locked: if status is None or Settings.get_instance().locked:
@ -217,54 +207,60 @@ class Toxygen:
self.actions()[2].setVisible(not Settings.get_instance().locked) self.actions()[2].setVisible(not Settings.get_instance().locked)
def languageChange(self, *args, **kwargs): def languageChange(self, *args, **kwargs):
self.actions()[0].setText(QtGui.QApplication.translate('tray', 'Open Toxygen', None, QtGui.QApplication.UnicodeUTF8)) self.actions()[0].setText(QtWidgets.QApplication.translate('tray', 'Open Toxygen'))
self.actions()[1].setText(QtGui.QApplication.translate('tray', 'Set status', None, QtGui.QApplication.UnicodeUTF8)) self.actions()[1].setText(QtWidgets.QApplication.translate('tray', 'Set status'))
self.actions()[2].setText(QtGui.QApplication.translate('tray', 'Exit', None, QtGui.QApplication.UnicodeUTF8)) self.actions()[2].setText(QtWidgets.QApplication.translate('tray', 'Exit'))
self.act.actions()[0].setText(QtGui.QApplication.translate('tray', 'Online', None, QtGui.QApplication.UnicodeUTF8)) self.act.actions()[0].setText(QtWidgets.QApplication.translate('tray', 'Online'))
self.act.actions()[1].setText(QtGui.QApplication.translate('tray', 'Away', None, QtGui.QApplication.UnicodeUTF8)) self.act.actions()[1].setText(QtWidgets.QApplication.translate('tray', 'Away'))
self.act.actions()[2].setText(QtGui.QApplication.translate('tray', 'Busy', None, QtGui.QApplication.UnicodeUTF8)) self.act.actions()[2].setText(QtWidgets.QApplication.translate('tray', 'Busy'))
m = Menu() m = Menu()
show = m.addAction(QtGui.QApplication.translate('tray', 'Open Toxygen', None, QtGui.QApplication.UnicodeUTF8)) show = m.addAction(QtWidgets.QApplication.translate('tray', 'Open Toxygen'))
sub = m.addMenu(QtGui.QApplication.translate('tray', 'Set status', None, QtGui.QApplication.UnicodeUTF8)) sub = m.addMenu(QtWidgets.QApplication.translate('tray', 'Set status'))
onl = sub.addAction(QtGui.QApplication.translate('tray', 'Online', None, QtGui.QApplication.UnicodeUTF8)) onl = sub.addAction(QtWidgets.QApplication.translate('tray', 'Online'))
away = sub.addAction(QtGui.QApplication.translate('tray', 'Away', None, QtGui.QApplication.UnicodeUTF8)) away = sub.addAction(QtWidgets.QApplication.translate('tray', 'Away'))
busy = sub.addAction(QtGui.QApplication.translate('tray', 'Busy', None, QtGui.QApplication.UnicodeUTF8)) busy = sub.addAction(QtWidgets.QApplication.translate('tray', 'Busy'))
onl.setCheckable(True) onl.setCheckable(True)
away.setCheckable(True) away.setCheckable(True)
busy.setCheckable(True) busy.setCheckable(True)
m.act = sub m.act = sub
exit = m.addAction(QtGui.QApplication.translate('tray', 'Exit', None, QtGui.QApplication.UnicodeUTF8)) exit = m.addAction(QtWidgets.QApplication.translate('tray', 'Exit'))
def show_window(): def show_window():
s = Settings.get_instance()
def show(): def show():
if not self.ms.isActiveWindow(): if not self.ms.isActiveWindow():
self.ms.setWindowState(self.ms.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) self.ms.setWindowState(self.ms.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
self.ms.activateWindow() self.ms.activateWindow()
self.ms.show() self.ms.show()
if not Settings.get_instance().locked: if not s.locked:
show() show()
else: else:
def correct_pass(): def correct_pass():
show() show()
Settings.get_instance().locked = False s.locked = False
self.p = UnlockAppScreen(toxencryptsave.ToxEncryptSave.get_instance(), correct_pass) s.unlockScreen = False
self.p.show() if not s.unlockScreen:
s.unlockScreen = True
self.p = UnlockAppScreen(toxes.ToxES.get_instance(), correct_pass)
self.p.show()
def tray_activated(reason): def tray_activated(reason):
if reason == QtGui.QSystemTrayIcon.DoubleClick: if reason == QtWidgets.QSystemTrayIcon.DoubleClick:
show_window() show_window()
def close_app(): def close_app():
settings.closing = True if not Settings.get_instance().locked:
self.ms.close() settings.closing = True
self.ms.close()
m.connect(show, QtCore.SIGNAL("triggered()"), show_window) show.triggered.connect(show_window)
m.connect(exit, QtCore.SIGNAL("triggered()"), close_app) exit.triggered.connect(close_app)
m.connect(m, QtCore.SIGNAL("aboutToShow()"), lambda: m.aboutToShow()) m.aboutToShow.connect(lambda: m.aboutToShowHandler())
sub.connect(onl, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(0)) onl.triggered.connect(lambda: m.newStatus(0))
sub.connect(away, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(1)) away.triggered.connect(lambda: m.newStatus(1))
sub.connect(busy, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(2)) busy.triggered.connect(lambda: m.newStatus(2))
self.tray.setContextMenu(m) self.tray.setContextMenu(m)
self.tray.show() self.tray.show()
@ -280,15 +276,13 @@ class Toxygen:
updater.download(version) updater.download(version)
updating = True updating = True
else: else:
reply = QtGui.QMessageBox.question(None, reply = QtWidgets.QMessageBox.question(None,
'Toxygen', 'Toxygen',
QtGui.QApplication.translate("login", QtWidgets.QApplication.translate("login",
'Update for Toxygen was found. Download and install it?', 'Update for Toxygen was found. Download and install it?'),
None, QtWidgets.QMessageBox.Yes,
QtGui.QApplication.UnicodeUTF8), QtWidgets.QMessageBox.No)
QtGui.QMessageBox.Yes, if reply == QtWidgets.QMessageBox.Yes:
QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
updater.download(version) updater.download(version)
updating = True updating = True
@ -316,7 +310,7 @@ class Toxygen:
if self.uri is not None: if self.uri is not None:
self.ms.add_contact(self.uri) self.ms.add_contact(self.uri)
app.connect(app, QtCore.SIGNAL("lastWindowClosed()"), app, QtCore.SLOT("quit()")) app.lastWindowClosed.connect(app.quit)
app.exec_() app.exec_()
self.init.stop = True self.init.stop = True
@ -475,6 +469,10 @@ def configure():
pass pass
def reset():
Settings.reset_auto_profile()
def main(): def main():
if len(sys.argv) == 1: if len(sys.argv) == 1:
toxygen = Toxygen() toxygen = Toxygen()
@ -484,7 +482,7 @@ def main():
print('Toxygen v' + program_version) print('Toxygen v' + program_version)
return return
elif arg == '--help': elif arg == '--help':
print('Usage:\ntoxygen path_to_profile\ntoxygen tox_id\ntoxygen --version') print('Usage:\ntoxygen path_to_profile\ntoxygen tox_id\ntoxygen --version\ntoxygen --reset')
return return
elif arg == '--configure': elif arg == '--configure':
configure() configure()
@ -492,6 +490,9 @@ def main():
elif arg == '--clean': elif arg == '--clean':
clean() clean()
return return
elif arg == '--reset':
reset()
return
else: else:
toxygen = Toxygen(arg) toxygen = Toxygen(arg)
toxygen.main() toxygen.main()

View File

@ -1,13 +1,15 @@
from menu import * from menu import *
from profile import * from profile import *
from list_items import * from list_items import *
from widgets import MultilineEdit, LineEdit, ComboBox from widgets import MultilineEdit, ComboBox
import plugin_support import plugin_support
from mainscreen_widgets import * from mainscreen_widgets import *
import settings import settings
import platform
import toxes
class MainWindow(QtGui.QMainWindow, Singleton): class MainWindow(QtWidgets.QMainWindow, Singleton):
def __init__(self, tox, reset, tray): def __init__(self, tox, reset, tray):
super().__init__() super().__init__()
@ -21,7 +23,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
self.ws = WelcomeScreen() self.ws = WelcomeScreen()
def setup_menu(self, Form): def setup_menu(self, Form):
box = QtGui.QHBoxLayout() box = QtWidgets.QHBoxLayout()
box.setContentsMargins(0, 0, 0, 0) box.setContentsMargins(0, 0, 0, 0)
box.setAlignment(QtCore.Qt.AlignLeft) box.setAlignment(QtCore.Qt.AlignLeft)
self.profile_button = MainMenuButton(Form) self.profile_button = MainMenuButton(Form)
@ -34,36 +36,38 @@ class MainWindow(QtGui.QMainWindow, Singleton):
box.addWidget(self.about_button) box.addWidget(self.about_button)
box.setSpacing(0) box.setSpacing(0)
self.menuProfile = QtGui.QMenu() self.menuProfile = QtWidgets.QMenu()
self.menuProfile.setObjectName("menuProfile") self.menuProfile.setObjectName("menuProfile")
self.menuSettings = QtGui.QMenu() self.menuSettings = QtWidgets.QMenu()
self.menuSettings.setObjectName("menuSettings") self.menuSettings.setObjectName("menuSettings")
self.menuPlugins = QtGui.QMenu() self.menuPlugins = QtWidgets.QMenu()
self.menuPlugins.setObjectName("menuPlugins") self.menuPlugins.setObjectName("menuPlugins")
self.menuAbout = QtGui.QMenu() self.menuAbout = QtWidgets.QMenu()
self.menuAbout.setObjectName("menuAbout") self.menuAbout.setObjectName("menuAbout")
self.actionAdd_friend = QtGui.QAction(Form) self.actionAdd_friend = QtWidgets.QAction(Form)
self.actionAdd_friend.setObjectName("actionAdd_friend") self.actionAdd_friend.setObjectName("actionAdd_friend")
self.actionprofilesettings = QtGui.QAction(Form) self.actionprofilesettings = QtWidgets.QAction(Form)
self.actionprofilesettings.setObjectName("actionprofilesettings") self.actionprofilesettings.setObjectName("actionprofilesettings")
self.actionPrivacy_settings = QtGui.QAction(Form) self.actionPrivacy_settings = QtWidgets.QAction(Form)
self.actionPrivacy_settings.setObjectName("actionPrivacy_settings") self.actionPrivacy_settings.setObjectName("actionPrivacy_settings")
self.actionInterface_settings = QtGui.QAction(Form) self.actionInterface_settings = QtWidgets.QAction(Form)
self.actionInterface_settings.setObjectName("actionInterface_settings") self.actionInterface_settings.setObjectName("actionInterface_settings")
self.actionNotifications = QtGui.QAction(Form) self.actionNotifications = QtWidgets.QAction(Form)
self.actionNotifications.setObjectName("actionNotifications") self.actionNotifications.setObjectName("actionNotifications")
self.actionNetwork = QtGui.QAction(Form) self.actionNetwork = QtWidgets.QAction(Form)
self.actionNetwork.setObjectName("actionNetwork") self.actionNetwork.setObjectName("actionNetwork")
self.actionAbout_program = QtGui.QAction(Form) self.actionAbout_program = QtWidgets.QAction(Form)
self.actionAbout_program.setObjectName("actionAbout_program") self.actionAbout_program.setObjectName("actionAbout_program")
self.updateSettings = QtGui.QAction(Form) self.updateSettings = QtWidgets.QAction(Form)
self.actionSettings = QtGui.QAction(Form) self.actionSettings = QtWidgets.QAction(Form)
self.actionSettings.setObjectName("actionSettings") self.actionSettings.setObjectName("actionSettings")
self.audioSettings = QtGui.QAction(Form) self.audioSettings = QtWidgets.QAction(Form)
self.pluginData = QtGui.QAction(Form) self.videoSettings = QtWidgets.QAction(Form)
self.importPlugin = QtGui.QAction(Form) self.pluginData = QtWidgets.QAction(Form)
self.lockApp = QtGui.QAction(Form) self.importPlugin = QtWidgets.QAction(Form)
self.reloadPlugins = QtWidgets.QAction(Form)
self.lockApp = QtWidgets.QAction(Form)
self.menuProfile.addAction(self.actionAdd_friend) self.menuProfile.addAction(self.actionAdd_friend)
self.menuProfile.addAction(self.actionSettings) self.menuProfile.addAction(self.actionSettings)
self.menuProfile.addAction(self.lockApp) self.menuProfile.addAction(self.lockApp)
@ -72,9 +76,11 @@ class MainWindow(QtGui.QMainWindow, Singleton):
self.menuSettings.addAction(self.actionNotifications) self.menuSettings.addAction(self.actionNotifications)
self.menuSettings.addAction(self.actionNetwork) self.menuSettings.addAction(self.actionNetwork)
self.menuSettings.addAction(self.audioSettings) self.menuSettings.addAction(self.audioSettings)
self.menuSettings.addAction(self.videoSettings)
self.menuSettings.addAction(self.updateSettings) self.menuSettings.addAction(self.updateSettings)
self.menuPlugins.addAction(self.pluginData) self.menuPlugins.addAction(self.pluginData)
self.menuPlugins.addAction(self.importPlugin) self.menuPlugins.addAction(self.importPlugin)
self.menuPlugins.addAction(self.reloadPlugins)
self.menuAbout.addAction(self.actionAbout_program) self.menuAbout.addAction(self.actionAbout_program)
self.profile_button.setMenu(self.menuProfile) self.profile_button.setMenu(self.menuProfile)
@ -90,10 +96,12 @@ class MainWindow(QtGui.QMainWindow, Singleton):
self.actionInterface_settings.triggered.connect(self.interface_settings) self.actionInterface_settings.triggered.connect(self.interface_settings)
self.actionNotifications.triggered.connect(self.notification_settings) self.actionNotifications.triggered.connect(self.notification_settings)
self.audioSettings.triggered.connect(self.audio_settings) self.audioSettings.triggered.connect(self.audio_settings)
self.videoSettings.triggered.connect(self.video_settings)
self.updateSettings.triggered.connect(self.update_settings) self.updateSettings.triggered.connect(self.update_settings)
self.pluginData.triggered.connect(self.plugins_menu) self.pluginData.triggered.connect(self.plugins_menu)
self.lockApp.triggered.connect(self.lock_app) self.lockApp.triggered.connect(self.lock_app)
self.importPlugin.triggered.connect(self.import_plugin) self.importPlugin.triggered.connect(self.import_plugin)
self.reloadPlugins.triggered.connect(self.reload_plugins)
Form.setLayout(box) Form.setLayout(box)
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
@ -108,36 +116,38 @@ class MainWindow(QtGui.QMainWindow, Singleton):
return super(MainWindow, self).event(event) return super(MainWindow, self).event(event)
def retranslateUi(self): def retranslateUi(self):
self.lockApp.setText(QtGui.QApplication.translate("MainWindow", "Lock", None, QtGui.QApplication.UnicodeUTF8)) self.lockApp.setText(QtWidgets.QApplication.translate("MainWindow", "Lock"))
self.plugins_button.setText(QtGui.QApplication.translate("MainWindow", "Plugins", None, QtGui.QApplication.UnicodeUTF8)) self.plugins_button.setText(QtWidgets.QApplication.translate("MainWindow", "Plugins"))
self.pluginData.setText(QtGui.QApplication.translate("MainWindow", "List of plugins", None, QtGui.QApplication.UnicodeUTF8)) self.pluginData.setText(QtWidgets.QApplication.translate("MainWindow", "List of plugins"))
self.profile_button.setText(QtGui.QApplication.translate("MainWindow", "Profile", None, QtGui.QApplication.UnicodeUTF8)) self.profile_button.setText(QtWidgets.QApplication.translate("MainWindow", "Profile"))
self.settings_button.setText(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8)) self.settings_button.setText(QtWidgets.QApplication.translate("MainWindow", "Settings"))
self.about_button.setText(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8)) self.about_button.setText(QtWidgets.QApplication.translate("MainWindow", "About"))
self.actionAdd_friend.setText(QtGui.QApplication.translate("MainWindow", "Add contact", None, QtGui.QApplication.UnicodeUTF8)) self.actionAdd_friend.setText(QtWidgets.QApplication.translate("MainWindow", "Add contact"))
self.actionprofilesettings.setText(QtGui.QApplication.translate("MainWindow", "Profile", None, QtGui.QApplication.UnicodeUTF8)) self.actionprofilesettings.setText(QtWidgets.QApplication.translate("MainWindow", "Profile"))
self.actionPrivacy_settings.setText(QtGui.QApplication.translate("MainWindow", "Privacy", None, QtGui.QApplication.UnicodeUTF8)) self.actionPrivacy_settings.setText(QtWidgets.QApplication.translate("MainWindow", "Privacy"))
self.actionInterface_settings.setText(QtGui.QApplication.translate("MainWindow", "Interface", None, QtGui.QApplication.UnicodeUTF8)) self.actionInterface_settings.setText(QtWidgets.QApplication.translate("MainWindow", "Interface"))
self.actionNotifications.setText(QtGui.QApplication.translate("MainWindow", "Notifications", None, QtGui.QApplication.UnicodeUTF8)) self.actionNotifications.setText(QtWidgets.QApplication.translate("MainWindow", "Notifications"))
self.actionNetwork.setText(QtGui.QApplication.translate("MainWindow", "Network", None, QtGui.QApplication.UnicodeUTF8)) self.actionNetwork.setText(QtWidgets.QApplication.translate("MainWindow", "Network"))
self.actionAbout_program.setText(QtGui.QApplication.translate("MainWindow", "About program", None, QtGui.QApplication.UnicodeUTF8)) self.actionAbout_program.setText(QtWidgets.QApplication.translate("MainWindow", "About program"))
self.actionSettings.setText(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8)) self.actionSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Settings"))
self.audioSettings.setText(QtGui.QApplication.translate("MainWindow", "Audio", None, QtGui.QApplication.UnicodeUTF8)) self.audioSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Audio"))
self.updateSettings.setText(QtGui.QApplication.translate("MainWindow", "Updates", None, QtGui.QApplication.UnicodeUTF8)) self.videoSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Video"))
self.contact_name.setPlaceholderText(QtGui.QApplication.translate("MainWindow", "Search", None, QtGui.QApplication.UnicodeUTF8)) self.updateSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Updates"))
self.sendMessageButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Send message", None, QtGui.QApplication.UnicodeUTF8)) self.contact_name.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "Search"))
self.callButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Start audio call with friend", None, QtGui.QApplication.UnicodeUTF8)) self.sendMessageButton.setToolTip(QtWidgets.QApplication.translate("MainWindow", "Send message"))
self.callButton.setToolTip(QtWidgets.QApplication.translate("MainWindow", "Start audio call with friend"))
self.online_contacts.clear() self.online_contacts.clear()
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "All", None, QtGui.QApplication.UnicodeUTF8)) self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "All"))
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online", None, QtGui.QApplication.UnicodeUTF8)) self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online"))
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online first", None, QtGui.QApplication.UnicodeUTF8)) self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online first"))
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Name", None, QtGui.QApplication.UnicodeUTF8)) self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Name"))
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online and by name", None, QtGui.QApplication.UnicodeUTF8)) self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online and by name"))
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online first and by name", None, QtGui.QApplication.UnicodeUTF8)) self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online first and by name"))
ind = Settings.get_instance()['sorting'] ind = Settings.get_instance()['sorting']
d = {0: 0, 1: 1, 2: 2, 3: 4, 1 | 4: 4, 2 | 4: 5} d = {0: 0, 1: 1, 2: 2, 3: 4, 1 | 4: 4, 2 | 4: 5}
self.online_contacts.setCurrentIndex(d[ind]) self.online_contacts.setCurrentIndex(d[ind])
self.importPlugin.setText(QtGui.QApplication.translate("MainWindow", "Import plugin", None, QtGui.QApplication.UnicodeUTF8)) self.importPlugin.setText(QtWidgets.QApplication.translate("MainWindow", "Import plugin"))
self.reloadPlugins.setText(QtWidgets.QApplication.translate("MainWindow", "Reload plugins"))
def setup_right_bottom(self, Form): def setup_right_bottom(self, Form):
Form.resize(650, 60) Form.resize(650, 60)
@ -149,7 +159,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
font.setFamily(settings.Settings.get_instance()['font']) font.setFamily(settings.Settings.get_instance()['font'])
self.messageEdit.setFont(font) self.messageEdit.setFont(font)
self.sendMessageButton = QtGui.QPushButton(Form) self.sendMessageButton = QtWidgets.QPushButton(Form)
self.sendMessageButton.setGeometry(QtCore.QRect(565, 3, 60, 55)) self.sendMessageButton.setGeometry(QtCore.QRect(565, 3, 60, 55))
self.sendMessageButton.setObjectName("sendMessageButton") self.sendMessageButton.setObjectName("sendMessageButton")
@ -172,7 +182,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
def setup_left_center_menu(self, Form): def setup_left_center_menu(self, Form):
Form.resize(270, 25) Form.resize(270, 25)
self.search_label = QtGui.QLabel(Form) self.search_label = QtWidgets.QLabel(Form)
self.search_label.setGeometry(QtCore.QRect(3, 2, 20, 20)) self.search_label.setGeometry(QtCore.QRect(3, 2, 20, 20))
pixmap = QtGui.QPixmap() pixmap = QtGui.QPixmap()
pixmap.load(curr_directory() + '/images/search.png') pixmap.load(curr_directory() + '/images/search.png')
@ -196,7 +206,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
Form.setMinimumSize(QtCore.QSize(270, 75)) Form.setMinimumSize(QtCore.QSize(270, 75))
Form.setMaximumSize(QtCore.QSize(270, 75)) Form.setMaximumSize(QtCore.QSize(270, 75))
Form.setBaseSize(QtCore.QSize(270, 75)) Form.setBaseSize(QtCore.QSize(270, 75))
self.avatar_label = Form.avatar_label = QtGui.QLabel(Form) self.avatar_label = Form.avatar_label = QtWidgets.QLabel(Form)
self.avatar_label.setGeometry(QtCore.QRect(5, 5, 64, 64)) self.avatar_label.setGeometry(QtCore.QRect(5, 5, 64, 64))
self.avatar_label.setScaledContents(False) self.avatar_label.setScaledContents(False)
self.avatar_label.setAlignment(QtCore.Qt.AlignCenter) self.avatar_label.setAlignment(QtCore.Qt.AlignCenter)
@ -224,7 +234,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
def setup_right_top(self, Form): def setup_right_top(self, Form):
Form.resize(650, 75) Form.resize(650, 75)
self.account_avatar = QtGui.QLabel(Form) self.account_avatar = QtWidgets.QLabel(Form)
self.account_avatar.setGeometry(QtCore.QRect(10, 5, 64, 64)) self.account_avatar.setGeometry(QtCore.QRect(10, 5, 64, 64))
self.account_avatar.setScaledContents(False) self.account_avatar.setScaledContents(False)
self.account_name = DataLabel(Form) self.account_name = DataLabel(Form)
@ -243,16 +253,16 @@ class MainWindow(QtGui.QMainWindow, Singleton):
font.setBold(False) font.setBold(False)
self.account_status.setFont(font) self.account_status.setFont(font)
self.account_status.setObjectName("account_status") self.account_status.setObjectName("account_status")
self.callButton = QtGui.QPushButton(Form) self.callButton = QtWidgets.QPushButton(Form)
self.callButton.setGeometry(QtCore.QRect(550, 5, 50, 50)) self.callButton.setGeometry(QtCore.QRect(550, 5, 50, 50))
self.callButton.setObjectName("callButton") self.callButton.setObjectName("callButton")
self.callButton.clicked.connect(lambda: self.profile.call_click(True)) self.callButton.clicked.connect(lambda: self.profile.call_click(True))
self.videocallButton = QtGui.QPushButton(Form) self.videocallButton = QtWidgets.QPushButton(Form)
self.videocallButton.setGeometry(QtCore.QRect(550, 5, 50, 50)) self.videocallButton.setGeometry(QtCore.QRect(550, 5, 50, 50))
self.videocallButton.setObjectName("videocallButton") self.videocallButton.setObjectName("videocallButton")
self.videocallButton.clicked.connect(lambda: self.profile.call_click(True, True)) self.videocallButton.clicked.connect(lambda: self.profile.call_click(True, True))
self.update_call_state('call') self.update_call_state('call')
self.typing = QtGui.QLabel(Form) self.typing = QtWidgets.QLabel(Form)
self.typing.setGeometry(QtCore.QRect(500, 25, 50, 30)) self.typing.setGeometry(QtCore.QRect(500, 25, 50, 30))
pixmap = QtGui.QPixmap(QtCore.QSize(50, 30)) pixmap = QtGui.QPixmap(QtCore.QSize(50, 30))
pixmap.load(curr_directory() + '/images/typing.png') pixmap.load(curr_directory() + '/images/typing.png')
@ -262,20 +272,19 @@ class MainWindow(QtGui.QMainWindow, Singleton):
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def setup_left_center(self, widget): def setup_left_center(self, widget):
self.friends_list = QtGui.QListWidget(widget) self.friends_list = QtWidgets.QListWidget(widget)
self.friends_list.setObjectName("friends_list") self.friends_list.setObjectName("friends_list")
self.friends_list.setGeometry(0, 0, 270, 310) self.friends_list.setGeometry(0, 0, 270, 310)
self.friends_list.clicked.connect(self.friend_click) self.friends_list.clicked.connect(self.friend_click)
self.friends_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.friends_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.friends_list.connect(self.friends_list, QtCore.SIGNAL("customContextMenuRequested(QPoint)"), self.friends_list.customContextMenuRequested.connect(self.friend_right_click)
self.friend_right_click) self.friends_list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.friends_list.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
self.friends_list.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) self.friends_list.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.friends_list.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.friends_list.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.friends_list.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu) self.friends_list.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu)
def setup_right_center(self, widget): def setup_right_center(self, widget):
self.messages = QtGui.QListWidget(widget) self.messages = QtWidgets.QListWidget(widget)
self.messages.setGeometry(0, 0, 620, 310) self.messages.setGeometry(0, 0, 620, 310)
self.messages.setObjectName("messages") self.messages.setObjectName("messages")
self.messages.setSpacing(1) self.messages.setSpacing(1)
@ -289,8 +298,8 @@ class MainWindow(QtGui.QMainWindow, Singleton):
self.profile.load_history() self.profile.load_history()
self.messages.verticalScrollBar().setValue(1) self.messages.verticalScrollBar().setValue(1)
self.messages.verticalScrollBar().valueChanged.connect(load) self.messages.verticalScrollBar().valueChanged.connect(load)
self.messages.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel) self.messages.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.messages.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) self.messages.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
def initUI(self, tox): def initUI(self, tox):
self.setMinimumSize(920, 500) self.setMinimumSize(920, 500)
@ -298,15 +307,15 @@ class MainWindow(QtGui.QMainWindow, Singleton):
self.setGeometry(s['x'], s['y'], s['width'], s['height']) self.setGeometry(s['x'], s['y'], s['width'], s['height'])
self.setWindowTitle('Toxygen') self.setWindowTitle('Toxygen')
os.chdir(curr_directory() + '/images/') os.chdir(curr_directory() + '/images/')
menu = QtGui.QWidget() menu = QtWidgets.QWidget()
main = QtGui.QWidget() main = QtWidgets.QWidget()
grid = QtGui.QGridLayout() grid = QtWidgets.QGridLayout()
search = QtGui.QWidget() search = QtWidgets.QWidget()
name = QtGui.QWidget() name = QtWidgets.QWidget()
info = QtGui.QWidget() info = QtWidgets.QWidget()
main_list = QtGui.QWidget() main_list = QtWidgets.QWidget()
messages = QtGui.QWidget() messages = QtWidgets.QWidget()
message_buttons = QtGui.QWidget() message_buttons = QtWidgets.QWidget()
self.setup_left_center_menu(search) self.setup_left_center_menu(search)
self.setup_left_top(name) self.setup_left_top(name)
self.setup_right_center(messages) self.setup_right_center(messages)
@ -363,9 +372,9 @@ class MainWindow(QtGui.QMainWindow, Singleton):
s['width'] = self.width() s['width'] = self.width()
s['height'] = self.height() s['height'] = self.height()
s.save() s.save()
QtGui.QApplication.closeAllWindows() QtWidgets.QApplication.closeAllWindows()
event.accept() event.accept()
elif QtGui.QSystemTrayIcon.isSystemTrayAvailable(): elif QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
event.ignore() event.ignore()
self.hide() self.hide()
@ -374,8 +383,12 @@ class MainWindow(QtGui.QMainWindow, Singleton):
self.close() self.close()
def resizeEvent(self, *args, **kwargs): def resizeEvent(self, *args, **kwargs):
self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 159) if platform.system() == 'Windows':
self.friends_list.setGeometry(0, 0, 270, self.height() - 129) self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155)
self.friends_list.setGeometry(0, 0, 270, self.height() - 125)
else:
self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 159)
self.friends_list.setGeometry(0, 0, 270, self.height() - 129)
self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 10, 50, 50)) self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 10, 50, 50))
self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 10, 50, 50)) self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 10, 50, 50))
@ -391,16 +404,18 @@ class MainWindow(QtGui.QMainWindow, Singleton):
self.profile.update() self.profile.update()
def keyPressEvent(self, event): def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Escape and QtGui.QSystemTrayIcon.isSystemTrayAvailable(): if event.key() == QtCore.Qt.Key_Escape and QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
self.hide() self.hide()
elif event.key() == QtCore.Qt.Key_C and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes(): elif event.key() == QtCore.Qt.Key_C and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
rows = list(map(lambda x: self.messages.row(x), self.messages.selectedItems())) rows = list(map(lambda x: self.messages.row(x), self.messages.selectedItems()))
indexes = (rows[0] - self.messages.count(), rows[-1] - self.messages.count()) indexes = (rows[0] - self.messages.count(), rows[-1] - self.messages.count())
s = self.profile.export_history(self.profile.active_friend, True, indexes) s = self.profile.export_history(self.profile.active_friend, True, indexes)
clipboard = QtGui.QApplication.clipboard() clipboard = QtWidgets.QApplication.clipboard()
clipboard.setText(s) clipboard.setText(s)
elif event.key() == QtCore.Qt.Key_Z and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes(): elif event.key() == QtCore.Qt.Key_Z and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
self.messages.clearSelection() self.messages.clearSelection()
elif event.key() == QtCore.Qt.Key_F and event.modifiers() & QtCore.Qt.ControlModifier:
self.show_search_field()
else: else:
super(MainWindow, self).keyPressEvent(event) super(MainWindow, self).keyPressEvent(event)
@ -410,9 +425,9 @@ class MainWindow(QtGui.QMainWindow, Singleton):
def about_program(self): def about_program(self):
import util import util
msgBox = QtGui.QMessageBox() msgBox = QtWidgets.QMessageBox()
msgBox.setWindowTitle(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8)) msgBox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "About"))
text = (QtGui.QApplication.translate("MainWindow", 'Toxygen is Tox client written on Python.\nVersion: ', None, QtGui.QApplication.UnicodeUTF8)) text = (QtWidgets.QApplication.translate("MainWindow", 'Toxygen is Tox client written on Python.\nVersion: '))
msgBox.setText(text + util.program_version + '\nGitHub: https://github.com/toxygen-project/toxygen/') msgBox.setText(text + util.program_version + '\nGitHub: https://github.com/toxygen-project/toxygen/')
msgBox.exec_() msgBox.exec_()
@ -448,41 +463,46 @@ class MainWindow(QtGui.QMainWindow, Singleton):
self.audio_s = AudioSettings() self.audio_s = AudioSettings()
self.audio_s.show() self.audio_s.show()
def video_settings(self):
self.video_s = VideoSettings()
self.video_s.show()
def update_settings(self): def update_settings(self):
self.update_s = UpdateSettings() self.update_s = UpdateSettings()
self.update_s.show() self.update_s.show()
def reload_plugins(self):
plugin_loader = plugin_support.PluginLoader.get_instance()
if plugin_loader is not None:
plugin_loader.reload()
def import_plugin(self): def import_plugin(self):
import util import util
directory = QtGui.QFileDialog.getExistingDirectory(self, directory = QtWidgets.QFileDialog.getExistingDirectory(self,
QtGui.QApplication.translate("MainWindow", 'Choose folder with plugin', QtWidgets.QApplication.translate("MainWindow", 'Choose folder with plugin'),
None,
QtGui.QApplication.UnicodeUTF8),
util.curr_directory(), util.curr_directory(),
QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog) QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
if directory: if directory:
src = directory + '/' src = directory + '/'
dest = curr_directory() + '/plugins/' dest = curr_directory() + '/plugins/'
util.copy(src, dest) util.copy(src, dest)
msgBox = QtGui.QMessageBox() msgBox = QtWidgets.QMessageBox()
msgBox.setWindowTitle( msgBox.setWindowTitle(
QtGui.QApplication.translate("MainWindow", "Restart Toxygen", None, QtGui.QApplication.UnicodeUTF8)) QtWidgets.QApplication.translate("MainWindow", "Restart Toxygen"))
msgBox.setText( msgBox.setText(
QtGui.QApplication.translate("MainWindow", 'Plugin will be loaded after restart', None, QtWidgets.QApplication.translate("MainWindow", 'Plugin will be loaded after restart'))
QtGui.QApplication.UnicodeUTF8))
msgBox.exec_() msgBox.exec_()
def lock_app(self): def lock_app(self):
if toxencryptsave.ToxEncryptSave.get_instance().has_password(): if toxes.ToxES.get_instance().has_password():
Settings.get_instance().locked = True Settings.get_instance().locked = True
self.hide() self.hide()
else: else:
msgBox = QtGui.QMessageBox() msgBox = QtWidgets.QMessageBox()
msgBox.setWindowTitle( msgBox.setWindowTitle(
QtGui.QApplication.translate("MainWindow", "Cannot lock app", None, QtGui.QApplication.UnicodeUTF8)) QtWidgets.QApplication.translate("MainWindow", "Cannot lock app"))
msgBox.setText( msgBox.setText(
QtGui.QApplication.translate("MainWindow", 'Error. Profile password is not set.', None, QtWidgets.QApplication.translate("MainWindow", 'Error. Profile password is not set.'))
QtGui.QApplication.UnicodeUTF8))
msgBox.exec_() msgBox.exec_()
def show_menu(self): def show_menu(self):
@ -505,8 +525,8 @@ class MainWindow(QtGui.QMainWindow, Singleton):
def send_file(self): def send_file(self):
self.menu.hide() self.menu.hide()
if self.profile.active_friend + 1: if self.profile.active_friend + 1:
choose = QtGui.QApplication.translate("MainWindow", 'Choose file', None, QtGui.QApplication.UnicodeUTF8) choose = QtWidgets.QApplication.translate("MainWindow", 'Choose file')
name = QtGui.QFileDialog.getOpenFileName(self, choose, options=QtGui.QFileDialog.DontUseNativeDialog) name = QtWidgets.QFileDialog.getOpenFileName(self, choose, options=QtWidgets.QFileDialog.DontUseNativeDialog)
if name[0]: if name[0]:
self.profile.send_file(name[0]) self.profile.send_file(name[0])
@ -547,14 +567,15 @@ class MainWindow(QtGui.QMainWindow, Singleton):
def call_finished(self): def call_finished(self):
self.update_call_state('call') self.update_call_state('call')
def update_call_state(self, fl): def update_call_state(self, state):
# TODO: do smth with video call button
os.chdir(curr_directory() + '/images/') os.chdir(curr_directory() + '/images/')
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(fl))
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(state))
icon = QtGui.QIcon(pixmap) icon = QtGui.QIcon(pixmap)
self.callButton.setIcon(icon) self.callButton.setIcon(icon)
self.callButton.setIconSize(QtCore.QSize(50, 50)) self.callButton.setIconSize(QtCore.QSize(50, 50))
pixmap = QtGui.QPixmap(curr_directory() + '/images/videocall.png')
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}_video.png'.format(state))
icon = QtGui.QIcon(pixmap) icon = QtGui.QIcon(pixmap)
self.videocallButton.setIcon(icon) self.videocallButton.setIcon(icon)
self.videocallButton.setIconSize(QtCore.QSize(35, 35)) self.videocallButton.setIconSize(QtCore.QSize(35, 35))
@ -571,40 +592,43 @@ class MainWindow(QtGui.QMainWindow, Singleton):
return return
settings = Settings.get_instance() settings = Settings.get_instance()
allowed = friend.tox_id in settings['auto_accept_from_friends'] allowed = friend.tox_id in settings['auto_accept_from_friends']
auto = QtGui.QApplication.translate("MainWindow", 'Disallow auto accept', None, QtGui.QApplication.UnicodeUTF8) if allowed else QtGui.QApplication.translate("MainWindow", 'Allow auto accept', None, QtGui.QApplication.UnicodeUTF8) auto = QtWidgets.QApplication.translate("MainWindow", 'Disallow auto accept') if allowed else QtWidgets.QApplication.translate("MainWindow", 'Allow auto accept')
if item is not None: if item is not None:
self.listMenu = QtGui.QMenu() self.listMenu = QtWidgets.QMenu()
set_alias_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Set alias', None, QtGui.QApplication.UnicodeUTF8)) set_alias_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Set alias'))
history_menu = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Chat history', None, QtGui.QApplication.UnicodeUTF8)) history_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Chat history'))
clear_history_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8)) clear_history_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Clear history'))
export_to_text_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Export as text', None, QtGui.QApplication.UnicodeUTF8)) export_to_text_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Export as text'))
export_to_html_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Export as HTML', None, QtGui.QApplication.UnicodeUTF8)) export_to_html_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Export as HTML'))
copy_menu = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Copy', None, QtGui.QApplication.UnicodeUTF8)) copy_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Copy'))
copy_name_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Name', None, QtGui.QApplication.UnicodeUTF8)) copy_name_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Name'))
copy_status_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Status message', None, QtGui.QApplication.UnicodeUTF8)) copy_status_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Status message'))
copy_key_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Public key', None, QtGui.QApplication.UnicodeUTF8)) copy_key_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Public key'))
auto_accept_item = self.listMenu.addAction(auto) auto_accept_item = self.listMenu.addAction(auto)
remove_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Remove friend', None, QtGui.QApplication.UnicodeUTF8)) remove_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Remove friend'))
notes_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Notes', None, QtGui.QApplication.UnicodeUTF8)) block_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Block friend'))
notes_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Notes'))
submenu = plugin_support.PluginLoader.get_instance().get_menu(self.listMenu, num) plugins_loader = plugin_support.PluginLoader.get_instance()
if len(submenu): if plugins_loader is not None:
plug = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8)) submenu = plugins_loader.get_menu(self.listMenu, num)
plug.addActions(submenu) if len(submenu):
self.connect(set_alias_item, QtCore.SIGNAL("triggered()"), lambda: self.set_alias(num)) plug = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins'))
self.connect(remove_item, QtCore.SIGNAL("triggered()"), lambda: self.remove_friend(num)) plug.addActions(submenu)
self.connect(copy_key_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_friend_key(num)) set_alias_item.triggered.connect(lambda: self.set_alias(num))
self.connect(clear_history_item, QtCore.SIGNAL("triggered()"), lambda: self.clear_history(num)) remove_item.triggered.connect(lambda: self.remove_friend(num))
self.connect(auto_accept_item, QtCore.SIGNAL("triggered()"), lambda: self.auto_accept(num, not allowed)) block_item.triggered.connect(lambda: self.block_friend(num))
self.connect(notes_item, QtCore.SIGNAL("triggered()"), lambda: self.show_note(friend)) copy_key_item.triggered.connect(lambda: self.copy_friend_key(num))
self.connect(copy_name_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_name(friend)) clear_history_item.triggered.connect(lambda: self.clear_history(num))
self.connect(copy_status_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_status(friend)) auto_accept_item.triggered.connect(lambda: self.auto_accept(num, not allowed))
self.connect(export_to_text_item, QtCore.SIGNAL("triggered()"), lambda: self.export_history(num)) notes_item.triggered.connect(lambda: self.show_note(friend))
self.connect(export_to_html_item, QtCore.SIGNAL("triggered()"), copy_name_item.triggered.connect(lambda: self.copy_name(friend))
lambda: self.export_history(num, False)) copy_status_item.triggered.connect(lambda: self.copy_status(friend))
export_to_text_item.triggered.connect(lambda: self.export_history(num))
export_to_html_item.triggered.connect(lambda: self.export_history(num, False))
parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0)) parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0))
self.listMenu.move(parent_position + pos) self.listMenu.move(parent_position + pos)
self.listMenu.show() self.listMenu.show()
@ -612,7 +636,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
def show_note(self, friend): def show_note(self, friend):
s = Settings.get_instance() s = Settings.get_instance()
note = s['notes'][friend.tox_id] if friend.tox_id in s['notes'] else '' note = s['notes'][friend.tox_id] if friend.tox_id in s['notes'] else ''
user = QtGui.QApplication.translate("MainWindow", 'Notes about user', None, QtGui.QApplication.UnicodeUTF8) user = QtWidgets.QApplication.translate("MainWindow", 'Notes about user')
user = '{} {}'.format(user, friend.name) user = '{} {}'.format(user, friend.name)
def save_note(text): def save_note(text):
@ -626,12 +650,11 @@ class MainWindow(QtGui.QMainWindow, Singleton):
def export_history(self, num, as_text=True): def export_history(self, num, as_text=True):
s = self.profile.export_history(num, as_text) s = self.profile.export_history(num, as_text)
directory = QtGui.QFileDialog.getExistingDirectory(None, directory = QtWidgets.QFileDialog.getExistingDirectory(None,
QtGui.QApplication.translate("MainWindow", 'Choose folder', QtWidgets.QApplication.translate("MainWindow",
None, 'Choose folder'),
QtGui.QApplication.UnicodeUTF8),
curr_directory(), curr_directory(),
QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog) QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
if directory: if directory:
name = 'exported_history_{}.{}'.format(convert_time(time.time()), 'txt' if as_text else 'html') name = 'exported_history_{}.{}'.format(convert_time(time.time()), 'txt' if as_text else 'html')
@ -644,17 +667,21 @@ class MainWindow(QtGui.QMainWindow, Singleton):
def remove_friend(self, num): def remove_friend(self, num):
self.profile.delete_friend(num) self.profile.delete_friend(num)
def block_friend(self, num):
friend = self.profile.get_friend(num)
self.profile.block_user(friend.tox_id)
def copy_friend_key(self, num): def copy_friend_key(self, num):
tox_id = self.profile.friend_public_key(num) tox_id = self.profile.friend_public_key(num)
clipboard = QtGui.QApplication.clipboard() clipboard = QtWidgets.QApplication.clipboard()
clipboard.setText(tox_id) clipboard.setText(tox_id)
def copy_name(self, friend): def copy_name(self, friend):
clipboard = QtGui.QApplication.clipboard() clipboard = QtWidgets.QApplication.clipboard()
clipboard.setText(friend.name) clipboard.setText(friend.name)
def copy_status(self, friend): def copy_status(self, friend):
clipboard = QtGui.QApplication.clipboard() clipboard = QtWidgets.QApplication.clipboard()
clipboard.setText(friend.status_message) clipboard.setText(friend.status_message)
def clear_history(self, num): def clear_history(self, num):
@ -685,7 +712,22 @@ class MainWindow(QtGui.QMainWindow, Singleton):
else: else:
super(MainWindow, self).mouseReleaseEvent(event) super(MainWindow, self).mouseReleaseEvent(event)
def show(self):
super().show()
self.profile.update()
def filtering(self): def filtering(self):
ind = self.online_contacts.currentIndex() ind = self.online_contacts.currentIndex()
d = {0: 0, 1: 1, 2: 2, 3: 4, 4: 1 | 4, 5: 2 | 4} d = {0: 0, 1: 1, 2: 2, 3: 4, 4: 1 | 4, 5: 2 | 4}
self.profile.filtration_and_sorting(d[ind], self.contact_name.text()) self.profile.filtration_and_sorting(d[ind], self.contact_name.text())
def show_search_field(self):
if hasattr(self, 'search_field') and self.search_field.isVisible():
return
if self.profile.get_curr_friend() is None:
return
self.search_field = SearchScreen(self.messages, self.messages.width(), self.messages.parent())
x, y = self.messages.x(), self.messages.y() + self.messages.height() - 40
self.search_field.setGeometry(x, y, self.messages.width(), 40)
self.messages.setGeometry(x, self.messages.y(), self.messages.width(), self.messages.height() - 40)
self.search_field.show()

View File

@ -1,14 +1,11 @@
try: from PyQt5 import QtCore, QtGui, QtWidgets
from PySide import QtCore, QtGui from widgets import RubberBand, create_menu, QRightClickButton, CenteredWidget, LineEdit
except ImportError:
from PyQt4 import QtCore, QtGui
from widgets import RubberBand, create_menu, QRightClickButton, CenteredWidget
from profile import Profile from profile import Profile
import smileys import smileys
import util import util
class MessageArea(QtGui.QPlainTextEdit): class MessageArea(QtWidgets.QPlainTextEdit):
"""User types messages here""" """User types messages here"""
def __init__(self, parent, form): def __init__(self, parent, form):
@ -20,7 +17,7 @@ class MessageArea(QtGui.QPlainTextEdit):
def keyPressEvent(self, event): def keyPressEvent(self, event):
if event.matches(QtGui.QKeySequence.Paste): if event.matches(QtGui.QKeySequence.Paste):
mimeData = QtGui.QApplication.clipboard().mimeData() mimeData = QtWidgets.QApplication.clipboard().mimeData()
if mimeData.hasUrls(): if mimeData.hasUrls():
for url in mimeData.urls(): for url in mimeData.urls():
self.pasteEvent(url.toString()) self.pasteEvent(url.toString())
@ -67,14 +64,14 @@ class MessageArea(QtGui.QPlainTextEdit):
e.ignore() e.ignore()
def pasteEvent(self, text=None): def pasteEvent(self, text=None):
text = text or QtGui.QApplication.clipboard().text() text = text or QtWidgets.QApplication.clipboard().text()
if text.startswith('file://'): if text.startswith('file://'):
self.parent.profile.send_file(text[7:]) self.parent.profile.send_file(text[7:])
else: else:
self.insertPlainText(text) self.insertPlainText(text)
class ScreenShotWindow(QtGui.QWidget): class ScreenShotWindow(QtWidgets.QWidget):
def __init__(self, parent): def __init__(self, parent):
super(ScreenShotWindow, self).__init__() super(ScreenShotWindow, self).__init__()
@ -84,6 +81,8 @@ class ScreenShotWindow(QtGui.QWidget):
self.showFullScreen() self.showFullScreen()
self.setWindowOpacity(0.5) self.setWindowOpacity(0.5)
self.rubberband = RubberBand() self.rubberband = RubberBand()
self.rubberband.setWindowFlags(self.rubberband.windowFlags() | QtCore.Qt.FramelessWindowHint)
self.rubberband.setAttribute(QtCore.Qt.WA_TranslucentBackground)
def closeEvent(self, *args): def closeEvent(self, *args):
if self.parent.isHidden(): if self.parent.isHidden():
@ -93,7 +92,7 @@ class ScreenShotWindow(QtGui.QWidget):
self.origin = event.pos() self.origin = event.pos()
self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize())) self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize()))
self.rubberband.show() self.rubberband.show()
QtGui.QWidget.mousePressEvent(self, event) QtWidgets.QWidget.mousePressEvent(self, event)
def mouseMoveEvent(self, event): def mouseMoveEvent(self, event):
if self.rubberband.isVisible(): if self.rubberband.isVisible():
@ -109,11 +108,12 @@ class ScreenShotWindow(QtGui.QWidget):
self.rubberband.hide() self.rubberband.hide()
rect = self.rubberband.geometry() rect = self.rubberband.geometry()
if rect.width() and rect.height(): if rect.width() and rect.height():
p = QtGui.QPixmap.grabWindow(QtGui.QApplication.desktop().winId(), screen = QtWidgets.QApplication.primaryScreen()
rect.x() + 4, p = screen.grabWindow(0,
rect.y() + 4, rect.x() + 4,
rect.width() - 8, rect.y() + 4,
rect.height() - 8) rect.width() - 8,
rect.height() - 8)
byte_array = QtCore.QByteArray() byte_array = QtCore.QByteArray()
buffer = QtCore.QBuffer(byte_array) buffer = QtCore.QBuffer(byte_array)
buffer.open(QtCore.QIODevice.WriteOnly) buffer.open(QtCore.QIODevice.WriteOnly)
@ -129,7 +129,7 @@ class ScreenShotWindow(QtGui.QWidget):
super(ScreenShotWindow, self).keyPressEvent(event) super(ScreenShotWindow, self).keyPressEvent(event)
class SmileyWindow(QtGui.QWidget): class SmileyWindow(QtWidgets.QWidget):
""" """
Smiley selection window Smiley selection window
""" """
@ -151,7 +151,7 @@ class SmileyWindow(QtGui.QWidget):
self.radio = [] self.radio = []
self.parent = parent self.parent = parent
for i in range(self.page_count): # buttons with smileys for i in range(self.page_count): # buttons with smileys
elem = QtGui.QRadioButton(self) elem = QtWidgets.QRadioButton(self)
elem.setGeometry(QtCore.QRect(i * 20 + 5, 180, 20, 20)) elem.setGeometry(QtCore.QRect(i * 20 + 5, 180, 20, 20))
elem.clicked.connect(lambda i=i: self.checked(i)) elem.clicked.connect(lambda i=i: self.checked(i))
self.radio.append(elem) self.radio.append(elem)
@ -160,7 +160,7 @@ class SmileyWindow(QtGui.QWidget):
self.setMinimumSize(width, 200) self.setMinimumSize(width, 200)
self.buttons = [] self.buttons = []
for i in range(self.page_size): # pages - radio buttons for i in range(self.page_size): # pages - radio buttons
b = QtGui.QPushButton(self) b = QtWidgets.QPushButton(self)
b.setGeometry(QtCore.QRect((i // 8) * 20 + 5, (i % 8) * 20, 20, 20)) b.setGeometry(QtCore.QRect((i // 8) * 20 + 5, (i % 8) * 20, 20, 20))
b.clicked.connect(lambda i=i: self.clicked(i)) b.clicked.connect(lambda i=i: self.clicked(i))
self.buttons.append(b) self.buttons.append(b)
@ -190,7 +190,7 @@ class SmileyWindow(QtGui.QWidget):
self.close() self.close()
class MenuButton(QtGui.QPushButton): class MenuButton(QtWidgets.QPushButton):
def __init__(self, parent, enter): def __init__(self, parent, enter):
super(MenuButton, self).__init__(parent) super(MenuButton, self).__init__(parent)
@ -201,86 +201,73 @@ class MenuButton(QtGui.QPushButton):
super(MenuButton, self).enterEvent(event) super(MenuButton, self).enterEvent(event)
class DropdownMenu(QtGui.QWidget): class DropdownMenu(QtWidgets.QWidget):
def __init__(self, parent): def __init__(self, parent):
super(DropdownMenu, self).__init__(parent) super(DropdownMenu, self).__init__(parent)
self.installEventFilter(self) self.installEventFilter(self)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.setMaximumSize(180, 120) self.setMaximumSize(120, 120)
self.setMinimumSize(180, 120) self.setMinimumSize(120, 120)
self.screenshotButton = QRightClickButton(self) self.screenshotButton = QRightClickButton(self)
self.screenshotButton.setGeometry(QtCore.QRect(0, 60, 60, 60)) self.screenshotButton.setGeometry(QtCore.QRect(0, 60, 60, 60))
self.screenshotButton.setObjectName("screenshotButton") self.screenshotButton.setObjectName("screenshotButton")
self.fileTransferButton = QtGui.QPushButton(self) self.fileTransferButton = QtWidgets.QPushButton(self)
self.fileTransferButton.setGeometry(QtCore.QRect(60, 60, 60, 60)) self.fileTransferButton.setGeometry(QtCore.QRect(60, 60, 60, 60))
self.fileTransferButton.setObjectName("fileTransferButton") self.fileTransferButton.setObjectName("fileTransferButton")
self.audioMessageButton = QtGui.QPushButton(self) self.smileyButton = QtWidgets.QPushButton(self)
self.audioMessageButton.setGeometry(QtCore.QRect(120, 60, 60, 60))
self.smileyButton = QtGui.QPushButton(self)
self.smileyButton.setGeometry(QtCore.QRect(0, 0, 60, 60)) self.smileyButton.setGeometry(QtCore.QRect(0, 0, 60, 60))
self.videoMessageButton = QtGui.QPushButton(self) self.stickerButton = QtWidgets.QPushButton(self)
self.videoMessageButton.setGeometry(QtCore.QRect(120, 0, 60, 60))
self.stickerButton = QtGui.QPushButton(self)
self.stickerButton.setGeometry(QtCore.QRect(60, 0, 60, 60)) self.stickerButton.setGeometry(QtCore.QRect(60, 0, 60, 60))
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/file.png') pixmap = QtGui.QPixmap(util.curr_directory() + '/images/file.png')
icon = QtGui.QIcon(pixmap) icon = QtGui.QIcon(pixmap)
self.fileTransferButton.setIcon(icon) self.fileTransferButton.setIcon(icon)
self.fileTransferButton.setIconSize(QtCore.QSize(50, 50)) self.fileTransferButton.setIconSize(QtCore.QSize(50, 50))
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/screenshot.png') pixmap = QtGui.QPixmap(util.curr_directory() + '/images/screenshot.png')
icon = QtGui.QIcon(pixmap) icon = QtGui.QIcon(pixmap)
self.screenshotButton.setIcon(icon) self.screenshotButton.setIcon(icon)
self.screenshotButton.setIconSize(QtCore.QSize(50, 60)) self.screenshotButton.setIconSize(QtCore.QSize(50, 60))
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/audio_message.png')
icon = QtGui.QIcon(pixmap)
self.audioMessageButton.setIcon(icon)
self.audioMessageButton.setIconSize(QtCore.QSize(50, 50))
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/smiley.png') pixmap = QtGui.QPixmap(util.curr_directory() + '/images/smiley.png')
icon = QtGui.QIcon(pixmap) icon = QtGui.QIcon(pixmap)
self.smileyButton.setIcon(icon) self.smileyButton.setIcon(icon)
self.smileyButton.setIconSize(QtCore.QSize(50, 50)) self.smileyButton.setIconSize(QtCore.QSize(50, 50))
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/video_message.png')
icon = QtGui.QIcon(pixmap)
self.videoMessageButton.setIcon(icon)
self.videoMessageButton.setIconSize(QtCore.QSize(55, 55))
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/sticker.png') pixmap = QtGui.QPixmap(util.curr_directory() + '/images/sticker.png')
icon = QtGui.QIcon(pixmap) icon = QtGui.QIcon(pixmap)
self.stickerButton.setIcon(icon) self.stickerButton.setIcon(icon)
self.stickerButton.setIconSize(QtCore.QSize(55, 55)) self.stickerButton.setIconSize(QtCore.QSize(55, 55))
self.screenshotButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send screenshot", None, QtGui.QApplication.UnicodeUTF8)) self.screenshotButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send screenshot"))
self.fileTransferButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send file", None, QtGui.QApplication.UnicodeUTF8)) self.fileTransferButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send file"))
self.audioMessageButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send audio message", None, QtGui.QApplication.UnicodeUTF8)) self.smileyButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Add smiley"))
self.videoMessageButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send video message", None, QtGui.QApplication.UnicodeUTF8)) self.stickerButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send sticker"))
self.smileyButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Add smiley", None, QtGui.QApplication.UnicodeUTF8))
self.stickerButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send sticker", None, QtGui.QApplication.UnicodeUTF8))
self.fileTransferButton.clicked.connect(parent.send_file) self.fileTransferButton.clicked.connect(parent.send_file)
self.screenshotButton.clicked.connect(parent.send_screenshot) self.screenshotButton.clicked.connect(parent.send_screenshot)
self.connect(self.screenshotButton, QtCore.SIGNAL("rightClicked()"), lambda: parent.send_screenshot(True)) self.screenshotButton.rightClicked.connect(lambda: parent.send_screenshot(True))
self.smileyButton.clicked.connect(parent.send_smiley) self.smileyButton.clicked.connect(parent.send_smiley)
self.stickerButton.clicked.connect(parent.send_sticker) self.stickerButton.clicked.connect(parent.send_sticker)
def leaveEvent(self, event): def leaveEvent(self, event):
self.close() self.close()
def eventFilter(self, object, event): def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.WindowDeactivate: if event.type() == QtCore.QEvent.WindowDeactivate:
self.close() self.close()
return False return False
class StickerItem(QtGui.QWidget): class StickerItem(QtWidgets.QWidget):
def __init__(self, fl): def __init__(self, fl):
super(StickerItem, self).__init__() super(StickerItem, self).__init__()
self._image_label = QtGui.QLabel(self) self._image_label = QtWidgets.QLabel(self)
self.path = fl self.path = fl
self.pixmap = QtGui.QPixmap() self.pixmap = QtGui.QPixmap()
self.pixmap.load(fl) self.pixmap.load(fl)
@ -290,7 +277,7 @@ class StickerItem(QtGui.QWidget):
self._image_label.setPixmap(self.pixmap) self._image_label.setPixmap(self.pixmap)
class StickerWindow(QtGui.QWidget): class StickerWindow(QtWidgets.QWidget):
"""Sticker selection window""" """Sticker selection window"""
def __init__(self, parent): def __init__(self, parent):
@ -298,16 +285,16 @@ class StickerWindow(QtGui.QWidget):
self.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.setMaximumSize(250, 200) self.setMaximumSize(250, 200)
self.setMinimumSize(250, 200) self.setMinimumSize(250, 200)
self.list = QtGui.QListWidget(self) self.list = QtWidgets.QListWidget(self)
self.list.setGeometry(QtCore.QRect(0, 0, 250, 200)) self.list.setGeometry(QtCore.QRect(0, 0, 250, 200))
self.arr = smileys.sticker_loader() self.arr = smileys.sticker_loader()
for sticker in self.arr: for sticker in self.arr:
item = StickerItem(sticker) item = StickerItem(sticker)
elem = QtGui.QListWidgetItem() elem = QtWidgets.QListWidgetItem()
elem.setSizeHint(QtCore.QSize(250, item.height())) elem.setSizeHint(QtCore.QSize(250, item.height()))
self.list.addItem(elem) self.list.addItem(elem)
self.list.setItemWidget(elem, item) self.list.setItemWidget(elem, item)
self.list.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel) self.list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.list.setSpacing(3) self.list.setSpacing(3)
self.list.clicked.connect(self.click) self.list.clicked.connect(self.click)
self.parent = parent self.parent = parent
@ -329,60 +316,44 @@ class WelcomeScreen(CenteredWidget):
self.setMinimumSize(250, 200) self.setMinimumSize(250, 200)
self.center() self.center()
self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.text = QtGui.QTextBrowser(self) self.text = QtWidgets.QTextBrowser(self)
self.text.setGeometry(QtCore.QRect(0, 0, 250, 170)) self.text.setGeometry(QtCore.QRect(0, 0, 250, 170))
self.text.setOpenExternalLinks(True) self.text.setOpenExternalLinks(True)
self.checkbox = QtGui.QCheckBox(self) self.checkbox = QtWidgets.QCheckBox(self)
self.checkbox.setGeometry(QtCore.QRect(5, 170, 240, 30)) self.checkbox.setGeometry(QtCore.QRect(5, 170, 240, 30))
self.checkbox.setText(QtGui.QApplication.translate('WelcomeScreen', "Don't show again", self.checkbox.setText(QtWidgets.QApplication.translate('WelcomeScreen', "Don't show again"))
None, QtGui.QApplication.UnicodeUTF8)) self.setWindowTitle(QtWidgets.QApplication.translate('WelcomeScreen', 'Tip of the day'))
self.setWindowTitle(QtGui.QApplication.translate('WelcomeScreen', 'Tip of the day',
None, QtGui.QApplication.UnicodeUTF8))
import random import random
num = random.randint(0, 10) num = random.randint(0, 10)
if num == 0: if num == 0:
text = QtGui.QApplication.translate('WelcomeScreen', 'Press Esc if you want hide app to tray.', text = QtWidgets.QApplication.translate('WelcomeScreen', 'Press Esc if you want hide app to tray.')
None, QtGui.QApplication.UnicodeUTF8)
elif num == 1: elif num == 1:
text = QtGui.QApplication.translate('WelcomeScreen', text = QtWidgets.QApplication.translate('WelcomeScreen',
'Right click on screenshot button hides app to tray during screenshot.', 'Right click on screenshot button hides app to tray during screenshot.')
None, QtGui.QApplication.UnicodeUTF8)
elif num == 2: elif num == 2:
text = QtGui.QApplication.translate('WelcomeScreen', text = QtWidgets.QApplication.translate('WelcomeScreen',
'You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a>', 'You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a>')
None, QtGui.QApplication.UnicodeUTF8)
elif num == 3: elif num == 3:
text = QtGui.QApplication.translate('WelcomeScreen', text = QtWidgets.QApplication.translate('WelcomeScreen',
'Use Settings -> Interface to customize interface.', 'Use Settings -> Interface to customize interface.')
None, QtGui.QApplication.UnicodeUTF8)
elif num == 4: elif num == 4:
text = QtGui.QApplication.translate('WelcomeScreen', text = QtWidgets.QApplication.translate('WelcomeScreen',
'Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.', 'Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.')
None, QtGui.QApplication.UnicodeUTF8)
elif num == 5: elif num == 5:
text = QtGui.QApplication.translate('WelcomeScreen', text = QtWidgets.QApplication.translate('WelcomeScreen',
'Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/xveduk/toxygen/blob/master/docs/plugins.md">Read more</a>', 'Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/xveduk/toxygen/blob/master/docs/plugins.md">Read more</a>')
None, QtGui.QApplication.UnicodeUTF8) elif num in (6, 7):
elif num == 6: text = QtWidgets.QApplication.translate('WelcomeScreen',
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.')
'New in Toxygen v0.2.6:<br>Updater<br>Better contact sorting<br>Plugins improvements',
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: elif num == 8:
text = QtGui.QApplication.translate('WelcomeScreen', text = QtWidgets.QApplication.translate('WelcomeScreen',
'Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu', 'Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu')
None, QtGui.QApplication.UnicodeUTF8)
elif num == 9: elif num == 9:
text = QtGui.QApplication.translate('WelcomeScreen', text = QtWidgets.QApplication.translate('WelcomeScreen',
'Use right click on inline image to save it', 'Use right click on inline image to save it')
None, QtGui.QApplication.UnicodeUTF8)
else: else:
text = QtGui.QApplication.translate('WelcomeScreen', text = QtWidgets.QApplication.translate('WelcomeScreen',
'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.', 'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.')
None, QtGui.QApplication.UnicodeUTF8)
self.text.setHtml(text) self.text.setHtml(text)
self.checkbox.stateChanged.connect(self.not_show) self.checkbox.stateChanged.connect(self.not_show)
QtCore.QTimer.singleShot(1000, self.show) QtCore.QTimer.singleShot(1000, self.show)
@ -394,7 +365,7 @@ class WelcomeScreen(CenteredWidget):
s.save() s.save()
class MainMenuButton(QtGui.QPushButton): class MainMenuButton(QtWidgets.QPushButton):
def __init__(self, *args): def __init__(self, *args):
super().__init__(*args) super().__init__(*args)
@ -404,3 +375,127 @@ class MainMenuButton(QtGui.QPushButton):
metrics = QtGui.QFontMetrics(self.font()) metrics = QtGui.QFontMetrics(self.font())
self.setFixedWidth(metrics.size(QtCore.Qt.TextSingleLine, text).width() + 20) self.setFixedWidth(metrics.size(QtCore.Qt.TextSingleLine, text).width() + 20)
super().setText(text) super().setText(text)
class ClickableLabel(QtWidgets.QLabel):
clicked = QtCore.pyqtSignal()
def __init__(self, *args):
super().__init__(*args)
def mouseReleaseEvent(self, ev):
self.clicked.emit()
class SearchScreen(QtWidgets.QWidget):
def __init__(self, messages, width, *args):
super().__init__(*args)
self.setMaximumSize(width, 40)
self.setMinimumSize(width, 40)
self._messages = messages
self.search_text = LineEdit(self)
self.search_text.setGeometry(0, 0, width - 160, 40)
self.search_button = ClickableLabel(self)
self.search_button.setGeometry(width - 160, 0, 40, 40)
pixmap = QtGui.QPixmap()
pixmap.load(util.curr_directory() + '/images/search.png')
self.search_button.setScaledContents(False)
self.search_button.setAlignment(QtCore.Qt.AlignCenter)
self.search_button.setPixmap(pixmap)
self.search_button.clicked.connect(self.search)
font = QtGui.QFont()
font.setPointSize(32)
font.setBold(True)
self.prev_button = QtWidgets.QPushButton(self)
self.prev_button.setGeometry(width - 120, 0, 40, 40)
self.prev_button.clicked.connect(self.prev)
self.prev_button.setText('\u25B2')
self.next_button = QtWidgets.QPushButton(self)
self.next_button.setGeometry(width - 80, 0, 40, 40)
self.next_button.clicked.connect(self.next)
self.next_button.setText('\u25BC')
self.close_button = QtWidgets.QPushButton(self)
self.close_button.setGeometry(width - 40, 0, 40, 40)
self.close_button.clicked.connect(self.close)
self.close_button.setText('×')
self.close_button.setFont(font)
font.setPointSize(18)
self.next_button.setFont(font)
self.prev_button.setFont(font)
self.retranslateUi()
def retranslateUi(self):
self.search_text.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "Search"))
def show(self):
super().show()
self.search_text.setFocus()
def search(self):
Profile.get_instance().update()
text = self.search_text.text()
friend = Profile.get_instance().get_curr_friend()
if text and friend and util.is_re_valid(text):
index = friend.search_string(text)
self.load_messages(index)
def prev(self):
friend = Profile.get_instance().get_curr_friend()
if friend is not None:
index = friend.search_prev()
self.load_messages(index)
def next(self):
friend = Profile.get_instance().get_curr_friend()
text = self.search_text.text()
if friend is not None:
index = friend.search_next()
if index is not None:
count = self._messages.count()
index += count
item = self._messages.item(index)
self._messages.scrollToItem(item)
self._messages.itemWidget(item).select_text(text)
else:
self.not_found(text)
def load_messages(self, index):
text = self.search_text.text()
if index is not None:
profile = Profile.get_instance()
count = self._messages.count()
while count + index < 0:
profile.load_history()
count = self._messages.count()
index += count
item = self._messages.item(index)
self._messages.scrollToItem(item)
self._messages.itemWidget(item).select_text(text)
else:
self.not_found(text)
def closeEvent(self, *args):
Profile.get_instance().update()
self._messages.setGeometry(0, 0, self._messages.width(), self._messages.height() + 40)
super().closeEvent(*args)
@staticmethod
def not_found(text):
mbox = QtWidgets.QMessageBox()
mbox_text = QtWidgets.QApplication.translate("MainWindow",
'Text "{}" was not found')
mbox.setText(mbox_text.format(text))
mbox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow",
'Not found'))
mbox.exec_()

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -4,7 +4,7 @@ import os
import importlib import importlib
import inspect import inspect
import plugins.plugin_super_class as pl import plugins.plugin_super_class as pl
import toxencryptsave import toxes
import sys import sys
@ -16,7 +16,7 @@ class PluginLoader(util.Singleton):
self._settings = settings self._settings = settings
self._plugins = {} # dict. key - plugin unique short name, value - tuple (plugin instance, is active) self._plugins = {} # dict. key - plugin unique short name, value - tuple (plugin instance, is active)
self._tox = tox self._tox = tox
self._encr = toxencryptsave.ToxEncryptSave.get_instance() self._encr = toxes.ToxES.get_instance()
def set_tox(self, tox): def set_tox(self, tox):
""" """
@ -51,7 +51,8 @@ class PluginLoader(util.Singleton):
continue continue
for elem in dir(module): for elem in dir(module):
obj = getattr(module, elem) obj = getattr(module, elem)
if inspect.isclass(obj) and hasattr(obj, 'is_plugin') and obj.is_plugin: # looking for plugin class in module # looking for plugin class in module
if inspect.isclass(obj) and hasattr(obj, 'is_plugin') and obj.is_plugin:
print('Plugin', elem) print('Plugin', elem)
try: # create instance of plugin class try: # create instance of plugin class
inst = obj(self._tox, self._profile, self._settings, self._encr) inst = obj(self._tox, self._profile, self._settings, self._encr)
@ -165,3 +166,8 @@ class PluginLoader(util.Singleton):
if self._plugins[key][1]: if self._plugins[key][1]:
self._plugins[key][0].close() self._plugins[key][0].close()
del self._plugins[key] del self._plugins[key]
def reload(self):
print('Reloading plugins')
self.stop()
self.load()

View File

@ -1,8 +1,5 @@
import os import os
try: from PyQt5 import QtCore, QtWidgets
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
MAX_SHORT_NAME_LENGTH = 5 MAX_SHORT_NAME_LENGTH = 5
@ -31,7 +28,7 @@ def log(name, data):
class PluginSuperClass: class PluginSuperClass:
""" """
Superclass for all plugins. Plugin is python module with at least one class derived from PluginSuperClass. Superclass for all plugins. Plugin is Python3 module with at least one class derived from PluginSuperClass.
""" """
is_plugin = True is_plugin = True
@ -43,7 +40,7 @@ class PluginSuperClass:
:param tox: tox instance :param tox: tox instance
:param profile: profile instance :param profile: profile instance
:param settings: profile settings :param settings: profile settings
:param encrypt_save: LibToxEncryptSave instance. :param encrypt_save: ToxES instance.
""" """
self._settings = settings self._settings = settings
self._profile = profile self._profile = profile
@ -137,10 +134,10 @@ class PluginSuperClass:
:param command: string with command :param command: string with command
""" """
if command == 'help': if command == 'help':
msgbox = QtGui.QMessageBox() msgbox = QtWidgets.QMessageBox()
title = QtGui.QApplication.translate("PluginWindow", "List of commands for plugin {}", None, QtGui.QApplication.UnicodeUTF8) title = QtWidgets.QApplication.translate("PluginWindow", "List of commands for plugin {}")
msgbox.setWindowTitle(title.format(self._name)) msgbox.setWindowTitle(title.format(self._name))
msgbox.setText(QtGui.QApplication.translate("PluginWindow", "No commands available", None, QtGui.QApplication.UnicodeUTF8)) msgbox.setText(QtWidgets.QApplication.translate("PluginWindow", "No commands available"))
msgbox.exec_() msgbox.exec_()
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
@ -151,7 +148,7 @@ class PluginSuperClass:
""" """
This method loads translations for GUI This method loads translations for GUI
""" """
app = QtGui.QApplication.instance() app = QtWidgets.QApplication.instance()
langs = self._settings.supported_languages() langs = self._settings.supported_languages()
curr_lang = self._settings['language'] curr_lang = self._settings['language']
if curr_lang in langs: if curr_lang in langs:
@ -169,7 +166,7 @@ class PluginSuperClass:
def load_settings(self): def load_settings(self):
""" """
This method loads settings of plugin and returns raw data This method loads settings of plugin and returns raw data
If file doesn't this method raises exception If file doesn't exist this method raises exception
""" """
with open(path_to_data(self._short_name) + 'settings.json', 'rb') as fl: with open(path_to_data(self._short_name) + 'settings.json', 'rb') as fl:
data = fl.read() data = fl.read()

View File

@ -1,8 +1,5 @@
from list_items import * from list_items import *
try: from PyQt5 import QtGui, QtWidgets
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
from friend import * from friend import *
from settings import * from settings import *
from toxcore_enums_and_consts import * from toxcore_enums_and_consts import *
@ -17,6 +14,8 @@ import avwidgets
import plugin_support import plugin_support
import basecontact import basecontact
import items_factory import items_factory
import cv2
import threading
class Profile(basecontact.BaseContact, Singleton): class Profile(basecontact.BaseContact, Singleton):
@ -39,8 +38,10 @@ class Profile(basecontact.BaseContact, Singleton):
self._tox = tox self._tox = tox
self._file_transfers = {} # dict of file transfers. key - tuple (friend_number, file_number) self._file_transfers = {} # dict of file transfers. key - tuple (friend_number, file_number)
self._call = calls.AV(tox.AV) # object with data about calls self._call = calls.AV(tox.AV) # object with data about calls
self._call_widgets = {} # dict of incoming call widgets
self._incoming_calls = set() self._incoming_calls = set()
self._load_history = True self._load_history = True
self._waiting_for_reconnection = False
self._factory = items_factory.ItemsFactory(self._screen.friends_list, self._messages) self._factory = items_factory.ItemsFactory(self._screen.friends_list, self._messages)
settings = Settings.get_instance() settings = Settings.get_instance()
self._sorting = settings['sorting'] self._sorting = settings['sorting']
@ -86,8 +87,8 @@ class Profile(basecontact.BaseContact, Singleton):
super(Profile, self).set_status(status) super(Profile, self).set_status(status)
if status is not None: if status is not None:
self._tox.self_set_status(status) self._tox.self_set_status(status)
else: elif not self._waiting_for_reconnection:
QtCore.QTimer.singleShot(45000, self.reconnect) QtCore.QTimer.singleShot(50000, self.reconnect)
def set_name(self, value): def set_name(self, value):
if self.name == value: if self.name == value:
@ -95,8 +96,7 @@ class Profile(basecontact.BaseContact, Singleton):
tmp = self.name tmp = self.name
super(Profile, self).set_name(value.encode('utf-8')) super(Profile, self).set_name(value.encode('utf-8'))
self._tox.self_set_name(self._name.encode('utf-8')) self._tox.self_set_name(self._name.encode('utf-8'))
message = QtGui.QApplication.translate("MainWindow", 'User {} is now known as {}', None, message = QtWidgets.QApplication.translate("MainWindow", 'User {} is now known as {}')
QtGui.QApplication.UnicodeUTF8)
message = message.format(tmp, value) message = message.format(tmp, value)
for friend in self._contacts: for friend in self._contacts:
friend.append_message(InfoMessage(message, time.time())) friend.append_message(InfoMessage(message, time.time()))
@ -127,6 +127,7 @@ class Profile(basecontact.BaseContact, Singleton):
""" """
filter_str = filter_str.lower() filter_str = filter_str.lower()
settings = Settings.get_instance() settings = Settings.get_instance()
number = self.get_active_number()
if sorting > 1: if sorting > 1:
if sorting & 2: if sorting & 2:
self._contacts = sorted(self._contacts, key=lambda x: int(x.status is not None), reverse=True) self._contacts = sorted(self._contacts, key=lambda x: int(x.status is not None), reverse=True)
@ -162,6 +163,7 @@ class Profile(basecontact.BaseContact, Singleton):
self._sorting, self._filter_string = sorting, filter_str self._sorting, self._filter_string = sorting, filter_str
settings['sorting'] = self._sorting settings['sorting'] = self._sorting
settings.save() settings.save()
self.set_active_by_number(number)
def update_filtration(self): def update_filtration(self):
""" """
@ -181,6 +183,9 @@ class Profile(basecontact.BaseContact, Singleton):
return None return None
return self._contacts[num] return self._contacts[num]
def get_curr_friend(self):
return self._contacts[self._active_friend] if self._active_friend + 1 else None
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
# Work with active friend # Work with active friend
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
@ -209,10 +214,11 @@ class Profile(basecontact.BaseContact, Singleton):
if value is not None: if value is not None:
if self._active_friend + 1 and self._active_friend != value: if self._active_friend + 1 and self._active_friend != value:
try: try:
self._contacts[self._active_friend].curr_text = self._screen.messageEdit.toPlainText() self.get_curr_friend().curr_text = self._screen.messageEdit.toPlainText()
except: except:
pass pass
friend = self._contacts[value] friend = self._contacts[value]
friend.remove_invalid_unsent_files()
if self._active_friend != value: if self._active_friend != value:
self._screen.messageEdit.setPlainText(friend.curr_text) self._screen.messageEdit.setPlainText(friend.curr_text)
self._active_friend = value self._active_friend = value
@ -238,7 +244,7 @@ class Profile(basecontact.BaseContact, Singleton):
if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer
try: try:
ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())] ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())]
ft.set_state_changed_handler(item.update) ft.set_state_changed_handler(item.update_transfer_state)
ft.signal() ft.signal()
except: except:
print('Incoming not started transfer - no info found') print('Incoming not started transfer - no info found')
@ -259,7 +265,7 @@ class Profile(basecontact.BaseContact, Singleton):
else: else:
self._screen.call_finished() self._screen.call_finished()
else: else:
friend = self._contacts[self._active_friend] friend = self.get_curr_friend()
self._screen.account_name.setText(friend.name) self._screen.account_name.setText(friend.name)
self._screen.account_status.setText(friend.status_message) self._screen.account_status.setText(friend.status_message)
@ -270,28 +276,33 @@ class Profile(basecontact.BaseContact, Singleton):
pixmap = QtGui.QPixmap(avatar_path) pixmap = QtGui.QPixmap(avatar_path)
self._screen.account_avatar.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio, self._screen.account_avatar.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation)) QtCore.Qt.SmoothTransformation))
self.update_filtration()
except Exception as ex: # no friend found. ignore except Exception as ex: # no friend found. ignore
log('Friend value: ' + str(value)) log('Friend value: ' + str(value))
log('Error in set active: ' + str(ex)) log('Error in set active: ' + str(ex))
raise raise
def set_active_by_number(self, number):
for i in range(len(self._contacts)):
if self._contacts[i].number == number:
self._active_friend = i
break
active_friend = property(get_active, set_active) active_friend = property(get_active, set_active)
def get_last_message(self): def get_last_message(self):
if self._active_friend + 1: if self._active_friend + 1:
return self._contacts[self._active_friend].get_last_message_text() return self.get_curr_friend().get_last_message_text()
else: else:
return '' return ''
def get_active_number(self): def get_active_number(self):
return self._contacts[self._active_friend].number if self._active_friend + 1 else -1 return self.get_curr_friend().number if self._active_friend + 1 else -1
def get_active_name(self): def get_active_name(self):
return self._contacts[self._active_friend].name if self._active_friend + 1 else '' return self.get_curr_friend().name if self._active_friend + 1 else ''
def is_active_online(self): def is_active_online(self):
return self._active_friend + 1 and self._contacts[self._active_friend].status is not None return self._active_friend + 1 and self.get_curr_friend().status is not None
def new_name(self, number, name): def new_name(self, number, name):
friend = self.get_friend_by_number(number) friend = self.get_friend_by_number(number)
@ -299,7 +310,7 @@ class Profile(basecontact.BaseContact, Singleton):
friend.set_name(name) friend.set_name(name)
name = str(name, 'utf-8') name = str(name, 'utf-8')
if friend.name == name and tmp != name: if friend.name == name and tmp != name:
message = QtGui.QApplication.translate("MainWindow", 'User {} is now known as {}', None, QtGui.QApplication.UnicodeUTF8) message = QtWidgets.QApplication.translate("MainWindow", 'User {} is now known as {}')
message = message.format(tmp, name) message = message.format(tmp, name)
friend.append_message(InfoMessage(message, time.time())) friend.append_message(InfoMessage(message, time.time()))
friend.actions = True friend.actions = True
@ -318,6 +329,7 @@ class Profile(basecontact.BaseContact, Singleton):
def send_files(self, friend_number): def send_files(self, friend_number):
friend = self.get_friend_by_number(friend_number) friend = self.get_friend_by_number(friend_number)
friend.remove_invalid_unsent_files()
files = friend.get_unsent_files() files = friend.get_unsent_files()
try: try:
for fl in files: for fl in files:
@ -366,7 +378,7 @@ class Profile(basecontact.BaseContact, Singleton):
""" """
if Settings.get_instance()['typing_notifications'] and self._active_friend + 1: if Settings.get_instance()['typing_notifications'] and self._active_friend + 1:
try: try:
friend = self._contacts[self._active_friend] friend = self.get_curr_friend()
if friend.status is not None: if friend.status is not None:
self._tox.self_set_typing(friend.number, typing) self._tox.self_set_typing(friend.number, typing)
except: except:
@ -399,25 +411,25 @@ class Profile(basecontact.BaseContact, Singleton):
for message in messages: for message in messages:
self.split_and_send(friend_number, message.get_data()[-1], message.get_data()[0].encode('utf-8')) self.split_and_send(friend_number, message.get_data()[-1], message.get_data()[0].encode('utf-8'))
friend.inc_receipts() friend.inc_receipts()
except: except Exception as ex:
pass log('Sending pending messages failed with ' + str(ex))
def split_and_send(self, number, message_type, message): def split_and_send(self, number, message_type, message):
""" """
Message splitting Message splitting. Message length cannot be > TOX_MAX_MESSAGE_LENGTH
:param number: friend's number :param number: friend's number
:param message_type: type of message :param message_type: type of message
:param message: message text :param message: message text
""" """
while len(message) > TOX_MAX_MESSAGE_LENGTH: while len(message) > TOX_MAX_MESSAGE_LENGTH:
size = TOX_MAX_MESSAGE_LENGTH * 4 / 5 size = TOX_MAX_MESSAGE_LENGTH * 4 // 5
last_part = message[size:TOX_MAX_MESSAGE_LENGTH] last_part = message[size:TOX_MAX_MESSAGE_LENGTH]
if ' ' in last_part: if b' ' in last_part:
index = last_part.index(' ') index = last_part.index(b' ')
elif ',' in last_part: elif b',' in last_part:
index = last_part.index(',') index = last_part.index(b',')
elif '.' in last_part: elif b'.' in last_part:
index = last_part.index('.') index = last_part.index(b'.')
else: else:
index = TOX_MAX_MESSAGE_LENGTH - size - 1 index = TOX_MAX_MESSAGE_LENGTH - size - 1
index += size + 1 index += size + 1
@ -436,7 +448,7 @@ class Profile(basecontact.BaseContact, Singleton):
t = time.time() t = time.time()
self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type) self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type)
self._messages.scrollToBottom() self._messages.scrollToBottom()
self._contacts[self._active_friend].append_message( self.get_curr_friend().append_message(
TextMessage(message, MESSAGE_OWNER['FRIEND'], t, message_type)) TextMessage(message, MESSAGE_OWNER['FRIEND'], t, message_type))
else: else:
friend = self.get_friend_by_number(friend_num) friend = self.get_friend_by_number(friend_num)
@ -475,7 +487,7 @@ class Profile(basecontact.BaseContact, Singleton):
friend.append_message(TextMessage(text, MESSAGE_OWNER['NOT_SENT'], t, message_type)) friend.append_message(TextMessage(text, MESSAGE_OWNER['NOT_SENT'], t, message_type))
def delete_message(self, time): def delete_message(self, time):
friend = self._contacts[self._active_friend] friend = self.get_curr_friend()
friend.delete_message(time) friend.delete_message(time)
self._history.delete_message(friend.tox_id, time) self._history.delete_message(friend.tox_id, time)
self.update() self.update()
@ -529,7 +541,7 @@ class Profile(basecontact.BaseContact, Singleton):
if not self._load_history: if not self._load_history:
return return
self._load_history = False self._load_history = False
friend = self._contacts[self._active_friend] friend = self.get_curr_friend()
friend.load_corr(False) friend.load_corr(False)
data = friend.get_corr() data = friend.get_corr()
if not data: if not data:
@ -552,7 +564,7 @@ class Profile(basecontact.BaseContact, Singleton):
if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer
try: try:
ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())] ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())]
ft.set_state_changed_handler(item.update) ft.set_state_changed_handler(item.update_transfer_state)
ft.signal() ft.signal()
except: except:
print('Incoming not started transfer - no info found') print('Incoming not started transfer - no info found')
@ -617,7 +629,7 @@ class Profile(basecontact.BaseContact, Singleton):
pixmap = None pixmap = None
if self._show_avatars: if self._show_avatars:
if owner == MESSAGE_OWNER['FRIEND']: if owner == MESSAGE_OWNER['FRIEND']:
pixmap = self._contacts[self._active_friend].get_pixmap() pixmap = self.get_curr_friend().get_pixmap()
else: else:
pixmap = self.get_pixmap() pixmap = self.get_pixmap()
return self._factory.message_item(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'], return self._factory.message_item(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'],
@ -649,17 +661,15 @@ class Profile(basecontact.BaseContact, Singleton):
""" """
friend = self._contacts[num] friend = self._contacts[num]
name = friend.name name = friend.name
dialog = QtGui.QApplication.translate('MainWindow', dialog = QtWidgets.QApplication.translate('MainWindow',
"Enter new alias for friend {} or leave empty to use friend's name:", "Enter new alias for friend {} or leave empty to use friend's name:")
None, QtGui.QApplication.UnicodeUTF8)
dialog = dialog.format(name) dialog = dialog.format(name)
title = QtGui.QApplication.translate('MainWindow', title = QtWidgets.QApplication.translate('MainWindow',
'Set alias', 'Set alias')
None, QtGui.QApplication.UnicodeUTF8)
text, ok = QtGui.QInputDialog.getText(None, text, ok = QtGui.QInputDialog.getText(None,
title, title,
dialog, dialog,
QtGui.QLineEdit.Normal, QtWidgets.QLineEdit.Normal,
name) name)
if ok: if ok:
settings = Settings.get_instance() settings = Settings.get_instance()
@ -784,9 +794,9 @@ class Profile(basecontact.BaseContact, Singleton):
raise Exception('TOX DNS lookup failed') raise Exception('TOX DNS lookup failed')
if len(tox_id) == TOX_PUBLIC_KEY_SIZE * 2: # public key if len(tox_id) == TOX_PUBLIC_KEY_SIZE * 2: # public key
self.add_friend(tox_id) self.add_friend(tox_id)
msgBox = QtGui.QMessageBox() msgBox = QtWidgets.QMessageBox()
msgBox.setWindowTitle(QtGui.QApplication.translate("MainWindow", "Friend added", None, QtGui.QApplication.UnicodeUTF8)) msgBox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "Friend added"))
text = (QtGui.QApplication.translate("MainWindow", 'Friend added without sending friend request', None, QtGui.QApplication.UnicodeUTF8)) text = (QtWidgets.QApplication.translate("MainWindow", 'Friend added without sending friend request'))
msgBox.setText(text) msgBox.setText(text)
msgBox.exec_() msgBox.exec_()
else: else:
@ -812,11 +822,11 @@ class Profile(basecontact.BaseContact, Singleton):
:param message: message :param message: message
""" """
try: try:
text = QtGui.QApplication.translate('MainWindow', 'User {} wants to add you to contact list. Message:\n{}', None, QtGui.QApplication.UnicodeUTF8) text = QtWidgets.QApplication.translate('MainWindow', 'User {} wants to add you to contact list. Message:\n{}')
info = text.format(tox_id, message) info = text.format(tox_id, message)
fr_req = QtGui.QApplication.translate('MainWindow', 'Friend request', None, QtGui.QApplication.UnicodeUTF8) fr_req = QtWidgets.QApplication.translate('MainWindow', 'Friend request')
reply = QtGui.QMessageBox.question(None, fr_req, info, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) reply = QtWidgets.QMessageBox.question(None, fr_req, info, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes: # accepted if reply == QtWidgets.QMessageBox.Yes: # accepted
self.add_friend(tox_id) self.add_friend(tox_id)
data = self._tox.get_savedata() data = self._tox.get_savedata()
ProfileHelper.get_instance().save_profile(data) ProfileHelper.get_instance().save_profile(data)
@ -845,9 +855,11 @@ class Profile(basecontact.BaseContact, Singleton):
self.update_filtration() self.update_filtration()
def reconnect(self): def reconnect(self):
self._waiting_for_reconnection = False
if self.status is None or all(list(map(lambda x: x.status is None, self._contacts))) and len(self._contacts): if self.status is None or all(list(map(lambda x: x.status is None, self._contacts))) and len(self._contacts):
self._waiting_for_reconnection = True
self.reset(self._screen.reset) self.reset(self._screen.reset)
QtCore.QTimer.singleShot(45000, self.reconnect) QtCore.QTimer.singleShot(50000, self.reconnect)
def close(self): def close(self):
for friend in self._contacts: for friend in self._contacts:
@ -876,7 +888,7 @@ class Profile(basecontact.BaseContact, Singleton):
settings = Settings.get_instance() settings = Settings.get_instance()
friend = self.get_friend_by_number(friend_number) friend = self.get_friend_by_number(friend_number)
auto = settings['allow_auto_accept'] and friend.tox_id in settings['auto_accept_from_friends'] 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'] inline = is_inline(file_name) and settings['allow_inline']
file_id = self._tox.file_get_file_id(friend_number, file_number) file_id = self._tox.file_get_file_id(friend_number, file_number)
accepted = True accepted = True
if file_id in self._paused_file_transfers: if file_id in self._paused_file_transfers:
@ -926,7 +938,7 @@ class Profile(basecontact.BaseContact, Singleton):
if friend_number == self.get_active_number(): if friend_number == self.get_active_number():
item = self.create_file_transfer_item(tm) item = self.create_file_transfer_item(tm)
if accepted: if accepted:
self._file_transfers[(friend_number, file_number)].set_state_changed_handler(item.update) self._file_transfers[(friend_number, file_number)].set_state_changed_handler(item.update_transfer_state)
self._messages.scrollToBottom() self._messages.scrollToBottom()
else: else:
friend.actions = True friend.actions = True
@ -961,7 +973,7 @@ class Profile(basecontact.BaseContact, Singleton):
0, -1) 0, -1)
def cancel_not_started_transfer(self, time): def cancel_not_started_transfer(self, time):
self._contacts[self._active_friend].delete_one_unsent_file(time) self.get_curr_friend().delete_one_unsent_file(time)
self.update() self.update()
def pause_transfer(self, friend_number, file_number, by_friend=False): def pause_transfer(self, friend_number, file_number, by_friend=False):
@ -1015,7 +1027,7 @@ class Profile(basecontact.BaseContact, Singleton):
self._file_transfers[(friend_number, file_number)] = rt self._file_transfers[(friend_number, file_number)] = rt
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['RESUME']) self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['RESUME'])
if item is not None: if item is not None:
rt.set_state_changed_handler(item.update) rt.set_state_changed_handler(item.update_transfer_state)
self.get_friend_by_number(friend_number).update_transfer_data(file_number, self.get_friend_by_number(friend_number).update_transfer_data(file_number,
TOX_FILE_TRANSFER_STATE['RUNNING']) TOX_FILE_TRANSFER_STATE['RUNNING'])
@ -1054,7 +1066,7 @@ class Profile(basecontact.BaseContact, Singleton):
st.get_file_number()) st.get_file_number())
item = self.create_file_transfer_item(tm) item = self.create_file_transfer_item(tm)
friend.append_message(tm) friend.append_message(tm)
st.set_state_changed_handler(item.update) st.set_state_changed_handler(item.update_transfer_state)
self._messages.scrollToBottom() self._messages.scrollToBottom()
def send_file(self, path, number=None, is_resend=False, file_id=None): def send_file(self, path, number=None, is_resend=False, file_id=None):
@ -1087,7 +1099,7 @@ class Profile(basecontact.BaseContact, Singleton):
st.get_file_number()) st.get_file_number())
if friend_number == self.get_active_number(): if friend_number == self.get_active_number():
item = self.create_file_transfer_item(tm) item = self.create_file_transfer_item(tm)
st.set_state_changed_handler(item.update) st.set_state_changed_handler(item.update_transfer_state)
self._messages.scrollToBottom() self._messages.scrollToBottom()
self._contacts[friend_number].append_message(tm) self._contacts[friend_number].append_message(tm)
@ -1103,7 +1115,6 @@ class Profile(basecontact.BaseContact, Singleton):
""" """
self._file_transfers[(friend_number, file_number)].send_chunk(position, size) self._file_transfers[(friend_number, file_number)].send_chunk(position, size)
@QtCore.Slot(int, int)
def transfer_finished(self, friend_number, file_number): def transfer_finished(self, friend_number, file_number):
transfer = self._file_transfers[(friend_number, file_number)] transfer = self._file_transfers[(friend_number, file_number)]
t = type(transfer) t = type(transfer)
@ -1120,7 +1131,7 @@ class Profile(basecontact.BaseContact, Singleton):
if friend_number == self.get_active_number(): if friend_number == self.get_active_number():
count = self._messages.count() count = self._messages.count()
if count + i + 1 >= 0: if count + i + 1 >= 0:
elem = QtGui.QListWidgetItem() elem = QtWidgets.QListWidgetItem()
item = InlineImageItem(transfer.get_data(), self._messages.width(), elem) item = InlineImageItem(transfer.get_data(), self._messages.width(), elem)
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height())) elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
self._messages.insertItem(count + i + 1, elem) self._messages.insertItem(count + i + 1, elem)
@ -1144,7 +1155,6 @@ class Profile(basecontact.BaseContact, Singleton):
if not os.path.isfile(avatar_path): # reset image if not os.path.isfile(avatar_path): # reset image
avatar_path = None avatar_path = None
sa = SendAvatar(avatar_path, self._tox, friend_number) sa = SendAvatar(avatar_path, self._tox, friend_number)
sa.set_transfer_finished_handler(self.transfer_finished)
self._file_transfers[(friend_number, sa.get_file_number())] = sa self._file_transfers[(friend_number, sa.get_file_number())] = sa
def incoming_avatar(self, friend_number, file_number, size): def incoming_avatar(self, friend_number, file_number, size):
@ -1157,6 +1167,7 @@ class Profile(basecontact.BaseContact, Singleton):
ra = ReceiveAvatar(self._tox, friend_number, size, file_number) ra = ReceiveAvatar(self._tox, friend_number, size, file_number)
if ra.state != TOX_FILE_TRANSFER_STATE['CANCELLED']: if ra.state != TOX_FILE_TRANSFER_STATE['CANCELLED']:
self._file_transfers[(friend_number, file_number)] = ra self._file_transfers[(friend_number, file_number)] = ra
ra.set_transfer_finished_handler(self.transfer_finished)
else: else:
self.get_friend_by_number(friend_number).load_avatar() self.get_friend_by_number(friend_number).load_avatar()
if self.get_active_number() == friend_number: if self.get_active_number() == friend_number:
@ -1190,12 +1201,10 @@ class Profile(basecontact.BaseContact, Singleton):
self._call(num, audio, video) self._call(num, audio, video)
self._screen.active_call() self._screen.active_call()
if video: if video:
text = QtGui.QApplication.translate("incoming_call", "Outgoing video call", None, text = QtWidgets.QApplication.translate("incoming_call", "Outgoing video call")
QtGui.QApplication.UnicodeUTF8)
else: else:
text = QtGui.QApplication.translate("incoming_call", "Outgoing audio call", None, text = QtWidgets.QApplication.translate("incoming_call", "Outgoing audio call")
QtGui.QApplication.UnicodeUTF8) self.get_curr_friend().append_message(InfoMessage(text, time.time()))
self._contacts[self._active_friend].append_message(InfoMessage(text, time.time()))
self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
self._messages.scrollToBottom() self._messages.scrollToBottom()
elif num in self._call: # finish or cancel call if you call with active friend elif num in self._call: # finish or cancel call if you call with active friend
@ -1209,11 +1218,9 @@ class Profile(basecontact.BaseContact, Singleton):
return return
friend = self.get_friend_by_number(friend_number) friend = self.get_friend_by_number(friend_number)
if video: if video:
text = QtGui.QApplication.translate("incoming_call", "Incoming video call", None, text = QtWidgets.QApplication.translate("incoming_call", "Incoming video call")
QtGui.QApplication.UnicodeUTF8)
else: else:
text = QtGui.QApplication.translate("incoming_call", "Incoming audio call", None, text = QtWidgets.QApplication.translate("incoming_call", "Incoming audio call")
QtGui.QApplication.UnicodeUTF8)
friend.append_message(InfoMessage(text, time.time())) friend.append_message(InfoMessage(text, time.time()))
self._incoming_calls.add(friend_number) self._incoming_calls.add(friend_number)
if friend_number == self.get_active_number(): if friend_number == self.get_active_number():
@ -1222,10 +1229,9 @@ class Profile(basecontact.BaseContact, Singleton):
self._messages.scrollToBottom() self._messages.scrollToBottom()
else: else:
friend.actions = True friend.actions = True
# TODO: dict of widgets self._call_widgets[friend_number] = avwidgets.IncomingCallWidget(friend_number, text, friend.name)
self._call_widget = avwidgets.IncomingCallWidget(friend_number, text, friend.name) self._call_widgets[friend_number].set_pixmap(friend.get_pixmap())
self._call_widget.set_pixmap(friend.get_pixmap()) self._call_widgets[friend_number].show()
self._call_widget.show()
def accept_call(self, friend_number, audio, video): def accept_call(self, friend_number, audio, video):
""" """
@ -1235,8 +1241,7 @@ class Profile(basecontact.BaseContact, Singleton):
self._screen.active_call() self._screen.active_call()
if friend_number in self._incoming_calls: if friend_number in self._incoming_calls:
self._incoming_calls.remove(friend_number) self._incoming_calls.remove(friend_number)
if hasattr(self, '_call_widget'): del self._call_widgets[friend_number]
del self._call_widget
def stop_call(self, friend_number, by_friend): def stop_call(self, friend_number, by_friend):
""" """
@ -1244,14 +1249,15 @@ class Profile(basecontact.BaseContact, Singleton):
""" """
if friend_number in self._incoming_calls: if friend_number in self._incoming_calls:
self._incoming_calls.remove(friend_number) self._incoming_calls.remove(friend_number)
text = QtGui.QApplication.translate("incoming_call", "Call declined", None, QtGui.QApplication.UnicodeUTF8) text = QtWidgets.QApplication.translate("incoming_call", "Call declined")
else: else:
text = QtGui.QApplication.translate("incoming_call", "Call finished", None, QtGui.QApplication.UnicodeUTF8) text = QtWidgets.QApplication.translate("incoming_call", "Call finished")
self._screen.call_finished() self._screen.call_finished()
self._call.finish_call(friend_number, by_friend) # finish or decline call self._call.finish_call(friend_number, by_friend) # finish or decline call
if hasattr(self, '_call_widget'): if hasattr(self, '_call_widget'):
self._call_widget.close() self._call_widget[friend_number].close()
del self._call_widget del self._call_widget[friend_number]
threading.Timer(2.0, lambda: cv2.destroyWindow(str(friend_number))).start()
friend = self.get_friend_by_number(friend_number) friend = self.get_friend_by_number(friend_number)
friend.append_message(InfoMessage(text, time.time())) friend.append_message(InfoMessage(text, time.time()))
if friend_number == self.get_active_number(): if friend_number == self.get_active_number():

View File

@ -3,7 +3,7 @@ import json
import os import os
from util import Singleton, curr_directory, log, copy, append_slash from util import Singleton, curr_directory, log, copy, append_slash
import pyaudio import pyaudio
from toxencryptsave import ToxEncryptSave from toxes import ToxES
import smileys import smileys
@ -19,7 +19,7 @@ class Settings(dict, Singleton):
if os.path.isfile(self.path): if os.path.isfile(self.path):
with open(self.path, 'rb') as fl: with open(self.path, 'rb') as fl:
data = fl.read() data = fl.read()
inst = ToxEncryptSave.get_instance() inst = ToxES.get_instance()
try: try:
if inst.is_data_encrypted(data): if inst.is_data_encrypted(data):
data = inst.pass_decrypt(data) data = inst.pass_decrypt(data)
@ -35,6 +35,7 @@ class Settings(dict, Singleton):
smileys.SmileyLoader(self) smileys.SmileyLoader(self)
self.locked = False self.locked = False
self.closing = False self.closing = False
self.unlockScreen = False
p = pyaudio.PyAudio() p = pyaudio.PyAudio()
input_devices = output_devices = 0 input_devices = output_devices = 0
for i in range(p.get_device_count()): for i in range(p.get_device_count()):
@ -46,10 +47,11 @@ class Settings(dict, Singleton):
self.audio = {'input': p.get_default_input_device_info()['index'] if input_devices else -1, self.audio = {'input': p.get_default_input_device_info()['index'] if input_devices else -1,
'output': p.get_default_output_device_info()['index'] if output_devices else -1, 'output': p.get_default_output_device_info()['index'] if output_devices else -1,
'enabled': input_devices and output_devices} 'enabled': input_devices and output_devices}
self.video = {'device': 0, 'width': 640, 'height': 480}
@staticmethod @staticmethod
def get_auto_profile(): def get_auto_profile():
p = Settings.get_default_path() + 'toxygen.json' p = Settings.get_global_settings_path()
if os.path.isfile(p): if os.path.isfile(p):
with open(p) as fl: with open(p) as fl:
data = fl.read() data = fl.read()
@ -60,7 +62,7 @@ class Settings(dict, Singleton):
@staticmethod @staticmethod
def set_auto_profile(path, name): def set_auto_profile(path, name):
p = Settings.get_default_path() + 'toxygen.json' p = Settings.get_global_settings_path()
if os.path.isfile(p): if os.path.isfile(p):
with open(p) as fl: with open(p) as fl:
data = fl.read() data = fl.read()
@ -74,7 +76,7 @@ class Settings(dict, Singleton):
@staticmethod @staticmethod
def reset_auto_profile(): def reset_auto_profile():
p = Settings.get_default_path() + 'toxygen.json' p = Settings.get_global_settings_path()
if os.path.isfile(p): if os.path.isfile(p):
with open(p) as fl: with open(p) as fl:
data = fl.read() data = fl.read()
@ -89,15 +91,8 @@ class Settings(dict, Singleton):
@staticmethod @staticmethod
def is_active_profile(path, name): def is_active_profile(path, name):
path = path + name + '.tox' path = path + name + '.lock'
settings = Settings.get_default_path() + 'toxygen.json' return os.path.isfile(path)
if os.path.isfile(settings):
with open(settings) as fl:
data = fl.read()
data = json.loads(data)
if 'active_profile' in data:
return path in data['active_profile']
return False
@staticmethod @staticmethod
def get_default_settings(): def get_default_settings():
@ -167,7 +162,7 @@ class Settings(dict, Singleton):
def save(self): def save(self):
text = json.dumps(self) text = json.dumps(self)
inst = ToxEncryptSave.get_instance() inst = ToxES.get_instance()
if inst.has_password(): if inst.has_password():
text = bytes(inst.pass_encrypt(bytes(text, 'utf-8'))) text = bytes(inst.pass_encrypt(bytes(text, 'utf-8')))
else: else:
@ -176,37 +171,19 @@ class Settings(dict, Singleton):
fl.write(text) fl.write(text)
def close(self): def close(self):
path = Settings.get_default_path() + 'toxygen.json' profile_path = ProfileHelper.get_path()
path = str(profile_path + str(self.name) + '.lock')
if os.path.isfile(path): if os.path.isfile(path):
with open(path) as fl: os.remove(path)
data = fl.read()
app_settings = json.loads(data)
try:
app_settings['active_profile'].remove(str(ProfileHelper.get_path() + self.name + '.tox'))
except:
pass
data = json.dumps(app_settings)
with open(path, 'w') as fl:
fl.write(data)
def set_active_profile(self): def set_active_profile(self):
""" """
Mark current profile as active Mark current profile as active
""" """
path = Settings.get_default_path() + 'toxygen.json'
if os.path.isfile(path):
with open(path) as fl:
data = fl.read()
app_settings = json.loads(data)
else:
app_settings = {}
if 'active_profile' not in app_settings:
app_settings['active_profile'] = []
profile_path = ProfileHelper.get_path() profile_path = ProfileHelper.get_path()
app_settings['active_profile'].append(str(profile_path + str(self.name) + '.tox')) path = str(profile_path + str(self.name) + '.lock')
data = json.dumps(app_settings)
with open(path, 'w') as fl: with open(path, 'w') as fl:
fl.write(data) fl.write('active')
def export(self, path): def export(self, path):
text = json.dumps(self) text = json.dumps(self)
@ -216,6 +193,10 @@ class Settings(dict, Singleton):
def update_path(self): def update_path(self):
self.path = ProfileHelper.get_path() + self.name + '.json' self.path = ProfileHelper.get_path() + self.name + '.json'
@staticmethod
def get_global_settings_path():
return curr_directory() + '/toxygen.json'
@staticmethod @staticmethod
def get_default_path(): def get_default_path():
if system() == 'Windows': if system() == 'Windows':
@ -252,7 +233,7 @@ class ProfileHelper(Singleton):
return self._directory return self._directory
def save_profile(self, data): def save_profile(self, data):
inst = ToxEncryptSave.get_instance() inst = ToxES.get_instance()
if inst.has_password(): if inst.has_password():
data = inst.pass_encrypt(data) data = inst.pass_encrypt(data)
with open(self._path, 'wb') as fl: with open(self._path, 'wb') as fl:

View File

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

View File

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

View File

@ -1309,4 +1309,16 @@ QListWidget > QLabel
image: none; image: none;
width: 0px; width: 0px;
height: 0px; height: 0px;
} }
ClickableLabel:focus
{
border-width: 1px;
border-color: #4A4949;
border-style: solid;
}
ClickableLabel:hover
{
background-color: #4A4949;
}

View File

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

View File

@ -1,64 +1,25 @@
import libtox import libtox
import util
from ctypes import c_size_t, create_string_buffer, byref, c_int, ArgumentError, c_char_p, c_bool from ctypes import c_size_t, create_string_buffer, byref, c_int, ArgumentError, c_char_p, c_bool
from toxencryptsave_enums_and_consts import *
TOX_ERR_ENCRYPTION = { class ToxEncryptSave:
# The function returned successfully.
'OK': 0,
# Some input data, or maybe the output pointer, was null.
'NULL': 1,
# The crypto lib was unable to derive a key from the given passphrase, which is usually a lack of memory issue. The
# functions accepting keys do not produce this error.
'KEY_DERIVATION_FAILED': 2,
# The encryption itself failed.
'FAILED': 3
}
TOX_ERR_DECRYPTION = {
# The function returned successfully.
'OK': 0,
# Some input data, or maybe the output pointer, was null.
'NULL': 1,
# The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes
'INVALID_LENGTH': 2,
# The input data is missing the magic number (i.e. wasn't created by this module, or is corrupted)
'BAD_FORMAT': 3,
# The crypto lib was unable to derive a key from the given passphrase, which is usually a lack of memory issue. The
# functions accepting keys do not produce this error.
'KEY_DERIVATION_FAILED': 4,
# The encrypted byte array could not be decrypted. Either the data was corrupt or the password/key was incorrect.
'FAILED': 5,
}
TOX_PASS_ENCRYPTION_EXTRA_LENGTH = 80
class ToxEncryptSave(util.Singleton):
def __init__(self): def __init__(self):
super().__init__()
self.libtoxencryptsave = libtox.LibToxEncryptSave() self.libtoxencryptsave = libtox.LibToxEncryptSave()
self._passphrase = None
def set_password(self, passphrase):
self._passphrase = passphrase
def has_password(self):
return bool(self._passphrase)
def is_password(self, password):
return self._passphrase == password
def is_data_encrypted(self, data): def is_data_encrypted(self, data):
"""
Checks if given data is encrypted
"""
func = self.libtoxencryptsave.tox_is_data_encrypted func = self.libtoxencryptsave.tox_is_data_encrypted
func.restype = c_bool func.restype = c_bool
result = func(c_char_p(bytes(data))) result = func(c_char_p(bytes(data)))
return result return result
def pass_encrypt(self, data): def pass_encrypt(self, data, password):
""" """
Encrypts the given data with the given passphrase. Encrypts the given data with the given password.
:return: output array :return: output array
""" """
@ -66,8 +27,8 @@ class ToxEncryptSave(util.Singleton):
tox_err_encryption = c_int() tox_err_encryption = c_int()
self.libtoxencryptsave.tox_pass_encrypt(c_char_p(data), self.libtoxencryptsave.tox_pass_encrypt(c_char_p(data),
c_size_t(len(data)), c_size_t(len(data)),
c_char_p(bytes(self._passphrase, 'utf-8')), c_char_p(bytes(password, 'utf-8')),
c_size_t(len(self._passphrase)), c_size_t(len(password)),
out, out,
byref(tox_err_encryption)) byref(tox_err_encryption))
tox_err_encryption = tox_err_encryption.value tox_err_encryption = tox_err_encryption.value
@ -81,9 +42,9 @@ class ToxEncryptSave(util.Singleton):
elif tox_err_encryption == TOX_ERR_ENCRYPTION['FAILED']: elif tox_err_encryption == TOX_ERR_ENCRYPTION['FAILED']:
raise RuntimeError('The encryption itself failed.') raise RuntimeError('The encryption itself failed.')
def pass_decrypt(self, data): def pass_decrypt(self, data, password):
""" """
Decrypts the given data with the given passphrase. Decrypts the given data with the given password.
:return: output array :return: output array
""" """
@ -91,8 +52,8 @@ class ToxEncryptSave(util.Singleton):
tox_err_decryption = c_int() tox_err_decryption = c_int()
self.libtoxencryptsave.tox_pass_decrypt(c_char_p(bytes(data)), self.libtoxencryptsave.tox_pass_decrypt(c_char_p(bytes(data)),
c_size_t(len(data)), c_size_t(len(data)),
c_char_p(bytes(self._passphrase, 'utf-8')), c_char_p(bytes(password, 'utf-8')),
c_size_t(len(self._passphrase)), c_size_t(len(password)),
out, out,
byref(tox_err_decryption)) byref(tox_err_decryption))
tox_err_decryption = tox_err_decryption.value tox_err_decryption = tox_err_decryption.value

View File

@ -0,0 +1,29 @@
TOX_ERR_ENCRYPTION = {
# The function returned successfully.
'OK': 0,
# Some input data, or maybe the output pointer, was null.
'NULL': 1,
# The crypto lib was unable to derive a key from the given passphrase, which is usually a lack of memory issue. The
# functions accepting keys do not produce this error.
'KEY_DERIVATION_FAILED': 2,
# The encryption itself failed.
'FAILED': 3
}
TOX_ERR_DECRYPTION = {
# The function returned successfully.
'OK': 0,
# Some input data, or maybe the output pointer, was null.
'NULL': 1,
# The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes
'INVALID_LENGTH': 2,
# The input data is missing the magic number (i.e. wasn't created by this module, or is corrupted)
'BAD_FORMAT': 3,
# The crypto lib was unable to derive a key from the given passphrase, which is usually a lack of memory issue. The
# functions accepting keys do not produce this error.
'KEY_DERIVATION_FAILED': 4,
# The encrypted byte array could not be decrypted. Either the data was corrupt or the password/key was incorrect.
'FAILED': 5,
}
TOX_PASS_ENCRYPTION_EXTRA_LENGTH = 80

28
toxygen/toxes.py Normal file
View File

@ -0,0 +1,28 @@
import util
import toxencryptsave
class ToxES(util.Singleton):
def __init__(self):
super().__init__()
self._toxencryptsave = toxencryptsave.ToxEncryptSave()
self._passphrase = None
def set_password(self, passphrase):
self._passphrase = passphrase
def has_password(self):
return bool(self._passphrase)
def is_password(self, password):
return self._passphrase == password
def is_data_encrypted(self, data):
return len(data) > 0 and self._toxencryptsave.is_data_encrypted(data)
def pass_encrypt(self, data):
return self._toxencryptsave.pass_encrypt(data, self._passphrase)
def pass_decrypt(self, data):
return self._toxencryptsave.pass_decrypt(data, self._passphrase)

View File

@ -84,98 +84,98 @@ can produce IP leak</source>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>
<message> <message>
<location filename="mainscreen.py" line="118"/> <location filename="mainscreen.py" line="123"/>
<source>Profile</source> <source>Profile</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="124"/> <location filename="mainscreen.py" line="129"/>
<source>Settings</source> <source>Settings</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="414"/> <location filename="mainscreen.py" line="426"/>
<source>About</source> <source>About</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="117"/> <location filename="mainscreen.py" line="122"/>
<source>Add contact</source> <source>Add contact</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="119"/> <location filename="mainscreen.py" line="124"/>
<source>Privacy</source> <source>Privacy</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="120"/> <location filename="mainscreen.py" line="125"/>
<source>Interface</source> <source>Interface</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="121"/> <location filename="mainscreen.py" line="126"/>
<source>Notifications</source> <source>Notifications</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="122"/> <location filename="mainscreen.py" line="127"/>
<source>Network</source> <source>Network</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="123"/> <location filename="mainscreen.py" line="128"/>
<source>About program</source> <source>About program</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="816"/> <location filename="profile.py" line="829"/>
<source>User {} wants to add you to contact list. Message: <source>User {} wants to add you to contact list. Message:
{}</source> {}</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="818"/> <location filename="profile.py" line="831"/>
<source>Friend request</source> <source>Friend request</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="509"/> <location filename="mainscreen.py" line="528"/>
<source>Choose file</source> <source>Choose file</source>
<translation>Choose file</translation> <translation>Choose file</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="574"/> <location filename="mainscreen.py" line="593"/>
<source>Disallow auto accept</source> <source>Disallow auto accept</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="575"/> <location filename="mainscreen.py" line="594"/>
<source>Allow auto accept</source> <source>Allow auto accept</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="577"/> <location filename="mainscreen.py" line="596"/>
<source>Set alias</source> <source>Set alias</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="580"/> <location filename="mainscreen.py" line="599"/>
<source>Clear history</source> <source>Clear history</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="590"/> <location filename="mainscreen.py" line="609"/>
<source>Remove friend</source> <source>Remove friend</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="655"/> <location filename="profile.py" line="668"/>
<source>Enter new alias for friend {} or leave empty to use friend&apos;s name:</source> <source>Enter new alias for friend {} or leave empty to use friend&apos;s name:</source>
<translation>Enter new alias for friend {} or leave empty to use friend&apos;s name:</translation> <translation>Enter new alias for friend {} or leave empty to use friend&apos;s name:</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="125"/> <location filename="mainscreen.py" line="130"/>
<source>Audio</source> <source>Audio</source>
<translation>Audio</translation> <translation>Audio</translation>
</message> </message>
@ -185,24 +185,24 @@ can produce IP leak</source>
<translation type="obsolete">Find contact</translation> <translation type="obsolete">Find contact</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="788"/> <location filename="profile.py" line="801"/>
<source>Friend added</source> <source>Friend added</source>
<translation>Friend added</translation> <translation>Friend added</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="415"/> <location filename="mainscreen.py" line="427"/>
<source>Toxygen is Tox client written on Python. <source>Toxygen is Tox client written on Python.
Version: </source> Version: </source>
<translation>Toxygen is Tox client written on Python. <translation>Toxygen is Tox client written on Python.
Version:</translation> Version:</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="789"/> <location filename="profile.py" line="802"/>
<source>Friend added without sending friend request</source> <source>Friend added without sending friend request</source>
<translation>Friend added without sending friend request</translation> <translation>Friend added without sending friend request</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="632"/> <location filename="mainscreen.py" line="655"/>
<source>Choose folder</source> <source>Choose folder</source>
<translation>Choose folder</translation> <translation>Choose folder</translation>
</message> </message>
@ -217,47 +217,47 @@ Version:</translation>
<translation type="obsolete">Send file</translation> <translation type="obsolete">Send file</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="128"/> <location filename="mainscreen.py" line="133"/>
<source>Send message</source> <source>Send message</source>
<translation>Send message</translation> <translation>Send message</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="129"/> <location filename="mainscreen.py" line="134"/>
<source>Start audio call with friend</source> <source>Start audio call with friend</source>
<translation>Start audio call with friend</translation> <translation>Start audio call with friend</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="595"/> <location filename="mainscreen.py" line="617"/>
<source>Plugins</source> <source>Plugins</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="113"/> <location filename="mainscreen.py" line="118"/>
<source>List of plugins</source> <source>List of plugins</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="127"/> <location filename="mainscreen_widgets.py" line="462"/>
<source>Search</source> <source>Search</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="131"/> <location filename="mainscreen.py" line="136"/>
<source>All</source> <source>All</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="132"/> <location filename="mainscreen.py" line="137"/>
<source>Online</source> <source>Online</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="591"/> <location filename="mainscreen.py" line="611"/>
<source>Notes</source> <source>Notes</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="616"/> <location filename="mainscreen.py" line="639"/>
<source>Notes about user</source> <source>Notes about user</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -307,42 +307,42 @@ Version:</translation>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="303"/> <location filename="profile.py" line="315"/>
<source>User {} is now known as {}</source> <source>User {} is now known as {}</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="list_items.py" line="167"/> <location filename="list_items.py" line="168"/>
<source>Delete message</source> <source>Delete message</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="111"/> <location filename="mainscreen.py" line="116"/>
<source>Lock</source> <source>Lock</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="482"/> <location filename="mainscreen.py" line="501"/>
<source>Cannot lock app</source> <source>Cannot lock app</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="485"/> <location filename="mainscreen.py" line="504"/>
<source>Error. Profile password is not set.</source> <source>Error. Profile password is not set.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="585"/> <location filename="mainscreen.py" line="604"/>
<source>Name</source> <source>Name</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="586"/> <location filename="mainscreen.py" line="605"/>
<source>Status message</source> <source>Status message</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="587"/> <location filename="mainscreen.py" line="606"/>
<source>Public key</source> <source>Public key</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -367,65 +367,85 @@ Version:</translation>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="140"/> <location filename="mainscreen.py" line="145"/>
<source>Import plugin</source> <source>Import plugin</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="460"/> <location filename="mainscreen.py" line="479"/>
<source>Choose folder with plugin</source> <source>Choose folder with plugin</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="469"/> <location filename="mainscreen.py" line="488"/>
<source>Restart Toxygen</source> <source>Restart Toxygen</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="472"/> <location filename="mainscreen.py" line="491"/>
<source>Plugin will be loaded after restart</source> <source>Plugin will be loaded after restart</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="list_items.py" line="48"/> <location filename="list_items.py" line="49"/>
<source>Quote selected text</source> <source>Quote selected text</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="579"/> <location filename="mainscreen.py" line="598"/>
<source>Chat history</source> <source>Chat history</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="581"/> <location filename="mainscreen.py" line="600"/>
<source>Export as text</source> <source>Export as text</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="582"/> <location filename="mainscreen.py" line="601"/>
<source>Export as HTML</source> <source>Export as HTML</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="126"/> <location filename="mainscreen.py" line="131"/>
<source>Updates</source> <source>Updates</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="133"/> <location filename="mainscreen.py" line="138"/>
<source>Online first</source> <source>Online first</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="135"/> <location filename="mainscreen.py" line="140"/>
<source>Online and by name</source> <source>Online and by name</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="136"/> <location filename="mainscreen.py" line="141"/>
<source>Online first and by name</source> <source>Online first and by name</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="mainscreen.py" line="610"/>
<source>Block friend</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen_widgets.py" line="528"/>
<source>Not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen_widgets.py" line="524"/>
<source>Text &quot;{}&quot; was not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="146"/>
<source>Reload plugins</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>MenuWindow</name> <name>MenuWindow</name>
@ -758,30 +778,25 @@ Version:</translation>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen_widgets.py" line="374"/> <location filename="mainscreen_widgets.py" line="370"/>
<source>Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.</source> <source>Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen_widgets.py" line="386"/> <location filename="mainscreen_widgets.py" line="382"/>
<source>Set new NoSpam to avoid spam friend requests: Profile -&gt; Settings -&gt; Set new NoSpam.</source> <source>Set new NoSpam to avoid spam friend requests: Profile -&gt; Settings -&gt; Set new NoSpam.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen_widgets.py" line="378"/> <location filename="mainscreen_widgets.py" line="374"/>
<source>Delete single message in chat: make right click on spinner or message time and choose &quot;Delete&quot; in menu</source> <source>Delete single message in chat: make right click on spinner or message time and choose &quot;Delete&quot; in menu</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen_widgets.py" line="382"/> <location filename="mainscreen_widgets.py" line="378"/>
<source>Use right click on inline image to save it</source> <source>Use right click on inline image to save it</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="mainscreen_widgets.py" line="370"/>
<source>New in Toxygen v0.2.6:&lt;br&gt;Updater&lt;br&gt;Better contact sorting&lt;br&gt;Plugins improvements</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>audioSettingsForm</name> <name>audioSettingsForm</name>
@ -804,32 +819,32 @@ Version:</translation>
<context> <context>
<name>incoming_call</name> <name>incoming_call</name>
<message> <message>
<location filename="profile.py" line="1214"/> <location filename="profile.py" line="1229"/>
<source>Incoming video call</source> <source>Incoming video call</source>
<translation>Incoming video call</translation> <translation>Incoming video call</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="1217"/> <location filename="profile.py" line="1232"/>
<source>Incoming audio call</source> <source>Incoming audio call</source>
<translation>Incoming audio call</translation> <translation>Incoming audio call</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="1195"/> <location filename="profile.py" line="1210"/>
<source>Outgoing video call</source> <source>Outgoing video call</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="1198"/> <location filename="profile.py" line="1213"/>
<source>Outgoing audio call</source> <source>Outgoing audio call</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="1248"/> <location filename="profile.py" line="1263"/>
<source>Call declined</source> <source>Call declined</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="1250"/> <location filename="profile.py" line="1265"/>
<source>Call finished</source> <source>Call finished</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -985,7 +1000,7 @@ Version:</translation>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="main.py" line="288"/> <location filename="main.py" line="290"/>
<source>Update for Toxygen was found. Download and install it?</source> <source>Update for Toxygen was found. Download and install it?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -1122,32 +1137,32 @@ Version:</translation>
<context> <context>
<name>tray</name> <name>tray</name>
<message> <message>
<location filename="main.py" line="228"/> <location filename="main.py" line="229"/>
<source>Open Toxygen</source> <source>Open Toxygen</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="main.py" line="237"/> <location filename="main.py" line="238"/>
<source>Exit</source> <source>Exit</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="main.py" line="229"/> <location filename="main.py" line="230"/>
<source>Set status</source> <source>Set status</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="main.py" line="230"/> <location filename="main.py" line="231"/>
<source>Online</source> <source>Online</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="main.py" line="231"/> <location filename="main.py" line="232"/>
<source>Away</source> <source>Away</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="main.py" line="232"/> <location filename="main.py" line="233"/>
<source>Busy</source> <source>Busy</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>

View File

@ -84,58 +84,58 @@ can produce IP leak</source>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>
<message> <message>
<location filename="mainscreen.py" line="118"/> <location filename="mainscreen.py" line="123"/>
<source>Profile</source> <source>Profile</source>
<translation>Profile</translation> <translation>Profile</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="124"/> <location filename="mainscreen.py" line="129"/>
<source>Settings</source> <source>Settings</source>
<translation>Paramêtres</translation> <translation>Paramêtres</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="414"/> <location filename="mainscreen.py" line="426"/>
<source>About</source> <source>About</source>
<translation>À Propos</translation> <translation>À Propos</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="117"/> <location filename="mainscreen.py" line="122"/>
<source>Add contact</source> <source>Add contact</source>
<translation>Rajouter un contact</translation> <translation>Rajouter un contact</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="119"/> <location filename="mainscreen.py" line="124"/>
<source>Privacy</source> <source>Privacy</source>
<translation>Confidentialité</translation> <translation>Confidentialité</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="120"/> <location filename="mainscreen.py" line="125"/>
<source>Interface</source> <source>Interface</source>
<translation>Interface</translation> <translation>Interface</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="121"/> <location filename="mainscreen.py" line="126"/>
<source>Notifications</source> <source>Notifications</source>
<translation>Notifications</translation> <translation>Notifications</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="122"/> <location filename="mainscreen.py" line="127"/>
<source>Network</source> <source>Network</source>
<translation>Réseau</translation> <translation>Réseau</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="123"/> <location filename="mainscreen.py" line="128"/>
<source>About program</source> <source>About program</source>
<translation>À propos du programme</translation> <translation>À propos du programme</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="816"/> <location filename="profile.py" line="829"/>
<source>User {} wants to add you to contact list. Message: <source>User {} wants to add you to contact list. Message:
{}</source> {}</source>
<translation>L&apos;Utilisateur {} veut vout rajouter à sa liste de contacts. Message : {}</translation> <translation>L&apos;Utilisateur {} veut vout rajouter à sa liste de contacts. Message : {}</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="818"/> <location filename="profile.py" line="831"/>
<source>Friend request</source> <source>Friend request</source>
<translation>Demande d&apos;amis</translation> <translation>Demande d&apos;amis</translation>
</message> </message>
@ -145,27 +145,27 @@ can produce IP leak</source>
<translation type="obsolete">Toxygen est un client Tox écris en Python 2.7. Version : </translation> <translation type="obsolete">Toxygen est un client Tox écris en Python 2.7. Version : </translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="509"/> <location filename="mainscreen.py" line="528"/>
<source>Choose file</source> <source>Choose file</source>
<translation>Choisir un fichier</translation> <translation>Choisir un fichier</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="574"/> <location filename="mainscreen.py" line="593"/>
<source>Disallow auto accept</source> <source>Disallow auto accept</source>
<translation>Désactiver l&apos;auto-réception</translation> <translation>Désactiver l&apos;auto-réception</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="575"/> <location filename="mainscreen.py" line="594"/>
<source>Allow auto accept</source> <source>Allow auto accept</source>
<translation>Activer l&apos;auto-réception</translation> <translation>Activer l&apos;auto-réception</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="577"/> <location filename="mainscreen.py" line="596"/>
<source>Set alias</source> <source>Set alias</source>
<translation>Définir un alias</translation> <translation>Définir un alias</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="580"/> <location filename="mainscreen.py" line="599"/>
<source>Clear history</source> <source>Clear history</source>
<translation>Vider l&apos;historique</translation> <translation>Vider l&apos;historique</translation>
</message> </message>
@ -175,17 +175,17 @@ can produce IP leak</source>
<translation type="obsolete">Copier la clé publique</translation> <translation type="obsolete">Copier la clé publique</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="590"/> <location filename="mainscreen.py" line="609"/>
<source>Remove friend</source> <source>Remove friend</source>
<translation>Retirer un ami</translation> <translation>Retirer un ami</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="655"/> <location filename="profile.py" line="668"/>
<source>Enter new alias for friend {} or leave empty to use friend&apos;s name:</source> <source>Enter new alias for friend {} or leave empty to use friend&apos;s name:</source>
<translation>Entrez un nouvel alias pour l&apos;ami {} ou laissez vide pour garder son nom de base :</translation> <translation>Entrez un nouvel alias pour l&apos;ami {} ou laissez vide pour garder son nom de base :</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="125"/> <location filename="mainscreen.py" line="130"/>
<source>Audio</source> <source>Audio</source>
<translation>Audio</translation> <translation>Audio</translation>
</message> </message>
@ -195,24 +195,24 @@ can produce IP leak</source>
<translation type="obsolete">Trouver le contact</translation> <translation type="obsolete">Trouver le contact</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="788"/> <location filename="profile.py" line="801"/>
<source>Friend added</source> <source>Friend added</source>
<translation>Ami rajouté</translation> <translation>Ami rajouté</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="415"/> <location filename="mainscreen.py" line="427"/>
<source>Toxygen is Tox client written on Python. <source>Toxygen is Tox client written on Python.
Version: </source> Version: </source>
<translation>Toxygen est un client Tox écrit en Python. <translation>Toxygen est un client Tox écrit en Python.
Version :</translation> Version :</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="789"/> <location filename="profile.py" line="802"/>
<source>Friend added without sending friend request</source> <source>Friend added without sending friend request</source>
<translation>Ami rajouté sans avoir envoyé de demande</translation> <translation>Ami rajouté sans avoir envoyé de demande</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="632"/> <location filename="mainscreen.py" line="655"/>
<source>Choose folder</source> <source>Choose folder</source>
<translation>Choisir le dossier</translation> <translation>Choisir le dossier</translation>
</message> </message>
@ -227,47 +227,47 @@ Version :</translation>
<translation type="obsolete">Envoyer le fichier</translation> <translation type="obsolete">Envoyer le fichier</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="128"/> <location filename="mainscreen.py" line="133"/>
<source>Send message</source> <source>Send message</source>
<translation>Envoyer le message</translation> <translation>Envoyer le message</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="129"/> <location filename="mainscreen.py" line="134"/>
<source>Start audio call with friend</source> <source>Start audio call with friend</source>
<translation>Lancer un appel audio avec un ami</translation> <translation>Lancer un appel audio avec un ami</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="595"/> <location filename="mainscreen.py" line="617"/>
<source>Plugins</source> <source>Plugins</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="113"/> <location filename="mainscreen.py" line="118"/>
<source>List of plugins</source> <source>List of plugins</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="127"/> <location filename="mainscreen_widgets.py" line="462"/>
<source>Search</source> <source>Search</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="131"/> <location filename="mainscreen.py" line="136"/>
<source>All</source> <source>All</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="132"/> <location filename="mainscreen.py" line="137"/>
<source>Online</source> <source>Online</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="591"/> <location filename="mainscreen.py" line="611"/>
<source>Notes</source> <source>Notes</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="616"/> <location filename="mainscreen.py" line="639"/>
<source>Notes about user</source> <source>Notes about user</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -317,42 +317,42 @@ Version :</translation>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="303"/> <location filename="profile.py" line="315"/>
<source>User {} is now known as {}</source> <source>User {} is now known as {}</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="list_items.py" line="167"/> <location filename="list_items.py" line="168"/>
<source>Delete message</source> <source>Delete message</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="111"/> <location filename="mainscreen.py" line="116"/>
<source>Lock</source> <source>Lock</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="482"/> <location filename="mainscreen.py" line="501"/>
<source>Cannot lock app</source> <source>Cannot lock app</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="485"/> <location filename="mainscreen.py" line="504"/>
<source>Error. Profile password is not set.</source> <source>Error. Profile password is not set.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="585"/> <location filename="mainscreen.py" line="604"/>
<source>Name</source> <source>Name</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="586"/> <location filename="mainscreen.py" line="605"/>
<source>Status message</source> <source>Status message</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="587"/> <location filename="mainscreen.py" line="606"/>
<source>Public key</source> <source>Public key</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -377,65 +377,85 @@ Version :</translation>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="140"/> <location filename="mainscreen.py" line="145"/>
<source>Import plugin</source> <source>Import plugin</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="460"/> <location filename="mainscreen.py" line="479"/>
<source>Choose folder with plugin</source> <source>Choose folder with plugin</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="469"/> <location filename="mainscreen.py" line="488"/>
<source>Restart Toxygen</source> <source>Restart Toxygen</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="472"/> <location filename="mainscreen.py" line="491"/>
<source>Plugin will be loaded after restart</source> <source>Plugin will be loaded after restart</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="list_items.py" line="48"/> <location filename="list_items.py" line="49"/>
<source>Quote selected text</source> <source>Quote selected text</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="579"/> <location filename="mainscreen.py" line="598"/>
<source>Chat history</source> <source>Chat history</source>
<translation type="unfinished">Historique de chat</translation> <translation type="unfinished">Historique de chat</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="581"/> <location filename="mainscreen.py" line="600"/>
<source>Export as text</source> <source>Export as text</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="582"/> <location filename="mainscreen.py" line="601"/>
<source>Export as HTML</source> <source>Export as HTML</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="126"/> <location filename="mainscreen.py" line="131"/>
<source>Updates</source> <source>Updates</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="133"/> <location filename="mainscreen.py" line="138"/>
<source>Online first</source> <source>Online first</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="135"/> <location filename="mainscreen.py" line="140"/>
<source>Online and by name</source> <source>Online and by name</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="136"/> <location filename="mainscreen.py" line="141"/>
<source>Online first and by name</source> <source>Online first and by name</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="mainscreen.py" line="610"/>
<source>Block friend</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen_widgets.py" line="528"/>
<source>Not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen_widgets.py" line="524"/>
<source>Text &quot;{}&quot; was not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="146"/>
<source>Reload plugins</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>MenuWindow</name> <name>MenuWindow</name>
@ -768,30 +788,25 @@ Version :</translation>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen_widgets.py" line="374"/> <location filename="mainscreen_widgets.py" line="370"/>
<source>Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.</source> <source>Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen_widgets.py" line="386"/> <location filename="mainscreen_widgets.py" line="382"/>
<source>Set new NoSpam to avoid spam friend requests: Profile -&gt; Settings -&gt; Set new NoSpam.</source> <source>Set new NoSpam to avoid spam friend requests: Profile -&gt; Settings -&gt; Set new NoSpam.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen_widgets.py" line="378"/> <location filename="mainscreen_widgets.py" line="374"/>
<source>Delete single message in chat: make right click on spinner or message time and choose &quot;Delete&quot; in menu</source> <source>Delete single message in chat: make right click on spinner or message time and choose &quot;Delete&quot; in menu</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="mainscreen_widgets.py" line="382"/> <location filename="mainscreen_widgets.py" line="378"/>
<source>Use right click on inline image to save it</source> <source>Use right click on inline image to save it</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="mainscreen_widgets.py" line="370"/>
<source>New in Toxygen v0.2.6:&lt;br&gt;Updater&lt;br&gt;Better contact sorting&lt;br&gt;Plugins improvements</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>audioSettingsForm</name> <name>audioSettingsForm</name>
@ -814,32 +829,32 @@ Version :</translation>
<context> <context>
<name>incoming_call</name> <name>incoming_call</name>
<message> <message>
<location filename="profile.py" line="1214"/> <location filename="profile.py" line="1229"/>
<source>Incoming video call</source> <source>Incoming video call</source>
<translation>Appel vidéo entrant</translation> <translation>Appel vidéo entrant</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="1217"/> <location filename="profile.py" line="1232"/>
<source>Incoming audio call</source> <source>Incoming audio call</source>
<translation>Appel audio entrant</translation> <translation>Appel audio entrant</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="1195"/> <location filename="profile.py" line="1210"/>
<source>Outgoing video call</source> <source>Outgoing video call</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="1198"/> <location filename="profile.py" line="1213"/>
<source>Outgoing audio call</source> <source>Outgoing audio call</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="1248"/> <location filename="profile.py" line="1263"/>
<source>Call declined</source> <source>Call declined</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="1250"/> <location filename="profile.py" line="1265"/>
<source>Call finished</source> <source>Call finished</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -1000,7 +1015,7 @@ Version :</translation>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="main.py" line="288"/> <location filename="main.py" line="290"/>
<source>Update for Toxygen was found. Download and install it?</source> <source>Update for Toxygen was found. Download and install it?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -1137,32 +1152,32 @@ Version :</translation>
<context> <context>
<name>tray</name> <name>tray</name>
<message> <message>
<location filename="main.py" line="228"/> <location filename="main.py" line="229"/>
<source>Open Toxygen</source> <source>Open Toxygen</source>
<translation>Ouvrir Toxygen</translation> <translation>Ouvrir Toxygen</translation>
</message> </message>
<message> <message>
<location filename="main.py" line="237"/> <location filename="main.py" line="238"/>
<source>Exit</source> <source>Exit</source>
<translation>Quitter</translation> <translation>Quitter</translation>
</message> </message>
<message> <message>
<location filename="main.py" line="229"/> <location filename="main.py" line="230"/>
<source>Set status</source> <source>Set status</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="main.py" line="230"/> <location filename="main.py" line="231"/>
<source>Online</source> <source>Online</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="main.py" line="231"/> <location filename="main.py" line="232"/>
<source>Away</source> <source>Away</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="main.py" line="232"/> <location filename="main.py" line="233"/>
<source>Busy</source> <source>Busy</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>

Binary file not shown.

View File

@ -87,84 +87,84 @@ can produce IP leak</source>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>
<message> <message>
<location filename="mainscreen.py" line="118"/> <location filename="mainscreen.py" line="123"/>
<source>Profile</source> <source>Profile</source>
<translation>Профиль</translation> <translation>Профиль</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="124"/> <location filename="mainscreen.py" line="129"/>
<source>Settings</source> <source>Settings</source>
<translation>Настройки</translation> <translation>Настройки</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="414"/> <location filename="mainscreen.py" line="426"/>
<source>About</source> <source>About</source>
<translation>О программе</translation> <translation>О программе</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="117"/> <location filename="mainscreen.py" line="122"/>
<source>Add contact</source> <source>Add contact</source>
<translation>Добавить контакт</translation> <translation>Добавить контакт</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="119"/> <location filename="mainscreen.py" line="124"/>
<source>Privacy</source> <source>Privacy</source>
<translation>Приватность</translation> <translation>Приватность</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="120"/> <location filename="mainscreen.py" line="125"/>
<source>Interface</source> <source>Interface</source>
<translation>Интерфейс</translation> <translation>Интерфейс</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="121"/> <location filename="mainscreen.py" line="126"/>
<source>Notifications</source> <source>Notifications</source>
<translation>Уведомления</translation> <translation>Уведомления</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="122"/> <location filename="mainscreen.py" line="127"/>
<source>Network</source> <source>Network</source>
<translation>Сеть</translation> <translation>Сеть</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="123"/> <location filename="mainscreen.py" line="128"/>
<source>About program</source> <source>About program</source>
<translation>О программе</translation> <translation>О программе</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="816"/> <location filename="profile.py" line="829"/>
<source>User {} wants to add you to contact list. Message: <source>User {} wants to add you to contact list. Message:
{}</source> {}</source>
<translation>Пользователь {} хочет добавить Вас в список контактов. Сообщение: <translation>Пользователь {} хочет добавить Вас в список контактов. Сообщение:
{}</translation> {}</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="818"/> <location filename="profile.py" line="831"/>
<source>Friend request</source> <source>Friend request</source>
<translation>Запрос на добавление в друзья</translation> <translation>Запрос на добавление в друзья</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="509"/> <location filename="mainscreen.py" line="528"/>
<source>Choose file</source> <source>Choose file</source>
<translation>Выберите файл</translation> <translation>Выберите файл</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="574"/> <location filename="mainscreen.py" line="593"/>
<source>Disallow auto accept</source> <source>Disallow auto accept</source>
<translation>Запретить автоматическое получение файлов</translation> <translation>Запретить автоматическое получение файлов</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="575"/> <location filename="mainscreen.py" line="594"/>
<source>Allow auto accept</source> <source>Allow auto accept</source>
<translation>Разрешить автоматическое получение файлов</translation> <translation>Разрешить автоматическое получение файлов</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="577"/> <location filename="mainscreen.py" line="596"/>
<source>Set alias</source> <source>Set alias</source>
<translation>Изменить псевдоним</translation> <translation>Изменить псевдоним</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="580"/> <location filename="mainscreen.py" line="599"/>
<source>Clear history</source> <source>Clear history</source>
<translation>Очистить историю</translation> <translation>Очистить историю</translation>
</message> </message>
@ -174,17 +174,17 @@ can produce IP leak</source>
<translation type="obsolete">Копировать публичный ключ</translation> <translation type="obsolete">Копировать публичный ключ</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="590"/> <location filename="mainscreen.py" line="609"/>
<source>Remove friend</source> <source>Remove friend</source>
<translation>Удалить друга</translation> <translation>Удалить друга</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="655"/> <location filename="profile.py" line="668"/>
<source>Enter new alias for friend {} or leave empty to use friend&apos;s name:</source> <source>Enter new alias for friend {} or leave empty to use friend&apos;s name:</source>
<translation>Введите новый псевдоним для друга {} или оставьте пустым для использования его имени:</translation> <translation>Введите новый псевдоним для друга {} или оставьте пустым для использования его имени:</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="125"/> <location filename="mainscreen.py" line="130"/>
<source>Audio</source> <source>Audio</source>
<translation>Аудио</translation> <translation>Аудио</translation>
</message> </message>
@ -194,23 +194,23 @@ can produce IP leak</source>
<translation type="obsolete">Найти контакт</translation> <translation type="obsolete">Найти контакт</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="788"/> <location filename="profile.py" line="801"/>
<source>Friend added</source> <source>Friend added</source>
<translation>Друг добавлен</translation> <translation>Друг добавлен</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="415"/> <location filename="mainscreen.py" line="427"/>
<source>Toxygen is Tox client written on Python. <source>Toxygen is Tox client written on Python.
Version: </source> Version: </source>
<translation>Toxygen - клиент для мессенджера Tox, написанный на Python. Версия: </translation> <translation>Toxygen - клиент для мессенджера Tox, написанный на Python. Версия: </translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="789"/> <location filename="profile.py" line="802"/>
<source>Friend added without sending friend request</source> <source>Friend added without sending friend request</source>
<translation>Друг добавлен без отправки запроса на добавление в друзья</translation> <translation>Друг добавлен без отправки запроса на добавление в друзья</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="632"/> <location filename="mainscreen.py" line="655"/>
<source>Choose folder</source> <source>Choose folder</source>
<translation>Выбрать папку</translation> <translation>Выбрать папку</translation>
</message> </message>
@ -225,47 +225,47 @@ Version: </source>
<translation type="obsolete">Отправить файл</translation> <translation type="obsolete">Отправить файл</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="128"/> <location filename="mainscreen.py" line="133"/>
<source>Send message</source> <source>Send message</source>
<translation>Отправить сообщение</translation> <translation>Отправить сообщение</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="129"/> <location filename="mainscreen.py" line="134"/>
<source>Start audio call with friend</source> <source>Start audio call with friend</source>
<translation>Начать аудиозвонок с другом</translation> <translation>Начать аудиозвонок с другом</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="595"/> <location filename="mainscreen.py" line="617"/>
<source>Plugins</source> <source>Plugins</source>
<translation>Плагины</translation> <translation>Плагины</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="113"/> <location filename="mainscreen.py" line="118"/>
<source>List of plugins</source> <source>List of plugins</source>
<translation>Список плагинов</translation> <translation>Список плагинов</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="127"/> <location filename="mainscreen_widgets.py" line="462"/>
<source>Search</source> <source>Search</source>
<translation>Поиск</translation> <translation>Поиск</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="131"/> <location filename="mainscreen.py" line="136"/>
<source>All</source> <source>All</source>
<translation>Все</translation> <translation>Все</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="132"/> <location filename="mainscreen.py" line="137"/>
<source>Online</source> <source>Online</source>
<translation>Онлайн</translation> <translation>Онлайн</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="591"/> <location filename="mainscreen.py" line="611"/>
<source>Notes</source> <source>Notes</source>
<translation>Заметки</translation> <translation>Заметки</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="616"/> <location filename="mainscreen.py" line="639"/>
<source>Notes about user</source> <source>Notes about user</source>
<translation>Заметки о пользователе</translation> <translation>Заметки о пользователе</translation>
</message> </message>
@ -315,42 +315,42 @@ Version: </source>
<translation>Сохранить</translation> <translation>Сохранить</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="303"/> <location filename="profile.py" line="315"/>
<source>User {} is now known as {}</source> <source>User {} is now known as {}</source>
<translation>Пользователь {} сейчас известен как {}</translation> <translation>Пользователь {} сейчас известен как {}</translation>
</message> </message>
<message> <message>
<location filename="list_items.py" line="167"/> <location filename="list_items.py" line="168"/>
<source>Delete message</source> <source>Delete message</source>
<translation>Удалить сообщение</translation> <translation>Удалить сообщение</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="111"/> <location filename="mainscreen.py" line="116"/>
<source>Lock</source> <source>Lock</source>
<translation>Заблокировать</translation> <translation>Заблокировать</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="482"/> <location filename="mainscreen.py" line="501"/>
<source>Cannot lock app</source> <source>Cannot lock app</source>
<translation>Невозможно заблокировать приложение</translation> <translation>Невозможно заблокировать приложение</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="485"/> <location filename="mainscreen.py" line="504"/>
<source>Error. Profile password is not set.</source> <source>Error. Profile password is not set.</source>
<translation>Ошибка. Пароль профиля не установлен.</translation> <translation>Ошибка. Пароль профиля не установлен.</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="585"/> <location filename="mainscreen.py" line="604"/>
<source>Name</source> <source>Name</source>
<translation>Имя</translation> <translation>Имя</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="586"/> <location filename="mainscreen.py" line="605"/>
<source>Status message</source> <source>Status message</source>
<translation>Статус</translation> <translation>Статус</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="587"/> <location filename="mainscreen.py" line="606"/>
<source>Public key</source> <source>Public key</source>
<translation>Публичный ключ</translation> <translation>Публичный ключ</translation>
</message> </message>
@ -375,65 +375,85 @@ Version: </source>
<translation>Выберите папку с паком смайлов</translation> <translation>Выберите папку с паком смайлов</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="140"/> <location filename="mainscreen.py" line="145"/>
<source>Import plugin</source> <source>Import plugin</source>
<translation>Импортировать плагин</translation> <translation>Импортировать плагин</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="460"/> <location filename="mainscreen.py" line="479"/>
<source>Choose folder with plugin</source> <source>Choose folder with plugin</source>
<translation>Выберите папку с плагином</translation> <translation>Выберите папку с плагином</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="469"/> <location filename="mainscreen.py" line="488"/>
<source>Restart Toxygen</source> <source>Restart Toxygen</source>
<translation>Перезапустите Toxygen</translation> <translation>Перезапустите Toxygen</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="472"/> <location filename="mainscreen.py" line="491"/>
<source>Plugin will be loaded after restart</source> <source>Plugin will be loaded after restart</source>
<translation>Плагин будет загружен после перезапуска</translation> <translation>Плагин будет загружен после перезапуска</translation>
</message> </message>
<message> <message>
<location filename="list_items.py" line="48"/> <location filename="list_items.py" line="49"/>
<source>Quote selected text</source> <source>Quote selected text</source>
<translation>Цитировать выбранный текст</translation> <translation>Цитировать выбранный текст</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="579"/> <location filename="mainscreen.py" line="598"/>
<source>Chat history</source> <source>Chat history</source>
<translation>История чата</translation> <translation>История чата</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="581"/> <location filename="mainscreen.py" line="600"/>
<source>Export as text</source> <source>Export as text</source>
<translation>Экспортировать как текст</translation> <translation>Экспортировать как текст</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="582"/> <location filename="mainscreen.py" line="601"/>
<source>Export as HTML</source> <source>Export as HTML</source>
<translation>Экспортировать как HTML</translation> <translation>Экспортировать как HTML</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="126"/> <location filename="mainscreen.py" line="131"/>
<source>Updates</source> <source>Updates</source>
<translation>Обновления</translation> <translation>Обновления</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="133"/> <location filename="mainscreen.py" line="138"/>
<source>Online first</source> <source>Online first</source>
<translation>Сначала онлайн</translation> <translation>Сначала онлайн</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="135"/> <location filename="mainscreen.py" line="140"/>
<source>Online and by name</source> <source>Online and by name</source>
<translation>Онлайн и по имени</translation> <translation>Онлайн и по имени</translation>
</message> </message>
<message> <message>
<location filename="mainscreen.py" line="136"/> <location filename="mainscreen.py" line="141"/>
<source>Online first and by name</source> <source>Online first and by name</source>
<translation>Сначала онлайн и по имени</translation> <translation>Сначала онлайн и по имени</translation>
</message> </message>
<message>
<location filename="mainscreen.py" line="610"/>
<source>Block friend</source>
<translation>Заблокировать друга</translation>
</message>
<message>
<location filename="mainscreen_widgets.py" line="528"/>
<source>Not found</source>
<translation>Не найдено</translation>
</message>
<message>
<location filename="mainscreen_widgets.py" line="524"/>
<source>Text &quot;{}&quot; was not found</source>
<translation>Текст &quot;{}&quot; не был найден</translation>
</message>
<message>
<location filename="mainscreen.py" line="146"/>
<source>Reload plugins</source>
<translation>Перезагрузить плагины</translation>
</message>
</context> </context>
<context> <context>
<name>MenuWindow</name> <name>MenuWindow</name>
@ -796,12 +816,12 @@ Version: </source>
<translation>Используйте Настройки -&gt; Интерфейс для настройки интерфейса.</translation> <translation>Используйте Настройки -&gt; Интерфейс для настройки интерфейса.</translation>
</message> </message>
<message> <message>
<location filename="mainscreen_widgets.py" line="374"/> <location filename="mainscreen_widgets.py" line="370"/>
<source>Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.</source> <source>Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.</source>
<translation>Toxygen поддерживает псевдооффлайн сообщения и файл трансферы.</translation> <translation>Toxygen поддерживает псевдооффлайн сообщения и файл трансферы.</translation>
</message> </message>
<message> <message>
<location filename="mainscreen_widgets.py" line="386"/> <location filename="mainscreen_widgets.py" line="382"/>
<source>Set new NoSpam to avoid spam friend requests: Profile -&gt; Settings -&gt; Set new NoSpam.</source> <source>Set new NoSpam to avoid spam friend requests: Profile -&gt; Settings -&gt; Set new NoSpam.</source>
<translation>Установите новый NoSpam, чтобы избежать спам запросов в друзья: Профиль-&gt;Настройки-&gt;Новый NoSpam.</translation> <translation>Установите новый NoSpam, чтобы избежать спам запросов в друзья: Профиль-&gt;Настройки-&gt;Новый NoSpam.</translation>
</message> </message>
@ -811,12 +831,12 @@ Version: </source>
<translation type="obsolete">Новое в Toxygen 0.2.3:&lt;br&gt;Соответствие TCS&lt;br&gt;Импорт плагинов, смайлов и стикеров&lt;br&gt;Исправления ошибок</translation> <translation type="obsolete">Новое в Toxygen 0.2.3:&lt;br&gt;Соответствие TCS&lt;br&gt;Импорт плагинов, смайлов и стикеров&lt;br&gt;Исправления ошибок</translation>
</message> </message>
<message> <message>
<location filename="mainscreen_widgets.py" line="378"/> <location filename="mainscreen_widgets.py" line="374"/>
<source>Delete single message in chat: make right click on spinner or message time and choose &quot;Delete&quot; in menu</source> <source>Delete single message in chat: make right click on spinner or message time and choose &quot;Delete&quot; in menu</source>
<translation>Чтобы удалить отдельное сообщение в чате сделайте правый клик на спиннер или время сообщения и выберите &quot;Удалить&quot; в меню</translation> <translation>Чтобы удалить отдельное сообщение в чате сделайте правый клик на спиннер или время сообщения и выберите &quot;Удалить&quot; в меню</translation>
</message> </message>
<message> <message>
<location filename="mainscreen_widgets.py" line="382"/> <location filename="mainscreen_widgets.py" line="378"/>
<source>Use right click on inline image to save it</source> <source>Use right click on inline image to save it</source>
<translation>Правый клик на инлайн изображении позволит сохранить его</translation> <translation>Правый клик на инлайн изображении позволит сохранить его</translation>
</message> </message>
@ -828,7 +848,7 @@ Version: </source>
<message> <message>
<location filename="mainscreen_widgets.py" line="370"/> <location filename="mainscreen_widgets.py" line="370"/>
<source>New in Toxygen v0.2.6:&lt;br&gt;Updater&lt;br&gt;Better contact sorting&lt;br&gt;Plugins improvements</source> <source>New in Toxygen v0.2.6:&lt;br&gt;Updater&lt;br&gt;Better contact sorting&lt;br&gt;Plugins improvements</source>
<translation>Новое в Toxygen v0.2.6:&lt;br&gt;Поддержка обновлений&lt;br&gt;Улучшенная сортировка контактов&lt;br&gt;Улучшения в работе плагинов</translation> <translation type="obsolete">Новое в Toxygen v0.2.6:&lt;br&gt;Поддержка обновлений&lt;br&gt;Улучшенная сортировка контактов&lt;br&gt;Улучшения в работе плагинов</translation>
</message> </message>
</context> </context>
<context> <context>
@ -852,32 +872,32 @@ Version: </source>
<context> <context>
<name>incoming_call</name> <name>incoming_call</name>
<message> <message>
<location filename="profile.py" line="1214"/> <location filename="profile.py" line="1229"/>
<source>Incoming video call</source> <source>Incoming video call</source>
<translation>Входящий видеозвонок</translation> <translation>Входящий видеозвонок</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="1217"/> <location filename="profile.py" line="1232"/>
<source>Incoming audio call</source> <source>Incoming audio call</source>
<translation>Входящий аудиозвонок</translation> <translation>Входящий аудиозвонок</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="1195"/> <location filename="profile.py" line="1210"/>
<source>Outgoing video call</source> <source>Outgoing video call</source>
<translation>Исходящий видеозвонок</translation> <translation>Исходящий видеозвонок</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="1198"/> <location filename="profile.py" line="1213"/>
<source>Outgoing audio call</source> <source>Outgoing audio call</source>
<translation>Исходящий аудиозвонок</translation> <translation>Исходящий аудиозвонок</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="1248"/> <location filename="profile.py" line="1263"/>
<source>Call declined</source> <source>Call declined</source>
<translation>Звонок отменен</translation> <translation>Звонок отменен</translation>
</message> </message>
<message> <message>
<location filename="profile.py" line="1250"/> <location filename="profile.py" line="1265"/>
<source>Call finished</source> <source>Call finished</source>
<translation>Звонок завершен</translation> <translation>Звонок завершен</translation>
</message> </message>
@ -1038,7 +1058,7 @@ Version: </source>
<translation>Ошибка сохранения профиля! Toxygen имеет разрешение на запись в данную папку?</translation> <translation>Ошибка сохранения профиля! Toxygen имеет разрешение на запись в данную папку?</translation>
</message> </message>
<message> <message>
<location filename="main.py" line="288"/> <location filename="main.py" line="290"/>
<source>Update for Toxygen was found. Download and install it?</source> <source>Update for Toxygen was found. Download and install it?</source>
<translation>Обновление для Toxygen было найдено. Загрузить и установить его?</translation> <translation>Обновление для Toxygen было найдено. Загрузить и установить его?</translation>
</message> </message>
@ -1077,7 +1097,7 @@ Version: </source>
<message> <message>
<location filename="passwordscreen.py" line="65"/> <location filename="passwordscreen.py" line="65"/>
<source>Password:</source> <source>Password:</source>
<translation>Пароль</translation> <translation>Пароль:</translation>
</message> </message>
<message> <message>
<location filename="passwordscreen.py" line="66"/> <location filename="passwordscreen.py" line="66"/>
@ -1176,32 +1196,32 @@ Version: </source>
<context> <context>
<name>tray</name> <name>tray</name>
<message> <message>
<location filename="main.py" line="228"/> <location filename="main.py" line="229"/>
<source>Open Toxygen</source> <source>Open Toxygen</source>
<translation>Открыть Toxygen</translation> <translation>Открыть Toxygen</translation>
</message> </message>
<message> <message>
<location filename="main.py" line="237"/> <location filename="main.py" line="238"/>
<source>Exit</source> <source>Exit</source>
<translation>Выход</translation> <translation>Выход</translation>
</message> </message>
<message> <message>
<location filename="main.py" line="229"/> <location filename="main.py" line="230"/>
<source>Set status</source> <source>Set status</source>
<translation>Изменить статус</translation> <translation>Изменить статус</translation>
</message> </message>
<message> <message>
<location filename="main.py" line="230"/> <location filename="main.py" line="231"/>
<source>Online</source> <source>Online</source>
<translation>Онлайн</translation> <translation>Онлайн</translation>
</message> </message>
<message> <message>
<location filename="main.py" line="231"/> <location filename="main.py" line="232"/>
<source>Away</source> <source>Away</source>
<translation>Нет на месте</translation> <translation>Нет на месте</translation>
</message> </message>
<message> <message>
<location filename="main.py" line="232"/> <location filename="main.py" line="233"/>
<source>Busy</source> <source>Busy</source>
<translation>Занят</translation> <translation>Занят</translation>
</message> </message>

View File

@ -3,10 +3,7 @@ import os
import settings import settings
import platform import platform
import urllib import urllib
try: from PyQt5 import QtNetwork, QtCore
from PySide import QtNetwork, QtCore
except ImportError:
from PyQt4 import QtNetwork, QtCore
import subprocess import subprocess

View File

@ -2,8 +2,9 @@ import os
import time import time
import shutil import shutil
import sys import sys
import re
program_version = '0.2.6' program_version = '0.3.0'
def log(data): def log(data):
@ -37,7 +38,7 @@ def remove(folder):
def convert_time(t): def convert_time(t):
offset = time.timezone - time.daylight * 3600 offset = time.timezone + time_offset() * 60
sec = int(t) - offset sec = int(t) - offset
m, s = divmod(sec, 60) m, s = divmod(sec, 60)
h, m = divmod(m, 60) h, m = divmod(m, 60)
@ -45,6 +46,20 @@ def convert_time(t):
return '%02d:%02d' % (h, m) return '%02d:%02d' % (h, m)
def time_offset():
if hasattr(time_offset, 'offset'):
return time_offset.offset
hours = int(time.strftime('%H'))
minutes = int(time.strftime('%M'))
sec = int(time.time()) - time.timezone
m, s = divmod(sec, 60)
h, m = divmod(m, 60)
d, h = divmod(h, 24)
result = hours * 60 + minutes - h * 60 - m
time_offset.offset = result
return result
def append_slash(s): def append_slash(s):
if len(s) and s[-1] not in ('\\', '/'): if len(s) and s[-1] not in ('\\', '/'):
s += '/' s += '/'
@ -55,6 +70,15 @@ def is_64_bit():
return sys.maxsize > 2 ** 32 return sys.maxsize > 2 ** 32
def is_re_valid(regex):
try:
re.compile(regex)
except re.error:
return False
else:
return True
class Singleton: class Singleton:
_instance = None _instance = None

View File

@ -1,28 +1,25 @@
try: from PyQt5 import QtCore, QtGui, QtWidgets
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
class DataLabel(QtGui.QLabel): class DataLabel(QtWidgets.QLabel):
""" """
Label with elided text Label with elided text
""" """
def setText(self, text): def setText(self, text):
text = ''.join(c if c <= '\U0010FFFF' else '\u25AF' for c in text) text = ''.join('\u25AF' if len(bytes(c, 'utf-8')) >= 4 else c for c in text)
metrics = QtGui.QFontMetrics(self.font()) metrics = QtGui.QFontMetrics(self.font())
text = metrics.elidedText(text, QtCore.Qt.ElideRight, self.width()) text = metrics.elidedText(text, QtCore.Qt.ElideRight, self.width())
super().setText(text) super().setText(text)
class ComboBox(QtGui.QComboBox): class ComboBox(QtWidgets.QComboBox):
def __init__(self, *args): def __init__(self, *args):
super().__init__(*args) super().__init__(*args)
self.view().setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Expanding) self.view().setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
class CenteredWidget(QtGui.QWidget): class CenteredWidget(QtWidgets.QWidget):
def __init__(self): def __init__(self):
super(CenteredWidget, self).__init__() super(CenteredWidget, self).__init__()
@ -30,12 +27,12 @@ class CenteredWidget(QtGui.QWidget):
def center(self): def center(self):
qr = self.frameGeometry() qr = self.frameGeometry()
cp = QtGui.QDesktopWidget().availableGeometry().center() cp = QtWidgets.QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp) qr.moveCenter(cp)
self.move(qr.topLeft()) self.move(qr.topLeft())
class LineEdit(QtGui.QLineEdit): class LineEdit(QtWidgets.QLineEdit):
def __init__(self, parent=None): def __init__(self, parent=None):
super(LineEdit, self).__init__(parent) super(LineEdit, self).__init__(parent)
@ -46,25 +43,27 @@ class LineEdit(QtGui.QLineEdit):
del menu del menu
class QRightClickButton(QtGui.QPushButton): class QRightClickButton(QtWidgets.QPushButton):
""" """
Button with right click support Button with right click support
""" """
rightClicked = QtCore.pyqtSignal()
def __init__(self, parent): def __init__(self, parent):
super(QRightClickButton, self).__init__(parent) super(QRightClickButton, self).__init__(parent)
def mousePressEvent(self, event): def mousePressEvent(self, event):
if event.button() == QtCore.Qt.RightButton: if event.button() == QtCore.Qt.RightButton:
self.emit(QtCore.SIGNAL("rightClicked()")) self.rightClicked.emit()
else: else:
super(QRightClickButton, self).mousePressEvent(event) super(QRightClickButton, self).mousePressEvent(event)
class RubberBand(QtGui.QRubberBand): class RubberBand(QtWidgets.QRubberBand):
def __init__(self): def __init__(self):
super(RubberBand, self).__init__(QtGui.QRubberBand.Rectangle, None) super(RubberBand, self).__init__(QtWidgets.QRubberBand.Rectangle, None)
self.setPalette(QtGui.QPalette(QtCore.Qt.transparent)) self.setPalette(QtGui.QPalette(QtCore.Qt.transparent))
self.pen = QtGui.QPen(QtCore.Qt.blue, 4) self.pen = QtGui.QPen(QtCore.Qt.blue, 4)
self.pen.setStyle(QtCore.Qt.SolidLine) self.pen.setStyle(QtCore.Qt.SolidLine)
@ -86,29 +85,21 @@ def create_menu(menu):
text = action.text() text = action.text()
if 'Link Location' in text: if 'Link Location' in text:
text = text.replace('Copy &Link Location', text = text.replace('Copy &Link Location',
QtGui.QApplication.translate("MainWindow", "Copy link location", None, QtWidgets.QApplication.translate("MainWindow", "Copy link location"))
QtGui.QApplication.UnicodeUTF8))
elif '&Copy' in text: elif '&Copy' in text:
text = text.replace('&Copy', QtGui.QApplication.translate("MainWindow", "Copy", None, text = text.replace('&Copy', QtWidgets.QApplication.translate("MainWindow", "Copy"))
QtGui.QApplication.UnicodeUTF8))
elif 'All' in text: elif 'All' in text:
text = text.replace('Select All', QtGui.QApplication.translate("MainWindow", "Select all", None, text = text.replace('Select All', QtWidgets.QApplication.translate("MainWindow", "Select all"))
QtGui.QApplication.UnicodeUTF8))
elif 'Delete' in text: elif 'Delete' in text:
text = text.replace('Delete', QtGui.QApplication.translate("MainWindow", "Delete", None, text = text.replace('Delete', QtWidgets.QApplication.translate("MainWindow", "Delete"))
QtGui.QApplication.UnicodeUTF8))
elif '&Paste' in text: elif '&Paste' in text:
text = text.replace('&Paste', QtGui.QApplication.translate("MainWindow", "Paste", None, text = text.replace('&Paste', QtWidgets.QApplication.translate("MainWindow", "Paste"))
QtGui.QApplication.UnicodeUTF8))
elif 'Cu&t' in text: elif 'Cu&t' in text:
text = text.replace('Cu&t', QtGui.QApplication.translate("MainWindow", "Cut", None, text = text.replace('Cu&t', QtWidgets.QApplication.translate("MainWindow", "Cut"))
QtGui.QApplication.UnicodeUTF8))
elif '&Undo' in text: elif '&Undo' in text:
text = text.replace('&Undo', QtGui.QApplication.translate("MainWindow", "Undo", None, text = text.replace('&Undo', QtWidgets.QApplication.translate("MainWindow", "Undo"))
QtGui.QApplication.UnicodeUTF8))
elif '&Redo' in text: elif '&Redo' in text:
text = text.replace('&Redo', QtGui.QApplication.translate("MainWindow", "Redo", None, text = text.replace('&Redo', QtWidgets.QApplication.translate("MainWindow", "Redo"))
QtGui.QApplication.UnicodeUTF8))
else: else:
menu.removeAction(action) menu.removeAction(action)
continue continue
@ -124,12 +115,12 @@ class MultilineEdit(CenteredWidget):
self.setMinimumSize(QtCore.QSize(350, 200)) self.setMinimumSize(QtCore.QSize(350, 200))
self.setMaximumSize(QtCore.QSize(350, 200)) self.setMaximumSize(QtCore.QSize(350, 200))
self.setWindowTitle(title) self.setWindowTitle(title)
self.edit = QtGui.QTextEdit(self) self.edit = QtWidgets.QTextEdit(self)
self.edit.setGeometry(QtCore.QRect(0, 0, 350, 150)) self.edit.setGeometry(QtCore.QRect(0, 0, 350, 150))
self.edit.setText(text) self.edit.setText(text)
self.button = QtGui.QPushButton(self) self.button = QtWidgets.QPushButton(self)
self.button.setGeometry(QtCore.QRect(0, 150, 350, 50)) self.button.setGeometry(QtCore.QRect(0, 150, 350, 50))
self.button.setText(QtGui.QApplication.translate("MainWindow", "Save", None, QtGui.QApplication.UnicodeUTF8)) self.button.setText(QtWidgets.QApplication.translate("MainWindow", "Save"))
self.button.clicked.connect(self.button_click) self.button.clicked.connect(self.button_click)
self.center() self.center()
self.save = save self.save = save