22 Commits

Author SHA1 Message Date
098f295cb0 Fix group join by chat id 2018-01-08 22:40:52 +03:00
2fc90880b8 api update 2017-01-27 21:09:15 +03:00
1ab59ee422 group leave fix 2016-07-15 13:03:44 -07:00
735c88d5bf notifications fix, reconnect added 2016-07-15 03:10:41 -07:00
2745caa531 invite fix 2016-07-15 12:12:06 +03:00
4abd72e278 more callbacks, fixes, peers 2016-07-14 22:23:56 +03:00
f775203f4c some fixes 2016-07-13 16:12:15 +03:00
8a53fc8727 menu update + group leaving 2016-07-12 23:31:32 +03:00
bb4e80ca09 fixes, gc loading on start 2016-07-12 22:53:02 +03:00
3602b3433e contact.py fixes and right click menu update 2016-07-12 17:53:38 +03:00
dc96f66d8c updates and fixes 2016-07-12 15:47:17 +03:00
bae87c8d72 some fixes and image 2016-07-12 13:48:59 +03:00
4f42098d56 incoming messages 2016-07-11 22:47:39 +03:00
f13274882a group creation, invites and message sending (untested) 2016-07-11 22:19:35 +03:00
c8bdb32e86 rebase 2016-07-11 17:52:03 +03:00
3ad7d20827 history improvements, ui for creation finished, some updates 2016-07-11 17:39:45 +03:00
3aba7dffd2 ui: chat creation window 2016-07-11 17:39:44 +03:00
9b0c6e63ce bug fixes 2016-07-11 17:31:16 +03:00
6703cbd301 basecontact created 2016-07-11 17:31:16 +03:00
40d0b03227 icon and gc.py 2016-07-11 17:28:38 +03:00
c0601444d9 generated wrapper without callbacks docs and errors check 2016-07-11 17:28:38 +03:00
c767ebe530 enums and consts 2016-07-11 17:28:38 +03:00
66 changed files with 2980 additions and 1107 deletions

View File

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

View File

@ -7,4 +7,4 @@ Install PyInstaller:
``pyinstaller --windowed --icon images/icon.ico main.py`` ``pyinstaller --windowed --icon images/icon.ico main.py``
Don't forget to copy /images/, /sounds/, /translations/, /styles/, /smileys/, /stickers/ (and /libs/libtox.dll, /libs/libsodium.a on Windows) to /dist/main/ Don't forget to copy /images/, /sounds/, /translations/, /styles/, /smileys/, /stickers/ (and /libs/libtox.dll on Windows) to /dist/main/

View File

@ -5,21 +5,19 @@
##Using pip3 ##Using pip3
### Windows ### Windows (32-bit interpreter)
``pip3.4 install toxygen`` ``pip3.4 install toxygen``
Run app using ``toxygen`` command. 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/) 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: 2. Install PortAudio:
``sudo apt-get install portaudio19-dev`` ``sudo apt-get install portaudio19-dev``
3. Install PySide: ``sudo apt-get install python3-pyside`` 3. Install toxygen:
4. Install toxygen:
``sudo pip3.4 install toxygen`` ``sudo pip3.4 install toxygen``
5. Run toxygen using ``toxygen`` command. 4 Run toxygen using ``toxygen`` command.
## From source code (recommended for developers) ## From source code (recommended for developers)
@ -33,8 +31,6 @@ Run app using ``toxygen`` command.
6. Download latest libtox.dll build, download latest libsodium.a build, put it into \src\libs\ 6. Download latest libtox.dll build, download latest libsodium.a build, put it into \src\libs\
7. Run \src\main.py. 7. Run \src\main.py.
Optional: install toxygen using setup.py: ``python3.4 setup.py install``
[libtox.dll for 32-bit Python](https://build.tox.chat/view/libtoxcore/job/libtoxcore_build_windows_x86_shared_release/lastSuccessfulBuild/artifact/libtoxcore_build_windows_x86_shared_release.zip) [libtox.dll for 32-bit Python](https://build.tox.chat/view/libtoxcore/job/libtoxcore_build_windows_x86_shared_release/lastSuccessfulBuild/artifact/libtoxcore_build_windows_x86_shared_release.zip)
[libtox.dll for 64-bit Python](https://build.tox.chat/view/libtoxcore/job/libtoxcore_build_windows_x86-64_shared_release/lastSuccessfulBuild/artifact/libtoxcore_build_windows_x86-64_shared_release.zip) [libtox.dll for 64-bit Python](https://build.tox.chat/view/libtoxcore/job/libtoxcore_build_windows_x86-64_shared_release/lastSuccessfulBuild/artifact/libtoxcore_build_windows_x86-64_shared_release.zip)
@ -43,20 +39,21 @@ Optional: install toxygen using setup.py: ``python3.4 setup.py install``
[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) [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 ### Linux
1. Install latest Python3: Dependencies:
1. Install latest Python3.4:
``sudo apt-get install python3`` ``sudo apt-get install python3``
2. Install PySide: ``sudo apt-get install python3-pyside`` or install [PyQt4](https://riverbankcomputing.com/software/pyqt/download). 2. [Install PySide](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).
3. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system (install in /usr/lib/) 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: 4. Install PyAudio:
``sudo apt-get install portaudio19-dev`` and ``sudo apt-get install python3-pyaudio`` (or ``pip3 install pyaudio``) ``sudo apt-get install portaudio19-dev`` and ``sudo apt-get install python3-pyaudio``
5. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip) 5. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
6. Unpack archive 6. Unpack archive
7. Run app: 7. Run app:
``python3.4 main.py`` ``python3.4 main.py``
Optional: install toxygen using setup.py: ``python3 setup.py install``
## Compile Toxygen ## Compile Toxygen
Check [compile.md](/docs/compile.md) for more info Check [compile.md](/docs/compile.md) for more info

114
toxygen/basecontact.py Normal file
View File

@ -0,0 +1,114 @@
import os
from settings import *
try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
class BaseContact:
"""
Class encapsulating TOX contact
Properties: name (alias of contact or name), status_message, status (connection status)
widget - widget for update
"""
def __init__(self, name, status_message, widget, tox_id):
"""
:param name: name, example: 'Toxygen user'
:param status_message: status message, example: 'Toxing on Toxygen'
:param widget: ContactItem instance
:param tox_id: tox id of contact
"""
self._name, self._status_message = name, status_message
self._status, self._widget = None, widget
self._widget.name.setText(name)
self._widget.status_message.setText(status_message)
self._tox_id = tox_id
self.load_avatar()
# -----------------------------------------------------------------------------------------------------------------
# Name - current name or alias of user
# -----------------------------------------------------------------------------------------------------------------
def get_name(self):
return self._name
def set_name(self, value):
self._name = str(value, 'utf-8')
self._widget.name.setText(self._name)
self._widget.name.repaint()
name = property(get_name, set_name)
# -----------------------------------------------------------------------------------------------------------------
# Status message or group topic
# -----------------------------------------------------------------------------------------------------------------
def get_status_message(self):
return self._status_message
def set_status_message(self, value):
self._status_message = str(value, 'utf-8')
self._widget.status_message.setText(self._status_message)
self._widget.status_message.repaint()
status_message = property(get_status_message, set_status_message)
# -----------------------------------------------------------------------------------------------------------------
# Status
# -----------------------------------------------------------------------------------------------------------------
def get_status(self):
return self._status
def set_status(self, value):
self._status = value
self._widget.connection_status.update(value)
status = property(get_status, set_status)
# -----------------------------------------------------------------------------------------------------------------
# TOX ID. WARNING: for friend it will return public key, for profile - full address
# -----------------------------------------------------------------------------------------------------------------
def get_tox_id(self):
return self._tox_id
tox_id = property(get_tox_id)
# -----------------------------------------------------------------------------------------------------------------
# Avatars
# -----------------------------------------------------------------------------------------------------------------
def load_avatar(self, default_path='avatar.png'):
"""
Tries to load avatar of contact or uses default avatar
"""
avatar_path = '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
os.chdir(ProfileHelper.get_path() + 'avatars/')
if not os.path.isfile(avatar_path): # load default image
avatar_path = default_path
os.chdir(curr_directory() + '/images/')
width = self._widget.avatar_label.width()
pixmap = QtGui.QPixmap(QtCore.QSize(width, width))
pixmap.load(avatar_path)
self._widget.avatar_label.setScaledContents(False)
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio))
self._widget.avatar_label.repaint()
def reset_avatar(self):
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
if os.path.isfile(avatar_path):
os.remove(avatar_path)
self.load_avatar()
def set_avatar(self, avatar):
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
with open(avatar_path, 'wb') as f:
f.write(avatar)
self.load_avatar()
def get_pixmap(self):
return self._widget.avatar_label.pixmap()

View File

@ -287,6 +287,49 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud
rate) rate)
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - group chats
# -----------------------------------------------------------------------------------------------------------------
def group_message(window, tray, tox):
"""
New message in group chat
"""
def wrapped(tox_link, group_number, peer_id, message_type, message, length, user_data):
profile = Profile.get_instance()
settings = Settings.get_instance()
message = str(message[:length], 'utf-8')
invoke_in_main_thread(profile.new_message, group_number, message_type, message, True, peer_id)
if not window.isActiveWindow():
bl = settings['notify_all_gc'] or profile.name in message
name = tox.group_peer_get_name(group_number, peer_id)
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and (not settings.locked) and bl:
invoke_in_main_thread(tray_notification, name, message, tray, window)
if settings['sound_notifications'] and bl and profile.status != TOX_USER_STATUS['BUSY']:
sound_notification(SOUND_NOTIFICATION['MESSAGE'])
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png'))
return wrapped
def group_invite(tox, friend_number, invite_data, length, user_data):
invoke_in_main_thread(Profile.get_instance().process_group_invite,
friend_number,
bytes(invite_data[:length]))
def group_self_join(tox, group_number, user_data):
pr = Profile.get_instance()
gc = pr.get_gc_by_number(group_number)
invoke_in_main_thread(gc.set_status, TOX_USER_STATUS['NONE'])
if not pr.is_active_a_friend() and pr.get_active_number() == group_number:
invoke_in_main_thread(pr.set_active)
def group_peer_join(tox, group_number, peer_id, user_data):
gc = Profile.get_instance().get_gc_by_number(group_number)
gc.add_peer(peer_id)
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
# Callbacks - initialization # Callbacks - initialization
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
@ -323,3 +366,8 @@ def init_callbacks(tox, window, tray):
tox.callback_friend_lossless_packet(lossless_packet, 0) tox.callback_friend_lossless_packet(lossless_packet, 0)
tox.callback_friend_lossy_packet(lossy_packet, 0) tox.callback_friend_lossy_packet(lossy_packet, 0)
tox.callback_group_message(group_message(window, tray, tox), 0)
tox.callback_group_invite(group_invite, 0)
tox.callback_group_self_join(group_self_join, 0)
tox.callback_group_peer_join(group_peer_join, 0)

View File

@ -1,114 +1,212 @@
import os
from settings import *
try: try:
from PySide import QtCore, QtGui from PySide import QtCore, QtGui
except ImportError: except ImportError:
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE import basecontact
from messages import *
from history import *
import file_transfers as ft
import util
class Contact: class Contact(basecontact.BaseContact):
""" """
Class encapsulating TOX contact Class encapsulating TOX contact
Properties: name (alias of contact or name), status_message, status (connection status) Properties: name (alias of contact or name), status_message, status (connection status)
widget - widget for update widget - widget for update
""" """
def __init__(self, name, status_message, widget, tox_id): def __init__(self, number, message_getter, name, status_message, widget, tox_id):
""" """
:param name: name, example: 'Toxygen user' :param name: name, example: 'Toxygen user'
:param status_message: status message, example: 'Toxing on Toxygen' :param status_message: status message, example: 'Toxing on Toxygen'
:param widget: ContactItem instance :param widget: ContactItem instance
:param tox_id: tox id of contact :param tox_id: tox id of contact
""" """
self._name, self._status_message = name, status_message super().__init__(name, status_message, widget, tox_id)
self._status, self._widget = None, widget self._message_getter = message_getter
self._widget.name.setText(name) self._new_messages = False
self._widget.status_message.setText(status_message) self._visible = True
self._tox_id = tox_id self._alias = False
self.load_avatar() self._number = number
self._corr = []
self._unsaved_messages = 0
self._history_loaded = self._new_actions = False
self._curr_text = ''
def __del__(self):
self.set_visibility(False)
del self._widget
if hasattr(self, '_message_getter'):
del self._message_getter
def load_corr(self, first_time=True):
"""
:param first_time: friend became active, load first part of messages
"""
if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')):
return
data = list(self._message_getter.get(PAGE_SIZE))
if data is not None and len(data):
data.reverse()
else:
return
data = list(map(lambda tupl: TextMessage(*tupl), data))
self._corr = data + self._corr
self._history_loaded = True
def get_corr_for_saving(self):
"""
Get data to save in db
:return: list of unsaved messages or []
"""
messages = list(filter(lambda x: x.get_type() <= 1, self._corr))
return list(map(lambda x: x.get_data(), messages[-self._unsaved_messages:])) if self._unsaved_messages else []
def get_corr(self):
return self._corr[:]
def append_message(self, message):
"""
:param message: text or file transfer message
"""
self._corr.append(message)
if message.get_type() <= 1:
self._unsaved_messages += 1
def get_last_message_text(self):
messages = list(filter(lambda x: x.get_type() <= 1 and x.get_owner() != MESSAGE_OWNER['FRIEND'], self._corr))
if messages:
return messages[-1].get_data()[0]
else:
return ''
def get_unsent_messages(self):
"""
:return list of unsent messages
"""
messages = filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
return list(messages)
def get_unsent_messages_for_saving(self):
"""
:return list of unsent messages for saving
"""
messages = filter(lambda x: x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
return list(map(lambda x: x.get_data(), messages))
def delete_message(self, time):
elem = list(filter(lambda x: type(x) is TextMessage and x.get_data()[2] == time, self._corr))[0]
tmp = list(filter(lambda x: x.get_type() <= 1, self._corr))
if elem in tmp[-self._unsaved_messages:]:
self._unsaved_messages -= 1
self._corr.remove(elem)
def delete_old_messages(self):
old = filter(lambda x: x.get_type() in (2, 3) and (x.get_status() >= 2 or x.get_status() is None),
self._corr[:-SAVE_MESSAGES])
old = list(old)
l = max(len(self._corr) - SAVE_MESSAGES, 0) - len(old)
self._unsaved_messages -= l
self._corr = old + self._corr[-SAVE_MESSAGES:]
def mark_as_sent(self):
try:
message = list(filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr))[0]
message.mark_as_sent()
except Exception as ex:
util.log('Mark as sent ex: ' + str(ex))
def clear_corr(self, save_unsent=False):
"""
Clear messages list
"""
if hasattr(self, '_message_getter'):
del self._message_getter
# don't delete data about active file transfer
if not save_unsent:
self._corr = list(filter(lambda x: x.get_type() in (2, 3) 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)
or (x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT']),
self._corr))
self._unsaved_messages = len(self.get_unsent_messages())
def get_curr_text(self):
return self._curr_text
def set_curr_text(self, value):
self._curr_text = value
curr_text = property(get_curr_text, set_curr_text)
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
# name - current name or alias of user # Visibility in friends' list
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
def get_name(self): def get_visibility(self):
return self._name return self._visible
def set_visibility(self, value):
self._visible = value
visibility = property(get_visibility, set_visibility)
# -----------------------------------------------------------------------------------------------------------------
# Unread messages and actions
# -----------------------------------------------------------------------------------------------------------------
def get_actions(self):
return self._new_actions
def set_actions(self, value):
self._new_actions = value
self._widget.connection_status.update(self.status, value)
actions = property(get_actions, set_actions) # unread messages, incoming files, av calls
def get_messages(self):
return self._new_messages
def inc_messages(self):
self._new_messages += 1
self._new_actions = True
self._widget.connection_status.update(self.status, True)
self._widget.messages.update(self._new_messages)
def reset_messages(self):
self._new_actions = False
self._new_messages = 0
self._widget.messages.update(self._new_messages)
self._widget.connection_status.update(self.status, False)
messages = property(get_messages)
# -----------------------------------------------------------------------------------------------------------------
# Number (can be used in toxcore)
# -----------------------------------------------------------------------------------------------------------------
def get_number(self):
return self._number
def set_number(self, value):
self._number = value
number = property(get_number, set_number)
# -----------------------------------------------------------------------------------------------------------------
# Alias support
# -----------------------------------------------------------------------------------------------------------------
def set_name(self, value): def set_name(self, value):
self._name = str(value, 'utf-8')
self._widget.name.setText(self._name)
self._widget.name.repaint()
name = property(get_name, set_name)
# -----------------------------------------------------------------------------------------------------------------
# Status message
# -----------------------------------------------------------------------------------------------------------------
def get_status_message(self):
return self._status_message
def set_status_message(self, value):
self._status_message = str(value, 'utf-8')
self._widget.status_message.setText(self._status_message)
self._widget.status_message.repaint()
status_message = property(get_status_message, set_status_message)
# -----------------------------------------------------------------------------------------------------------------
# Status
# -----------------------------------------------------------------------------------------------------------------
def get_status(self):
return self._status
def set_status(self, value):
self._status = value
self._widget.connection_status.update(value)
status = property(get_status, set_status)
# -----------------------------------------------------------------------------------------------------------------
# TOX ID. WARNING: for friend it will return public key, for profile - full address
# -----------------------------------------------------------------------------------------------------------------
def get_tox_id(self):
return self._tox_id
tox_id = property(get_tox_id)
# -----------------------------------------------------------------------------------------------------------------
# Avatars
# -----------------------------------------------------------------------------------------------------------------
def load_avatar(self):
""" """
Tries to load avatar of contact or uses default avatar Set new name or ignore if alias exists
:param value: new name
""" """
avatar_path = '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) if not self._alias:
os.chdir(ProfileHelper.get_path() + 'avatars/') super(Contact, self).set_name(value)
if not os.path.isfile(avatar_path): # load default image
avatar_path = 'avatar.png'
os.chdir(curr_directory() + '/images/')
width = self._widget.avatar_label.width()
pixmap = QtGui.QPixmap(QtCore.QSize(width, width))
pixmap.load(avatar_path)
self._widget.avatar_label.setScaledContents(False)
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio))
self._widget.avatar_label.repaint()
def reset_avatar(self): def set_alias(self, alias):
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) self._alias = bool(alias)
if os.path.isfile(avatar_path):
os.remove(avatar_path)
self.load_avatar()
def set_avatar(self, avatar):
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
with open(avatar_path, 'wb') as f:
f.write(avatar)
self.load_avatar()
def get_pixmap(self):
return self._widget.avatar_label.pixmap()

View File

@ -1,8 +1,5 @@
import contact import contact
from messages import * from messages import *
from history import *
import util
import file_transfers as ft
class Friend(contact.Contact): class Friend(contact.Contact):
@ -10,28 +7,15 @@ class Friend(contact.Contact):
Friend in list of friends. Can be hidden, properties 'has unread messages' and 'has alias' added Friend in list of friends. Can be hidden, properties 'has unread messages' and 'has alias' added
""" """
def __init__(self, message_getter, number, *args): def __init__(self, *args):
""" """
:param message_getter: gets messages from db
:param number: number of friend. :param number: number of friend.
""" """
super(Friend, self).__init__(*args) super(Friend, self).__init__(*args)
self._number = number
self._new_messages = False
self._visible = True
self._alias = False
self._message_getter = message_getter
self._corr = []
self._unsaved_messages = 0
self._history_loaded = self._new_actions = False
self._receipts = 0 self._receipts = 0
self._curr_text = ''
def __del__(self): def __del__(self):
self.set_visibility(False) super().__del__()
del self._widget
if hasattr(self, '_message_getter'):
del self._message_getter
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
# History support # History support
@ -50,100 +34,6 @@ class Friend(contact.Contact):
self._receipts -= 1 self._receipts -= 1
self.mark_as_sent() self.mark_as_sent()
def load_corr(self, first_time=True):
"""
:param first_time: friend became active, load first part of messages
"""
if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')):
return
data = list(self._message_getter.get(PAGE_SIZE))
if data is not None and len(data):
data.reverse()
else:
return
data = list(map(lambda tupl: TextMessage(*tupl), data))
self._corr = data + self._corr
self._history_loaded = True
def get_corr_for_saving(self):
"""
Get data to save in db
:return: list of unsaved messages or []
"""
messages = list(filter(lambda x: x.get_type() <= 1, self._corr))
return list(map(lambda x: x.get_data(), messages[-self._unsaved_messages:])) if self._unsaved_messages else []
def get_corr(self):
return self._corr[:]
def append_message(self, message):
"""
:param message: text or file transfer message
"""
self._corr.append(message)
if message.get_type() <= 1:
self._unsaved_messages += 1
def get_last_message_text(self):
messages = list(filter(lambda x: x.get_type() <= 1 and x.get_owner() != MESSAGE_OWNER['FRIEND'], self._corr))
if messages:
return messages[-1].get_data()[0]
else:
return ''
def get_unsent_messages(self):
"""
:return list of unsent messages
"""
messages = filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
return list(messages)
def get_unsent_messages_for_saving(self):
"""
:return list of unsent messages for saving
"""
messages = filter(lambda x: x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
return list(map(lambda x: x.get_data(), messages))
def delete_message(self, time):
elem = list(filter(lambda x: type(x) is TextMessage and x.get_data()[2] == time, self._corr))[0]
tmp = list(filter(lambda x: x.get_type() <= 1, self._corr))
if elem in tmp[-self._unsaved_messages:]:
self._unsaved_messages -= 1
self._corr.remove(elem)
def mark_as_sent(self):
try:
message = list(filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr))[0]
message.mark_as_sent()
except Exception as ex:
util.log('Mark as sent ex: ' + str(ex))
def clear_corr(self, save_unsent=False):
"""
Clear messages list
"""
if hasattr(self, '_message_getter'):
del self._message_getter
# don't delete data about active file transfer
if not save_unsent:
self._corr = list(filter(lambda x: x.get_type() == 2 and
x.get_status() in ft.ACTIVE_FILE_TRANSFERS, self._corr))
self._unsaved_messages = 0
else:
self._corr = list(filter(lambda x: (x.get_type() == 2 and x.get_status() in ft.ACTIVE_FILE_TRANSFERS)
or (x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT']),
self._corr))
self._unsaved_messages = len(self.get_unsent_messages())
def get_curr_text(self):
return self._curr_text
def set_curr_text(self, value):
self._curr_text = value
curr_text = property(get_curr_text, set_curr_text)
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
# File transfers support # File transfers support
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
@ -172,72 +62,3 @@ class Friend(contact.Contact):
def delete_one_unsent_file(self, time): def delete_one_unsent_file(self, time):
self._corr = list(filter(lambda x: not (type(x) is UnsentFile and x.get_data()[2] == time), self._corr)) self._corr = list(filter(lambda x: not (type(x) is UnsentFile and x.get_data()[2] == time), self._corr))
# -----------------------------------------------------------------------------------------------------------------
# Alias support
# -----------------------------------------------------------------------------------------------------------------
def set_name(self, value):
"""
Set new name or ignore if alias exists
:param value: new name
"""
if not self._alias:
super(Friend, self).set_name(value)
def set_alias(self, alias):
self._alias = bool(alias)
# -----------------------------------------------------------------------------------------------------------------
# Visibility in friends' list
# -----------------------------------------------------------------------------------------------------------------
def get_visibility(self):
return self._visible
def set_visibility(self, value):
self._visible = value
visibility = property(get_visibility, set_visibility)
# -----------------------------------------------------------------------------------------------------------------
# Unread messages from friend
# -----------------------------------------------------------------------------------------------------------------
def get_actions(self):
return self._new_actions
def set_actions(self, value):
self._new_actions = value
self._widget.connection_status.update(self.status, value)
actions = property(get_actions, set_actions) # unread messages, incoming files, av calls
def get_messages(self):
return self._new_messages
def inc_messages(self):
self._new_messages += 1
self._new_actions = True
self._widget.connection_status.update(self.status, True)
self._widget.messages.update(self._new_messages)
def reset_messages(self):
self._new_actions = False
self._new_messages = 0
self._widget.messages.update(self._new_messages)
self._widget.connection_status.update(self.status, False)
messages = property(get_messages)
# -----------------------------------------------------------------------------------------------------------------
# Friend's number (can be used in toxcore)
# -----------------------------------------------------------------------------------------------------------------
def get_number(self):
return self._number
def set_number(self, value):
self._number = value
number = property(get_number, set_number)

36
toxygen/groupchat.py Normal file
View File

@ -0,0 +1,36 @@
import contact
class GroupChat(contact.Contact):
def __init__(self, tox, *args):
super().__init__(*args)
self._tox = tox
def load_avatar(self, default_path='group.png'):
super().load_avatar(default_path)
def set_status(self, value):
print('In gc set_status')
super().set_status(value)
self.name = bytes(self._tox.group_get_name(self._number), 'utf-8')
self._tox_id = self._tox.group_get_chat_id(self._number)
self.status_message = bytes(self._tox.group_get_topic(self._number), 'utf-8')
def add_peer(self, peer_id):
print(peer_id)
print(self._tox.group_peer_get_name(self._number, peer_id))
# TODO: get peers list and add other methods
def get_peers_list(self):
return []
class Peer:
def __init__(self, peer_id, name, status, role):
self._data = (peer_id, name, status, role)
def get_data(self):
return self._data

View File

@ -8,6 +8,8 @@ from toxencryptsave import ToxEncryptSave
PAGE_SIZE = 42 PAGE_SIZE = 42
SAVE_MESSAGES = 150
MESSAGE_OWNER = { MESSAGE_OWNER = {
'ME': 0, 'ME': 0,
'FRIEND': 1, 'FRIEND': 1,

BIN
toxygen/images/group.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -159,14 +159,6 @@ class MessageItem(QtGui.QWidget):
return True return True
return False 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))
class ContactItem(QtGui.QWidget): class ContactItem(QtGui.QWidget):
""" """
@ -181,7 +173,6 @@ class ContactItem(QtGui.QWidget):
size = 32 if mode else 64 size = 32 if mode else 64
self.avatar_label.setGeometry(QtCore.QRect(3, 4, size, size)) self.avatar_label.setGeometry(QtCore.QRect(3, 4, size, size))
self.avatar_label.setScaledContents(True) self.avatar_label.setScaledContents(True)
self.avatar_label.setAlignment(QtCore.Qt.AlignCenter)
self.name = DataLabel(self) 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)) 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 = QtGui.QFont()

View File

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

View File

@ -11,7 +11,6 @@ from mainscreen import MainWindow
from callbacks import init_callbacks from callbacks import init_callbacks
from util import curr_directory, program_version from util import curr_directory, program_version
import styles.style import styles.style
import platform
import toxencryptsave import toxencryptsave
from passwordscreen import PasswordScreen, UnlockAppScreen, SetProfilePasswordScreen from passwordscreen import PasswordScreen, UnlockAppScreen, SetProfilePasswordScreen
from plugin_support import PluginLoader from plugin_support import PluginLoader
@ -53,9 +52,6 @@ class Toxygen:
app.setWindowIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) app.setWindowIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
self.app = app self.app = app
if platform.system() == 'Linux':
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
# application color scheme # application color scheme
with open(curr_directory() + '/styles/style.qss') as fl: with open(curr_directory() + '/styles/style.qss') as fl:
dark_style = fl.read() dark_style = fl.read()

View File

@ -29,6 +29,8 @@ class MainWindow(QtGui.QMainWindow):
self.menuProfile = QtGui.QMenu(self.menubar) self.menuProfile = QtGui.QMenu(self.menubar)
self.menuProfile.setObjectName("menuProfile") self.menuProfile.setObjectName("menuProfile")
self.menuGroupChats = QtGui.QMenu(self.menubar)
self.menuGroupChats.setObjectName("menuGroupChats")
self.menuSettings = QtGui.QMenu(self.menubar) self.menuSettings = QtGui.QMenu(self.menubar)
self.menuSettings.setObjectName("menuSettings") self.menuSettings.setObjectName("menuSettings")
self.menuPlugins = QtGui.QMenu(self.menubar) self.menuPlugins = QtGui.QMenu(self.menubar)
@ -56,6 +58,12 @@ class MainWindow(QtGui.QMainWindow):
self.pluginData = QtGui.QAction(MainWindow) self.pluginData = QtGui.QAction(MainWindow)
self.importPlugin = QtGui.QAction(MainWindow) self.importPlugin = QtGui.QAction(MainWindow)
self.lockApp = QtGui.QAction(MainWindow) self.lockApp = QtGui.QAction(MainWindow)
self.createGC = QtGui.QAction(MainWindow)
self.joinGC = QtGui.QAction(MainWindow)
self.gcRequests = QtGui.QAction(MainWindow)
self.menuGroupChats.addAction(self.createGC)
self.menuGroupChats.addAction(self.joinGC)
self.menuGroupChats.addAction(self.gcRequests)
self.menuProfile.addAction(self.actionAdd_friend) self.menuProfile.addAction(self.actionAdd_friend)
self.menuProfile.addAction(self.actionSettings) self.menuProfile.addAction(self.actionSettings)
self.menuProfile.addAction(self.lockApp) self.menuProfile.addAction(self.lockApp)
@ -68,6 +76,7 @@ class MainWindow(QtGui.QMainWindow):
self.menuPlugins.addAction(self.importPlugin) self.menuPlugins.addAction(self.importPlugin)
self.menuAbout.addAction(self.actionAbout_program) self.menuAbout.addAction(self.actionAbout_program)
self.menubar.addAction(self.menuProfile.menuAction()) self.menubar.addAction(self.menuProfile.menuAction())
self.menubar.addAction(self.menuGroupChats.menuAction())
self.menubar.addAction(self.menuSettings.menuAction()) self.menubar.addAction(self.menuSettings.menuAction())
self.menubar.addAction(self.menuPlugins.menuAction()) self.menubar.addAction(self.menuPlugins.menuAction())
self.menubar.addAction(self.menuAbout.menuAction()) self.menubar.addAction(self.menuAbout.menuAction())
@ -75,14 +84,18 @@ class MainWindow(QtGui.QMainWindow):
self.actionAbout_program.triggered.connect(self.about_program) self.actionAbout_program.triggered.connect(self.about_program)
self.actionNetwork.triggered.connect(self.network_settings) self.actionNetwork.triggered.connect(self.network_settings)
self.actionAdd_friend.triggered.connect(self.add_contact) 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.actionPrivacy_settings.triggered.connect(self.privacy_settings)
self.actionInterface_settings.triggered.connect(self.interface_settings) self.actionInterface_settings.triggered.connect(self.interface_settings)
self.actionNotifications.triggered.connect(self.notification_settings) self.actionNotifications.triggered.connect(self.notification_settings)
self.audioSettings.triggered.connect(self.audio_settings) self.audioSettings.triggered.connect(self.audio_settings)
self.pluginData.triggered.connect(self.plugins_menu) self.pluginData.triggered.connect(self.plugins_menu)
self.lockApp.triggered.connect(self.lock_app) self.lockApp.triggered.connect(self.lock_app)
self.importPlugin.triggered.connect(self.import_plugin) self.importPlugin.triggered.connect(self.import_plugin)
self.createGC.triggered.connect(self.create_groupchat)
self.joinGC.triggered.connect(self.join_groupchat)
QtCore.QMetaObject.connectSlotsByName(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow)
def languageChange(self, *args, **kwargs): def languageChange(self, *args, **kwargs):
@ -94,7 +107,11 @@ class MainWindow(QtGui.QMainWindow):
return super(MainWindow, self).event(event) return super(MainWindow, self).event(event)
def retranslateUi(self): def retranslateUi(self):
self.joinGC.setText(QtGui.QApplication.translate("MainWindow", "Join group chat", None, QtGui.QApplication.UnicodeUTF8))
self.lockApp.setText(QtGui.QApplication.translate("MainWindow", "Lock", None, QtGui.QApplication.UnicodeUTF8)) self.lockApp.setText(QtGui.QApplication.translate("MainWindow", "Lock", None, QtGui.QApplication.UnicodeUTF8))
self.menuGroupChats.setTitle(QtGui.QApplication.translate("MainWindow", "Group chats", None, QtGui.QApplication.UnicodeUTF8))
self.createGC.setText(QtGui.QApplication.translate("MainWindow", "Create group chat", None, QtGui.QApplication.UnicodeUTF8))
self.gcRequests.setText(QtGui.QApplication.translate("MainWindow", "Groupchat requests", None, QtGui.QApplication.UnicodeUTF8))
self.menuPlugins.setTitle(QtGui.QApplication.translate("MainWindow", "Plugins", None, QtGui.QApplication.UnicodeUTF8)) self.menuPlugins.setTitle(QtGui.QApplication.translate("MainWindow", "Plugins", None, QtGui.QApplication.UnicodeUTF8))
self.pluginData.setText(QtGui.QApplication.translate("MainWindow", "List of plugins", None, QtGui.QApplication.UnicodeUTF8)) self.pluginData.setText(QtGui.QApplication.translate("MainWindow", "List of plugins", None, QtGui.QApplication.UnicodeUTF8))
self.menuProfile.setTitle(QtGui.QApplication.translate("MainWindow", "Profile", None, QtGui.QApplication.UnicodeUTF8)) self.menuProfile.setTitle(QtGui.QApplication.translate("MainWindow", "Profile", None, QtGui.QApplication.UnicodeUTF8))
@ -177,7 +194,6 @@ class MainWindow(QtGui.QMainWindow):
self.avatar_label = Form.avatar_label = QtGui.QLabel(Form) self.avatar_label = Form.avatar_label = QtGui.QLabel(Form)
self.avatar_label.setGeometry(QtCore.QRect(5, 30, 64, 64)) self.avatar_label.setGeometry(QtCore.QRect(5, 30, 64, 64))
self.avatar_label.setScaledContents(True) self.avatar_label.setScaledContents(True)
self.avatar_label.setAlignment(QtCore.Qt.AlignCenter)
self.name = Form.name = DataLabel(Form) self.name = Form.name = DataLabel(Form)
Form.name.setGeometry(QtCore.QRect(75, 40, 150, 25)) Form.name.setGeometry(QtCore.QRect(75, 40, 150, 25))
font = QtGui.QFont() font = QtGui.QFont()
@ -194,9 +210,9 @@ class MainWindow(QtGui.QMainWindow):
Form.status_message.setObjectName("status_message") Form.status_message.setObjectName("status_message")
self.connection_status = Form.connection_status = StatusCircle(Form) self.connection_status = Form.connection_status = StatusCircle(Form)
Form.connection_status.setGeometry(QtCore.QRect(230, 35, 32, 32)) Form.connection_status.setGeometry(QtCore.QRect(230, 35, 32, 32))
self.avatar_label.mouseReleaseEvent = self.profilesettings self.avatar_label.mouseReleaseEvent = self.profile_settings
self.status_message.mouseReleaseEvent = self.profilesettings self.status_message.mouseReleaseEvent = self.profile_settings
self.name.mouseReleaseEvent = self.profilesettings self.name.mouseReleaseEvent = self.profile_settings
self.connection_status.raise_() self.connection_status.raise_()
Form.connection_status.setObjectName("connection_status") Form.connection_status.setObjectName("connection_status")
@ -221,6 +237,11 @@ class MainWindow(QtGui.QMainWindow):
font.setBold(False) font.setBold(False)
self.account_status.setFont(font) self.account_status.setFont(font)
self.account_status.setObjectName("account_status") self.account_status.setObjectName("account_status")
self.account_status.mouseReleaseEvent = self.show_chat_menu
self.account_name.mouseReleaseEvent = self.show_chat_menu
self.account_avatar.mouseReleaseEvent = self.show_chat_menu
self.callButton = QtGui.QPushButton(Form) self.callButton = QtGui.QPushButton(Form)
self.callButton.setGeometry(QtCore.QRect(550, 30, 50, 50)) self.callButton.setGeometry(QtCore.QRect(550, 30, 50, 50))
self.callButton.setObjectName("callButton") self.callButton.setObjectName("callButton")
@ -378,7 +399,7 @@ class MainWindow(QtGui.QMainWindow):
self.a_c = AddContact(link) self.a_c = AddContact(link)
self.a_c.show() self.a_c.show()
def profilesettings(self, *args): def profile_settings(self, *args):
self.p_s = ProfileSettings() self.p_s = ProfileSettings()
self.p_s.show() self.p_s.show()
@ -440,6 +461,19 @@ class MainWindow(QtGui.QMainWindow):
120)) 120))
self.menu.show() self.menu.show()
def create_groupchat(self):
self.gc = AddGroupchat()
self.gc.show()
def join_groupchat(self):
self.gc = JoinGroupchat()
self.gc.show()
def show_chat_menu(self):
pr = Profile.get_instance()
if not pr.is_active_a_friend():
pass # TODO: show list of users in chat
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
# Messages, calls and file transfers # Messages, calls and file transfers
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
@ -450,6 +484,8 @@ class MainWindow(QtGui.QMainWindow):
def send_file(self): def send_file(self):
self.menu.hide() self.menu.hide()
if not self.profile.is_active_a_friend():
return
if self.profile.active_friend + 1: if self.profile.active_friend + 1:
choose = QtGui.QApplication.translate("MainWindow", 'Choose file', None, QtGui.QApplication.UnicodeUTF8) choose = QtGui.QApplication.translate("MainWindow", 'Choose file', None, QtGui.QApplication.UnicodeUTF8)
name = QtGui.QFileDialog.getOpenFileName(self, choose, options=QtGui.QFileDialog.DontUseNativeDialog) name = QtGui.QFileDialog.getOpenFileName(self, choose, options=QtGui.QFileDialog.DontUseNativeDialog)
@ -458,6 +494,8 @@ class MainWindow(QtGui.QMainWindow):
def send_screenshot(self, hide=False): def send_screenshot(self, hide=False):
self.menu.hide() self.menu.hide()
if not self.profile.is_active_a_friend():
return
if self.profile.active_friend + 1: if self.profile.active_friend + 1:
self.sw = ScreenShotWindow(self) self.sw = ScreenShotWindow(self)
self.sw.show() self.sw.show()
@ -476,6 +514,8 @@ class MainWindow(QtGui.QMainWindow):
def send_sticker(self): def send_sticker(self):
self.menu.hide() self.menu.hide()
if not self.profile.is_active_a_friend():
return
if self.profile.active_friend + 1: if self.profile.active_friend + 1:
self.sticker = StickerWindow(self) self.sticker = StickerWindow(self)
self.sticker.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(), self.sticker.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(),
@ -512,35 +552,60 @@ class MainWindow(QtGui.QMainWindow):
def friend_right_click(self, pos): def friend_right_click(self, pos):
item = self.friends_list.itemAt(pos) item = self.friends_list.itemAt(pos)
num = self.friends_list.indexFromItem(item).row() num = self.friends_list.indexFromItem(item).row()
friend = Profile.get_instance().get_friend(num) friend = Profile.get_instance().get_friend_or_gc(num)
settings = Settings.get_instance() settings = Settings.get_instance()
allowed = friend.tox_id in settings['auto_accept_from_friends'] allowed = friend.tox_id in settings['auto_accept_from_friends']
auto = QtGui.QApplication.translate("MainWindow", 'Disallow auto accept', None, QtGui.QApplication.UnicodeUTF8) if allowed else QtGui.QApplication.translate("MainWindow", 'Allow auto accept', None, QtGui.QApplication.UnicodeUTF8) auto = QtGui.QApplication.translate("MainWindow", 'Disallow auto accept', None, QtGui.QApplication.UnicodeUTF8) if allowed else QtGui.QApplication.translate("MainWindow", 'Allow auto accept', None, QtGui.QApplication.UnicodeUTF8)
if item is not None: if item is not None:
self.listMenu = QtGui.QMenu() self.listMenu = QtGui.QMenu()
set_alias_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Set alias', None, QtGui.QApplication.UnicodeUTF8)) if type(friend) is Friend:
clear_history_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8)) arr = Profile.get_instance().get_all_gc()
copy_menu = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Copy', None, QtGui.QApplication.UnicodeUTF8)) if arr:
copy_name_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Name', None, QtGui.QApplication.UnicodeUTF8)) gc_menu = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Invite to group chat', None, QtGui.QApplication.UnicodeUTF8))
copy_status_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Status message', None, QtGui.QApplication.UnicodeUTF8)) for gc in arr:
copy_key_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Public key', None, QtGui.QApplication.UnicodeUTF8)) item = gc_menu.addAction(gc.name)
self.connect(item, QtCore.SIGNAL("triggered()"),
lambda: Profile.get_instance().invite_friend(gc.number, friend.number))
auto_accept_item = self.listMenu.addAction(auto) set_alias_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Set alias', None, QtGui.QApplication.UnicodeUTF8))
remove_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Remove friend', None, QtGui.QApplication.UnicodeUTF8)) clear_history_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8))
notes_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Notes', None, QtGui.QApplication.UnicodeUTF8)) copy_menu = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Copy', None, QtGui.QApplication.UnicodeUTF8))
copy_name_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Name', None, QtGui.QApplication.UnicodeUTF8))
copy_status_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Status message', None, QtGui.QApplication.UnicodeUTF8))
copy_key_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Public key', None, QtGui.QApplication.UnicodeUTF8))
auto_accept_item = self.listMenu.addAction(auto)
remove_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Remove friend', None, QtGui.QApplication.UnicodeUTF8))
notes_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Notes', None, QtGui.QApplication.UnicodeUTF8))
submenu = plugin_support.PluginLoader.get_instance().get_menu(self.listMenu, num)
if len(submenu):
plug = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8))
plug.addActions(submenu)
self.connect(remove_item, QtCore.SIGNAL("triggered()"), lambda: self.remove_friend(num))
self.connect(auto_accept_item, QtCore.SIGNAL("triggered()"), lambda: self.auto_accept(num, not allowed))
else:
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", 'Topic', None, QtGui.QApplication.UnicodeUTF8))
copy_key_item = copy_menu.addAction(
QtGui.QApplication.translate("MainWindow", 'Public key', None, QtGui.QApplication.UnicodeUTF8))
leave_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Leave group', None, QtGui.QApplication.UnicodeUTF8))
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))
notes_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Notes', None, QtGui.QApplication.UnicodeUTF8))
self.connect(leave_item, QtCore.SIGNAL("triggered()"), lambda: Profile.get_instance().leave_group(num))
submenu = plugin_support.PluginLoader.get_instance().get_menu(self.listMenu, num)
if len(submenu):
plug = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8))
plug.addActions(submenu)
self.connect(set_alias_item, QtCore.SIGNAL("triggered()"), lambda: self.set_alias(num))
self.connect(remove_item, QtCore.SIGNAL("triggered()"), lambda: self.remove_friend(num))
self.connect(copy_key_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_friend_key(num))
self.connect(clear_history_item, QtCore.SIGNAL("triggered()"), lambda: self.clear_history(num))
self.connect(auto_accept_item, QtCore.SIGNAL("triggered()"), lambda: self.auto_accept(num, not allowed))
self.connect(notes_item, QtCore.SIGNAL("triggered()"), lambda: self.show_note(friend)) self.connect(notes_item, QtCore.SIGNAL("triggered()"), lambda: self.show_note(friend))
self.connect(set_alias_item, QtCore.SIGNAL("triggered()"), lambda: self.set_alias(num))
self.connect(clear_history_item, QtCore.SIGNAL("triggered()"), lambda: self.clear_history(num))
self.connect(copy_name_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_name(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(copy_status_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_status(friend))
self.connect(copy_key_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_friend_key(num))
parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0)) parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0))
self.listMenu.move(parent_position + pos) self.listMenu.move(parent_position + pos)
self.listMenu.show() self.listMenu.show()
@ -564,7 +629,7 @@ class MainWindow(QtGui.QMainWindow):
self.profile.set_alias(num) self.profile.set_alias(num)
def remove_friend(self, num): def remove_friend(self, num):
self.profile.delete_friend(num) self.profile.delete_friend_or_gc(num)
def copy_friend_key(self, num): def copy_friend_key(self, num):
tox_id = self.profile.friend_public_key(num) tox_id = self.profile.friend_public_key(num)

View File

@ -330,7 +330,7 @@ class WelcomeScreen(CenteredWidget):
self.setWindowTitle(QtGui.QApplication.translate('WelcomeScreen', 'Tip of the day', self.setWindowTitle(QtGui.QApplication.translate('WelcomeScreen', 'Tip of the day',
None, QtGui.QApplication.UnicodeUTF8)) None, QtGui.QApplication.UnicodeUTF8))
import random import random
num = random.randint(0, 10) num = random.randint(0, 8)
if num == 0: if num == 0:
text = QtGui.QApplication.translate('WelcomeScreen', 'Press Esc if you want hide app to tray.', text = QtGui.QApplication.translate('WelcomeScreen', 'Press Esc if you want hide app to tray.',
None, QtGui.QApplication.UnicodeUTF8) None, QtGui.QApplication.UnicodeUTF8)
@ -356,20 +356,12 @@ class WelcomeScreen(CenteredWidget):
None, QtGui.QApplication.UnicodeUTF8) None, QtGui.QApplication.UnicodeUTF8)
elif num == 6: elif num == 6:
text = QtGui.QApplication.translate('WelcomeScreen', text = QtGui.QApplication.translate('WelcomeScreen',
'New in Toxygen v0.2.3:<br>TCS compliance<br>Plugins, smileys and stickers import<br>Bug fixes', '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',
None, QtGui.QApplication.UnicodeUTF8) None, QtGui.QApplication.UnicodeUTF8)
elif num == 7: elif num == 7:
text = QtGui.QApplication.translate('WelcomeScreen', text = QtGui.QApplication.translate('WelcomeScreen',
'Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.', '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) 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: else:
text = QtGui.QApplication.translate('WelcomeScreen', text = QtGui.QApplication.translate('WelcomeScreen',
'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.', 'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.',

View File

@ -11,6 +11,97 @@ import toxencryptsave
import plugin_support import plugin_support
class AddGroupchat(CenteredWidget):
def __init__(self):
super().__init__()
self.initUI()
self.retranslateUi()
self.center()
def initUI(self):
self.setObjectName('AddGC')
self.resize(570, 240)
self.setMaximumSize(QtCore.QSize(570, 240))
self.setMinimumSize(QtCore.QSize(570, 240))
self.label = QtGui.QLabel(self)
self.label.setGeometry(QtCore.QRect(50, 20, 470, 20))
self.createGCButton = QtGui.QPushButton(self)
self.createGCButton.setGeometry(QtCore.QRect(50, 190, 470, 30))
self.name = LineEdit(self)
self.name.setGeometry(QtCore.QRect(50, 40, 470, 27))
self.privacy_type = QtGui.QLabel(self)
self.privacy_type.setGeometry(QtCore.QRect(50, 70, 470, 20))
self.privacy_combobox = QtGui.QComboBox(self)
self.privacy_combobox.setGeometry(QtCore.QRect(50, 90, 470, 30))
self.pass_label = QtGui.QLabel(self)
self.pass_label.setGeometry(QtCore.QRect(50, 130, 470, 20))
self.password = LineEdit(self)
self.password.setGeometry(QtCore.QRect(50, 150, 470, 27))
self.password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
self.createGCButton.clicked.connect(self.button_click)
QtCore.QMetaObject.connectSlotsByName(self)
def retranslateUi(self):
self.setWindowTitle(QtGui.QApplication.translate('AddGC', "Create group chat", None, QtGui.QApplication.UnicodeUTF8))
self.createGCButton.setText(QtGui.QApplication.translate("AddGC", "Create", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate('AddGC', "Name:", None, QtGui.QApplication.UnicodeUTF8))
self.privacy_type.setText(QtGui.QApplication.translate('AddGC', "Privacy type:", None, QtGui.QApplication.UnicodeUTF8))
self.privacy_combobox.addItem(QtGui.QApplication.translate('AddGC', "Public", None, QtGui.QApplication.UnicodeUTF8))
self.privacy_combobox.addItem(QtGui.QApplication.translate('AddGC', "Private", None, QtGui.QApplication.UnicodeUTF8))
self.name.setPlaceholderText(QtGui.QApplication.translate('AddGC', "Not empty group name", None, QtGui.QApplication.UnicodeUTF8))
self.password.setPlaceholderText(QtGui.QApplication.translate('AddGC', "Optional password", None, QtGui.QApplication.UnicodeUTF8))
self.pass_label.setText(QtGui.QApplication.translate('AddGC', "Password:", None, QtGui.QApplication.UnicodeUTF8))
def button_click(self):
if self.name.text():
Profile.get_instance().create_gc(self.name.text(),
self.privacy_combobox.currentIndex() == 0,
self.password.text())
self.close()
class JoinGroupchat(CenteredWidget):
def __init__(self):
super().__init__()
self.initUI()
self.retranslateUi()
self.center()
def initUI(self):
self.setObjectName('AddGC')
self.resize(570, 150)
self.setMaximumSize(QtCore.QSize(570, 150))
self.setMinimumSize(QtCore.QSize(570, 150))
self.joinGCButton = QtGui.QPushButton(self)
self.joinGCButton.setGeometry(QtCore.QRect(50, 110, 470, 30))
self.id = LineEdit(self)
self.id.setGeometry(QtCore.QRect(50, 10, 470, 30))
self.password = LineEdit(self)
self.password.setGeometry(QtCore.QRect(50, 50, 470, 30))
self.password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
self.joinGCButton.clicked.connect(self.button_click)
QtCore.QMetaObject.connectSlotsByName(self)
def retranslateUi(self):
self.setWindowTitle(
QtGui.QApplication.translate('JoinGC', "Join group chat", None, QtGui.QApplication.UnicodeUTF8))
self.joinGCButton.setText(
QtGui.QApplication.translate("JoinGC", "Join", None, QtGui.QApplication.UnicodeUTF8))
self.id.setPlaceholderText(
QtGui.QApplication.translate('JoinGC', "Group ID", None, QtGui.QApplication.UnicodeUTF8))
self.password.setPlaceholderText(
QtGui.QApplication.translate('JoinGC', "Optional password", None, QtGui.QApplication.UnicodeUTF8))
def button_click(self):
if self.id.text():
Profile.get_instance().join_gc(self.id.text().strip(), self.password.text())
self.close()
class AddContact(CenteredWidget): class AddContact(CenteredWidget):
"""Add contact form""" """Add contact form"""
@ -263,7 +354,7 @@ class ProfileSettings(CenteredWidget):
buffer = QtCore.QBuffer(byte_array) buffer = QtCore.QBuffer(byte_array)
buffer.open(QtCore.QIODevice.WriteOnly) buffer.open(QtCore.QIODevice.WriteOnly)
bitmap.save(buffer, 'PNG') bitmap.save(buffer, 'PNG')
Profile.get_instance().set_avatar(bytes(byte_array.data())) Profile.get_instance().set_avatar(str(byte_array.data()))
def export_profile(self): def export_profile(self):
directory = QtGui.QFileDialog.getExistingDirectory(options=QtGui.QFileDialog.DontUseNativeDialog) + '/' directory = QtGui.QFileDialog.getExistingDirectory(options=QtGui.QFileDialog.DontUseNativeDialog) + '/'
@ -510,28 +601,33 @@ class NotificationsSettings(CenteredWidget):
def initUI(self): def initUI(self):
self.setObjectName("notificationsForm") self.setObjectName("notificationsForm")
self.resize(350, 180) self.resize(350, 200)
self.setMinimumSize(QtCore.QSize(350, 180)) self.setMinimumSize(QtCore.QSize(350, 200))
self.setMaximumSize(QtCore.QSize(350, 180)) self.setMaximumSize(QtCore.QSize(350, 200))
self.enableNotifications = QtGui.QCheckBox(self) self.enableNotifications = QtGui.QCheckBox(self)
self.enableNotifications.setGeometry(QtCore.QRect(10, 20, 340, 18)) self.enableNotifications.setGeometry(QtCore.QRect(10, 20, 340, 18))
self.callsSound = QtGui.QCheckBox(self) self.callsSound = QtGui.QCheckBox(self)
self.callsSound.setGeometry(QtCore.QRect(10, 120, 340, 18)) self.callsSound.setGeometry(QtCore.QRect(10, 120, 340, 18))
self.soundNotifications = QtGui.QCheckBox(self) self.soundNotifications = QtGui.QCheckBox(self)
self.soundNotifications.setGeometry(QtCore.QRect(10, 70, 340, 18)) self.soundNotifications.setGeometry(QtCore.QRect(10, 70, 340, 18))
self.gcNotifications = QtGui.QCheckBox(self)
self.gcNotifications.setGeometry(QtCore.QRect(10, 170, 340, 18))
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(12) font.setPointSize(12)
self.callsSound.setFont(font) self.callsSound.setFont(font)
self.soundNotifications.setFont(font) self.soundNotifications.setFont(font)
self.enableNotifications.setFont(font) self.enableNotifications.setFont(font)
self.gcNotifications.setFont(font)
s = Settings.get_instance() s = Settings.get_instance()
self.enableNotifications.setChecked(s['notifications']) self.enableNotifications.setChecked(s['notifications'])
self.soundNotifications.setChecked(s['sound_notifications']) self.soundNotifications.setChecked(s['sound_notifications'])
self.callsSound.setChecked(s['calls_sound']) self.callsSound.setChecked(s['calls_sound'])
self.gcNotifications.setChecked(s['notify_all_gc'])
self.retranslateUi() self.retranslateUi()
QtCore.QMetaObject.connectSlotsByName(self) QtCore.QMetaObject.connectSlotsByName(self)
def retranslateUi(self): def retranslateUi(self):
self.gcNotifications.setText(QtGui.QApplication.translate("notificationsForm", "Enable group chat notifications", None, QtGui.QApplication.UnicodeUTF8))
self.setWindowTitle(QtGui.QApplication.translate("notificationsForm", "Notification settings", None, QtGui.QApplication.UnicodeUTF8)) self.setWindowTitle(QtGui.QApplication.translate("notificationsForm", "Notification settings", None, QtGui.QApplication.UnicodeUTF8))
self.enableNotifications.setText(QtGui.QApplication.translate("notificationsForm", "Enable notifications", None, QtGui.QApplication.UnicodeUTF8)) self.enableNotifications.setText(QtGui.QApplication.translate("notificationsForm", "Enable notifications", None, QtGui.QApplication.UnicodeUTF8))
self.callsSound.setText(QtGui.QApplication.translate("notificationsForm", "Enable call\'s sound", None, QtGui.QApplication.UnicodeUTF8)) self.callsSound.setText(QtGui.QApplication.translate("notificationsForm", "Enable call\'s sound", None, QtGui.QApplication.UnicodeUTF8))
@ -542,6 +638,7 @@ class NotificationsSettings(CenteredWidget):
settings['notifications'] = self.enableNotifications.isChecked() settings['notifications'] = self.enableNotifications.isChecked()
settings['sound_notifications'] = self.soundNotifications.isChecked() settings['sound_notifications'] = self.soundNotifications.isChecked()
settings['calls_sound'] = self.callsSound.isChecked() settings['calls_sound'] = self.callsSound.isChecked()
settings['notify_all_gc'] = self.gcNotifications.isChecked()
settings.save() settings.save()
@ -554,8 +651,8 @@ class InterfaceSettings(CenteredWidget):
def initUI(self): def initUI(self):
self.setObjectName("interfaceForm") self.setObjectName("interfaceForm")
self.setMinimumSize(QtCore.QSize(400, 580)) self.setMinimumSize(QtCore.QSize(400, 550))
self.setMaximumSize(QtCore.QSize(400, 580)) self.setMaximumSize(QtCore.QSize(400, 550))
self.label = QtGui.QLabel(self) self.label = QtGui.QLabel(self)
self.label.setGeometry(QtCore.QRect(30, 10, 370, 20)) self.label.setGeometry(QtCore.QRect(30, 10, 370, 20))
font = QtGui.QFont() font = QtGui.QFont()
@ -610,30 +707,25 @@ class InterfaceSettings(CenteredWidget):
self.messages_font_size.setCurrentIndex(settings['message_font_size'] - 10) self.messages_font_size.setCurrentIndex(settings['message_font_size'] - 10)
self.unread = QtGui.QPushButton(self) self.unread = QtGui.QPushButton(self)
self.unread.setGeometry(QtCore.QRect(30, 465, 340, 30)) self.unread.setGeometry(QtCore.QRect(30, 425, 340, 30))
self.unread.clicked.connect(self.select_color) self.unread.clicked.connect(self.select_color)
self.compact_mode = QtGui.QCheckBox(self) self.compact_mode = QtGui.QCheckBox(self)
self.compact_mode.setGeometry(QtCore.QRect(30, 380, 370, 20)) self.compact_mode.setGeometry(QtCore.QRect(30, 380, 370, 20))
self.compact_mode.setChecked(settings['compact_mode']) self.compact_mode.setChecked(settings['compact_mode'])
self.show_avatars = QtGui.QCheckBox(self)
self.show_avatars.setGeometry(QtCore.QRect(30, 410, 370, 20))
self.show_avatars.setChecked(settings['show_avatars'])
self.import_smileys = QtGui.QPushButton(self) self.import_smileys = QtGui.QPushButton(self)
self.import_smileys.setGeometry(QtCore.QRect(30, 505, 340, 30)) self.import_smileys.setGeometry(QtCore.QRect(30, 465, 340, 30))
self.import_smileys.clicked.connect(self.import_sm) self.import_smileys.clicked.connect(self.import_sm)
self.import_stickers = QtGui.QPushButton(self) self.import_stickers = QtGui.QPushButton(self)
self.import_stickers.setGeometry(QtCore.QRect(30, 545, 340, 30)) self.import_stickers.setGeometry(QtCore.QRect(30, 505, 340, 30))
self.import_stickers.clicked.connect(self.import_st) self.import_stickers.clicked.connect(self.import_st)
self.retranslateUi() self.retranslateUi()
QtCore.QMetaObject.connectSlotsByName(self) QtCore.QMetaObject.connectSlotsByName(self)
def retranslateUi(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.setWindowTitle(QtGui.QApplication.translate("interfaceForm", "Interface settings", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("interfaceForm", "Theme:", None, QtGui.QApplication.UnicodeUTF8)) self.label.setText(QtGui.QApplication.translate("interfaceForm", "Theme:", None, QtGui.QApplication.UnicodeUTF8))
self.lang.setText(QtGui.QApplication.translate("interfaceForm", "Language:", None, QtGui.QApplication.UnicodeUTF8)) self.lang.setText(QtGui.QApplication.translate("interfaceForm", "Language:", None, QtGui.QApplication.UnicodeUTF8))
@ -694,9 +786,6 @@ class InterfaceSettings(CenteredWidget):
if settings['compact_mode'] != self.compact_mode.isChecked(): if settings['compact_mode'] != self.compact_mode.isChecked():
settings['compact_mode'] = self.compact_mode.isChecked() settings['compact_mode'] = self.compact_mode.isChecked()
restart = True 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['smiley_pack'] = self.smiley_pack.currentText()
smileys.SmileyLoader.get_instance().load_pack() smileys.SmileyLoader.get_instance().load_pack()
language = self.lang_choose.currentText() language = self.lang_choose.currentText()

View File

@ -39,6 +39,18 @@ class TextMessage(Message):
return self._message, self._owner, self._time, self._type return self._message, self._owner, self._time, self._type
class GroupChatTextMessage(TextMessage):
def __init__(self, friend_name, *args):
super().__init__(*args)
self._name = friend_name
def get_data(self):
data = list(super().get_data())
data.append(self._name)
return tuple(data)
class TransferMessage(Message): class TransferMessage(Message):
""" """
Message with info about file transfer Message with info about file transfer
@ -71,6 +83,7 @@ class TransferMessage(Message):
class UnsentFile(Message): class UnsentFile(Message):
def __init__(self, path, data, time): def __init__(self, path, data, time):
super(UnsentFile, self).__init__(MESSAGE_TYPE['FILE_TRANSFER'], 0, time) super(UnsentFile, self).__init__(MESSAGE_TYPE['FILE_TRANSFER'], 0, time)
self._data, self._path = data, path self._data, self._path = data, path

View File

@ -15,9 +15,11 @@ import time
import calls import calls
import avwidgets import avwidgets
import plugin_support import plugin_support
import basecontact
from groupchat import *
class Profile(contact.Contact, Singleton): class Profile(basecontact.BaseContact, Singleton):
""" """
Profile of current toxygen user. Contains friends list, tox instance Profile of current toxygen user. Contains friends list, tox instance
""" """
@ -26,11 +28,11 @@ class Profile(contact.Contact, Singleton):
:param tox: tox instance :param tox: tox instance
:param screen: ref to main screen :param screen: ref to main screen
""" """
contact.Contact.__init__(self, basecontact.BaseContact.__init__(self,
tox.self_get_name(), tox.self_get_name(),
tox.self_get_status_message(), tox.self_get_status_message(),
screen.user_info, screen.user_info,
tox.self_get_address()) tox.self_get_address())
Singleton.__init__(self) Singleton.__init__(self)
self._screen = screen self._screen = screen
self._messages = screen.messages self._messages = screen.messages
@ -39,15 +41,14 @@ class Profile(contact.Contact, Singleton):
self._call = calls.AV(tox.AV) # object with data about calls self._call = calls.AV(tox.AV) # object with data about calls
self._incoming_calls = set() self._incoming_calls = set()
self._load_history = True self._load_history = True
self._gc_invites = {} # dict of gc invites. key - friend number, value - list of gc data
settings = Settings.get_instance() settings = Settings.get_instance()
self._show_online = settings['show_online_friends'] self._show_online = settings['show_online_friends']
self._show_avatars = settings['show_avatars']
self._friend_item_height = 40 if settings['compact_mode'] else 70
screen.online_contacts.setCurrentIndex(int(self._show_online)) screen.online_contacts.setCurrentIndex(int(self._show_online))
aliases = settings['friends_aliases'] aliases = settings['friends_aliases']
data = tox.self_get_friend_list() data = tox.self_get_friend_list()
self._history = History(tox.self_get_public_key()) # connection to db self._history = History(tox.self_get_public_key()) # connection to db
self._friends, self._active_friend = [], -1 self._friends_and_gc, self._active_friend_or_gc = [], -1
for i in data: # creates list of friends for i in data: # creates list of friends
tox_id = tox.friend_get_public_key(i) tox_id = tox.friend_get_public_key(i)
try: try:
@ -60,9 +61,27 @@ class Profile(contact.Contact, Singleton):
if not self._history.friend_exists_in_db(tox_id): if not self._history.friend_exists_in_db(tox_id):
self._history.add_friend_to_db(tox_id) self._history.add_friend_to_db(tox_id)
message_getter = self._history.messages_getter(tox_id) message_getter = self._history.messages_getter(tox_id)
friend = Friend(message_getter, i, name, status_message, item, tox_id) friend = Friend(i, message_getter, name, status_message, item, tox_id)
friend.set_alias(alias) friend.set_alias(alias)
self._friends.append(friend) self._friends_and_gc.append(friend)
l = self._tox.group_get_number_groups()
for i in range(l): # creates list of group chats
tox_id = tox.group_get_chat_id(i)
try:
alias = list(filter(lambda x: x[0] == tox_id, aliases))[0][1]
except:
alias = ''
item = self.create_friend_item()
name = alias or tox.group_get_name(i) or tox_id
status_message = tox.group_get_topic(i)
if not self._history.friend_exists_in_db(tox_id):
self._history.add_friend_to_db(tox_id)
message_getter = self._history.messages_getter(tox_id)
gc = GroupChat(self._tox, i, message_getter, name, status_message, item, tox_id)
gc.set_alias(alias)
self._friends_and_gc.append(gc)
self.filtration(self._show_online) self.filtration(self._show_online)
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
@ -77,6 +96,9 @@ class Profile(contact.Contact, Singleton):
self.set_status((self._status + 1) % 3) self.set_status((self._status + 1) % 3)
def set_status(self, status): def set_status(self, status):
if self.status is None:
for gc in filter(lambda x: type(x) is GroupChat, self._friends_and_gc):
self._tox.group_reconnect(gc.number)
super(Profile, self).set_status(status) super(Profile, self).set_status(status)
if status is not None: if status is not None:
self._tox.self_set_status(status) self._tox.self_set_status(status)
@ -89,11 +111,11 @@ class Profile(contact.Contact, Singleton):
self._tox.self_set_name(self._name.encode('utf-8')) self._tox.self_set_name(self._name.encode('utf-8'))
message = QtGui.QApplication.translate("MainWindow", 'User {} is now known as {}', None, message = QtGui.QApplication.translate("MainWindow", 'User {} is now known as {}', None,
QtGui.QApplication.UnicodeUTF8) QtGui.QApplication.UnicodeUTF8)
message = message.format(tmp, value) message = message.format(tmp, str(value, 'utf-8'))
for friend in self._friends: for friend in self._friends_and_gc:
friend.append_message(InfoMessage(message, time.time())) friend.append_message(InfoMessage(message, time.time()))
if self._active_friend + 1: if self._active_friend_or_gc + 1:
self.create_message_item(message, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) self.create_message_item(message, curr_time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
self._messages.scrollToBottom() self._messages.scrollToBottom()
def set_status_message(self, value): def set_status_message(self, value):
@ -119,12 +141,12 @@ class Profile(contact.Contact, Singleton):
""" """
filter_str = filter_str.lower() filter_str = filter_str.lower()
settings = Settings.get_instance() settings = Settings.get_instance()
for index, friend in enumerate(self._friends): for index, friend in enumerate(self._friends_and_gc):
friend.visibility = (friend.status is not None or not show_online) and (filter_str in friend.name.lower()) friend.visibility = (friend.status is not None or not show_online) and (filter_str in friend.name.lower())
friend.visibility = friend.visibility or friend.messages or friend.actions friend.visibility = friend.visibility or friend.messages or friend.actions
if friend.visibility: if friend.visibility:
self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250,
self._friend_item_height)) 40 if settings['compact_mode'] else 70))
else: else:
self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, 0)) self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, 0))
self._show_online, self._filter_string = show_online, filter_str self._show_online, self._filter_string = show_online, filter_str
@ -138,29 +160,32 @@ class Profile(contact.Contact, Singleton):
self.filtration(self._show_online, self._filter_string) self.filtration(self._show_online, self._filter_string)
def get_friend_by_number(self, num): def get_friend_by_number(self, num):
return list(filter(lambda x: x.number == num, self._friends))[0] return list(filter(lambda x: x.number == num and type(x) is Friend, self._friends_and_gc))[0]
def get_friend(self, num): def get_gc_by_number(self, num):
return self._friends[num] return list(filter(lambda x: x.number == num and type(x) is not Friend, self._friends_and_gc))[0]
def get_friend_or_gc(self, num):
return self._friends_and_gc[num]
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
# Work with active friend # Work with active friend
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
def get_active(self): def get_active(self):
return self._active_friend return self._active_friend_or_gc
def set_active(self, value=None): def set_active(self, value=None):
""" """
Change current active friend or update info Change current active friend or update info
:param value: number of new active friend in friend's list or None to update active user's data :param value: number of new active friend in friend's list or None to update active user's data
""" """
if value is None and self._active_friend == -1: # nothing to update if value is None and self._active_friend_or_gc == -1: # nothing to update
return return
if value == -1: # all friends were deleted if value == -1: # all friends were deleted
self._screen.account_name.setText('') self._screen.account_name.setText('')
self._screen.account_status.setText('') self._screen.account_status.setText('')
self._active_friend = -1 self._active_friend_or_gc = -1
self._screen.account_avatar.setHidden(True) self._screen.account_avatar.setHidden(True)
self._messages.clear() self._messages.clear()
self._screen.messageEdit.clear() self._screen.messageEdit.clear()
@ -169,26 +194,30 @@ class Profile(contact.Contact, Singleton):
self.send_typing(False) self.send_typing(False)
self._screen.typing.setVisible(False) self._screen.typing.setVisible(False)
if value is not None: if value is not None:
if self._active_friend + 1: if self._active_friend_or_gc + 1:
try: try:
self._friends[self._active_friend].curr_text = self._screen.messageEdit.toPlainText() self._friends_and_gc[self._active_friend_or_gc].curr_text = self._screen.messageEdit.toPlainText()
except: except:
pass pass
self._active_friend = value self._active_friend_or_gc = value
friend = self._friends[value] friend_or_gc = self._friends_and_gc[value]
self._friends[value].reset_messages() friend_or_gc.reset_messages()
self._screen.messageEdit.setPlainText(friend.curr_text) friend_or_gc.delete_old_messages()
self._screen.messageEdit.setPlainText(friend_or_gc.curr_text)
self._messages.clear() self._messages.clear()
friend.load_corr() friend_or_gc.load_corr()
messages = friend.get_corr()[-PAGE_SIZE:] messages = friend_or_gc.get_corr()[-PAGE_SIZE:]
self._load_history = False self._load_history = False
for message in messages: for message in messages:
if message.get_type() <= 1: if message.get_type() <= 1:
data = message.get_data() data = message.get_data()
self.create_message_item(data[0], self.create_message_item(data[0],
data[2], data[2],
data[1], data[1],
data[3]) data[3],
True,
data[4] if len(data) == 5 else None)
elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']:
if message.get_status() is None: if message.get_status() is None:
self.create_unsent_file_item(message) self.create_unsent_file_item(message)
@ -218,13 +247,16 @@ class Profile(contact.Contact, Singleton):
else: else:
self._screen.call_finished() self._screen.call_finished()
else: else:
friend = self._friends[self._active_friend] friend_or_gc = self._friends_and_gc[self._active_friend_or_gc]
self._screen.account_name.setText(friend.name) self._screen.account_name.setText(friend_or_gc.name)
self._screen.account_status.setText(friend.status_message) self._screen.account_status.setText(friend_or_gc.status_message)
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(friend.tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(friend_or_gc.tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
if not os.path.isfile(avatar_path): # load default image if not os.path.isfile(avatar_path): # load default image
avatar_path = curr_directory() + '/images/avatar.png' if type(friend_or_gc) is Friend:
avatar_path = curr_directory() + '/images/avatar.png'
else:
avatar_path = curr_directory() + '/images/group.png'
os.chdir(os.path.dirname(avatar_path)) os.chdir(os.path.dirname(avatar_path))
pixmap = QtGui.QPixmap(QtCore.QSize(64, 64)) pixmap = QtGui.QPixmap(QtCore.QSize(64, 64))
pixmap.load(avatar_path) pixmap.load(avatar_path)
@ -239,17 +271,20 @@ class Profile(contact.Contact, Singleton):
active_friend = property(get_active, set_active) active_friend = property(get_active, set_active)
def is_active_a_friend(self):
return type(self._friends_and_gc[self._active_friend_or_gc]) is Friend
def get_last_message(self): def get_last_message(self):
return self._friends[self._active_friend].get_last_message_text() return self._friends_and_gc[self._active_friend_or_gc].get_last_message_text()
def get_active_number(self): def get_active_number(self):
return self._friends[self._active_friend].number if self._active_friend + 1 else -1 return self._friends_and_gc[self._active_friend_or_gc].number if self._active_friend_or_gc + 1 else -1
def get_active_name(self): def get_active_name(self):
return self._friends[self._active_friend].name if self._active_friend + 1 else '' return self._friends_and_gc[self._active_friend_or_gc].name if self._active_friend_or_gc + 1 else ''
def is_active_online(self): def is_active_online(self):
return self._active_friend + 1 and self._friends[self._active_friend].status is not None return self._active_friend_or_gc + 1 and self._friends_and_gc[self._active_friend_or_gc].status is not None
def new_name(self, number, name): def new_name(self, number, name):
friend = self.get_friend_by_number(number) friend = self.get_friend_by_number(number)
@ -267,8 +302,8 @@ class Profile(contact.Contact, Singleton):
self.set_active(None) self.set_active(None)
def update(self): def update(self):
if self._active_friend + 1: if self._active_friend_or_gc + 1:
self.set_active(self._active_friend) self.set_active(self._active_friend_or_gc)
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
# Friend connection status callbacks # Friend connection status callbacks
@ -308,8 +343,8 @@ class Profile(contact.Contact, Singleton):
""" """
Send typing notification to a friend Send typing notification to a friend
""" """
if Settings.get_instance()['typing_notifications'] and self._active_friend + 1: if Settings.get_instance()['typing_notifications'] and self._active_friend_or_gc + 1:
friend = self._friends[self._active_friend] friend = self._friends_and_gc[self._active_friend_or_gc]
if friend.status is not None: if friend.status is not None:
self._tox.self_set_typing(friend.number, typing) self._tox.self_set_typing(friend.number, typing)
@ -343,12 +378,13 @@ class Profile(contact.Contact, Singleton):
except: except:
pass pass
def split_and_send(self, number, message_type, message): def split_and_send(self, number, message_type, message, is_group=False):
""" """
Message splitting Message splitting
:param number: friend's number :param number: friend or gc number
:param message_type: type of message :param message_type: type of message
:param message: message text :param message: message text
:param is_group: send to group
""" """
while len(message) > TOX_MAX_MESSAGE_LENGTH: while len(message) > TOX_MAX_MESSAGE_LENGTH:
size = TOX_MAX_MESSAGE_LENGTH * 4 / 5 size = TOX_MAX_MESSAGE_LENGTH * 4 / 5
@ -362,61 +398,95 @@ class Profile(contact.Contact, Singleton):
else: else:
index = TOX_MAX_MESSAGE_LENGTH - size - 1 index = TOX_MAX_MESSAGE_LENGTH - size - 1
index += size + 1 index += size + 1
self._tox.friend_send_message(number, message_type, message[:index]) if not is_group:
self._tox.friend_send_message(number, message_type, message[:index])
else:
self._tox.group_send_message(number, message_type, message[:index])
message = message[index:] message = message[index:]
self._tox.friend_send_message(number, message_type, message) if not is_group:
self._tox.friend_send_message(number, message_type, message)
else:
self._tox.group_send_message(number, message_type, message)
def new_message(self, friend_num, message_type, message): def new_message(self, num, message_type, message, is_group=False, peer_id=-1):
""" """
Current user gets new message Current user gets new message
:param friend_num: friend_num of friend who sent message :param num: num of friend or gc who sent message
:param message_type: message type - plain text or action message (/me) :param message_type: message type - plain text or action message (/me)
:param message: text of message :param message: text of message
:param is_group: is group chat message or not
:param peer_id: if gc - peer id
""" """
if friend_num == self.get_active_number(): # add message to list t = time.time()
t = time.time() active = False
self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type) if num == self.get_active_number() and is_group != self.is_active_a_friend(): # add message to list
if not is_group:
self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type)
else:
self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type, True,
self._tox.group_peer_get_name(num, peer_id))
self._messages.scrollToBottom() self._messages.scrollToBottom()
self._friends[self._active_friend].append_message( active = True
TextMessage(message, MESSAGE_OWNER['FRIEND'], t, message_type)) if is_group:
friend_or_gc = self.get_gc_by_number(num)
friend_or_gc.append_message(GroupChatTextMessage(self._tox.group_peer_get_name(num, peer_id),
message, MESSAGE_OWNER['FRIEND'],
time.time(), message_type))
else: else:
friend = self.get_friend_by_number(friend_num) friend_or_gc = self.get_friend_by_number(num)
friend.inc_messages() friend_or_gc.append_message(TextMessage(message, MESSAGE_OWNER['FRIEND'], time.time(), message_type))
friend.append_message( if not active:
TextMessage(message, MESSAGE_OWNER['FRIEND'], time.time(), message_type)) friend_or_gc.inc_messages()
if not friend.visibility: if not friend_or_gc.visibility:
self.update_filtration() self.update_filtration()
def send_message(self, text, friend_num=None): def send_message(self, text, number=None, is_gc=False):
""" """
Send message Send message
:param text: message text :param text: message text
:param friend_num: num of friend :param number: num of friend or gc
:param is_gc: is group chat
""" """
if friend_num is None: if number is None:
friend_num = self.get_active_number() number = self.get_active_number()
is_gc = not self.is_active_a_friend()
if text.startswith('/plugin '): if text.startswith('/plugin '):
plugin_support.PluginLoader.get_instance().command(text[8:]) plugin_support.PluginLoader.get_instance().command(text[8:])
self._screen.messageEdit.clear() self._screen.messageEdit.clear()
elif text and friend_num + 1: elif text and number + 1:
text = ''.join(c if c <= '\u10FFFF' else '\u25AF' for c in text)
if text.startswith('/me '): if text.startswith('/me '):
message_type = TOX_MESSAGE_TYPE['ACTION'] message_type = TOX_MESSAGE_TYPE['ACTION']
text = text[4:] text = text[4:]
else: else:
message_type = TOX_MESSAGE_TYPE['NORMAL'] message_type = TOX_MESSAGE_TYPE['NORMAL']
friend = self.get_friend_by_number(friend_num)
friend.inc_receipts() if not is_gc:
if friend.status is not None: friend_or_gc = self.get_friend_by_number(number)
self.split_and_send(friend.number, message_type, text.encode('utf-8')) else:
if friend.number == self.get_active_number(): friend_or_gc = self.get_gc_by_number(number)
t = time.time() t = time.time()
self.create_message_item(text, t, MESSAGE_OWNER['NOT_SENT'], message_type)
self._screen.messageEdit.clear() if not is_gc:
self._messages.scrollToBottom() friend_or_gc.inc_receipts()
friend.append_message(TextMessage(text, MESSAGE_OWNER['NOT_SENT'], t, message_type)) if friend_or_gc.status is not None:
self.split_and_send(friend_or_gc.number, message_type, text.encode('utf-8'))
if friend_or_gc.number == self.get_active_number() and self.is_active_a_friend():
self.create_message_item(text, t, MESSAGE_OWNER['NOT_SENT'], message_type)
self._screen.messageEdit.clear()
self._messages.scrollToBottom()
friend_or_gc.append_message(TextMessage(text, MESSAGE_OWNER['NOT_SENT'], t, message_type))
else:
self.split_and_send(friend_or_gc.number, message_type, text.encode('utf-8'), True)
if friend_or_gc.number == self.get_active_number() and not self.is_active_a_friend():
self.create_message_item(text, t, MESSAGE_OWNER['ME'], message_type)
self._screen.messageEdit.clear()
self._messages.scrollToBottom()
friend_or_gc.append_message(TextMessage(text, MESSAGE_OWNER['ME'], t, message_type))
def delete_message(self, time): def delete_message(self, time):
friend = self._friends[self._active_friend] friend = self._friends_and_gc[self._active_friend_or_gc]
friend.delete_message(time) friend.delete_message(time)
self._history.delete_message(friend.tox_id, time) self._history.delete_message(friend.tox_id, time)
self.update() self.update()
@ -430,20 +500,21 @@ class Profile(contact.Contact, Singleton):
Save history to db Save history to db
""" """
s = Settings.get_instance() s = Settings.get_instance()
# TODO: different saving for friends and gc
if hasattr(self, '_history'): if hasattr(self, '_history'):
if s['save_history']: if s['save_history']:
for friend in self._friends: for friend_or_gc in self._friends_and_gc:
if not self._history.friend_exists_in_db(friend.tox_id): if not self._history.friend_exists_in_db(friend_or_gc.tox_id):
self._history.add_friend_to_db(friend.tox_id) self._history.add_friend_to_db(friend_or_gc.tox_id)
if not s['save_unsent_only']: if not s['save_unsent_only']:
messages = friend.get_corr_for_saving() messages = friend_or_gc.get_corr_for_saving()
else: else:
messages = friend.get_unsent_messages_for_saving() messages = friend_or_gc.get_unsent_messages_for_saving()
self._history.delete_messages(friend.tox_id) self._history.delete_messages(friend_or_gc.tox_id)
self._history.save_messages_to_db(friend.tox_id, messages) self._history.save_messages_to_db(friend_or_gc.tox_id, messages)
unsent_messages = friend.get_unsent_messages() unsent_messages = friend_or_gc.get_unsent_messages()
unsent_time = unsent_messages[0].get_data()[2] if len(unsent_messages) else time.time() + 1 unsent_time = unsent_messages[0].get_data()[2] if len(unsent_messages) else time.time() + 1
self._history.update_messages(friend.tox_id, unsent_time) self._history.update_messages(friend_or_gc.tox_id, unsent_time)
self._history.save() self._history.save()
del self._history del self._history
@ -452,14 +523,15 @@ class Profile(contact.Contact, Singleton):
Clear chat history Clear chat history
""" """
if num is not None: if num is not None:
friend = self._friends[num] friend = self._friends_and_gc[num]
friend.clear_corr(save_unsent) friend.clear_corr(save_unsent)
if self._history.friend_exists_in_db(friend.tox_id): if self._history.friend_exists_in_db(friend.tox_id):
self._history.delete_messages(friend.tox_id) self._history.delete_messages(friend.tox_id)
self._history.delete_friend_from_db(friend.tox_id) self._history.delete_friend_from_db(friend.tox_id)
else: # clear all history else: # clear all history
for number in range(len(self._friends)): for number in range(len(self._friends_and_gc)):
self.clear_history(number, save_unsent) self.clear_history(number, save_unsent)
if num is None or num == self.get_active_number(): if num is None or num == self.get_active_number():
self.update() self.update()
@ -470,9 +542,10 @@ class Profile(contact.Contact, Singleton):
if not self._load_history: if not self._load_history:
return return
self._load_history = False self._load_history = False
friend = self._friends[self._active_friend] friend_or_gc = self._friends_and_gc[self._active_friend_or_gc]
friend.load_corr(False)
data = friend.get_corr() friend_or_gc.load_corr(False)
data = friend_or_gc.get_corr()
if not data: if not data:
return return
data.reverse() data.reverse()
@ -526,17 +599,14 @@ class Profile(contact.Contact, Singleton):
self._screen.friends_list.setItemWidget(elem, item) self._screen.friends_list.setItemWidget(elem, item)
return item return item
def create_message_item(self, text, time, owner, message_type, append=True): def create_message_item(self, text, time, owner, message_type, append=True, friend_name=None):
if message_type == MESSAGE_TYPE['INFO_MESSAGE']: if message_type == MESSAGE_TYPE['INFO_MESSAGE']:
name = '' name = ''
elif owner == MESSAGE_OWNER['FRIEND']: elif owner == MESSAGE_OWNER['FRIEND']:
name = self.get_active_name() name = friend_name or self.get_active_name()
else: else:
name = self._name name = self._name
item = MessageItem(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'], message_type, self._messages) 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 = QtGui.QListWidgetItem()
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height())) elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
if append: if append:
@ -592,7 +662,7 @@ class Profile(contact.Contact, Singleton):
""" """
Set new alias for friend Set new alias for friend
""" """
friend = self._friends[num] friend = self._friends_and_gc[num]
name = friend.name name = friend.name
dialog = QtGui.QApplication.translate('MainWindow', dialog = QtGui.QApplication.translate('MainWindow',
"Enter new alias for friend {} or leave empty to use friend's name:", "Enter new alias for friend {} or leave empty to use friend's name:",
@ -630,14 +700,16 @@ class Profile(contact.Contact, Singleton):
self.update() self.update()
def friend_public_key(self, num): def friend_public_key(self, num):
return self._friends[num].tox_id return self._friends_and_gc[num].tox_id
def delete_friend(self, num): def delete_friend_or_gc(self, num, is_gc=False, message=None):
""" """
Removes friend from contact list Removes friend or gc from contact list
:param num: number of friend in list :param num: number of friend or gc in list
:param is_gc: is a group chat
:param message: message in gc
""" """
friend = self._friends[num] friend = self._friends_and_gc[num]
settings = Settings.get_instance() settings = Settings.get_instance()
try: try:
index = list(map(lambda x: x[0], settings['friends_aliases'])).index(friend.tox_id) index = list(map(lambda x: x[0], settings['friends_aliases'])).index(friend.tox_id)
@ -650,11 +722,14 @@ class Profile(contact.Contact, Singleton):
self.clear_history(num) self.clear_history(num)
if self._history.friend_exists_in_db(friend.tox_id): if self._history.friend_exists_in_db(friend.tox_id):
self._history.delete_friend_from_db(friend.tox_id) self._history.delete_friend_from_db(friend.tox_id)
self._tox.friend_delete(friend.number) if not is_gc:
del self._friends[num] self._tox.friend_delete(friend.number)
else:
self._tox.group_leave(friend.number, message.encode('utf-8') if message is not None else None)
del self._friends_and_gc[num]
self._screen.friends_list.takeItem(num) self._screen.friends_list.takeItem(num)
if num == self._active_friend: # active friend was deleted if num == self._active_friend_or_gc: # active friend or gc was deleted
if not len(self._friends): # last friend was deleted if not len(self._friends_and_gc): # last contact was deleted
self.set_active(-1) self.set_active(-1)
else: else:
self.set_active(0) self.set_active(0)
@ -675,7 +750,7 @@ class Profile(contact.Contact, Singleton):
log('Accept friend request failed! ' + str(ex)) log('Accept friend request failed! ' + str(ex))
message_getter = None message_getter = None
friend = Friend(message_getter, num, tox_id, '', item, tox_id) friend = Friend(message_getter, num, tox_id, '', item, tox_id)
self._friends.append(friend) self._friends_and_gc.append(friend)
def block_user(self, tox_id): def block_user(self, tox_id):
""" """
@ -690,7 +765,7 @@ class Profile(contact.Contact, Singleton):
settings.save() settings.save()
try: try:
num = self._tox.friend_by_public_key(tox_id) num = self._tox.friend_by_public_key(tox_id)
self.delete_friend(num) self.delete_friend_or_gc(num)
data = self._tox.get_savedata() data = self._tox.get_savedata()
ProfileHelper.get_instance().save_profile(data) ProfileHelper.get_instance().save_profile(data)
except: # not in friend list except: # not in friend list
@ -742,7 +817,7 @@ class Profile(contact.Contact, Singleton):
self._history.add_friend_to_db(tox_id) self._history.add_friend_to_db(tox_id)
message_getter = self._history.messages_getter(tox_id) message_getter = self._history.messages_getter(tox_id)
friend = Friend(message_getter, result, tox_id, '', item, tox_id) friend = Friend(message_getter, result, tox_id, '', item, tox_id)
self._friends.append(friend) self._friends_and_gc.append(friend)
data = self._tox.get_savedata() data = self._tox.get_savedata()
ProfileHelper.get_instance().save_profile(data) ProfileHelper.get_instance().save_profile(data)
return True return True
@ -785,7 +860,7 @@ class Profile(contact.Contact, Singleton):
del self._tox del self._tox
self._tox = restart() self._tox = restart()
self.status = None self.status = None
for friend in self._friends: for friend in self._friends_and_gc:
friend.status = None friend.status = None
self.update_filtration() self.update_filtration()
@ -793,8 +868,8 @@ class Profile(contact.Contact, Singleton):
if hasattr(self, '_call'): if hasattr(self, '_call'):
self._call.stop() self._call.stop()
del self._call del self._call
for i in range(len(self._friends)): for i in range(len(self._friends_and_gc)):
del self._friends[0] del self._friends_and_gc[0]
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
# File transfers support # File transfers support
@ -878,7 +953,7 @@ class Profile(contact.Contact, Singleton):
0, -1) 0, -1)
def cancel_not_started_transfer(self, time): def cancel_not_started_transfer(self, time):
self._friends[self._active_friend].delete_one_unsent_file(time) self._friends_and_gc[self._active_friend_or_gc].delete_one_unsent_file(time)
self.update() self.update()
def pause_transfer(self, friend_number, file_number, by_friend=False): def pause_transfer(self, friend_number, file_number, by_friend=False):
@ -943,7 +1018,6 @@ class Profile(contact.Contact, Singleton):
:param data: raw data - png :param data: raw data - png
""" """
self.send_inline(data, 'toxygen_inline.png') self.send_inline(data, 'toxygen_inline.png')
self._messages.repaint()
def send_sticker(self, path): def send_sticker(self, path):
with open(path, 'rb') as fl: with open(path, 'rb') as fl:
@ -1002,7 +1076,7 @@ class Profile(contact.Contact, Singleton):
st.get_file_number()) st.get_file_number())
item = self.create_file_transfer_item(tm) item = self.create_file_transfer_item(tm)
st.set_state_changed_handler(item.update) st.set_state_changed_handler(item.update)
self._friends[self._active_friend].append_message(tm) self._friends_and_gc[self._active_friend_or_gc].append_message(tm)
self._messages.scrollToBottom() self._messages.scrollToBottom()
def incoming_chunk(self, friend_number, file_number, position, data): def incoming_chunk(self, friend_number, file_number, position, data):
@ -1095,12 +1169,12 @@ class Profile(contact.Contact, Singleton):
def reset_avatar(self): def reset_avatar(self):
super(Profile, self).reset_avatar() super(Profile, self).reset_avatar()
for friend in filter(lambda x: x.status is not None, self._friends): for friend in filter(lambda x: x.status is not None, self._friends_and_gc):
self.send_avatar(friend.number) self.send_avatar(friend.number)
def set_avatar(self, data): def set_avatar(self, data):
super(Profile, self).set_avatar(data) super(Profile, self).set_avatar(data)
for friend in filter(lambda x: x.status is not None, self._friends): for friend in filter(lambda x: x.status is not None, self._friends_and_gc):
self.send_avatar(friend.number) self.send_avatar(friend.number)
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
@ -1114,6 +1188,8 @@ class Profile(contact.Contact, Singleton):
def call_click(self, audio=True, video=False): def call_click(self, audio=True, video=False):
"""User clicked audio button in main window""" """User clicked audio button in main window"""
if not self.is_active_a_friend():
return
num = self.get_active_number() num = self.get_active_number()
if num not in self._call and self.is_active_online(): # start call if num not in self._call and self.is_active_online(): # start call
self._call(num, audio, video) self._call(num, audio, video)
@ -1124,7 +1200,8 @@ class Profile(contact.Contact, Singleton):
else: else:
text = QtGui.QApplication.translate("incoming_call", "Outgoing audio call", None, text = QtGui.QApplication.translate("incoming_call", "Outgoing audio call", None,
QtGui.QApplication.UnicodeUTF8) QtGui.QApplication.UnicodeUTF8)
self._friends[self._active_friend].append_message(InfoMessage(text, time.time()))
self._friends_and_gc[self._active_friend_or_gc].append_message(InfoMessage(text, time.time()))
self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
self._messages.scrollToBottom() self._messages.scrollToBottom()
elif num in self._call: # finish or cancel call if you call with active friend elif num in self._call: # finish or cancel call if you call with active friend
@ -1182,6 +1259,74 @@ class Profile(contact.Contact, Singleton):
self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
self._messages.scrollToBottom() self._messages.scrollToBottom()
# -----------------------------------------------------------------------------------------------------------------
# Group chats support
# -----------------------------------------------------------------------------------------------------------------
def get_all_gc(self):
return list(filter(lambda x: type(x) is GroupChat, self._friends_and_gc))
def add_gc(self, num):
try:
tox_id = self._tox.group_get_chat_id(num)
name = self._tox.group_get_name(num)
topic = self._tox.group_get_topic(num)
except:
tox_id = name = topic = ''
item = self.create_friend_item()
try:
if not self._history.friend_exists_in_db(tox_id):
self._history.add_friend_to_db(tox_id)
message_getter = self._history.messages_getter(tox_id)
except Exception as ex: # something is wrong
log('Accept friend request failed! ' + str(ex))
message_getter = None
gc = GroupChat(self._tox, num, message_getter, name, topic, item, tox_id)
self._friends_and_gc.append(gc)
def join_gc(self, chat_id, password):
num = self._tox.group_join(chat_id, password if password else None)
if num != 2 ** 32 - 1:
self.add_gc(num)
else:
pass # TODO: join failed, show error
def create_gc(self, name, is_public, password):
privacy_state = TOX_GROUP_PRIVACY_STATE['TOX_GROUP_PRIVACY_STATE_PUBLIC'] if is_public else TOX_GROUP_PRIVACY_STATE['TOX_GROUP_PRIVACY_STATE_PRIVATE']
num = self._tox.group_new(privacy_state, bytes(name, 'utf-8'))
if password:
self._tox.group_founder_set_password(num, bytes(password, 'utf-8'))
self.add_gc(num)
self.get_gc_by_number(num).set_status(TOX_USER_STATUS['NONE'])
def process_group_invite(self, friend_num, data):
# TODO: support password
try:
text = QtGui.QApplication.translate('MainWindow', 'User {} invites you to group',
None, QtGui.QApplication.UnicodeUTF8)
info = text.format(self.get_friend_by_number(friend_num).name)
fr_req = QtGui.QApplication.translate('MainWindow', 'Group chat invite', None, QtGui.QApplication.UnicodeUTF8)
reply = QtGui.QMessageBox.question(None, fr_req, info, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes: # accepted
num = self._tox.group_invite_accept(data, friend_num)
data = self._tox.get_savedata()
ProfileHelper.get_instance().save_profile(data)
print('In gc invite', num)
self.add_gc(num)
elif reply != QtGui.QMessageBox.No:
if friend_num in self._gc_invites:
self._gc_invites[friend_num].append(data)
else:
self._gc_invites[friend_num] = data
except Exception as ex: # something is wrong
log('Accept group chat invite failed! ' + str(ex))
def invite_friend(self, group_number, friend_number):
self._tox.group_invite_friend(group_number, friend_number)
def leave_group(self, num, message=None):
self.delete_friend_or_gc(num, True, message)
def tox_factory(data=None, settings=None): def tox_factory(data=None, settings=None):
""" """

View File

@ -110,7 +110,6 @@ class Settings(dict, Singleton):
'show_online_friends': False, 'show_online_friends': False,
'auto_accept_from_friends': [], 'auto_accept_from_friends': [],
'friends_aliases': [], 'friends_aliases': [],
'show_avatars': False,
'typing_notifications': False, 'typing_notifications': False,
'calls_sound': True, 'calls_sound': True,
'blocked': [], 'blocked': [],
@ -127,7 +126,8 @@ class Settings(dict, Singleton):
'unread_color': 'red', 'unread_color': 'red',
'save_unsent_only': False, 'save_unsent_only': False,
'compact_mode': False, 'compact_mode': False,
'show_welcome_screen': True 'show_welcome_screen': True,
'notify_all_gc': False
} }
@staticmethod @staticmethod

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 883 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 891 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 809 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 893 B

View File

@ -1 +0,0 @@
{"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.

Before

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 956 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 956 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 929 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 935 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 921 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 920 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 829 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 996 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 957 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 882 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 890 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 751 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 989 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 782 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 967 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 886 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 918 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 990 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

View File

@ -92,6 +92,22 @@ class Tox:
self.file_recv_chunk_cb = None self.file_recv_chunk_cb = None
self.friend_lossy_packet_cb = None self.friend_lossy_packet_cb = None
self.friend_lossless_packet_cb = None self.friend_lossless_packet_cb = None
self.group_moderation_cb = None
self.group_join_fail_cb = None
self.group_self_join_cb = None
self.group_invite_cb = None
self.group_custom_packet_cb = None
self.group_private_message_cb = None
self.group_private_message_cb = None
self.group_message_cb = None
self.group_password_cb = None
self.group_peer_limit_cb = None
self.group_privacy_state_cb = None
self.group_topic_cb = None
self.group_peer_status_cb = None
self.group_peer_name_cb = None
self.group_peer_exit_cb = None
self.group_peer_join_cb = None
self.AV = ToxAV(self._tox_pointer) self.AV = ToxAV(self._tox_pointer)
@ -1510,3 +1526,935 @@ class Tox:
return result return result
elif tox_err_get_port == TOX_ERR_GET_PORT['NOT_BOUND']: elif tox_err_get_port == TOX_ERR_GET_PORT['NOT_BOUND']:
raise RuntimeError('The instance was not bound to any port.') raise RuntimeError('The instance was not bound to any port.')
# -----------------------------------------------------------------------------------------------------------------
# Group chat instance management
# -----------------------------------------------------------------------------------------------------------------
def group_new(self, privacy_state, group_name):
"""
Creates a new group chat.
This function creates a new group chat object and adds it to the chats array.
The client should initiate its peer list with self info after calling this function, as
the peer_join callback will not be triggered.
:param privacy_state: The privacy state of the group. If this is set to TOX_GROUP_PRIVACY_STATE_PUBLIC,
the group will attempt to announce itself to the DHT and anyone with the Chat ID may join.
Otherwise a friend invite will be required to join the group.
:param group_name: The name of the group. The name must be non-NULL.
:return group number on success, UINT32_MAX on failure.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_new(self._tox_pointer, privacy_state, group_name,
len(group_name), byref(error))
return result
def group_join(self, chat_id, password):
"""
Joins a group chat with specified Chat ID.
This function creates a new group chat object, adds it to the chats array, and sends
a DHT announcement to find peers in the group associated with chat_id. Once a peer has been
found a join attempt will be initiated.
:param chat_id: The Chat ID of the group you wish to join. This must be TOX_GROUP_CHAT_ID_SIZE bytes.
:param password: The password required to join the group. Set to NULL if no password is required.
:return groupnumber on success, UINT32_MAX on failure.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_join(self._tox_pointer, string_to_bin(chat_id),
password,
len(password) if password is not None else 0,
byref(error))
return result
def group_reconnect(self, groupnumber):
"""
Reconnects to a group.
This function disconnects from all peers in the group, then attempts to reconnect with the group.
The caller's state is not changed (i.e. name, status, role, chat public key etc.)
:param groupnumber: The group number of the group we wish to reconnect to.
:return True on success.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_reconnect(self._tox_pointer, groupnumber, byref(error))
return result
def group_leave(self, groupnumber, message):
"""
Leaves a group.
This function sends a parting packet containing a custom (non-obligatory) message to all
peers in a group, and deletes the group from the chat array. All group state information is permanently
lost, including keys and role credentials.
:param groupnumber: The group number of the group we wish to leave.
:param message: The parting message to be sent to all the peers. Set to NULL if we do not wish to
send a parting message.
:return True if the group chat instance was successfully deleted.
"""
error = c_int()
f = Tox.libtoxcore.tox_group_leave
f.restype = c_bool
result = f(self._tox_pointer, groupnumber, message,
len(message) if message is not None else 0, byref(error))
return result
# -----------------------------------------------------------------------------------------------------------------
# Group user-visible client information (nickname/status/role/public key)
# -----------------------------------------------------------------------------------------------------------------
def group_self_set_name(self, groupnumber, name):
"""
Set the client's nickname for the group instance designated by the given group number.
Nickname length cannot exceed TOX_MAX_NAME_LENGTH. If length is equal to zero or name is a NULL
pointer, the function call will fail.
:param name: A byte array containing the new nickname.
:return True on success.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_self_set_name(self._tox_pointer, groupnumber, name, len(name), byref(error))
return result
def group_self_get_name_size(self, groupnumber):
"""
Return the length of the client's current nickname for the group instance designated
by groupnumber as passed to tox_group_self_set_name.
If no nickname was set before calling this function, the name is empty,
and this function returns 0.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_self_get_name_size(self._tox_pointer, groupnumber, byref(error))
return result
def group_self_get_name(self, groupnumber):
"""
Write the nickname set by tox_group_self_set_name to a byte array.
If no nickname was set before calling this function, the name is empty,
and this function has no effect.
Call tox_group_self_get_name_size to find out how much memory to allocate for the result.
:return nickname
"""
error = c_int()
size = self.group_self_get_name_size(groupnumber)
name = create_string_buffer(size)
result = Tox.libtoxcore.tox_group_self_get_name(self._tox_pointer, groupnumber, name, byref(error))
return str(name[:size], 'utf-8')
def group_self_set_status(self, groupnumber, status):
"""
Set the client's status for the group instance. Status must be a TOX_USER_STATUS.
:return True on success.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_self_set_status(self._tox_pointer, groupnumber, status, byref(error))
return result
def group_self_get_status(self, groupnumber):
"""
returns the client's status for the group instance on success.
return value is unspecified on failure.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_self_get_status(self._tox_pointer, groupnumber, byref(error))
return result
def group_self_get_role(self, groupnumber):
"""
returns the client's role for the group instance on success.
return value is unspecified on failure.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_self_get_role(self._tox_pointer, groupnumber, byref(error))
return result
def group_self_get_peer_id(self, groupnumber):
"""
returns the client's peer id for the group instance on success.
return value is unspecified on failure.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_self_get_peer_id(self._tox_pointer, groupnumber, byref(error))
return result
def group_self_get_public_key(self, groupnumber):
"""
Write the client's group public key designated by the given group number to a byte array.
This key will be permanently tied to the client's identity for this particular group until
the client explicitly leaves the group or gets kicked/banned. This key is the only way for
other peers to reliably identify the client across client restarts.
`public_key` should have room for at least TOX_GROUP_PEER_PUBLIC_KEY_SIZE bytes.
:return public key
"""
error = c_int()
key = create_string_buffer(TOX_GROUP_PEER_PUBLIC_KEY_SIZE)
result = Tox.libtoxcore.tox_group_self_get_public_key(self._tox_pointer, groupnumber,
key, byref(error))
return bin_to_string(key, TOX_GROUP_PEER_PUBLIC_KEY_SIZE)
# -----------------------------------------------------------------------------------------------------------------
# Peer-specific group state queries.
# -----------------------------------------------------------------------------------------------------------------
def group_peer_get_name_size(self, groupnumber, peer_id):
"""
Return the length of the peer's name. If the group number or ID is invalid, the
return value is unspecified.
The return value is equal to the `length` argument received by the last
`group_peer_name` callback.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_peer_get_name_size(self._tox_pointer, groupnumber, peer_id, byref(error))
return result
def group_peer_get_name(self, groupnumber, peer_id):
"""
Write the name of the peer designated by the given ID to a byte
array.
Call tox_group_peer_get_name_size to determine the allocation size for the `name` parameter.
The data written to `name` is equal to the data received by the last
`group_peer_name` callback.
:param groupnumber: The group number of the group we wish to query.
:param peer_id: The ID of the peer whose name we want to retrieve.
:return name.
"""
error = c_int()
size = self.group_peer_get_name_size(groupnumber, peer_id)
name = create_string_buffer(size)
result = Tox.libtoxcore.tox_group_peer_get_name(self._tox_pointer, groupnumber, peer_id, name, byref(error))
return str(name[:], 'utf-8')
def group_peer_get_status(self, groupnumber, peer_id):
"""
Return the peer's user status (away/busy/...). If the ID or group number is
invalid, the return value is unspecified.
The status returned is equal to the last status received through the
`group_peer_status` callback.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_peer_get_status(self._tox_pointer, groupnumber, peer_id, byref(error))
return result
def group_peer_get_role(self, groupnumber, peer_id):
"""
Return the peer's role (user/moderator/founder...). If the ID or group number is
invalid, the return value is unspecified.
The role returned is equal to the last role received through the
`group_moderation` callback.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_peer_get_role(self._tox_pointer, groupnumber, peer_id, byref(error))
return result
def group_peer_get_public_key(self, groupnumber, peer_id):
"""
Write the group public key with the designated peer_id for the designated group number to public_key.
This key will be parmanently tied to a particular peer until they explicitly leave the group or
get kicked/banned, and is the only way to reliably identify the same peer across client restarts.
`public_key` should have room for at least TOX_GROUP_PEER_PUBLIC_KEY_SIZE bytes.
:return public key
"""
error = c_int()
key = create_string_buffer(TOX_GROUP_PEER_PUBLIC_KEY_SIZE)
result = Tox.libtoxcore.tox_group_peer_get_public_key(self._tox_pointer, groupnumber, peer_id,
key, byref(error))
return bin_to_string(key, TOX_GROUP_PEER_PUBLIC_KEY_SIZE)
def callback_group_peer_name(self, callback, user_data):
"""
Set the callback for the `group_peer_name` event. Pass NULL to unset.
This event is triggered when a peer changes their nickname.
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_char_p, c_size_t, c_void_p)
self.group_peer_name_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_group_peer_name(self._tox_pointer, self.group_peer_name_cb, user_data)
def callback_group_peer_status(self, callback, user_data):
"""
Set the callback for the `group_peer_status` event. Pass NULL to unset.
This event is triggered when a peer changes their status.
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_int, c_void_p)
self.group_peer_status_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_group_peer_status(self._tox_pointer, self.group_peer_status_cb, user_data)
# -----------------------------------------------------------------------------------------------------------------
# Group chat state queries and events.
# -----------------------------------------------------------------------------------------------------------------
def group_set_topic(self, groupnumber, topic):
"""
Set the group topic and broadcast it to the rest of the group.
topic length cannot be longer than TOX_GROUP_MAX_TOPIC_LENGTH. If length is equal to zero or
topic is set to NULL, the topic will be unset.
:return True on success.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_set_topic(self._tox_pointer, groupnumber, topic, len(topic), byref(error))
return result
def group_get_topic_size(self, groupnumber):
"""
Return the length of the group topic. If the group number is invalid, the
return value is unspecified.
The return value is equal to the `length` argument received by the last
`group_topic` callback.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_get_topic_size(self._tox_pointer, groupnumber, byref(error))
return result
def group_get_topic(self, groupnumber):
"""
Write the topic designated by the given group number to a byte array.
Call tox_group_get_topic_size to determine the allocation size for the `topic` parameter.
The data written to `topic` is equal to the data received by the last
`group_topic` callback.
:return topic
"""
error = c_int()
size = self.group_get_topic_size(groupnumber)
topic = create_string_buffer(size)
result = Tox.libtoxcore.tox_group_get_topic(self._tox_pointer, groupnumber, topic, byref(error))
return str(topic[:size], 'utf-8')
def group_get_name_size(self, groupnumber):
"""
Return the length of the group name. If the group number is invalid, the
return value is unspecified.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_get_name_size(self._tox_pointer, groupnumber, byref(error))
return result
def group_get_name(self, groupnumber):
"""
Write the name of the group designated by the given group number to a byte array.
Call tox_group_get_name_size to determine the allocation size for the `name` parameter.
:return true on success.
"""
error = c_int()
size = self.group_get_name_size(groupnumber)
name = create_string_buffer(size)
result = Tox.libtoxcore.tox_group_get_name(self._tox_pointer, groupnumber,
name, byref(error))
return str(name[:size], 'utf-8')
def group_get_chat_id(self, groupnumber):
"""
Write the Chat ID designated by the given group number to a byte array.
`chat_id` should have room for at least TOX_GROUP_CHAT_ID_SIZE bytes.
:return chat id.
"""
error = c_int()
buff = create_string_buffer(TOX_GROUP_CHAT_ID_SIZE)
result = Tox.libtoxcore.tox_group_get_chat_id(self._tox_pointer, groupnumber,
buff, byref(error))
return bin_to_string(buff, TOX_GROUP_CHAT_ID_SIZE)
def group_get_number_groups(self):
"""
Return the number of groups in the Tox chats array.
"""
result = Tox.libtoxcore.tox_group_get_number_groups(self._tox_pointer)
return result
def group_get_privacy_state(self, groupnumber):
"""
Return the privacy state of the group designated by the given group number. If group number
is invalid, the return value is unspecified.
The value returned is equal to the data received by the last
`group_privacy_state` callback.
see the `Group chat founder controls` section for the respective set function.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_get_privacy_state(self._tox_pointer, groupnumber, byref(error))
return result
def group_get_peer_limit(self, groupnumber):
"""
Return the maximum number of peers allowed for the group designated by the given group number.
If the group number is invalid, the return value is unspecified.
The value returned is equal to the data received by the last
`group_peer_limit` callback.
see the `Group chat founder controls` section for the respective set function.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_get_peer_limit(self._tox_pointer, groupnumber, byref(error))
return result
def group_get_password_size(self, groupnumber):
"""
Return the length of the group password. If the group number is invalid, the
return value is unspecified.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_get_password_size(self._tox_pointer, groupnumber, byref(error))
return result
def group_get_password(self, groupnumber):
"""
Write the password for the group designated by the given group number to a byte array.
Call tox_group_get_password_size to determine the allocation size for the `password` parameter.
The data received is equal to the data received by the last
`group_password` callback.
see the `Group chat founder controls` section for the respective set function.
:return password
"""
error = c_int()
size = self.group_get_password_size(groupnumber)
password = create_string_buffer(size)
result = Tox.libtoxcore.tox_group_get_password(self._tox_pointer, groupnumber,
password, byref(error))
return str(password[:size], 'utf-8')
def callback_group_topic(self, callback, user_data):
"""
Set the callback for the `group_topic` event. Pass NULL to unset.
This event is triggered when a peer changes the group topic.
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_char_p, c_size_t, c_void_p)
self.group_topic_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_group_topic(self._tox_pointer, self.group_topic_cb, user_data)
def callback_group_privacy_state(self, callback, user_data):
"""
Set the callback for the `group_privacy_state` event. Pass NULL to unset.
This event is triggered when the group founder changes the privacy state.
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_int, c_void_p)
self.group_privacy_state_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_group_privacy_state(self._tox_pointer, self.group_privacy_state_cb, user_data)
def callback_group_peer_limit(self, callback, user_data):
"""
Set the callback for the `group_peer_limit` event. Pass NULL to unset.
This event is triggered when the group founder changes the maximum peer limit.
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_void_p)
self.group_peer_limit_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_group_peer_limit(self._tox_pointer, self.group_peer_limit_cb, user_data)
def callback_group_password(self, callback, user_data):
"""
Set the callback for the `group_password` event. Pass NULL to unset.
This event is triggered when the group founder changes the group password.
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_char_p, c_size_t, c_void_p)
self.group_password_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_group_password(self._tox_pointer, self.group_password_cb, user_data)
# -----------------------------------------------------------------------------------------------------------------
# Group message sending
# -----------------------------------------------------------------------------------------------------------------
def group_send_custom_packet(self, groupnumber, lossless, data):
"""
Send a custom packet to the group.
If lossless is true the packet will be lossless. Lossless packet behaviour is comparable
to TCP (reliability, arrive in order) but with packets instead of a stream.
If lossless is false, the packet will be lossy. Lossy packets behave like UDP packets,
meaning they might never reach the other side or might arrive more than once (if someone
is messing with the connection) or might arrive in the wrong order.
Unless latency is an issue or message reliability is not important, it is recommended that you use
lossless custom packets.
:param groupnumber: The group number of the group the message is intended for.
:param lossless: True if the packet should be lossless.
:param data A byte array containing the packet data.
:return True on success.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_send_custom_packet(self._tox_pointer, groupnumber, lossless, data,
len(data), byref(error))
return result
def group_send_private_message(self, groupnumber, peer_id, message):
"""
Send a text chat message to the specified peer in the specified group.
This function creates a group private message packet and pushes it into the send
queue.
The message length may not exceed TOX_MAX_MESSAGE_LENGTH. Larger messages
must be split by the client and sent as separate messages. Other clients can
then reassemble the fragments. Messages may not be empty.
:param groupnumber: The group number of the group the message is intended for.
:param peer_id: The ID of the peer the message is intended for.
:param message: A non-NULL pointer to the first element of a byte array containing the message text.
:return True on success.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_send_private_message(self._tox_pointer, groupnumber, peer_id, message,
len(message), byref(error))
return result
def group_send_message(self, groupnumber, type, message):
"""
Send a text chat message to the group.
This function creates a group message packet and pushes it into the send
queue.
The message length may not exceed TOX_MAX_MESSAGE_LENGTH. Larger messages
must be split by the client and sent as separate messages. Other clients can
then reassemble the fragments. Messages may not be empty.
:param groupnumber: The group number of the group the message is intended for.
:param type: Message type (normal, action, ...).
:param message: A non-NULL pointer to the first element of a byte array containing the message text.
:return True on success.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_send_message(self._tox_pointer, groupnumber, type, message, len(message),
byref(error))
return result
# -----------------------------------------------------------------------------------------------------------------
# Group message receiving
# -----------------------------------------------------------------------------------------------------------------
def callback_group_message(self, callback, user_data):
"""
Set the callback for the `group_message` event. Pass NULL to unset.
This event is triggered when the client receives a group message.
Callback: python function with params:
tox Tox* instance
groupnumber The group number of the group the message is intended for.
peer_id The ID of the peer who sent the message.
type The type of message (normal, action, ...).
message The message data.
length The length of the message.
user_data - user data
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_int, c_char_p, c_size_t, c_void_p)
self.group_message_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_group_message(self._tox_pointer, self.group_message_cb, user_data)
def callback_group_private_message(self, callback, user_data):
"""
Set the callback for the `group_private_message` event. Pass NULL to unset.
This event is triggered when the client receives a private message.
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_char_p, c_size_t, c_void_p)
self.group_private_message_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_group_private_message(self._tox_pointer, self.group_private_message_cb, user_data)
def callback_group_custom_packet(self, callback, user_data):
"""
Set the callback for the `group_custom_packet` event. Pass NULL to unset.
This event is triggered when the client receives a custom packet.
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, POINTER(c_uint8), c_void_p)
self.group_custom_packet_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_group_custom_packet(self._tox_pointer, self.group_custom_packet_cb, user_data)
# -----------------------------------------------------------------------------------------------------------------
# Group chat inviting and join/part events
# -----------------------------------------------------------------------------------------------------------------
def group_invite_friend(self, groupnumber, friend_number):
"""
Invite a friend to a group.
This function creates an invite request packet and pushes it to the send queue.
:param groupnumber: The group number of the group the message is intended for.
:param friend_number: The friend number of the friend the invite is intended for.
:return True on success.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_invite_friend(self._tox_pointer, groupnumber, friend_number, byref(error))
return result
def group_invite_accept(self, invite_data, friend_number, password=None):
"""
Accept an invite to a group chat that the client previously received from a friend. The invite
is only valid while the inviter is present in the group.
:param invite_data: The invite data received from the `group_invite` event.
:param password: The password required to join the group. Set to NULL if no password is required.
:return the groupnumber on success, UINT32_MAX on failure.
"""
error = c_int()
f = Tox.libtoxcore.tox_group_invite_accept
f.restype = c_uint32
result = f(self._tox_pointer, friend_number, invite_data, len(invite_data), password,
len(password) if password is not None else 0, byref(error))
print('Invite accept. Result:', result, 'Error:', error.value)
return result
def callback_group_invite(self, callback, user_data):
"""
Set the callback for the `group_invite` event. Pass NULL to unset.
This event is triggered when the client receives a group invite from a friend. The client must store
invite_data which is used to join the group via tox_group_invite_accept.
Callback: python function with params:
tox - Tox*
friend_number The friend number of the contact who sent the invite.
invite_data The invite data.
length The length of invite_data.
user_data - user data
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t, c_void_p)
self.group_invite_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_group_invite(self._tox_pointer, self.group_invite_cb, user_data)
def callback_group_peer_join(self, callback, user_data):
"""
Set the callback for the `group_peer_join` event. Pass NULL to unset.
This event is triggered when a peer other than self joins the group.
Callback: python function with params:
tox - Tox*
group_number - group number
peer_id - peer id
user_data - user data
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_void_p)
self.group_peer_join_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_group_peer_join(self._tox_pointer, self.group_peer_join_cb, user_data)
def callback_group_peer_exit(self, callback, user_data):
"""
Set the callback for the `group_peer_exit` event. Pass NULL to unset.
This event is triggered when a peer other than self exits the group.
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_char_p, c_size_t, c_void_p)
self.group_peer_exit_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_group_peer_exit(self._tox_pointer, self.group_peer_exit_cb, user_data)
def callback_group_self_join(self, callback, user_data):
"""
Set the callback for the `group_self_join` event. Pass NULL to unset.
This event is triggered when the client has successfully joined a group. Use this to initialize
any group information the client may need.
Callback: python fucntion with params:
tox - *Tox
group_number - group number
user_data - user data
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_void_p)
self.group_self_join_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_group_self_join(self._tox_pointer, self.group_self_join_cb, user_data)
def callback_group_join_fail(self, callback, user_data):
"""
Set the callback for the `group_join_fail` event. Pass NULL to unset.
This event is triggered when the client fails to join a group.
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_int, c_void_p)
self.group_join_fail_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_group_join_fail(self._tox_pointer, self.group_join_fail_cb, user_data)
# -----------------------------------------------------------------------------------------------------------------
# Group chat founder controls (these only work for the group founder)
# -----------------------------------------------------------------------------------------------------------------
def group_founder_set_password(self, groupnumber, password):
"""
Set or unset the group password.
This function sets the groups password, creates a new group shared state including the change,
and distributes it to the rest of the group.
:param groupnumber: The group number of the group for which we wish to set the password.
:param password: The password we want to set. Set password to NULL to unset the password.
:return True on success.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_founder_set_password(self._tox_pointer, groupnumber, password,
len(password), byref(error))
return result
def group_founder_set_privacy_state(self, groupnumber, privacy_state):
"""
Set the group privacy state.
This function sets the group's privacy state, creates a new group shared state
including the change, and distributes it to the rest of the group.
If an attempt is made to set the privacy state to the same state that the group is already
in, the function call will be successful and no action will be taken.
:param groupnumber: The group number of the group for which we wish to change the privacy state.
:param privacy_state: The privacy state we wish to set the group to.
:return true on success.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_founder_set_privacy_state(self._tox_pointer, groupnumber, privacy_state,
byref(error))
return result
def group_founder_set_peer_limit(self, groupnumber, max_peers):
"""
Set the group peer limit.
This function sets a limit for the number of peers who may be in the group, creates a new
group shared state including the change, and distributes it to the rest of the group.
:param groupnumber: The group number of the group for which we wish to set the peer limit.
:param max_peers: The maximum number of peers to allow in the group.
:return True on success.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_founder_set_peer_limit(self._tox_pointer, groupnumber,
max_peers, byref(error))
return result
# -----------------------------------------------------------------------------------------------------------------
# Group chat moderation
# -----------------------------------------------------------------------------------------------------------------
def group_toggle_ignore(self, groupnumber, peer_id, ignore):
"""
Ignore or unignore a peer.
:param groupnumber: The group number of the group the in which you wish to ignore a peer.
:param peer_id: The ID of the peer who shall be ignored or unignored.
:param ignore: True to ignore the peer, false to unignore the peer.
:return True on success.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_toggle_ignore(self._tox_pointer, groupnumber, peer_id, ignore, byref(error))
return result
def group_mod_set_role(self, groupnumber, peer_id, role):
"""
Set a peer's role.
This function will first remove the peer's previous role and then assign them a new role.
It will also send a packet to the rest of the group, requesting that they perform
the role reassignment. Note: peers cannot be set to the founder role.
:param groupnumber: The group number of the group the in which you wish set the peer's role.
:param peer_id: The ID of the peer whose role you wish to set.
:param role: The role you wish to set the peer to.
:return True on success.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_mod_set_role(self._tox_pointer, groupnumber, peer_id, role, byref(error))
return result
def group_mod_remove_peer(self, groupnumber, peer_id, set_ban):
"""
Kick/ban a peer.
This function will remove a peer from the caller's peer list and optionally add their IP address
to the ban list. It will also send a packet to all group members requesting them
to do the same.
:param groupnumber: The group number of the group the ban is intended for.
:param peer_id: The ID of the peer who will be kicked and/or added to the ban list.
:param set_ban: Set to true if a ban shall be set on the peer's IP address.
:return True on success.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_mod_remove_peer(self._tox_pointer, groupnumber, peer_id,
set_ban, byref(error))
return result
def group_mod_remove_ban(self, groupnumber, ban_id):
"""
Removes a ban.
This function removes a ban entry from the ban list, and sends a packet to the rest of
the group requesting that they do the same.
:param groupnumber: The group number of the group in which the ban is to be removed.
:param ban_id: The ID of the ban entry that shall be removed.
:return True on success
"""
error = c_int()
result = Tox.libtoxcore.tox_group_mod_remove_ban(self._tox_pointer, groupnumber, ban_id, byref(error))
return result
def callback_group_moderation(self, callback, user_data):
"""
Set the callback for the `group_moderation` event. Pass NULL to unset.
This event is triggered when a moderator or founder executes a moderation event.
"""
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint32, c_int, c_void_p)
self.group_moderation_cb = c_callback(callback)
Tox.libtoxcore.tox_callback_group_moderation(self._tox_pointer, self.group_moderation_cb, user_data)
# -----------------------------------------------------------------------------------------------------------------
# Group chat ban list queries
# -----------------------------------------------------------------------------------------------------------------
def group_ban_get_list_size(self, groupnumber):
"""
Return the number of entries in the ban list for the group designated by
the given group number. If the group number is invalid, the return value is unspecified.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_ban_get_list_size(self._tox_pointer, groupnumber, byref(error))
return result
def group_ban_get_list(self, groupnumber):
"""
Copy a list of valid ban list ID's into an array.
Call tox_group_ban_get_list_size to determine the number of elements to allocate.
return true on success.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_ban_get_list(self._tox_pointer, groupnumber, POINTER(c_uint32)(
create_string_buffer(sizeof(c_uint32) * self.group_ban_get_list_size(groupnumber)), byref(error)))
return result
def group_ban_get_name_size(self, groupnumber, ban_id):
"""
Return the length of the name for the ban list entry designated by ban_id, in the
group designated by the given group number. If either groupnumber or ban_id is invalid,
the return value is unspecified.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_ban_get_name_size(self._tox_pointer, groupnumber, ban_id, byref(error))
return result
def group_ban_get_name(self, groupnumber, ban_id):
"""
Write the name of the ban entry designated by ban_id in the group designated by the
given group number to a byte array.
Call tox_group_ban_get_name_size to find out how much memory to allocate for the result.
:return name
"""
error = c_int()
size = self.group_ban_get_name_size(groupnumber, ban_id)
name = create_string_buffer()
result = Tox.libtoxcore.tox_group_ban_get_name(self._tox_pointer, groupnumber, ban_id,
name, byref(error))
return str(name[:size], 'utf-8')
def group_ban_get_time_set(self, groupnumber, ban_id):
"""
Return a time stamp indicating the time the ban was set, for the ban list entry
designated by ban_id, in the group designated by the given group number.
If either groupnumber or ban_id is invalid, the return value is unspecified.
"""
error = c_int()
result = Tox.libtoxcore.tox_group_ban_get_time_set(self._tox_pointer, groupnumber, ban_id, byref(error))
return result

View File

@ -188,6 +188,729 @@ TOX_ERR_GET_PORT = {
'NOT_BOUND': 1, 'NOT_BOUND': 1,
} }
TOX_GROUP_PRIVACY_STATE = {
#
# The group is considered to be public. Anyone may join the group using the Chat ID.
#
# If the group is in this state, even if the Chat ID is never explicitly shared
# with someone outside of the group, information including the Chat ID, IP addresses,
# and peer ID's (but not Tox ID's) is visible to anyone with access to a node
# storing a DHT entry for the given group.
#
'TOX_GROUP_PRIVACY_STATE_PUBLIC': 0,
#
# The group is considered to be private. The only way to join the group is by having
# someone in your contact list send you an invite.
#
# If the group is in this state, no group information (mentioned above) is present in the DHT;
# the DHT is not used for any purpose at all. If a public group is set to private,
# all DHT information related to the group will expire shortly.
#
'TOX_GROUP_PRIVACY_STATE_PRIVATE': 1
}
TOX_GROUP_ROLE = {
#
# May kick and ban all other peers as well as set their role to anything (except founder).
# Founders may also set the group password, toggle the privacy state, and set the peer limit.
#
'TOX_GROUP_ROLE_FOUNDER': 0,
#
# May kick, ban and set the user and observer roles for peers below this role.
# May also set the group topic.
#
'TOX_GROUP_ROLE_MODERATOR': 1,
#
# May communicate with other peers normally.
#
'TOX_GROUP_ROLE_USER': 2,
#
# May observe the group and ignore peers; may not communicate with other peers or with the group.
#
'TOX_GROUP_ROLE_OBSERVER': 3
}
TOX_ERR_GROUP_NEW = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_NEW_OK': 0,
#
# The group name exceeded TOX_GROUP_MAX_GROUP_NAME_LENGTH.
#
'TOX_ERR_GROUP_NEW_TOO_LONG': 1,
#
# group_name is NULL or length is zero.
#
'TOX_ERR_GROUP_NEW_EMPTY': 2,
#
# TOX_GROUP_PRIVACY_STATE is an invalid type.
#
'TOX_ERR_GROUP_NEW_PRIVACY': 3,
#
# The group instance failed to initialize.
#
'TOX_ERR_GROUP_NEW_INIT': 4,
#
# The group state failed to initialize. This usually indicates that something went wrong
# related to cryptographic signing.
#
'TOX_ERR_GROUP_NEW_STATE': 5,
#
# The group failed to announce to the DHT. This indicates a network related error.
#
'TOX_ERR_GROUP_NEW_ANNOUNCE': 6,
}
TOX_ERR_GROUP_JOIN = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_JOIN_OK': 0,
#
# The group instance failed to initialize.
#
'TOX_ERR_GROUP_JOIN_INIT': 1,
#
# The chat_id pointer is set to NULL or a group with chat_id already exists. This usually
# happens if the client attempts to create multiple sessions for the same group.
#
'TOX_ERR_GROUP_JOIN_BAD_CHAT_ID': 2,
#
# Password length exceeded TOX_GROUP_MAX_PASSWORD_SIZE.
#
'TOX_ERR_GROUP_JOIN_TOO_LONG': 3,
}
TOX_ERR_GROUP_RECONNECT = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_RECONNECT_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_RECONNECT_GROUP_NOT_FOUND': 1,
}
TOX_ERR_GROUP_LEAVE = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_LEAVE_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_LEAVE_GROUP_NOT_FOUND': 1,
#
# Message length exceeded 'TOX_GROUP_MAX_PART_LENGTH.
#
'TOX_ERR_GROUP_LEAVE_TOO_LONG': 2,
#
# The parting packet failed to send.
#
'TOX_ERR_GROUP_LEAVE_FAIL_SEND': 3,
#
# The group chat instance failed to be deleted. This may occur due to memory related errors.
#
'TOX_ERR_GROUP_LEAVE_DELETE_FAIL': 4,
}
TOX_ERR_GROUP_SELF_QUERY = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_SELF_QUERY_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_SELF_QUERY_GROUP_NOT_FOUND': 1,
}
TOX_ERR_GROUP_SELF_NAME_SET = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_SELF_NAME_SET_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_SELF_NAME_SET_GROUP_NOT_FOUND': 1,
#
# Name length exceeded 'TOX_MAX_NAME_LENGTH.
#
'TOX_ERR_GROUP_SELF_NAME_SET_TOO_LONG': 2,
#
# The length given to the set function is zero or name is a NULL pointer.
#
'TOX_ERR_GROUP_SELF_NAME_SET_INVALID': 3,
#
# The name is already taken by another peer in the group.
#
'TOX_ERR_GROUP_SELF_NAME_SET_TAKEN': 4,
#
# The packet failed to send.
#
'TOX_ERR_GROUP_SELF_NAME_SET_FAIL_SEND': 5
}
TOX_ERR_GROUP_SELF_STATUS_SET = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_SELF_STATUS_SET_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_SELF_STATUS_SET_GROUP_NOT_FOUND': 1,
#
# An invalid type was passed to the set function.
#
'TOX_ERR_GROUP_SELF_STATUS_SET_INVALID': 2,
#
# The packet failed to send.
#
'TOX_ERR_GROUP_SELF_STATUS_SET_FAIL_SEND': 3
}
TOX_ERR_GROUP_PEER_QUERY = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_PEER_QUERY_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_PEER_QUERY_GROUP_NOT_FOUND': 1,
#
# The ID passed did not designate a valid peer.
#
'TOX_ERR_GROUP_PEER_QUERY_PEER_NOT_FOUND': 2
}
TOX_ERR_GROUP_STATE_QUERIES = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_STATE_QUERIES_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_STATE_QUERIES_GROUP_NOT_FOUND': 1
}
TOX_ERR_GROUP_TOPIC_SET = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_TOPIC_SET_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_TOPIC_SET_GROUP_NOT_FOUND': 1,
#
# Topic length exceeded 'TOX_GROUP_MAX_TOPIC_LENGTH.
#
'TOX_ERR_GROUP_TOPIC_SET_TOO_LONG': 2,
#
# The caller does not have the required permissions to set the topic.
#
'TOX_ERR_GROUP_TOPIC_SET_PERMISSIONS': 3,
#
# The packet could not be created. This error is usually related to cryptographic signing.
#
'TOX_ERR_GROUP_TOPIC_SET_FAIL_CREATE': 4,
#
# The packet failed to send.
#
'TOX_ERR_GROUP_TOPIC_SET_FAIL_SEND': 5
}
TOX_ERR_GROUP_SEND_MESSAGE = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_SEND_MESSAGE_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_SEND_MESSAGE_GROUP_NOT_FOUND': 1,
#
# Message length exceeded 'TOX_MAX_MESSAGE_LENGTH.
#
'TOX_ERR_GROUP_SEND_MESSAGE_TOO_LONG': 2,
#
# The message pointer is null or length is zero.
#
'TOX_ERR_GROUP_SEND_MESSAGE_EMPTY': 3,
#
# The message type is invalid.
#
'TOX_ERR_GROUP_SEND_MESSAGE_BAD_TYPE': 4,
#
# The caller does not have the required permissions to send group messages.
#
'TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS': 5,
#
# Packet failed to send.
#
'TOX_ERR_GROUP_SEND_MESSAGE_FAIL_SEND': 6
}
TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_GROUP_NOT_FOUND': 1,
#
# The ID passed did not designate a valid peer.
#
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_PEER_NOT_FOUND': 2,
#
# Message length exceeded 'TOX_MAX_MESSAGE_LENGTH.
#
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_TOO_LONG': 3,
#
# The message pointer is null or length is zero.
#
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_EMPTY': 4,
#
# The caller does not have the required permissions to send group messages.
#
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_PERMISSIONS': 5,
#
# Packet failed to send.
#
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_FAIL_SEND': 6
}
TOX_ERR_GROUP_SEND_CUSTOM_PACKET = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_GROUP_NOT_FOUND': 1,
#
# Message length exceeded 'TOX_MAX_MESSAGE_LENGTH.
#
'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_TOO_LONG': 2,
#
# The message pointer is null or length is zero.
#
'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_EMPTY': 3,
#
# The caller does not have the required permissions to send group messages.
#
'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_PERMISSIONS': 4
}
TOX_ERR_GROUP_INVITE_FRIEND = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_INVITE_FRIEND_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_INVITE_FRIEND_GROUP_NOT_FOUND': 1,
#
# The friend number passed did not designate a valid friend.
#
'TOX_ERR_GROUP_INVITE_FRIEND_FRIEND_NOT_FOUND': 2,
#
# Creation of the invite packet failed. This indicates a network related error.
#
'TOX_ERR_GROUP_INVITE_FRIEND_INVITE_FAIL': 3,
#
# Packet failed to send.
#
'TOX_ERR_GROUP_INVITE_FRIEND_FAIL_SEND': 4
}
TOX_ERR_GROUP_INVITE_ACCEPT = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_INVITE_ACCEPT_OK': 0,
#
# The invite data is not in the expected format.
#
'TOX_ERR_GROUP_INVITE_ACCEPT_BAD_INVITE': 1,
#
# The group instance failed to initialize.
#
'TOX_ERR_GROUP_INVITE_ACCEPT_INIT_FAILED': 2,
#
# Password length exceeded 'TOX_GROUP_MAX_PASSWORD_SIZE.
#
'TOX_ERR_GROUP_INVITE_ACCEPT_TOO_LONG': 3
}
TOX_GROUP_JOIN_FAIL = {
#
# You are using the same nickname as someone who is already in the group.
#
'TOX_GROUP_JOIN_FAIL_NAME_TAKEN': 0,
#
# The group peer limit has been reached.
#
'TOX_GROUP_JOIN_FAIL_PEER_LIMIT': 1,
#
# You have supplied an invalid password.
#
'TOX_GROUP_JOIN_FAIL_INVALID_PASSWORD': 2,
#
# The join attempt failed due to an unspecified error. This often occurs when the group is
# not found in the DHT.
#
'TOX_GROUP_JOIN_FAIL_UNKNOWN': 3
}
TOX_ERR_GROUP_FOUNDER_SET_PASSWORD = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_GROUP_NOT_FOUND': 1,
#
# The caller does not have the required permissions to set the password.
#
'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_PERMISSIONS': 2,
#
# Password length exceeded 'TOX_GROUP_MAX_PASSWORD_SIZE.
#
'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_TOO_LONG': 3,
#
# The packet failed to send.
#
'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_FAIL_SEND': 4
}
TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_GROUP_NOT_FOUND': 1,
#
# 'TOX_GROUP_PRIVACY_STATE is an invalid type.
#
'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_INVALID': 2,
#
# The caller does not have the required permissions to set the privacy state.
#
'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_PERMISSIONS': 3,
#
# The privacy state could not be set. This may occur due to an error related to
# cryptographic signing of the new shared state.
#
'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_FAIL_SET': 4,
#
# The packet failed to send.
#
'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_FAIL_SEND': 5
}
TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_GROUP_NOT_FOUND': 1,
#
# The caller does not have the required permissions to set the peer limit.
#
'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_PERMISSIONS': 2,
#
# The peer limit could not be set. This may occur due to an error related to
# cryptographic signing of the new shared state.
#
'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_FAIL_SET': 3,
#
# The packet failed to send.
#
'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_FAIL_SEND': 4
}
TOX_ERR_GROUP_TOGGLE_IGNORE = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_TOGGLE_IGNORE_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_TOGGLE_IGNORE_GROUP_NOT_FOUND': 1,
#
# The ID passed did not designate a valid peer.
#
'TOX_ERR_GROUP_TOGGLE_IGNORE_PEER_NOT_FOUND': 2
}
TOX_ERR_GROUP_MOD_SET_ROLE = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_MOD_SET_ROLE_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_MOD_SET_ROLE_GROUP_NOT_FOUND': 1,
#
# The ID passed did not designate a valid peer. Note: you cannot set your own role.
#
'TOX_ERR_GROUP_MOD_SET_ROLE_PEER_NOT_FOUND': 2,
#
# The caller does not have the required permissions for this action.
#
'TOX_ERR_GROUP_MOD_SET_ROLE_PERMISSIONS': 3,
#
# The role assignment is invalid. This will occur if you try to set a peer's role to
# the role they already have.
#
'TOX_ERR_GROUP_MOD_SET_ROLE_ASSIGNMENT': 4,
#
# The role was not successfully set. This may occur if something goes wrong with role setting': ,
# or if the packet fails to send.
#
'TOX_ERR_GROUP_MOD_SET_ROLE_FAIL_ACTION': 5
}
TOX_ERR_GROUP_MOD_REMOVE_PEER = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_MOD_REMOVE_PEER_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_MOD_REMOVE_PEER_GROUP_NOT_FOUND': 1,
#
# The ID passed did not designate a valid peer.
#
'TOX_ERR_GROUP_MOD_REMOVE_PEER_PEER_NOT_FOUND': 2,
#
# The caller does not have the required permissions for this action.
#
'TOX_ERR_GROUP_MOD_REMOVE_PEER_PERMISSIONS': 3,
#
# The peer could not be removed from the group.
#
# If a ban was set': , this error indicates that the ban entry could not be created.
# This is usually due to the peer's IP address already occurring in the ban list. It may also
# be due to the entry containing invalid peer information': , or a failure to cryptographically
# authenticate the entry.
#
'TOX_ERR_GROUP_MOD_REMOVE_PEER_FAIL_ACTION': 4,
#
# The packet failed to send.
#
'TOX_ERR_GROUP_MOD_REMOVE_PEER_FAIL_SEND': 5
}
TOX_ERR_GROUP_MOD_REMOVE_BAN = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_MOD_REMOVE_BAN_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_MOD_REMOVE_BAN_GROUP_NOT_FOUND': 1,
#
# The caller does not have the required permissions for this action.
#
'TOX_ERR_GROUP_MOD_REMOVE_BAN_PERMISSIONS': 2,
#
# The ban entry could not be removed. This may occur if ban_id does not designate
# a valid ban entry.
#
'TOX_ERR_GROUP_MOD_REMOVE_BAN_FAIL_ACTION': 3,
#
# The packet failed to send.
#
'TOX_ERR_GROUP_MOD_REMOVE_BAN_FAIL_SEND': 4
}
TOX_GROUP_MOD_EVENT = {
#
# A peer has been kicked from the group.
#
'TOX_GROUP_MOD_EVENT_KICK': 0,
#
# A peer has been banned from the group.
#
'TOX_GROUP_MOD_EVENT_BAN': 1,
#
# A peer as been given the observer role.
#
'TOX_GROUP_MOD_EVENT_OBSERVER': 2,
#
# A peer has been given the user role.
#
'TOX_GROUP_MOD_EVENT_USER': 3,
#
# A peer has been given the moderator role.
#
'TOX_GROUP_MOD_EVENT_MODERATOR': 4,
}
TOX_ERR_GROUP_BAN_QUERY = {
#
# The function returned successfully.
#
'TOX_ERR_GROUP_BAN_QUERY_OK': 0,
#
# The group number passed did not designate a valid group.
#
'TOX_ERR_GROUP_BAN_QUERY_GROUP_NOT_FOUND': 1,
#
# The ban_id does not designate a valid ban list entry.
#
'TOX_ERR_GROUP_BAN_QUERY_BAD_ID': 2,
}
TOX_PUBLIC_KEY_SIZE = 32 TOX_PUBLIC_KEY_SIZE = 32
TOX_ADDRESS_SIZE = TOX_PUBLIC_KEY_SIZE + 6 TOX_ADDRESS_SIZE = TOX_PUBLIC_KEY_SIZE + 6
@ -196,6 +919,18 @@ TOX_MAX_FRIEND_REQUEST_LENGTH = 1016
TOX_MAX_MESSAGE_LENGTH = 1372 TOX_MAX_MESSAGE_LENGTH = 1372
TOX_GROUP_MAX_TOPIC_LENGTH = 512
TOX_GROUP_MAX_PART_LENGTH = 128
TOX_GROUP_MAX_GROUP_NAME_LENGTH = 48
TOX_GROUP_MAX_PASSWORD_SIZE = 32
TOX_GROUP_CHAT_ID_SIZE = 32
TOX_GROUP_PEER_PUBLIC_KEY_SIZE = 32
TOX_MAX_NAME_LENGTH = 128 TOX_MAX_NAME_LENGTH = 128
TOX_MAX_STATUS_MESSAGE_LENGTH = 1007 TOX_MAX_STATUS_MESSAGE_LENGTH = 1007

View File

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

View File

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

Binary file not shown.

View File

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

View File

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