35 Commits

Author SHA1 Message Date
adcc32fc49 resuming fix 2016-08-05 16:40:16 +03:00
61e7aad847 bug fixes 2016-08-05 16:40:16 +03:00
742d853b11 threads fix and update 2016-08-05 16:40:16 +03:00
39fe859fe5 travis fix 2016-08-05 16:40:16 +03:00
2a0895018a thread for incoming file transfers 2016-08-05 16:40:16 +03:00
d1437b3445 file resuming part 3 2016-08-05 16:40:16 +03:00
59154d081f file resuming (part 2) 2016-08-05 16:40:16 +03:00
99e8691f0b file resuming across restarts (partial) 2016-08-05 16:40:15 +03:00
b0e82dfd08 font and closing to tray update 2016-08-05 15:58:25 +03:00
3a64121d72 avatars fix 2016-08-04 18:23:47 +03:00
99f31cc302 utf-8 fix 2016-08-04 16:11:04 +03:00
19de605b79 docs update 2016-08-01 22:07:01 +03:00
9e410254bf messages selection 2016-07-29 15:27:46 +03:00
e970fbed80 todo 2016-07-28 23:49:43 +03:00
9ed62d4414 travis 2016-07-28 23:21:57 +03:00
9516723c7f chat history export 2016-07-28 23:00:04 +03:00
52e6ace847 bug fixes 2016-07-28 18:04:02 +03:00
c7f50af25c docs fixes 2016-07-27 17:59:35 +03:00
7d8646b432 docs update 2016-07-27 17:53:50 +03:00
883a30f806 portability updates 2016-07-27 17:13:57 +03:00
3bd7655203 setup.py update 2016-07-27 15:45:34 +03:00
fdfc74521b some updates and fixes 2016-07-27 14:50:36 +03:00
3db10ead6a pyqt4 fixes 2016-07-26 21:03:18 +03:00
546eb9f042 fix #6 2016-07-26 20:36:50 +03:00
08ef8294df os x and freebsd support (untested) 2016-07-26 19:09:57 +03:00
af5db43248 pyqtSlot fix 2016-07-21 08:03:32 +03:00
697a9efb51 message menu update - plugins, quotes 2016-07-19 23:19:42 +03:00
6297da1c69 drag n drop (windows) 2016-07-19 20:18:29 +03:00
e78ba3942b autoreconnect and pyaudio fix 2016-07-19 15:14:30 +03:00
28cedae342 v0.2.3 2016-07-14 12:32:21 +03:00
3f9a35e164 avatars in chat 2016-07-13 23:09:34 +03:00
babeeb969c smileys, stickers, avatars update 2016-07-13 21:30:51 +03:00
01e6d45232 bug fixes 2016-07-13 17:16:15 +03:00
c865ae4df6 translations and bug fix 2016-07-12 19:40:26 +03:00
59452aa525 docs update 2016-07-12 18:05:33 +03:00
75 changed files with 1363 additions and 710 deletions

31
.travis.yml Normal file
View File

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

View File

@ -10,6 +10,8 @@ include toxygen/smileys/animated/config.json
include toxygen/smileys/starwars/*.gif
include toxygen/smileys/starwars/*.png
include toxygen/smileys/starwars/config.json
include toxygen/smileys/ksk/*.png
include toxygen/smileys/ksk/config.json
include toxygen/styles/style.qss
include toxygen/translations/*.qm
include toxygen/libs/libtox.dll

View File

@ -1,17 +1,22 @@
# Toxygen
Toxygen is cross-platform [Tox](https://tox.chat/) client written in Python3
[![Release](https://img.shields.io/github/release/xveduk/toxygen.svg?style=flat)](https://github.com/xveduk/toxygen/releases/latest)
[![Open issues](https://img.shields.io/github/issues/xveduk/toxygen.svg?style=flat)](https://github.com/xveduk/toxygen/issues)
[![License](https://img.shields.io/badge/license-GPLv3-blue.svg?style=flat)](https://raw.githubusercontent.com/xveduk/toxygen/master/LICENSE.md)
Toxygen is cross-platform [Tox](https://tox.chat/) client written in pure Python3
### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md)
[![Release](https://img.shields.io/github/release/xveduk/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/releases/latest)
[![Stars](https://img.shields.io/github/stars/xveduk/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/stargazers)
[![Open issues](https://img.shields.io/github/issues/xveduk/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/issues)
[![License](https://img.shields.io/badge/license-GPLv3-blue.svg?style=flat)](https://raw.githubusercontent.com/toxygen-project/toxygen/master/LICENSE.md)
### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md) - [Compile](/docs/compile.md) - [Contact](/docs/contact.md)
### Supported OS:
- Windows
- Linux
- OS X
### Features
###Features
- [x] 1v1 messages
- [x] File transfers
- [x] Audio
@ -42,19 +47,17 @@ Toxygen is cross-platform [Tox](https://tox.chat/) client written in Python3
- [ ] Desktop sharing
- [ ] Group chats
###Downloads
[Releases](https://github.com/xveduk/toxygen/releases)
### Downloads
[Releases](https://github.com/toxygen-project/toxygen/releases)
[Download last stable version](https://github.com/xveduk/toxygen/archive/master.zip)
[Download last stable version](https://github.com/toxygen-project/toxygen/archive/master.zip)
[Download develop version](https://github.com/xveduk/toxygen/archive/develop.zip)
[Download develop version](https://github.com/toxygen-project/toxygen/archive/develop.zip)
###Screenshots
### Screenshots
*Toxygen on Ubuntu and Windows*
![Ubuntu](/docs/ubuntu.png)
![Windows](/docs/windows.png)
###Docs
### Docs
[Check /docs/ for more info](/docs/)

View File

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

5
docs/contact.md Normal file
View File

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

View File

@ -3,33 +3,50 @@
## Use precompiled binary:
[Check our releases page](https://github.com/xveduk/toxygen/releases)
##Using pip3
## Using pip3
### Windows (32-bit interpreter)
### Windows
``pip3.4 install toxygen``
Run app using ``toxygen`` command.
##Linux
### Linux
1. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system (install in /usr/lib/)
2. Install PortAudio:
``sudo apt-get install portaudio19-dev``
3. Install toxygen:
3. Install PySide: ``sudo apt-get install python3-pyside``
4. Install toxygen:
``sudo pip3.4 install toxygen``
4 Run toxygen using ``toxygen`` command.
5. Run toxygen using ``toxygen`` command.
### OS X
1. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system
2. Install PortAudio:
``brew install portaudio``
3. Install toxygen:
``pip3 install toxygen``
4. Run toxygen using ``toxygen`` command.
## Packages
Coming soon.
## From source code (recommended for developers)
### Windows
1. [Download and install latest Python 3.4](https://www.python.org/downloads/windows/)
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4#installing-pyside-on-a-windows-system) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
3. Install PyAudio: ``pip3.4 install pyaudio``
4. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
5. Unpack archive
6. Download latest libtox.dll build, download latest libsodium.a build, put it into \src\libs\
7. Run \src\main.py.
7. Run \toxygen\main.py.
Optional: install toxygen using setup.py: ``python3.4 setup.py install``
[libtox.dll for 32-bit Python](https://build.tox.chat/view/libtoxcore/job/libtoxcore_build_windows_x86_shared_release/lastSuccessfulBuild/artifact/libtoxcore_build_windows_x86_shared_release.zip)
@ -39,21 +56,31 @@ Run app using ``toxygen`` command.
[libsodium.a for 64-bit Python](https://build.tox.chat/view/libsodium/job/libsodium_build_windows_x86-64_static_release/lastSuccessfulBuild/artifact/libsodium_build_windows_x86-64_static_release.zip)
### Linux
Dependencies:
1. Install latest Python3.4:
1. Install latest Python3:
``sudo apt-get install python3``
2. [Install PySide](https://wiki.qt.io/PySide_Binaries_Linux) (recommended), using terminal - ``sudo apt-get install python3-pyside``, or install [PyQt4](https://riverbankcomputing.com/software/pyqt/download).
2. Install PySide: ``sudo apt-get install python3-pyside`` or install [PyQt4](https://riverbankcomputing.com/software/pyqt/download) (``sudo apt-get install python3-pyqt4``).
3. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system (install in /usr/lib/)
4. Install PyAudio:
``sudo apt-get install portaudio19-dev`` and ``sudo apt-get install python3-pyaudio``
``sudo apt-get install portaudio19-dev`` and ``sudo apt-get install python3-pyaudio`` (or ``pip3 install pyaudio``)
5. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
6. Unpack archive
7. Run app:
``python3.4 main.py``
## Compile Toxygen
Check [compile.md](/docs/compile.md) for more info
Optional: install toxygen using setup.py: ``python3.4 setup.py install``
### OS X
1. [Download and install latest Python 3.4](https://www.python.org/downloads/mac-osx/)
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4#installing-pyside-on-a-mac-os-x-system) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
3. Install PortAudio:
``brew install portaudio``
4. Install PyAudio: ``pip3 install pyaudio``
5. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system
6. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
7. Unpack archive
8. Run \toxygen\main.py.
Optional: install toxygen using setup.py: ``python3 setup.py install``

View File

@ -12,6 +12,7 @@ All plugin's data should be stored in following structure:
|---plugin_short_name.py
|---/plugin_short_name/
|---settings.json
|---readme.txt
|---logs.txt
|---other_files
```

View File

@ -3,14 +3,20 @@ from setuptools.command.install import install
from platform import system
from subprocess import call
from toxygen.util import program_version
import sys
version = program_version + '.0'
MODULES = ['PyAudio']
MODULES = []
if system() == 'Windows':
MODULES.append('PySide')
if system() in ('Windows', 'Darwin'):
MODULES = ['PyAudio', 'PySide']
else:
try:
import pyaudio
except ImportError:
MODULES = ['PyAudio']
class InstallScript(install):
@ -18,11 +24,25 @@ class InstallScript(install):
def run(self):
install.run(self)
OS = system()
if OS == 'Windows':
try:
if system() == 'Windows':
call(["toxygen", "--configure"])
elif OS == 'Linux':
else:
call(["toxygen", "--clean"])
except:
try:
params = list(filter(lambda x: x.startswith('--prefix='), sys.argv))
if params:
path = params[0][len('--prefix='):]
if path[-1] not in ('/', '\\'):
path += '/'
path += 'bin/toxygen'
if system() == 'Windows':
call([path, "--configure"])
else:
call([path, "--clean"])
except:
pass
setup(name='Toxygen',
version=version,

4
tests/travis.py Normal file
View File

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

View File

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

View File

@ -9,6 +9,14 @@ from toxcore_enums_and_consts import *
from toxav_enums import *
from tox import bin_to_string
from plugin_support import PluginLoader
import queue
import threading
import util
# -----------------------------------------------------------------------------------------------------------------
# Threads
# -----------------------------------------------------------------------------------------------------------------
class InvokeEvent(QtCore.QEvent):
@ -33,6 +41,44 @@ _invoker = Invoker()
def invoke_in_main_thread(fn, *args, **kwargs):
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
class FileTransfersThread(threading.Thread):
def __init__(self):
self._queue = queue.Queue()
self._timeout = 0.01
self._continue = True
super().__init__()
def execute(self, function, *args, **kwargs):
self._queue.put((function, args, kwargs))
def stop(self):
self._continue = False
def run(self):
while self._continue:
try:
function, args, kwargs = self._queue.get(timeout=self._timeout)
function(*args, **kwargs)
except queue.Empty:
pass
except queue.Full:
util.log('Queue is Full in _thread')
except Exception as ex:
util.log('Exception in _thread: ' + str(ex))
_thread = FileTransfersThread()
def start():
_thread.start()
def stop():
_thread.stop()
_thread.join()
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - current user
# -----------------------------------------------------------------------------------------------------------------
@ -68,7 +114,7 @@ def friend_status(tox, friend_num, new_status, user_data):
if friend.status is None and Settings.get_instance()['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
invoke_in_main_thread(friend.set_status, new_status)
invoke_in_main_thread(profile.send_files, friend_num)
invoke_in_main_thread(QtCore.QTimer.singleShot, 5000, lambda: profile.send_files(friend_num))
invoke_in_main_thread(profile.update_filtration)
@ -197,28 +243,15 @@ def file_recv_chunk(tox, friend_number, file_number, position, chunk, length, us
"""
Incoming chunk
"""
if not length:
invoke_in_main_thread(Profile.get_instance().incoming_chunk,
friend_number,
file_number,
position,
None)
else:
Profile.get_instance().incoming_chunk(friend_number, file_number, position, chunk[:length])
_thread.execute(Profile.get_instance().incoming_chunk, friend_number, file_number, position,
chunk[:length] if length else None)
def file_chunk_request(tox, friend_number, file_number, position, size, user_data):
"""
Outgoing chunk
"""
if size:
Profile.get_instance().outgoing_chunk(friend_number, file_number, position, size)
else:
invoke_in_main_thread(Profile.get_instance().outgoing_chunk,
friend_number,
file_number,
position,
size)
def file_recv_control(tox, friend_number, file_number, file_control, user_data):
@ -280,7 +313,6 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud
"""
New audio chunk
"""
# print(audio_samples_per_channel, audio_channels_count, rate)
Profile.get_instance().call.chunk(
bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]),
audio_channels_count,

View File

@ -92,10 +92,9 @@ class Contact:
avatar_path = 'avatar.png'
os.chdir(curr_directory() + '/images/')
width = self._widget.avatar_label.width()
pixmap = QtGui.QPixmap(QtCore.QSize(width, width))
pixmap.load(avatar_path)
self._widget.avatar_label.setScaledContents(False)
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio))
pixmap = QtGui.QPixmap(avatar_path)
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation))
self._widget.avatar_label.repaint()
def reset_avatar(self):

View File

@ -8,8 +8,7 @@ try:
from PySide import QtCore
except ImportError:
from PyQt4 import QtCore
# TODO: threads!
QtCore.Signal = QtCore.pyqtSignal
TOX_FILE_TRANSFER_STATE = {
@ -34,10 +33,13 @@ ALLOWED_FILES = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png')
class StateSignal(QtCore.QObject):
try:
signal = QtCore.Signal(int, float, int) # state and progress
except:
signal = QtCore.pyqtSignal(int, float, int) # state and progress - pyqt4
signal = QtCore.Signal(int, float, int) # state, progress, time in sec
class TransferFinishedSignal(QtCore.QObject):
signal = QtCore.Signal(int, int) # friend number, file number
class FileTransfer(QtCore.QObject):
@ -56,6 +58,8 @@ class FileTransfer(QtCore.QObject):
self._size = float(size)
self._done = 0
self._state_changed = StateSignal()
self._finished = TransferFinishedSignal()
self._file_id = None
def set_tox(self, tox):
self._tox = tox
@ -63,6 +67,9 @@ class FileTransfer(QtCore.QObject):
def set_state_changed_handler(self, handler):
self._state_changed.signal.connect(handler)
def set_transfer_finished_handler(self, handler):
self._finished.signal.connect(handler)
def signal(self):
percentage = self._done / self._size if self._size else 0
if self._creation_time is None or not percentage:
@ -71,12 +78,21 @@ class FileTransfer(QtCore.QObject):
t = ((time() - self._creation_time) / percentage) * (1 - percentage)
self._state_changed.signal.emit(self.state, percentage, int(t))
def finished(self):
self._finished.signal.emit(self._friend_number, self._file_number)
def get_file_number(self):
return self._file_number
def get_friend_number(self):
return self._friend_number
def get_id(self):
return self._file_id
def get_path(self):
return self._path
def cancel(self):
self.send_control(TOX_FILE_CONTROL['CANCEL'])
if hasattr(self, '_file'):
@ -122,6 +138,7 @@ class SendTransfer(FileTransfer):
self.state = TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
self._file_number = tox.file_send(friend_number, kind, size, file_id,
bytes(basename(path), 'utf-8') if path else b'')
self._file_id = self.get_file_id()
def send_chunk(self, position, size):
"""
@ -136,11 +153,11 @@ class SendTransfer(FileTransfer):
data = self._file.read(size)
self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
self._done += size
self.signal()
else:
if hasattr(self, '_file'):
self._file.close()
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
self.finished()
self.signal()
@ -180,9 +197,9 @@ class SendFromBuffer(FileTransfer):
data = self._data[position:position + size]
self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
self._done += size
self.signal()
else:
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
self.finished()
self.signal()
@ -204,16 +221,23 @@ class SendFromFileBuffer(SendTransfer):
class ReceiveTransfer(FileTransfer):
def __init__(self, path, tox, friend_number, size, file_number):
def __init__(self, path, tox, friend_number, size, file_number, position=0):
super(ReceiveTransfer, self).__init__(path, tox, friend_number, size, file_number)
self._file = open(self._path, 'wb')
self._file.truncate(0)
self._file_size = 0
self._file_size = position
self._file.truncate(position)
self._missed = set()
self._file_id = self.get_file_id()
self._done = position
def cancel(self):
super(ReceiveTransfer, self).cancel()
remove(self._path)
def total_size(self):
self._missed.add(self._file_size)
return min(self._missed)
def write_chunk(self, position, data):
"""
Incoming chunk
@ -225,12 +249,15 @@ class ReceiveTransfer(FileTransfer):
if data is None:
self._file.close()
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
self.signal()
self.finished()
else:
data = bytearray(data)
if self._file_size < position:
self._file.seek(0, 2)
self._file.write(b'\0' * (position - self._file_size))
self._missed.add(self._file_size)
else:
self._missed.discard(position)
self._file.seek(position)
self._file.write(data)
l = len(data)
@ -258,6 +285,7 @@ class ReceiveToBuffer(FileTransfer):
self._creation_time = time()
if data is None:
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
self.finished()
else:
data = bytes(data)
l = len(data)

View File

@ -65,6 +65,14 @@ class Friend(contact.Contact):
self._corr = data + self._corr
self._history_loaded = True
def load_all_corr(self):
data = list(self._message_getter.get_all())
if data is not None and len(data):
data.reverse()
data = list(map(lambda tupl: TextMessage(*tupl), data))
self._corr = data + self._corr
self._history_loaded = True
def get_corr_for_saving(self):
"""
Get data to save in db
@ -127,11 +135,11 @@ class Friend(contact.Contact):
del self._message_getter
# don't delete data about active file transfer
if not save_unsent:
self._corr = list(filter(lambda x: x.get_type() in (2, 3) and
self._corr = list(filter(lambda x: x.get_type() == 2 and
x.get_status() in ft.ACTIVE_FILE_TRANSFERS, self._corr))
self._unsaved_messages = 0
else:
self._corr = list(filter(lambda x: (x.get_type() in (2, 3) and x.get_status() in ft.ACTIVE_FILE_TRANSFERS)
self._corr = list(filter(lambda x: (x.get_type() == 2 and x.get_status() in ft.ACTIVE_FILE_TRANSFERS)
or (x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT']),
self._corr))
self._unsaved_messages = len(self.get_unsent_messages())

View File

@ -6,13 +6,14 @@ import util
class LibToxCore:
def __init__(self):
if system() == 'Linux':
# libtoxcore and libsodium must be installed in your os
self._libtoxcore = CDLL('libtoxcore.so')
elif system() == 'Windows':
if system() == 'Windows':
self._libtoxcore = CDLL(util.curr_directory() + '/libs/libtox.dll')
else:
raise OSError('Unknown system.')
# libtoxcore and libsodium must be installed in your os
try:
self._libtoxcore = CDLL('libtoxcore.so')
except:
self._libtoxcore = CDLL(util.curr_directory() + '/libs/libtoxcore.so')
def __getattr__(self, item):
return self._libtoxcore.__getattr__(item)
@ -21,14 +22,15 @@ class LibToxCore:
class LibToxAV:
def __init__(self):
if system() == 'Linux':
# that /usr/lib/libtoxav.so must exists
self._libtoxav = CDLL('libtoxav.so')
elif system() == 'Windows':
if system() == 'Windows':
# on Windows av api is in libtox.dll
self._libtoxav = CDLL(util.curr_directory() + '/libs/libtox.dll')
else:
raise OSError('Unknown system.')
# /usr/lib/libtoxav.so must exists
try:
self._libtoxav = CDLL('libtoxav.so')
except:
self._libtoxav = CDLL(util.curr_directory() + '/libs/libtoxav.so')
def __getattr__(self, item):
return self._libtoxav.__getattr__(item)
@ -37,14 +39,15 @@ class LibToxAV:
class LibToxEncryptSave:
def __init__(self):
if system() == 'Linux':
# /usr/lib/libtoxencryptsave.so must exists
self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.so')
elif system() == 'Windows':
if system() == 'Windows':
# on Windows profile encryption api is in libtox.dll
self._lib_tox_encrypt_save = CDLL(util.curr_directory() + '/libs/libtox.dll')
else:
raise OSError('Unknown system.')
# /usr/lib/libtoxencryptsave.so must exists
try:
self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.so')
except:
self._lib_tox_encrypt_save = CDLL(util.curr_directory() + '/libs/libtoxencryptsave.so')
def __getattr__(self, item):
return self._lib_tox_encrypt_save.__getattr__(item)

View File

@ -3,6 +3,7 @@ try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
QtCore.Slot = QtCore.pyqtSlot
import profile
from file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR
from util import curr_directory, convert_time, curr_time
@ -32,7 +33,7 @@ class MessageEdit(QtGui.QTextBrowser):
else:
self.setHtml(text)
font = QtGui.QFont()
font.setFamily("Times New Roman")
font.setFamily(settings.Settings.get_instance()['font'])
font.setPixelSize(settings.Settings.get_instance()['message_font_size'])
font.setBold(False)
self.setFont(font)
@ -42,10 +43,31 @@ class MessageEdit(QtGui.QTextBrowser):
def contextMenuEvent(self, event):
menu = create_menu(self.createStandardContextMenu(event.pos()))
quote = menu.addAction(QtGui.QApplication.translate("MainWindow", 'Quote selected text', None, QtGui.QApplication.UnicodeUTF8))
quote.triggered.connect(self.quote_text)
text = self.textCursor().selection().toPlainText()
if not text:
quote.setEnabled(False)
else:
import plugin_support
submenu = plugin_support.PluginLoader.get_instance().get_message_menu(menu, text)
if len(submenu):
plug = menu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8))
plug.addActions(submenu)
menu.popup(event.globalPos())
menu.exec_(event.globalPos())
del menu
def quote_text(self):
text = self.textCursor().selection().toPlainText()
if text:
import mainscreen
window = mainscreen.MainWindow.get_instance()
text = '>' + '\n>'.join(text.split('\n'))
if window.messageEdit.toPlainText():
text = '\n' + text
window.messageEdit.appendPlainText(text)
def on_anchor_clicked(self, url):
text = str(url.toString())
if text.startswith('tox:'):
@ -108,7 +130,7 @@ class MessageItem(QtGui.QWidget):
self.name.setGeometry(QtCore.QRect(2, 2, 95, 20))
self.name.setTextFormat(QtCore.Qt.PlainText)
font = QtGui.QFont()
font.setFamily("Times New Roman")
font.setFamily(settings.Settings.get_instance()['font'])
font.setPointSize(11)
font.setBold(True)
self.name.setFont(font)
@ -116,8 +138,6 @@ class MessageItem(QtGui.QWidget):
self.time = QtGui.QLabel(self)
self.time.setGeometry(QtCore.QRect(parent.width() - 50, 0, 50, 20))
font = QtGui.QFont()
font.setFamily("Times New Roman")
font.setPointSize(10)
font.setBold(False)
self.time.setFont(font)
@ -159,6 +179,14 @@ class MessageItem(QtGui.QWidget):
return True
return False
def set_avatar(self, pixmap):
self.name.setAlignment(QtCore.Qt.AlignCenter)
self.message.setAlignment(QtCore.Qt.AlignVCenter)
self.setFixedHeight(max(self.height(), 36))
self.name.setFixedHeight(self.height())
self.message.setFixedHeight(self.height())
self.name.setPixmap(pixmap.scaled(30, 30, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))
class ContactItem(QtGui.QWidget):
"""
@ -172,11 +200,12 @@ class ContactItem(QtGui.QWidget):
self.avatar_label = QtGui.QLabel(self)
size = 32 if mode else 64
self.avatar_label.setGeometry(QtCore.QRect(3, 4, size, size))
self.avatar_label.setScaledContents(True)
self.avatar_label.setScaledContents(False)
self.avatar_label.setAlignment(QtCore.Qt.AlignCenter)
self.name = DataLabel(self)
self.name.setGeometry(QtCore.QRect(50 if mode else 75, 3 if mode else 10, 150, 15 if mode else 25))
font = QtGui.QFont()
font.setFamily("Times New Roman")
font.setFamily(settings.Settings.get_instance()['font'])
font.setPointSize(10 if mode else 12)
font.setBold(True)
self.name.setFont(font)
@ -233,7 +262,7 @@ class UnreadMessagesCount(QtGui.QWidget):
self.label.setGeometry(QtCore.QRect(0, 0, 30, 20))
self.label.setVisible(False)
font = QtGui.QFont()
font.setFamily("Times New Roman")
font.setFamily(settings.Settings.get_instance()['font'])
font.setPointSize(12)
font.setBold(True)
self.label.setFont(font)
@ -269,7 +298,7 @@ class FileTransferItem(QtGui.QListWidget):
self.name.setGeometry(QtCore.QRect(3, 7, 95, 20))
self.name.setTextFormat(QtCore.Qt.PlainText)
font = QtGui.QFont()
font.setFamily("Times New Roman")
font.setFamily(settings.Settings.get_instance()['font'])
font.setPointSize(11)
font.setBold(True)
self.name.setFont(font)
@ -381,7 +410,7 @@ class FileTransferItem(QtGui.QListWidget):
if time + 1:
m, s = divmod(time, 60)
self.time_left.setText('{0:02d}:{1:02d}'.format(m, s))
if self.state != state:
if self.state != state and self.state in ACTIVE_FILE_TRANSFERS:
if state == TOX_FILE_TRANSFER_STATE['CANCELLED']:
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
self.cancel.setVisible(False)

View File

@ -57,7 +57,7 @@ class LoginScreen(CenteredWidget):
self.load_profile.raise_()
self.new_name.raise_()
self.new_profile.raise_()
self.toxygen.setGeometry(QtCore.QRect(160, 10, 90, 21))
self.toxygen.setGeometry(QtCore.QRect(160, 8, 90, 25))
font = QtGui.QFont()
font.setFamily("Impact")
font.setPointSize(16)

View File

@ -8,9 +8,10 @@ except ImportError:
from PyQt4 import QtCore, QtGui
from bootstrap import node_generator
from mainscreen import MainWindow
from callbacks import init_callbacks
from callbacks import init_callbacks, stop, start
from util import curr_directory, program_version
import styles.style
import platform
import toxencryptsave
from passwordscreen import PasswordScreen, UnlockAppScreen, SetProfilePasswordScreen
from plugin_support import PluginLoader
@ -52,6 +53,9 @@ class Toxygen:
app.setWindowIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
self.app = app
if platform.system() == 'Linux':
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
# application color scheme
with open(curr_directory() + '/styles/style.qss') as fl:
dark_style = fl.read()
@ -121,7 +125,19 @@ class Toxygen:
set_pass.show()
self.app.connect(self.app, QtCore.SIGNAL("lastWindowClosed()"), self.app, QtCore.SLOT("quit()"))
self.app.exec_()
ProfileHelper(Settings.get_default_path(), name).save_profile(self.tox.get_savedata())
reply = QtGui.QMessageBox.question(None,
'Profile {}'.format(name),
QtGui.QApplication.translate("login",
'Do you want to save profile in default folder? If no, profile will be saved in program folder',
None,
QtGui.QApplication.UnicodeUTF8),
QtGui.QMessageBox.Yes,
QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
path = Settings.get_default_path()
else:
path = curr_directory()
ProfileHelper(path, name).save_profile(self.tox.get_savedata())
path = Settings.get_default_path()
settings = Settings(name)
if curr_lang in langs:
@ -222,8 +238,16 @@ class Toxygen:
self.p = UnlockAppScreen(toxencryptsave.ToxEncryptSave.get_instance(), correct_pass)
self.p.show()
def tray_activated(reason):
if reason == QtGui.QSystemTrayIcon.DoubleClick:
show_window()
def close_app():
settings.closing = True
self.ms.close()
m.connect(show, QtCore.SIGNAL("triggered()"), show_window)
m.connect(exit, QtCore.SIGNAL("triggered()"), lambda: app.exit())
m.connect(exit, QtCore.SIGNAL("triggered()"), close_app)
m.connect(m, QtCore.SIGNAL("aboutToShow()"), lambda: m.aboutToShow())
sub.connect(onl, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(0))
sub.connect(away, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(1))
@ -231,12 +255,14 @@ class Toxygen:
self.tray.setContextMenu(m)
self.tray.show()
self.tray.activated.connect(tray_activated)
self.ms.show()
plugin_helper = PluginLoader(self.tox, settings) # plugin support
plugin_helper.load()
start()
# init thread
self.init = self.InitThread(self.tox, self.ms, self.tray)
self.init.start()
@ -256,6 +282,7 @@ class Toxygen:
self.mainloop.stop = True
self.avloop.stop = True
plugin_helper.stop()
stop()
self.mainloop.wait()
self.init.wait()
self.avloop.wait()
@ -415,7 +442,7 @@ def main():
else: # started with argument(s)
arg = sys.argv[1]
if arg == '--version':
print('Toxygen ' + program_version)
print('Toxygen v' + program_version)
return
elif arg == '--help':
print('Usage:\ntoxygen path_to_profile\ntoxygen tox_id\ntoxygen --version')

View File

@ -6,12 +6,14 @@ from list_items import *
from widgets import MultilineEdit, LineEdit
import plugin_support
from mainscreen_widgets import *
import settings
class MainWindow(QtGui.QMainWindow):
class MainWindow(QtGui.QMainWindow, Singleton):
def __init__(self, tox, reset, tray):
super(MainWindow, self).__init__()
super().__init__()
Singleton.__init__(self)
self.reset = reset
self.tray = tray
self.setAcceptDrops(True)
@ -75,7 +77,7 @@ class MainWindow(QtGui.QMainWindow):
self.actionAbout_program.triggered.connect(self.about_program)
self.actionNetwork.triggered.connect(self.network_settings)
self.actionAdd_friend.triggered.connect(self.add_contact)
self.actionSettings.triggered.connect(self.profilesettings)
self.actionSettings.triggered.connect(self.profile_settings)
self.actionPrivacy_settings.triggered.connect(self.privacy_settings)
self.actionInterface_settings.triggered.connect(self.interface_settings)
self.actionNotifications.triggered.connect(self.notification_settings)
@ -91,6 +93,7 @@ class MainWindow(QtGui.QMainWindow):
def event(self, event):
if event.type() == QtCore.QEvent.WindowActivate:
self.tray.setIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
self.messages.repaint()
return super(MainWindow, self).event(event)
def retranslateUi(self):
@ -125,6 +128,7 @@ class MainWindow(QtGui.QMainWindow):
self.messageEdit.setObjectName("messageEdit")
font = QtGui.QFont()
font.setPointSize(10)
font.setFamily(settings.Settings.get_instance()['font'])
self.messageEdit.setFont(font)
self.sendMessageButton = QtGui.QPushButton(Form)
@ -176,11 +180,12 @@ class MainWindow(QtGui.QMainWindow):
Form.setBaseSize(QtCore.QSize(270, 100))
self.avatar_label = Form.avatar_label = QtGui.QLabel(Form)
self.avatar_label.setGeometry(QtCore.QRect(5, 30, 64, 64))
self.avatar_label.setScaledContents(True)
self.avatar_label.setScaledContents(False)
self.avatar_label.setAlignment(QtCore.Qt.AlignCenter)
self.name = Form.name = DataLabel(Form)
Form.name.setGeometry(QtCore.QRect(75, 40, 150, 25))
font = QtGui.QFont()
font.setFamily("Times New Roman")
font.setFamily(settings.Settings.get_instance()['font'])
font.setPointSize(14)
font.setBold(True)
Form.name.setFont(font)
@ -193,9 +198,9 @@ class MainWindow(QtGui.QMainWindow):
Form.status_message.setObjectName("status_message")
self.connection_status = Form.connection_status = StatusCircle(Form)
Form.connection_status.setGeometry(QtCore.QRect(230, 35, 32, 32))
self.avatar_label.mouseReleaseEvent = self.profilesettings
self.status_message.mouseReleaseEvent = self.profilesettings
self.name.mouseReleaseEvent = self.profilesettings
self.avatar_label.mouseReleaseEvent = self.profile_settings
self.status_message.mouseReleaseEvent = self.profile_settings
self.name.mouseReleaseEvent = self.profile_settings
self.connection_status.raise_()
Form.connection_status.setObjectName("connection_status")
@ -203,12 +208,12 @@ class MainWindow(QtGui.QMainWindow):
Form.resize(650, 100)
self.account_avatar = QtGui.QLabel(Form)
self.account_avatar.setGeometry(QtCore.QRect(10, 30, 64, 64))
self.account_avatar.setScaledContents(True)
self.account_avatar.setScaledContents(False)
self.account_name = DataLabel(Form)
self.account_name.setGeometry(QtCore.QRect(100, 25, 400, 25))
self.account_name.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse)
font = QtGui.QFont()
font.setFamily("Times New Roman")
font.setFamily(settings.Settings.get_instance()['font'])
font.setPointSize(14)
font.setBold(True)
self.account_name.setFont(font)
@ -255,7 +260,7 @@ class MainWindow(QtGui.QMainWindow):
self.messages.setSpacing(1)
self.messages.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.messages.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.messages.setFocusPolicy(QtCore.Qt.NoFocus)
# self.messages.setFocusPolicy(QtCore.Qt.NoFocus)
def load(pos):
if not pos:
@ -263,6 +268,7 @@ class MainWindow(QtGui.QMainWindow):
self.messages.verticalScrollBar().setValue(1)
self.messages.verticalScrollBar().valueChanged.connect(load)
self.messages.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
self.messages.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
def initUI(self, tox):
self.setMinimumSize(920, 500)
@ -319,16 +325,21 @@ class MainWindow(QtGui.QMainWindow):
self.retranslateUi()
self.profile = Profile(tox, self)
def closeEvent(self, *args, **kwargs):
def closeEvent(self, event):
self.profile.save_history()
self.profile.close()
s = Settings.get_instance()
s['x'] = self.pos().x()
s['y'] = self.pos().y()
if not s['close_to_tray'] or s.closing:
s['x'] = self.geometry().x()
s['y'] = self.geometry().y()
s['width'] = self.width()
s['height'] = self.height()
s.save()
QtGui.QApplication.closeAllWindows()
event.accept()
else:
event.ignore()
self.hide()
def resizeEvent(self, *args, **kwargs):
self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155)
@ -350,6 +361,14 @@ class MainWindow(QtGui.QMainWindow):
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Escape:
self.hide()
elif event.key() == QtCore.Qt.Key_C and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
rows = list(map(lambda x: self.messages.row(x), self.messages.selectedItems()))
indexes = (rows[0] - self.messages.count(), rows[-1] - self.messages.count())
s = self.profile.export_history(self.profile.active_friend, True, indexes)
clipboard = QtGui.QApplication.clipboard()
clipboard.setText(s)
elif event.key() == QtCore.Qt.Key_Z and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
self.messages.clearSelection()
else:
super(MainWindow, self).keyPressEvent(event)
@ -362,7 +381,7 @@ class MainWindow(QtGui.QMainWindow):
msgBox = QtGui.QMessageBox()
msgBox.setWindowTitle(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8))
text = (QtGui.QApplication.translate("MainWindow", 'Toxygen is Tox client written on Python.\nVersion: ', None, QtGui.QApplication.UnicodeUTF8))
msgBox.setText(text + util.program_version + '\nGitHub: github.com/xveduk/toxygen/')
msgBox.setText(text + util.program_version + '\nGitHub: https://github.com/toxygen-project/toxygen/')
msgBox.exec_()
def network_settings(self):
@ -374,10 +393,10 @@ class MainWindow(QtGui.QMainWindow):
self.p_s.show()
def add_contact(self, link=''):
self.a_c = AddContact(link)
self.a_c = AddContact(link or '')
self.a_c.show()
def profilesettings(self, *args):
def profile_settings(self, *args):
self.p_s = ProfileSettings()
self.p_s.show()
@ -518,7 +537,12 @@ class MainWindow(QtGui.QMainWindow):
if item is not None:
self.listMenu = QtGui.QMenu()
set_alias_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Set alias', None, QtGui.QApplication.UnicodeUTF8))
clear_history_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8))
history_menu = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Chat history', None, QtGui.QApplication.UnicodeUTF8))
clear_history_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8))
export_to_text_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Export as text', None, QtGui.QApplication.UnicodeUTF8))
export_to_html_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Export as HTML', None, QtGui.QApplication.UnicodeUTF8))
copy_menu = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Copy', None, QtGui.QApplication.UnicodeUTF8))
copy_name_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Name', None, QtGui.QApplication.UnicodeUTF8))
copy_status_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Status message', None, QtGui.QApplication.UnicodeUTF8))
@ -540,6 +564,9 @@ class MainWindow(QtGui.QMainWindow):
self.connect(notes_item, QtCore.SIGNAL("triggered()"), lambda: self.show_note(friend))
self.connect(copy_name_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_name(friend))
self.connect(copy_status_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_status(friend))
self.connect(export_to_text_item, QtCore.SIGNAL("triggered()"), lambda: self.export_history(num))
self.connect(export_to_html_item, QtCore.SIGNAL("triggered()"),
lambda: self.export_history(num, False))
parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0))
self.listMenu.move(parent_position + pos)
self.listMenu.show()
@ -559,6 +586,20 @@ class MainWindow(QtGui.QMainWindow):
self.note = MultilineEdit(user, note, save_note)
self.note.show()
def export_history(self, num, as_text=True):
s = self.profile.export_history(num, as_text)
directory = QtGui.QFileDialog.getExistingDirectory(None,
QtGui.QApplication.translate("MainWindow", 'Choose folder',
None,
QtGui.QApplication.UnicodeUTF8),
curr_directory(),
QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog)
if directory:
name = 'exported_history_{}.{}'.format(convert_time(time.time()), 'txt' if as_text else 'html')
with open(directory + '/' + name, 'wt') as fl:
fl.write(s)
def set_alias(self, num):
self.profile.set_alias(num)
@ -608,4 +649,3 @@ class MainWindow(QtGui.QMainWindow):
def filtering(self):
self.profile.filtration(self.online_contacts.currentIndex() == 1, self.contact_name.text())

View File

@ -20,6 +20,11 @@ class MessageArea(QtGui.QPlainTextEdit):
def keyPressEvent(self, event):
if event.matches(QtGui.QKeySequence.Paste):
mimeData = QtGui.QApplication.clipboard().mimeData()
if mimeData.hasUrls():
for url in mimeData.urls():
self.pasteEvent(url.toString())
else:
self.pasteEvent()
elif event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
modifiers = event.modifiers()
@ -51,9 +56,13 @@ class MessageArea(QtGui.QPlainTextEdit):
e.accept()
def dropEvent(self, e):
if e.mimeData().hasFormat('text/plain'):
if e.mimeData().hasFormat('text/plain') or e.mimeData().hasFormat('text/html'):
e.accept()
self.pasteEvent(e.mimeData().text())
elif e.mimeData().hasUrls():
for url in e.mimeData().urls():
self.pasteEvent(url.toString())
e.accept()
else:
e.ignore()
@ -330,7 +339,7 @@ class WelcomeScreen(CenteredWidget):
self.setWindowTitle(QtGui.QApplication.translate('WelcomeScreen', 'Tip of the day',
None, QtGui.QApplication.UnicodeUTF8))
import random
num = random.randint(0, 8)
num = random.randint(0, 10)
if num == 0:
text = QtGui.QApplication.translate('WelcomeScreen', 'Press Esc if you want hide app to tray.',
None, QtGui.QApplication.UnicodeUTF8)
@ -356,12 +365,20 @@ class WelcomeScreen(CenteredWidget):
None, QtGui.QApplication.UnicodeUTF8)
elif num == 6:
text = QtGui.QApplication.translate('WelcomeScreen',
'New in Toxygen v0.2.2:<br>Users can lock application using profile password.<br>Compact contact list support<br>Bug fixes<br>Tox DNS improvements',
'New in Toxygen v0.2.3:<br>TCS compliance<br>Plugins, smileys and stickers import<br>Bug fixes',
None, QtGui.QApplication.UnicodeUTF8)
elif num == 7:
text = QtGui.QApplication.translate('WelcomeScreen',
'Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.',
None, QtGui.QApplication.UnicodeUTF8)
elif num == 8:
text = QtGui.QApplication.translate('WelcomeScreen',
'Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu',
None, QtGui.QApplication.UnicodeUTF8)
elif num == 9:
text = QtGui.QApplication.translate('WelcomeScreen',
'Use right click on inline image to save it',
None, QtGui.QApplication.UnicodeUTF8)
else:
text = QtGui.QApplication.translate('WelcomeScreen',
'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.',
@ -375,4 +392,3 @@ class WelcomeScreen(CenteredWidget):
s = settings.Settings.get_instance()
s['show_welcome_screen'] = False
s.save()

View File

@ -37,6 +37,7 @@ class AddContact(CenteredWidget):
self.error_label = DataLabel(self)
self.error_label.setGeometry(QtCore.QRect(120, 10, 420, 20))
font = QtGui.QFont()
font.setFamily(Settings.get_instance()['font'])
font.setPointSize(10)
font.setWeight(30)
self.error_label.setFont(font)
@ -51,7 +52,6 @@ class AddContact(CenteredWidget):
self.message.setObjectName("label_2")
self.retranslateUi()
self.message_edit.setText('Hello! Add me to your contact list please')
font = QtGui.QFont()
font.setPointSize(12)
font.setBold(True)
self.label.setFont(font)
@ -102,6 +102,7 @@ class ProfileSettings(CenteredWidget):
self.label = QtGui.QLabel(self)
self.label.setGeometry(QtCore.QRect(40, 30, 91, 25))
font = QtGui.QFont()
font.setFamily(Settings.get_instance()['font'])
font.setPointSize(18)
font.setWeight(75)
font.setBold(True)
@ -263,16 +264,28 @@ class ProfileSettings(CenteredWidget):
buffer = QtCore.QBuffer(byte_array)
buffer.open(QtCore.QIODevice.WriteOnly)
bitmap.save(buffer, 'PNG')
Profile.get_instance().set_avatar(str(byte_array.data()))
Profile.get_instance().set_avatar(bytes(byte_array.data()))
def export_profile(self):
directory = QtGui.QFileDialog.getExistingDirectory(options=QtGui.QFileDialog.DontUseNativeDialog) + '/'
directory = QtGui.QFileDialog.getExistingDirectory(options=QtGui.QFileDialog.DontUseNativeDialog,
dir=curr_directory()) + '/'
if directory != '/':
ProfileHelper.get_instance().export_profile(directory)
reply = QtGui.QMessageBox.question(None,
QtGui.QApplication.translate("ProfileSettingsForm",
'Use new path',
None,
QtGui.QApplication.UnicodeUTF8),
QtGui.QApplication.translate("ProfileSettingsForm",
'Do you want to move your profile to this location?',
None,
QtGui.QApplication.UnicodeUTF8),
QtGui.QMessageBox.Yes,
QtGui.QMessageBox.No)
settings = Settings.get_instance()
settings.export(directory)
profile = Profile.get_instance()
profile.export_history(directory)
profile.export_db(directory)
ProfileHelper.get_instance().export_profile(directory, reply == QtGui.QMessageBox.Yes)
def closeEvent(self, event):
profile = Profile.get_instance()
@ -520,11 +533,12 @@ class NotificationsSettings(CenteredWidget):
self.soundNotifications = QtGui.QCheckBox(self)
self.soundNotifications.setGeometry(QtCore.QRect(10, 70, 340, 18))
font = QtGui.QFont()
s = Settings.get_instance()
font.setFamily(s['font'])
font.setPointSize(12)
self.callsSound.setFont(font)
self.soundNotifications.setFont(font)
self.enableNotifications.setFont(font)
s = Settings.get_instance()
self.enableNotifications.setChecked(s['notifications'])
self.soundNotifications.setChecked(s['sound_notifications'])
self.callsSound.setChecked(s['calls_sound'])
@ -554,19 +568,20 @@ class InterfaceSettings(CenteredWidget):
def initUI(self):
self.setObjectName("interfaceForm")
self.setMinimumSize(QtCore.QSize(400, 550))
self.setMaximumSize(QtCore.QSize(400, 550))
self.setMinimumSize(QtCore.QSize(400, 650))
self.setMaximumSize(QtCore.QSize(400, 650))
self.label = QtGui.QLabel(self)
self.label.setGeometry(QtCore.QRect(30, 10, 370, 20))
settings = Settings.get_instance()
font = QtGui.QFont()
font.setPointSize(14)
font.setBold(True)
font.setFamily(settings['font'])
self.label.setFont(font)
self.themeSelect = QtGui.QComboBox(self)
self.themeSelect.setGeometry(QtCore.QRect(30, 40, 120, 30))
list_of_themes = ['dark']
self.themeSelect.addItems(list_of_themes)
settings = Settings.get_instance()
theme = settings['theme']
if theme in list_of_themes:
index = list_of_themes.index(theme)
@ -610,25 +625,38 @@ class InterfaceSettings(CenteredWidget):
self.messages_font_size.setCurrentIndex(settings['message_font_size'] - 10)
self.unread = QtGui.QPushButton(self)
self.unread.setGeometry(QtCore.QRect(30, 425, 340, 30))
self.unread.setGeometry(QtCore.QRect(30, 470, 340, 30))
self.unread.clicked.connect(self.select_color)
self.compact_mode = QtGui.QCheckBox(self)
self.compact_mode.setGeometry(QtCore.QRect(30, 380, 370, 20))
self.compact_mode.setChecked(settings['compact_mode'])
self.close_to_tray = QtGui.QCheckBox(self)
self.close_to_tray.setGeometry(QtCore.QRect(30, 410, 370, 20))
self.close_to_tray.setChecked(settings['close_to_tray'])
self.show_avatars = QtGui.QCheckBox(self)
self.show_avatars.setGeometry(QtCore.QRect(30, 440, 370, 20))
self.show_avatars.setChecked(settings['show_avatars'])
self.choose_font = QtGui.QPushButton(self)
self.choose_font.setGeometry(QtCore.QRect(30, 510, 340, 30))
self.choose_font.clicked.connect(self.new_font)
self.import_smileys = QtGui.QPushButton(self)
self.import_smileys.setGeometry(QtCore.QRect(30, 465, 340, 30))
self.import_smileys.setGeometry(QtCore.QRect(30, 550, 340, 30))
self.import_smileys.clicked.connect(self.import_sm)
self.import_stickers = QtGui.QPushButton(self)
self.import_stickers.setGeometry(QtCore.QRect(30, 505, 340, 30))
self.import_stickers.setGeometry(QtCore.QRect(30, 590, 340, 30))
self.import_stickers.clicked.connect(self.import_st)
self.retranslateUi()
QtCore.QMetaObject.connectSlotsByName(self)
def retranslateUi(self):
self.show_avatars.setText(QtGui.QApplication.translate("interfaceForm", "Show avatars in chat", None, QtGui.QApplication.UnicodeUTF8))
self.setWindowTitle(QtGui.QApplication.translate("interfaceForm", "Interface settings", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("interfaceForm", "Theme:", None, QtGui.QApplication.UnicodeUTF8))
self.lang.setText(QtGui.QApplication.translate("interfaceForm", "Language:", None, QtGui.QApplication.UnicodeUTF8))
@ -640,6 +668,8 @@ class InterfaceSettings(CenteredWidget):
self.compact_mode.setText(QtGui.QApplication.translate("interfaceForm", "Compact contact list", None, QtGui.QApplication.UnicodeUTF8))
self.import_smileys.setText(QtGui.QApplication.translate("interfaceForm", "Import smiley pack", None, QtGui.QApplication.UnicodeUTF8))
self.import_stickers.setText(QtGui.QApplication.translate("interfaceForm", "Import sticker pack", None, QtGui.QApplication.UnicodeUTF8))
self.close_to_tray.setText(QtGui.QApplication.translate("interfaceForm", "Close to tray", None, QtGui.QApplication.UnicodeUTF8))
self.choose_font.setText(QtGui.QApplication.translate("interfaceForm", "Select font", None, QtGui.QApplication.UnicodeUTF8))
def import_st(self):
directory = QtGui.QFileDialog.getExistingDirectory(self,
@ -669,6 +699,20 @@ class InterfaceSettings(CenteredWidget):
dest = curr_directory() + '/smileys/' + os.path.basename(directory) + '/'
copy(src, dest)
def new_font(self):
settings = Settings.get_instance()
font, ok = QtGui.QFontDialog.getFont(QtGui.QFont(settings['font'], 10), self)
if ok:
settings['font'] = font.family()
settings.save()
msgBox = QtGui.QMessageBox()
text = QtGui.QApplication.translate("interfaceForm", 'Restart app to apply settings', None,
QtGui.QApplication.UnicodeUTF8)
msgBox.setWindowTitle(QtGui.QApplication.translate("interfaceForm", 'Restart required', None,
QtGui.QApplication.UnicodeUTF8))
msgBox.setText(text)
msgBox.exec_()
def select_color(self):
col = QtGui.QColorDialog.getColor()
@ -689,7 +733,11 @@ class InterfaceSettings(CenteredWidget):
if settings['compact_mode'] != self.compact_mode.isChecked():
settings['compact_mode'] = self.compact_mode.isChecked()
restart = True
if settings['show_avatars'] != self.show_avatars.isChecked():
settings['show_avatars'] = self.show_avatars.isChecked()
restart = True
settings['smiley_pack'] = self.smiley_pack.currentText()
settings['close_to_tray'] = self.close_to_tray.isChecked()
smileys.SmileyLoader.get_instance().load_pack()
language = self.lang_choose.currentText()
if settings['language'] != language:
@ -733,9 +781,11 @@ class AudioSettings(CenteredWidget):
self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20))
self.out_label = QtGui.QLabel(self)
self.out_label.setGeometry(QtCore.QRect(25, 65, 350, 20))
settings = Settings.get_instance()
font = QtGui.QFont()
font.setPointSize(16)
font.setBold(True)
font.setFamily(settings['font'])
self.in_label.setFont(font)
self.out_label.setFont(font)
self.input = QtGui.QComboBox(self)
@ -743,7 +793,6 @@ class AudioSettings(CenteredWidget):
self.output = QtGui.QComboBox(self)
self.output.setGeometry(QtCore.QRect(25, 90, 350, 30))
p = pyaudio.PyAudio()
settings = Settings.get_instance()
self.in_indexes, self.out_indexes = [], []
for i in range(p.get_device_count()):
device = p.get_device_info_by_index(i)

View File

@ -147,6 +147,16 @@ class PluginLoader(util.Singleton):
continue
return result
def get_message_menu(self, menu, selected_text):
result = []
for elem in self._plugins.values():
if elem[1]:
try:
result.extend(elem[0].get_message_menu(menu, selected_text))
except:
continue
return result
def stop(self):
"""
App is closing, stop all plugins

View File

@ -88,6 +88,15 @@ class PluginSuperClass:
"""
return []
def get_message_menu(self, menu, text):
"""
This method creates items for menu which called on right click in message
:param menu: menu instance
:param text: selected text
:return list of QAction's
"""
return []
def get_window(self):
"""
This method should return window for plugins with GUI or None

View File

@ -41,6 +41,10 @@ class Profile(contact.Contact, Singleton):
self._load_history = True
settings = Settings.get_instance()
self._show_online = settings['show_online_friends']
self._show_avatars = settings['show_avatars']
self._friend_item_height = 40 if settings['compact_mode'] else 70
self._paused_file_transfers = dict(settings['paused_file_transfers'])
# key - file id, value: [path, friend number, is incoming, start position]
screen.online_contacts.setCurrentIndex(int(self._show_online))
aliases = settings['friends_aliases']
data = tox.self_get_friend_list()
@ -78,6 +82,8 @@ class Profile(contact.Contact, Singleton):
super(Profile, self).set_status(status)
if status is not None:
self._tox.self_set_status(status)
else:
QtCore.QTimer.singleShot(30000, self.reconnect)
def set_name(self, value):
if self.name == value:
@ -122,7 +128,7 @@ class Profile(contact.Contact, Singleton):
friend.visibility = friend.visibility or friend.messages or friend.actions
if friend.visibility:
self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250,
40 if settings['compact_mode'] else 70))
self._friend_item_height))
else:
self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, 0))
self._show_online, self._filter_string = show_online, filter_str
@ -167,15 +173,16 @@ class Profile(contact.Contact, Singleton):
self.send_typing(False)
self._screen.typing.setVisible(False)
if value is not None:
if self._active_friend + 1:
if self._active_friend + 1 and self._active_friend != value:
try:
self._friends[self._active_friend].curr_text = self._screen.messageEdit.toPlainText()
except:
pass
self._active_friend = value
friend = self._friends[value]
self._friends[value].reset_messages()
if self._active_friend != value:
self._screen.messageEdit.setPlainText(friend.curr_text)
self._active_friend = value
self._friends[value].reset_messages()
self._messages.clear()
friend.load_corr()
messages = friend.get_corr()[-PAGE_SIZE:]
@ -224,11 +231,9 @@ class Profile(contact.Contact, Singleton):
if not os.path.isfile(avatar_path): # load default image
avatar_path = curr_directory() + '/images/avatar.png'
os.chdir(os.path.dirname(avatar_path))
pixmap = QtGui.QPixmap(QtCore.QSize(64, 64))
pixmap.load(avatar_path)
self._screen.account_avatar.setScaledContents(False)
self._screen.account_avatar.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio))
self._screen.account_avatar.repaint() # comment?
pixmap = QtGui.QPixmap(avatar_path)
self._screen.account_avatar.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation))
self.update_filtration()
except Exception as ex: # no friend found. ignore
log('Friend value: ' + str(value))
@ -283,6 +288,13 @@ class Profile(contact.Contact, Singleton):
else:
self.send_file(data[0], friend_number, True)
friend.clear_unsent_files()
for key in list(self._paused_file_transfers.keys()):
data = self._paused_file_transfers[key]
if not os.path.exists(data[0]):
del self._paused_file_transfers[key]
elif data[1] == friend_number and not data[2]:
self.send_file(data[0], friend_number, True, key)
del self._paused_file_transfers[key]
if friend_number == self.get_active_number():
self.update()
except Exception as ex:
@ -292,11 +304,18 @@ class Profile(contact.Contact, Singleton):
"""
Friend with specified number quit
"""
# TODO: fix and add full file resuming support
self.get_friend_by_number(friend_number).status = None
self.friend_typing(friend_number, False)
if friend_number in self._call:
self._call.finish_call(friend_number, True)
for friend_num, file_num in list(self._file_transfers.keys()):
if friend_num == friend_number:
ft = self._file_transfers[(friend_num, file_num)]
if type(ft) is SendTransfer:
self._paused_file_transfers[ft.get_id()] = [ft.get_path(), friend_num, False, -1]
elif type(ft) is ReceiveTransfer:
self._paused_file_transfers[ft.get_id()] = [ft.get_path(), friend_num, True, ft.total_size()]
self.cancel_transfer(friend_num, file_num, True)
# -----------------------------------------------------------------------------------------------------------------
# Typing notifications
@ -307,9 +326,12 @@ class Profile(contact.Contact, Singleton):
Send typing notification to a friend
"""
if Settings.get_instance()['typing_notifications'] and self._active_friend + 1:
try:
friend = self._friends[self._active_friend]
if friend.status is not None:
self._tox.self_set_typing(friend.number, typing)
except:
pass
def friend_typing(self, friend_number, typing):
"""
@ -397,7 +419,6 @@ class Profile(contact.Contact, Singleton):
plugin_support.PluginLoader.get_instance().command(text[8:])
self._screen.messageEdit.clear()
elif text and friend_num + 1:
text = ''.join(c if c <= '\u10FFFF' else '\u25AF' for c in text)
if text.startswith('/me '):
message_type = TOX_MESSAGE_TYPE['ACTION']
text = text[4:]
@ -484,7 +505,7 @@ class Profile(contact.Contact, Singleton):
data[1],
data[3],
False)
elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']:
elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: # file transfer
if message.get_status() is None:
self.create_unsent_file_item(message)
continue
@ -496,19 +517,45 @@ class Profile(contact.Contact, Singleton):
ft.signal()
except:
print('Incoming not started transfer - no info found')
elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline
self.create_inline_item(message.get_data())
elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline image
self.create_inline_item(message.get_data(), False)
else: # info message
data = message.get_data()
self.create_message_item(data[0],
data[2],
'',
data[3])
data[3],
False)
self._load_history = True
def export_history(self, directory):
def export_db(self, directory):
self._history.export(directory)
def export_history(self, num, as_text=True, _range=None):
friend = self._friends[num]
if _range is None:
friend.load_all_corr()
if _range is None:
corr = friend.get_corr()
elif _range[1] + 1:
corr = friend.get_corr()[_range[0]:_range[1] + 1]
else:
corr = friend.get_corr()[_range[0]:]
arr = []
new_line = '\n' if as_text else '<br>'
for message in corr:
if type(message) is TextMessage:
data = message.get_data()
if as_text:
x = '[{}] {}: {}\n'
else:
x = '[{}] <b>{}:</b> {}<br>'
arr.append(x.format(convert_time(data[2]) if data[1] != MESSAGE_OWNER['NOT_SENT'] else 'Unsent',
friend.name if data[1] == MESSAGE_OWNER['FRIEND'] else self.name,
data[0]))
s = new_line.join(arr)
return s
# -----------------------------------------------------------------------------------------------------------------
# Factories for friend, message and file transfer items
# -----------------------------------------------------------------------------------------------------------------
@ -533,6 +580,9 @@ class Profile(contact.Contact, Singleton):
else:
name = self._name
item = MessageItem(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'], message_type, self._messages)
if self._show_avatars:
item.set_avatar(self._friends[self._active_friend].get_pixmap() if owner == MESSAGE_OWNER[
'FRIEND'] else self.get_pixmap())
elem = QtGui.QListWidgetItem()
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
if append:
@ -773,24 +823,34 @@ class Profile(contact.Contact, Singleton):
Recreate tox instance
:param restart: method which calls restart and returns new tox instance
"""
# TODO: file transfers!!
for key in list(self._file_transfers.keys()):
self._file_transfers[key].cancelled()
del self._file_transfers[key]
for friend in self._friends:
self.friend_exit(friend.number)
self._call.stop()
del self._call
del self._tox
self._tox = restart()
self._call = calls.AV(self._tox.AV)
self.status = None
for friend in self._friends:
friend.status = None
friend.number = self._tox.friend_by_public_key(friend.tox_id) # numbers update
self.update_filtration()
def reconnect(self):
if self.status is None or all(list(map(lambda x: x.status is None, self._friends))):
self.reset(self._screen.reset)
QtCore.QTimer.singleShot(30000, self.reconnect)
def close(self):
for friend in self._friends:
self.friend_exit(friend.number)
for i in range(len(self._friends)):
del self._friends[0]
if hasattr(self, '_call'):
self._call.stop()
del self._call
for i in range(len(self._friends)):
del self._friends[0]
s = Settings.get_instance()
s['paused_file_transfers'] = dict(self._paused_file_transfers) if s['resend_files'] else {}
s.save()
# -----------------------------------------------------------------------------------------------------------------
# File transfers support
@ -808,7 +868,24 @@ class Profile(contact.Contact, Singleton):
friend = self.get_friend_by_number(friend_number)
auto = settings['allow_auto_accept'] and friend.tox_id in settings['auto_accept_from_friends']
inline = (file_name in ALLOWED_FILES) and settings['allow_inline']
if inline and size < 1024 * 1024:
file_id = self._tox.file_get_file_id(friend_number, file_number)
accepted = True
if file_id in self._paused_file_transfers:
data = self._paused_file_transfers[file_id]
pos = data[-1] if os.path.exists(data[0]) else 0
if pos >= size:
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
return
self._tox.file_seek(friend_number, file_number, pos)
self.accept_transfer(None, data[0], friend_number, file_number, size, False, pos)
tm = TransferMessage(MESSAGE_OWNER['FRIEND'],
time.time(),
TOX_FILE_TRANSFER_STATE['RUNNING'],
size,
file_name,
friend_number,
file_number)
elif inline and size < 1024 * 1024:
self.accept_transfer(None, '', friend_number, file_number, size, True)
tm = TransferMessage(MESSAGE_OWNER['FRIEND'],
time.time(),
@ -836,9 +913,10 @@ class Profile(contact.Contact, Singleton):
file_name,
friend_number,
file_number)
accepted = False
if friend_number == self.get_active_number():
item = self.create_file_transfer_item(tm)
if (inline and size < 1024 * 1024) or auto:
if accepted:
self._file_transfers[(friend_number, file_number)].set_state_changed_handler(item.update)
self._messages.scrollToBottom()
else:
@ -892,18 +970,14 @@ class Profile(contact.Contact, Singleton):
"""
self.get_friend_by_number(friend_number).update_transfer_data(file_number,
TOX_FILE_TRANSFER_STATE['RUNNING'])
# if (friend_number, file_number) not in self._file_transfers:
# print self._file_transfers
# print (friend_number, file_number)
# return
tr = self._file_transfers[(friend_number, file_number)]
if by_friend:
tr.state = TOX_FILE_TRANSFER_STATE['RUNNING']
tr.signal()
else: # send seek control?
else:
tr.send_control(TOX_FILE_CONTROL['RESUME'])
def accept_transfer(self, item, path, friend_number, file_number, size, inline=False):
def accept_transfer(self, item, path, friend_number, file_number, size, inline=False, from_position=0):
"""
:param item: transfer item.
:param path: path for saving
@ -911,9 +985,11 @@ class Profile(contact.Contact, Singleton):
:param file_number: file number
:param size: file size
:param inline: is inline image
:param from_position: position for start
"""
path, file_name = os.path.split(path)
new_file_name, i = file_name, 1
if not from_position:
while os.path.isfile(path + '/' + new_file_name): # file with same name already exists
if '.' in file_name: # has extension
d = file_name.rindex('.')
@ -923,9 +999,10 @@ class Profile(contact.Contact, Singleton):
i += 1
path = os.path.join(path, new_file_name)
if not inline:
rt = ReceiveTransfer(path, self._tox, friend_number, size, file_number)
rt = ReceiveTransfer(path, self._tox, friend_number, size, file_number, from_position)
else:
rt = ReceiveToBuffer(self._tox, friend_number, size, file_number)
rt.set_transfer_finished_handler(self.transfer_finished)
self._file_transfers[(friend_number, file_number)] = rt
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['RESUME'])
if item is not None:
@ -939,6 +1016,7 @@ class Profile(contact.Contact, Singleton):
:param data: raw data - png
"""
self.send_inline(data, 'toxygen_inline.png')
self._messages.repaint()
def send_sticker(self, path):
with open(path, 'rb') as fl:
@ -956,6 +1034,7 @@ class Profile(contact.Contact, Singleton):
elif friend.status is None and is_resend:
raise RuntimeError()
st = SendFromBuffer(self._tox, friend.number, data, file_name)
st.set_transfer_finished_handler(self.transfer_finished)
self._file_transfers[(friend.number, st.get_file_number())] = st
tm = TransferMessage(MESSAGE_OWNER['ME'],
time.time(),
@ -969,14 +1048,15 @@ class Profile(contact.Contact, Singleton):
st.set_state_changed_handler(item.update)
self._messages.scrollToBottom()
def send_file(self, path, number=None, is_resend=False):
def send_file(self, path, number=None, is_resend=False, file_id=None):
"""
Send file to current active friend
:param path: file path
:param number: friend_number
:param is_resend: is 'offline' message
:param file_id: file id of transfer
"""
friend_number = number or self.get_active_number()
friend_number = self.get_active_number() if number is None else number
friend = self.get_friend_by_number(friend_number)
if friend.status is None and not is_resend:
m = UnsentFile(path, None, time.time())
@ -986,7 +1066,8 @@ class Profile(contact.Contact, Singleton):
elif friend.status is None and is_resend:
print('Error in sending')
raise RuntimeError()
st = SendTransfer(path, self._tox, friend_number)
st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id)
st.set_transfer_finished_handler(self.transfer_finished)
self._file_transfers[(friend_number, st.get_file_number())] = st
tm = TransferMessage(MESSAGE_OWNER['ME'],
time.time(),
@ -995,23 +1076,32 @@ class Profile(contact.Contact, Singleton):
os.path.basename(path),
friend_number,
st.get_file_number())
if friend_number == self.get_active_number():
item = self.create_file_transfer_item(tm)
st.set_state_changed_handler(item.update)
self._friends[self._active_friend].append_message(tm)
self._messages.scrollToBottom()
self._friends[friend_number].append_message(tm)
def incoming_chunk(self, friend_number, file_number, position, data):
"""
Incoming chunk
"""
if (friend_number, file_number) in self._file_transfers:
self._file_transfers[(friend_number, file_number)].write_chunk(position, data)
def outgoing_chunk(self, friend_number, file_number, position, size):
"""
Outgoing chunk
"""
self._file_transfers[(friend_number, file_number)].send_chunk(position, size)
@QtCore.Slot(int, int)
def transfer_finished(self, friend_number, file_number):
transfer = self._file_transfers[(friend_number, file_number)]
transfer.write_chunk(position, data)
if transfer.state not in ACTIVE_FILE_TRANSFERS: # finished or cancelled
if type(transfer) is ReceiveAvatar:
t = type(transfer)
if t is ReceiveAvatar:
self.get_friend_by_number(friend_number).load_avatar()
self.set_active(None)
elif type(transfer) is ReceiveToBuffer: # inline image
elif t is ReceiveToBuffer or (t is SendFromBuffer and Settings.get_instance()['allow_inline']): # inline image
print('inline')
inline = InlineImage(transfer.get_data())
i = self.get_friend_by_number(friend_number).update_transfer_data(file_number,
@ -1025,39 +1115,11 @@ class Profile(contact.Contact, Singleton):
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
self._messages.insertItem(count + i + 1, elem)
self._messages.setItemWidget(elem, item)
else:
elif t is not SendAvatar:
self.get_friend_by_number(friend_number).update_transfer_data(file_number,
TOX_FILE_TRANSFER_STATE['FINISHED'])
del self._file_transfers[(friend_number, file_number)]
def outgoing_chunk(self, friend_number, file_number, position, size):
"""
Outgoing chunk
"""
if (friend_number, file_number) in self._file_transfers:
transfer = self._file_transfers[(friend_number, file_number)]
transfer.send_chunk(position, size)
if transfer.state not in ACTIVE_FILE_TRANSFERS: # finished or cancelled
del self._file_transfers[(friend_number, file_number)]
if type(transfer) is not SendAvatar:
if type(transfer) is SendFromBuffer and Settings.get_instance()['allow_inline']: # inline
inline = InlineImage(transfer.get_data())
print('inline')
i = self.get_friend_by_number(friend_number).update_transfer_data(file_number,
TOX_FILE_TRANSFER_STATE[
'FINISHED'],
inline)
if friend_number == self.get_active_number():
count = self._messages.count()
if count + i + 1 >= 0:
elem = QtGui.QListWidgetItem()
item = InlineImageItem(transfer.get_data(), self._messages.width(), elem)
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
self._messages.insertItem(count + i + 1, elem)
self._messages.setItemWidget(elem, item)
else:
self.get_friend_by_number(friend_number).update_transfer_data(file_number,
TOX_FILE_TRANSFER_STATE['FINISHED'])
del transfer
# -----------------------------------------------------------------------------------------------------------------
# Avatars support
@ -1111,6 +1173,8 @@ class Profile(contact.Contact, Singleton):
"""User clicked audio button in main window"""
num = self.get_active_number()
if num not in self._call and self.is_active_online(): # start call
if not Settings.get_instance().audio['enabled']:
return
self._call(num, audio, video)
self._screen.active_call()
if video:
@ -1129,6 +1193,8 @@ class Profile(contact.Contact, Singleton):
"""
Incoming call from friend. Only audio is supported now
"""
if not Settings.get_instance().audio['enabled']:
return
friend = self.get_friend_by_number(friend_number)
if video:
text = QtGui.QApplication.translate("incoming_call", "Incoming video call", None,
@ -1144,6 +1210,7 @@ class Profile(contact.Contact, Singleton):
self._messages.scrollToBottom()
else:
friend.actions = True
# TODO: dict of widgets
self._call_widget = avwidgets.IncomingCallWidget(friend_number, text, friend.name)
self._call_widget.set_pixmap(friend.get_pixmap())
self._call_widget.show()
@ -1170,6 +1237,7 @@ class Profile(contact.Contact, Singleton):
self._screen.call_finished()
self._call.finish_call(friend_number, by_friend) # finish or decline call
if hasattr(self, '_call_widget'):
self._call_widget.close()
del self._call_widget
friend = self.get_friend_by_number(friend_number)
friend.append_message(InfoMessage(text, time.time()))

View File

@ -1,8 +1,7 @@
from platform import system
import json
import os
import locale
from util import Singleton, curr_directory, log
from util import Singleton, curr_directory, log, copy
import pyaudio
from toxencryptsave import ToxEncryptSave
import smileys
@ -34,10 +33,19 @@ class Settings(dict, Singleton):
super(Settings, self).__init__(Settings.get_default_settings())
self.save()
smileys.SmileyLoader(self)
p = pyaudio.PyAudio()
self.locked = False
self.audio = {'input': p.get_default_input_device_info()['index'],
'output': p.get_default_output_device_info()['index']}
self.closing = False
p = pyaudio.PyAudio()
input_devices = output_devices = 0
for i in range(p.get_device_count()):
device = p.get_device_info_by_index(i)
if device["maxInputChannels"]:
input_devices += 1
if device["maxOutputChannels"]:
output_devices += 1
self.audio = {'input': p.get_default_input_device_info()['index'] if input_devices else -1,
'output': p.get_default_output_device_info()['index'] if output_devices else -1,
'enabled': input_devices and output_devices}
@staticmethod
def get_auto_profile():
@ -109,7 +117,10 @@ class Settings(dict, Singleton):
'auto_accept_path': None,
'show_online_friends': False,
'auto_accept_from_friends': [],
'paused_file_transfers': {},
'resend_files': True,
'friends_aliases': [],
'show_avatars': False,
'typing_notifications': False,
'calls_sound': True,
'blocked': [],
@ -126,7 +137,9 @@ class Settings(dict, Singleton):
'unread_color': 'red',
'save_unsent_only': False,
'compact_mode': False,
'show_welcome_screen': True
'show_welcome_screen': True,
'close_to_tray': False,
'font': 'Times New Roman'
}
@staticmethod
@ -193,12 +206,17 @@ class Settings(dict, Singleton):
with open(path + str(self.name) + '.json', 'w') as fl:
fl.write(text)
def update_path(self):
self.path = ProfileHelper.get_path() + self.name + '.json'
@staticmethod
def get_default_path():
if system() == 'Linux':
return os.getenv('HOME') + '/.config/tox/'
elif system() == 'Windows':
if system() == 'Windows':
return os.getenv('APPDATA') + '/Tox/'
elif system() == 'Darwin':
return os.getenv('HOME') + '/Library/Application Support/Tox/'
else:
return os.getenv('HOME') + '/.config/tox/'
class ProfileHelper(Singleton):
@ -233,13 +251,18 @@ class ProfileHelper(Singleton):
fl.write(data)
print('Profile saved successfully')
def export_profile(self, new_path):
new_path += os.path.basename(self._path)
def export_profile(self, new_path, use_new_path):
path = new_path + os.path.basename(self._path)
with open(self._path, 'rb') as fin:
data = fin.read()
with open(new_path, 'wb') as fout:
with open(path, 'wb') as fout:
fout.write(data)
print('Profile exported successfully')
copy(self._directory + 'avatars', new_path + 'avatars')
if use_new_path:
self._path = new_path + os.path.basename(self._path)
self._directory = new_path
Settings.get_instance().update_path()
@staticmethod
def find_profiles():

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 891 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 893 B

View File

@ -0,0 +1 @@
{"BD": "cool2.png", "v_v": "calm.png", ":/": "getlost.png", ":(": "sad.png", ":)": "smile.png", ":*": "kiss.png", ":animal:": "pawn.png", "=|": "none.png", "=*": "kiss.png", ":heart:": "heart.png", "B]": "cool.png", "=o": "shocked.png", ":0": "shocked.png", "=S": "none2.png", "=]": "smile2.png", "=\\": "getlost.png", "B-)": "cool.png", ":pawn:": "pawn.png", "=O": "shocked.png", ">:\\": "angry2.png", ":redstar:": "redstar.png", ":o": "shocked.png", "=0": "shocked.png", "B-D": "cool2.png", ":|": "none.png", ":''(": "cry.png", "=/": "getlost.png", "=)": "smile.png", "=(": "sad.png", "B-]": "cool.png", ":O": "shocked.png", ":D": "grin.png", "B)": "cool.png", ":'(": "cry.png", ":]": "smile2.png", ":music:": "notes.png", ":P": "tongue.png", ":S": "none2.png", ":evil:": "evil.png", ":-O": "shocked.png", ":zzzzz:": "zzz.png", ">:[]": "angry.png", ";|": "none.png", ":-\\": "getlost.png", ":-]": "smile2.png", ":-S": "none2.png", ":-P": "tongue.png", ";o": "shocked.png", ";S": "none2.png", ":\\": "getlost.png", ";P": "tongue.png", ":pet:": "pawn.png", ":-o": "shocked.png", ";]": "blink.png", ";\\": "getlost.png", ":oops:": "oops.png", ":-|": "none.png", ";D": "grin.png", ";O": "shocked.png", "@->-": "flower.png", ";0": "shocked.png", ":zzz:": "zzz.png", ":cool2:": "cool2.png", "^_^": "pleased.png", ":)))": "grin.png", ";)": "blink.png", ";/": "getlost.png", ":-*": "kiss.png", ":-(": "sad.png", ":-)": "smile.png", "8-[]": "scared.png", ":cool:": "cool.png", ":kiss:": "kiss.png", ":notes:": "notes.png", ":calm:": "calm.png", ":-0": "shocked.png", ":greenstar:": "greenstar.png", ">:][": "angry.png", ">:]]": "evil2.png", "B))": "cool2.png", ">:)": "evil.png", ">:(": "angry3.png", ">:/": "angry2.png", ":lol:": "lol.png", ":scared:": "scared.png", ">:>": "evil.png", ">:<": "angry3.png", ">:D": "evil2.png", "B]]": "cool2.png", ">:((": "angry3.png", ">:[": "angry3.png", ":sick:": "unwell.png", ":-/": "getlost.png", ":cry:": "cry.png", "<3": "heart.png", ":leaf:": "leaf.png", ">:))": "evil2.png", ":bluestar:": "bluestar.png", ";-0": "shocked.png", ":weed:": "leaf.png", ":zzzz:": "zzz.png", ":sing:": "notes.png", ":yellowstar:": "yellowstar.png", ";-/": "getlost.png", ";-)": "blink.png", ":dead:": "dead.png", ";-S": "none2.png", "^^": "pleased.png", ";-P": "tongue.png", ";-]": "blink.png", ";-\\": "getlost.png", ":flower:": "flower.png", ":puke:": "unwell.png", ";-O": "shocked.png", ":love:": "heart.png", ";-o": "shocked.png", ":))))": "grin.png", ":))": "grin.png"}

Binary file not shown.

After

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 B

BIN
toxygen/smileys/ksk/cry.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 929 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 935 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B

BIN
toxygen/smileys/ksk/lol.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 957 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 989 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 782 B

BIN
toxygen/smileys/ksk/sad.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 918 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

BIN
toxygen/smileys/ksk/zzz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 990 B

BIN
toxygen/stickers/tox/tox_logo.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

View File

@ -1245,10 +1245,20 @@ QPushButton:hover
}
#messages:item:selected
{
background-color: #1E90FF;
}
MessageEdit
{
background-color: transparent;
}
#messages:item:selected QListWidgetItem
{
background-color: #1E90FF;
}
#friends_list:item:selected
{
background-color: #333333;

View File

@ -12,8 +12,6 @@ class ToxAV:
peers.
"""
libtoxav = LibToxAV()
# -----------------------------------------------------------------------------------------------------------------
# Creation and destruction
# -----------------------------------------------------------------------------------------------------------------
@ -24,9 +22,10 @@ class ToxAV:
:param tox_pointer: pointer to Tox instance
"""
self.libtoxav = LibToxAV()
toxav_err_new = c_int()
ToxAV.libtoxav.toxav_new.restype = POINTER(c_void_p)
self._toxav_pointer = ToxAV.libtoxav.toxav_new(tox_pointer, byref(toxav_err_new))
self.libtoxav.toxav_new.restype = POINTER(c_void_p)
self._toxav_pointer = self.libtoxav.toxav_new(tox_pointer, byref(toxav_err_new))
toxav_err_new = toxav_err_new.value
if toxav_err_new == TOXAV_ERR_NEW['NULL']:
raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
@ -48,7 +47,7 @@ class ToxAV:
If any calls were ongoing, these will be forcibly terminated without notifying peers. After calling this
function, no other functions may be called and the av pointer becomes invalid.
"""
ToxAV.libtoxav.toxav_kill(self._toxav_pointer)
self.libtoxav.toxav_kill(self._toxav_pointer)
def get_tox_pointer(self):
"""
@ -56,8 +55,8 @@ class ToxAV:
:return: pointer to the Tox instance
"""
ToxAV.libtoxav.toxav_get_tox.restype = POINTER(c_void_p)
return ToxAV.libtoxav.toxav_get_tox(self._toxav_pointer)
self.libtoxav.toxav_get_tox.restype = POINTER(c_void_p)
return self.libtoxav.toxav_get_tox(self._toxav_pointer)
# -----------------------------------------------------------------------------------------------------------------
# A/V event loop
@ -70,14 +69,14 @@ class ToxAV:
:return: interval in milliseconds
"""
return ToxAV.libtoxav.toxav_iteration_interval(self._toxav_pointer)
return self.libtoxav.toxav_iteration_interval(self._toxav_pointer)
def iterate(self):
"""
Main loop for the session. This function needs to be called in intervals of toxav_iteration_interval()
milliseconds. It is best called in the separate thread from tox_iterate.
"""
ToxAV.libtoxav.toxav_iterate(self._toxav_pointer)
self.libtoxav.toxav_iterate(self._toxav_pointer)
# -----------------------------------------------------------------------------------------------------------------
# Call setup
@ -97,7 +96,7 @@ class ToxAV:
:return: True on success.
"""
toxav_err_call = c_int()
result = ToxAV.libtoxav.toxav_call(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
result = self.libtoxav.toxav_call(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
c_uint32(video_bit_rate), byref(toxav_err_call))
toxav_err_call = toxav_err_call.value
if toxav_err_call == TOXAV_ERR_CALL['OK']:
@ -131,7 +130,7 @@ class ToxAV:
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_bool, c_bool, c_void_p)
self.call_cb = c_callback(callback)
ToxAV.libtoxav.toxav_callback_call(self._toxav_pointer, self.call_cb, user_data)
self.libtoxav.toxav_callback_call(self._toxav_pointer, self.call_cb, user_data)
def answer(self, friend_number, audio_bit_rate, video_bit_rate):
"""
@ -146,7 +145,7 @@ class ToxAV:
:return: True on success.
"""
toxav_err_answer = c_int()
result = ToxAV.libtoxav.toxav_answer(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
result = self.libtoxav.toxav_answer(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
c_uint32(video_bit_rate), byref(toxav_err_answer))
toxav_err_answer = toxav_err_answer.value
if toxav_err_answer == TOXAV_ERR_ANSWER['OK']:
@ -184,7 +183,7 @@ class ToxAV:
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_void_p)
self.call_state_cb = c_callback(callback)
ToxAV.libtoxav.toxav_callback_call_state(self._toxav_pointer, self.call_state_cb, user_data)
self.libtoxav.toxav_callback_call_state(self._toxav_pointer, self.call_state_cb, user_data)
# -----------------------------------------------------------------------------------------------------------------
# Call control
@ -199,7 +198,7 @@ class ToxAV:
:return: True on success.
"""
toxav_err_call_control = c_int()
result = ToxAV.libtoxav.toxav_call_control(self._toxav_pointer, c_uint32(friend_number), c_int(control),
result = self.libtoxav.toxav_call_control(self._toxav_pointer, c_uint32(friend_number), c_int(control),
byref(toxav_err_call_control))
toxav_err_call_control = toxav_err_call_control.value
if toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['OK']:
@ -241,7 +240,7 @@ class ToxAV:
24000, or 48000.
"""
toxav_err_send_frame = c_int()
result = ToxAV.libtoxav.toxav_audio_send_frame(self._toxav_pointer, c_uint32(friend_number),
result = self.libtoxav.toxav_audio_send_frame(self._toxav_pointer, c_uint32(friend_number),
cast(pcm, c_void_p),
c_size_t(sample_count), c_uint8(channels),
c_uint32(sampling_rate), byref(toxav_err_send_frame))
@ -281,7 +280,7 @@ class ToxAV:
:param v: V (Chroma) plane data.
"""
toxav_err_send_frame = c_int()
result = ToxAV.libtoxav.toxav_video_send_frame(self._toxav_pointer, c_uint32(friend_number), c_uint16(width),
result = self.libtoxav.toxav_video_send_frame(self._toxav_pointer, c_uint32(friend_number), c_uint16(width),
c_uint16(height), c_char_p(y), c_char_p(u), c_char_p(v),
byref(toxav_err_send_frame))
toxav_err_send_frame = toxav_err_send_frame.value
@ -328,7 +327,7 @@ class ToxAV:
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t, c_uint8, c_uint32, c_void_p)
self.audio_receive_frame_cb = c_callback(callback)
ToxAV.libtoxav.toxav_callback_audio_receive_frame(self._toxav_pointer, self.audio_receive_frame_cb, user_data)
self.libtoxav.toxav_callback_audio_receive_frame(self._toxav_pointer, self.audio_receive_frame_cb, user_data)
def callback_video_receive_frame(self, callback, user_data):
"""
@ -360,4 +359,4 @@ class ToxAV:
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint16, c_uint16, POINTER(c_uint8), POINTER(c_uint8),
POINTER(c_uint8), c_int32, c_int32, c_int32, c_void_p)
self.video_receive_frame_cb = c_callback(callback)
ToxAV.libtoxav.toxav_callback_video_receive_frame(self._toxav_pointer, self.video_receive_frame_cb, user_data)
self.libtoxav.toxav_callback_video_receive_frame(self._toxav_pointer, self.video_receive_frame_cb, user_data)

View File

@ -36,10 +36,9 @@ TOX_PASS_ENCRYPTION_EXTRA_LENGTH = 80
class ToxEncryptSave(util.Singleton):
libtoxencryptsave = libtox.LibToxEncryptSave()
def __init__(self):
super().__init__()
self.libtoxencryptsave = libtox.LibToxEncryptSave()
self._passphrase = None
def set_password(self, passphrase):

View File

@ -3,22 +3,22 @@
<context>
<name>AddContact</name>
<message>
<location filename="menu.py" line="70"/>
<location filename="menu.py" line="75"/>
<source>Add contact</source>
<translation>Add contact</translation>
</message>
<message>
<location filename="menu.py" line="72"/>
<location filename="menu.py" line="77"/>
<source>TOX ID:</source>
<translation>TOX ID:</translation>
</message>
<message>
<location filename="menu.py" line="73"/>
<location filename="menu.py" line="78"/>
<source>Message:</source>
<translation>Message:</translation>
</message>
<message>
<location filename="menu.py" line="74"/>
<location filename="menu.py" line="79"/>
<source>TOX ID or public key of contact</source>
<translation type="unfinished"></translation>
</message>
@ -34,32 +34,32 @@
<context>
<name>Form</name>
<message>
<location filename="menu.py" line="71"/>
<location filename="menu.py" line="76"/>
<source>Send request</source>
<translation>Send request</translation>
</message>
<message>
<location filename="menu.py" line="334"/>
<location filename="menu.py" line="339"/>
<source>IPv6</source>
<translation>IPv6</translation>
</message>
<message>
<location filename="menu.py" line="335"/>
<location filename="menu.py" line="340"/>
<source>UDP</source>
<translation>UDP</translation>
</message>
<message>
<location filename="menu.py" line="336"/>
<location filename="menu.py" line="341"/>
<source>Proxy</source>
<translation>Proxy</translation>
</message>
<message>
<location filename="menu.py" line="337"/>
<location filename="menu.py" line="342"/>
<source>IP:</source>
<translation>IP:</translation>
</message>
<message>
<location filename="menu.py" line="338"/>
<location filename="menu.py" line="343"/>
<source>Port:</source>
<translation>Port:</translation>
</message>
@ -69,12 +69,12 @@
<translation type="obsolete">Online contacts</translation>
</message>
<message>
<location filename="menu.py" line="340"/>
<location filename="menu.py" line="345"/>
<source>HTTP</source>
<translation>HTTP</translation>
</message>
<message>
<location filename="menu.py" line="342"/>
<location filename="menu.py" line="347"/>
<source>WARNING:
using proxy with enabled UDP
can produce IP leak</source>
@ -84,98 +84,98 @@ can produce IP leak</source>
<context>
<name>MainWindow</name>
<message>
<location filename="mainscreen.py" line="101"/>
<location filename="mainscreen.py" line="104"/>
<source>Profile</source>
<translation></translation>
</message>
<message>
<location filename="mainscreen.py" line="107"/>
<location filename="mainscreen.py" line="110"/>
<source>Settings</source>
<translation></translation>
</message>
<message>
<location filename="mainscreen.py" line="359"/>
<location filename="mainscreen.py" line="364"/>
<source>About</source>
<translation></translation>
</message>
<message>
<location filename="mainscreen.py" line="100"/>
<location filename="mainscreen.py" line="103"/>
<source>Add contact</source>
<translation></translation>
</message>
<message>
<location filename="mainscreen.py" line="102"/>
<location filename="mainscreen.py" line="105"/>
<source>Privacy</source>
<translation></translation>
</message>
<message>
<location filename="mainscreen.py" line="103"/>
<location filename="mainscreen.py" line="106"/>
<source>Interface</source>
<translation></translation>
</message>
<message>
<location filename="mainscreen.py" line="104"/>
<location filename="mainscreen.py" line="107"/>
<source>Notifications</source>
<translation></translation>
</message>
<message>
<location filename="mainscreen.py" line="105"/>
<location filename="mainscreen.py" line="108"/>
<source>Network</source>
<translation></translation>
</message>
<message>
<location filename="mainscreen.py" line="106"/>
<location filename="mainscreen.py" line="109"/>
<source>About program</source>
<translation></translation>
</message>
<message>
<location filename="profile.py" line="753"/>
<location filename="profile.py" line="761"/>
<source>User {} wants to add you to contact list. Message:
{}</source>
<translation></translation>
</message>
<message>
<location filename="profile.py" line="755"/>
<location filename="profile.py" line="763"/>
<source>Friend request</source>
<translation></translation>
</message>
<message>
<location filename="mainscreen.py" line="430"/>
<location filename="mainscreen.py" line="455"/>
<source>Choose file</source>
<translation>Choose file</translation>
</message>
<message>
<location filename="mainscreen.py" line="493"/>
<location filename="mainscreen.py" line="518"/>
<source>Disallow auto accept</source>
<translation></translation>
</message>
<message>
<location filename="mainscreen.py" line="494"/>
<location filename="mainscreen.py" line="519"/>
<source>Allow auto accept</source>
<translation></translation>
</message>
<message>
<location filename="mainscreen.py" line="496"/>
<location filename="mainscreen.py" line="521"/>
<source>Set alias</source>
<translation></translation>
</message>
<message>
<location filename="mainscreen.py" line="497"/>
<location filename="mainscreen.py" line="522"/>
<source>Clear history</source>
<translation></translation>
</message>
<message>
<location filename="mainscreen.py" line="504"/>
<location filename="mainscreen.py" line="529"/>
<source>Remove friend</source>
<translation></translation>
</message>
<message>
<location filename="profile.py" line="592"/>
<location filename="profile.py" line="600"/>
<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>
</message>
<message>
<location filename="mainscreen.py" line="108"/>
<location filename="mainscreen.py" line="111"/>
<source>Audio</source>
<translation>Audio</translation>
</message>
@ -185,24 +185,24 @@ can produce IP leak</source>
<translation type="obsolete">Find contact</translation>
</message>
<message>
<location filename="profile.py" line="725"/>
<location filename="profile.py" line="733"/>
<source>Friend added</source>
<translation>Friend added</translation>
</message>
<message>
<location filename="mainscreen.py" line="360"/>
<location filename="mainscreen.py" line="365"/>
<source>Toxygen is Tox client written on Python.
Version: </source>
<translation>Toxygen is Tox client written on Python.
Version:</translation>
</message>
<message>
<location filename="profile.py" line="726"/>
<location filename="profile.py" line="734"/>
<source>Friend added without sending friend request</source>
<translation>Friend added without sending friend request</translation>
</message>
<message>
<location filename="list_items.py" line="486"/>
<location filename="list_items.py" line="495"/>
<source>Choose folder</source>
<translation>Choose folder</translation>
</message>
@ -217,47 +217,47 @@ Version:</translation>
<translation type="obsolete">Send file</translation>
</message>
<message>
<location filename="mainscreen.py" line="110"/>
<location filename="mainscreen.py" line="113"/>
<source>Send message</source>
<translation>Send message</translation>
</message>
<message>
<location filename="mainscreen.py" line="111"/>
<location filename="mainscreen.py" line="114"/>
<source>Start audio call with friend</source>
<translation>Start audio call with friend</translation>
</message>
<message>
<location filename="mainscreen.py" line="509"/>
<location filename="mainscreen.py" line="534"/>
<source>Plugins</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="96"/>
<location filename="mainscreen.py" line="99"/>
<source>List of plugins</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="109"/>
<location filename="mainscreen.py" line="112"/>
<source>Search</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="113"/>
<location filename="mainscreen.py" line="116"/>
<source>All</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="114"/>
<location filename="mainscreen.py" line="117"/>
<source>Online</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="505"/>
<location filename="mainscreen.py" line="530"/>
<source>Notes</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="527"/>
<location filename="mainscreen.py" line="552"/>
<source>Notes about user</source>
<translation type="unfinished"></translation>
</message>
@ -307,7 +307,7 @@ Version:</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="profile.py" line="259"/>
<location filename="profile.py" line="261"/>
<source>User {} is now known as {}</source>
<translation type="unfinished"></translation>
</message>
@ -317,35 +317,75 @@ Version:</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="94"/>
<location filename="mainscreen.py" line="97"/>
<source>Lock</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="403"/>
<location filename="mainscreen.py" line="428"/>
<source>Cannot lock app</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="406"/>
<location filename="mainscreen.py" line="431"/>
<source>Error. Profile password is not set.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="499"/>
<location filename="mainscreen.py" line="524"/>
<source>Name</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="500"/>
<location filename="mainscreen.py" line="525"/>
<source>Status message</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="501"/>
<location filename="mainscreen.py" line="526"/>
<source>Public key</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="main.py" line="105"/>
<source>Error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="main.py" line="108"/>
<source>Profile with this name already exists</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="654"/>
<source>Choose folder with sticker pack</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="668"/>
<source>Choose folder with smiley pack</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="119"/>
<source>Import plugin</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="406"/>
<source>Choose folder with plugin</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="415"/>
<source>Restart Toxygen</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="418"/>
<source>Plugin will be loaded after restart</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MenuWindow</name>
@ -398,12 +438,12 @@ Version:</translation>
<context>
<name>NetworkSettings</name>
<message>
<location filename="menu.py" line="333"/>
<location filename="menu.py" line="338"/>
<source>Network settings</source>
<translation>Network settings</translation>
</message>
<message>
<location filename="menu.py" line="339"/>
<location filename="menu.py" line="344"/>
<source>Restart TOX core</source>
<translation>Restart Tox core</translation>
</message>
@ -411,12 +451,12 @@ Version:</translation>
<context>
<name>PluginWindow</name>
<message>
<location filename="plugins/plugin_super_class.py" line="132"/>
<location filename="plugins/plugin_super_class.py" line="133"/>
<source>List of commands for plugin {}</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="plugins/plugin_super_class.py" line="133"/>
<location filename="plugins/plugin_super_class.py" line="134"/>
<source>No commands available</source>
<translation type="unfinished"></translation>
</message>
@ -424,42 +464,42 @@ Version:</translation>
<context>
<name>PluginsForm</name>
<message>
<location filename="menu.py" line="761"/>
<location filename="menu.py" line="812"/>
<source>Plugins</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="762"/>
<location filename="menu.py" line="813"/>
<source>Open selected plugin</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="775"/>
<location filename="menu.py" line="826"/>
<source>No GUI found for this plugin</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="791"/>
<location filename="menu.py" line="842"/>
<source>No description available</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="807"/>
<location filename="menu.py" line="858"/>
<source>Disable plugin</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="809"/>
<location filename="menu.py" line="860"/>
<source>Enable plugin</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="799"/>
<location filename="menu.py" line="850"/>
<source>No plugins found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="776"/>
<location filename="menu.py" line="827"/>
<source>Error</source>
<translation type="unfinished"></translation>
</message>
@ -467,122 +507,122 @@ Version:</translation>
<context>
<name>ProfileSettingsForm</name>
<message>
<location filename="menu.py" line="169"/>
<location filename="menu.py" line="174"/>
<source>Export profile</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="170"/>
<location filename="menu.py" line="175"/>
<source>Profile settings</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="171"/>
<location filename="menu.py" line="176"/>
<source>Name:</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="172"/>
<location filename="menu.py" line="177"/>
<source>Status:</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="173"/>
<location filename="menu.py" line="178"/>
<source>TOX ID:</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="174"/>
<location filename="menu.py" line="179"/>
<source>Copy TOX ID</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="175"/>
<location filename="menu.py" line="180"/>
<source>New avatar</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="176"/>
<location filename="menu.py" line="181"/>
<source>Reset avatar</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="177"/>
<location filename="menu.py" line="182"/>
<source>New NoSpam</source>
<translation>New NoSpam</translation>
</message>
<message>
<location filename="menu.py" line="178"/>
<location filename="menu.py" line="183"/>
<source>Profile password</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="179"/>
<location filename="menu.py" line="184"/>
<source>Password (at least 8 symbols)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="180"/>
<location filename="menu.py" line="185"/>
<source>Confirm password</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="181"/>
<location filename="menu.py" line="186"/>
<source>Set password</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="221"/>
<location filename="menu.py" line="226"/>
<source>Passwords do not match</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="183"/>
<location filename="menu.py" line="188"/>
<source>Leaving blank will reset current password</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="184"/>
<location filename="menu.py" line="189"/>
<source>There is no way to recover lost passwords</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="217"/>
<location filename="menu.py" line="222"/>
<source>Password must be at least 8 symbols</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="250"/>
<location filename="menu.py" line="255"/>
<source>Choose avatar</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="185"/>
<location filename="menu.py" line="190"/>
<source>Online</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="186"/>
<location filename="menu.py" line="191"/>
<source>Away</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="187"/>
<location filename="menu.py" line="192"/>
<source>Busy</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="202"/>
<location filename="menu.py" line="207"/>
<source>Mark as not default profile</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="206"/>
<location filename="menu.py" line="211"/>
<source>Mark as default profile</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="188"/>
<location filename="menu.py" line="193"/>
<source>Copy public key</source>
<translation type="unfinished"></translation>
</message>
@ -619,11 +659,6 @@ Version:</translation>
<source>Since v0.1.3 Toxygen supports plugins. &lt;a href=&quot;https://github.com/xveduk/toxygen/blob/master/docs/plugins.md&quot;&gt;Read more&lt;/a&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen_widgets.py" line="361"/>
<source>New in Toxygen v0.2.2:&lt;br&gt;Users can lock application using profile password.&lt;br&gt;Compact contact list support&lt;br&gt;Bug fixes&lt;br&gt;Tox DNS improvements</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen_widgets.py" line="341"/>
<source>Right click on screenshot button hides app to tray during screenshot.</source>
@ -640,25 +675,40 @@ Version:</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen_widgets.py" line="369"/>
<location filename="mainscreen_widgets.py" line="377"/>
<source>Set new NoSpam to avoid spam friend requests: Profile -&gt; Settings -&gt; Set new NoSpam.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen_widgets.py" line="361"/>
<source>New in Toxygen v0.2.3:&lt;br&gt;TCS compliance&lt;br&gt;Plugins, smileys and stickers import&lt;br&gt;Bug fixes</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen_widgets.py" line="369"/>
<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>
</message>
<message>
<location filename="mainscreen_widgets.py" line="373"/>
<source>Use right click on inline image to save it</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>audioSettingsForm</name>
<message>
<location filename="menu.py" line="718"/>
<location filename="menu.py" line="769"/>
<source>Audio settings</source>
<translation>Audio settings</translation>
</message>
<message>
<location filename="menu.py" line="719"/>
<location filename="menu.py" line="770"/>
<source>Input device:</source>
<translation>Input device:</translation>
</message>
<message>
<location filename="menu.py" line="720"/>
<location filename="menu.py" line="771"/>
<source>Output device:</source>
<translation>Output device:</translation>
</message>
@ -666,32 +716,32 @@ Version:</translation>
<context>
<name>incoming_call</name>
<message>
<location filename="profile.py" line="1132"/>
<location filename="profile.py" line="1141"/>
<source>Incoming video call</source>
<translation>Incoming video call</translation>
</message>
<message>
<location filename="profile.py" line="1135"/>
<location filename="profile.py" line="1144"/>
<source>Incoming audio call</source>
<translation>Incoming audio call</translation>
</message>
<message>
<location filename="profile.py" line="1115"/>
<location filename="profile.py" line="1124"/>
<source>Outgoing video call</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="profile.py" line="1118"/>
<location filename="profile.py" line="1127"/>
<source>Outgoing audio call</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="profile.py" line="1164"/>
<location filename="profile.py" line="1173"/>
<source>Call declined</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="profile.py" line="1166"/>
<location filename="profile.py" line="1175"/>
<source>Call finished</source>
<translation type="unfinished"></translation>
</message>
@ -699,60 +749,75 @@ Version:</translation>
<context>
<name>interfaceForm</name>
<message>
<location filename="menu.py" line="619"/>
<location filename="menu.py" line="637"/>
<source>Interface settings</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="620"/>
<location filename="menu.py" line="638"/>
<source>Theme:</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="621"/>
<location filename="menu.py" line="639"/>
<source>Language:</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="622"/>
<location filename="menu.py" line="640"/>
<source>Smileys</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="623"/>
<location filename="menu.py" line="641"/>
<source>Smiley pack:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="624"/>
<location filename="menu.py" line="642"/>
<source>Mirror mode</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="625"/>
<location filename="menu.py" line="643"/>
<source>Messages font size:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="667"/>
<location filename="menu.py" line="718"/>
<source>Restart app to apply settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="668"/>
<location filename="menu.py" line="719"/>
<source>Restart required</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="626"/>
<location filename="menu.py" line="644"/>
<source>Select unread messages notification color</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="627"/>
<location filename="menu.py" line="645"/>
<source>Compact contact list</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="646"/>
<source>Import smiley pack</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="647"/>
<source>Import sticker pack</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="636"/>
<source>Show avatars in chat</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>login</name>
@ -802,30 +867,35 @@ Version:</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="main.py" line="127"/>
<location filename="main.py" line="154"/>
<source>Other instance of Toxygen uses this profile or profile was not properly closed. Continue?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="main.py" line="120"/>
<source>Do you want to set profile password?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>notificationsForm</name>
<message>
<location filename="menu.py" line="530"/>
<location filename="menu.py" line="535"/>
<source>Notification settings</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="531"/>
<location filename="menu.py" line="536"/>
<source>Enable notifications</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="532"/>
<location filename="menu.py" line="537"/>
<source>Enable call&apos;s sound</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="533"/>
<location filename="menu.py" line="538"/>
<source>Enable sound notifications</source>
<translation></translation>
</message>
@ -833,72 +903,72 @@ Version:</translation>
<context>
<name>privacySettings</name>
<message>
<location filename="menu.py" line="426"/>
<location filename="menu.py" line="431"/>
<source>Privacy settings</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="427"/>
<location filename="menu.py" line="432"/>
<source>Save chat history</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="428"/>
<location filename="menu.py" line="433"/>
<source>Allow file auto accept</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="429"/>
<location filename="menu.py" line="434"/>
<source>Send typing notifications</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="430"/>
<location filename="menu.py" line="435"/>
<source>Auto accept default path:</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="431"/>
<location filename="menu.py" line="436"/>
<source>Change</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="432"/>
<location filename="menu.py" line="437"/>
<source>Allow inlines</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="477"/>
<location filename="menu.py" line="482"/>
<source>Chat history</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="480"/>
<location filename="menu.py" line="485"/>
<source>History will be cleaned! Continue?</source>
<translation></translation>
</message>
<message>
<location filename="menu.py" line="434"/>
<location filename="menu.py" line="439"/>
<source>Blocked users:</source>
<translation>Blocked users:</translation>
</message>
<message>
<location filename="menu.py" line="435"/>
<location filename="menu.py" line="440"/>
<source>Unblock</source>
<translation>Unblock</translation>
</message>
<message>
<location filename="menu.py" line="436"/>
<location filename="menu.py" line="441"/>
<source>Block user</source>
<translation>Block user</translation>
</message>
<message>
<location filename="menu.py" line="448"/>
<location filename="menu.py" line="453"/>
<source>Add to friend list</source>
<translation>Add to friend list</translation>
</message>
<message>
<location filename="menu.py" line="449"/>
<location filename="menu.py" line="454"/>
<source>Do you want to add this user to friend list?</source>
<translation>Do you want to add this user to friend list?</translation>
</message>
@ -908,12 +978,12 @@ Version:</translation>
<translation type="obsolete">Block by TOX ID:</translation>
</message>
<message>
<location filename="menu.py" line="433"/>
<location filename="menu.py" line="438"/>
<source>Block by public key:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="437"/>
<location filename="menu.py" line="442"/>
<source>Save unsent messages only</source>
<translation type="unfinished"></translation>
</message>
@ -921,32 +991,32 @@ Version:</translation>
<context>
<name>tray</name>
<message>
<location filename="main.py" line="176"/>
<location filename="main.py" line="203"/>
<source>Open Toxygen</source>
<translation></translation>
</message>
<message>
<location filename="main.py" line="185"/>
<location filename="main.py" line="212"/>
<source>Exit</source>
<translation></translation>
</message>
<message>
<location filename="main.py" line="177"/>
<location filename="main.py" line="204"/>
<source>Set status</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="main.py" line="178"/>
<location filename="main.py" line="205"/>
<source>Online</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="main.py" line="179"/>
<location filename="main.py" line="206"/>
<source>Away</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="main.py" line="180"/>
<location filename="main.py" line="207"/>
<source>Busy</source>
<translation type="unfinished"></translation>
</message>

View File

@ -3,22 +3,22 @@
<context>
<name>AddContact</name>
<message>
<location filename="menu.py" line="70"/>
<location filename="menu.py" line="75"/>
<source>Add contact</source>
<translation>Rajouter un contact</translation>
</message>
<message>
<location filename="menu.py" line="72"/>
<location filename="menu.py" line="77"/>
<source>TOX ID:</source>
<translation>ID TOX :</translation>
</message>
<message>
<location filename="menu.py" line="73"/>
<location filename="menu.py" line="78"/>
<source>Message:</source>
<translation>Message :</translation>
</message>
<message>
<location filename="menu.py" line="74"/>
<location filename="menu.py" line="79"/>
<source>TOX ID or public key of contact</source>
<translation type="unfinished"></translation>
</message>
@ -34,32 +34,32 @@
<context>
<name>Form</name>
<message>
<location filename="menu.py" line="71"/>
<location filename="menu.py" line="76"/>
<source>Send request</source>
<translation>Envoyer une demande</translation>
</message>
<message>
<location filename="menu.py" line="334"/>
<location filename="menu.py" line="339"/>
<source>IPv6</source>
<translation>IPv6</translation>
</message>
<message>
<location filename="menu.py" line="335"/>
<location filename="menu.py" line="340"/>
<source>UDP</source>
<translation>UDP</translation>
</message>
<message>
<location filename="menu.py" line="336"/>
<location filename="menu.py" line="341"/>
<source>Proxy</source>
<translation>Proxy</translation>
</message>
<message>
<location filename="menu.py" line="337"/>
<location filename="menu.py" line="342"/>
<source>IP:</source>
<translation>IP :</translation>
</message>
<message>
<location filename="menu.py" line="338"/>
<location filename="menu.py" line="343"/>
<source>Port:</source>
<translation>Port :</translation>
</message>
@ -69,12 +69,12 @@
<translation type="obsolete">Contacts connectés</translation>
</message>
<message>
<location filename="menu.py" line="340"/>
<location filename="menu.py" line="345"/>
<source>HTTP</source>
<translation>HTTP</translation>
</message>
<message>
<location filename="menu.py" line="342"/>
<location filename="menu.py" line="347"/>
<source>WARNING:
using proxy with enabled UDP
can produce IP leak</source>
@ -84,58 +84,58 @@ can produce IP leak</source>
<context>
<name>MainWindow</name>
<message>
<location filename="mainscreen.py" line="101"/>
<location filename="mainscreen.py" line="104"/>
<source>Profile</source>
<translation>Profile</translation>
</message>
<message>
<location filename="mainscreen.py" line="107"/>
<location filename="mainscreen.py" line="110"/>
<source>Settings</source>
<translation>Paramêtres</translation>
</message>
<message>
<location filename="mainscreen.py" line="359"/>
<location filename="mainscreen.py" line="364"/>
<source>About</source>
<translation>À Propos</translation>
</message>
<message>
<location filename="mainscreen.py" line="100"/>
<location filename="mainscreen.py" line="103"/>
<source>Add contact</source>
<translation>Rajouter un contact</translation>
</message>
<message>
<location filename="mainscreen.py" line="102"/>
<location filename="mainscreen.py" line="105"/>
<source>Privacy</source>
<translation>Confidentialité</translation>
</message>
<message>
<location filename="mainscreen.py" line="103"/>
<location filename="mainscreen.py" line="106"/>
<source>Interface</source>
<translation>Interface</translation>
</message>
<message>
<location filename="mainscreen.py" line="104"/>
<location filename="mainscreen.py" line="107"/>
<source>Notifications</source>
<translation>Notifications</translation>
</message>
<message>
<location filename="mainscreen.py" line="105"/>
<location filename="mainscreen.py" line="108"/>
<source>Network</source>
<translation>Réseau</translation>
</message>
<message>
<location filename="mainscreen.py" line="106"/>
<location filename="mainscreen.py" line="109"/>
<source>About program</source>
<translation>À propos du programme</translation>
</message>
<message>
<location filename="profile.py" line="753"/>
<location filename="profile.py" line="761"/>
<source>User {} wants to add you to contact list. Message:
{}</source>
<translation>L&apos;Utilisateur {} veut vout rajouter à sa liste de contacts. Message : {}</translation>
</message>
<message>
<location filename="profile.py" line="755"/>
<location filename="profile.py" line="763"/>
<source>Friend request</source>
<translation>Demande d&apos;amis</translation>
</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>
</message>
<message>
<location filename="mainscreen.py" line="430"/>
<location filename="mainscreen.py" line="455"/>
<source>Choose file</source>
<translation>Choisir un fichier</translation>
</message>
<message>
<location filename="mainscreen.py" line="493"/>
<location filename="mainscreen.py" line="518"/>
<source>Disallow auto accept</source>
<translation>Désactiver l&apos;auto-réception</translation>
</message>
<message>
<location filename="mainscreen.py" line="494"/>
<location filename="mainscreen.py" line="519"/>
<source>Allow auto accept</source>
<translation>Activer l&apos;auto-réception</translation>
</message>
<message>
<location filename="mainscreen.py" line="496"/>
<location filename="mainscreen.py" line="521"/>
<source>Set alias</source>
<translation>Définir un alias</translation>
</message>
<message>
<location filename="mainscreen.py" line="497"/>
<location filename="mainscreen.py" line="522"/>
<source>Clear history</source>
<translation>Vider l&apos;historique</translation>
</message>
@ -175,17 +175,17 @@ can produce IP leak</source>
<translation type="obsolete">Copier la clé publique</translation>
</message>
<message>
<location filename="mainscreen.py" line="504"/>
<location filename="mainscreen.py" line="529"/>
<source>Remove friend</source>
<translation>Retirer un ami</translation>
</message>
<message>
<location filename="profile.py" line="592"/>
<location filename="profile.py" line="600"/>
<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>
</message>
<message>
<location filename="mainscreen.py" line="108"/>
<location filename="mainscreen.py" line="111"/>
<source>Audio</source>
<translation>Audio</translation>
</message>
@ -195,24 +195,24 @@ can produce IP leak</source>
<translation type="obsolete">Trouver le contact</translation>
</message>
<message>
<location filename="profile.py" line="725"/>
<location filename="profile.py" line="733"/>
<source>Friend added</source>
<translation>Ami rajouté</translation>
</message>
<message>
<location filename="mainscreen.py" line="360"/>
<location filename="mainscreen.py" line="365"/>
<source>Toxygen is Tox client written on Python.
Version: </source>
<translation>Toxygen est un client Tox écrit en Python.
Version :</translation>
</message>
<message>
<location filename="profile.py" line="726"/>
<location filename="profile.py" line="734"/>
<source>Friend added without sending friend request</source>
<translation>Ami rajouté sans avoir envoyé de demande</translation>
</message>
<message>
<location filename="list_items.py" line="486"/>
<location filename="list_items.py" line="495"/>
<source>Choose folder</source>
<translation>Choisir le dossier</translation>
</message>
@ -227,47 +227,47 @@ Version :</translation>
<translation type="obsolete">Envoyer le fichier</translation>
</message>
<message>
<location filename="mainscreen.py" line="110"/>
<location filename="mainscreen.py" line="113"/>
<source>Send message</source>
<translation>Envoyer le message</translation>
</message>
<message>
<location filename="mainscreen.py" line="111"/>
<location filename="mainscreen.py" line="114"/>
<source>Start audio call with friend</source>
<translation>Lancer un appel audio avec un ami</translation>
</message>
<message>
<location filename="mainscreen.py" line="509"/>
<location filename="mainscreen.py" line="534"/>
<source>Plugins</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="96"/>
<location filename="mainscreen.py" line="99"/>
<source>List of plugins</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="109"/>
<location filename="mainscreen.py" line="112"/>
<source>Search</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="113"/>
<location filename="mainscreen.py" line="116"/>
<source>All</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="114"/>
<location filename="mainscreen.py" line="117"/>
<source>Online</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="505"/>
<location filename="mainscreen.py" line="530"/>
<source>Notes</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="527"/>
<location filename="mainscreen.py" line="552"/>
<source>Notes about user</source>
<translation type="unfinished"></translation>
</message>
@ -317,7 +317,7 @@ Version :</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="profile.py" line="259"/>
<location filename="profile.py" line="261"/>
<source>User {} is now known as {}</source>
<translation type="unfinished"></translation>
</message>
@ -327,35 +327,75 @@ Version :</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="94"/>
<location filename="mainscreen.py" line="97"/>
<source>Lock</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="403"/>
<location filename="mainscreen.py" line="428"/>
<source>Cannot lock app</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="406"/>
<location filename="mainscreen.py" line="431"/>
<source>Error. Profile password is not set.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="499"/>
<location filename="mainscreen.py" line="524"/>
<source>Name</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="500"/>
<location filename="mainscreen.py" line="525"/>
<source>Status message</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="501"/>
<location filename="mainscreen.py" line="526"/>
<source>Public key</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="main.py" line="105"/>
<source>Error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="main.py" line="108"/>
<source>Profile with this name already exists</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="654"/>
<source>Choose folder with sticker pack</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="668"/>
<source>Choose folder with smiley pack</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="119"/>
<source>Import plugin</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="406"/>
<source>Choose folder with plugin</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="415"/>
<source>Restart Toxygen</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen.py" line="418"/>
<source>Plugin will be loaded after restart</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MenuWindow</name>
@ -408,12 +448,12 @@ Version :</translation>
<context>
<name>NetworkSettings</name>
<message>
<location filename="menu.py" line="333"/>
<location filename="menu.py" line="338"/>
<source>Network settings</source>
<translation>Paramètres réseaux</translation>
</message>
<message>
<location filename="menu.py" line="339"/>
<location filename="menu.py" line="344"/>
<source>Restart TOX core</source>
<translation>Relancer le noyau TOX</translation>
</message>
@ -421,12 +461,12 @@ Version :</translation>
<context>
<name>PluginWindow</name>
<message>
<location filename="plugins/plugin_super_class.py" line="132"/>
<location filename="plugins/plugin_super_class.py" line="133"/>
<source>List of commands for plugin {}</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="plugins/plugin_super_class.py" line="133"/>
<location filename="plugins/plugin_super_class.py" line="134"/>
<source>No commands available</source>
<translation type="unfinished"></translation>
</message>
@ -434,42 +474,42 @@ Version :</translation>
<context>
<name>PluginsForm</name>
<message>
<location filename="menu.py" line="761"/>
<location filename="menu.py" line="812"/>
<source>Plugins</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="762"/>
<location filename="menu.py" line="813"/>
<source>Open selected plugin</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="775"/>
<location filename="menu.py" line="826"/>
<source>No GUI found for this plugin</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="791"/>
<location filename="menu.py" line="842"/>
<source>No description available</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="807"/>
<location filename="menu.py" line="858"/>
<source>Disable plugin</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="809"/>
<location filename="menu.py" line="860"/>
<source>Enable plugin</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="799"/>
<location filename="menu.py" line="850"/>
<source>No plugins found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="776"/>
<location filename="menu.py" line="827"/>
<source>Error</source>
<translation type="unfinished"></translation>
</message>
@ -477,122 +517,122 @@ Version :</translation>
<context>
<name>ProfileSettingsForm</name>
<message>
<location filename="menu.py" line="169"/>
<location filename="menu.py" line="174"/>
<source>Export profile</source>
<translation>Exporter le profile</translation>
</message>
<message>
<location filename="menu.py" line="170"/>
<location filename="menu.py" line="175"/>
<source>Profile settings</source>
<translation>Paramêtres du profil</translation>
</message>
<message>
<location filename="menu.py" line="171"/>
<location filename="menu.py" line="176"/>
<source>Name:</source>
<translation>Nom :</translation>
</message>
<message>
<location filename="menu.py" line="172"/>
<location filename="menu.py" line="177"/>
<source>Status:</source>
<translation>Status :</translation>
</message>
<message>
<location filename="menu.py" line="173"/>
<location filename="menu.py" line="178"/>
<source>TOX ID:</source>
<translation>ID TOX :</translation>
</message>
<message>
<location filename="menu.py" line="174"/>
<location filename="menu.py" line="179"/>
<source>Copy TOX ID</source>
<translation>Copier l&apos;ID TOX</translation>
</message>
<message>
<location filename="menu.py" line="175"/>
<location filename="menu.py" line="180"/>
<source>New avatar</source>
<translation>Nouvel avatar</translation>
</message>
<message>
<location filename="menu.py" line="176"/>
<location filename="menu.py" line="181"/>
<source>Reset avatar</source>
<translation>Réinitialiser l&apos;avatar</translation>
</message>
<message>
<location filename="menu.py" line="177"/>
<location filename="menu.py" line="182"/>
<source>New NoSpam</source>
<translation>Nouveau NoSpam</translation>
</message>
<message>
<location filename="menu.py" line="178"/>
<location filename="menu.py" line="183"/>
<source>Profile password</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="179"/>
<location filename="menu.py" line="184"/>
<source>Password (at least 8 symbols)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="180"/>
<location filename="menu.py" line="185"/>
<source>Confirm password</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="181"/>
<location filename="menu.py" line="186"/>
<source>Set password</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="221"/>
<location filename="menu.py" line="226"/>
<source>Passwords do not match</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="183"/>
<location filename="menu.py" line="188"/>
<source>Leaving blank will reset current password</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="184"/>
<location filename="menu.py" line="189"/>
<source>There is no way to recover lost passwords</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="217"/>
<location filename="menu.py" line="222"/>
<source>Password must be at least 8 symbols</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="250"/>
<location filename="menu.py" line="255"/>
<source>Choose avatar</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="185"/>
<location filename="menu.py" line="190"/>
<source>Online</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="186"/>
<location filename="menu.py" line="191"/>
<source>Away</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="187"/>
<location filename="menu.py" line="192"/>
<source>Busy</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="202"/>
<location filename="menu.py" line="207"/>
<source>Mark as not default profile</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="206"/>
<location filename="menu.py" line="211"/>
<source>Mark as default profile</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="188"/>
<location filename="menu.py" line="193"/>
<source>Copy public key</source>
<translation type="unfinished">Copier la clé publique</translation>
</message>
@ -629,11 +669,6 @@ Version :</translation>
<source>Since v0.1.3 Toxygen supports plugins. &lt;a href=&quot;https://github.com/xveduk/toxygen/blob/master/docs/plugins.md&quot;&gt;Read more&lt;/a&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen_widgets.py" line="361"/>
<source>New in Toxygen v0.2.2:&lt;br&gt;Users can lock application using profile password.&lt;br&gt;Compact contact list support&lt;br&gt;Bug fixes&lt;br&gt;Tox DNS improvements</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen_widgets.py" line="341"/>
<source>Right click on screenshot button hides app to tray during screenshot.</source>
@ -650,25 +685,40 @@ Version :</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen_widgets.py" line="369"/>
<location filename="mainscreen_widgets.py" line="377"/>
<source>Set new NoSpam to avoid spam friend requests: Profile -&gt; Settings -&gt; Set new NoSpam.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen_widgets.py" line="361"/>
<source>New in Toxygen v0.2.3:&lt;br&gt;TCS compliance&lt;br&gt;Plugins, smileys and stickers import&lt;br&gt;Bug fixes</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="mainscreen_widgets.py" line="369"/>
<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>
</message>
<message>
<location filename="mainscreen_widgets.py" line="373"/>
<source>Use right click on inline image to save it</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>audioSettingsForm</name>
<message>
<location filename="menu.py" line="718"/>
<location filename="menu.py" line="769"/>
<source>Audio settings</source>
<translation>Paramètres audio</translation>
</message>
<message>
<location filename="menu.py" line="719"/>
<location filename="menu.py" line="770"/>
<source>Input device:</source>
<translation>Péripherique d&apos;entrée :</translation>
</message>
<message>
<location filename="menu.py" line="720"/>
<location filename="menu.py" line="771"/>
<source>Output device:</source>
<translation>Péripherique de sortie :</translation>
</message>
@ -676,32 +726,32 @@ Version :</translation>
<context>
<name>incoming_call</name>
<message>
<location filename="profile.py" line="1132"/>
<location filename="profile.py" line="1141"/>
<source>Incoming video call</source>
<translation>Appel vidéo entrant</translation>
</message>
<message>
<location filename="profile.py" line="1135"/>
<location filename="profile.py" line="1144"/>
<source>Incoming audio call</source>
<translation>Appel audio entrant</translation>
</message>
<message>
<location filename="profile.py" line="1115"/>
<location filename="profile.py" line="1124"/>
<source>Outgoing video call</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="profile.py" line="1118"/>
<location filename="profile.py" line="1127"/>
<source>Outgoing audio call</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="profile.py" line="1164"/>
<location filename="profile.py" line="1173"/>
<source>Call declined</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="profile.py" line="1166"/>
<location filename="profile.py" line="1175"/>
<source>Call finished</source>
<translation type="unfinished"></translation>
</message>
@ -709,60 +759,75 @@ Version :</translation>
<context>
<name>interfaceForm</name>
<message>
<location filename="menu.py" line="619"/>
<location filename="menu.py" line="637"/>
<source>Interface settings</source>
<translation>Paramêtres de l&apos;interface</translation>
</message>
<message>
<location filename="menu.py" line="620"/>
<location filename="menu.py" line="638"/>
<source>Theme:</source>
<translation>Thème :</translation>
</message>
<message>
<location filename="menu.py" line="621"/>
<location filename="menu.py" line="639"/>
<source>Language:</source>
<translation>Langue :</translation>
</message>
<message>
<location filename="menu.py" line="622"/>
<location filename="menu.py" line="640"/>
<source>Smileys</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="623"/>
<location filename="menu.py" line="641"/>
<source>Smiley pack:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="624"/>
<location filename="menu.py" line="642"/>
<source>Mirror mode</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="625"/>
<location filename="menu.py" line="643"/>
<source>Messages font size:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="667"/>
<location filename="menu.py" line="718"/>
<source>Restart app to apply settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="668"/>
<location filename="menu.py" line="719"/>
<source>Restart required</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="626"/>
<location filename="menu.py" line="644"/>
<source>Select unread messages notification color</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="627"/>
<location filename="menu.py" line="645"/>
<source>Compact contact list</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="646"/>
<source>Import smiley pack</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="647"/>
<source>Import sticker pack</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="636"/>
<source>Show avatars in chat</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>login</name>
@ -817,30 +882,35 @@ Version :</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="main.py" line="127"/>
<location filename="main.py" line="154"/>
<source>Other instance of Toxygen uses this profile or profile was not properly closed. Continue?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="main.py" line="120"/>
<source>Do you want to set profile password?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>notificationsForm</name>
<message>
<location filename="menu.py" line="530"/>
<location filename="menu.py" line="535"/>
<source>Notification settings</source>
<translation>Paramêtres de notification</translation>
</message>
<message>
<location filename="menu.py" line="531"/>
<location filename="menu.py" line="536"/>
<source>Enable notifications</source>
<translation>Activer les notifications</translation>
</message>
<message>
<location filename="menu.py" line="532"/>
<location filename="menu.py" line="537"/>
<source>Enable call&apos;s sound</source>
<translation>Activer les sons d&apos;appel</translation>
</message>
<message>
<location filename="menu.py" line="533"/>
<location filename="menu.py" line="538"/>
<source>Enable sound notifications</source>
<translation>Activer les sons de notifications</translation>
</message>
@ -848,72 +918,72 @@ Version :</translation>
<context>
<name>privacySettings</name>
<message>
<location filename="menu.py" line="426"/>
<location filename="menu.py" line="431"/>
<source>Privacy settings</source>
<translation>Paramêtres de confidentialité</translation>
</message>
<message>
<location filename="menu.py" line="427"/>
<location filename="menu.py" line="432"/>
<source>Save chat history</source>
<translation>Sauvegarder l&apos;historique de chat</translation>
</message>
<message>
<location filename="menu.py" line="428"/>
<location filename="menu.py" line="433"/>
<source>Allow file auto accept</source>
<translation>Autoriser les fichier automatiquement</translation>
</message>
<message>
<location filename="menu.py" line="429"/>
<location filename="menu.py" line="434"/>
<source>Send typing notifications</source>
<translation>Notifier la frappe</translation>
</message>
<message>
<location filename="menu.py" line="430"/>
<location filename="menu.py" line="435"/>
<source>Auto accept default path:</source>
<translation>Chemin d&apos;accès des fichiers acceptés automatiquement :</translation>
</message>
<message>
<location filename="menu.py" line="431"/>
<location filename="menu.py" line="436"/>
<source>Change</source>
<translation>Modifier</translation>
</message>
<message>
<location filename="menu.py" line="432"/>
<location filename="menu.py" line="437"/>
<source>Allow inlines</source>
<translation>Activer l&apos;auto-réception</translation>
</message>
<message>
<location filename="menu.py" line="477"/>
<location filename="menu.py" line="482"/>
<source>Chat history</source>
<translation>Historique de chat</translation>
</message>
<message>
<location filename="menu.py" line="480"/>
<location filename="menu.py" line="485"/>
<source>History will be cleaned! Continue?</source>
<translation>L&apos;Historique va être nettoyé ! Confirmer ?</translation>
</message>
<message>
<location filename="menu.py" line="434"/>
<location filename="menu.py" line="439"/>
<source>Blocked users:</source>
<translation>Utilisateurs bloqués :</translation>
</message>
<message>
<location filename="menu.py" line="435"/>
<location filename="menu.py" line="440"/>
<source>Unblock</source>
<translation>Débloquer</translation>
</message>
<message>
<location filename="menu.py" line="436"/>
<location filename="menu.py" line="441"/>
<source>Block user</source>
<translation>Bloquer l&apos;utilisateur</translation>
</message>
<message>
<location filename="menu.py" line="448"/>
<location filename="menu.py" line="453"/>
<source>Add to friend list</source>
<translation>Ajouter à la liste des amis</translation>
</message>
<message>
<location filename="menu.py" line="449"/>
<location filename="menu.py" line="454"/>
<source>Do you want to add this user to friend list?</source>
<translation>Voulez vous rajouter cet utilisateur à votre liste d&apos;amis ?</translation>
</message>
@ -923,12 +993,12 @@ Version :</translation>
<translation type="obsolete">Bloquer l&apos;ID TOX :</translation>
</message>
<message>
<location filename="menu.py" line="433"/>
<location filename="menu.py" line="438"/>
<source>Block by public key:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="menu.py" line="437"/>
<location filename="menu.py" line="442"/>
<source>Save unsent messages only</source>
<translation type="unfinished"></translation>
</message>
@ -936,32 +1006,32 @@ Version :</translation>
<context>
<name>tray</name>
<message>
<location filename="main.py" line="176"/>
<location filename="main.py" line="203"/>
<source>Open Toxygen</source>
<translation>Ouvrir Toxygen</translation>
</message>
<message>
<location filename="main.py" line="185"/>
<location filename="main.py" line="212"/>
<source>Exit</source>
<translation>Quitter</translation>
</message>
<message>
<location filename="main.py" line="177"/>
<location filename="main.py" line="204"/>
<source>Set status</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="main.py" line="178"/>
<location filename="main.py" line="205"/>
<source>Online</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="main.py" line="179"/>
<location filename="main.py" line="206"/>
<source>Away</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="main.py" line="180"/>
<location filename="main.py" line="207"/>
<source>Busy</source>
<translation type="unfinished"></translation>
</message>

Binary file not shown.

View File

@ -1,25 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.0" language="ru_RU">
<!DOCTYPE TS><TS version="1.1" language="ru_RU">
<context>
<name>AddContact</name>
<message>
<location filename="menu.py" line="70"/>
<location filename="menu.py" line="75"/>
<source>Add contact</source>
<translation>Добавить контакт</translation>
</message>
<message>
<location filename="menu.py" line="72"/>
<location filename="menu.py" line="77"/>
<source>TOX ID:</source>
<translation>TOX ID:</translation>
</message>
<message>
<location filename="menu.py" line="73"/>
<location filename="menu.py" line="78"/>
<source>Message:</source>
<translation>Сообщение:</translation>
</message>
<message>
<location filename="menu.py" line="74"/>
<location filename="menu.py" line="79"/>
<source>TOX ID or public key of contact</source>
<translation>TOX ID или публичный ключ контакта</translation>
</message>
@ -35,32 +34,32 @@
<context>
<name>Form</name>
<message>
<location filename="menu.py" line="71"/>
<location filename="menu.py" line="76"/>
<source>Send request</source>
<translation>Отправить запрос</translation>
</message>
<message>
<location filename="menu.py" line="334"/>
<location filename="menu.py" line="339"/>
<source>IPv6</source>
<translation>IPv6</translation>
</message>
<message>
<location filename="menu.py" line="335"/>
<location filename="menu.py" line="340"/>
<source>UDP</source>
<translation>UDP</translation>
</message>
<message>
<location filename="menu.py" line="336"/>
<location filename="menu.py" line="341"/>
<source>Proxy</source>
<translation>Прокси</translation>
</message>
<message>
<location filename="menu.py" line="337"/>
<location filename="menu.py" line="342"/>
<source>IP:</source>
<translation>IP:</translation>
</message>
<message>
<location filename="menu.py" line="338"/>
<location filename="menu.py" line="343"/>
<source>Port:</source>
<translation>Порт:</translation>
</message>
@ -70,12 +69,12 @@
<translation type="obsolete">Контакты в сети</translation>
</message>
<message>
<location filename="menu.py" line="340"/>
<location filename="menu.py" line="345"/>
<source>HTTP</source>
<translation>HTTP</translation>
</message>
<message>
<location filename="menu.py" line="342"/>
<location filename="menu.py" line="347"/>
<source>WARNING:
using proxy with enabled UDP
can produce IP leak</source>
@ -87,84 +86,84 @@ can produce IP leak</source>
<context>
<name>MainWindow</name>
<message>
<location filename="mainscreen.py" line="101"/>
<location filename="mainscreen.py" line="104"/>
<source>Profile</source>
<translation>Профиль</translation>
</message>
<message>
<location filename="mainscreen.py" line="107"/>
<location filename="mainscreen.py" line="110"/>
<source>Settings</source>
<translation>Настройки</translation>
</message>
<message>
<location filename="mainscreen.py" line="359"/>
<location filename="mainscreen.py" line="364"/>
<source>About</source>
<translation>О программе</translation>
</message>
<message>
<location filename="mainscreen.py" line="100"/>
<location filename="mainscreen.py" line="103"/>
<source>Add contact</source>
<translation>Добавить контакт</translation>
</message>
<message>
<location filename="mainscreen.py" line="102"/>
<location filename="mainscreen.py" line="105"/>
<source>Privacy</source>
<translation>Приватность</translation>
</message>
<message>
<location filename="mainscreen.py" line="103"/>
<location filename="mainscreen.py" line="106"/>
<source>Interface</source>
<translation>Интерфейс</translation>
</message>
<message>
<location filename="mainscreen.py" line="104"/>
<location filename="mainscreen.py" line="107"/>
<source>Notifications</source>
<translation>Уведомления</translation>
</message>
<message>
<location filename="mainscreen.py" line="105"/>
<location filename="mainscreen.py" line="108"/>
<source>Network</source>
<translation>Сеть</translation>
</message>
<message>
<location filename="mainscreen.py" line="106"/>
<location filename="mainscreen.py" line="109"/>
<source>About program</source>
<translation>О программе</translation>
</message>
<message>
<location filename="profile.py" line="753"/>
<location filename="profile.py" line="761"/>
<source>User {} wants to add you to contact list. Message:
{}</source>
<translation>Пользователь {} хочет добавить Вас в список контактов. Сообщение:
{}</translation>
</message>
<message>
<location filename="profile.py" line="755"/>
<location filename="profile.py" line="763"/>
<source>Friend request</source>
<translation>Запрос на добавление в друзья</translation>
</message>
<message>
<location filename="mainscreen.py" line="430"/>
<location filename="mainscreen.py" line="455"/>
<source>Choose file</source>
<translation>Выберите файл</translation>
</message>
<message>
<location filename="mainscreen.py" line="493"/>
<location filename="mainscreen.py" line="518"/>
<source>Disallow auto accept</source>
<translation>Запретить автоматическое получение файлов</translation>
</message>
<message>
<location filename="mainscreen.py" line="494"/>
<location filename="mainscreen.py" line="519"/>
<source>Allow auto accept</source>
<translation>Разрешить автоматическое получение файлов</translation>
</message>
<message>
<location filename="mainscreen.py" line="496"/>
<location filename="mainscreen.py" line="521"/>
<source>Set alias</source>
<translation>Изменить псевдоним</translation>
</message>
<message>
<location filename="mainscreen.py" line="497"/>
<location filename="mainscreen.py" line="522"/>
<source>Clear history</source>
<translation>Очистить историю</translation>
</message>
@ -174,17 +173,17 @@ can produce IP leak</source>
<translation type="obsolete">Копировать публичный ключ</translation>
</message>
<message>
<location filename="mainscreen.py" line="504"/>
<location filename="mainscreen.py" line="529"/>
<source>Remove friend</source>
<translation>Удалить друга</translation>
</message>
<message>
<location filename="profile.py" line="592"/>
<location filename="profile.py" line="600"/>
<source>Enter new alias for friend {} or leave empty to use friend&apos;s name:</source>
<translation>Введите новый псевдоним для друга {} или оставьте пустым для использования его имени:</translation>
</message>
<message>
<location filename="mainscreen.py" line="108"/>
<location filename="mainscreen.py" line="111"/>
<source>Audio</source>
<translation>Аудио</translation>
</message>
@ -194,23 +193,23 @@ can produce IP leak</source>
<translation type="obsolete">Найти контакт</translation>
</message>
<message>
<location filename="profile.py" line="725"/>
<location filename="profile.py" line="733"/>
<source>Friend added</source>
<translation>Друг добавлен</translation>
</message>
<message>
<location filename="mainscreen.py" line="360"/>
<location filename="mainscreen.py" line="365"/>
<source>Toxygen is Tox client written on Python.
Version: </source>
<translation>Toxygen - клиент для мессенджера Tox, написанный на Python. Версия: </translation>
</message>
<message>
<location filename="profile.py" line="726"/>
<location filename="profile.py" line="734"/>
<source>Friend added without sending friend request</source>
<translation>Друг добавлен без отправки запроса на добавление в друзья</translation>
</message>
<message>
<location filename="list_items.py" line="486"/>
<location filename="list_items.py" line="495"/>
<source>Choose folder</source>
<translation>Выбрать папку</translation>
</message>
@ -225,47 +224,47 @@ Version: </source>
<translation type="obsolete">Отправить файл</translation>
</message>
<message>
<location filename="mainscreen.py" line="110"/>
<location filename="mainscreen.py" line="113"/>
<source>Send message</source>
<translation>Отправить сообщение</translation>
</message>
<message>
<location filename="mainscreen.py" line="111"/>
<location filename="mainscreen.py" line="114"/>
<source>Start audio call with friend</source>
<translation>Начать аудиозвонок с другом</translation>
</message>
<message>
<location filename="mainscreen.py" line="509"/>
<location filename="mainscreen.py" line="534"/>
<source>Plugins</source>
<translation>Плагины</translation>
</message>
<message>
<location filename="mainscreen.py" line="96"/>
<location filename="mainscreen.py" line="99"/>
<source>List of plugins</source>
<translation>Список плагинов</translation>
</message>
<message>
<location filename="mainscreen.py" line="109"/>
<location filename="mainscreen.py" line="112"/>
<source>Search</source>
<translation>Поиск</translation>
</message>
<message>
<location filename="mainscreen.py" line="113"/>
<location filename="mainscreen.py" line="116"/>
<source>All</source>
<translation>Все</translation>
</message>
<message>
<location filename="mainscreen.py" line="114"/>
<location filename="mainscreen.py" line="117"/>
<source>Online</source>
<translation>Онлайн</translation>
</message>
<message>
<location filename="mainscreen.py" line="505"/>
<location filename="mainscreen.py" line="530"/>
<source>Notes</source>
<translation>Заметки</translation>
</message>
<message>
<location filename="mainscreen.py" line="527"/>
<location filename="mainscreen.py" line="552"/>
<source>Notes about user</source>
<translation>Заметки о пользователе</translation>
</message>
@ -315,7 +314,7 @@ Version: </source>
<translation>Сохранить</translation>
</message>
<message>
<location filename="profile.py" line="259"/>
<location filename="profile.py" line="261"/>
<source>User {} is now known as {}</source>
<translation>Пользователь {} сейчас известен как {}</translation>
</message>
@ -325,35 +324,75 @@ Version: </source>
<translation>Удалить сообщение</translation>
</message>
<message>
<location filename="mainscreen.py" line="94"/>
<location filename="mainscreen.py" line="97"/>
<source>Lock</source>
<translation>Заблокировать</translation>
</message>
<message>
<location filename="mainscreen.py" line="403"/>
<location filename="mainscreen.py" line="428"/>
<source>Cannot lock app</source>
<translation>Невозможно заблокировать приложение</translation>
</message>
<message>
<location filename="mainscreen.py" line="406"/>
<location filename="mainscreen.py" line="431"/>
<source>Error. Profile password is not set.</source>
<translation>Ошибка. Пароль профиля не установлен.</translation>
</message>
<message>
<location filename="mainscreen.py" line="499"/>
<location filename="mainscreen.py" line="524"/>
<source>Name</source>
<translation>Имя</translation>
</message>
<message>
<location filename="mainscreen.py" line="500"/>
<location filename="mainscreen.py" line="525"/>
<source>Status message</source>
<translation>Статус</translation>
</message>
<message>
<location filename="mainscreen.py" line="501"/>
<location filename="mainscreen.py" line="526"/>
<source>Public key</source>
<translation>Публичный ключ</translation>
</message>
<message>
<location filename="main.py" line="105"/>
<source>Error</source>
<translation>Ошибка</translation>
</message>
<message>
<location filename="main.py" line="108"/>
<source>Profile with this name already exists</source>
<translation>Профиль с данным именем уже существует</translation>
</message>
<message>
<location filename="menu.py" line="654"/>
<source>Choose folder with sticker pack</source>
<translation>Выберите папку в паком стикеров</translation>
</message>
<message>
<location filename="menu.py" line="668"/>
<source>Choose folder with smiley pack</source>
<translation>Выберите папку с паком смайлов</translation>
</message>
<message>
<location filename="mainscreen.py" line="119"/>
<source>Import plugin</source>
<translation>Импортировать плагин</translation>
</message>
<message>
<location filename="mainscreen.py" line="406"/>
<source>Choose folder with plugin</source>
<translation>Выберите папку с плагином</translation>
</message>
<message>
<location filename="mainscreen.py" line="415"/>
<source>Restart Toxygen</source>
<translation>Перезапустите Toxygen</translation>
</message>
<message>
<location filename="mainscreen.py" line="418"/>
<source>Plugin will be loaded after restart</source>
<translation>Плагин будет загружен после перезапуска</translation>
</message>
</context>
<context>
<name>MenuWindow</name>
@ -406,12 +445,12 @@ Version: </source>
<context>
<name>NetworkSettings</name>
<message>
<location filename="menu.py" line="333"/>
<location filename="menu.py" line="338"/>
<source>Network settings</source>
<translation>Настройки сети</translation>
</message>
<message>
<location filename="menu.py" line="339"/>
<location filename="menu.py" line="344"/>
<source>Restart TOX core</source>
<translation>Перезапустить ядро TOX</translation>
</message>
@ -419,12 +458,12 @@ Version: </source>
<context>
<name>PluginWindow</name>
<message>
<location filename="plugins/plugin_super_class.py" line="132"/>
<location filename="plugins/plugin_super_class.py" line="133"/>
<source>List of commands for plugin {}</source>
<translation>Список команд для плагина {}</translation>
</message>
<message>
<location filename="plugins/plugin_super_class.py" line="133"/>
<location filename="plugins/plugin_super_class.py" line="134"/>
<source>No commands available</source>
<translation>Команды не найдены</translation>
</message>
@ -432,42 +471,42 @@ Version: </source>
<context>
<name>PluginsForm</name>
<message>
<location filename="menu.py" line="761"/>
<location filename="menu.py" line="812"/>
<source>Plugins</source>
<translation>Плагины</translation>
</message>
<message>
<location filename="menu.py" line="762"/>
<location filename="menu.py" line="813"/>
<source>Open selected plugin</source>
<translation>Открыть выбранный плагин</translation>
</message>
<message>
<location filename="menu.py" line="775"/>
<location filename="menu.py" line="826"/>
<source>No GUI found for this plugin</source>
<translation>GUI для данного плагина не найден</translation>
</message>
<message>
<location filename="menu.py" line="791"/>
<location filename="menu.py" line="842"/>
<source>No description available</source>
<translation>Описание недоступно</translation>
</message>
<message>
<location filename="menu.py" line="807"/>
<location filename="menu.py" line="858"/>
<source>Disable plugin</source>
<translation>Отключить плагин</translation>
</message>
<message>
<location filename="menu.py" line="809"/>
<location filename="menu.py" line="860"/>
<source>Enable plugin</source>
<translation>Включить плагин</translation>
</message>
<message>
<location filename="menu.py" line="799"/>
<location filename="menu.py" line="850"/>
<source>No plugins found</source>
<translation>Плагины не найдены</translation>
</message>
<message>
<location filename="menu.py" line="776"/>
<location filename="menu.py" line="827"/>
<source>Error</source>
<translation>Ошибка</translation>
</message>
@ -475,32 +514,32 @@ Version: </source>
<context>
<name>ProfileSettingsForm</name>
<message>
<location filename="menu.py" line="169"/>
<location filename="menu.py" line="174"/>
<source>Export profile</source>
<translation>Экспорт профиля</translation>
</message>
<message>
<location filename="menu.py" line="170"/>
<location filename="menu.py" line="175"/>
<source>Profile settings</source>
<translation>Настройки профиля</translation>
</message>
<message>
<location filename="menu.py" line="171"/>
<location filename="menu.py" line="176"/>
<source>Name:</source>
<translation>Имя:</translation>
</message>
<message>
<location filename="menu.py" line="172"/>
<location filename="menu.py" line="177"/>
<source>Status:</source>
<translation>Статус:</translation>
</message>
<message>
<location filename="menu.py" line="173"/>
<location filename="menu.py" line="178"/>
<source>TOX ID:</source>
<translation>TOX ID:</translation>
</message>
<message>
<location filename="menu.py" line="174"/>
<location filename="menu.py" line="179"/>
<source>Copy TOX ID</source>
<translation>Копировать TOX ID</translation>
</message>
@ -510,92 +549,92 @@ Version: </source>
<translation type="obsolete">Язык:</translation>
</message>
<message>
<location filename="menu.py" line="175"/>
<location filename="menu.py" line="180"/>
<source>New avatar</source>
<translation>Новый аватар</translation>
</message>
<message>
<location filename="menu.py" line="176"/>
<location filename="menu.py" line="181"/>
<source>Reset avatar</source>
<translation>Сбросить аватар</translation>
</message>
<message>
<location filename="menu.py" line="177"/>
<location filename="menu.py" line="182"/>
<source>New NoSpam</source>
<translation>Новый NoSpam</translation>
</message>
<message>
<location filename="menu.py" line="178"/>
<location filename="menu.py" line="183"/>
<source>Profile password</source>
<translation>Пароль профиля</translation>
</message>
<message>
<location filename="menu.py" line="179"/>
<location filename="menu.py" line="184"/>
<source>Password (at least 8 symbols)</source>
<translation>Пароль (минимум 8 символов)</translation>
</message>
<message>
<location filename="menu.py" line="180"/>
<location filename="menu.py" line="185"/>
<source>Confirm password</source>
<translation>Подтверждение пароля</translation>
</message>
<message>
<location filename="menu.py" line="181"/>
<location filename="menu.py" line="186"/>
<source>Set password</source>
<translation>Изменить пароль</translation>
</message>
<message>
<location filename="menu.py" line="221"/>
<location filename="menu.py" line="226"/>
<source>Passwords do not match</source>
<translation>Пароли не совпадают</translation>
</message>
<message>
<location filename="menu.py" line="183"/>
<location filename="menu.py" line="188"/>
<source>Leaving blank will reset current password</source>
<translation>Пустое поле сбросит текущий пароль</translation>
</message>
<message>
<location filename="menu.py" line="184"/>
<location filename="menu.py" line="189"/>
<source>There is no way to recover lost passwords</source>
<translation>Восстановление забытых паролей не поддерживается</translation>
</message>
<message>
<location filename="menu.py" line="217"/>
<location filename="menu.py" line="222"/>
<source>Password must be at least 8 symbols</source>
<translation>Пароль должен быть длиной не менее 8 символов</translation>
</message>
<message>
<location filename="menu.py" line="250"/>
<location filename="menu.py" line="255"/>
<source>Choose avatar</source>
<translation>Выбрать аватар</translation>
</message>
<message>
<location filename="menu.py" line="185"/>
<location filename="menu.py" line="190"/>
<source>Online</source>
<translation>Онлайн</translation>
</message>
<message>
<location filename="menu.py" line="186"/>
<location filename="menu.py" line="191"/>
<source>Away</source>
<translation>Нет на месте</translation>
</message>
<message>
<location filename="menu.py" line="187"/>
<location filename="menu.py" line="192"/>
<source>Busy</source>
<translation>Занят</translation>
</message>
<message>
<location filename="menu.py" line="202"/>
<location filename="menu.py" line="207"/>
<source>Mark as not default profile</source>
<translation>Отключить автозагрузку профиля</translation>
</message>
<message>
<location filename="menu.py" line="206"/>
<location filename="menu.py" line="211"/>
<source>Mark as default profile</source>
<translation>Сделать профилем по умолчанию</translation>
</message>
<message>
<location filename="menu.py" line="188"/>
<location filename="menu.py" line="193"/>
<source>Copy public key</source>
<translation>Копировать публичный ключ</translation>
</message>
@ -645,7 +684,7 @@ Version: </source>
<message>
<location filename="mainscreen_widgets.py" line="361"/>
<source>New in Toxygen v0.2.2:&lt;br&gt;Users can lock application using profile password.&lt;br&gt;Compact contact list support&lt;br&gt;Bug fixes&lt;br&gt;Tox DNS improvements</source>
<translation>С версии 0.1.3 Toxygen поддерживает плагины. &lt;a href=&quot;https://github.com/xveduk/toxygen/blob/master/docs/plugins.md&quot;&gt;Узнать больше.&lt;/a&gt;</translation>
<translation type="obsolete">С версии 0.1.3 Toxygen поддерживает плагины. &lt;a href=&quot;https://github.com/xveduk/toxygen/blob/master/docs/plugins.md&quot;&gt;Узнать больше.&lt;/a&gt;</translation>
</message>
<message>
<location filename="mainscreen_widgets.py" line="367"/>
@ -673,25 +712,40 @@ Version: </source>
<translation>Toxygen поддерживает псевдооффлайн сообщения и файл трансферы.</translation>
</message>
<message>
<location filename="mainscreen_widgets.py" line="369"/>
<location filename="mainscreen_widgets.py" line="377"/>
<source>Set new NoSpam to avoid spam friend requests: Profile -&gt; Settings -&gt; Set new NoSpam.</source>
<translation>Установите новый NoSpam, чтобы избежать спам запросов в друзья: Профиль-&gt;Настройки-&gt;Новый NoSpam.</translation>
</message>
<message>
<location filename="mainscreen_widgets.py" line="361"/>
<source>New in Toxygen v0.2.3:&lt;br&gt;TCS compliance&lt;br&gt;Plugins, smileys and stickers import&lt;br&gt;Bug fixes</source>
<translation>Новое в Toxygen 0.2.3:&lt;br&gt;Соответствие TCS&lt;br&gt;Импорт плагинов, смайлов и стикеров&lt;br&gt;Исправления ошибок</translation>
</message>
<message>
<location filename="mainscreen_widgets.py" line="369"/>
<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>
</message>
<message>
<location filename="mainscreen_widgets.py" line="373"/>
<source>Use right click on inline image to save it</source>
<translation>Правый клик на инлайн изображении позволит сохранить его</translation>
</message>
</context>
<context>
<name>audioSettingsForm</name>
<message>
<location filename="menu.py" line="718"/>
<location filename="menu.py" line="769"/>
<source>Audio settings</source>
<translation>Настройки аудио</translation>
</message>
<message>
<location filename="menu.py" line="719"/>
<location filename="menu.py" line="770"/>
<source>Input device:</source>
<translation>Устройство ввода:</translation>
</message>
<message>
<location filename="menu.py" line="720"/>
<location filename="menu.py" line="771"/>
<source>Output device:</source>
<translation>Устройство вывода:</translation>
</message>
@ -699,32 +753,32 @@ Version: </source>
<context>
<name>incoming_call</name>
<message>
<location filename="profile.py" line="1132"/>
<location filename="profile.py" line="1141"/>
<source>Incoming video call</source>
<translation>Входящий видеозвонок</translation>
</message>
<message>
<location filename="profile.py" line="1135"/>
<location filename="profile.py" line="1144"/>
<source>Incoming audio call</source>
<translation>Входящий аудиозвонок</translation>
</message>
<message>
<location filename="profile.py" line="1115"/>
<location filename="profile.py" line="1124"/>
<source>Outgoing video call</source>
<translation>Исходящий видеозвонок</translation>
</message>
<message>
<location filename="profile.py" line="1118"/>
<location filename="profile.py" line="1127"/>
<source>Outgoing audio call</source>
<translation>Исходящий аудиозвонок</translation>
</message>
<message>
<location filename="profile.py" line="1164"/>
<location filename="profile.py" line="1173"/>
<source>Call declined</source>
<translation>Звонок отменен</translation>
</message>
<message>
<location filename="profile.py" line="1166"/>
<location filename="profile.py" line="1175"/>
<source>Call finished</source>
<translation>Звонок завершен</translation>
</message>
@ -732,60 +786,75 @@ Version: </source>
<context>
<name>interfaceForm</name>
<message>
<location filename="menu.py" line="619"/>
<location filename="menu.py" line="637"/>
<source>Interface settings</source>
<translation>Настройки интерфейса</translation>
</message>
<message>
<location filename="menu.py" line="620"/>
<location filename="menu.py" line="638"/>
<source>Theme:</source>
<translation>Тема:</translation>
</message>
<message>
<location filename="menu.py" line="621"/>
<location filename="menu.py" line="639"/>
<source>Language:</source>
<translation>Язык:</translation>
</message>
<message>
<location filename="menu.py" line="622"/>
<location filename="menu.py" line="640"/>
<source>Smileys</source>
<translation>Смайлики</translation>
</message>
<message>
<location filename="menu.py" line="623"/>
<location filename="menu.py" line="641"/>
<source>Smiley pack:</source>
<translation>Набор смайликов:</translation>
</message>
<message>
<location filename="menu.py" line="624"/>
<location filename="menu.py" line="642"/>
<source>Mirror mode</source>
<translation>Зеркальный режим</translation>
</message>
<message>
<location filename="menu.py" line="625"/>
<location filename="menu.py" line="643"/>
<source>Messages font size:</source>
<translation>Размер шрифта сообщений:</translation>
</message>
<message>
<location filename="menu.py" line="667"/>
<location filename="menu.py" line="718"/>
<source>Restart app to apply settings</source>
<translation>Для применения настроек необходимо перезапустить приложение</translation>
</message>
<message>
<location filename="menu.py" line="668"/>
<location filename="menu.py" line="719"/>
<source>Restart required</source>
<translation>Требуется перезапуск</translation>
</message>
<message>
<location filename="menu.py" line="626"/>
<location filename="menu.py" line="644"/>
<source>Select unread messages notification color</source>
<translation>Цвет уведомления о сообщении</translation>
</message>
<message>
<location filename="menu.py" line="627"/>
<location filename="menu.py" line="645"/>
<source>Compact contact list</source>
<translation>Компактный список контактов</translation>
</message>
<message>
<location filename="menu.py" line="646"/>
<source>Import smiley pack</source>
<translation>Импортировать смайлы</translation>
</message>
<message>
<location filename="menu.py" line="647"/>
<source>Import sticker pack</source>
<translation>Импортировать стикеры</translation>
</message>
<message>
<location filename="menu.py" line="636"/>
<source>Show avatars in chat</source>
<translation>Показывать аватары в чате</translation>
</message>
</context>
<context>
<name>login</name>
@ -840,30 +909,35 @@ Version: </source>
<translation>Имя профиля</translation>
</message>
<message>
<location filename="main.py" line="127"/>
<location filename="main.py" line="154"/>
<source>Other instance of Toxygen uses this profile or profile was not properly closed. Continue?</source>
<translation>Этот профиль используется другим экземпляром Toxygen или не был правильно закрыт. Продолжить?</translation>
</message>
<message>
<location filename="main.py" line="120"/>
<source>Do you want to set profile password?</source>
<translation>Хотите ли вы установить пароль профиля?</translation>
</message>
</context>
<context>
<name>notificationsForm</name>
<message>
<location filename="menu.py" line="530"/>
<location filename="menu.py" line="535"/>
<source>Notification settings</source>
<translation>Настройки уведомлений</translation>
</message>
<message>
<location filename="menu.py" line="531"/>
<location filename="menu.py" line="536"/>
<source>Enable notifications</source>
<translation>Включить уведомления</translation>
</message>
<message>
<location filename="menu.py" line="532"/>
<location filename="menu.py" line="537"/>
<source>Enable call&apos;s sound</source>
<translation>Включить звук звонка</translation>
</message>
<message>
<location filename="menu.py" line="533"/>
<location filename="menu.py" line="538"/>
<source>Enable sound notifications</source>
<translation>Включить звуковые уведомления
</translation>
@ -872,72 +946,72 @@ Version: </source>
<context>
<name>privacySettings</name>
<message>
<location filename="menu.py" line="426"/>
<location filename="menu.py" line="431"/>
<source>Privacy settings</source>
<translation>Настройки приватности</translation>
</message>
<message>
<location filename="menu.py" line="427"/>
<location filename="menu.py" line="432"/>
<source>Save chat history</source>
<translation>Сохранять историю переписки</translation>
</message>
<message>
<location filename="menu.py" line="428"/>
<location filename="menu.py" line="433"/>
<source>Allow file auto accept</source>
<translation>Разрешить автополучение файлов</translation>
</message>
<message>
<location filename="menu.py" line="429"/>
<location filename="menu.py" line="434"/>
<source>Send typing notifications</source>
<translation>Посылать уведомления о наборе текста</translation>
</message>
<message>
<location filename="menu.py" line="430"/>
<location filename="menu.py" line="435"/>
<source>Auto accept default path:</source>
<translation>Путь автоприема файлов:</translation>
</message>
<message>
<location filename="menu.py" line="431"/>
<location filename="menu.py" line="436"/>
<source>Change</source>
<translation>Изменить</translation>
</message>
<message>
<location filename="menu.py" line="432"/>
<location filename="menu.py" line="437"/>
<source>Allow inlines</source>
<translation>Разрешать инлайны</translation>
</message>
<message>
<location filename="menu.py" line="477"/>
<location filename="menu.py" line="482"/>
<source>Chat history</source>
<translation>История чата</translation>
</message>
<message>
<location filename="menu.py" line="480"/>
<location filename="menu.py" line="485"/>
<source>History will be cleaned! Continue?</source>
<translation>История переписки будет очищена! Продолжить?</translation>
</message>
<message>
<location filename="menu.py" line="434"/>
<location filename="menu.py" line="439"/>
<source>Blocked users:</source>
<translation>Заблокированные пользователи:</translation>
</message>
<message>
<location filename="menu.py" line="435"/>
<location filename="menu.py" line="440"/>
<source>Unblock</source>
<translation>Разблокировать</translation>
</message>
<message>
<location filename="menu.py" line="436"/>
<location filename="menu.py" line="441"/>
<source>Block user</source>
<translation>Заблокировать пользователя</translation>
</message>
<message>
<location filename="menu.py" line="448"/>
<location filename="menu.py" line="453"/>
<source>Add to friend list</source>
<translation>Добавить в список друзей</translation>
</message>
<message>
<location filename="menu.py" line="449"/>
<location filename="menu.py" line="454"/>
<source>Do you want to add this user to friend list?</source>
<translation>Добавить этого пользователя в список друзей?</translation>
</message>
@ -947,12 +1021,12 @@ Version: </source>
<translation type="obsolete">Блокировать по TOX ID:</translation>
</message>
<message>
<location filename="menu.py" line="433"/>
<location filename="menu.py" line="438"/>
<source>Block by public key:</source>
<translation>Блокировать по публичному ключу:</translation>
</message>
<message>
<location filename="menu.py" line="437"/>
<location filename="menu.py" line="442"/>
<source>Save unsent messages only</source>
<translation>Сохранять только неотправленные сообщения</translation>
</message>
@ -960,32 +1034,32 @@ Version: </source>
<context>
<name>tray</name>
<message>
<location filename="main.py" line="176"/>
<location filename="main.py" line="203"/>
<source>Open Toxygen</source>
<translation>Открыть Toxygen</translation>
</message>
<message>
<location filename="main.py" line="185"/>
<location filename="main.py" line="212"/>
<source>Exit</source>
<translation>Выход</translation>
</message>
<message>
<location filename="main.py" line="177"/>
<location filename="main.py" line="204"/>
<source>Set status</source>
<translation>Изменить статус</translation>
</message>
<message>
<location filename="main.py" line="178"/>
<location filename="main.py" line="205"/>
<source>Online</source>
<translation>Онлайн</translation>
</message>
<message>
<location filename="main.py" line="179"/>
<location filename="main.py" line="206"/>
<source>Away</source>
<translation>Нет на месте</translation>
</message>
<message>
<location filename="main.py" line="180"/>
<location filename="main.py" line="207"/>
<source>Busy</source>
<translation>Занят</translation>
</message>

View File

@ -2,7 +2,7 @@ import os
import time
import shutil
program_version = '0.2.2'
program_version = '0.2.3'
def log(data):

View File

@ -9,7 +9,7 @@ class DataLabel(QtGui.QLabel):
Label with elided text
"""
def setText(self, text):
text = ''.join(c if c <= '\u10FFFF' else '\u25AF' for c in text)
text = ''.join(c if c <= '\U0010FFFF' else '\u25AF' for c in text)
metrics = QtGui.QFontMetrics(self.font())
text = metrics.elidedText(text, QtCore.Qt.ElideRight, self.width())
super().setText(text)