Compare commits
3 Commits
0a54012cf5
...
6f0c1a444e
Author | SHA1 | Date | |
---|---|---|---|
6f0c1a444e | |||
b51ec9bd71 | |||
fda07698db |
13
build/Dockerfile
Normal file
13
build/Dockerfile
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
FROM ubuntu:16.04
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install build-essential libtool autotools-dev automake checkinstall cmake check git yasm libsodium-dev libopus-dev libvpx-dev pkg-config -y && \
|
||||||
|
git clone https://github.com/ingvar1995/toxcore.git --branch=ngc_rebase && \
|
||||||
|
cd toxcore && mkdir _build && cd _build && \
|
||||||
|
cmake .. && make && make install
|
||||||
|
|
||||||
|
RUN apt-get install portaudio19-dev python3-pyqt5 python3-pyaudio python3-pip -y && \
|
||||||
|
pip3 install numpy pydenticon opencv-python pyinstaller
|
||||||
|
|
||||||
|
RUN useradd -ms /bin/bash toxygen
|
||||||
|
USER toxygen
|
33
build/build.sh
Normal file
33
build/build.sh
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
cd ~
|
||||||
|
git clone https://github.com/toxygen-project/toxygen.git --branch=next_gen
|
||||||
|
cd toxygen/toxygen
|
||||||
|
|
||||||
|
pyinstaller --windowed --icon=images/icon.ico main.py
|
||||||
|
|
||||||
|
cp -r styles dist/main/
|
||||||
|
find . -type f ! -name '*.qss' -delete
|
||||||
|
cp -r plugins dist/main/
|
||||||
|
mkdir -p dist/main/ui/views
|
||||||
|
cp -r ui/views dist/main/ui/
|
||||||
|
cp -r sounds dist/main/
|
||||||
|
cp -r smileys dist/main/
|
||||||
|
cp -r stickers dist/main/
|
||||||
|
cp -r bootstrap dist/main/
|
||||||
|
find . -type f ! -name '*.json' -delete
|
||||||
|
cp -r images dist/main/
|
||||||
|
cp -r translations dist/main/
|
||||||
|
find . -name "*.ts" -type f -delete
|
||||||
|
|
||||||
|
cd dist
|
||||||
|
mv main toxygen
|
||||||
|
cd toxygen
|
||||||
|
mv main toxygen
|
||||||
|
wget -O updater https://github.com/toxygen-project/toxygen_updater/releases/download/v0.1/toxygen_updater_linux_64
|
||||||
|
echo "[Paths]" >> qt.conf
|
||||||
|
echo "Prefix = PyQt5/Qt" >> qt.conf
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
tar -zcvf toxygen_linux_64.tar.gz toxygen > /dev/null
|
||||||
|
rm -rf toxygen
|
424
toxygen/app.py
Normal file
424
toxygen/app.py
Normal file
@ -0,0 +1,424 @@
|
|||||||
|
from middleware import threads
|
||||||
|
import middleware.callbacks as callbacks
|
||||||
|
from PyQt5 import QtWidgets, QtGui, QtCore
|
||||||
|
import ui.password_screen as password_screen
|
||||||
|
import updater.updater as updater
|
||||||
|
import os
|
||||||
|
from middleware.tox_factory import tox_factory
|
||||||
|
import wrapper.toxencryptsave as tox_encrypt_save
|
||||||
|
import user_data.toxes
|
||||||
|
from user_data.settings import Settings
|
||||||
|
from ui.login_screen import LoginScreen
|
||||||
|
from user_data.profile_manager import ProfileManager
|
||||||
|
from plugin_support.plugin_support import PluginLoader
|
||||||
|
from ui.main_screen import MainWindow
|
||||||
|
from ui import tray
|
||||||
|
import utils.ui as util_ui
|
||||||
|
import utils.util as util
|
||||||
|
from contacts.profile import Profile
|
||||||
|
from file_transfers.file_transfers_handler import FileTransfersHandler
|
||||||
|
from contacts.contact_provider import ContactProvider
|
||||||
|
from contacts.friend_factory import FriendFactory
|
||||||
|
from contacts.group_factory import GroupFactory
|
||||||
|
from contacts.contacts_manager import ContactsManager
|
||||||
|
from av.calls_manager import CallsManager
|
||||||
|
from history.database import Database
|
||||||
|
from ui.widgets_factory import WidgetsFactory
|
||||||
|
from smileys.smileys import SmileyLoader
|
||||||
|
from ui.items_factories import MessagesItemsFactory, ContactItemsFactory
|
||||||
|
from messenger.messenger import Messenger
|
||||||
|
from network.tox_dns import ToxDns
|
||||||
|
from history.history import History
|
||||||
|
from file_transfers.file_transfers_messages_service import FileTransfersMessagesService
|
||||||
|
from groups.groups_service import GroupsService
|
||||||
|
from ui.create_profile_screen import CreateProfileScreen
|
||||||
|
from common.provider import Provider
|
||||||
|
from contacts.group_peer_factory import GroupPeerFactory
|
||||||
|
from user_data.backup_service import BackupService
|
||||||
|
import styles.style # TODO: dynamic loading
|
||||||
|
|
||||||
|
|
||||||
|
class App:
|
||||||
|
|
||||||
|
def __init__(self, version, path_to_profile=None, uri=None):
|
||||||
|
self._version = version
|
||||||
|
self._app = self._settings = self._profile_manager = self._plugin_loader = self._messenger = None
|
||||||
|
self._tox = self._ms = self._init = self._main_loop = self._av_loop = None
|
||||||
|
self._uri = self._toxes = self._tray = self._file_transfer_handler = self._contacts_provider = None
|
||||||
|
self._friend_factory = self._calls_manager = self._contacts_manager = self._smiley_loader = None
|
||||||
|
self._group_peer_factory = self._tox_dns = self._backup_service = None
|
||||||
|
self._group_factory = self._groups_service = self._profile = None
|
||||||
|
if uri is not None and uri.startswith('tox:'):
|
||||||
|
self._uri = uri[4:]
|
||||||
|
self._path = path_to_profile
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Public methods
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def main(self):
|
||||||
|
"""
|
||||||
|
Main function of app. loads login screen if needed and starts main screen
|
||||||
|
"""
|
||||||
|
self._app = QtWidgets.QApplication([])
|
||||||
|
self._load_icon()
|
||||||
|
|
||||||
|
if util.get_platform() == 'Linux':
|
||||||
|
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
|
||||||
|
|
||||||
|
self._load_base_style()
|
||||||
|
|
||||||
|
if not self._select_and_load_profile():
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._try_to_update():
|
||||||
|
return
|
||||||
|
|
||||||
|
self._load_app_styles()
|
||||||
|
self._load_app_translations()
|
||||||
|
|
||||||
|
self._create_dependencies()
|
||||||
|
self._start_threads()
|
||||||
|
|
||||||
|
if self._uri is not None:
|
||||||
|
self._ms.add_contact(self._uri)
|
||||||
|
|
||||||
|
self._app.lastWindowClosed.connect(self._app.quit)
|
||||||
|
|
||||||
|
self._execute_app()
|
||||||
|
|
||||||
|
self._stop_app()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# App executing
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _execute_app(self):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
self._app.exec_()
|
||||||
|
except Exception as ex:
|
||||||
|
util.log('Unhandled exception: ' + str(ex))
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
def _stop_app(self):
|
||||||
|
self._plugin_loader.stop()
|
||||||
|
self._stop_threads()
|
||||||
|
self._file_transfer_handler.stop()
|
||||||
|
self._tray.hide()
|
||||||
|
self._save_profile()
|
||||||
|
self._settings.close()
|
||||||
|
self._kill_toxav()
|
||||||
|
self._kill_tox()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# App loading
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _load_base_style(self):
|
||||||
|
with open(util.join_path(util.get_styles_directory(), 'dark_style.qss')) as fl:
|
||||||
|
style = fl.read()
|
||||||
|
self._app.setStyleSheet(style)
|
||||||
|
|
||||||
|
def _load_app_styles(self):
|
||||||
|
# application color scheme
|
||||||
|
if self._settings['theme'] == 'dark':
|
||||||
|
return
|
||||||
|
for theme in self._settings.built_in_themes().keys():
|
||||||
|
if self._settings['theme'] != theme:
|
||||||
|
continue
|
||||||
|
theme_path = self._settings.built_in_themes()[theme]
|
||||||
|
file_path = util.join_path(util.get_styles_directory(), theme_path)
|
||||||
|
with open(file_path) as fl:
|
||||||
|
style = fl.read()
|
||||||
|
self._app.setStyleSheet(style)
|
||||||
|
break
|
||||||
|
|
||||||
|
def _load_login_screen_translations(self):
|
||||||
|
current_language, supported_languages = self._get_languages()
|
||||||
|
if current_language not in supported_languages:
|
||||||
|
return
|
||||||
|
lang_path = supported_languages[current_language]
|
||||||
|
translator = QtCore.QTranslator()
|
||||||
|
translator.load(util.get_translations_directory() + lang_path)
|
||||||
|
self._app.installTranslator(translator)
|
||||||
|
self._app.translator = translator
|
||||||
|
|
||||||
|
def _load_icon(self):
|
||||||
|
icon_file = os.path.join(util.get_images_directory(), 'icon.png')
|
||||||
|
self._app.setWindowIcon(QtGui.QIcon(icon_file))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_languages():
|
||||||
|
current_locale = QtCore.QLocale()
|
||||||
|
curr_language = current_locale.languageToString(current_locale.language())
|
||||||
|
supported_languages = Settings.supported_languages()
|
||||||
|
|
||||||
|
return curr_language, supported_languages
|
||||||
|
|
||||||
|
def _load_app_translations(self):
|
||||||
|
lang = Settings.supported_languages()[self._settings['language']]
|
||||||
|
translator = QtCore.QTranslator()
|
||||||
|
translator.load(os.path.join(util.get_translations_directory(), lang))
|
||||||
|
self._app.installTranslator(translator)
|
||||||
|
self._app.translator = translator
|
||||||
|
|
||||||
|
def _select_and_load_profile(self):
|
||||||
|
encrypt_save = tox_encrypt_save.ToxEncryptSave()
|
||||||
|
self._toxes = user_data.toxes.ToxES(encrypt_save)
|
||||||
|
|
||||||
|
if self._path is not None: # toxygen was started with path to profile
|
||||||
|
self._load_existing_profile(self._path)
|
||||||
|
else:
|
||||||
|
auto_profile = Settings.get_auto_profile()
|
||||||
|
if auto_profile is None: # no default profile
|
||||||
|
result = self._select_profile()
|
||||||
|
if result is None:
|
||||||
|
return False
|
||||||
|
if result.is_new_profile(): # create new profile
|
||||||
|
if not self._create_new_profile(result.profile_path):
|
||||||
|
return False
|
||||||
|
else: # load existing profile
|
||||||
|
self._load_existing_profile(result.profile_path)
|
||||||
|
self._path = result.profile_path
|
||||||
|
else: # default profile
|
||||||
|
self._path = auto_profile
|
||||||
|
self._load_existing_profile(auto_profile)
|
||||||
|
|
||||||
|
if Settings.is_active_profile(self._path): # profile is in use
|
||||||
|
profile_name = util.get_profile_name_from_path(self._path)
|
||||||
|
title = util_ui.tr('Profile {}').format(profile_name)
|
||||||
|
text = util_ui.tr(
|
||||||
|
'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?')
|
||||||
|
reply = util_ui.question(text, title)
|
||||||
|
if not reply:
|
||||||
|
return False
|
||||||
|
|
||||||
|
self._settings.set_active_profile()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Threads
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _start_threads(self, initial_start=True):
|
||||||
|
# init thread
|
||||||
|
self._init = threads.InitThread(self._tox, self._plugin_loader, self._settings, initial_start)
|
||||||
|
self._init.start()
|
||||||
|
|
||||||
|
# starting threads for tox iterate and toxav iterate
|
||||||
|
self._main_loop = threads.ToxIterateThread(self._tox)
|
||||||
|
self._main_loop.start()
|
||||||
|
self._av_loop = threads.ToxAVIterateThread(self._tox.AV)
|
||||||
|
self._av_loop.start()
|
||||||
|
|
||||||
|
if initial_start:
|
||||||
|
threads.start_file_transfer_thread()
|
||||||
|
|
||||||
|
def _stop_threads(self, is_app_closing=True):
|
||||||
|
self._init.stop_thread()
|
||||||
|
|
||||||
|
self._av_loop.stop_thread()
|
||||||
|
self._main_loop.stop_thread()
|
||||||
|
|
||||||
|
if is_app_closing:
|
||||||
|
threads.stop_file_transfer_thread()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Profiles
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _select_profile(self):
|
||||||
|
self._load_login_screen_translations()
|
||||||
|
ls = LoginScreen()
|
||||||
|
profiles = ProfileManager.find_profiles()
|
||||||
|
ls.update_select(profiles)
|
||||||
|
ls.show()
|
||||||
|
self._app.exec_()
|
||||||
|
|
||||||
|
return ls.result
|
||||||
|
|
||||||
|
def _load_existing_profile(self, profile_path):
|
||||||
|
self._profile_manager = ProfileManager(self._toxes, profile_path)
|
||||||
|
data = self._profile_manager.open_profile()
|
||||||
|
if self._toxes.is_data_encrypted(data):
|
||||||
|
data = self._enter_password(data)
|
||||||
|
self._settings = Settings(self._toxes, profile_path.replace('.tox', '.json'))
|
||||||
|
self._tox = self._create_tox(data)
|
||||||
|
|
||||||
|
def _create_new_profile(self, profile_name):
|
||||||
|
result = self._get_create_profile_screen_result()
|
||||||
|
if result is None:
|
||||||
|
return False
|
||||||
|
if result.save_into_default_folder:
|
||||||
|
profile_path = util.join_path(Settings.get_default_path(), profile_name + '.tox')
|
||||||
|
else:
|
||||||
|
profile_path = util.join_path(util.curr_directory(__file__), profile_name + '.tox')
|
||||||
|
if os.path.isfile(profile_path):
|
||||||
|
util_ui.message_box(util_ui.tr('Profile with this name already exists'),
|
||||||
|
util_ui.tr('Error'))
|
||||||
|
return False
|
||||||
|
name = profile_name or 'toxygen_user'
|
||||||
|
self._tox = tox_factory()
|
||||||
|
self._tox.self_set_name(name if name else 'Toxygen User')
|
||||||
|
self._tox.self_set_status_message('Toxing on Toxygen')
|
||||||
|
self._path = profile_path
|
||||||
|
if result.password:
|
||||||
|
self._toxes.set_password(result.password)
|
||||||
|
self._settings = Settings(self._toxes, self._path.replace('.tox', '.json'))
|
||||||
|
self._profile_manager = ProfileManager(self._toxes, profile_path)
|
||||||
|
try:
|
||||||
|
self._save_profile()
|
||||||
|
except Exception as ex:
|
||||||
|
print(ex)
|
||||||
|
util.log('Profile creation exception: ' + str(ex))
|
||||||
|
text = util_ui.tr('Profile saving error! Does Toxygen have permission to write to this directory?')
|
||||||
|
util_ui.message_box(text, util_ui.tr('Error'))
|
||||||
|
|
||||||
|
return False
|
||||||
|
current_language, supported_languages = self._get_languages()
|
||||||
|
if current_language in supported_languages:
|
||||||
|
self._settings['language'] = current_language
|
||||||
|
self._settings.save()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _get_create_profile_screen_result(self):
|
||||||
|
cps = CreateProfileScreen()
|
||||||
|
cps.show()
|
||||||
|
self._app.exec_()
|
||||||
|
|
||||||
|
return cps.result
|
||||||
|
|
||||||
|
def _save_profile(self, data=None):
|
||||||
|
data = data or self._tox.get_savedata()
|
||||||
|
self._profile_manager.save_profile(data)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Other private methods
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _enter_password(self, data):
|
||||||
|
"""
|
||||||
|
Show password screen
|
||||||
|
"""
|
||||||
|
p = password_screen.PasswordScreen(self._toxes, data)
|
||||||
|
p.show()
|
||||||
|
self._app.lastWindowClosed.connect(self._app.quit)
|
||||||
|
self._app.exec_()
|
||||||
|
if p.result is not None:
|
||||||
|
return p.result
|
||||||
|
self._force_exit()
|
||||||
|
|
||||||
|
def _reset(self):
|
||||||
|
"""
|
||||||
|
Create new tox instance (new network settings)
|
||||||
|
:return: tox instance
|
||||||
|
"""
|
||||||
|
self._contacts_manager.reset_contacts_statuses()
|
||||||
|
self._stop_threads(False)
|
||||||
|
data = self._tox.get_savedata()
|
||||||
|
self._save_profile(data)
|
||||||
|
self._kill_toxav()
|
||||||
|
self._kill_tox()
|
||||||
|
# create new tox instance
|
||||||
|
self._tox = self._create_tox(data)
|
||||||
|
self._start_threads(False)
|
||||||
|
|
||||||
|
tox_savers = [self._friend_factory, self._group_factory, self._plugin_loader, self._contacts_manager,
|
||||||
|
self._contacts_provider, self._messenger, self._file_transfer_handler, self._groups_service,
|
||||||
|
self._profile]
|
||||||
|
for tox_saver in tox_savers:
|
||||||
|
tox_saver.set_tox(self._tox)
|
||||||
|
|
||||||
|
self._calls_manager.set_toxav(self._tox.AV)
|
||||||
|
self._contacts_manager.update_friends_numbers()
|
||||||
|
self._contacts_manager.update_groups_lists()
|
||||||
|
self._contacts_manager.update_groups_numbers()
|
||||||
|
|
||||||
|
self._init_callbacks()
|
||||||
|
|
||||||
|
def _create_dependencies(self):
|
||||||
|
self._backup_service = BackupService(self._settings, self._profile_manager)
|
||||||
|
self._smiley_loader = SmileyLoader(self._settings)
|
||||||
|
self._tox_dns = ToxDns(self._settings)
|
||||||
|
self._ms = MainWindow(self._settings, self._tray)
|
||||||
|
db = Database(self._path.replace('.tox', '.db'), self._toxes)
|
||||||
|
|
||||||
|
contact_items_factory = ContactItemsFactory(self._settings, self._ms)
|
||||||
|
self._friend_factory = FriendFactory(self._profile_manager, self._settings,
|
||||||
|
self._tox, db, contact_items_factory)
|
||||||
|
self._group_factory = GroupFactory(self._profile_manager, self._settings, self._tox, db, contact_items_factory)
|
||||||
|
self._group_peer_factory = GroupPeerFactory(self._tox, self._profile_manager, db, contact_items_factory)
|
||||||
|
self._contacts_provider = ContactProvider(self._tox, self._friend_factory, self._group_factory,
|
||||||
|
self._group_peer_factory)
|
||||||
|
self._profile = Profile(self._profile_manager, self._tox, self._ms, self._contacts_provider, self._reset)
|
||||||
|
self._init_profile()
|
||||||
|
self._plugin_loader = PluginLoader(self._settings, self)
|
||||||
|
history = None
|
||||||
|
messages_items_factory = MessagesItemsFactory(self._settings, self._plugin_loader, self._smiley_loader,
|
||||||
|
self._ms, lambda m: history.delete_message(m))
|
||||||
|
history = History(self._contacts_provider, db, self._settings, self._ms, messages_items_factory)
|
||||||
|
self._contacts_manager = ContactsManager(self._tox, self._settings, self._ms, self._profile_manager,
|
||||||
|
self._contacts_provider, history, self._tox_dns,
|
||||||
|
messages_items_factory)
|
||||||
|
history.set_contacts_manager(self._contacts_manager)
|
||||||
|
self._calls_manager = CallsManager(self._tox.AV, self._settings, self._ms, self._contacts_manager)
|
||||||
|
self._messenger = Messenger(self._tox, self._plugin_loader, self._ms, self._contacts_manager,
|
||||||
|
self._contacts_provider, messages_items_factory, self._profile,
|
||||||
|
self._calls_manager)
|
||||||
|
file_transfers_message_service = FileTransfersMessagesService(self._contacts_manager, messages_items_factory,
|
||||||
|
self._profile, self._ms)
|
||||||
|
self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider,
|
||||||
|
file_transfers_message_service, self._profile)
|
||||||
|
messages_items_factory.set_file_transfers_handler(self._file_transfer_handler)
|
||||||
|
widgets_factory = None
|
||||||
|
widgets_factory_provider = Provider(lambda: widgets_factory)
|
||||||
|
self._groups_service = GroupsService(self._tox, self._contacts_manager, self._contacts_provider, self._ms,
|
||||||
|
widgets_factory_provider)
|
||||||
|
widgets_factory = WidgetsFactory(self._settings, self._profile, self._profile_manager, self._contacts_manager,
|
||||||
|
self._file_transfer_handler, self._smiley_loader, self._plugin_loader,
|
||||||
|
self._toxes, self._version, self._groups_service, history,
|
||||||
|
self._contacts_provider)
|
||||||
|
self._tray = tray.init_tray(self._profile, self._settings, self._ms, self._toxes)
|
||||||
|
self._ms.set_dependencies(widgets_factory, self._tray, self._contacts_manager, self._messenger, self._profile,
|
||||||
|
self._plugin_loader, self._file_transfer_handler, history, self._calls_manager,
|
||||||
|
self._groups_service, self._toxes)
|
||||||
|
|
||||||
|
self._tray.show()
|
||||||
|
self._ms.show()
|
||||||
|
|
||||||
|
self._init_callbacks()
|
||||||
|
|
||||||
|
def _try_to_update(self):
|
||||||
|
updating = updater.start_update_if_needed(self._version, self._settings)
|
||||||
|
if updating:
|
||||||
|
self._save_profile()
|
||||||
|
self._settings.close()
|
||||||
|
self._kill_toxav()
|
||||||
|
self._kill_tox()
|
||||||
|
return updating
|
||||||
|
|
||||||
|
def _create_tox(self, data):
|
||||||
|
return tox_factory(data, self._settings)
|
||||||
|
|
||||||
|
def _force_exit(self):
|
||||||
|
raise SystemExit()
|
||||||
|
|
||||||
|
def _init_callbacks(self):
|
||||||
|
callbacks.init_callbacks(self._tox, self._profile, self._settings, self._plugin_loader, self._contacts_manager,
|
||||||
|
self._calls_manager, self._file_transfer_handler, self._ms, self._tray,
|
||||||
|
self._messenger, self._groups_service, self._contacts_provider)
|
||||||
|
|
||||||
|
def _init_profile(self):
|
||||||
|
if not self._profile.has_avatar():
|
||||||
|
self._profile.reset_avatar(self._settings['identicons'])
|
||||||
|
|
||||||
|
def _kill_toxav(self):
|
||||||
|
self._calls_manager.set_toxav(None)
|
||||||
|
self._tox.AV.kill()
|
||||||
|
|
||||||
|
def _kill_tox(self):
|
||||||
|
self._tox.kill()
|
0
toxygen/av/__init__.py
Normal file
0
toxygen/av/__init__.py
Normal file
58
toxygen/av/call.py
Normal file
58
toxygen/av/call.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
|
||||||
|
|
||||||
|
class Call:
|
||||||
|
|
||||||
|
def __init__(self, out_audio, out_video, in_audio=False, in_video=False):
|
||||||
|
self._in_audio = in_audio
|
||||||
|
self._in_video = in_video
|
||||||
|
self._out_audio = out_audio
|
||||||
|
self._out_video = out_video
|
||||||
|
self._is_active = False
|
||||||
|
|
||||||
|
def get_is_active(self):
|
||||||
|
return self._is_active
|
||||||
|
|
||||||
|
def set_is_active(self, value):
|
||||||
|
self._is_active = value
|
||||||
|
|
||||||
|
is_active = property(get_is_active, set_is_active)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Audio
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_in_audio(self):
|
||||||
|
return self._in_audio
|
||||||
|
|
||||||
|
def set_in_audio(self, value):
|
||||||
|
self._in_audio = value
|
||||||
|
|
||||||
|
in_audio = property(get_in_audio, set_in_audio)
|
||||||
|
|
||||||
|
def get_out_audio(self):
|
||||||
|
return self._out_audio
|
||||||
|
|
||||||
|
def set_out_audio(self, value):
|
||||||
|
self._out_audio = value
|
||||||
|
|
||||||
|
out_audio = property(get_out_audio, set_out_audio)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Video
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_in_video(self):
|
||||||
|
return self._in_video
|
||||||
|
|
||||||
|
def set_in_video(self, value):
|
||||||
|
self._in_video = value
|
||||||
|
|
||||||
|
in_video = property(get_in_video, set_in_video)
|
||||||
|
|
||||||
|
def get_out_video(self):
|
||||||
|
return self._out_video
|
||||||
|
|
||||||
|
def set_out_video(self, value):
|
||||||
|
self._out_video = value
|
||||||
|
|
||||||
|
out_video = property(get_out_video, set_out_video)
|
@ -1,77 +1,20 @@
|
|||||||
import pyaudio
|
import pyaudio
|
||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
import settings
|
from wrapper.toxav_enums import *
|
||||||
from toxav_enums import *
|
|
||||||
import cv2
|
import cv2
|
||||||
import itertools
|
import itertools
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import screen_sharing
|
from av import screen_sharing
|
||||||
# TODO: play sound until outgoing call will be started or cancelled
|
from av.call import Call
|
||||||
|
import common.tox_save
|
||||||
|
|
||||||
|
|
||||||
class Call:
|
class AV(common.tox_save.ToxAvSave):
|
||||||
|
|
||||||
def __init__(self, out_audio, out_video, in_audio=False, in_video=False):
|
def __init__(self, toxav, settings):
|
||||||
self._in_audio = in_audio
|
super().__init__(toxav)
|
||||||
self._in_video = in_video
|
self._settings = settings
|
||||||
self._out_audio = out_audio
|
|
||||||
self._out_video = out_video
|
|
||||||
self._is_active = False
|
|
||||||
|
|
||||||
def get_is_active(self):
|
|
||||||
return self._is_active
|
|
||||||
|
|
||||||
def set_is_active(self, value):
|
|
||||||
self._is_active = value
|
|
||||||
|
|
||||||
is_active = property(get_is_active, set_is_active)
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Audio
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_in_audio(self):
|
|
||||||
return self._in_audio
|
|
||||||
|
|
||||||
def set_in_audio(self, value):
|
|
||||||
self._in_audio = value
|
|
||||||
|
|
||||||
in_audio = property(get_in_audio, set_in_audio)
|
|
||||||
|
|
||||||
def get_out_audio(self):
|
|
||||||
return self._out_audio
|
|
||||||
|
|
||||||
def set_out_audio(self, value):
|
|
||||||
self._out_audio = value
|
|
||||||
|
|
||||||
out_audio = property(get_out_audio, set_out_audio)
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Video
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_in_video(self):
|
|
||||||
return self._in_video
|
|
||||||
|
|
||||||
def set_in_video(self, value):
|
|
||||||
self._in_video = value
|
|
||||||
|
|
||||||
in_video = property(get_in_video, set_in_video)
|
|
||||||
|
|
||||||
def get_out_video(self):
|
|
||||||
return self._out_video
|
|
||||||
|
|
||||||
def set_out_video(self, value):
|
|
||||||
self._out_video = value
|
|
||||||
|
|
||||||
out_video = property(get_out_video, set_out_video)
|
|
||||||
|
|
||||||
|
|
||||||
class AV:
|
|
||||||
|
|
||||||
def __init__(self, toxav):
|
|
||||||
self._toxav = toxav
|
|
||||||
self._running = True
|
self._running = True
|
||||||
|
|
||||||
self._calls = {} # dict: key - friend number, value - Call instance
|
self._calls = {} # dict: key - friend number, value - Call instance
|
||||||
@ -174,7 +117,7 @@ class AV:
|
|||||||
rate=self._audio_rate,
|
rate=self._audio_rate,
|
||||||
channels=self._audio_channels,
|
channels=self._audio_channels,
|
||||||
input=True,
|
input=True,
|
||||||
input_device_index=settings.Settings.get_instance().audio['input'],
|
input_device_index=self._settings.audio['input'],
|
||||||
frames_per_buffer=self._audio_sample_count * 10)
|
frames_per_buffer=self._audio_sample_count * 10)
|
||||||
|
|
||||||
self._audio_thread = threading.Thread(target=self.send_audio)
|
self._audio_thread = threading.Thread(target=self.send_audio)
|
||||||
@ -203,15 +146,14 @@ class AV:
|
|||||||
return
|
return
|
||||||
|
|
||||||
self._video_running = True
|
self._video_running = True
|
||||||
s = settings.Settings.get_instance()
|
|
||||||
self._video_width = s.video['width']
|
self._video_width = s.video['width']
|
||||||
self._video_height = s.video['height']
|
self._video_height = s.video['height']
|
||||||
|
|
||||||
if s.video['device'] == -1:
|
if s.video['device'] == -1:
|
||||||
self._video = screen_sharing.DesktopGrabber(s.video['x'], s.video['y'],
|
self._video = screen_sharing.DesktopGrabber(self._settings.video['x'], self._settings.video['y'],
|
||||||
s.video['width'], s.video['height'])
|
self._settings.video['width'], self._settings.video['height'])
|
||||||
else:
|
else:
|
||||||
self._video = cv2.VideoCapture(s.video['device'])
|
self._video = cv2.VideoCapture(self._settings.video['device'])
|
||||||
self._video.set(cv2.CAP_PROP_FPS, 25)
|
self._video.set(cv2.CAP_PROP_FPS, 25)
|
||||||
self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width)
|
self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width)
|
||||||
self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._video_height)
|
self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._video_height)
|
||||||
@ -241,7 +183,7 @@ class AV:
|
|||||||
self._out_stream = self._audio.open(format=pyaudio.paInt16,
|
self._out_stream = self._audio.open(format=pyaudio.paInt16,
|
||||||
channels=channels_count,
|
channels=channels_count,
|
||||||
rate=rate,
|
rate=rate,
|
||||||
output_device_index=settings.Settings.get_instance().audio['output'],
|
output_device_index=self._settings.audio['output'],
|
||||||
output=True)
|
output=True)
|
||||||
self._out_stream.write(samples)
|
self._out_stream.write(samples)
|
||||||
|
|
116
toxygen/av/calls_manager.py
Normal file
116
toxygen/av/calls_manager.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import threading
|
||||||
|
import cv2
|
||||||
|
import av.calls
|
||||||
|
from messenger.messages import *
|
||||||
|
from ui import av_widgets
|
||||||
|
import common.event as event
|
||||||
|
|
||||||
|
|
||||||
|
class CallsManager:
|
||||||
|
|
||||||
|
def __init__(self, toxav, settings, screen, contacts_manager):
|
||||||
|
self._call = av.calls.AV(toxav, settings) # object with data about calls
|
||||||
|
self._call_widgets = {} # dict of incoming call widgets
|
||||||
|
self._incoming_calls = set()
|
||||||
|
self._settings = settings
|
||||||
|
self._screen = screen
|
||||||
|
self._contacts_manager = contacts_manager
|
||||||
|
self._call_started_event = event.Event() # friend_number, audio, video, is_outgoing
|
||||||
|
self._call_finished_event = event.Event() # friend_number, is_declined
|
||||||
|
|
||||||
|
def set_toxav(self, toxav):
|
||||||
|
self._call.set_toxav(toxav)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Events
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_call_started_event(self):
|
||||||
|
return self._call_started_event
|
||||||
|
|
||||||
|
call_started_event = property(get_call_started_event)
|
||||||
|
|
||||||
|
def get_call_finished_event(self):
|
||||||
|
return self._call_finished_event
|
||||||
|
|
||||||
|
call_finished_event = property(get_call_finished_event)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# AV support
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def call_click(self, audio=True, video=False):
|
||||||
|
"""User clicked audio button in main window"""
|
||||||
|
num = self._contacts_manager.get_active_number()
|
||||||
|
if not self._contacts_manager.is_active_a_friend():
|
||||||
|
return
|
||||||
|
if num not in self._call and self._contacts_manager.is_active_online(): # start call
|
||||||
|
if not self._settings.audio['enabled']:
|
||||||
|
return
|
||||||
|
self._call(num, audio, video)
|
||||||
|
self._screen.active_call()
|
||||||
|
self._call_started_event(num, audio, video, True)
|
||||||
|
elif num in self._call: # finish or cancel call if you call with active friend
|
||||||
|
self.stop_call(num, False)
|
||||||
|
|
||||||
|
def incoming_call(self, audio, video, friend_number):
|
||||||
|
"""
|
||||||
|
Incoming call from friend.
|
||||||
|
"""
|
||||||
|
if not self._settings.audio['enabled']:
|
||||||
|
return
|
||||||
|
friend = self._contacts_manager.get_friend_by_number(friend_number)
|
||||||
|
self._call_started_event(friend_number, audio, video, False)
|
||||||
|
self._incoming_calls.add(friend_number)
|
||||||
|
if friend_number == self._contacts_manager.get_active_number():
|
||||||
|
self._screen.incoming_call()
|
||||||
|
else:
|
||||||
|
friend.actions = True
|
||||||
|
text = util_ui.tr("Incoming video call") if video else util_ui.tr("Incoming audio call")
|
||||||
|
self._call_widgets[friend_number] = self._get_incoming_call_widget(friend_number, text, friend.name)
|
||||||
|
self._call_widgets[friend_number].set_pixmap(friend.get_pixmap())
|
||||||
|
self._call_widgets[friend_number].show()
|
||||||
|
|
||||||
|
def accept_call(self, friend_number, audio, video):
|
||||||
|
"""
|
||||||
|
Accept incoming call with audio or video
|
||||||
|
"""
|
||||||
|
self._call.accept_call(friend_number, audio, video)
|
||||||
|
self._screen.active_call()
|
||||||
|
if friend_number in self._incoming_calls:
|
||||||
|
self._incoming_calls.remove(friend_number)
|
||||||
|
del self._call_widgets[friend_number]
|
||||||
|
|
||||||
|
def stop_call(self, friend_number, by_friend):
|
||||||
|
"""
|
||||||
|
Stop call with friend
|
||||||
|
"""
|
||||||
|
if friend_number in self._incoming_calls:
|
||||||
|
self._incoming_calls.remove(friend_number)
|
||||||
|
is_declined = True
|
||||||
|
else:
|
||||||
|
is_declined = False
|
||||||
|
self._screen.call_finished()
|
||||||
|
is_video = self._call.is_video_call(friend_number)
|
||||||
|
self._call.finish_call(friend_number, by_friend) # finish or decline call
|
||||||
|
if friend_number in self._call_widgets:
|
||||||
|
self._call_widgets[friend_number].close()
|
||||||
|
del self._call_widgets[friend_number]
|
||||||
|
|
||||||
|
def destroy_window():
|
||||||
|
if is_video:
|
||||||
|
cv2.destroyWindow(str(friend_number))
|
||||||
|
|
||||||
|
threading.Timer(2.0, destroy_window).start()
|
||||||
|
self._call_finished_event(friend_number, is_declined)
|
||||||
|
|
||||||
|
def friend_exit(self, friend_number):
|
||||||
|
if friend_number in self._call:
|
||||||
|
self._call.finish_call(friend_number, True)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Private methods
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _get_incoming_call_widget(self, friend_number, text, friend_name):
|
||||||
|
return av_widgets.IncomingCallWidget(self._settings, self, friend_number, text, friend_name)
|
0
toxygen/bootstrap/__init__.py
Normal file
0
toxygen/bootstrap/__init__.py
Normal file
@ -1,11 +1,13 @@
|
|||||||
import random
|
import random
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from util import log, curr_directory
|
from utils.util import *
|
||||||
import settings
|
|
||||||
from PyQt5 import QtNetwork, QtCore
|
from PyQt5 import QtNetwork, QtCore
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_NODES_COUNT = 4
|
||||||
|
|
||||||
|
|
||||||
class Node:
|
class Node:
|
||||||
|
|
||||||
def __init__(self, node):
|
def __init__(self, node):
|
||||||
@ -18,48 +20,42 @@ class Node:
|
|||||||
priority = property(get_priority)
|
priority = property(get_priority)
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
return bytes(self._ip, 'utf-8'), self._port, self._tox_key
|
return self._ip, self._port, self._tox_key
|
||||||
|
|
||||||
|
|
||||||
def generate_nodes():
|
def generate_nodes(nodes_count=DEFAULT_NODES_COUNT):
|
||||||
with open(curr_directory() + '/nodes.json', 'rt') as fl:
|
with open(_get_nodes_path(), 'rt') as fl:
|
||||||
json_nodes = json.loads(fl.read())['nodes']
|
json_nodes = json.loads(fl.read())['nodes']
|
||||||
nodes = map(lambda json_node: Node(json_node), json_nodes)
|
nodes = map(lambda json_node: Node(json_node), json_nodes)
|
||||||
sorted_nodes = sorted(nodes, key=lambda x: x.priority)[-4:]
|
nodes = filter(lambda n: n.priority > 0, nodes)
|
||||||
|
sorted_nodes = sorted(nodes, key=lambda x: x.priority)
|
||||||
|
if nodes_count is not None:
|
||||||
|
sorted_nodes = sorted_nodes[-DEFAULT_NODES_COUNT:]
|
||||||
for node in sorted_nodes:
|
for node in sorted_nodes:
|
||||||
yield node.get_data()
|
yield node.get_data()
|
||||||
|
|
||||||
|
|
||||||
def save_nodes(nodes):
|
def download_nodes_list(settings):
|
||||||
if not nodes:
|
|
||||||
return
|
|
||||||
print('Saving nodes...')
|
|
||||||
with open(curr_directory() + '/nodes.json', 'wb') as fl:
|
|
||||||
fl.write(nodes)
|
|
||||||
|
|
||||||
|
|
||||||
def download_nodes_list():
|
|
||||||
url = 'https://nodes.tox.chat/json'
|
url = 'https://nodes.tox.chat/json'
|
||||||
s = settings.Settings.get_instance()
|
if not settings['download_nodes_list']:
|
||||||
if not s['download_nodes_list']:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if not s['proxy_type']: # no proxy
|
if not settings['proxy_type']: # no proxy
|
||||||
try:
|
try:
|
||||||
req = urllib.request.Request(url)
|
req = urllib.request.Request(url)
|
||||||
req.add_header('Content-Type', 'application/json')
|
req.add_header('Content-Type', 'application/json')
|
||||||
response = urllib.request.urlopen(req)
|
response = urllib.request.urlopen(req)
|
||||||
result = response.read()
|
result = response.read()
|
||||||
save_nodes(result)
|
_save_nodes(result)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
log('TOX nodes loading error: ' + str(ex))
|
log('TOX nodes loading error: ' + str(ex))
|
||||||
else: # proxy
|
else: # proxy
|
||||||
netman = QtNetwork.QNetworkAccessManager()
|
netman = QtNetwork.QNetworkAccessManager()
|
||||||
proxy = QtNetwork.QNetworkProxy()
|
proxy = QtNetwork.QNetworkProxy()
|
||||||
proxy.setType(
|
proxy.setType(
|
||||||
QtNetwork.QNetworkProxy.Socks5Proxy if s['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy)
|
QtNetwork.QNetworkProxy.Socks5Proxy if settings['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy)
|
||||||
proxy.setHostName(s['proxy_host'])
|
proxy.setHostName(settings['proxy_host'])
|
||||||
proxy.setPort(s['proxy_port'])
|
proxy.setPort(settings['proxy_port'])
|
||||||
netman.setProxy(proxy)
|
netman.setProxy(proxy)
|
||||||
try:
|
try:
|
||||||
request = QtNetwork.QNetworkRequest()
|
request = QtNetwork.QNetworkRequest()
|
||||||
@ -70,6 +66,18 @@ def download_nodes_list():
|
|||||||
QtCore.QThread.msleep(1)
|
QtCore.QThread.msleep(1)
|
||||||
QtCore.QCoreApplication.processEvents()
|
QtCore.QCoreApplication.processEvents()
|
||||||
data = bytes(reply.readAll().data())
|
data = bytes(reply.readAll().data())
|
||||||
save_nodes(data)
|
_save_nodes(data)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
log('TOX nodes loading error: ' + str(ex))
|
log('TOX nodes loading error: ' + str(ex))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_nodes_path():
|
||||||
|
return join_path(curr_directory(__file__), 'nodes.json')
|
||||||
|
|
||||||
|
|
||||||
|
def _save_nodes(nodes):
|
||||||
|
if not nodes:
|
||||||
|
return
|
||||||
|
print('Saving nodes...')
|
||||||
|
with open(_get_nodes_path(), 'wb') as fl:
|
||||||
|
fl.write(nodes)
|
1
toxygen/bootstrap/nodes.json
Normal file
1
toxygen/bootstrap/nodes.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"nodes":[{"ipv4":"80.211.19.83","ipv6":"-","port":33445,"public_key":"A2D7BF17C10A12C339B9F4E8DD77DEEE8457D580535A6F0D0F9AF04B8B4C4420","status_udp":true,"status_tcp":true}]}
|
@ -1,469 +0,0 @@
|
|||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
|
||||||
from notifications import *
|
|
||||||
from settings import Settings
|
|
||||||
from profile import Profile
|
|
||||||
from toxcore_enums_and_consts import *
|
|
||||||
from toxav_enums import *
|
|
||||||
from tox import bin_to_string
|
|
||||||
from plugin_support import PluginLoader
|
|
||||||
import queue
|
|
||||||
import threading
|
|
||||||
import util
|
|
||||||
import cv2
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Threads
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
class InvokeEvent(QtCore.QEvent):
|
|
||||||
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
|
|
||||||
|
|
||||||
def __init__(self, fn, *args, **kwargs):
|
|
||||||
QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
|
|
||||||
self.fn = fn
|
|
||||||
self.args = args
|
|
||||||
self.kwargs = kwargs
|
|
||||||
|
|
||||||
|
|
||||||
class Invoker(QtCore.QObject):
|
|
||||||
|
|
||||||
def event(self, event):
|
|
||||||
event.fn(*event.args, **event.kwargs)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
_invoker = Invoker()
|
|
||||||
|
|
||||||
|
|
||||||
def invoke_in_main_thread(fn, *args, **kwargs):
|
|
||||||
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
|
|
||||||
|
|
||||||
|
|
||||||
class FileTransfersThread(threading.Thread):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._queue = queue.Queue()
|
|
||||||
self._timeout = 0.01
|
|
||||||
self._continue = True
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
def execute(self, function, *args, **kwargs):
|
|
||||||
self._queue.put((function, args, kwargs))
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self._continue = False
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
while self._continue:
|
|
||||||
try:
|
|
||||||
function, args, kwargs = self._queue.get(timeout=self._timeout)
|
|
||||||
function(*args, **kwargs)
|
|
||||||
except queue.Empty:
|
|
||||||
pass
|
|
||||||
except queue.Full:
|
|
||||||
util.log('Queue is Full in _thread')
|
|
||||||
except Exception as ex:
|
|
||||||
util.log('Exception in _thread: ' + str(ex))
|
|
||||||
|
|
||||||
|
|
||||||
_thread = FileTransfersThread()
|
|
||||||
|
|
||||||
|
|
||||||
def start():
|
|
||||||
_thread.start()
|
|
||||||
|
|
||||||
|
|
||||||
def stop():
|
|
||||||
_thread.stop()
|
|
||||||
_thread.join()
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - current user
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def self_connection_status(tox_link):
|
|
||||||
"""
|
|
||||||
Current user changed connection status (offline, UDP, TCP)
|
|
||||||
"""
|
|
||||||
def wrapped(tox, connection, user_data):
|
|
||||||
print('Connection status: ', str(connection))
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
if profile.status is None:
|
|
||||||
status = tox_link.self_get_status()
|
|
||||||
invoke_in_main_thread(profile.set_status, status)
|
|
||||||
elif connection == TOX_CONNECTION['NONE']:
|
|
||||||
invoke_in_main_thread(profile.set_status, None)
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - friends
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def friend_status(tox, friend_num, new_status, user_data):
|
|
||||||
"""
|
|
||||||
Check friend's status (none, busy, away)
|
|
||||||
"""
|
|
||||||
print("Friend's #{} status changed!".format(friend_num))
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
friend = profile.get_friend_by_number(friend_num)
|
|
||||||
if friend.status is None and Settings.get_instance()['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
|
||||||
sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
|
|
||||||
invoke_in_main_thread(friend.set_status, new_status)
|
|
||||||
invoke_in_main_thread(QtCore.QTimer.singleShot, 5000, lambda: profile.send_files(friend_num))
|
|
||||||
invoke_in_main_thread(profile.update_filtration)
|
|
||||||
|
|
||||||
|
|
||||||
def friend_connection_status(tox, friend_num, new_status, user_data):
|
|
||||||
"""
|
|
||||||
Check friend's connection status (offline, udp, tcp)
|
|
||||||
"""
|
|
||||||
print("Friend #{} connection status: {}".format(friend_num, new_status))
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
friend = profile.get_friend_by_number(friend_num)
|
|
||||||
if new_status == TOX_CONNECTION['NONE']:
|
|
||||||
invoke_in_main_thread(profile.friend_exit, friend_num)
|
|
||||||
invoke_in_main_thread(profile.update_filtration)
|
|
||||||
if Settings.get_instance()['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
|
||||||
sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
|
|
||||||
elif friend.status is None:
|
|
||||||
invoke_in_main_thread(profile.send_avatar, friend_num)
|
|
||||||
invoke_in_main_thread(PluginLoader.get_instance().friend_online, friend_num)
|
|
||||||
|
|
||||||
|
|
||||||
def friend_name(tox, friend_num, name, size, user_data):
|
|
||||||
"""
|
|
||||||
Friend changed his name
|
|
||||||
"""
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
print('New name friend #' + str(friend_num))
|
|
||||||
invoke_in_main_thread(profile.new_name, friend_num, name)
|
|
||||||
|
|
||||||
|
|
||||||
def friend_status_message(tox, friend_num, status_message, size, user_data):
|
|
||||||
"""
|
|
||||||
:return: function for callback friend_status_message. It updates friend's status message
|
|
||||||
and calls window repaint
|
|
||||||
"""
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
friend = profile.get_friend_by_number(friend_num)
|
|
||||||
invoke_in_main_thread(friend.set_status_message, status_message)
|
|
||||||
print('User #{} has new status'.format(friend_num))
|
|
||||||
invoke_in_main_thread(profile.send_messages, friend_num)
|
|
||||||
if profile.get_active_number() == friend_num:
|
|
||||||
invoke_in_main_thread(profile.set_active)
|
|
||||||
|
|
||||||
|
|
||||||
def friend_message(window, tray):
|
|
||||||
"""
|
|
||||||
New message from friend
|
|
||||||
"""
|
|
||||||
def wrapped(tox, friend_number, message_type, message, size, user_data):
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
settings = Settings.get_instance()
|
|
||||||
message = str(message, 'utf-8')
|
|
||||||
invoke_in_main_thread(profile.new_message, friend_number, message_type, message)
|
|
||||||
if not window.isActiveWindow():
|
|
||||||
friend = profile.get_friend_by_number(friend_number)
|
|
||||||
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
|
|
||||||
invoke_in_main_thread(tray_notification, friend.name, message, tray, window)
|
|
||||||
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
|
||||||
sound_notification(SOUND_NOTIFICATION['MESSAGE'])
|
|
||||||
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png'))
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
|
|
||||||
def friend_request(tox, public_key, message, message_size, user_data):
|
|
||||||
"""
|
|
||||||
Called when user get new friend request
|
|
||||||
"""
|
|
||||||
print('Friend request')
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE])
|
|
||||||
tox_id = bin_to_string(key, TOX_PUBLIC_KEY_SIZE)
|
|
||||||
if tox_id not in Settings.get_instance()['blocked']:
|
|
||||||
invoke_in_main_thread(profile.process_friend_request, tox_id, str(message, 'utf-8'))
|
|
||||||
|
|
||||||
|
|
||||||
def friend_typing(tox, friend_number, typing, user_data):
|
|
||||||
invoke_in_main_thread(Profile.get_instance().friend_typing, friend_number, typing)
|
|
||||||
|
|
||||||
|
|
||||||
def friend_read_receipt(tox, friend_number, message_id, user_data):
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
profile.get_friend_by_number(friend_number).dec_receipt()
|
|
||||||
if friend_number == profile.get_active_number():
|
|
||||||
invoke_in_main_thread(profile.receipt)
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - file transfers
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def tox_file_recv(window, tray):
|
|
||||||
"""
|
|
||||||
New incoming file
|
|
||||||
"""
|
|
||||||
def wrapped(tox, friend_number, file_number, file_type, size, file_name, file_name_size, user_data):
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
settings = Settings.get_instance()
|
|
||||||
if file_type == TOX_FILE_KIND['DATA']:
|
|
||||||
print('File')
|
|
||||||
try:
|
|
||||||
file_name = str(file_name[:file_name_size], 'utf-8')
|
|
||||||
except:
|
|
||||||
file_name = 'toxygen_file'
|
|
||||||
invoke_in_main_thread(profile.incoming_file_transfer,
|
|
||||||
friend_number,
|
|
||||||
file_number,
|
|
||||||
size,
|
|
||||||
file_name)
|
|
||||||
if not window.isActiveWindow():
|
|
||||||
friend = profile.get_friend_by_number(friend_number)
|
|
||||||
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
|
|
||||||
file_from = QtWidgets.QApplication.translate("Callback", "File from")
|
|
||||||
invoke_in_main_thread(tray_notification, file_from + ' ' + friend.name, file_name, tray, window)
|
|
||||||
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
|
||||||
sound_notification(SOUND_NOTIFICATION['FILE_TRANSFER'])
|
|
||||||
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png'))
|
|
||||||
else: # AVATAR
|
|
||||||
print('Avatar')
|
|
||||||
invoke_in_main_thread(profile.incoming_avatar,
|
|
||||||
friend_number,
|
|
||||||
file_number,
|
|
||||||
size)
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
|
|
||||||
def file_recv_chunk(tox, friend_number, file_number, position, chunk, length, user_data):
|
|
||||||
"""
|
|
||||||
Incoming chunk
|
|
||||||
"""
|
|
||||||
_thread.execute(Profile.get_instance().incoming_chunk, friend_number, file_number, position,
|
|
||||||
chunk[:length] if length else None)
|
|
||||||
|
|
||||||
|
|
||||||
def file_chunk_request(tox, friend_number, file_number, position, size, user_data):
|
|
||||||
"""
|
|
||||||
Outgoing chunk
|
|
||||||
"""
|
|
||||||
Profile.get_instance().outgoing_chunk(friend_number, file_number, position, size)
|
|
||||||
|
|
||||||
|
|
||||||
def file_recv_control(tox, friend_number, file_number, file_control, user_data):
|
|
||||||
"""
|
|
||||||
Friend cancelled, paused or resumed file transfer
|
|
||||||
"""
|
|
||||||
if file_control == TOX_FILE_CONTROL['CANCEL']:
|
|
||||||
invoke_in_main_thread(Profile.get_instance().cancel_transfer, friend_number, file_number, True)
|
|
||||||
elif file_control == TOX_FILE_CONTROL['PAUSE']:
|
|
||||||
invoke_in_main_thread(Profile.get_instance().pause_transfer, friend_number, file_number, True)
|
|
||||||
elif file_control == TOX_FILE_CONTROL['RESUME']:
|
|
||||||
invoke_in_main_thread(Profile.get_instance().resume_transfer, friend_number, file_number, True)
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - custom packets
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def lossless_packet(tox, friend_number, data, length, user_data):
|
|
||||||
"""
|
|
||||||
Incoming lossless packet
|
|
||||||
"""
|
|
||||||
data = data[:length]
|
|
||||||
plugin = PluginLoader.get_instance()
|
|
||||||
invoke_in_main_thread(plugin.callback_lossless, friend_number, data)
|
|
||||||
|
|
||||||
|
|
||||||
def lossy_packet(tox, friend_number, data, length, user_data):
|
|
||||||
"""
|
|
||||||
Incoming lossy packet
|
|
||||||
"""
|
|
||||||
data = data[:length]
|
|
||||||
plugin = PluginLoader.get_instance()
|
|
||||||
invoke_in_main_thread(plugin.callback_lossy, friend_number, data)
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - audio
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def call_state(toxav, friend_number, mask, user_data):
|
|
||||||
"""
|
|
||||||
New call state
|
|
||||||
"""
|
|
||||||
print(friend_number, mask)
|
|
||||||
if mask == TOXAV_FRIEND_CALL_STATE['FINISHED'] or mask == TOXAV_FRIEND_CALL_STATE['ERROR']:
|
|
||||||
invoke_in_main_thread(Profile.get_instance().stop_call, friend_number, True)
|
|
||||||
else:
|
|
||||||
Profile.get_instance().call.toxav_call_state_cb(friend_number, mask)
|
|
||||||
|
|
||||||
|
|
||||||
def call(toxav, friend_number, audio, video, user_data):
|
|
||||||
"""
|
|
||||||
Incoming call from friend
|
|
||||||
"""
|
|
||||||
print(friend_number, audio, video)
|
|
||||||
invoke_in_main_thread(Profile.get_instance().incoming_call, audio, video, friend_number)
|
|
||||||
|
|
||||||
|
|
||||||
def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, audio_channels_count, rate, user_data):
|
|
||||||
"""
|
|
||||||
New audio chunk
|
|
||||||
"""
|
|
||||||
Profile.get_instance().call.audio_chunk(
|
|
||||||
bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]),
|
|
||||||
audio_channels_count,
|
|
||||||
rate)
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - video
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, ustride, vstride, user_data):
|
|
||||||
"""
|
|
||||||
Creates yuv frame from y, u, v and shows it using OpenCV
|
|
||||||
For yuv => bgr we need this YUV420 frame:
|
|
||||||
|
|
||||||
width
|
|
||||||
-------------------------
|
|
||||||
| |
|
|
||||||
| Y | height
|
|
||||||
| |
|
|
||||||
-------------------------
|
|
||||||
| | |
|
|
||||||
| U even | U odd | height // 4
|
|
||||||
| | |
|
|
||||||
-------------------------
|
|
||||||
| | |
|
|
||||||
| V even | V odd | height // 4
|
|
||||||
| | |
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
width // 2 width // 2
|
|
||||||
|
|
||||||
It can be created from initial y, u, v using slices
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
y_size = abs(max(width, abs(ystride)))
|
|
||||||
u_size = abs(max(width // 2, abs(ustride)))
|
|
||||||
v_size = abs(max(width // 2, abs(vstride)))
|
|
||||||
|
|
||||||
y = np.asarray(y[:y_size * height], dtype=np.uint8).reshape(height, y_size)
|
|
||||||
u = np.asarray(u[:u_size * height // 2], dtype=np.uint8).reshape(height // 2, u_size)
|
|
||||||
v = np.asarray(v[:v_size * height // 2], dtype=np.uint8).reshape(height // 2, v_size)
|
|
||||||
|
|
||||||
width -= width % 4
|
|
||||||
height -= height % 4
|
|
||||||
|
|
||||||
frame = np.zeros((int(height * 1.5), width), dtype=np.uint8)
|
|
||||||
|
|
||||||
frame[:height, :] = y[:height, :width]
|
|
||||||
frame[height:height * 5 // 4, :width // 2] = u[:height // 2:2, :width // 2]
|
|
||||||
frame[height:height * 5 // 4, width // 2:] = u[1:height // 2:2, :width // 2]
|
|
||||||
|
|
||||||
frame[height * 5 // 4:, :width // 2] = v[:height // 2:2, :width // 2]
|
|
||||||
frame[height * 5 // 4:, width // 2:] = v[1:height // 2:2, :width // 2]
|
|
||||||
|
|
||||||
frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420)
|
|
||||||
|
|
||||||
invoke_in_main_thread(cv2.imshow, str(friend_number), frame)
|
|
||||||
except Exception as ex:
|
|
||||||
print(ex)
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - groups
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def group_invite(tox, friend_number, gc_type, data, length, user_data):
|
|
||||||
invoke_in_main_thread(Profile.get_instance().group_invite, friend_number, gc_type,
|
|
||||||
bytes(data[:length]))
|
|
||||||
|
|
||||||
|
|
||||||
def show_gc_notification(window, tray, message, group_number, peer_number):
|
|
||||||
profile = Profile.get_instance()
|
|
||||||
settings = Settings.get_instance()
|
|
||||||
chat = profile.get_group_by_number(group_number)
|
|
||||||
peer_name = chat.get_peer_name(peer_number)
|
|
||||||
if not window.isActiveWindow() and (profile.name in message or settings['group_notifications']):
|
|
||||||
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
|
|
||||||
invoke_in_main_thread(tray_notification, chat.name + ' ' + peer_name, message, tray, window)
|
|
||||||
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
|
||||||
sound_notification(SOUND_NOTIFICATION['MESSAGE'])
|
|
||||||
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png'))
|
|
||||||
|
|
||||||
|
|
||||||
def group_message(window, tray):
|
|
||||||
def wrapped(tox, group_number, peer_number, message, length, user_data):
|
|
||||||
message = str(message[:length], 'utf-8')
|
|
||||||
invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number,
|
|
||||||
peer_number, TOX_MESSAGE_TYPE['NORMAL'], message)
|
|
||||||
show_gc_notification(window, tray, message, group_number, peer_number)
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
|
|
||||||
def group_action(window, tray):
|
|
||||||
def wrapped(tox, group_number, peer_number, message, length, user_data):
|
|
||||||
message = str(message[:length], 'utf-8')
|
|
||||||
invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number,
|
|
||||||
peer_number, TOX_MESSAGE_TYPE['ACTION'], message)
|
|
||||||
show_gc_notification(window, tray, message, group_number, peer_number)
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
|
|
||||||
def group_title(tox, group_number, peer_number, title, length, user_data):
|
|
||||||
invoke_in_main_thread(Profile.get_instance().new_gc_title, group_number,
|
|
||||||
title[:length])
|
|
||||||
|
|
||||||
|
|
||||||
def group_namelist_change(tox, group_number, peer_number, change, user_data):
|
|
||||||
invoke_in_main_thread(Profile.get_instance().update_gc, group_number)
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Callbacks - initialization
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def init_callbacks(tox, window, tray):
|
|
||||||
"""
|
|
||||||
Initialization of all callbacks.
|
|
||||||
:param tox: tox instance
|
|
||||||
:param window: main window
|
|
||||||
:param tray: tray (for notifications)
|
|
||||||
"""
|
|
||||||
tox.callback_self_connection_status(self_connection_status(tox), 0)
|
|
||||||
|
|
||||||
tox.callback_friend_status(friend_status, 0)
|
|
||||||
tox.callback_friend_message(friend_message(window, tray), 0)
|
|
||||||
tox.callback_friend_connection_status(friend_connection_status, 0)
|
|
||||||
tox.callback_friend_name(friend_name, 0)
|
|
||||||
tox.callback_friend_status_message(friend_status_message, 0)
|
|
||||||
tox.callback_friend_request(friend_request, 0)
|
|
||||||
tox.callback_friend_typing(friend_typing, 0)
|
|
||||||
tox.callback_friend_read_receipt(friend_read_receipt, 0)
|
|
||||||
|
|
||||||
tox.callback_file_recv(tox_file_recv(window, tray), 0)
|
|
||||||
tox.callback_file_recv_chunk(file_recv_chunk, 0)
|
|
||||||
tox.callback_file_chunk_request(file_chunk_request, 0)
|
|
||||||
tox.callback_file_recv_control(file_recv_control, 0)
|
|
||||||
|
|
||||||
toxav = tox.AV
|
|
||||||
toxav.callback_call_state(call_state, 0)
|
|
||||||
toxav.callback_call(call, 0)
|
|
||||||
toxav.callback_audio_receive_frame(callback_audio, 0)
|
|
||||||
toxav.callback_video_receive_frame(video_receive_frame, 0)
|
|
||||||
|
|
||||||
tox.callback_friend_lossless_packet(lossless_packet, 0)
|
|
||||||
tox.callback_friend_lossy_packet(lossy_packet, 0)
|
|
||||||
|
|
||||||
tox.callback_group_invite(group_invite)
|
|
||||||
tox.callback_group_message(group_message(window, tray))
|
|
||||||
tox.callback_group_action(group_action(window, tray))
|
|
||||||
tox.callback_group_title(group_title)
|
|
||||||
tox.callback_group_namelist_change(group_namelist_change)
|
|
0
toxygen/common/__init__.py
Normal file
0
toxygen/common/__init__.py
Normal file
26
toxygen/common/event.py
Normal file
26
toxygen/common/event.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
|
||||||
|
class Event:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._callbacks = set()
|
||||||
|
|
||||||
|
def __iadd__(self, callback):
|
||||||
|
self.add_callback(callback)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __isub__(self, callback):
|
||||||
|
self.remove_callback(callback)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
for callback in self._callbacks:
|
||||||
|
callback(*args, **kwargs)
|
||||||
|
|
||||||
|
def add_callback(self, callback):
|
||||||
|
self._callbacks.add(callback)
|
||||||
|
|
||||||
|
def remove_callback(self, callback):
|
||||||
|
self._callbacks.discard(callback)
|
13
toxygen/common/provider.py
Normal file
13
toxygen/common/provider.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
|
||||||
|
class Provider:
|
||||||
|
|
||||||
|
def __init__(self, get_item_action):
|
||||||
|
self._get_item_action = get_item_action
|
||||||
|
self._item = None
|
||||||
|
|
||||||
|
def get_item(self):
|
||||||
|
if self._item is None:
|
||||||
|
self._item = self._get_item_action()
|
||||||
|
|
||||||
|
return self._item
|
18
toxygen/common/tox_save.py
Normal file
18
toxygen/common/tox_save.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
|
||||||
|
class ToxSave:
|
||||||
|
|
||||||
|
def __init__(self, tox):
|
||||||
|
self._tox = tox
|
||||||
|
|
||||||
|
def set_tox(self, tox):
|
||||||
|
self._tox = tox
|
||||||
|
|
||||||
|
|
||||||
|
class ToxAvSave:
|
||||||
|
|
||||||
|
def __init__(self, toxav):
|
||||||
|
self._toxav = toxav
|
||||||
|
|
||||||
|
def set_toxav(self, toxav):
|
||||||
|
self._toxav = toxav
|
0
toxygen/contacts/__init__.py
Normal file
0
toxygen/contacts/__init__.py
Normal file
@ -1,6 +1,9 @@
|
|||||||
from settings import *
|
from user_data.settings import *
|
||||||
from PyQt5 import QtCore, QtGui
|
from PyQt5 import QtCore, QtGui
|
||||||
from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
|
from wrapper.toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
|
||||||
|
import utils.util as util
|
||||||
|
import common.event as event
|
||||||
|
import contacts.common as common
|
||||||
|
|
||||||
|
|
||||||
class BaseContact:
|
class BaseContact:
|
||||||
@ -11,16 +14,21 @@ class BaseContact:
|
|||||||
Base class for all contacts.
|
Base class for all contacts.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, status_message, widget, tox_id):
|
def __init__(self, profile_manager, 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._profile_manager = profile_manager
|
||||||
self._name, self._status_message = name, status_message
|
self._name, self._status_message = name, status_message
|
||||||
self._status, self._widget = None, widget
|
self._status, self._widget = None, widget
|
||||||
self._tox_id = tox_id
|
self._tox_id = tox_id
|
||||||
|
self._name_changed_event = event.Event()
|
||||||
|
self._status_message_changed_event = event.Event()
|
||||||
|
self._status_changed_event = event.Event()
|
||||||
|
self._avatar_changed_event = event.Event()
|
||||||
self.init_widget()
|
self.init_widget()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
@ -31,12 +39,20 @@ class BaseContact:
|
|||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
def set_name(self, value):
|
def set_name(self, value):
|
||||||
self._name = str(value, 'utf-8')
|
if self._name == value:
|
||||||
|
return
|
||||||
|
self._name = value
|
||||||
self._widget.name.setText(self._name)
|
self._widget.name.setText(self._name)
|
||||||
self._widget.name.repaint()
|
self._widget.name.repaint()
|
||||||
|
self._name_changed_event(self._name)
|
||||||
|
|
||||||
name = property(get_name, set_name)
|
name = property(get_name, set_name)
|
||||||
|
|
||||||
|
def get_name_changed_event(self):
|
||||||
|
return self._name_changed_event
|
||||||
|
|
||||||
|
name_changed_event = property(get_name_changed_event)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Status message
|
# Status message
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
@ -45,12 +61,20 @@ class BaseContact:
|
|||||||
return self._status_message
|
return self._status_message
|
||||||
|
|
||||||
def set_status_message(self, value):
|
def set_status_message(self, value):
|
||||||
self._status_message = str(value, 'utf-8')
|
if self._status_message == value:
|
||||||
|
return
|
||||||
|
self._status_message = value
|
||||||
self._widget.status_message.setText(self._status_message)
|
self._widget.status_message.setText(self._status_message)
|
||||||
self._widget.status_message.repaint()
|
self._widget.status_message.repaint()
|
||||||
|
self._status_message_changed_event(self._status_message)
|
||||||
|
|
||||||
status_message = property(get_status_message, set_status_message)
|
status_message = property(get_status_message, set_status_message)
|
||||||
|
|
||||||
|
def get_status_message_changed_event(self):
|
||||||
|
return self._status_message_changed_event
|
||||||
|
|
||||||
|
status_message_changed_event = property(get_status_message_changed_event)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Status
|
# Status
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
@ -59,11 +83,19 @@ class BaseContact:
|
|||||||
return self._status
|
return self._status
|
||||||
|
|
||||||
def set_status(self, value):
|
def set_status(self, value):
|
||||||
|
if self._status == value:
|
||||||
|
return
|
||||||
self._status = value
|
self._status = value
|
||||||
self._widget.connection_status.update(value)
|
self._widget.connection_status.update(value)
|
||||||
|
self._status_changed_event(self._status)
|
||||||
|
|
||||||
status = property(get_status, set_status)
|
status = property(get_status, set_status)
|
||||||
|
|
||||||
|
def get_status_changed_event(self):
|
||||||
|
return self._status_changed_event
|
||||||
|
|
||||||
|
status_changed_event = property(get_status_changed_event)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# TOX ID. WARNING: for friend it will return public key, for profile - full address
|
# TOX ID. WARNING: for friend it will return public key, for profile - full address
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
@ -81,24 +113,25 @@ class BaseContact:
|
|||||||
"""
|
"""
|
||||||
Tries to load avatar of contact or uses default avatar
|
Tries to load avatar of contact or uses default avatar
|
||||||
"""
|
"""
|
||||||
prefix = ProfileHelper.get_path() + 'avatars/'
|
avatar_path = self.get_avatar_path()
|
||||||
avatar_path = prefix + '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
|
||||||
if not os.path.isfile(avatar_path) or not os.path.getsize(avatar_path): # load default image
|
|
||||||
avatar_path = curr_directory() + '/images/avatar.png'
|
|
||||||
width = self._widget.avatar_label.width()
|
width = self._widget.avatar_label.width()
|
||||||
pixmap = QtGui.QPixmap(avatar_path)
|
pixmap = QtGui.QPixmap(avatar_path)
|
||||||
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
|
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
|
||||||
QtCore.Qt.SmoothTransformation))
|
QtCore.Qt.SmoothTransformation))
|
||||||
self._widget.avatar_label.repaint()
|
self._widget.avatar_label.repaint()
|
||||||
|
self._avatar_changed_event(avatar_path)
|
||||||
|
|
||||||
def reset_avatar(self):
|
def reset_avatar(self, generate_new):
|
||||||
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
avatar_path = self.get_avatar_path()
|
||||||
if os.path.isfile(avatar_path):
|
if os.path.isfile(avatar_path) and not avatar_path == self._get_default_avatar_path():
|
||||||
os.remove(avatar_path)
|
os.remove(avatar_path)
|
||||||
|
if generate_new:
|
||||||
|
self.set_avatar(common.generate_avatar(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]))
|
||||||
|
else:
|
||||||
self.load_avatar()
|
self.load_avatar()
|
||||||
|
|
||||||
def set_avatar(self, avatar):
|
def set_avatar(self, avatar):
|
||||||
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
avatar_path = self.get_contact_avatar_path()
|
||||||
with open(avatar_path, 'wb') as f:
|
with open(avatar_path, 'wb') as f:
|
||||||
f.write(avatar)
|
f.write(avatar)
|
||||||
self.load_avatar()
|
self.load_avatar()
|
||||||
@ -106,13 +139,42 @@ class BaseContact:
|
|||||||
def get_pixmap(self):
|
def get_pixmap(self):
|
||||||
return self._widget.avatar_label.pixmap()
|
return self._widget.avatar_label.pixmap()
|
||||||
|
|
||||||
|
def get_avatar_path(self):
|
||||||
|
avatar_path = self.get_contact_avatar_path()
|
||||||
|
if not os.path.isfile(avatar_path) or not os.path.getsize(avatar_path): # load default image
|
||||||
|
avatar_path = self._get_default_avatar_path()
|
||||||
|
|
||||||
|
return avatar_path
|
||||||
|
|
||||||
|
def get_contact_avatar_path(self):
|
||||||
|
directory = util.join_path(self._profile_manager.get_dir(), 'avatars')
|
||||||
|
|
||||||
|
return util.join_path(directory, '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]))
|
||||||
|
|
||||||
|
def has_avatar(self):
|
||||||
|
path = self.get_contact_avatar_path()
|
||||||
|
|
||||||
|
return util.file_exists(path)
|
||||||
|
|
||||||
|
def get_avatar_changed_event(self):
|
||||||
|
return self._avatar_changed_event
|
||||||
|
|
||||||
|
avatar_changed_event = property(get_avatar_changed_event)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Widgets
|
# Widgets
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
def init_widget(self):
|
def init_widget(self):
|
||||||
if self._widget is not None:
|
self._widget.name.setText(self._name)
|
||||||
self._widget.name.setText(self._name)
|
self._widget.status_message.setText(self._status_message)
|
||||||
self._widget.status_message.setText(self._status_message)
|
self._widget.connection_status.update(self._status)
|
||||||
self._widget.connection_status.update(self._status)
|
self.load_avatar()
|
||||||
self.load_avatar()
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Private methods
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_default_avatar_path():
|
||||||
|
return util.join_path(util.get_images_directory(), 'avatar.png')
|
50
toxygen/contacts/common.py
Normal file
50
toxygen/contacts/common.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
from pydenticon import Generator
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Typing notifications
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class BaseTypingNotificationHandler:
|
||||||
|
|
||||||
|
DEFAULT_HANDLER = None
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def send(self, tox, is_typing):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FriendTypingNotificationHandler(BaseTypingNotificationHandler):
|
||||||
|
|
||||||
|
def __init__(self, friend_number):
|
||||||
|
super().__init__()
|
||||||
|
self._friend_number = friend_number
|
||||||
|
|
||||||
|
def send(self, tox, is_typing):
|
||||||
|
tox.self_set_typing(self._friend_number, is_typing)
|
||||||
|
|
||||||
|
|
||||||
|
BaseTypingNotificationHandler.DEFAULT_HANDLER = BaseTypingNotificationHandler()
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Identicons support
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def generate_avatar(public_key):
|
||||||
|
foreground = ['rgb(45,79,255)', 'rgb(185, 66, 244)', 'rgb(185, 66, 244)',
|
||||||
|
'rgb(254,180,44)', 'rgb(252, 2, 2)', 'rgb(109, 198, 0)',
|
||||||
|
'rgb(226,121,234)', 'rgb(130, 135, 124)',
|
||||||
|
'rgb(30,179,253)', 'rgb(160, 157, 0)',
|
||||||
|
'rgb(232,77,65)', 'rgb(102, 4, 4)',
|
||||||
|
'rgb(49,203,115)',
|
||||||
|
'rgb(141,69,170)']
|
||||||
|
generator = Generator(5, 5, foreground=foreground, background='rgba(42,42,42,0)')
|
||||||
|
digest = hashlib.sha256(public_key.encode('utf-8')).hexdigest()
|
||||||
|
identicon = generator.generate(digest, 220, 220, padding=(10, 10, 10, 10))
|
||||||
|
|
||||||
|
return identicon
|
@ -1,9 +1,8 @@
|
|||||||
from PyQt5 import QtCore, QtGui
|
from history.database import *
|
||||||
from history import *
|
from contacts import basecontact, common
|
||||||
import basecontact
|
from messenger.messages import *
|
||||||
import util
|
from contacts.contact_menu import *
|
||||||
from messages import *
|
from file_transfers import file_transfers as ft
|
||||||
import file_transfers as ft
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
@ -13,12 +12,12 @@ class Contact(basecontact.BaseContact):
|
|||||||
Properties: number, message getter, history etc. Base class for friend and gc classes
|
Properties: number, message getter, history etc. Base class for friend and gc classes
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, message_getter, number, name, status_message, widget, tox_id):
|
def __init__(self, profile_manager, message_getter, number, name, status_message, widget, tox_id):
|
||||||
"""
|
"""
|
||||||
:param message_getter: gets messages from db
|
:param message_getter: gets messages from db
|
||||||
:param number: number of friend.
|
:param number: number of friend.
|
||||||
"""
|
"""
|
||||||
super().__init__(name, status_message, widget, tox_id)
|
super().__init__(profile_manager, name, status_message, widget, tox_id)
|
||||||
self._number = number
|
self._number = number
|
||||||
self._new_messages = False
|
self._new_messages = False
|
||||||
self._visible = True
|
self._visible = True
|
||||||
@ -44,18 +43,22 @@ class Contact(basecontact.BaseContact):
|
|||||||
"""
|
"""
|
||||||
:param first_time: friend became active, load first part of messages
|
:param first_time: friend became active, load first part of messages
|
||||||
"""
|
"""
|
||||||
if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')):
|
try:
|
||||||
return
|
if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')):
|
||||||
if self._message_getter is None:
|
return
|
||||||
return
|
if self._message_getter is None:
|
||||||
data = list(self._message_getter.get(PAGE_SIZE))
|
return
|
||||||
if data is not None and len(data):
|
data = list(self._message_getter.get(PAGE_SIZE))
|
||||||
data.reverse()
|
if data is not None and len(data):
|
||||||
else:
|
data.reverse()
|
||||||
return
|
else:
|
||||||
data = list(map(lambda tupl: TextMessage(*tupl), data))
|
return
|
||||||
self._corr = data + self._corr
|
data = list(map(lambda p: self._get_text_message(p), data))
|
||||||
self._history_loaded = True
|
self._corr = data + self._corr
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
self._history_loaded = True
|
||||||
|
|
||||||
def load_all_corr(self):
|
def load_all_corr(self):
|
||||||
"""
|
"""
|
||||||
@ -66,7 +69,7 @@ class Contact(basecontact.BaseContact):
|
|||||||
data = list(self._message_getter.get_all())
|
data = list(self._message_getter.get_all())
|
||||||
if data is not None and len(data):
|
if data is not None and len(data):
|
||||||
data.reverse()
|
data.reverse()
|
||||||
data = list(map(lambda tupl: TextMessage(*tupl), data))
|
data = list(map(lambda p: self._get_text_message(p), data))
|
||||||
self._corr = data + self._corr
|
self._corr = data + self._corr
|
||||||
self._history_loaded = True
|
self._history_loaded = True
|
||||||
|
|
||||||
@ -75,8 +78,8 @@ class Contact(basecontact.BaseContact):
|
|||||||
Get data to save in db
|
Get data to save in db
|
||||||
:return: list of unsaved messages or []
|
:return: list of unsaved messages or []
|
||||||
"""
|
"""
|
||||||
messages = list(filter(lambda x: x.get_type() <= 1, self._corr))
|
messages = list(filter(lambda m: m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']), self._corr))
|
||||||
return list(map(lambda x: x.get_data(), messages[-self._unsaved_messages:])) if self._unsaved_messages else []
|
return messages[-self._unsaved_messages:] if self._unsaved_messages else []
|
||||||
|
|
||||||
def get_corr(self):
|
def get_corr(self):
|
||||||
return self._corr[:]
|
return self._corr[:]
|
||||||
@ -86,16 +89,31 @@ class Contact(basecontact.BaseContact):
|
|||||||
:param message: text or file transfer message
|
:param message: text or file transfer message
|
||||||
"""
|
"""
|
||||||
self._corr.append(message)
|
self._corr.append(message)
|
||||||
if message.get_type() <= 1:
|
if message.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']):
|
||||||
self._unsaved_messages += 1
|
self._unsaved_messages += 1
|
||||||
|
|
||||||
def get_last_message_text(self):
|
def get_last_message_text(self):
|
||||||
messages = list(filter(lambda x: x.get_type() <= 1 and x.get_owner() != MESSAGE_OWNER['FRIEND'], self._corr))
|
messages = list(filter(lambda m: m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION'])
|
||||||
|
and m.author.type != MESSAGE_AUTHOR['FRIEND'], self._corr))
|
||||||
if messages:
|
if messages:
|
||||||
return messages[-1].get_data()[0]
|
return messages[-1].text
|
||||||
else:
|
else:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
def remove_messages_widgets(self):
|
||||||
|
for message in self._corr:
|
||||||
|
message.remove_widget()
|
||||||
|
|
||||||
|
def get_message(self, _filter):
|
||||||
|
return list(filter(lambda m: _filter(m), self._corr))[0]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_text_message(params):
|
||||||
|
(message, author_type, author_name, unix_time, message_type, unique_id) = params
|
||||||
|
author = MessageAuthor(author_name, author_type)
|
||||||
|
|
||||||
|
return TextMessage(message, author, unix_time, message_type, unique_id)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Unsent messages
|
# Unsent messages
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
@ -104,19 +122,21 @@ class Contact(basecontact.BaseContact):
|
|||||||
"""
|
"""
|
||||||
:return list of unsent messages
|
:return list of unsent messages
|
||||||
"""
|
"""
|
||||||
messages = filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
|
messages = filter(lambda m: m.author is not None and m.author.type == MESSAGE_AUTHOR['NOT_SENT'], self._corr)
|
||||||
return list(messages)
|
return list(messages)
|
||||||
|
|
||||||
def get_unsent_messages_for_saving(self):
|
def get_unsent_messages_for_saving(self):
|
||||||
"""
|
"""
|
||||||
:return list of unsent messages for saving
|
: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)
|
messages = filter(lambda m: m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION'])
|
||||||
return list(map(lambda x: x.get_data(), messages))
|
and m.author.type == MESSAGE_AUTHOR['NOT_SENT'], self._corr)
|
||||||
|
return list(messages)
|
||||||
|
|
||||||
def mark_as_sent(self):
|
def mark_as_sent(self, tox_message_id):
|
||||||
try:
|
try:
|
||||||
message = list(filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr))[0]
|
message = list(filter(lambda m: m.author is not None and m.author.type == MESSAGE_AUTHOR['NOT_SENT']
|
||||||
|
and m.tox_message_id == tox_message_id, self._corr))[0]
|
||||||
message.mark_as_sent()
|
message.mark_as_sent()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
util.log('Mark as sent ex: ' + str(ex))
|
util.log('Mark as sent ex: ' + str(ex))
|
||||||
@ -125,9 +145,9 @@ class Contact(basecontact.BaseContact):
|
|||||||
# Message deletion
|
# Message deletion
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
def delete_message(self, time):
|
def delete_message(self, message_id):
|
||||||
elem = list(filter(lambda x: type(x) in (TextMessage, GroupChatMessage) and x.get_data()[2] == time, self._corr))[0]
|
elem = list(filter(lambda m: m.message_id == message_id, self._corr))[0]
|
||||||
tmp = list(filter(lambda x: x.get_type() <= 1, self._corr))
|
tmp = list(filter(lambda m: m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']), self._corr))
|
||||||
if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages:
|
if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages:
|
||||||
self._unsaved_messages -= 1
|
self._unsaved_messages -= 1
|
||||||
self._corr.remove(elem)
|
self._corr.remove(elem)
|
||||||
@ -138,14 +158,14 @@ class Contact(basecontact.BaseContact):
|
|||||||
"""
|
"""
|
||||||
Delete old messages (reduces RAM usage if messages saving is not enabled)
|
Delete old messages (reduces RAM usage if messages saving is not enabled)
|
||||||
"""
|
"""
|
||||||
def save_message(x):
|
def save_message(m):
|
||||||
if x.get_type() == 2 and (x.get_status() >= 2 or x.get_status() is None):
|
if m.type == MESSAGE_TYPE['FILE_TRANSFER'] and (m.state not in ACTIVE_FILE_TRANSFERS):
|
||||||
return True
|
return True
|
||||||
return x.get_owner() == MESSAGE_OWNER['NOT_SENT']
|
return m.author is not None and m.author.type == MESSAGE_AUTHOR['NOT_SENT']
|
||||||
|
|
||||||
old = filter(save_message, self._corr[:-SAVE_MESSAGES])
|
old = filter(save_message, self._corr[:-SAVE_MESSAGES])
|
||||||
self._corr = list(old) + self._corr[-SAVE_MESSAGES:]
|
self._corr = list(old) + self._corr[-SAVE_MESSAGES:]
|
||||||
text_messages = filter(lambda x: x.get_type() <= 1, self._corr)
|
text_messages = filter(lambda m: m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']), self._corr)
|
||||||
self._unsaved_messages = min(self._unsaved_messages, len(list(text_messages)))
|
self._unsaved_messages = min(self._unsaved_messages, len(list(text_messages)))
|
||||||
self._search_index = 0
|
self._search_index = 0
|
||||||
|
|
||||||
@ -158,12 +178,14 @@ class Contact(basecontact.BaseContact):
|
|||||||
self._search_index = 0
|
self._search_index = 0
|
||||||
# don't delete data about active file transfer
|
# don't delete data about active file transfer
|
||||||
if not save_unsent:
|
if not save_unsent:
|
||||||
self._corr = list(filter(lambda x: x.get_type() == 2 and
|
self._corr = list(filter(lambda m: m.type == MESSAGE_TYPE['FILE_TRANSFER'] and
|
||||||
x.get_status() in ft.ACTIVE_FILE_TRANSFERS, self._corr))
|
m.state in ft.ACTIVE_FILE_TRANSFERS, self._corr))
|
||||||
self._unsaved_messages = 0
|
self._unsaved_messages = 0
|
||||||
else:
|
else:
|
||||||
self._corr = list(filter(lambda x: (x.get_type() == 2 and x.get_status() in ft.ACTIVE_FILE_TRANSFERS)
|
self._corr = list(filter(lambda m: (m.type == MESSAGE_TYPE['FILE_TRANSFER']
|
||||||
or (x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT']),
|
and m.state in ft.ACTIVE_FILE_TRANSFERS)
|
||||||
|
or (m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION'])
|
||||||
|
and m.author.type == MESSAGE_AUTHOR['NOT_SENT']),
|
||||||
self._corr))
|
self._corr))
|
||||||
self._unsaved_messages = len(self.get_unsent_messages())
|
self._unsaved_messages = len(self.get_unsent_messages())
|
||||||
|
|
||||||
@ -179,9 +201,9 @@ class Contact(basecontact.BaseContact):
|
|||||||
while True:
|
while True:
|
||||||
l = len(self._corr)
|
l = len(self._corr)
|
||||||
for i in range(self._search_index - 1, -l - 1, -1):
|
for i in range(self._search_index - 1, -l - 1, -1):
|
||||||
if self._corr[i].get_type() > 1:
|
if self._corr[i].type not in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']):
|
||||||
continue
|
continue
|
||||||
message = self._corr[i].get_data()[0]
|
message = self._corr[i].text
|
||||||
if re.search(self._search_string, message, re.IGNORECASE) is not None:
|
if re.search(self._search_string, message, re.IGNORECASE) is not None:
|
||||||
self._search_index = i
|
self._search_index = i
|
||||||
return i
|
return i
|
||||||
@ -194,9 +216,9 @@ class Contact(basecontact.BaseContact):
|
|||||||
if not self._search_index:
|
if not self._search_index:
|
||||||
return None
|
return None
|
||||||
for i in range(self._search_index + 1, 0):
|
for i in range(self._search_index + 1, 0):
|
||||||
if self._corr[i].get_type() > 1:
|
if self._corr[i].type not in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']):
|
||||||
continue
|
continue
|
||||||
message = self._corr[i].get_data()[0]
|
message = self._corr[i].text
|
||||||
if re.search(self._search_string, message, re.IGNORECASE) is not None:
|
if re.search(self._search_string, message, re.IGNORECASE) is not None:
|
||||||
self._search_index = i
|
self._search_index = i
|
||||||
return i
|
return i
|
||||||
@ -229,6 +251,9 @@ class Contact(basecontact.BaseContact):
|
|||||||
def set_alias(self, alias):
|
def set_alias(self, alias):
|
||||||
self._alias = bool(alias)
|
self._alias = bool(alias)
|
||||||
|
|
||||||
|
def has_alias(self):
|
||||||
|
return self._alias
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Visibility in friends' list
|
# Visibility in friends' list
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
@ -241,10 +266,6 @@ class Contact(basecontact.BaseContact):
|
|||||||
|
|
||||||
visibility = property(get_visibility, set_visibility)
|
visibility = property(get_visibility, set_visibility)
|
||||||
|
|
||||||
def set_widget(self, widget):
|
|
||||||
self._widget = widget
|
|
||||||
self.init_widget()
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Unread messages and other actions from friend
|
# Unread messages and other actions from friend
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
@ -276,7 +297,7 @@ class Contact(basecontact.BaseContact):
|
|||||||
messages = property(get_messages)
|
messages = property(get_messages)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Friend's number (can be used in toxcore)
|
# Friend's or group's number (can be used in toxcore)
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
def get_number(self):
|
def get_number(self):
|
||||||
@ -286,3 +307,27 @@ class Contact(basecontact.BaseContact):
|
|||||||
self._number = value
|
self._number = value
|
||||||
|
|
||||||
number = property(get_number, set_number)
|
number = property(get_number, set_number)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Typing notifications
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_typing_notification_handler(self):
|
||||||
|
return common.BaseTypingNotificationHandler.DEFAULT_HANDLER
|
||||||
|
|
||||||
|
typing_notification_handler = property(get_typing_notification_handler)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Context menu support
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_context_menu_generator(self):
|
||||||
|
return BaseContactMenuGenerator(self)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Filtration support
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def set_widget(self, widget):
|
||||||
|
self._widget = widget
|
||||||
|
self.init_widget()
|
229
toxygen/contacts/contact_menu.py
Normal file
229
toxygen/contacts/contact_menu.py
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
from PyQt5 import QtWidgets
|
||||||
|
import utils.ui as util_ui
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Builder
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _create_menu(menu_name, parent):
|
||||||
|
menu_name = menu_name or ''
|
||||||
|
|
||||||
|
return QtWidgets.QMenu(menu_name) if parent is None else parent.addMenu(menu_name)
|
||||||
|
|
||||||
|
|
||||||
|
class ContactMenuBuilder:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._actions = {}
|
||||||
|
self._submenus = {}
|
||||||
|
self._name = None
|
||||||
|
self._index = 0
|
||||||
|
|
||||||
|
def with_name(self, name):
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def with_action(self, text, handler):
|
||||||
|
self._add_action(text, handler)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def with_optional_action(self, text, handler, show_action):
|
||||||
|
if show_action:
|
||||||
|
self._add_action(text, handler)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def with_actions(self, actions):
|
||||||
|
for action in actions:
|
||||||
|
(text, handler) = action
|
||||||
|
self._add_action(text, handler)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def with_submenu(self, submenu_builder):
|
||||||
|
self._add_submenu(submenu_builder)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def with_optional_submenu(self, submenu_builder):
|
||||||
|
if submenu_builder is not None:
|
||||||
|
self._add_submenu(submenu_builder)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def build(self, parent=None):
|
||||||
|
menu = _create_menu(self._name, parent)
|
||||||
|
|
||||||
|
for i in range(self._index):
|
||||||
|
if i in self._actions:
|
||||||
|
text, handler = self._actions[i]
|
||||||
|
action = menu.addAction(text)
|
||||||
|
action.triggered.connect(handler)
|
||||||
|
else:
|
||||||
|
submenu_builder = self._submenus[i]
|
||||||
|
submenu = submenu_builder.build(menu)
|
||||||
|
menu.addMenu(submenu)
|
||||||
|
|
||||||
|
return menu
|
||||||
|
|
||||||
|
def _add_submenu(self, submenu):
|
||||||
|
self._submenus[self._index] = submenu
|
||||||
|
self._index += 1
|
||||||
|
|
||||||
|
def _add_action(self, text, handler):
|
||||||
|
self._actions[self._index] = (text, handler)
|
||||||
|
self._index += 1
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Generators
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
class BaseContactMenuGenerator:
|
||||||
|
|
||||||
|
def __init__(self, contact):
|
||||||
|
self._contact = contact
|
||||||
|
|
||||||
|
def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service, history_loader):
|
||||||
|
return ContactMenuBuilder().build()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Private methods
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _generate_copy_menu_builder(self, main_screen):
|
||||||
|
copy_menu_builder = ContactMenuBuilder()
|
||||||
|
(copy_menu_builder
|
||||||
|
.with_name(util_ui.tr('Copy'))
|
||||||
|
.with_action(util_ui.tr('Name'), lambda: main_screen.copy_text(self._contact.name))
|
||||||
|
.with_action(util_ui.tr('Status message'), lambda: main_screen.copy_text(self._contact.status_message))
|
||||||
|
.with_action(util_ui.tr('Public key'), lambda: main_screen.copy_text(self._contact.tox_id))
|
||||||
|
)
|
||||||
|
|
||||||
|
return copy_menu_builder
|
||||||
|
|
||||||
|
def _generate_history_menu_builder(self, history_loader, main_screen):
|
||||||
|
history_menu_builder = ContactMenuBuilder()
|
||||||
|
(history_menu_builder
|
||||||
|
.with_name(util_ui.tr('Chat history'))
|
||||||
|
.with_action(util_ui.tr('Clear history'), lambda: history_loader.clear_history(self._contact)
|
||||||
|
or main_screen.messages.clear())
|
||||||
|
.with_action(util_ui.tr('Export as text'), lambda: history_loader.export_history(self._contact))
|
||||||
|
.with_action(util_ui.tr('Export as HTML'), lambda: history_loader.export_history(self._contact, False))
|
||||||
|
)
|
||||||
|
|
||||||
|
return history_menu_builder
|
||||||
|
|
||||||
|
|
||||||
|
class FriendMenuGenerator(BaseContactMenuGenerator):
|
||||||
|
|
||||||
|
def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service, history_loader):
|
||||||
|
history_menu_builder = self._generate_history_menu_builder(history_loader, main_screen)
|
||||||
|
copy_menu_builder = self._generate_copy_menu_builder(main_screen)
|
||||||
|
plugins_menu_builder = self._generate_plugins_menu_builder(plugin_loader, number)
|
||||||
|
groups_menu_builder = self._generate_groups_menu(contacts_manager, groups_service)
|
||||||
|
|
||||||
|
allowed = self._contact.tox_id in settings['auto_accept_from_friends']
|
||||||
|
auto = util_ui.tr('Disallow auto accept') if allowed else util_ui.tr('Allow auto accept')
|
||||||
|
|
||||||
|
builder = ContactMenuBuilder()
|
||||||
|
menu = (builder
|
||||||
|
.with_action(util_ui.tr('Set alias'), lambda: main_screen.set_alias(number))
|
||||||
|
.with_submenu(history_menu_builder)
|
||||||
|
.with_submenu(copy_menu_builder)
|
||||||
|
.with_action(auto, lambda: main_screen.auto_accept(number, not allowed))
|
||||||
|
.with_action(util_ui.tr('Remove friend'), lambda: main_screen.remove_friend(number))
|
||||||
|
.with_action(util_ui.tr('Block friend'), lambda: main_screen.block_friend(number))
|
||||||
|
.with_action(util_ui.tr('Notes'), lambda: main_screen.show_note(self._contact))
|
||||||
|
.with_optional_submenu(plugins_menu_builder)
|
||||||
|
.with_optional_submenu(groups_menu_builder)
|
||||||
|
).build()
|
||||||
|
|
||||||
|
return menu
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Private methods
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _generate_plugins_menu_builder(plugin_loader, number):
|
||||||
|
if plugin_loader is None:
|
||||||
|
return None
|
||||||
|
plugins_actions = plugin_loader.get_menu(number)
|
||||||
|
if not len(plugins_actions):
|
||||||
|
return None
|
||||||
|
plugins_menu_builder = ContactMenuBuilder()
|
||||||
|
(plugins_menu_builder
|
||||||
|
.with_name(util_ui.tr('Plugins'))
|
||||||
|
.with_actions(plugins_actions)
|
||||||
|
)
|
||||||
|
|
||||||
|
return plugins_menu_builder
|
||||||
|
|
||||||
|
def _generate_groups_menu(self, contacts_manager, groups_service):
|
||||||
|
chats = contacts_manager.get_group_chats()
|
||||||
|
if not len(chats) or self._contact.status is None:
|
||||||
|
return None
|
||||||
|
groups_menu_builder = ContactMenuBuilder()
|
||||||
|
(groups_menu_builder
|
||||||
|
.with_name(util_ui.tr('Invite to group'))
|
||||||
|
.with_actions([(g.name, lambda: groups_service.invite_friend(self._contact.number, g.number)) for g in chats])
|
||||||
|
)
|
||||||
|
|
||||||
|
return groups_menu_builder
|
||||||
|
|
||||||
|
|
||||||
|
class GroupMenuGenerator(BaseContactMenuGenerator):
|
||||||
|
|
||||||
|
def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service, history_loader):
|
||||||
|
copy_menu_builder = self._generate_copy_menu_builder(main_screen)
|
||||||
|
history_menu_builder = self._generate_history_menu_builder(history_loader, main_screen)
|
||||||
|
|
||||||
|
builder = ContactMenuBuilder()
|
||||||
|
menu = (builder
|
||||||
|
.with_action(util_ui.tr('Set alias'), lambda: main_screen.set_alias(number))
|
||||||
|
.with_submenu(copy_menu_builder)
|
||||||
|
.with_submenu(history_menu_builder)
|
||||||
|
.with_optional_action(util_ui.tr('Manage group'),
|
||||||
|
lambda: groups_service.show_group_management_screen(self._contact),
|
||||||
|
self._contact.is_self_founder())
|
||||||
|
.with_optional_action(util_ui.tr('Group settings'),
|
||||||
|
lambda: groups_service.show_group_settings_screen(self._contact),
|
||||||
|
not self._contact.is_self_founder())
|
||||||
|
.with_optional_action(util_ui.tr('Set topic'),
|
||||||
|
lambda: groups_service.set_group_topic(self._contact),
|
||||||
|
self._contact.is_self_moderator_or_founder())
|
||||||
|
.with_action(util_ui.tr('Bans list'),
|
||||||
|
lambda: groups_service.show_bans_list(self._contact))
|
||||||
|
.with_action(util_ui.tr('Reconnect to group'),
|
||||||
|
lambda: groups_service.reconnect_to_group(self._contact.number))
|
||||||
|
.with_optional_action(util_ui.tr('Disconnect from group'),
|
||||||
|
lambda: groups_service.disconnect_from_group(self._contact.number),
|
||||||
|
self._contact.status is not None)
|
||||||
|
.with_action(util_ui.tr('Leave group'), lambda: groups_service.leave_group(self._contact.number))
|
||||||
|
.with_action(util_ui.tr('Notes'), lambda: main_screen.show_note(self._contact))
|
||||||
|
).build()
|
||||||
|
|
||||||
|
return menu
|
||||||
|
|
||||||
|
|
||||||
|
class GroupPeerMenuGenerator(BaseContactMenuGenerator):
|
||||||
|
|
||||||
|
def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service, history_loader):
|
||||||
|
copy_menu_builder = self._generate_copy_menu_builder(main_screen)
|
||||||
|
history_menu_builder = self._generate_history_menu_builder(history_loader, main_screen)
|
||||||
|
|
||||||
|
builder = ContactMenuBuilder()
|
||||||
|
menu = (builder
|
||||||
|
.with_action(util_ui.tr('Set alias'), lambda: main_screen.set_alias(number))
|
||||||
|
.with_submenu(copy_menu_builder)
|
||||||
|
.with_submenu(history_menu_builder)
|
||||||
|
.with_action(util_ui.tr('Quit chat'),
|
||||||
|
lambda: contacts_manager.remove_group_peer(self._contact))
|
||||||
|
.with_action(util_ui.tr('Notes'), lambda: main_screen.show_note(self._contact))
|
||||||
|
).build()
|
||||||
|
|
||||||
|
return menu
|
107
toxygen/contacts/contact_provider.py
Normal file
107
toxygen/contacts/contact_provider.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import common.tox_save as tox_save
|
||||||
|
|
||||||
|
|
||||||
|
class ContactProvider(tox_save.ToxSave):
|
||||||
|
|
||||||
|
def __init__(self, tox, friend_factory, group_factory, group_peer_factory):
|
||||||
|
super().__init__(tox)
|
||||||
|
self._friend_factory = friend_factory
|
||||||
|
self._group_factory = group_factory
|
||||||
|
self._group_peer_factory = group_peer_factory
|
||||||
|
self._cache = {} # key - contact's public key, value - contact instance
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Friends
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_friend_by_number(self, friend_number):
|
||||||
|
public_key = self._tox.friend_get_public_key(friend_number)
|
||||||
|
|
||||||
|
return self.get_friend_by_public_key(public_key)
|
||||||
|
|
||||||
|
def get_friend_by_public_key(self, public_key):
|
||||||
|
friend = self._get_contact_from_cache(public_key)
|
||||||
|
if friend is not None:
|
||||||
|
return friend
|
||||||
|
friend = self._friend_factory.create_friend_by_public_key(public_key)
|
||||||
|
self._add_to_cache(public_key, friend)
|
||||||
|
|
||||||
|
return friend
|
||||||
|
|
||||||
|
def get_all_friends(self):
|
||||||
|
friend_numbers = self._tox.self_get_friend_list()
|
||||||
|
friends = map(lambda n: self.get_friend_by_number(n), friend_numbers)
|
||||||
|
|
||||||
|
return list(friends)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Groups
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_all_groups(self):
|
||||||
|
group_numbers = range(self._tox.group_get_number_groups())
|
||||||
|
groups = map(lambda n: self.get_group_by_number(n), group_numbers)
|
||||||
|
|
||||||
|
return list(groups)
|
||||||
|
|
||||||
|
def get_group_by_number(self, group_number):
|
||||||
|
public_key = self._tox.group_get_chat_id(group_number)
|
||||||
|
|
||||||
|
return self.get_group_by_public_key(public_key)
|
||||||
|
|
||||||
|
def get_group_by_public_key(self, public_key):
|
||||||
|
group = self._get_contact_from_cache(public_key)
|
||||||
|
if group is not None:
|
||||||
|
return group
|
||||||
|
group = self._group_factory.create_group_by_public_key(public_key)
|
||||||
|
self._add_to_cache(public_key, group)
|
||||||
|
|
||||||
|
return group
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Group peers
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_all_group_peers(self):
|
||||||
|
return list()
|
||||||
|
|
||||||
|
def get_group_peer_by_id(self, group, peer_id):
|
||||||
|
peer = group.get_peer_by_id(peer_id)
|
||||||
|
|
||||||
|
return self._get_group_peer(group, peer)
|
||||||
|
|
||||||
|
def get_group_peer_by_public_key(self, group, public_key):
|
||||||
|
peer = group.get_peer_by_public_key(public_key)
|
||||||
|
|
||||||
|
return self._get_group_peer(group, peer)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# All contacts
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_all(self):
|
||||||
|
return self.get_all_friends() + self.get_all_groups() + self.get_all_group_peers()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Caching
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def clear_cache(self):
|
||||||
|
self._cache.clear()
|
||||||
|
|
||||||
|
def remove_contact_from_cache(self, contact_public_key):
|
||||||
|
if contact_public_key in self._cache:
|
||||||
|
del self._cache[contact_public_key]
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Private methods
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _get_contact_from_cache(self, public_key):
|
||||||
|
return self._cache[public_key] if public_key in self._cache else None
|
||||||
|
|
||||||
|
def _add_to_cache(self, public_key, contact):
|
||||||
|
self._cache[public_key] = contact
|
||||||
|
|
||||||
|
def _get_group_peer(self, group, peer):
|
||||||
|
return self._group_peer_factory.create_group_peer(group, peer)
|
575
toxygen/contacts/contacts_manager.py
Normal file
575
toxygen/contacts/contacts_manager.py
Normal file
@ -0,0 +1,575 @@
|
|||||||
|
from contacts.friend import Friend
|
||||||
|
from contacts.group_chat import GroupChat
|
||||||
|
from messenger.messages import *
|
||||||
|
from common.tox_save import ToxSave
|
||||||
|
from contacts.group_peer_contact import GroupPeerContact
|
||||||
|
|
||||||
|
|
||||||
|
class ContactsManager(ToxSave):
|
||||||
|
"""
|
||||||
|
Represents contacts list.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, tox, settings, screen, profile_manager, contact_provider, history, tox_dns,
|
||||||
|
messages_items_factory):
|
||||||
|
super().__init__(tox)
|
||||||
|
self._settings = settings
|
||||||
|
self._screen = screen
|
||||||
|
self._profile_manager = profile_manager
|
||||||
|
self._contact_provider = contact_provider
|
||||||
|
self._tox_dns = tox_dns
|
||||||
|
self._messages_items_factory = messages_items_factory
|
||||||
|
self._messages = screen.messages
|
||||||
|
self._contacts, self._active_contact = [], -1
|
||||||
|
self._active_contact_changed = Event()
|
||||||
|
self._sorting = settings['sorting']
|
||||||
|
self._filter_string = ''
|
||||||
|
screen.contacts_filter.setCurrentIndex(int(self._sorting))
|
||||||
|
self._history = history
|
||||||
|
self._load_contacts()
|
||||||
|
|
||||||
|
def get_contact(self, num):
|
||||||
|
if num < 0 or num >= len(self._contacts):
|
||||||
|
return None
|
||||||
|
return self._contacts[num]
|
||||||
|
|
||||||
|
def get_curr_contact(self):
|
||||||
|
return self._contacts[self._active_contact] if self._active_contact + 1 else None
|
||||||
|
|
||||||
|
def save_profile(self):
|
||||||
|
data = self._tox.get_savedata()
|
||||||
|
self._profile_manager.save_profile(data)
|
||||||
|
|
||||||
|
def is_friend_active(self, friend_number):
|
||||||
|
if not self.is_active_a_friend():
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.get_curr_contact().number == friend_number
|
||||||
|
|
||||||
|
def is_group_active(self, group_number):
|
||||||
|
if self.is_active_a_friend():
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.get_curr_contact().number == group_number
|
||||||
|
|
||||||
|
def is_contact_active(self, contact):
|
||||||
|
return self._contacts[self._active_contact].tox_id == contact.tox_id
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Reconnection support
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def reset_contacts_statuses(self):
|
||||||
|
for contact in self._contacts:
|
||||||
|
contact.status = None
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Work with active friend
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_active(self):
|
||||||
|
return self._active_contact
|
||||||
|
|
||||||
|
def set_active(self, value):
|
||||||
|
"""
|
||||||
|
Change current active friend or update info
|
||||||
|
:param value: number of new active friend in friend's list
|
||||||
|
"""
|
||||||
|
if value is None and self._active_contact == -1: # nothing to update
|
||||||
|
return
|
||||||
|
if value == -1: # all friends were deleted
|
||||||
|
self._screen.account_name.setText('')
|
||||||
|
self._screen.account_status.setText('')
|
||||||
|
self._screen.account_status.setToolTip('')
|
||||||
|
self._active_contact = -1
|
||||||
|
self._screen.account_avatar.setHidden(True)
|
||||||
|
self._messages.clear()
|
||||||
|
self._screen.messageEdit.clear()
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self._screen.typing.setVisible(False)
|
||||||
|
current_contact = self.get_curr_contact()
|
||||||
|
if current_contact is not None:
|
||||||
|
# TODO: send when needed
|
||||||
|
current_contact.typing_notification_handler.send(self._tox, False)
|
||||||
|
current_contact.remove_messages_widgets() # TODO: if required
|
||||||
|
self._unsubscribe_from_events(current_contact)
|
||||||
|
|
||||||
|
if self._active_contact + 1 and self._active_contact != value:
|
||||||
|
try:
|
||||||
|
current_contact.curr_text = self._screen.messageEdit.toPlainText()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
contact = self._contacts[value]
|
||||||
|
self._subscribe_to_events(contact)
|
||||||
|
contact.remove_invalid_unsent_files()
|
||||||
|
if self._active_contact != value:
|
||||||
|
self._screen.messageEdit.setPlainText(contact.curr_text)
|
||||||
|
self._active_contact = value
|
||||||
|
contact.reset_messages()
|
||||||
|
if not self._settings['save_history']:
|
||||||
|
contact.delete_old_messages()
|
||||||
|
self._messages.clear()
|
||||||
|
contact.load_corr()
|
||||||
|
corr = contact.get_corr()[-PAGE_SIZE:]
|
||||||
|
for message in corr:
|
||||||
|
if message.type == MESSAGE_TYPE['FILE_TRANSFER']:
|
||||||
|
self._messages_items_factory.create_file_transfer_item(message)
|
||||||
|
elif message.type == MESSAGE_TYPE['INLINE']:
|
||||||
|
self._messages_items_factory.create_inline_item(message)
|
||||||
|
else:
|
||||||
|
self._messages_items_factory.create_message_item(message)
|
||||||
|
self._messages.scrollToBottom()
|
||||||
|
# if value in self._call:
|
||||||
|
# self._screen.active_call()
|
||||||
|
# elif value in self._incoming_calls:
|
||||||
|
# self._screen.incoming_call()
|
||||||
|
# else:
|
||||||
|
# self._screen.call_finished()
|
||||||
|
self._set_current_contact_data(contact)
|
||||||
|
self._active_contact_changed(contact)
|
||||||
|
except Exception as ex: # no friend found. ignore
|
||||||
|
util.log('Friend value: ' + str(value))
|
||||||
|
util.log('Error in set active: ' + str(ex))
|
||||||
|
raise
|
||||||
|
|
||||||
|
active_contact = property(get_active, set_active)
|
||||||
|
|
||||||
|
def get_active_contact_changed(self):
|
||||||
|
return self._active_contact_changed
|
||||||
|
|
||||||
|
active_contact_changed = property(get_active_contact_changed)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
if self._active_contact + 1:
|
||||||
|
self.set_active(self._active_contact)
|
||||||
|
|
||||||
|
def is_active_a_friend(self):
|
||||||
|
return type(self.get_curr_contact()) is Friend
|
||||||
|
|
||||||
|
def is_active_a_group(self):
|
||||||
|
return type(self.get_curr_contact()) is GroupChat
|
||||||
|
|
||||||
|
def is_active_a_group_chat_peer(self):
|
||||||
|
return type(self.get_curr_contact()) is GroupPeerContact
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Filtration
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def filtration_and_sorting(self, sorting=0, filter_str=''):
|
||||||
|
"""
|
||||||
|
Filtration of friends list
|
||||||
|
:param sorting: 0 - no sorting, 1 - online only, 2 - online first, 3 - by name,
|
||||||
|
4 - online and by name, 5 - online first and by name
|
||||||
|
:param filter_str: show contacts which name contains this substring
|
||||||
|
"""
|
||||||
|
filter_str = filter_str.lower()
|
||||||
|
current_contact = self.get_curr_contact()
|
||||||
|
|
||||||
|
if sorting > 5 or sorting < 0:
|
||||||
|
sorting = 0
|
||||||
|
|
||||||
|
if sorting in (1, 2, 4, 5): # online first
|
||||||
|
self._contacts = sorted(self._contacts, key=lambda x: int(x.status is not None), reverse=True)
|
||||||
|
sort_by_name = sorting in (4, 5)
|
||||||
|
# save results of previous sorting
|
||||||
|
online_friends = filter(lambda x: x.status is not None, self._contacts)
|
||||||
|
online_friends_count = len(list(online_friends))
|
||||||
|
part1 = self._contacts[:online_friends_count]
|
||||||
|
part2 = self._contacts[online_friends_count:]
|
||||||
|
key_lambda = lambda x: x.name.lower() if sort_by_name else x.number
|
||||||
|
part1 = sorted(part1, key=key_lambda)
|
||||||
|
part2 = sorted(part2, key=key_lambda)
|
||||||
|
self._contacts = part1 + part2
|
||||||
|
elif sorting == 0:
|
||||||
|
contacts = sorted(self._contacts, key=lambda c: c.number)
|
||||||
|
friends = filter(lambda c: type(c) is Friend, contacts)
|
||||||
|
groups = filter(lambda c: type(c) is GroupChat, contacts)
|
||||||
|
group_peers = filter(lambda c: type(c) is GroupPeerContact, contacts)
|
||||||
|
self._contacts = list(friends) + list(groups) + list(group_peers)
|
||||||
|
else:
|
||||||
|
self._contacts = sorted(self._contacts, key=lambda x: x.name.lower())
|
||||||
|
|
||||||
|
# change item widgets
|
||||||
|
for index, contact in enumerate(self._contacts):
|
||||||
|
list_item = self._screen.friends_list.item(index)
|
||||||
|
item_widget = self._screen.friends_list.itemWidget(list_item)
|
||||||
|
contact.set_widget(item_widget)
|
||||||
|
|
||||||
|
for index, friend in enumerate(self._contacts):
|
||||||
|
filtered_by_name = filter_str in friend.name.lower()
|
||||||
|
friend.visibility = (friend.status is not None or sorting not in (1, 4)) and filtered_by_name
|
||||||
|
# show friend even if it's hidden when there any unread messages/actions
|
||||||
|
friend.visibility = friend.visibility or friend.messages or friend.actions
|
||||||
|
item = self._screen.friends_list.item(index)
|
||||||
|
item_widget = self._screen.friends_list.itemWidget(item)
|
||||||
|
item.setSizeHint(QtCore.QSize(250, item_widget.height() if friend.visibility else 0))
|
||||||
|
|
||||||
|
# save soring results
|
||||||
|
self._sorting, self._filter_string = sorting, filter_str
|
||||||
|
self._settings['sorting'] = self._sorting
|
||||||
|
self._settings.save()
|
||||||
|
|
||||||
|
# update active contact
|
||||||
|
if current_contact is not None:
|
||||||
|
index = self._contacts.index(current_contact)
|
||||||
|
self.set_active(index)
|
||||||
|
|
||||||
|
def update_filtration(self):
|
||||||
|
"""
|
||||||
|
Update list of contacts when 1 of friends change connection status
|
||||||
|
"""
|
||||||
|
self.filtration_and_sorting(self._sorting, self._filter_string)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Contact getters
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_friend_by_number(self, number):
|
||||||
|
return list(filter(lambda c: c.number == number and type(c) is Friend, self._contacts))[0]
|
||||||
|
|
||||||
|
def get_group_by_number(self, number):
|
||||||
|
return list(filter(lambda c: c.number == number and type(c) is GroupChat, self._contacts))[0]
|
||||||
|
|
||||||
|
def get_or_create_group_peer_contact(self, group_number, peer_id):
|
||||||
|
group = self.get_group_by_number(group_number)
|
||||||
|
peer = group.get_peer_by_id(peer_id)
|
||||||
|
if not self.check_if_contact_exists(peer.public_key):
|
||||||
|
self.add_group_peer(group, peer)
|
||||||
|
|
||||||
|
return self.get_contact_by_tox_id(peer.public_key)
|
||||||
|
|
||||||
|
def check_if_contact_exists(self, tox_id):
|
||||||
|
return any(filter(lambda c: c.tox_id == tox_id, self._contacts))
|
||||||
|
|
||||||
|
def get_contact_by_tox_id(self, tox_id):
|
||||||
|
return list(filter(lambda c: c.tox_id == tox_id, self._contacts))[0]
|
||||||
|
|
||||||
|
def get_active_number(self):
|
||||||
|
return self.get_curr_contact().number if self._active_contact + 1 else -1
|
||||||
|
|
||||||
|
def get_active_name(self):
|
||||||
|
return self.get_curr_contact().name if self._active_contact + 1 else ''
|
||||||
|
|
||||||
|
def is_active_online(self):
|
||||||
|
return self._active_contact + 1 and self.get_curr_contact().status is not None
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Work with friends (remove, block, set alias, get public key)
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def set_alias(self, num):
|
||||||
|
"""
|
||||||
|
Set new alias for friend
|
||||||
|
"""
|
||||||
|
friend = self._contacts[num]
|
||||||
|
name = friend.name
|
||||||
|
text = util_ui.tr("Enter new alias for friend {} or leave empty to use friend's name:").format(name)
|
||||||
|
title = util_ui.tr('Set alias')
|
||||||
|
text, ok = util_ui.text_dialog(text, title, name)
|
||||||
|
if not ok:
|
||||||
|
return
|
||||||
|
aliases = self._settings['friends_aliases']
|
||||||
|
if text:
|
||||||
|
friend.name = text
|
||||||
|
try:
|
||||||
|
index = list(map(lambda x: x[0], aliases)).index(friend.tox_id)
|
||||||
|
aliases[index] = (friend.tox_id, text)
|
||||||
|
except:
|
||||||
|
aliases.append((friend.tox_id, text))
|
||||||
|
friend.set_alias(text)
|
||||||
|
else: # use default name
|
||||||
|
friend.name = self._tox.friend_get_name(friend.number)
|
||||||
|
friend.set_alias('')
|
||||||
|
try:
|
||||||
|
index = list(map(lambda x: x[0], aliases)).index(friend.tox_id)
|
||||||
|
del aliases[index]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self._settings.save()
|
||||||
|
|
||||||
|
def friend_public_key(self, num):
|
||||||
|
return self._contacts[num].tox_id
|
||||||
|
|
||||||
|
def delete_friend(self, num):
|
||||||
|
"""
|
||||||
|
Removes friend from contact list
|
||||||
|
:param num: number of friend in list
|
||||||
|
"""
|
||||||
|
friend = self._contacts[num]
|
||||||
|
self._cleanup_contact_data(friend)
|
||||||
|
self._tox.friend_delete(friend.number)
|
||||||
|
self._delete_contact(num)
|
||||||
|
|
||||||
|
def add_friend(self, tox_id):
|
||||||
|
"""
|
||||||
|
Adds friend to list
|
||||||
|
"""
|
||||||
|
self._tox.friend_add_norequest(tox_id)
|
||||||
|
self._add_friend(tox_id)
|
||||||
|
self.update_filtration()
|
||||||
|
|
||||||
|
def block_user(self, tox_id):
|
||||||
|
"""
|
||||||
|
Block user with specified tox id (or public key) - delete from friends list and ignore friend requests
|
||||||
|
"""
|
||||||
|
tox_id = tox_id[:TOX_PUBLIC_KEY_SIZE * 2]
|
||||||
|
if tox_id == self._tox.self_get_address[:TOX_PUBLIC_KEY_SIZE * 2]:
|
||||||
|
return
|
||||||
|
if tox_id not in self._settings['blocked']:
|
||||||
|
self._settings['blocked'].append(tox_id)
|
||||||
|
self._settings.save()
|
||||||
|
try:
|
||||||
|
num = self._tox.friend_by_public_key(tox_id)
|
||||||
|
self.delete_friend(num)
|
||||||
|
self.save_profile()
|
||||||
|
except: # not in friend list
|
||||||
|
pass
|
||||||
|
|
||||||
|
def unblock_user(self, tox_id, add_to_friend_list):
|
||||||
|
"""
|
||||||
|
Unblock user
|
||||||
|
:param tox_id: tox id of contact
|
||||||
|
:param add_to_friend_list: add this contact to friend list or not
|
||||||
|
"""
|
||||||
|
self._settings['blocked'].remove(tox_id)
|
||||||
|
self._settings.save()
|
||||||
|
if add_to_friend_list:
|
||||||
|
self.add_friend(tox_id)
|
||||||
|
self.save_profile()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Groups support
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_group_chats(self):
|
||||||
|
return list(filter(lambda c: type(c) is GroupChat, self._contacts))
|
||||||
|
|
||||||
|
def add_group(self, group_number):
|
||||||
|
group = self._contact_provider.get_group_by_number(group_number)
|
||||||
|
index = len(self._contacts)
|
||||||
|
self._contacts.append(group)
|
||||||
|
group.reset_avatar(self._settings['identicons'])
|
||||||
|
self._save_profile()
|
||||||
|
self.set_active(index)
|
||||||
|
self.update_filtration()
|
||||||
|
|
||||||
|
def delete_group(self, group_number):
|
||||||
|
group = self.get_group_by_number(group_number)
|
||||||
|
self._cleanup_contact_data(group)
|
||||||
|
num = self._contacts.index(group)
|
||||||
|
self._delete_contact(num)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Groups private messaging
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def add_group_peer(self, group, peer):
|
||||||
|
contact = self._contact_provider.get_group_peer_by_id(group, peer.id)
|
||||||
|
if self.check_if_contact_exists(contact.tox_id):
|
||||||
|
return
|
||||||
|
self._contacts.append(contact)
|
||||||
|
contact.reset_avatar(self._settings['identicons'])
|
||||||
|
self._save_profile()
|
||||||
|
|
||||||
|
def remove_group_peer_by_id(self, group, peer_id):
|
||||||
|
peer = group.get_peer_by_id(peer_id)
|
||||||
|
if not self.check_if_contact_exists(peer.public_key):
|
||||||
|
return
|
||||||
|
contact = self.get_contact_by_tox_id(peer.public_key)
|
||||||
|
self.remove_group_peer(contact)
|
||||||
|
|
||||||
|
def remove_group_peer(self, group_peer_contact):
|
||||||
|
contact = self.get_contact_by_tox_id(group_peer_contact.tox_id)
|
||||||
|
self._cleanup_contact_data(contact)
|
||||||
|
num = self._contacts.index(contact)
|
||||||
|
self._delete_contact(num)
|
||||||
|
|
||||||
|
def get_gc_peer_name(self, name):
|
||||||
|
group = self.get_curr_contact()
|
||||||
|
|
||||||
|
names = sorted(group.get_peers_names())
|
||||||
|
if name in names: # return next nick
|
||||||
|
index = names.index(name)
|
||||||
|
index = (index + 1) % len(names)
|
||||||
|
|
||||||
|
return names[index]
|
||||||
|
|
||||||
|
suggested_names = list(filter(lambda x: x.startswith(name), names))
|
||||||
|
if not len(suggested_names):
|
||||||
|
return '\t'
|
||||||
|
|
||||||
|
return suggested_names[0]
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Friend requests
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def send_friend_request(self, tox_id, message):
|
||||||
|
"""
|
||||||
|
Function tries to send request to contact with specified id
|
||||||
|
:param tox_id: id of new contact or tox dns 4 value
|
||||||
|
:param message: additional message
|
||||||
|
:return: True on success else error string
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
message = message or 'Hello! Add me to your contact list please'
|
||||||
|
if '@' in tox_id: # value like groupbot@toxme.io
|
||||||
|
tox_id = self._tox_dns.lookup(tox_id)
|
||||||
|
if tox_id is None:
|
||||||
|
raise Exception('TOX DNS lookup failed')
|
||||||
|
if len(tox_id) == TOX_PUBLIC_KEY_SIZE * 2: # public key
|
||||||
|
self.add_friend(tox_id)
|
||||||
|
title = util_ui.tr('Friend added')
|
||||||
|
text = util_ui.tr('Friend added without sending friend request')
|
||||||
|
util_ui.message_box(text, title)
|
||||||
|
else:
|
||||||
|
self._tox.friend_add(tox_id, message.encode('utf-8'))
|
||||||
|
tox_id = tox_id[:TOX_PUBLIC_KEY_SIZE * 2]
|
||||||
|
self._add_friend(tox_id)
|
||||||
|
self.update_filtration()
|
||||||
|
self.save_profile()
|
||||||
|
return True
|
||||||
|
except Exception as ex: # wrong data
|
||||||
|
util.log('Friend request failed with ' + str(ex))
|
||||||
|
return str(ex)
|
||||||
|
|
||||||
|
def process_friend_request(self, tox_id, message):
|
||||||
|
"""
|
||||||
|
Accept or ignore friend request
|
||||||
|
:param tox_id: tox id of contact
|
||||||
|
:param message: message
|
||||||
|
"""
|
||||||
|
if tox_id in self._settings['blocked']:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
text = util_ui.tr('User {} wants to add you to contact list. Message:\n{}')
|
||||||
|
reply = util_ui.question(text.format(tox_id, message), util_ui.tr('Friend request'))
|
||||||
|
if reply: # accepted
|
||||||
|
self.add_friend(tox_id)
|
||||||
|
data = self._tox.get_savedata()
|
||||||
|
self._profile_manager.save_profile(data)
|
||||||
|
except Exception as ex: # something is wrong
|
||||||
|
util.log('Accept friend request failed! ' + str(ex))
|
||||||
|
|
||||||
|
def can_send_typing_notification(self):
|
||||||
|
return self._settings['typing_notifications'] and not self.is_active_a_group_chat_peer()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Contacts numbers update
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def update_friends_numbers(self):
|
||||||
|
for friend in self._contact_provider.get_all_friends():
|
||||||
|
friend.number = self._tox.friend_by_public_key(friend.tox_id)
|
||||||
|
self.update_filtration()
|
||||||
|
|
||||||
|
def update_groups_numbers(self):
|
||||||
|
groups = self._contact_provider.get_all_groups()
|
||||||
|
for i in range(len(groups)):
|
||||||
|
chat_id = self._tox.group_get_chat_id(i)
|
||||||
|
group = self.get_contact_by_tox_id(chat_id)
|
||||||
|
group.number = i
|
||||||
|
self.update_filtration()
|
||||||
|
|
||||||
|
def update_groups_lists(self):
|
||||||
|
groups = self._contact_provider.get_all_groups()
|
||||||
|
for group in groups:
|
||||||
|
group.remove_all_peers_except_self()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Private methods
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _load_contacts(self):
|
||||||
|
self._load_friends()
|
||||||
|
self._load_groups()
|
||||||
|
if len(self._contacts):
|
||||||
|
self.set_active(0)
|
||||||
|
for contact in filter(lambda c: not c.has_avatar(), self._contacts):
|
||||||
|
contact.reset_avatar(self._settings['identicons'])
|
||||||
|
self.update_filtration()
|
||||||
|
|
||||||
|
def _load_friends(self):
|
||||||
|
self._contacts.extend(self._contact_provider.get_all_friends())
|
||||||
|
|
||||||
|
def _load_groups(self):
|
||||||
|
self._contacts.extend(self._contact_provider.get_all_groups())
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Current contact subscriptions
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _subscribe_to_events(self, contact):
|
||||||
|
contact.name_changed_event.add_callback(self._current_contact_name_changed)
|
||||||
|
contact.status_changed_event.add_callback(self._current_contact_status_changed)
|
||||||
|
contact.status_message_changed_event.add_callback(self._current_contact_status_message_changed)
|
||||||
|
contact.avatar_changed_event.add_callback(self._current_contact_avatar_changed)
|
||||||
|
|
||||||
|
def _unsubscribe_from_events(self, contact):
|
||||||
|
contact.name_changed_event.remove_callback(self._current_contact_name_changed)
|
||||||
|
contact.status_changed_event.remove_callback(self._current_contact_status_changed)
|
||||||
|
contact.status_message_changed_event.remove_callback(self._current_contact_status_message_changed)
|
||||||
|
contact.avatar_changed_event.remove_callback(self._current_contact_avatar_changed)
|
||||||
|
|
||||||
|
def _current_contact_name_changed(self, name):
|
||||||
|
self._screen.account_name.setText(name)
|
||||||
|
|
||||||
|
def _current_contact_status_changed(self, status):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _current_contact_status_message_changed(self, status_message):
|
||||||
|
self._screen.account_status.setText(status_message)
|
||||||
|
|
||||||
|
def _current_contact_avatar_changed(self, avatar_path):
|
||||||
|
self._set_current_contact_avatar(avatar_path)
|
||||||
|
|
||||||
|
def _set_current_contact_data(self, contact):
|
||||||
|
self._screen.account_name.setText(contact.name)
|
||||||
|
self._screen.account_status.setText(contact.status_message)
|
||||||
|
self._set_current_contact_avatar(contact.get_avatar_path())
|
||||||
|
|
||||||
|
def _set_current_contact_avatar(self, avatar_path):
|
||||||
|
width = self._screen.account_avatar.width()
|
||||||
|
pixmap = QtGui.QPixmap(avatar_path)
|
||||||
|
self._screen.account_avatar.setPixmap(pixmap.scaled(width, width,
|
||||||
|
QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))
|
||||||
|
|
||||||
|
def _add_friend(self, tox_id):
|
||||||
|
self._history.add_friend_to_db(tox_id)
|
||||||
|
friend = self._contact_provider.get_friend_by_public_key(tox_id)
|
||||||
|
index = len(self._contacts)
|
||||||
|
self._contacts.append(friend)
|
||||||
|
if not friend.has_avatar():
|
||||||
|
friend.reset_avatar(self._settings['identicons'])
|
||||||
|
self._save_profile()
|
||||||
|
self.set_active(index)
|
||||||
|
|
||||||
|
def _save_profile(self):
|
||||||
|
data = self._tox.get_savedata()
|
||||||
|
self._profile_manager.save_profile(data)
|
||||||
|
|
||||||
|
def _cleanup_contact_data(self, contact):
|
||||||
|
try:
|
||||||
|
index = list(map(lambda x: x[0], self._settings['friends_aliases'])).index(contact.tox_id)
|
||||||
|
del self._settings['friends_aliases'][index]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if contact.tox_id in self._settings['notes']:
|
||||||
|
del self._settings['notes'][contact.tox_id]
|
||||||
|
self._settings.save()
|
||||||
|
self._history.delete_history(contact)
|
||||||
|
if contact.has_avatar():
|
||||||
|
avatar_path = contact.get_contact_avatar_path()
|
||||||
|
remove(avatar_path)
|
||||||
|
|
||||||
|
def _delete_contact(self, num):
|
||||||
|
self.set_active(-1 if len(self._contacts) == 1 else 0)
|
||||||
|
|
||||||
|
self._contact_provider.remove_contact_from_cache(self._contacts[num].tox_id)
|
||||||
|
del self._contacts[num]
|
||||||
|
self._screen.friends_list.takeItem(num)
|
||||||
|
self._save_profile()
|
||||||
|
|
||||||
|
self.update_filtration()
|
74
toxygen/contacts/friend.py
Normal file
74
toxygen/contacts/friend.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
from contacts import contact, common
|
||||||
|
from messenger.messages import *
|
||||||
|
import os
|
||||||
|
from contacts.contact_menu import *
|
||||||
|
|
||||||
|
|
||||||
|
class Friend(contact.Contact):
|
||||||
|
"""
|
||||||
|
Friend in list of friends.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, profile_manager, message_getter, number, name, status_message, widget, tox_id):
|
||||||
|
super().__init__(profile_manager, message_getter, number, name, status_message, widget, tox_id)
|
||||||
|
self._receipts = 0
|
||||||
|
self._typing_notification_handler = common.FriendTypingNotificationHandler(number)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# File transfers support
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def insert_inline(self, before_message_id, inline):
|
||||||
|
"""
|
||||||
|
Update status of active transfer and load inline if needed
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
tr = list(filter(lambda m: m.message_id == before_message_id, self._corr))[0]
|
||||||
|
i = self._corr.index(tr)
|
||||||
|
if inline: # inline was loaded
|
||||||
|
self._corr.insert(i, inline)
|
||||||
|
return i - len(self._corr)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_unsent_files(self):
|
||||||
|
messages = filter(lambda m: type(m) is UnsentFileMessage, self._corr)
|
||||||
|
return list(messages)
|
||||||
|
|
||||||
|
def clear_unsent_files(self):
|
||||||
|
self._corr = list(filter(lambda m: type(m) is not UnsentFileMessage, self._corr))
|
||||||
|
|
||||||
|
def remove_invalid_unsent_files(self):
|
||||||
|
def is_valid(message):
|
||||||
|
if type(message) is not UnsentFileMessage:
|
||||||
|
return True
|
||||||
|
if message.data is not None:
|
||||||
|
return True
|
||||||
|
return os.path.exists(message.path)
|
||||||
|
|
||||||
|
self._corr = list(filter(is_valid, self._corr))
|
||||||
|
|
||||||
|
def delete_one_unsent_file(self, message_id):
|
||||||
|
self._corr = list(filter(lambda m: not (type(m) is UnsentFileMessage and m.message_id == message_id),
|
||||||
|
self._corr))
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Full status
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_full_status(self):
|
||||||
|
return self._status_message
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Typing notifications
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_typing_notification_handler(self):
|
||||||
|
return self._typing_notification_handler
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Context menu support
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_context_menu_generator(self):
|
||||||
|
return FriendMenuGenerator(self)
|
44
toxygen/contacts/friend_factory.py
Normal file
44
toxygen/contacts/friend_factory.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
from contacts.friend import Friend
|
||||||
|
from common.tox_save import ToxSave
|
||||||
|
|
||||||
|
|
||||||
|
class FriendFactory(ToxSave):
|
||||||
|
|
||||||
|
def __init__(self, profile_manager, settings, tox, db, items_factory):
|
||||||
|
super().__init__(tox)
|
||||||
|
self._profile_manager = profile_manager
|
||||||
|
self._settings = settings
|
||||||
|
self._db = db
|
||||||
|
self._items_factory = items_factory
|
||||||
|
|
||||||
|
def create_friend_by_public_key(self, public_key):
|
||||||
|
friend_number = self._tox.friend_by_public_key(public_key)
|
||||||
|
|
||||||
|
return self.create_friend_by_number(friend_number)
|
||||||
|
|
||||||
|
def create_friend_by_number(self, friend_number):
|
||||||
|
aliases = self._settings['friends_aliases']
|
||||||
|
tox_id = self._tox.friend_get_public_key(friend_number)
|
||||||
|
try:
|
||||||
|
alias = list(filter(lambda x: x[0] == tox_id, aliases))[0][1]
|
||||||
|
except:
|
||||||
|
alias = ''
|
||||||
|
item = self._create_friend_item()
|
||||||
|
name = alias or self._tox.friend_get_name(friend_number) or tox_id
|
||||||
|
status_message = self._tox.friend_get_status_message(friend_number)
|
||||||
|
message_getter = self._db.messages_getter(tox_id)
|
||||||
|
friend = Friend(self._profile_manager, message_getter, friend_number, name, status_message, item, tox_id)
|
||||||
|
friend.set_alias(alias)
|
||||||
|
|
||||||
|
return friend
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Private methods
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _create_friend_item(self):
|
||||||
|
"""
|
||||||
|
Method-factory
|
||||||
|
:return: new widget for friend instance
|
||||||
|
"""
|
||||||
|
return self._items_factory.create_contact_item()
|
137
toxygen/contacts/group_chat.py
Normal file
137
toxygen/contacts/group_chat.py
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
from contacts import contact
|
||||||
|
from contacts.contact_menu import GroupMenuGenerator
|
||||||
|
import utils.util as util
|
||||||
|
from groups.group_peer import GroupChatPeer
|
||||||
|
from wrapper import toxcore_enums_and_consts as constants
|
||||||
|
from common.tox_save import ToxSave
|
||||||
|
from groups.group_ban import GroupBan
|
||||||
|
|
||||||
|
|
||||||
|
class GroupChat(contact.Contact, ToxSave):
|
||||||
|
|
||||||
|
def __init__(self, tox, profile_manager, message_getter, number, name, status_message, widget, tox_id, is_private):
|
||||||
|
super().__init__(profile_manager, message_getter, number, name, status_message, widget, tox_id)
|
||||||
|
ToxSave.__init__(self, tox)
|
||||||
|
|
||||||
|
self._is_private = is_private
|
||||||
|
self._password = str()
|
||||||
|
self._peers_limit = 512
|
||||||
|
self._peers = []
|
||||||
|
self._add_self_to_gc()
|
||||||
|
|
||||||
|
def remove_invalid_unsent_files(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_context_menu_generator(self):
|
||||||
|
return GroupMenuGenerator(self)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Properties
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_is_private(self):
|
||||||
|
return self._is_private
|
||||||
|
|
||||||
|
def set_is_private(self, is_private):
|
||||||
|
self._is_private = is_private
|
||||||
|
|
||||||
|
is_private = property(get_is_private, set_is_private)
|
||||||
|
|
||||||
|
def get_password(self):
|
||||||
|
return self._password
|
||||||
|
|
||||||
|
def set_password(self, password):
|
||||||
|
self._password = password
|
||||||
|
|
||||||
|
password = property(get_password, set_password)
|
||||||
|
|
||||||
|
def get_peers_limit(self):
|
||||||
|
return self._peers_limit
|
||||||
|
|
||||||
|
def set_peers_limit(self, peers_limit):
|
||||||
|
self._peers_limit = peers_limit
|
||||||
|
|
||||||
|
peers_limit = property(get_peers_limit, set_peers_limit)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Peers methods
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_self_peer(self):
|
||||||
|
return self._peers[0]
|
||||||
|
|
||||||
|
def get_self_name(self):
|
||||||
|
return self._peers[0].name
|
||||||
|
|
||||||
|
def get_self_role(self):
|
||||||
|
return self._peers[0].role
|
||||||
|
|
||||||
|
def is_self_moderator_or_founder(self):
|
||||||
|
return self.get_self_role() <= constants.TOX_GROUP_ROLE['MODERATOR']
|
||||||
|
|
||||||
|
def is_self_founder(self):
|
||||||
|
return self.get_self_role() == constants.TOX_GROUP_ROLE['FOUNDER']
|
||||||
|
|
||||||
|
def add_peer(self, peer_id, is_current_user=False):
|
||||||
|
peer = GroupChatPeer(peer_id,
|
||||||
|
self._tox.group_peer_get_name(self._number, peer_id),
|
||||||
|
self._tox.group_peer_get_status(self._number, peer_id),
|
||||||
|
self._tox.group_peer_get_role(self._number, peer_id),
|
||||||
|
self._tox.group_peer_get_public_key(self._number, peer_id),
|
||||||
|
is_current_user)
|
||||||
|
self._peers.append(peer)
|
||||||
|
|
||||||
|
def remove_peer(self, peer_id):
|
||||||
|
if peer_id == self.get_self_peer().id: # we were kicked or banned
|
||||||
|
self.remove_all_peers_except_self()
|
||||||
|
else:
|
||||||
|
peer = self.get_peer_by_id(peer_id)
|
||||||
|
self._peers.remove(peer)
|
||||||
|
|
||||||
|
def get_peer_by_id(self, peer_id):
|
||||||
|
peers = list(filter(lambda p: p.id == peer_id, self._peers))
|
||||||
|
|
||||||
|
return peers[0]
|
||||||
|
|
||||||
|
def get_peer_by_public_key(self, public_key):
|
||||||
|
peers = list(filter(lambda p: p.public_key == public_key, self._peers))
|
||||||
|
|
||||||
|
return peers[0]
|
||||||
|
|
||||||
|
def remove_all_peers_except_self(self):
|
||||||
|
self._peers = self._peers[:1]
|
||||||
|
|
||||||
|
def get_peers_names(self):
|
||||||
|
peers_names = map(lambda p: p.name, self._peers)
|
||||||
|
|
||||||
|
return list(peers_names)
|
||||||
|
|
||||||
|
def get_peers(self):
|
||||||
|
return self._peers[:]
|
||||||
|
|
||||||
|
peers = property(get_peers)
|
||||||
|
|
||||||
|
def get_bans(self):
|
||||||
|
ban_ids = self._tox.group_ban_get_list(self._number)
|
||||||
|
bans = []
|
||||||
|
for ban_id in ban_ids:
|
||||||
|
ban = GroupBan(ban_id,
|
||||||
|
self._tox.group_ban_get_target(self._number, ban_id),
|
||||||
|
self._tox.group_ban_get_time_set(self._number, ban_id))
|
||||||
|
bans.append(ban)
|
||||||
|
|
||||||
|
return bans
|
||||||
|
|
||||||
|
bans = property(get_bans)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Private methods
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_default_avatar_path():
|
||||||
|
return util.join_path(util.get_images_directory(), 'group.png')
|
||||||
|
|
||||||
|
def _add_self_to_gc(self):
|
||||||
|
peer_id = self._tox.group_self_get_peer_id(self._number)
|
||||||
|
self.add_peer(peer_id, True)
|
53
toxygen/contacts/group_factory.py
Normal file
53
toxygen/contacts/group_factory.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
from contacts.group_chat import GroupChat
|
||||||
|
from common.tox_save import ToxSave
|
||||||
|
import wrapper.toxcore_enums_and_consts as constants
|
||||||
|
|
||||||
|
|
||||||
|
class GroupFactory(ToxSave):
|
||||||
|
|
||||||
|
def __init__(self, profile_manager, settings, tox, db, items_factory):
|
||||||
|
super().__init__(tox)
|
||||||
|
self._profile_manager = profile_manager
|
||||||
|
self._settings = settings
|
||||||
|
self._db = db
|
||||||
|
self._items_factory = items_factory
|
||||||
|
|
||||||
|
def create_group_by_public_key(self, public_key):
|
||||||
|
group_number = self._get_group_number_by_chat_id(public_key)
|
||||||
|
|
||||||
|
return self.create_group_by_number(group_number)
|
||||||
|
|
||||||
|
def create_group_by_number(self, group_number):
|
||||||
|
aliases = self._settings['friends_aliases']
|
||||||
|
tox_id = self._tox.group_get_chat_id(group_number)
|
||||||
|
try:
|
||||||
|
alias = list(filter(lambda x: x[0] == tox_id, aliases))[0][1]
|
||||||
|
except:
|
||||||
|
alias = ''
|
||||||
|
item = self._create_group_item()
|
||||||
|
name = alias or self._tox.group_get_name(group_number) or tox_id
|
||||||
|
status_message = self._tox.group_get_topic(group_number)
|
||||||
|
message_getter = self._db.messages_getter(tox_id)
|
||||||
|
is_private = self._tox.group_get_privacy_state(group_number) == constants.TOX_GROUP_PRIVACY_STATE['PRIVATE']
|
||||||
|
group = GroupChat(self._tox, self._profile_manager, message_getter, group_number, name, status_message,
|
||||||
|
item, tox_id, is_private)
|
||||||
|
group.set_alias(alias)
|
||||||
|
|
||||||
|
return group
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Private methods
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _create_group_item(self):
|
||||||
|
"""
|
||||||
|
Method-factory
|
||||||
|
:return: new widget for group instance
|
||||||
|
"""
|
||||||
|
return self._items_factory.create_contact_item()
|
||||||
|
|
||||||
|
def _get_group_number_by_chat_id(self, chat_id):
|
||||||
|
for i in range(self._tox.group_get_number_groups()):
|
||||||
|
if self._tox.group_get_chat_id(i) == chat_id:
|
||||||
|
return i
|
||||||
|
return -1
|
20
toxygen/contacts/group_peer_contact.py
Normal file
20
toxygen/contacts/group_peer_contact.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import contacts.contact
|
||||||
|
from contacts.contact_menu import GroupPeerMenuGenerator
|
||||||
|
|
||||||
|
|
||||||
|
class GroupPeerContact(contacts.contact.Contact):
|
||||||
|
|
||||||
|
def __init__(self, profile_manager, message_getter, peer_number, name, widget, tox_id, group_pk):
|
||||||
|
super().__init__(profile_manager, message_getter, peer_number, name, str(), widget, tox_id)
|
||||||
|
self._group_pk = group_pk
|
||||||
|
|
||||||
|
def get_group_pk(self):
|
||||||
|
return self._group_pk
|
||||||
|
|
||||||
|
group_pk = property(get_group_pk)
|
||||||
|
|
||||||
|
def remove_invalid_unsent_files(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_context_menu_generator(self):
|
||||||
|
return GroupPeerMenuGenerator(self)
|
23
toxygen/contacts/group_peer_factory.py
Normal file
23
toxygen/contacts/group_peer_factory.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from common.tox_save import ToxSave
|
||||||
|
from contacts.group_peer_contact import GroupPeerContact
|
||||||
|
|
||||||
|
|
||||||
|
class GroupPeerFactory(ToxSave):
|
||||||
|
|
||||||
|
def __init__(self, tox, profile_manager, db, items_factory):
|
||||||
|
super().__init__(tox)
|
||||||
|
self._profile_manager = profile_manager
|
||||||
|
self._db = db
|
||||||
|
self._items_factory = items_factory
|
||||||
|
|
||||||
|
def create_group_peer(self, group, peer):
|
||||||
|
item = self._create_group_peer_item()
|
||||||
|
message_getter = self._db.messages_getter(peer.public_key)
|
||||||
|
group_peer_contact = GroupPeerContact(self._profile_manager, message_getter, peer.id, peer.name,
|
||||||
|
item, peer.public_key, group.tox_id)
|
||||||
|
group_peer_contact.status = peer.status
|
||||||
|
|
||||||
|
return group_peer_contact
|
||||||
|
|
||||||
|
def _create_group_peer_item(self):
|
||||||
|
return self._items_factory.create_contact_item()
|
87
toxygen/contacts/profile.py
Normal file
87
toxygen/contacts/profile.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
from contacts import basecontact
|
||||||
|
import random
|
||||||
|
import threading
|
||||||
|
import common.tox_save as tox_save
|
||||||
|
from middleware.threads import invoke_in_main_thread
|
||||||
|
|
||||||
|
|
||||||
|
class Profile(basecontact.BaseContact, tox_save.ToxSave):
|
||||||
|
"""
|
||||||
|
Profile of current toxygen user.
|
||||||
|
"""
|
||||||
|
def __init__(self, profile_manager, tox, screen, contacts_provider, reset_action):
|
||||||
|
"""
|
||||||
|
:param tox: tox instance
|
||||||
|
:param screen: ref to main screen
|
||||||
|
"""
|
||||||
|
basecontact.BaseContact.__init__(self,
|
||||||
|
profile_manager,
|
||||||
|
tox.self_get_name(),
|
||||||
|
tox.self_get_status_message(),
|
||||||
|
screen,
|
||||||
|
tox.self_get_address())
|
||||||
|
tox_save.ToxSave.__init__(self, tox)
|
||||||
|
self._screen = screen
|
||||||
|
self._messages = screen.messages
|
||||||
|
self._contacts_provider = contacts_provider
|
||||||
|
self._reset_action = reset_action
|
||||||
|
self._waiting_for_reconnection = False
|
||||||
|
self._timer = None
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Edit current user's data
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def change_status(self):
|
||||||
|
"""
|
||||||
|
Changes status of user (online, away, busy)
|
||||||
|
"""
|
||||||
|
if self._status is not None:
|
||||||
|
self.set_status((self._status + 1) % 3)
|
||||||
|
|
||||||
|
def set_status(self, status):
|
||||||
|
super().set_status(status)
|
||||||
|
if status is not None:
|
||||||
|
self._tox.self_set_status(status)
|
||||||
|
elif not self._waiting_for_reconnection:
|
||||||
|
self._waiting_for_reconnection = True
|
||||||
|
self._timer = threading.Timer(50, self._reconnect)
|
||||||
|
self._timer.start()
|
||||||
|
|
||||||
|
def set_name(self, value):
|
||||||
|
if self.name == value:
|
||||||
|
return
|
||||||
|
super().set_name(value)
|
||||||
|
self._tox.self_set_name(self._name)
|
||||||
|
|
||||||
|
def set_status_message(self, value):
|
||||||
|
super().set_status_message(value)
|
||||||
|
self._tox.self_set_status_message(self._status_message)
|
||||||
|
|
||||||
|
def set_new_nospam(self):
|
||||||
|
"""Sets new nospam part of tox id"""
|
||||||
|
self._tox.self_set_nospam(random.randint(0, 4294967295)) # no spam - uint32
|
||||||
|
self._tox_id = self._tox.self_get_address()
|
||||||
|
|
||||||
|
return self._tox_id
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Reset
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def restart(self):
|
||||||
|
"""
|
||||||
|
Recreate tox instance
|
||||||
|
"""
|
||||||
|
self.status = None
|
||||||
|
invoke_in_main_thread(self._reset_action)
|
||||||
|
|
||||||
|
def _reconnect(self):
|
||||||
|
self._waiting_for_reconnection = False
|
||||||
|
contacts = self._contacts_provider.get_all_friends()
|
||||||
|
all_friends_offline = all(list(map(lambda x: x.status is None, contacts)))
|
||||||
|
if self.status is None or (all_friends_offline and len(contacts)):
|
||||||
|
self._waiting_for_reconnection = True
|
||||||
|
self.restart()
|
||||||
|
self._timer = threading.Timer(50, self._reconnect)
|
||||||
|
self._timer.start()
|
0
toxygen/file_transfers/__init__.py
Normal file
0
toxygen/file_transfers/__init__.py
Normal file
@ -1,20 +1,21 @@
|
|||||||
from toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL
|
from wrapper.toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL
|
||||||
from os.path import basename, getsize, exists, dirname
|
from os.path import basename, getsize, exists, dirname
|
||||||
from os import remove, rename, chdir
|
from os import remove, rename, chdir
|
||||||
from time import time, sleep
|
from time import time
|
||||||
from tox import Tox
|
from wrapper.tox import Tox
|
||||||
import settings
|
from common.event import Event
|
||||||
from PyQt5 import QtCore
|
from middleware.threads import invoke_in_main_thread
|
||||||
|
|
||||||
|
|
||||||
TOX_FILE_TRANSFER_STATE = {
|
FILE_TRANSFER_STATE = {
|
||||||
'RUNNING': 0,
|
'RUNNING': 0,
|
||||||
'PAUSED_BY_USER': 1,
|
'PAUSED_BY_USER': 1,
|
||||||
'CANCELLED': 2,
|
'CANCELLED': 2,
|
||||||
'FINISHED': 3,
|
'FINISHED': 3,
|
||||||
'PAUSED_BY_FRIEND': 4,
|
'PAUSED_BY_FRIEND': 4,
|
||||||
'INCOMING_NOT_STARTED': 5,
|
'INCOMING_NOT_STARTED': 5,
|
||||||
'OUTGOING_NOT_STARTED': 6
|
'OUTGOING_NOT_STARTED': 6,
|
||||||
|
'UNSENT': 7
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTIVE_FILE_TRANSFERS = (0, 1, 4, 5, 6)
|
ACTIVE_FILE_TRANSFERS = (0, 1, 4, 5, 6)
|
||||||
@ -25,102 +26,106 @@ DO_NOT_SHOW_ACCEPT_BUTTON = (2, 3, 4, 6)
|
|||||||
|
|
||||||
SHOW_PROGRESS_BAR = (0, 1, 4)
|
SHOW_PROGRESS_BAR = (0, 1, 4)
|
||||||
|
|
||||||
ALLOWED_FILES = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png')
|
|
||||||
|
|
||||||
|
|
||||||
def is_inline(file_name):
|
def is_inline(file_name):
|
||||||
return file_name in ALLOWED_FILES or file_name.startswith('qTox_Screenshot_') or file_name.startswith('qTox_Image_')
|
allowed_inlines = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png')
|
||||||
|
|
||||||
|
return file_name in allowed_inlines or file_name.startswith('qTox_Image_')
|
||||||
|
|
||||||
|
|
||||||
class StateSignal(QtCore.QObject):
|
class FileTransfer:
|
||||||
|
|
||||||
signal = QtCore.pyqtSignal(int, float, int) # state, progress, time in sec
|
|
||||||
|
|
||||||
|
|
||||||
class TransferFinishedSignal(QtCore.QObject):
|
|
||||||
|
|
||||||
signal = QtCore.pyqtSignal(int, int) # friend number, file number
|
|
||||||
|
|
||||||
|
|
||||||
class FileTransfer(QtCore.QObject):
|
|
||||||
"""
|
"""
|
||||||
Superclass for file transfers
|
Superclass for file transfers
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, path, tox, friend_number, size, file_number=None):
|
def __init__(self, path, tox, friend_number, size, file_number=None):
|
||||||
QtCore.QObject.__init__(self)
|
|
||||||
self._path = path
|
self._path = path
|
||||||
self._tox = tox
|
self._tox = tox
|
||||||
self._friend_number = friend_number
|
self._friend_number = friend_number
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['RUNNING']
|
self._state = FILE_TRANSFER_STATE['RUNNING']
|
||||||
self._file_number = file_number
|
self._file_number = file_number
|
||||||
self._creation_time = None
|
self._creation_time = None
|
||||||
self._size = float(size)
|
self._size = float(size)
|
||||||
self._done = 0
|
self._done = 0
|
||||||
self._state_changed = StateSignal()
|
self._state_changed_event = Event()
|
||||||
self._finished = TransferFinishedSignal()
|
self._finished_event = Event()
|
||||||
self._file_id = None
|
self._file_id = self._file = None
|
||||||
|
|
||||||
def set_tox(self, tox):
|
|
||||||
self._tox = tox
|
|
||||||
|
|
||||||
def set_state_changed_handler(self, handler):
|
def set_state_changed_handler(self, handler):
|
||||||
self._state_changed.signal.connect(handler)
|
self._state_changed_event += lambda *args: invoke_in_main_thread(handler, *args)
|
||||||
|
|
||||||
def set_transfer_finished_handler(self, handler):
|
def set_transfer_finished_handler(self, handler):
|
||||||
self._finished.signal.connect(handler)
|
self._finished_event += lambda *args: invoke_in_main_thread(handler, *args)
|
||||||
|
|
||||||
def signal(self):
|
|
||||||
percentage = self._done / self._size if self._size else 0
|
|
||||||
if self._creation_time is None or not percentage:
|
|
||||||
t = -1
|
|
||||||
else:
|
|
||||||
t = ((time() - self._creation_time) / percentage) * (1 - percentage)
|
|
||||||
self._state_changed.signal.emit(self.state, percentage, int(t))
|
|
||||||
|
|
||||||
def finished(self):
|
|
||||||
self._finished.signal.emit(self._friend_number, self._file_number)
|
|
||||||
|
|
||||||
def get_file_number(self):
|
def get_file_number(self):
|
||||||
return self._file_number
|
return self._file_number
|
||||||
|
|
||||||
|
file_number = property(get_file_number)
|
||||||
|
|
||||||
|
def get_state(self):
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
def set_state(self, value):
|
||||||
|
self._state = value
|
||||||
|
self._signal()
|
||||||
|
|
||||||
|
state = property(get_state, set_state)
|
||||||
|
|
||||||
def get_friend_number(self):
|
def get_friend_number(self):
|
||||||
return self._friend_number
|
return self._friend_number
|
||||||
|
|
||||||
def get_id(self):
|
friend_number = property(get_friend_number)
|
||||||
|
|
||||||
|
def get_file_id(self):
|
||||||
return self._file_id
|
return self._file_id
|
||||||
|
|
||||||
|
file_id = property(get_file_id)
|
||||||
|
|
||||||
def get_path(self):
|
def get_path(self):
|
||||||
return self._path
|
return self._path
|
||||||
|
|
||||||
|
path = property(get_path)
|
||||||
|
|
||||||
|
def get_size(self):
|
||||||
|
return self._size
|
||||||
|
|
||||||
|
size = property(get_size)
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self):
|
||||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
||||||
if hasattr(self, '_file'):
|
if self._file is not None:
|
||||||
self._file.close()
|
self._file.close()
|
||||||
self.signal()
|
self._signal()
|
||||||
|
|
||||||
def cancelled(self):
|
def cancelled(self):
|
||||||
if hasattr(self, '_file'):
|
if self._file is not None:
|
||||||
sleep(0.1)
|
|
||||||
self._file.close()
|
self._file.close()
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['CANCELLED']
|
self.set_state(FILE_TRANSFER_STATE['CANCELLED'])
|
||||||
self.signal()
|
|
||||||
|
|
||||||
def pause(self, by_friend):
|
def pause(self, by_friend):
|
||||||
if not by_friend:
|
if not by_friend:
|
||||||
self.send_control(TOX_FILE_CONTROL['PAUSE'])
|
self.send_control(TOX_FILE_CONTROL['PAUSE'])
|
||||||
else:
|
else:
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND']
|
self.set_state(FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'])
|
||||||
self.signal()
|
|
||||||
|
|
||||||
def send_control(self, control):
|
def send_control(self, control):
|
||||||
if self._tox.file_control(self._friend_number, self._file_number, control):
|
if self._tox.file_control(self._friend_number, self._file_number, control):
|
||||||
self.state = control
|
self.set_state(control)
|
||||||
self.signal()
|
|
||||||
|
|
||||||
def get_file_id(self):
|
def get_file_id(self):
|
||||||
return self._tox.file_get_file_id(self._friend_number, self._file_number)
|
return self._tox.file_get_file_id(self._friend_number, self._file_number)
|
||||||
|
|
||||||
|
def _signal(self):
|
||||||
|
percentage = self._done / self._size if self._size else 0
|
||||||
|
if self._creation_time is None or not percentage:
|
||||||
|
t = -1
|
||||||
|
else:
|
||||||
|
t = ((time() - self._creation_time) / percentage) * (1 - percentage)
|
||||||
|
self._state_changed_event(self.state, percentage, int(t))
|
||||||
|
|
||||||
|
def _finished(self):
|
||||||
|
self._finished_event(self._friend_number, self._file_number)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Send file
|
# Send file
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
@ -130,12 +135,14 @@ class SendTransfer(FileTransfer):
|
|||||||
|
|
||||||
def __init__(self, path, tox, friend_number, kind=TOX_FILE_KIND['DATA'], file_id=None):
|
def __init__(self, path, tox, friend_number, kind=TOX_FILE_KIND['DATA'], file_id=None):
|
||||||
if path is not None:
|
if path is not None:
|
||||||
self._file = open(path, 'rb')
|
fl = open(path, 'rb')
|
||||||
size = getsize(path)
|
size = getsize(path)
|
||||||
else:
|
else:
|
||||||
|
fl = None
|
||||||
size = 0
|
size = 0
|
||||||
super(SendTransfer, self).__init__(path, tox, friend_number, size)
|
super().__init__(path, tox, friend_number, size)
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
|
self._file = fl
|
||||||
|
self.state = FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
|
||||||
self._file_number = tox.file_send(friend_number, kind, size, file_id,
|
self._file_number = tox.file_send(friend_number, kind, size, file_id,
|
||||||
bytes(basename(path), 'utf-8') if path else b'')
|
bytes(basename(path), 'utf-8') if path else b'')
|
||||||
self._file_id = self.get_file_id()
|
self._file_id = self.get_file_id()
|
||||||
@ -153,12 +160,12 @@ class SendTransfer(FileTransfer):
|
|||||||
data = self._file.read(size)
|
data = self._file.read(size)
|
||||||
self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
|
self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
|
||||||
self._done += size
|
self._done += size
|
||||||
|
self._signal()
|
||||||
else:
|
else:
|
||||||
if hasattr(self, '_file'):
|
if self._file is not None:
|
||||||
self._file.close()
|
self._file.close()
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
self.state = FILE_TRANSFER_STATE['FINISHED']
|
||||||
self.finished()
|
self._finished()
|
||||||
self.signal()
|
|
||||||
|
|
||||||
|
|
||||||
class SendAvatar(SendTransfer):
|
class SendAvatar(SendTransfer):
|
||||||
@ -168,11 +175,11 @@ class SendAvatar(SendTransfer):
|
|||||||
|
|
||||||
def __init__(self, path, tox, friend_number):
|
def __init__(self, path, tox, friend_number):
|
||||||
if path is None:
|
if path is None:
|
||||||
hash = None
|
avatar_hash = None
|
||||||
else:
|
else:
|
||||||
with open(path, 'rb') as fl:
|
with open(path, 'rb') as fl:
|
||||||
hash = Tox.hash(fl.read())
|
avatar_hash = Tox.hash(fl.read())
|
||||||
super(SendAvatar, self).__init__(path, tox, friend_number, TOX_FILE_KIND['AVATAR'], hash)
|
super().__init__(path, tox, friend_number, TOX_FILE_KIND['AVATAR'], avatar_hash)
|
||||||
|
|
||||||
|
|
||||||
class SendFromBuffer(FileTransfer):
|
class SendFromBuffer(FileTransfer):
|
||||||
@ -181,8 +188,8 @@ class SendFromBuffer(FileTransfer):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, tox, friend_number, data, file_name):
|
def __init__(self, tox, friend_number, data, file_name):
|
||||||
super(SendFromBuffer, self).__init__(None, tox, friend_number, len(data))
|
super().__init__(None, tox, friend_number, len(data))
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
|
self.state = FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
|
||||||
self._data = data
|
self._data = data
|
||||||
self._file_number = tox.file_send(friend_number, TOX_FILE_KIND['DATA'],
|
self._file_number = tox.file_send(friend_number, TOX_FILE_KIND['DATA'],
|
||||||
len(data), None, bytes(file_name, 'utf-8'))
|
len(data), None, bytes(file_name, 'utf-8'))
|
||||||
@ -190,6 +197,8 @@ class SendFromBuffer(FileTransfer):
|
|||||||
def get_data(self):
|
def get_data(self):
|
||||||
return self._data
|
return self._data
|
||||||
|
|
||||||
|
data = property(get_data)
|
||||||
|
|
||||||
def send_chunk(self, position, size):
|
def send_chunk(self, position, size):
|
||||||
if self._creation_time is None:
|
if self._creation_time is None:
|
||||||
self._creation_time = time()
|
self._creation_time = time()
|
||||||
@ -198,18 +207,18 @@ class SendFromBuffer(FileTransfer):
|
|||||||
self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
|
self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
|
||||||
self._done += size
|
self._done += size
|
||||||
else:
|
else:
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
self.state = FILE_TRANSFER_STATE['FINISHED']
|
||||||
self.finished()
|
self._finished()
|
||||||
self.signal()
|
self._signal()
|
||||||
|
|
||||||
|
|
||||||
class SendFromFileBuffer(SendTransfer):
|
class SendFromFileBuffer(SendTransfer):
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
super(SendFromFileBuffer, self).__init__(*args)
|
super().__init__(*args)
|
||||||
|
|
||||||
def send_chunk(self, position, size):
|
def send_chunk(self, position, size):
|
||||||
super(SendFromFileBuffer, self).send_chunk(position, size)
|
super().send_chunk(position, size)
|
||||||
if not size:
|
if not size:
|
||||||
chdir(dirname(self._path))
|
chdir(dirname(self._path))
|
||||||
remove(self._path)
|
remove(self._path)
|
||||||
@ -222,7 +231,7 @@ class SendFromFileBuffer(SendTransfer):
|
|||||||
class ReceiveTransfer(FileTransfer):
|
class ReceiveTransfer(FileTransfer):
|
||||||
|
|
||||||
def __init__(self, path, tox, friend_number, size, file_number, position=0):
|
def __init__(self, path, tox, friend_number, size, file_number, position=0):
|
||||||
super(ReceiveTransfer, self).__init__(path, tox, friend_number, size, file_number)
|
super().__init__(path, tox, friend_number, size, file_number)
|
||||||
self._file = open(self._path, 'wb')
|
self._file = open(self._path, 'wb')
|
||||||
self._file_size = position
|
self._file_size = position
|
||||||
self._file.truncate(position)
|
self._file.truncate(position)
|
||||||
@ -231,11 +240,12 @@ class ReceiveTransfer(FileTransfer):
|
|||||||
self._done = position
|
self._done = position
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self):
|
||||||
super(ReceiveTransfer, self).cancel()
|
super().cancel()
|
||||||
remove(self._path)
|
remove(self._path)
|
||||||
|
|
||||||
def total_size(self):
|
def total_size(self):
|
||||||
self._missed.add(self._file_size)
|
self._missed.add(self._file_size)
|
||||||
|
|
||||||
return min(self._missed)
|
return min(self._missed)
|
||||||
|
|
||||||
def write_chunk(self, position, data):
|
def write_chunk(self, position, data):
|
||||||
@ -248,8 +258,8 @@ class ReceiveTransfer(FileTransfer):
|
|||||||
self._creation_time = time()
|
self._creation_time = time()
|
||||||
if data is None:
|
if data is None:
|
||||||
self._file.close()
|
self._file.close()
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
self.state = FILE_TRANSFER_STATE['FINISHED']
|
||||||
self.finished()
|
self._finished()
|
||||||
else:
|
else:
|
||||||
data = bytearray(data)
|
data = bytearray(data)
|
||||||
if self._file_size < position:
|
if self._file_size < position:
|
||||||
@ -264,7 +274,7 @@ class ReceiveTransfer(FileTransfer):
|
|||||||
if position + l > self._file_size:
|
if position + l > self._file_size:
|
||||||
self._file_size = position + l
|
self._file_size = position + l
|
||||||
self._done += l
|
self._done += l
|
||||||
self.signal()
|
self._signal()
|
||||||
|
|
||||||
|
|
||||||
class ReceiveToBuffer(FileTransfer):
|
class ReceiveToBuffer(FileTransfer):
|
||||||
@ -273,19 +283,21 @@ class ReceiveToBuffer(FileTransfer):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, tox, friend_number, size, file_number):
|
def __init__(self, tox, friend_number, size, file_number):
|
||||||
super(ReceiveToBuffer, self).__init__(None, tox, friend_number, size, file_number)
|
super().__init__(None, tox, friend_number, size, file_number)
|
||||||
self._data = bytes()
|
self._data = bytes()
|
||||||
self._data_size = 0
|
self._data_size = 0
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
return self._data
|
return self._data
|
||||||
|
|
||||||
|
data = property(get_data)
|
||||||
|
|
||||||
def write_chunk(self, position, data):
|
def write_chunk(self, position, data):
|
||||||
if self._creation_time is None:
|
if self._creation_time is None:
|
||||||
self._creation_time = time()
|
self._creation_time = time()
|
||||||
if data is None:
|
if data is None:
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
self.state = FILE_TRANSFER_STATE['FINISHED']
|
||||||
self.finished()
|
self._finished()
|
||||||
else:
|
else:
|
||||||
data = bytes(data)
|
data = bytes(data)
|
||||||
l = len(data)
|
l = len(data)
|
||||||
@ -295,7 +307,7 @@ class ReceiveToBuffer(FileTransfer):
|
|||||||
if position + l > self._data_size:
|
if position + l > self._data_size:
|
||||||
self._data_size = position + l
|
self._data_size = position + l
|
||||||
self._done += l
|
self._done += l
|
||||||
self.signal()
|
self._signal()
|
||||||
|
|
||||||
|
|
||||||
class ReceiveAvatar(ReceiveTransfer):
|
class ReceiveAvatar(ReceiveTransfer):
|
||||||
@ -304,20 +316,17 @@ class ReceiveAvatar(ReceiveTransfer):
|
|||||||
"""
|
"""
|
||||||
MAX_AVATAR_SIZE = 512 * 1024
|
MAX_AVATAR_SIZE = 512 * 1024
|
||||||
|
|
||||||
def __init__(self, tox, friend_number, size, file_number):
|
def __init__(self, path, tox, friend_number, size, file_number):
|
||||||
path = settings.ProfileHelper.get_path() + 'avatars/{}.png'.format(tox.friend_get_public_key(friend_number))
|
full_path = path + '.tmp'
|
||||||
super(ReceiveAvatar, self).__init__(path + '.tmp', tox, friend_number, size, file_number)
|
super().__init__(full_path, tox, friend_number, size, file_number)
|
||||||
if size > self.MAX_AVATAR_SIZE:
|
if size > self.MAX_AVATAR_SIZE:
|
||||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
||||||
self._file.close()
|
self._file.close()
|
||||||
remove(path + '.tmp')
|
remove(full_path)
|
||||||
elif not size:
|
elif not size:
|
||||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
||||||
self._file.close()
|
self._file.close()
|
||||||
if exists(path):
|
remove(full_path)
|
||||||
remove(path)
|
|
||||||
self._file.close()
|
|
||||||
remove(path + '.tmp')
|
|
||||||
elif exists(path):
|
elif exists(path):
|
||||||
hash = self.get_file_id()
|
hash = self.get_file_id()
|
||||||
with open(path, 'rb') as fl:
|
with open(path, 'rb') as fl:
|
||||||
@ -326,22 +335,17 @@ class ReceiveAvatar(ReceiveTransfer):
|
|||||||
if hash == existing_hash:
|
if hash == existing_hash:
|
||||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
||||||
self._file.close()
|
self._file.close()
|
||||||
remove(path + '.tmp')
|
remove(full_path)
|
||||||
else:
|
else:
|
||||||
self.send_control(TOX_FILE_CONTROL['RESUME'])
|
self.send_control(TOX_FILE_CONTROL['RESUME'])
|
||||||
else:
|
else:
|
||||||
self.send_control(TOX_FILE_CONTROL['RESUME'])
|
self.send_control(TOX_FILE_CONTROL['RESUME'])
|
||||||
|
|
||||||
def write_chunk(self, position, data):
|
def write_chunk(self, position, data):
|
||||||
super(ReceiveAvatar, self).write_chunk(position, data)
|
if data is None:
|
||||||
if self.state:
|
|
||||||
avatar_path = self._path[:-4]
|
avatar_path = self._path[:-4]
|
||||||
if exists(avatar_path):
|
if exists(avatar_path):
|
||||||
chdir(dirname(avatar_path))
|
chdir(dirname(avatar_path))
|
||||||
remove(avatar_path)
|
remove(avatar_path)
|
||||||
rename(self._path, avatar_path)
|
rename(self._path, avatar_path)
|
||||||
self.finished(True)
|
super().write_chunk(position, data)
|
||||||
|
|
||||||
def finished(self, emit=False):
|
|
||||||
if emit:
|
|
||||||
super().finished()
|
|
304
toxygen/file_transfers/file_transfers_handler.py
Normal file
304
toxygen/file_transfers/file_transfers_handler.py
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
from messenger.messages import *
|
||||||
|
from ui.contact_items import *
|
||||||
|
import utils.util as util
|
||||||
|
from common.tox_save import ToxSave
|
||||||
|
|
||||||
|
|
||||||
|
class FileTransfersHandler(ToxSave):
|
||||||
|
|
||||||
|
def __init__(self, tox, settings, contact_provider, file_transfers_message_service, profile):
|
||||||
|
super().__init__(tox)
|
||||||
|
self._settings = settings
|
||||||
|
self._contact_provider = contact_provider
|
||||||
|
self._file_transfers_message_service = file_transfers_message_service
|
||||||
|
self._file_transfers = {}
|
||||||
|
# key = (friend number, file number), value - transfer instance
|
||||||
|
self._paused_file_transfers = dict(settings['paused_file_transfers'])
|
||||||
|
# key - file id, value: [path, friend number, is incoming, start position]
|
||||||
|
self._insert_inline_before = {}
|
||||||
|
# key = (friend number, file number), value - message id
|
||||||
|
|
||||||
|
profile.avatar_changed_event.add_callback(self._send_avatar_to_contacts)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self._settings['paused_file_transfers'] = self._paused_file_transfers if self._settings['resend_files'] else {}
|
||||||
|
self._settings.save()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# File transfers support
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def incoming_file_transfer(self, friend_number, file_number, size, file_name):
|
||||||
|
"""
|
||||||
|
New transfer
|
||||||
|
:param friend_number: number of friend who sent file
|
||||||
|
:param file_number: file number
|
||||||
|
:param size: file size in bytes
|
||||||
|
:param file_name: file name without path
|
||||||
|
"""
|
||||||
|
friend = self._get_friend_by_number(friend_number)
|
||||||
|
auto = self._settings['allow_auto_accept'] and friend.tox_id in self._settings['auto_accept_from_friends']
|
||||||
|
inline = is_inline(file_name) and self._settings['allow_inline']
|
||||||
|
file_id = self._tox.file_get_file_id(friend_number, file_number)
|
||||||
|
accepted = True
|
||||||
|
if file_id in self._paused_file_transfers:
|
||||||
|
(path, ft_friend_number, is_incoming, start_position) = self._paused_file_transfers[file_id]
|
||||||
|
pos = start_position if os.path.exists(path) else 0
|
||||||
|
if pos >= size:
|
||||||
|
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
|
||||||
|
return
|
||||||
|
self._tox.file_seek(friend_number, file_number, pos)
|
||||||
|
self._file_transfers_message_service.add_incoming_transfer_message(
|
||||||
|
friend, accepted, size, file_name, file_number)
|
||||||
|
self.accept_transfer(path, friend_number, file_number, size, False, pos)
|
||||||
|
elif inline and size < 1024 * 1024:
|
||||||
|
self._file_transfers_message_service.add_incoming_transfer_message(
|
||||||
|
friend, accepted, size, file_name, file_number)
|
||||||
|
self.accept_transfer('', friend_number, file_number, size, True)
|
||||||
|
elif auto:
|
||||||
|
path = self._settings['auto_accept_path'] or util.curr_directory()
|
||||||
|
self._file_transfers_message_service.add_incoming_transfer_message(
|
||||||
|
friend, accepted, size, file_name, file_number)
|
||||||
|
self.accept_transfer(path + '/' + file_name, friend_number, file_number, size)
|
||||||
|
else:
|
||||||
|
accepted = False
|
||||||
|
self._file_transfers_message_service.add_incoming_transfer_message(
|
||||||
|
friend, accepted, size, file_name, file_number)
|
||||||
|
|
||||||
|
def cancel_transfer(self, friend_number, file_number, already_cancelled=False):
|
||||||
|
"""
|
||||||
|
Stop transfer
|
||||||
|
:param friend_number: number of friend
|
||||||
|
:param file_number: file number
|
||||||
|
:param already_cancelled: was cancelled by friend
|
||||||
|
"""
|
||||||
|
if (friend_number, file_number) in self._file_transfers:
|
||||||
|
tr = self._file_transfers[(friend_number, file_number)]
|
||||||
|
if not already_cancelled:
|
||||||
|
tr.cancel()
|
||||||
|
else:
|
||||||
|
tr.cancelled()
|
||||||
|
if (friend_number, file_number) in self._file_transfers:
|
||||||
|
del tr
|
||||||
|
del self._file_transfers[(friend_number, file_number)]
|
||||||
|
elif not already_cancelled:
|
||||||
|
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
|
||||||
|
|
||||||
|
def cancel_not_started_transfer(self, friend_number, message_id):
|
||||||
|
self._get_friend_by_number(friend_number).delete_one_unsent_file(message_id)
|
||||||
|
|
||||||
|
def pause_transfer(self, friend_number, file_number, by_friend=False):
|
||||||
|
"""
|
||||||
|
Pause transfer with specified data
|
||||||
|
"""
|
||||||
|
tr = self._file_transfers[(friend_number, file_number)]
|
||||||
|
tr.pause(by_friend)
|
||||||
|
|
||||||
|
def resume_transfer(self, friend_number, file_number, by_friend=False):
|
||||||
|
"""
|
||||||
|
Resume transfer with specified data
|
||||||
|
"""
|
||||||
|
tr = self._file_transfers[(friend_number, file_number)]
|
||||||
|
if by_friend:
|
||||||
|
tr.state = FILE_TRANSFER_STATE['RUNNING']
|
||||||
|
else:
|
||||||
|
tr.send_control(TOX_FILE_CONTROL['RESUME'])
|
||||||
|
|
||||||
|
def accept_transfer(self, path, friend_number, file_number, size, inline=False, from_position=0):
|
||||||
|
"""
|
||||||
|
:param path: path for saving
|
||||||
|
:param friend_number: friend number
|
||||||
|
:param file_number: file number
|
||||||
|
:param size: file size
|
||||||
|
:param inline: is inline image
|
||||||
|
:param from_position: position for start
|
||||||
|
"""
|
||||||
|
path = self._generate_valid_path(path, from_position)
|
||||||
|
friend = self._get_friend_by_number(friend_number)
|
||||||
|
if not inline:
|
||||||
|
rt = ReceiveTransfer(path, self._tox, friend_number, size, file_number, from_position)
|
||||||
|
else:
|
||||||
|
rt = ReceiveToBuffer(self._tox, friend_number, size, file_number)
|
||||||
|
rt.set_transfer_finished_handler(self.transfer_finished)
|
||||||
|
message = friend.get_message(lambda m: m.type == MESSAGE_TYPE['FILE_TRANSFER']
|
||||||
|
and m.state in (FILE_TRANSFER_STATE['INCOMING_NOT_STARTED'],
|
||||||
|
FILE_TRANSFER_STATE['RUNNING'])
|
||||||
|
and m.file_number == file_number)
|
||||||
|
rt.set_state_changed_handler(message.transfer_updated)
|
||||||
|
self._file_transfers[(friend_number, file_number)] = rt
|
||||||
|
rt.send_control(TOX_FILE_CONTROL['RESUME'])
|
||||||
|
if inline:
|
||||||
|
self._insert_inline_before[(friend_number, file_number)] = message.message_id
|
||||||
|
|
||||||
|
def send_screenshot(self, data, friend_number):
|
||||||
|
"""
|
||||||
|
Send screenshot
|
||||||
|
:param data: raw data - png format
|
||||||
|
:param friend_number: friend number
|
||||||
|
"""
|
||||||
|
self.send_inline(data, 'toxygen_inline.png', friend_number)
|
||||||
|
|
||||||
|
def send_sticker(self, path, friend_number):
|
||||||
|
with open(path, 'rb') as fl:
|
||||||
|
data = fl.read()
|
||||||
|
self.send_inline(data, 'sticker.png', friend_number)
|
||||||
|
|
||||||
|
def send_inline(self, data, file_name, friend_number, is_resend=False):
|
||||||
|
friend = self._get_friend_by_number(friend_number)
|
||||||
|
if friend.status is None and not is_resend:
|
||||||
|
self._file_transfers_message_service.add_unsent_file_message(friend, file_name, data)
|
||||||
|
return
|
||||||
|
elif friend.status is None and is_resend:
|
||||||
|
raise RuntimeError()
|
||||||
|
st = SendFromBuffer(self._tox, friend.number, data, file_name)
|
||||||
|
self._send_file_add_set_handlers(st, friend, file_name, True)
|
||||||
|
|
||||||
|
def send_file(self, path, friend_number, is_resend=False, file_id=None):
|
||||||
|
"""
|
||||||
|
Send file to current active friend
|
||||||
|
:param path: file path
|
||||||
|
:param friend_number: friend_number
|
||||||
|
:param is_resend: is 'offline' message
|
||||||
|
:param file_id: file id of transfer
|
||||||
|
"""
|
||||||
|
friend = self._get_friend_by_number(friend_number)
|
||||||
|
if friend.status is None and not is_resend:
|
||||||
|
self._file_transfers_message_service.add_unsent_file_message(friend, path, None)
|
||||||
|
return
|
||||||
|
elif friend.status is None and is_resend:
|
||||||
|
print('Error in sending')
|
||||||
|
return
|
||||||
|
st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id)
|
||||||
|
file_name = os.path.basename(path)
|
||||||
|
self._send_file_add_set_handlers(st, friend, file_name)
|
||||||
|
|
||||||
|
def incoming_chunk(self, friend_number, file_number, position, data):
|
||||||
|
"""
|
||||||
|
Incoming chunk
|
||||||
|
"""
|
||||||
|
self._file_transfers[(friend_number, file_number)].write_chunk(position, data)
|
||||||
|
|
||||||
|
def outgoing_chunk(self, friend_number, file_number, position, size):
|
||||||
|
"""
|
||||||
|
Outgoing chunk
|
||||||
|
"""
|
||||||
|
self._file_transfers[(friend_number, file_number)].send_chunk(position, size)
|
||||||
|
|
||||||
|
def transfer_finished(self, friend_number, file_number):
|
||||||
|
transfer = self._file_transfers[(friend_number, file_number)]
|
||||||
|
t = type(transfer)
|
||||||
|
if t is ReceiveAvatar:
|
||||||
|
self._get_friend_by_number(friend_number).load_avatar()
|
||||||
|
elif t is ReceiveToBuffer or (t is SendFromBuffer and self._settings['allow_inline']): # inline image
|
||||||
|
print('inline')
|
||||||
|
inline = InlineImageMessage(transfer.data)
|
||||||
|
message_id = self._insert_inline_before[(friend_number, file_number)]
|
||||||
|
del self._insert_inline_before[(friend_number, file_number)]
|
||||||
|
index = self._get_friend_by_number(friend_number).insert_inline(message_id, inline)
|
||||||
|
self._file_transfers_message_service.add_inline_message(transfer, index)
|
||||||
|
del self._file_transfers[(friend_number, file_number)]
|
||||||
|
|
||||||
|
def send_files(self, friend_number):
|
||||||
|
friend = self._get_friend_by_number(friend_number)
|
||||||
|
friend.remove_invalid_unsent_files()
|
||||||
|
files = friend.get_unsent_files()
|
||||||
|
try:
|
||||||
|
for fl in files:
|
||||||
|
data, path = fl.data, fl.path
|
||||||
|
if data is not None:
|
||||||
|
self.send_inline(data, path, friend_number, True)
|
||||||
|
else:
|
||||||
|
self.send_file(path, friend_number, True)
|
||||||
|
friend.clear_unsent_files()
|
||||||
|
for key in self._paused_file_transfers.keys():
|
||||||
|
(path, ft_friend_number, is_incoming, start_position) = self._paused_file_transfers[key]
|
||||||
|
if not os.path.exists(path):
|
||||||
|
del self._paused_file_transfers[key]
|
||||||
|
elif ft_friend_number == friend_number and not is_incoming:
|
||||||
|
self.send_file(path, friend_number, True, key)
|
||||||
|
del self._paused_file_transfers[key]
|
||||||
|
except Exception as ex:
|
||||||
|
print('Exception in file sending: ' + str(ex))
|
||||||
|
|
||||||
|
def friend_exit(self, friend_number):
|
||||||
|
for friend_num, file_num in self._file_transfers.keys():
|
||||||
|
if friend_num != friend_number:
|
||||||
|
continue
|
||||||
|
ft = self._file_transfers[(friend_num, file_num)]
|
||||||
|
if type(ft) is SendTransfer:
|
||||||
|
self._paused_file_transfers[ft.file_id] = [ft.path, friend_num, False, -1]
|
||||||
|
elif type(ft) is ReceiveTransfer and ft.state != FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
|
||||||
|
self._paused_file_transfers[ft.file_id] = [ft.path, friend_num, True, ft.total_size()]
|
||||||
|
self.cancel_transfer(friend_num, file_num, True)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Avatars support
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def send_avatar(self, friend_number, avatar_path=None):
|
||||||
|
"""
|
||||||
|
:param friend_number: number of friend who should get new avatar
|
||||||
|
:param avatar_path: path to avatar or None if reset
|
||||||
|
"""
|
||||||
|
sa = SendAvatar(avatar_path, self._tox, friend_number)
|
||||||
|
self._file_transfers[(friend_number, sa.file_number)] = sa
|
||||||
|
|
||||||
|
def incoming_avatar(self, friend_number, file_number, size):
|
||||||
|
"""
|
||||||
|
Friend changed avatar
|
||||||
|
:param friend_number: friend number
|
||||||
|
:param file_number: file number
|
||||||
|
:param size: size of avatar or 0 (default avatar)
|
||||||
|
"""
|
||||||
|
friend = self._get_friend_by_number(friend_number)
|
||||||
|
ra = ReceiveAvatar(friend.get_contact_avatar_path(), self._tox, friend_number, size, file_number)
|
||||||
|
if ra.state != FILE_TRANSFER_STATE['CANCELLED']:
|
||||||
|
self._file_transfers[(friend_number, file_number)] = ra
|
||||||
|
ra.set_transfer_finished_handler(self.transfer_finished)
|
||||||
|
elif not size:
|
||||||
|
friend.reset_avatar(self._settings['identicons'])
|
||||||
|
|
||||||
|
def _send_avatar_to_contacts(self, _):
|
||||||
|
friends = self._get_all_friends()
|
||||||
|
for friend in filter(self._is_friend_online, friends):
|
||||||
|
self.send_avatar(friend.number)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Private methods
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _is_friend_online(self, friend_number):
|
||||||
|
friend = self._get_friend_by_number(friend_number)
|
||||||
|
|
||||||
|
return friend.status is not None
|
||||||
|
|
||||||
|
def _get_friend_by_number(self, friend_number):
|
||||||
|
return self._contact_provider.get_friend_by_number(friend_number)
|
||||||
|
|
||||||
|
def _get_all_friends(self):
|
||||||
|
return self._contact_provider.get_all_friends()
|
||||||
|
|
||||||
|
def _send_file_add_set_handlers(self, st, friend, file_name, inline=False):
|
||||||
|
st.set_transfer_finished_handler(self.transfer_finished)
|
||||||
|
file_number = st.get_file_number()
|
||||||
|
self._file_transfers[(friend.number, file_number)] = st
|
||||||
|
tm = self._file_transfers_message_service.add_outgoing_transfer_message(friend, st.size, file_name, file_number)
|
||||||
|
st.set_state_changed_handler(tm.transfer_updated)
|
||||||
|
if inline:
|
||||||
|
self._insert_inline_before[(friend.number, file_number)] = tm.message_id
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _generate_valid_path(path, from_position):
|
||||||
|
path, file_name = os.path.split(path)
|
||||||
|
new_file_name, i = file_name, 1
|
||||||
|
if not from_position:
|
||||||
|
while os.path.isfile(join_path(path, new_file_name)): # file with same name already exists
|
||||||
|
if '.' in file_name: # has extension
|
||||||
|
d = file_name.rindex('.')
|
||||||
|
else: # no extension
|
||||||
|
d = len(file_name)
|
||||||
|
new_file_name = file_name[:d] + ' ({})'.format(i) + file_name[d:]
|
||||||
|
i += 1
|
||||||
|
path = join_path(path, new_file_name)
|
||||||
|
|
||||||
|
return path
|
78
toxygen/file_transfers/file_transfers_messages_service.py
Normal file
78
toxygen/file_transfers/file_transfers_messages_service.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
from messenger.messenger import *
|
||||||
|
import utils.util as util
|
||||||
|
from file_transfers.file_transfers import *
|
||||||
|
|
||||||
|
|
||||||
|
class FileTransfersMessagesService:
|
||||||
|
|
||||||
|
def __init__(self, contacts_manager, messages_items_factory, profile, main_screen):
|
||||||
|
self._contacts_manager = contacts_manager
|
||||||
|
self._messages_items_factory = messages_items_factory
|
||||||
|
self._profile = profile
|
||||||
|
self._messages = main_screen.messages
|
||||||
|
|
||||||
|
def add_incoming_transfer_message(self, friend, accepted, size, file_name, file_number):
|
||||||
|
author = MessageAuthor(friend.name, MESSAGE_AUTHOR['FRIEND'])
|
||||||
|
status = FILE_TRANSFER_STATE['RUNNING'] if accepted else FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']
|
||||||
|
tm = TransferMessage(author, util.get_unix_time(), status, size, file_name, friend.number, file_number)
|
||||||
|
|
||||||
|
if self._is_friend_active(friend.number):
|
||||||
|
self._create_file_transfer_item(tm)
|
||||||
|
self._messages.scrollToBottom()
|
||||||
|
else:
|
||||||
|
friend.actions = True
|
||||||
|
|
||||||
|
friend.append_message(tm)
|
||||||
|
|
||||||
|
return tm
|
||||||
|
|
||||||
|
def add_outgoing_transfer_message(self, friend, size, file_name, file_number):
|
||||||
|
author = MessageAuthor(self._profile.name, MESSAGE_AUTHOR['ME'])
|
||||||
|
status = FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
|
||||||
|
tm = TransferMessage(author, util.get_unix_time(), status, size, file_name, friend.number, file_number)
|
||||||
|
|
||||||
|
if self._is_friend_active(friend.number):
|
||||||
|
self._create_file_transfer_item(tm)
|
||||||
|
self._messages.scrollToBottom()
|
||||||
|
|
||||||
|
friend.append_message(tm)
|
||||||
|
|
||||||
|
return tm
|
||||||
|
|
||||||
|
def add_inline_message(self, transfer, index):
|
||||||
|
if not self._is_friend_active(transfer.friend_number):
|
||||||
|
return
|
||||||
|
count = self._messages.count()
|
||||||
|
if count + index + 1 >= 0:
|
||||||
|
self._create_inline_item(transfer.data, count + index + 1)
|
||||||
|
|
||||||
|
def add_unsent_file_message(self, friend, file_path, data):
|
||||||
|
author = MessageAuthor(self._profile.name, MESSAGE_AUTHOR['ME'])
|
||||||
|
size = os.path.getsize(file_path) if data is None else len(data)
|
||||||
|
tm = UnsentFileMessage(file_path, data, util.get_unix_time(), author, size, friend.number)
|
||||||
|
friend.append_message(tm)
|
||||||
|
|
||||||
|
if self._is_friend_active(friend.number):
|
||||||
|
self._create_unsent_file_item(tm)
|
||||||
|
self._messages.scrollToBottom()
|
||||||
|
|
||||||
|
return tm
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Private methods
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _is_friend_active(self, friend_number):
|
||||||
|
if not self._contacts_manager.is_active_a_friend():
|
||||||
|
return False
|
||||||
|
|
||||||
|
return friend_number == self._contacts_manager.get_active_number()
|
||||||
|
|
||||||
|
def _create_file_transfer_item(self, tm):
|
||||||
|
return self._messages_items_factory.create_file_transfer_item(tm)
|
||||||
|
|
||||||
|
def _create_inline_item(self, data, position):
|
||||||
|
return self._messages_items_factory.create_inline_item(data, False, position)
|
||||||
|
|
||||||
|
def _create_unsent_file_item(self, tm):
|
||||||
|
return self._messages_items_factory.create_unsent_file_item(tm)
|
@ -1,75 +0,0 @@
|
|||||||
import contact
|
|
||||||
from messages import *
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
class Friend(contact.Contact):
|
|
||||||
"""
|
|
||||||
Friend in list of friends.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, message_getter, number, name, status_message, widget, tox_id):
|
|
||||||
super().__init__(message_getter, number, name, status_message, widget, tox_id)
|
|
||||||
self._receipts = 0
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# File transfers support
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def update_transfer_data(self, file_number, status, inline=None):
|
|
||||||
"""
|
|
||||||
Update status of active transfer and load inline if needed
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
tr = list(filter(lambda x: x.get_type() == MESSAGE_TYPE['FILE_TRANSFER'] and x.is_active(file_number),
|
|
||||||
self._corr))[0]
|
|
||||||
tr.set_status(status)
|
|
||||||
i = self._corr.index(tr)
|
|
||||||
if inline: # inline was loaded
|
|
||||||
self._corr.insert(i, inline)
|
|
||||||
return i - len(self._corr)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_unsent_files(self):
|
|
||||||
messages = filter(lambda x: type(x) is UnsentFile, self._corr)
|
|
||||||
return messages
|
|
||||||
|
|
||||||
def clear_unsent_files(self):
|
|
||||||
self._corr = list(filter(lambda x: type(x) is not UnsentFile, self._corr))
|
|
||||||
|
|
||||||
def remove_invalid_unsent_files(self):
|
|
||||||
def is_valid(message):
|
|
||||||
if type(message) is not UnsentFile:
|
|
||||||
return True
|
|
||||||
if message.get_data()[1] is not None:
|
|
||||||
return True
|
|
||||||
return os.path.exists(message.get_data()[0])
|
|
||||||
self._corr = list(filter(is_valid, self._corr))
|
|
||||||
|
|
||||||
def delete_one_unsent_file(self, time):
|
|
||||||
self._corr = list(filter(lambda x: not (type(x) is UnsentFile and x.get_data()[2] == time), self._corr))
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# History support
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_receipts(self):
|
|
||||||
return self._receipts
|
|
||||||
|
|
||||||
receipts = property(get_receipts) # read receipts
|
|
||||||
|
|
||||||
def inc_receipts(self):
|
|
||||||
self._receipts += 1
|
|
||||||
|
|
||||||
def dec_receipt(self):
|
|
||||||
if self._receipts:
|
|
||||||
self._receipts -= 1
|
|
||||||
self.mark_as_sent()
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Full status
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def get_full_status(self):
|
|
||||||
return self._status_message
|
|
@ -1,49 +0,0 @@
|
|||||||
import contact
|
|
||||||
import util
|
|
||||||
from PyQt5 import QtGui, QtCore
|
|
||||||
import toxcore_enums_and_consts as constants
|
|
||||||
|
|
||||||
|
|
||||||
class GroupChat(contact.Contact):
|
|
||||||
|
|
||||||
def __init__(self, name, status_message, widget, tox, group_number):
|
|
||||||
super().__init__(None, group_number, name, status_message, widget, None)
|
|
||||||
self._tox = tox
|
|
||||||
self.set_status(constants.TOX_USER_STATUS['NONE'])
|
|
||||||
|
|
||||||
def set_name(self, name):
|
|
||||||
self._tox.group_set_title(self._number, name)
|
|
||||||
super().set_name(name)
|
|
||||||
|
|
||||||
def send_message(self, message):
|
|
||||||
self._tox.group_message_send(self._number, message.encode('utf-8'))
|
|
||||||
|
|
||||||
def new_title(self, title):
|
|
||||||
super().set_name(title)
|
|
||||||
|
|
||||||
def load_avatar(self):
|
|
||||||
path = util.curr_directory() + '/images/group.png'
|
|
||||||
width = self._widget.avatar_label.width()
|
|
||||||
pixmap = QtGui.QPixmap(path)
|
|
||||||
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
|
|
||||||
QtCore.Qt.SmoothTransformation))
|
|
||||||
self._widget.avatar_label.repaint()
|
|
||||||
|
|
||||||
def remove_invalid_unsent_files(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_names(self):
|
|
||||||
peers_count = self._tox.group_number_peers(self._number)
|
|
||||||
names = []
|
|
||||||
for i in range(peers_count):
|
|
||||||
name = self._tox.group_peername(self._number, i)
|
|
||||||
names.append(name)
|
|
||||||
names = sorted(names, key=lambda n: n.lower())
|
|
||||||
return names
|
|
||||||
|
|
||||||
def get_full_status(self):
|
|
||||||
names = self.get_names()
|
|
||||||
return '\n'.join(names)
|
|
||||||
|
|
||||||
def get_peer_name(self, peer_number):
|
|
||||||
return self._tox.group_peername(self._number, peer_number)
|
|
0
toxygen/groups/__init__.py
Normal file
0
toxygen/groups/__init__.py
Normal file
23
toxygen/groups/group_ban.py
Normal file
23
toxygen/groups/group_ban.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
|
||||||
|
class GroupBan:
|
||||||
|
|
||||||
|
def __init__(self, ban_id, ban_target, ban_time):
|
||||||
|
self._ban_id = ban_id
|
||||||
|
self._ban_target = ban_target
|
||||||
|
self._ban_time = ban_time
|
||||||
|
|
||||||
|
def get_ban_id(self):
|
||||||
|
return self._ban_id
|
||||||
|
|
||||||
|
ban_id = property(get_ban_id)
|
||||||
|
|
||||||
|
def get_ban_target(self):
|
||||||
|
return self._ban_target
|
||||||
|
|
||||||
|
ban_target = property(get_ban_target)
|
||||||
|
|
||||||
|
def get_ban_time(self):
|
||||||
|
return self._ban_time
|
||||||
|
|
||||||
|
ban_time = property(get_ban_time)
|
23
toxygen/groups/group_invite.py
Normal file
23
toxygen/groups/group_invite.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
|
||||||
|
class GroupInvite:
|
||||||
|
|
||||||
|
def __init__(self, friend_public_key, chat_name, invite_data):
|
||||||
|
self._friend_public_key = friend_public_key
|
||||||
|
self._chat_name = chat_name
|
||||||
|
self._invite_data = invite_data[:]
|
||||||
|
|
||||||
|
def get_friend_public_key(self):
|
||||||
|
return self._friend_public_key
|
||||||
|
|
||||||
|
friend_public_key = property(get_friend_public_key)
|
||||||
|
|
||||||
|
def get_chat_name(self):
|
||||||
|
return self._chat_name
|
||||||
|
|
||||||
|
chat_name = property(get_chat_name)
|
||||||
|
|
||||||
|
def get_invite_data(self):
|
||||||
|
return self._invite_data[:]
|
||||||
|
|
||||||
|
invite_data = property(get_invite_data)
|
70
toxygen/groups/group_peer.py
Normal file
70
toxygen/groups/group_peer.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
|
||||||
|
|
||||||
|
class GroupChatPeer:
|
||||||
|
"""
|
||||||
|
Represents peer in group chat.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, peer_id, name, status, role, public_key, is_current_user=False, is_muted=False):
|
||||||
|
self._peer_id = peer_id
|
||||||
|
self._name = name
|
||||||
|
self._status = status
|
||||||
|
self._role = role
|
||||||
|
self._public_key = public_key
|
||||||
|
self._is_current_user = is_current_user
|
||||||
|
self._is_muted = is_muted
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Readonly properties
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_id(self):
|
||||||
|
return self._peer_id
|
||||||
|
|
||||||
|
id = property(get_id)
|
||||||
|
|
||||||
|
def get_public_key(self):
|
||||||
|
return self._public_key
|
||||||
|
|
||||||
|
public_key = property(get_public_key)
|
||||||
|
|
||||||
|
def get_is_current_user(self):
|
||||||
|
return self._is_current_user
|
||||||
|
|
||||||
|
is_current_user = property(get_is_current_user)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Read-write properties
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_name(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
def set_name(self, name):
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
name = property(get_name, set_name)
|
||||||
|
|
||||||
|
def get_status(self):
|
||||||
|
return self._status
|
||||||
|
|
||||||
|
def set_status(self, status):
|
||||||
|
self._status = status
|
||||||
|
|
||||||
|
status = property(get_status, set_status)
|
||||||
|
|
||||||
|
def get_role(self):
|
||||||
|
return self._role
|
||||||
|
|
||||||
|
def set_role(self, role):
|
||||||
|
self._role = role
|
||||||
|
|
||||||
|
role = property(get_role, set_role)
|
||||||
|
|
||||||
|
def get_is_muted(self):
|
||||||
|
return self._is_muted
|
||||||
|
|
||||||
|
def set_is_muted(self, is_muted):
|
||||||
|
self._is_muted = is_muted
|
||||||
|
|
||||||
|
is_muted = property(get_is_muted, set_is_muted)
|
242
toxygen/groups/groups_service.py
Normal file
242
toxygen/groups/groups_service.py
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
import common.tox_save as tox_save
|
||||||
|
import utils.ui as util_ui
|
||||||
|
from groups.peers_list import PeersListGenerator
|
||||||
|
from groups.group_invite import GroupInvite
|
||||||
|
import wrapper.toxcore_enums_and_consts as constants
|
||||||
|
|
||||||
|
|
||||||
|
class GroupsService(tox_save.ToxSave):
|
||||||
|
|
||||||
|
def __init__(self, tox, contacts_manager, contacts_provider, main_screen, widgets_factory_provider):
|
||||||
|
super().__init__(tox)
|
||||||
|
self._contacts_manager = contacts_manager
|
||||||
|
self._contacts_provider = contacts_provider
|
||||||
|
self._main_screen = main_screen
|
||||||
|
self._peers_list_widget = main_screen.peers_list
|
||||||
|
self._widgets_factory_provider = widgets_factory_provider
|
||||||
|
self._group_invites = []
|
||||||
|
self._screen = None
|
||||||
|
|
||||||
|
def set_tox(self, tox):
|
||||||
|
super().set_tox(tox)
|
||||||
|
for group in self._get_all_groups():
|
||||||
|
group.set_tox(tox)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Groups creation
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def create_new_gc(self, name, privacy_state, nick, status):
|
||||||
|
group_number = self._tox.group_new(privacy_state, name, nick, status)
|
||||||
|
if group_number == -1:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._add_new_group_by_number(group_number)
|
||||||
|
group = self._get_group_by_number(group_number)
|
||||||
|
group.status = constants.TOX_USER_STATUS['NONE']
|
||||||
|
self._contacts_manager.update_filtration()
|
||||||
|
|
||||||
|
def join_gc_by_id(self, chat_id, password, nick, status):
|
||||||
|
group_number = self._tox.group_join(chat_id, password, nick, status)
|
||||||
|
self._add_new_group_by_number(group_number)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Groups reconnect and leaving
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def leave_group(self, group_number):
|
||||||
|
self._tox.group_leave(group_number)
|
||||||
|
self._contacts_manager.delete_group(group_number)
|
||||||
|
|
||||||
|
def disconnect_from_group(self, group_number):
|
||||||
|
self._tox.group_disconnect(group_number)
|
||||||
|
group = self._get_group_by_number(group_number)
|
||||||
|
group.status = None
|
||||||
|
self._clear_peers_list(group)
|
||||||
|
|
||||||
|
def reconnect_to_group(self, group_number):
|
||||||
|
self._tox.group_reconnect(group_number)
|
||||||
|
group = self._get_group_by_number(group_number)
|
||||||
|
group.status = constants.TOX_USER_STATUS['NONE']
|
||||||
|
self._clear_peers_list(group)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Group invites
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def invite_friend(self, friend_number, group_number):
|
||||||
|
self._tox.group_invite_friend(group_number, friend_number)
|
||||||
|
|
||||||
|
def process_group_invite(self, friend_number, group_name, invite_data):
|
||||||
|
friend = self._get_friend_by_number(friend_number)
|
||||||
|
invite = GroupInvite(friend.tox_id, group_name, invite_data)
|
||||||
|
self._group_invites.append(invite)
|
||||||
|
self._update_invites_button_state()
|
||||||
|
|
||||||
|
def accept_group_invite(self, invite, name, status, password):
|
||||||
|
pk = invite.friend_public_key
|
||||||
|
friend = self._get_friend_by_public_key(pk)
|
||||||
|
self._join_gc_via_invite(invite.invite_data, friend.number, name, status, password)
|
||||||
|
self._delete_group_invite(invite)
|
||||||
|
self._update_invites_button_state()
|
||||||
|
|
||||||
|
def decline_group_invite(self, invite):
|
||||||
|
self._delete_group_invite(invite)
|
||||||
|
self._main_screen.update_gc_invites_button_state()
|
||||||
|
|
||||||
|
def get_group_invites(self):
|
||||||
|
return self._group_invites[:]
|
||||||
|
|
||||||
|
group_invites = property(get_group_invites)
|
||||||
|
|
||||||
|
def get_group_invites_count(self):
|
||||||
|
return len(self._group_invites)
|
||||||
|
|
||||||
|
group_invites_count = property(get_group_invites_count)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Group info methods
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def update_group_info(self, group):
|
||||||
|
group.name = self._tox.group_get_name(group.number)
|
||||||
|
group.status_message = self._tox.group_get_topic(group.number)
|
||||||
|
|
||||||
|
def set_group_topic(self, group):
|
||||||
|
if not group.is_self_moderator_or_founder():
|
||||||
|
return
|
||||||
|
text = util_ui.tr('New topic for group "{}":'.format(group.name))
|
||||||
|
title = util_ui.tr('Set group topic')
|
||||||
|
topic, ok = util_ui.text_dialog(text, title, group.status_message)
|
||||||
|
if not ok or not topic:
|
||||||
|
return
|
||||||
|
self._tox.group_set_topic(group.number, topic)
|
||||||
|
group.status_message = topic
|
||||||
|
|
||||||
|
def show_group_management_screen(self, group):
|
||||||
|
widgets_factory = self._get_widgets_factory()
|
||||||
|
self._screen = widgets_factory.create_group_management_screen(group)
|
||||||
|
self._screen.show()
|
||||||
|
|
||||||
|
def show_group_settings_screen(self, group):
|
||||||
|
widgets_factory = self._get_widgets_factory()
|
||||||
|
self._screen = widgets_factory.create_group_settings_screen(group)
|
||||||
|
self._screen.show()
|
||||||
|
|
||||||
|
def set_group_password(self, group, password):
|
||||||
|
if group.password == password:
|
||||||
|
return
|
||||||
|
self._tox.group_founder_set_password(group.number, password)
|
||||||
|
group.password = password
|
||||||
|
|
||||||
|
def set_group_peers_limit(self, group, peers_limit):
|
||||||
|
if group.peers_limit == peers_limit:
|
||||||
|
return
|
||||||
|
self._tox.group_founder_set_peer_limit(group.number, peers_limit)
|
||||||
|
group.peers_limit = peers_limit
|
||||||
|
|
||||||
|
def set_group_privacy_state(self, group, privacy_state):
|
||||||
|
is_private = privacy_state == constants.TOX_GROUP_PRIVACY_STATE['PRIVATE']
|
||||||
|
if group.is_private == is_private:
|
||||||
|
return
|
||||||
|
self._tox.group_founder_set_privacy_state(group.number, privacy_state)
|
||||||
|
group.is_private = is_private
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Peers list
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def generate_peers_list(self):
|
||||||
|
if not self._contacts_manager.is_active_a_group():
|
||||||
|
return
|
||||||
|
group = self._contacts_manager.get_curr_contact()
|
||||||
|
PeersListGenerator().generate(group.peers, self, self._peers_list_widget, group.tox_id)
|
||||||
|
|
||||||
|
def peer_selected(self, chat_id, peer_id):
|
||||||
|
widgets_factory = self._get_widgets_factory()
|
||||||
|
group = self._get_group_by_public_key(chat_id)
|
||||||
|
self_peer = group.get_self_peer()
|
||||||
|
if self_peer.id != peer_id:
|
||||||
|
self._screen = widgets_factory.create_peer_screen_window(group, peer_id)
|
||||||
|
else:
|
||||||
|
self._screen = widgets_factory.create_self_peer_screen_window(group)
|
||||||
|
self._screen.show()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Peers actions
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def set_new_peer_role(self, group, peer, role):
|
||||||
|
self._tox.group_mod_set_role(group.number, peer.id, role)
|
||||||
|
peer.role = role
|
||||||
|
self.generate_peers_list()
|
||||||
|
|
||||||
|
def toggle_ignore_peer(self, group, peer, ignore):
|
||||||
|
self._tox.group_toggle_ignore(group.number, peer.id, ignore)
|
||||||
|
peer.is_muted = ignore
|
||||||
|
|
||||||
|
def set_self_info(self, group, name, status):
|
||||||
|
self._tox.group_self_set_name(group.number, name)
|
||||||
|
self._tox.group_self_set_status(group.number, status)
|
||||||
|
self_peer = group.get_self_peer()
|
||||||
|
self_peer.name = name
|
||||||
|
self_peer.status = status
|
||||||
|
self.generate_peers_list()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Bans support
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def show_bans_list(self, group):
|
||||||
|
widgets_factory = self._get_widgets_factory()
|
||||||
|
self._screen = widgets_factory.create_groups_bans_screen(group)
|
||||||
|
self._screen.show()
|
||||||
|
|
||||||
|
def ban_peer(self, group, peer_id, ban_type):
|
||||||
|
self._tox.group_mod_ban_peer(group.number, peer_id, ban_type)
|
||||||
|
|
||||||
|
def kick_peer(self, group, peer_id):
|
||||||
|
self._tox.group_mod_remove_peer(group.number, peer_id)
|
||||||
|
|
||||||
|
def cancel_ban(self, group_number, ban_id):
|
||||||
|
self._tox.group_mod_remove_ban(group_number, ban_id)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Private methods
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _add_new_group_by_number(self, group_number):
|
||||||
|
self._contacts_manager.add_group(group_number)
|
||||||
|
|
||||||
|
def _get_group_by_number(self, group_number):
|
||||||
|
return self._contacts_provider.get_group_by_number(group_number)
|
||||||
|
|
||||||
|
def _get_group_by_public_key(self, public_key):
|
||||||
|
return self._contacts_provider.get_group_by_public_key(public_key)
|
||||||
|
|
||||||
|
def _get_all_groups(self):
|
||||||
|
return self._contacts_provider.get_all_groups()
|
||||||
|
|
||||||
|
def _get_friend_by_number(self, friend_number):
|
||||||
|
return self._contacts_provider.get_friend_by_number(friend_number)
|
||||||
|
|
||||||
|
def _get_friend_by_public_key(self, public_key):
|
||||||
|
return self._contacts_provider.get_friend_by_public_key(public_key)
|
||||||
|
|
||||||
|
def _clear_peers_list(self, group):
|
||||||
|
group.remove_all_peers_except_self()
|
||||||
|
self.generate_peers_list()
|
||||||
|
|
||||||
|
def _delete_group_invite(self, invite):
|
||||||
|
if invite in self._group_invites:
|
||||||
|
self._group_invites.remove(invite)
|
||||||
|
|
||||||
|
def _join_gc_via_invite(self, invite_data, friend_number, nick, status, password):
|
||||||
|
group_number = self._tox.group_invite_accept(invite_data, friend_number, nick, status, password)
|
||||||
|
self._add_new_group_by_number(group_number)
|
||||||
|
|
||||||
|
def _update_invites_button_state(self):
|
||||||
|
self._main_screen.update_gc_invites_button_state()
|
||||||
|
|
||||||
|
def _get_widgets_factory(self):
|
||||||
|
return self._widgets_factory_provider.get_item()
|
104
toxygen/groups/peers_list.py
Normal file
104
toxygen/groups/peers_list.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
from ui.group_peers_list import PeerItem, PeerTypeItem
|
||||||
|
from wrapper.toxcore_enums_and_consts import *
|
||||||
|
from ui.widgets import *
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Builder
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
class PeerListBuilder:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._peers = {}
|
||||||
|
self._titles = {}
|
||||||
|
self._index = 0
|
||||||
|
self._handler = None
|
||||||
|
|
||||||
|
def with_click_handler(self, handler):
|
||||||
|
self._handler = handler
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def with_title(self, title):
|
||||||
|
self._titles[self._index] = title
|
||||||
|
self._index += 1
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def with_peers(self, peers):
|
||||||
|
for peer in peers:
|
||||||
|
self._add_peer(peer)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def build(self, list_widget):
|
||||||
|
list_widget.clear()
|
||||||
|
|
||||||
|
for i in range(self._index):
|
||||||
|
if i in self._peers:
|
||||||
|
peer = self._peers[i]
|
||||||
|
self._add_peer_item(peer, list_widget)
|
||||||
|
else:
|
||||||
|
title = self._titles[i]
|
||||||
|
self._add_peer_type_item(title, list_widget)
|
||||||
|
|
||||||
|
def _add_peer_item(self, peer, parent):
|
||||||
|
item = PeerItem(peer, self._handler, parent.width(), parent)
|
||||||
|
self._add_item(parent, item)
|
||||||
|
|
||||||
|
def _add_peer_type_item(self, text, parent):
|
||||||
|
item = PeerTypeItem(text, parent.width(), parent)
|
||||||
|
self._add_item(parent, item)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _add_item(parent, item):
|
||||||
|
elem = QtWidgets.QListWidgetItem(parent)
|
||||||
|
elem.setSizeHint(QtCore.QSize(parent.width(), item.height()))
|
||||||
|
parent.addItem(elem)
|
||||||
|
parent.setItemWidget(elem, item)
|
||||||
|
|
||||||
|
def _add_peer(self, peer):
|
||||||
|
self._peers[self._index] = peer
|
||||||
|
self._index += 1
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Generators
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
class PeersListGenerator:
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate(peers_list, groups_service, list_widget, chat_id):
|
||||||
|
admin_title = util_ui.tr('Administrator')
|
||||||
|
moderators_title = util_ui.tr('Moderators')
|
||||||
|
users_title = util_ui.tr('Users')
|
||||||
|
observers_title = util_ui.tr('Observers')
|
||||||
|
|
||||||
|
admins = list(filter(lambda p: p.role == TOX_GROUP_ROLE['FOUNDER'], peers_list))
|
||||||
|
moderators = list(filter(lambda p: p.role == TOX_GROUP_ROLE['MODERATOR'], peers_list))
|
||||||
|
users = list(filter(lambda p: p.role == TOX_GROUP_ROLE['USER'], peers_list))
|
||||||
|
observers = list(filter(lambda p: p.role == TOX_GROUP_ROLE['OBSERVER'], peers_list))
|
||||||
|
|
||||||
|
builder = (PeerListBuilder()
|
||||||
|
.with_click_handler(lambda peer_id: groups_service.peer_selected(chat_id, peer_id)))
|
||||||
|
if len(admins):
|
||||||
|
(builder
|
||||||
|
.with_title(admin_title)
|
||||||
|
.with_peers(admins))
|
||||||
|
if len(moderators):
|
||||||
|
(builder
|
||||||
|
.with_title(moderators_title)
|
||||||
|
.with_peers(moderators))
|
||||||
|
if len(users):
|
||||||
|
(builder
|
||||||
|
.with_title(users_title)
|
||||||
|
.with_peers(users))
|
||||||
|
if len(observers):
|
||||||
|
(builder
|
||||||
|
.with_title(observers_title)
|
||||||
|
.with_peers(observers))
|
||||||
|
|
||||||
|
builder.build(list_widget)
|
@ -1,215 +0,0 @@
|
|||||||
from sqlite3 import connect
|
|
||||||
import settings
|
|
||||||
from os import chdir
|
|
||||||
import os.path
|
|
||||||
from toxes import ToxES
|
|
||||||
|
|
||||||
|
|
||||||
PAGE_SIZE = 42
|
|
||||||
|
|
||||||
TIMEOUT = 11
|
|
||||||
|
|
||||||
SAVE_MESSAGES = 250
|
|
||||||
|
|
||||||
MESSAGE_OWNER = {
|
|
||||||
'ME': 0,
|
|
||||||
'FRIEND': 1,
|
|
||||||
'NOT_SENT': 2
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class History:
|
|
||||||
|
|
||||||
def __init__(self, name):
|
|
||||||
self._name = name
|
|
||||||
chdir(settings.ProfileHelper.get_path())
|
|
||||||
path = settings.ProfileHelper.get_path() + self._name + '.hstr'
|
|
||||||
if os.path.exists(path):
|
|
||||||
decr = ToxES.get_instance()
|
|
||||||
try:
|
|
||||||
with open(path, 'rb') as fin:
|
|
||||||
data = fin.read()
|
|
||||||
if decr.is_data_encrypted(data):
|
|
||||||
data = decr.pass_decrypt(data)
|
|
||||||
with open(path, 'wb') as fout:
|
|
||||||
fout.write(data)
|
|
||||||
except:
|
|
||||||
os.remove(path)
|
|
||||||
db = connect(name + '.hstr', timeout=TIMEOUT)
|
|
||||||
cursor = db.cursor()
|
|
||||||
cursor.execute('CREATE TABLE IF NOT EXISTS friends('
|
|
||||||
' tox_id TEXT PRIMARY KEY'
|
|
||||||
')')
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
encr = ToxES.get_instance()
|
|
||||||
if encr.has_password():
|
|
||||||
path = settings.ProfileHelper.get_path() + self._name + '.hstr'
|
|
||||||
with open(path, 'rb') as fin:
|
|
||||||
data = fin.read()
|
|
||||||
data = encr.pass_encrypt(bytes(data))
|
|
||||||
with open(path, 'wb') as fout:
|
|
||||||
fout.write(data)
|
|
||||||
|
|
||||||
def export(self, directory):
|
|
||||||
path = settings.ProfileHelper.get_path() + self._name + '.hstr'
|
|
||||||
new_path = directory + self._name + '.hstr'
|
|
||||||
with open(path, 'rb') as fin:
|
|
||||||
data = fin.read()
|
|
||||||
encr = ToxES.get_instance()
|
|
||||||
if encr.has_password():
|
|
||||||
data = encr.pass_encrypt(data)
|
|
||||||
with open(new_path, 'wb') as fout:
|
|
||||||
fout.write(data)
|
|
||||||
|
|
||||||
def add_friend_to_db(self, tox_id):
|
|
||||||
chdir(settings.ProfileHelper.get_path())
|
|
||||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
|
||||||
try:
|
|
||||||
cursor = db.cursor()
|
|
||||||
cursor.execute('INSERT INTO friends VALUES (?);', (tox_id, ))
|
|
||||||
cursor.execute('CREATE TABLE id' + tox_id + '('
|
|
||||||
' id INTEGER PRIMARY KEY,'
|
|
||||||
' message TEXT,'
|
|
||||||
' owner INTEGER,'
|
|
||||||
' unix_time REAL,'
|
|
||||||
' message_type INTEGER'
|
|
||||||
')')
|
|
||||||
db.commit()
|
|
||||||
except:
|
|
||||||
print('Database is locked!')
|
|
||||||
db.rollback()
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
def delete_friend_from_db(self, tox_id):
|
|
||||||
chdir(settings.ProfileHelper.get_path())
|
|
||||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
|
||||||
try:
|
|
||||||
cursor = db.cursor()
|
|
||||||
cursor.execute('DELETE FROM friends WHERE tox_id=?;', (tox_id, ))
|
|
||||||
cursor.execute('DROP TABLE id' + tox_id + ';')
|
|
||||||
db.commit()
|
|
||||||
except:
|
|
||||||
print('Database is locked!')
|
|
||||||
db.rollback()
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
def friend_exists_in_db(self, tox_id):
|
|
||||||
chdir(settings.ProfileHelper.get_path())
|
|
||||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
|
||||||
cursor = db.cursor()
|
|
||||||
cursor.execute('SELECT 0 FROM friends WHERE tox_id=?', (tox_id, ))
|
|
||||||
result = cursor.fetchone()
|
|
||||||
db.close()
|
|
||||||
return result is not None
|
|
||||||
|
|
||||||
def save_messages_to_db(self, tox_id, messages_iter):
|
|
||||||
chdir(settings.ProfileHelper.get_path())
|
|
||||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
|
||||||
try:
|
|
||||||
cursor = db.cursor()
|
|
||||||
cursor.executemany('INSERT INTO id' + tox_id + '(message, owner, unix_time, message_type) '
|
|
||||||
'VALUES (?, ?, ?, ?);', messages_iter)
|
|
||||||
db.commit()
|
|
||||||
except:
|
|
||||||
print('Database is locked!')
|
|
||||||
db.rollback()
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
def update_messages(self, tox_id, unsent_time):
|
|
||||||
chdir(settings.ProfileHelper.get_path())
|
|
||||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
|
||||||
try:
|
|
||||||
cursor = db.cursor()
|
|
||||||
cursor.execute('UPDATE id' + tox_id + ' SET owner = 0 '
|
|
||||||
'WHERE unix_time < ' + str(unsent_time) + ' AND owner = 2;')
|
|
||||||
db.commit()
|
|
||||||
except:
|
|
||||||
print('Database is locked!')
|
|
||||||
db.rollback()
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
def delete_message(self, tox_id, time):
|
|
||||||
start, end = str(time - 0.01), str(time + 0.01)
|
|
||||||
chdir(settings.ProfileHelper.get_path())
|
|
||||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
|
||||||
try:
|
|
||||||
cursor = db.cursor()
|
|
||||||
cursor.execute('DELETE FROM id' + tox_id + ' WHERE unix_time < ' + end + ' AND unix_time > ' +
|
|
||||||
start + ';')
|
|
||||||
db.commit()
|
|
||||||
except:
|
|
||||||
print('Database is locked!')
|
|
||||||
db.rollback()
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
def delete_messages(self, tox_id):
|
|
||||||
chdir(settings.ProfileHelper.get_path())
|
|
||||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
|
||||||
try:
|
|
||||||
cursor = db.cursor()
|
|
||||||
cursor.execute('DELETE FROM id' + tox_id + ';')
|
|
||||||
db.commit()
|
|
||||||
except:
|
|
||||||
print('Database is locked!')
|
|
||||||
db.rollback()
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
def messages_getter(self, tox_id):
|
|
||||||
return History.MessageGetter(self._name, tox_id)
|
|
||||||
|
|
||||||
class MessageGetter:
|
|
||||||
|
|
||||||
def __init__(self, name, tox_id):
|
|
||||||
self._count = 0
|
|
||||||
self._name = name
|
|
||||||
self._tox_id = tox_id
|
|
||||||
self._db = self._cursor = None
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
chdir(settings.ProfileHelper.get_path())
|
|
||||||
self._db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
|
||||||
self._cursor = self._db.cursor()
|
|
||||||
self._cursor.execute('SELECT message, owner, unix_time, message_type FROM id' + self._tox_id +
|
|
||||||
' ORDER BY unix_time DESC;')
|
|
||||||
|
|
||||||
def disconnect(self):
|
|
||||||
self._db.close()
|
|
||||||
|
|
||||||
def get_one(self):
|
|
||||||
self.connect()
|
|
||||||
self.skip()
|
|
||||||
data = self._cursor.fetchone()
|
|
||||||
self._count += 1
|
|
||||||
self.disconnect()
|
|
||||||
return data
|
|
||||||
|
|
||||||
def get_all(self):
|
|
||||||
self.connect()
|
|
||||||
data = self._cursor.fetchall()
|
|
||||||
self.disconnect()
|
|
||||||
self._count = len(data)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def get(self, count):
|
|
||||||
self.connect()
|
|
||||||
self.skip()
|
|
||||||
data = self._cursor.fetchmany(count)
|
|
||||||
self.disconnect()
|
|
||||||
self._count += len(data)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def skip(self):
|
|
||||||
if self._count:
|
|
||||||
self._cursor.fetchmany(self._count)
|
|
||||||
|
|
||||||
def delete_one(self):
|
|
||||||
if self._count:
|
|
||||||
self._count -= 1
|
|
0
toxygen/history/__init__.py
Normal file
0
toxygen/history/__init__.py
Normal file
201
toxygen/history/database.py
Normal file
201
toxygen/history/database.py
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
from sqlite3 import connect
|
||||||
|
import os.path
|
||||||
|
import utils.util as util
|
||||||
|
|
||||||
|
|
||||||
|
TIMEOUT = 11
|
||||||
|
|
||||||
|
SAVE_MESSAGES = 500
|
||||||
|
|
||||||
|
MESSAGE_AUTHOR = {
|
||||||
|
'ME': 0,
|
||||||
|
'FRIEND': 1,
|
||||||
|
'NOT_SENT': 2,
|
||||||
|
'GC_PEER': 3
|
||||||
|
}
|
||||||
|
|
||||||
|
CONTACT_TYPE = {
|
||||||
|
'FRIEND': 0,
|
||||||
|
'GC_PEER': 1,
|
||||||
|
'GC_PEER_PRIVATE': 2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Database:
|
||||||
|
|
||||||
|
def __init__(self, path, toxes):
|
||||||
|
self._path, self._toxes = path, toxes
|
||||||
|
self._name = os.path.basename(path)
|
||||||
|
if os.path.exists(path):
|
||||||
|
try:
|
||||||
|
with open(path, 'rb') as fin:
|
||||||
|
data = fin.read()
|
||||||
|
if toxes.is_data_encrypted(data):
|
||||||
|
data = toxes.pass_decrypt(data)
|
||||||
|
with open(path, 'wb') as fout:
|
||||||
|
fout.write(data)
|
||||||
|
except Exception as ex:
|
||||||
|
util.log('Db reading error: ' + str(ex))
|
||||||
|
os.remove(path)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Public methods
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
if self._toxes.has_password():
|
||||||
|
with open(self._path, 'rb') as fin:
|
||||||
|
data = fin.read()
|
||||||
|
data = self._toxes.pass_encrypt(bytes(data))
|
||||||
|
with open(self._path, 'wb') as fout:
|
||||||
|
fout.write(data)
|
||||||
|
|
||||||
|
def export(self, directory):
|
||||||
|
new_path = util.join_path(directory, self._name)
|
||||||
|
with open(self._path, 'rb') as fin:
|
||||||
|
data = fin.read()
|
||||||
|
if self._toxes.has_password():
|
||||||
|
data = self._toxes.pass_encrypt(data)
|
||||||
|
with open(new_path, 'wb') as fout:
|
||||||
|
fout.write(data)
|
||||||
|
|
||||||
|
def add_friend_to_db(self, tox_id):
|
||||||
|
db = self._connect()
|
||||||
|
try:
|
||||||
|
cursor = db.cursor()
|
||||||
|
cursor.execute('CREATE TABLE IF NOT EXISTS id' + tox_id + '('
|
||||||
|
' id INTEGER PRIMARY KEY,'
|
||||||
|
' author_name TEXT,'
|
||||||
|
' message TEXT,'
|
||||||
|
' author_type INTEGER,'
|
||||||
|
' unix_time REAL,'
|
||||||
|
' message_type INTEGER'
|
||||||
|
')')
|
||||||
|
db.commit()
|
||||||
|
except:
|
||||||
|
print('Database is locked!')
|
||||||
|
db.rollback()
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
def delete_friend_from_db(self, tox_id):
|
||||||
|
db = self._connect()
|
||||||
|
try:
|
||||||
|
cursor = db.cursor()
|
||||||
|
cursor.execute('DROP TABLE id' + tox_id + ';')
|
||||||
|
db.commit()
|
||||||
|
except:
|
||||||
|
print('Database is locked!')
|
||||||
|
db.rollback()
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
def save_messages_to_db(self, tox_id, messages_iter):
|
||||||
|
db = self._connect()
|
||||||
|
try:
|
||||||
|
cursor = db.cursor()
|
||||||
|
cursor.executemany('INSERT INTO id' + tox_id +
|
||||||
|
'(message, author_name, author_type, unix_time, message_type) ' +
|
||||||
|
'VALUES (?, ?, ?, ?, ?, ?);', messages_iter)
|
||||||
|
db.commit()
|
||||||
|
except:
|
||||||
|
print('Database is locked!')
|
||||||
|
db.rollback()
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
def update_messages(self, tox_id, message_id):
|
||||||
|
db = self._connect()
|
||||||
|
try:
|
||||||
|
cursor = db.cursor()
|
||||||
|
cursor.execute('UPDATE id' + tox_id + ' SET author = 0 '
|
||||||
|
'WHERE id = ' + str(message_id) + ' AND author = 2;')
|
||||||
|
db.commit()
|
||||||
|
except:
|
||||||
|
print('Database is locked!')
|
||||||
|
db.rollback()
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
def delete_message(self, tox_id, unique_id):
|
||||||
|
db = self._connect()
|
||||||
|
try:
|
||||||
|
cursor = db.cursor()
|
||||||
|
cursor.execute('DELETE FROM id' + tox_id + ' WHERE id = ' + str(unique_id) + ';')
|
||||||
|
db.commit()
|
||||||
|
except:
|
||||||
|
print('Database is locked!')
|
||||||
|
db.rollback()
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
def delete_messages(self, tox_id):
|
||||||
|
db = self._connect()
|
||||||
|
try:
|
||||||
|
cursor = db.cursor()
|
||||||
|
cursor.execute('DELETE FROM id' + tox_id + ';')
|
||||||
|
db.commit()
|
||||||
|
except:
|
||||||
|
print('Database is locked!')
|
||||||
|
db.rollback()
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
def messages_getter(self, tox_id):
|
||||||
|
self.add_friend_to_db(tox_id)
|
||||||
|
|
||||||
|
return Database.MessageGetter(self._path, tox_id)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Messages loading
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class MessageGetter:
|
||||||
|
|
||||||
|
def __init__(self, path, tox_id):
|
||||||
|
self._count = 0
|
||||||
|
self._path = path
|
||||||
|
self._tox_id = tox_id
|
||||||
|
self._db = self._cursor = None
|
||||||
|
|
||||||
|
def get_one(self):
|
||||||
|
return self.get(1)
|
||||||
|
|
||||||
|
def get_all(self):
|
||||||
|
self._connect()
|
||||||
|
data = self._cursor.fetchall()
|
||||||
|
self._disconnect()
|
||||||
|
self._count = len(data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get(self, count):
|
||||||
|
self._connect()
|
||||||
|
self.skip()
|
||||||
|
data = self._cursor.fetchmany(count)
|
||||||
|
self._disconnect()
|
||||||
|
self._count += len(data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def skip(self):
|
||||||
|
if self._count:
|
||||||
|
self._cursor.fetchmany(self._count)
|
||||||
|
|
||||||
|
def delete_one(self):
|
||||||
|
if self._count:
|
||||||
|
self._count -= 1
|
||||||
|
|
||||||
|
def _connect(self):
|
||||||
|
self._db = connect(self._path, timeout=TIMEOUT)
|
||||||
|
self._cursor = self._db.cursor()
|
||||||
|
self._cursor.execute('SELECT message, author_type, author_name, unix_time, message_type, id FROM id' +
|
||||||
|
self._tox_id + ' ORDER BY unix_time DESC;')
|
||||||
|
|
||||||
|
def _disconnect(self):
|
||||||
|
self._db.close()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Private methods
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _connect(self):
|
||||||
|
return connect(self._path, timeout=TIMEOUT)
|
138
toxygen/history/history.py
Normal file
138
toxygen/history/history.py
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
from history.history_logs_generators import *
|
||||||
|
|
||||||
|
|
||||||
|
class History:
|
||||||
|
|
||||||
|
def __init__(self, contact_provider, db, settings, main_screen, messages_items_factory):
|
||||||
|
self._contact_provider = contact_provider
|
||||||
|
self._db = db
|
||||||
|
self._settings = settings
|
||||||
|
self._messages = main_screen.messages
|
||||||
|
self._messages_items_factory = messages_items_factory
|
||||||
|
self._is_loading = False
|
||||||
|
self._contacts_manager = None
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
del self._db
|
||||||
|
|
||||||
|
def set_contacts_manager(self, contacts_manager):
|
||||||
|
self._contacts_manager = contacts_manager
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# History support
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def save_history(self):
|
||||||
|
"""
|
||||||
|
Save history to db
|
||||||
|
"""
|
||||||
|
if self._settings['save_db']:
|
||||||
|
for friend in self._contact_provider.get_all_friends():
|
||||||
|
self._db.add_friend_to_db(friend.tox_id)
|
||||||
|
if not self._settings['save_unsent_only']:
|
||||||
|
messages = friend.get_corr_for_saving()
|
||||||
|
else:
|
||||||
|
messages = friend.get_unsent_messages_for_saving()
|
||||||
|
self._db.delete_messages(friend.tox_id)
|
||||||
|
messages = map(lambda m: (m.text, m.author.name, m.author.type, m.time, m.type), messages)
|
||||||
|
self._db.save_messages_to_db(friend.tox_id, messages)
|
||||||
|
|
||||||
|
self._db.save()
|
||||||
|
|
||||||
|
def clear_history(self, friend, save_unsent=False):
|
||||||
|
"""
|
||||||
|
Clear chat history
|
||||||
|
"""
|
||||||
|
friend.clear_corr(save_unsent)
|
||||||
|
self._db.delete_friend_from_db(friend.tox_id)
|
||||||
|
|
||||||
|
def export_history(self, contact, as_text=True):
|
||||||
|
extension = 'txt' if as_text else 'html'
|
||||||
|
file_name, _ = util_ui.save_file_dialog(util_ui.tr('Choose file name'), extension)
|
||||||
|
|
||||||
|
if not file_name:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not file_name.endswith('.' + extension):
|
||||||
|
file_name += '.' + extension
|
||||||
|
|
||||||
|
history = self.generate_history(contact, as_text)
|
||||||
|
with open(file_name, 'wt') as fl:
|
||||||
|
fl.write(history)
|
||||||
|
|
||||||
|
def delete_message(self, message):
|
||||||
|
contact = self._contacts_manager.get_curr_contact()
|
||||||
|
if message.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']):
|
||||||
|
if message.is_saved():
|
||||||
|
self._db.delete_message(contact.tox_id, message.id)
|
||||||
|
contact.delete_message(message.message_id)
|
||||||
|
|
||||||
|
def load_history(self, friend):
|
||||||
|
"""
|
||||||
|
Tries to load next part of messages
|
||||||
|
"""
|
||||||
|
if self._is_loading:
|
||||||
|
return
|
||||||
|
self._is_loading = True
|
||||||
|
friend.load_corr(False)
|
||||||
|
messages = friend.get_corr()
|
||||||
|
if not messages:
|
||||||
|
self._is_loading = False
|
||||||
|
return
|
||||||
|
messages.reverse()
|
||||||
|
messages = messages[self._messages.count():self._messages.count() + PAGE_SIZE]
|
||||||
|
for message in messages:
|
||||||
|
message_type = message.get_type()
|
||||||
|
if message_type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']): # text message
|
||||||
|
self._create_message_item(message)
|
||||||
|
elif message_type == MESSAGE_TYPE['FILE_TRANSFER']: # file transfer
|
||||||
|
if message.state == FILE_TRANSFER_STATE['UNSENT']:
|
||||||
|
self._create_unsent_file_item(message)
|
||||||
|
else:
|
||||||
|
self._create_file_transfer_item(message)
|
||||||
|
elif message_type == MESSAGE_TYPE['INLINE']: # inline image
|
||||||
|
self._create_inline_item(message)
|
||||||
|
else: # info message
|
||||||
|
self._create_message_item(message)
|
||||||
|
self._is_loading = False
|
||||||
|
|
||||||
|
def get_message_getter(self, friend_public_key):
|
||||||
|
self._db.add_friend_to_db(friend_public_key)
|
||||||
|
|
||||||
|
return self._db.messages_getter(friend_public_key)
|
||||||
|
|
||||||
|
def delete_history(self, friend):
|
||||||
|
self._db.delete_friend_from_db(friend.tox_id)
|
||||||
|
|
||||||
|
def add_friend_to_db(self, tox_id):
|
||||||
|
self._db.add_friend_to_db(tox_id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_history(contact, as_text=True, _range=None):
|
||||||
|
if _range is None:
|
||||||
|
contact.load_all_corr()
|
||||||
|
corr = contact.get_corr()
|
||||||
|
elif _range[1] + 1:
|
||||||
|
corr = contact.get_corr()[_range[0]:_range[1] + 1]
|
||||||
|
else:
|
||||||
|
corr = contact.get_corr()[_range[0]:]
|
||||||
|
|
||||||
|
generator = TextHistoryGenerator(corr, contact.name) if as_text else HtmlHistoryGenerator(corr, contact.name)
|
||||||
|
|
||||||
|
return generator.generate()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Items creation
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _create_message_item(self, message):
|
||||||
|
return self._messages_items_factory.create_message_item(message, False)
|
||||||
|
|
||||||
|
def _create_unsent_file_item(self, message):
|
||||||
|
return self._messages_items_factory.create_unsent_file_item(message, False)
|
||||||
|
|
||||||
|
def _create_file_transfer_item(self, message):
|
||||||
|
return self._messages_items_factory.create_file_transfer_item(message, False)
|
||||||
|
|
||||||
|
def _create_inline_item(self, message):
|
||||||
|
return self._messages_items_factory.create_inline_item(message, False)
|
48
toxygen/history/history_logs_generators.py
Normal file
48
toxygen/history/history_logs_generators.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from messenger.messages import *
|
||||||
|
import utils.util as util
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryLogsGenerator:
|
||||||
|
|
||||||
|
def __init__(self, history, contact_name):
|
||||||
|
self._history = history
|
||||||
|
self._contact_name = contact_name
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
return str()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_message_time(message):
|
||||||
|
return util.convert_time(message.time) if message.author.type != MESSAGE_AUTHOR['NOT_SENT'] else 'Unsent'
|
||||||
|
|
||||||
|
|
||||||
|
class HtmlHistoryGenerator(HistoryLogsGenerator):
|
||||||
|
|
||||||
|
def __init__(self, history, contact_name):
|
||||||
|
super().__init__(history, contact_name)
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
arr = []
|
||||||
|
for message in self._history:
|
||||||
|
if type(message) is TextMessage:
|
||||||
|
x = '[{}] <b>{}:</b> {}<br>'
|
||||||
|
arr.append(x.format(self._get_message_time(message), message.author.name, message.text))
|
||||||
|
s = '<br>'.join(arr)
|
||||||
|
html = '<html><head><meta charset="UTF-8"><title>{}</title></head><body>{}</body></html>'
|
||||||
|
|
||||||
|
return html.format(self._contact_name, s)
|
||||||
|
|
||||||
|
|
||||||
|
class TextHistoryGenerator(HistoryLogsGenerator):
|
||||||
|
|
||||||
|
def __init__(self, history, contact_name):
|
||||||
|
super().__init__(history, contact_name)
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
arr = [self._contact_name]
|
||||||
|
for message in self._history:
|
||||||
|
if type(message) is TextMessage:
|
||||||
|
x = '[{}] {}: {}\n'
|
||||||
|
arr.append(x.format(self._get_message_time(message), message.author.name, message.text))
|
||||||
|
|
||||||
|
return '\n'.join(arr)
|
@ -1,68 +0,0 @@
|
|||||||
from PyQt5 import QtWidgets, QtCore
|
|
||||||
from list_items import *
|
|
||||||
|
|
||||||
|
|
||||||
class ItemsFactory:
|
|
||||||
|
|
||||||
def __init__(self, friends_list, messages):
|
|
||||||
self._friends = friends_list
|
|
||||||
self._messages = messages
|
|
||||||
|
|
||||||
def friend_item(self):
|
|
||||||
item = ContactItem()
|
|
||||||
elem = QtWidgets.QListWidgetItem(self._friends)
|
|
||||||
elem.setSizeHint(QtCore.QSize(250, item.height()))
|
|
||||||
self._friends.addItem(elem)
|
|
||||||
self._friends.setItemWidget(elem, item)
|
|
||||||
return item
|
|
||||||
|
|
||||||
def message_item(self, text, time, name, sent, message_type, append, pixmap):
|
|
||||||
item = MessageItem(text, time, name, sent, message_type, self._messages)
|
|
||||||
if pixmap is not None:
|
|
||||||
item.set_avatar(pixmap)
|
|
||||||
elem = QtWidgets.QListWidgetItem()
|
|
||||||
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
|
|
||||||
if append:
|
|
||||||
self._messages.addItem(elem)
|
|
||||||
else:
|
|
||||||
self._messages.insertItem(0, elem)
|
|
||||||
self._messages.setItemWidget(elem, item)
|
|
||||||
return item
|
|
||||||
|
|
||||||
def inline_item(self, data, append):
|
|
||||||
elem = QtWidgets.QListWidgetItem()
|
|
||||||
item = InlineImageItem(data, self._messages.width(), elem)
|
|
||||||
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
|
|
||||||
if append:
|
|
||||||
self._messages.addItem(elem)
|
|
||||||
else:
|
|
||||||
self._messages.insertItem(0, elem)
|
|
||||||
self._messages.setItemWidget(elem, item)
|
|
||||||
return item
|
|
||||||
|
|
||||||
def unsent_file_item(self, file_name, size, name, time, append):
|
|
||||||
item = UnsentFileItem(file_name,
|
|
||||||
size,
|
|
||||||
name,
|
|
||||||
time,
|
|
||||||
self._messages.width())
|
|
||||||
elem = QtWidgets.QListWidgetItem()
|
|
||||||
elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34))
|
|
||||||
if append:
|
|
||||||
self._messages.addItem(elem)
|
|
||||||
else:
|
|
||||||
self._messages.insertItem(0, elem)
|
|
||||||
self._messages.setItemWidget(elem, item)
|
|
||||||
return item
|
|
||||||
|
|
||||||
def file_transfer_item(self, data, append):
|
|
||||||
data.append(self._messages.width())
|
|
||||||
item = FileTransferItem(*data)
|
|
||||||
elem = QtWidgets.QListWidgetItem()
|
|
||||||
elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34))
|
|
||||||
if append:
|
|
||||||
self._messages.addItem(elem)
|
|
||||||
else:
|
|
||||||
self._messages.insertItem(0, elem)
|
|
||||||
self._messages.setItemWidget(elem, item)
|
|
||||||
return item
|
|
@ -1,59 +0,0 @@
|
|||||||
from platform import system
|
|
||||||
from ctypes import CDLL
|
|
||||||
import util
|
|
||||||
|
|
||||||
|
|
||||||
class LibToxCore:
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
if system() == 'Windows':
|
|
||||||
self._libtoxcore = CDLL(util.curr_directory() + '/libs/libtox.dll')
|
|
||||||
elif system() == 'Darwin':
|
|
||||||
self._libtoxcore = CDLL('libtoxcore.dylib')
|
|
||||||
else:
|
|
||||||
# libtoxcore and libsodium must be installed in your os
|
|
||||||
try:
|
|
||||||
self._libtoxcore = CDLL('libtoxcore.so')
|
|
||||||
except:
|
|
||||||
self._libtoxcore = CDLL(util.curr_directory() + '/libs/libtoxcore.so')
|
|
||||||
|
|
||||||
def __getattr__(self, item):
|
|
||||||
return self._libtoxcore.__getattr__(item)
|
|
||||||
|
|
||||||
|
|
||||||
class LibToxAV:
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
if system() == 'Windows':
|
|
||||||
# on Windows av api is in libtox.dll
|
|
||||||
self._libtoxav = CDLL(util.curr_directory() + '/libs/libtox.dll')
|
|
||||||
elif system() == 'Darwin':
|
|
||||||
self._libtoxav = CDLL('libtoxav.dylib')
|
|
||||||
else:
|
|
||||||
# /usr/lib/libtoxav.so must exists
|
|
||||||
try:
|
|
||||||
self._libtoxav = CDLL('libtoxav.so')
|
|
||||||
except:
|
|
||||||
self._libtoxav = CDLL(util.curr_directory() + '/libs/libtoxav.so')
|
|
||||||
|
|
||||||
def __getattr__(self, item):
|
|
||||||
return self._libtoxav.__getattr__(item)
|
|
||||||
|
|
||||||
|
|
||||||
class LibToxEncryptSave:
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
if system() == 'Windows':
|
|
||||||
# on Windows profile encryption api is in libtox.dll
|
|
||||||
self._lib_tox_encrypt_save = CDLL(util.curr_directory() + '/libs/libtox.dll')
|
|
||||||
elif system() == 'Darwin':
|
|
||||||
self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.dylib')
|
|
||||||
else:
|
|
||||||
# /usr/lib/libtoxencryptsave.so must exists
|
|
||||||
try:
|
|
||||||
self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.so')
|
|
||||||
except:
|
|
||||||
self._lib_tox_encrypt_save = CDLL(util.curr_directory() + '/libs/libtoxencryptsave.so')
|
|
||||||
|
|
||||||
def __getattr__(self, item):
|
|
||||||
return self._lib_tox_encrypt_save.__getattr__(item)
|
|
@ -1,103 +0,0 @@
|
|||||||
from PyQt5 import QtWidgets, QtCore
|
|
||||||
from widgets import *
|
|
||||||
|
|
||||||
|
|
||||||
class NickEdit(LineEdit):
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
|
||||||
super(NickEdit, self).__init__(parent)
|
|
||||||
self.parent = parent
|
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
|
||||||
if event.key() == QtCore.Qt.Key_Return:
|
|
||||||
self.parent.create_profile()
|
|
||||||
else:
|
|
||||||
super(NickEdit, self).keyPressEvent(event)
|
|
||||||
|
|
||||||
|
|
||||||
class LoginScreen(CenteredWidget):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(LoginScreen, self).__init__()
|
|
||||||
self.initUI()
|
|
||||||
self.center()
|
|
||||||
|
|
||||||
def initUI(self):
|
|
||||||
self.resize(400, 200)
|
|
||||||
self.setMinimumSize(QtCore.QSize(400, 200))
|
|
||||||
self.setMaximumSize(QtCore.QSize(400, 200))
|
|
||||||
self.new_profile = QtWidgets.QPushButton(self)
|
|
||||||
self.new_profile.setGeometry(QtCore.QRect(20, 150, 171, 27))
|
|
||||||
self.new_profile.clicked.connect(self.create_profile)
|
|
||||||
self.label = QtWidgets.QLabel(self)
|
|
||||||
self.label.setGeometry(QtCore.QRect(20, 70, 101, 17))
|
|
||||||
self.new_name = NickEdit(self)
|
|
||||||
self.new_name.setGeometry(QtCore.QRect(20, 100, 171, 31))
|
|
||||||
self.load_profile = QtWidgets.QPushButton(self)
|
|
||||||
self.load_profile.setGeometry(QtCore.QRect(220, 150, 161, 27))
|
|
||||||
self.load_profile.clicked.connect(self.load_ex_profile)
|
|
||||||
self.default = QtWidgets.QCheckBox(self)
|
|
||||||
self.default.setGeometry(QtCore.QRect(220, 110, 131, 22))
|
|
||||||
self.groupBox = QtWidgets.QGroupBox(self)
|
|
||||||
self.groupBox.setGeometry(QtCore.QRect(210, 40, 181, 151))
|
|
||||||
self.comboBox = QtWidgets.QComboBox(self.groupBox)
|
|
||||||
self.comboBox.setGeometry(QtCore.QRect(10, 30, 161, 27))
|
|
||||||
self.groupBox_2 = QtWidgets.QGroupBox(self)
|
|
||||||
self.groupBox_2.setGeometry(QtCore.QRect(10, 40, 191, 151))
|
|
||||||
self.toxygen = QtWidgets.QLabel(self)
|
|
||||||
self.groupBox.raise_()
|
|
||||||
self.groupBox_2.raise_()
|
|
||||||
self.comboBox.raise_()
|
|
||||||
self.default.raise_()
|
|
||||||
self.load_profile.raise_()
|
|
||||||
self.new_name.raise_()
|
|
||||||
self.new_profile.raise_()
|
|
||||||
self.toxygen.setGeometry(QtCore.QRect(160, 8, 90, 25))
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setFamily("Impact")
|
|
||||||
font.setPointSize(16)
|
|
||||||
self.toxygen.setFont(font)
|
|
||||||
self.toxygen.setObjectName("toxygen")
|
|
||||||
self.type = 0
|
|
||||||
self.number = -1
|
|
||||||
self.load_as_default = False
|
|
||||||
self.name = None
|
|
||||||
self.retranslateUi()
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(self)
|
|
||||||
|
|
||||||
def retranslateUi(self):
|
|
||||||
self.new_name.setPlaceholderText(QtWidgets.QApplication.translate("login", "Profile name"))
|
|
||||||
self.setWindowTitle(QtWidgets.QApplication.translate("login", "Log in"))
|
|
||||||
self.new_profile.setText(QtWidgets.QApplication.translate("login", "Create"))
|
|
||||||
self.label.setText(QtWidgets.QApplication.translate("login", "Profile name:"))
|
|
||||||
self.load_profile.setText(QtWidgets.QApplication.translate("login", "Load profile"))
|
|
||||||
self.default.setText(QtWidgets.QApplication.translate("login", "Use as default"))
|
|
||||||
self.groupBox.setTitle(QtWidgets.QApplication.translate("login", "Load existing profile"))
|
|
||||||
self.groupBox_2.setTitle(QtWidgets.QApplication.translate("login", "Create new profile"))
|
|
||||||
self.toxygen.setText(QtWidgets.QApplication.translate("login", "toxygen"))
|
|
||||||
|
|
||||||
def create_profile(self):
|
|
||||||
self.type = 1
|
|
||||||
self.name = self.new_name.text()
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def load_ex_profile(self):
|
|
||||||
if not self.create_only:
|
|
||||||
self.type = 2
|
|
||||||
self.number = self.comboBox.currentIndex()
|
|
||||||
self.load_as_default = self.default.isChecked()
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def update_select(self, data):
|
|
||||||
list_of_profiles = []
|
|
||||||
for elem in data:
|
|
||||||
list_of_profiles.append(elem)
|
|
||||||
self.comboBox.addItems(list_of_profiles)
|
|
||||||
self.create_only = not list_of_profiles
|
|
||||||
|
|
||||||
def update_on_close(self, func):
|
|
||||||
self.onclose = func
|
|
||||||
|
|
||||||
def closeEvent(self, event):
|
|
||||||
self.onclose(self.type, self.number, self.load_as_default, self.name)
|
|
||||||
event.accept()
|
|
@ -1,757 +0,0 @@
|
|||||||
from menu import *
|
|
||||||
from profile import *
|
|
||||||
from list_items import *
|
|
||||||
from widgets import MultilineEdit, ComboBox
|
|
||||||
import plugin_support
|
|
||||||
from mainscreen_widgets import *
|
|
||||||
import settings
|
|
||||||
import toxes
|
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QtWidgets.QMainWindow, Singleton):
|
|
||||||
|
|
||||||
def __init__(self, tox, reset, tray):
|
|
||||||
super().__init__()
|
|
||||||
Singleton.__init__(self)
|
|
||||||
self.reset = reset
|
|
||||||
self.tray = tray
|
|
||||||
self.setAcceptDrops(True)
|
|
||||||
self.initUI(tox)
|
|
||||||
self._saved = False
|
|
||||||
if settings.Settings.get_instance()['show_welcome_screen']:
|
|
||||||
self.ws = WelcomeScreen()
|
|
||||||
|
|
||||||
def setup_menu(self, window):
|
|
||||||
self.menubar = QtWidgets.QMenuBar(window)
|
|
||||||
self.menubar.setObjectName("menubar")
|
|
||||||
self.menubar.setNativeMenuBar(False)
|
|
||||||
self.menubar.setMinimumSize(self.width(), 25)
|
|
||||||
self.menubar.setMaximumSize(self.width(), 25)
|
|
||||||
self.menubar.setBaseSize(self.width(), 25)
|
|
||||||
self.menuProfile = QtWidgets.QMenu(self.menubar)
|
|
||||||
|
|
||||||
self.menuProfile = QtWidgets.QMenu(self.menubar)
|
|
||||||
self.menuProfile.setObjectName("menuProfile")
|
|
||||||
self.menuSettings = QtWidgets.QMenu(self.menubar)
|
|
||||||
self.menuSettings.setObjectName("menuSettings")
|
|
||||||
self.menuPlugins = QtWidgets.QMenu(self.menubar)
|
|
||||||
self.menuPlugins.setObjectName("menuPlugins")
|
|
||||||
self.menuAbout = QtWidgets.QMenu(self.menubar)
|
|
||||||
self.menuAbout.setObjectName("menuAbout")
|
|
||||||
|
|
||||||
self.actionAdd_friend = QtWidgets.QAction(window)
|
|
||||||
self.actionAdd_gc = QtWidgets.QAction(window)
|
|
||||||
self.actionAdd_friend.setObjectName("actionAdd_friend")
|
|
||||||
self.actionprofilesettings = QtWidgets.QAction(window)
|
|
||||||
self.actionprofilesettings.setObjectName("actionprofilesettings")
|
|
||||||
self.actionPrivacy_settings = QtWidgets.QAction(window)
|
|
||||||
self.actionPrivacy_settings.setObjectName("actionPrivacy_settings")
|
|
||||||
self.actionInterface_settings = QtWidgets.QAction(window)
|
|
||||||
self.actionInterface_settings.setObjectName("actionInterface_settings")
|
|
||||||
self.actionNotifications = QtWidgets.QAction(window)
|
|
||||||
self.actionNotifications.setObjectName("actionNotifications")
|
|
||||||
self.actionNetwork = QtWidgets.QAction(window)
|
|
||||||
self.actionNetwork.setObjectName("actionNetwork")
|
|
||||||
self.actionAbout_program = QtWidgets.QAction(window)
|
|
||||||
self.actionAbout_program.setObjectName("actionAbout_program")
|
|
||||||
self.updateSettings = QtWidgets.QAction(window)
|
|
||||||
self.actionSettings = QtWidgets.QAction(window)
|
|
||||||
self.actionSettings.setObjectName("actionSettings")
|
|
||||||
self.audioSettings = QtWidgets.QAction(window)
|
|
||||||
self.videoSettings = QtWidgets.QAction(window)
|
|
||||||
self.pluginData = QtWidgets.QAction(window)
|
|
||||||
self.importPlugin = QtWidgets.QAction(window)
|
|
||||||
self.reloadPlugins = QtWidgets.QAction(window)
|
|
||||||
self.lockApp = QtWidgets.QAction(window)
|
|
||||||
self.menuProfile.addAction(self.actionAdd_friend)
|
|
||||||
self.menuProfile.addAction(self.actionAdd_gc)
|
|
||||||
self.menuProfile.addAction(self.actionSettings)
|
|
||||||
self.menuProfile.addAction(self.lockApp)
|
|
||||||
self.menuSettings.addAction(self.actionPrivacy_settings)
|
|
||||||
self.menuSettings.addAction(self.actionInterface_settings)
|
|
||||||
self.menuSettings.addAction(self.actionNotifications)
|
|
||||||
self.menuSettings.addAction(self.actionNetwork)
|
|
||||||
self.menuSettings.addAction(self.audioSettings)
|
|
||||||
self.menuSettings.addAction(self.videoSettings)
|
|
||||||
self.menuSettings.addAction(self.updateSettings)
|
|
||||||
self.menuPlugins.addAction(self.pluginData)
|
|
||||||
self.menuPlugins.addAction(self.importPlugin)
|
|
||||||
self.menuPlugins.addAction(self.reloadPlugins)
|
|
||||||
self.menuAbout.addAction(self.actionAbout_program)
|
|
||||||
|
|
||||||
self.menubar.addAction(self.menuProfile.menuAction())
|
|
||||||
self.menubar.addAction(self.menuSettings.menuAction())
|
|
||||||
self.menubar.addAction(self.menuPlugins.menuAction())
|
|
||||||
self.menubar.addAction(self.menuAbout.menuAction())
|
|
||||||
|
|
||||||
self.actionAbout_program.triggered.connect(self.about_program)
|
|
||||||
self.actionNetwork.triggered.connect(self.network_settings)
|
|
||||||
self.actionAdd_friend.triggered.connect(self.add_contact)
|
|
||||||
self.actionAdd_gc.triggered.connect(self.create_gc)
|
|
||||||
self.actionSettings.triggered.connect(self.profile_settings)
|
|
||||||
self.actionPrivacy_settings.triggered.connect(self.privacy_settings)
|
|
||||||
self.actionInterface_settings.triggered.connect(self.interface_settings)
|
|
||||||
self.actionNotifications.triggered.connect(self.notification_settings)
|
|
||||||
self.audioSettings.triggered.connect(self.audio_settings)
|
|
||||||
self.videoSettings.triggered.connect(self.video_settings)
|
|
||||||
self.updateSettings.triggered.connect(self.update_settings)
|
|
||||||
self.pluginData.triggered.connect(self.plugins_menu)
|
|
||||||
self.lockApp.triggered.connect(self.lock_app)
|
|
||||||
self.importPlugin.triggered.connect(self.import_plugin)
|
|
||||||
self.reloadPlugins.triggered.connect(self.reload_plugins)
|
|
||||||
|
|
||||||
def languageChange(self, *args, **kwargs):
|
|
||||||
self.retranslateUi()
|
|
||||||
|
|
||||||
def event(self, event):
|
|
||||||
if event.type() == QtCore.QEvent.WindowActivate:
|
|
||||||
self.tray.setIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
|
|
||||||
self.messages.repaint()
|
|
||||||
return super(MainWindow, self).event(event)
|
|
||||||
|
|
||||||
def retranslateUi(self):
|
|
||||||
self.lockApp.setText(QtWidgets.QApplication.translate("MainWindow", "Lock"))
|
|
||||||
self.menuPlugins.setTitle(QtWidgets.QApplication.translate("MainWindow", "Plugins"))
|
|
||||||
self.pluginData.setText(QtWidgets.QApplication.translate("MainWindow", "List of plugins"))
|
|
||||||
self.menuProfile.setTitle(QtWidgets.QApplication.translate("MainWindow", "Profile"))
|
|
||||||
self.menuSettings.setTitle(QtWidgets.QApplication.translate("MainWindow", "Settings"))
|
|
||||||
self.menuAbout.setTitle(QtWidgets.QApplication.translate("MainWindow", "About"))
|
|
||||||
self.actionAdd_friend.setText(QtWidgets.QApplication.translate("MainWindow", "Add contact"))
|
|
||||||
self.actionAdd_gc.setText(QtWidgets.QApplication.translate("MainWindow", "Create group chat"))
|
|
||||||
self.actionprofilesettings.setText(QtWidgets.QApplication.translate("MainWindow", "Profile"))
|
|
||||||
self.actionPrivacy_settings.setText(QtWidgets.QApplication.translate("MainWindow", "Privacy"))
|
|
||||||
self.actionInterface_settings.setText(QtWidgets.QApplication.translate("MainWindow", "Interface"))
|
|
||||||
self.actionNotifications.setText(QtWidgets.QApplication.translate("MainWindow", "Notifications"))
|
|
||||||
self.actionNetwork.setText(QtWidgets.QApplication.translate("MainWindow", "Network"))
|
|
||||||
self.actionAbout_program.setText(QtWidgets.QApplication.translate("MainWindow", "About program"))
|
|
||||||
self.actionSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Settings"))
|
|
||||||
self.audioSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Audio"))
|
|
||||||
self.videoSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Video"))
|
|
||||||
self.updateSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Updates"))
|
|
||||||
self.contact_name.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "Search"))
|
|
||||||
self.sendMessageButton.setToolTip(QtWidgets.QApplication.translate("MainWindow", "Send message"))
|
|
||||||
self.callButton.setToolTip(QtWidgets.QApplication.translate("MainWindow", "Start audio call with friend"))
|
|
||||||
self.online_contacts.clear()
|
|
||||||
self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "All"))
|
|
||||||
self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online"))
|
|
||||||
self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online first"))
|
|
||||||
self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Name"))
|
|
||||||
self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online and by name"))
|
|
||||||
self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online first and by name"))
|
|
||||||
ind = Settings.get_instance()['sorting']
|
|
||||||
d = {0: 0, 1: 1, 2: 2, 3: 4, 1 | 4: 4, 2 | 4: 5}
|
|
||||||
self.online_contacts.setCurrentIndex(d[ind])
|
|
||||||
self.importPlugin.setText(QtWidgets.QApplication.translate("MainWindow", "Import plugin"))
|
|
||||||
self.reloadPlugins.setText(QtWidgets.QApplication.translate("MainWindow", "Reload plugins"))
|
|
||||||
|
|
||||||
def setup_right_bottom(self, Form):
|
|
||||||
Form.resize(650, 60)
|
|
||||||
self.messageEdit = MessageArea(Form, self)
|
|
||||||
self.messageEdit.setGeometry(QtCore.QRect(0, 3, 450, 55))
|
|
||||||
self.messageEdit.setObjectName("messageEdit")
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setPointSize(11)
|
|
||||||
font.setFamily(settings.Settings.get_instance()['font'])
|
|
||||||
self.messageEdit.setFont(font)
|
|
||||||
|
|
||||||
self.sendMessageButton = QtWidgets.QPushButton(Form)
|
|
||||||
self.sendMessageButton.setGeometry(QtCore.QRect(565, 3, 60, 55))
|
|
||||||
self.sendMessageButton.setObjectName("sendMessageButton")
|
|
||||||
|
|
||||||
self.menuButton = MenuButton(Form, self.show_menu)
|
|
||||||
self.menuButton.setGeometry(QtCore.QRect(QtCore.QRect(455, 3, 55, 55)))
|
|
||||||
|
|
||||||
pixmap = QtGui.QPixmap('send.png')
|
|
||||||
icon = QtGui.QIcon(pixmap)
|
|
||||||
self.sendMessageButton.setIcon(icon)
|
|
||||||
self.sendMessageButton.setIconSize(QtCore.QSize(45, 60))
|
|
||||||
|
|
||||||
pixmap = QtGui.QPixmap('menu.png')
|
|
||||||
icon = QtGui.QIcon(pixmap)
|
|
||||||
self.menuButton.setIcon(icon)
|
|
||||||
self.menuButton.setIconSize(QtCore.QSize(40, 40))
|
|
||||||
|
|
||||||
self.sendMessageButton.clicked.connect(self.send_message)
|
|
||||||
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
|
||||||
|
|
||||||
def setup_left_center_menu(self, Form):
|
|
||||||
Form.resize(270, 25)
|
|
||||||
self.search_label = QtWidgets.QLabel(Form)
|
|
||||||
self.search_label.setGeometry(QtCore.QRect(3, 2, 20, 20))
|
|
||||||
pixmap = QtGui.QPixmap()
|
|
||||||
pixmap.load(curr_directory() + '/images/search.png')
|
|
||||||
self.search_label.setScaledContents(False)
|
|
||||||
self.search_label.setPixmap(pixmap)
|
|
||||||
|
|
||||||
self.contact_name = LineEdit(Form)
|
|
||||||
self.contact_name.setGeometry(QtCore.QRect(0, 0, 150, 25))
|
|
||||||
self.contact_name.setObjectName("contact_name")
|
|
||||||
self.contact_name.textChanged.connect(self.filtering)
|
|
||||||
|
|
||||||
self.online_contacts = ComboBox(Form)
|
|
||||||
self.online_contacts.setGeometry(QtCore.QRect(150, 0, 120, 25))
|
|
||||||
self.online_contacts.activated[int].connect(lambda x: self.filtering())
|
|
||||||
self.search_label.raise_()
|
|
||||||
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
|
||||||
|
|
||||||
def setup_left_top(self, Form):
|
|
||||||
Form.setCursor(QtCore.Qt.PointingHandCursor)
|
|
||||||
Form.setMinimumSize(QtCore.QSize(270, 75))
|
|
||||||
Form.setMaximumSize(QtCore.QSize(270, 75))
|
|
||||||
Form.setBaseSize(QtCore.QSize(270, 75))
|
|
||||||
self.avatar_label = Form.avatar_label = QtWidgets.QLabel(Form)
|
|
||||||
self.avatar_label.setGeometry(QtCore.QRect(5, 5, 64, 64))
|
|
||||||
self.avatar_label.setScaledContents(False)
|
|
||||||
self.avatar_label.setAlignment(QtCore.Qt.AlignCenter)
|
|
||||||
self.name = Form.name = DataLabel(Form)
|
|
||||||
Form.name.setGeometry(QtCore.QRect(75, 15, 150, 25))
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setFamily(settings.Settings.get_instance()['font'])
|
|
||||||
font.setPointSize(14)
|
|
||||||
font.setBold(True)
|
|
||||||
Form.name.setFont(font)
|
|
||||||
Form.name.setObjectName("name")
|
|
||||||
self.status_message = Form.status_message = DataLabel(Form)
|
|
||||||
Form.status_message.setGeometry(QtCore.QRect(75, 35, 170, 25))
|
|
||||||
font.setPointSize(12)
|
|
||||||
font.setBold(False)
|
|
||||||
Form.status_message.setFont(font)
|
|
||||||
Form.status_message.setObjectName("status_message")
|
|
||||||
self.connection_status = Form.connection_status = StatusCircle(Form)
|
|
||||||
Form.connection_status.setGeometry(QtCore.QRect(230, 10, 32, 32))
|
|
||||||
self.avatar_label.mouseReleaseEvent = self.profile_settings
|
|
||||||
self.status_message.mouseReleaseEvent = self.profile_settings
|
|
||||||
self.name.mouseReleaseEvent = self.profile_settings
|
|
||||||
self.connection_status.raise_()
|
|
||||||
Form.connection_status.setObjectName("connection_status")
|
|
||||||
|
|
||||||
def setup_right_top(self, Form):
|
|
||||||
Form.resize(650, 75)
|
|
||||||
self.account_avatar = QtWidgets.QLabel(Form)
|
|
||||||
self.account_avatar.setGeometry(QtCore.QRect(10, 5, 64, 64))
|
|
||||||
self.account_avatar.setScaledContents(False)
|
|
||||||
self.account_name = DataLabel(Form)
|
|
||||||
self.account_name.setGeometry(QtCore.QRect(100, 0, 400, 25))
|
|
||||||
self.account_name.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse)
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setFamily(settings.Settings.get_instance()['font'])
|
|
||||||
font.setPointSize(14)
|
|
||||||
font.setBold(True)
|
|
||||||
self.account_name.setFont(font)
|
|
||||||
self.account_name.setObjectName("account_name")
|
|
||||||
self.account_status = DataLabel(Form)
|
|
||||||
self.account_status.setGeometry(QtCore.QRect(100, 20, 400, 25))
|
|
||||||
self.account_status.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse)
|
|
||||||
font.setPointSize(12)
|
|
||||||
font.setBold(False)
|
|
||||||
self.account_status.setFont(font)
|
|
||||||
self.account_status.setObjectName("account_status")
|
|
||||||
self.callButton = QtWidgets.QPushButton(Form)
|
|
||||||
self.callButton.setGeometry(QtCore.QRect(550, 5, 50, 50))
|
|
||||||
self.callButton.setObjectName("callButton")
|
|
||||||
self.callButton.clicked.connect(lambda: self.profile.call_click(True))
|
|
||||||
self.videocallButton = QtWidgets.QPushButton(Form)
|
|
||||||
self.videocallButton.setGeometry(QtCore.QRect(550, 5, 50, 50))
|
|
||||||
self.videocallButton.setObjectName("videocallButton")
|
|
||||||
self.videocallButton.clicked.connect(lambda: self.profile.call_click(True, True))
|
|
||||||
self.update_call_state('call')
|
|
||||||
self.typing = QtWidgets.QLabel(Form)
|
|
||||||
self.typing.setGeometry(QtCore.QRect(500, 25, 50, 30))
|
|
||||||
pixmap = QtGui.QPixmap(QtCore.QSize(50, 30))
|
|
||||||
pixmap.load(curr_directory() + '/images/typing.png')
|
|
||||||
self.typing.setScaledContents(False)
|
|
||||||
self.typing.setPixmap(pixmap.scaled(50, 30, QtCore.Qt.KeepAspectRatio))
|
|
||||||
self.typing.setVisible(False)
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
|
||||||
|
|
||||||
def setup_left_center(self, widget):
|
|
||||||
self.friends_list = QtWidgets.QListWidget(widget)
|
|
||||||
self.friends_list.setObjectName("friends_list")
|
|
||||||
self.friends_list.setGeometry(0, 0, 270, 310)
|
|
||||||
self.friends_list.clicked.connect(self.friend_click)
|
|
||||||
self.friends_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
|
||||||
self.friends_list.customContextMenuRequested.connect(self.friend_right_click)
|
|
||||||
self.friends_list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
|
||||||
self.friends_list.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
|
||||||
self.friends_list.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
|
||||||
self.friends_list.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu)
|
|
||||||
|
|
||||||
def setup_right_center(self, widget):
|
|
||||||
self.messages = QtWidgets.QListWidget(widget)
|
|
||||||
self.messages.setGeometry(0, 0, 620, 310)
|
|
||||||
self.messages.setObjectName("messages")
|
|
||||||
self.messages.setSpacing(1)
|
|
||||||
self.messages.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
|
||||||
self.messages.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
|
||||||
self.messages.focusOutEvent = lambda event: self.messages.clearSelection()
|
|
||||||
self.messages.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu)
|
|
||||||
|
|
||||||
def load(pos):
|
|
||||||
if not pos:
|
|
||||||
self.profile.load_history()
|
|
||||||
self.messages.verticalScrollBar().setValue(1)
|
|
||||||
self.messages.verticalScrollBar().valueChanged.connect(load)
|
|
||||||
self.messages.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
|
||||||
self.messages.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
|
||||||
|
|
||||||
def initUI(self, tox):
|
|
||||||
self.setMinimumSize(920, 500)
|
|
||||||
s = Settings.get_instance()
|
|
||||||
self.setGeometry(s['x'], s['y'], s['width'], s['height'])
|
|
||||||
self.setWindowTitle('Toxygen')
|
|
||||||
os.chdir(curr_directory() + '/images/')
|
|
||||||
menu = QtWidgets.QWidget()
|
|
||||||
main = QtWidgets.QWidget()
|
|
||||||
grid = QtWidgets.QGridLayout()
|
|
||||||
search = QtWidgets.QWidget()
|
|
||||||
name = QtWidgets.QWidget()
|
|
||||||
info = QtWidgets.QWidget()
|
|
||||||
main_list = QtWidgets.QWidget()
|
|
||||||
messages = QtWidgets.QWidget()
|
|
||||||
message_buttons = QtWidgets.QWidget()
|
|
||||||
self.setup_left_center_menu(search)
|
|
||||||
self.setup_left_top(name)
|
|
||||||
self.setup_right_center(messages)
|
|
||||||
self.setup_right_top(info)
|
|
||||||
self.setup_right_bottom(message_buttons)
|
|
||||||
self.setup_left_center(main_list)
|
|
||||||
self.setup_menu(menu)
|
|
||||||
if not Settings.get_instance()['mirror_mode']:
|
|
||||||
grid.addWidget(search, 2, 0)
|
|
||||||
grid.addWidget(name, 1, 0)
|
|
||||||
grid.addWidget(messages, 2, 1, 2, 1)
|
|
||||||
grid.addWidget(info, 1, 1)
|
|
||||||
grid.addWidget(message_buttons, 4, 1)
|
|
||||||
grid.addWidget(main_list, 3, 0, 2, 1)
|
|
||||||
grid.setColumnMinimumWidth(1, 500)
|
|
||||||
grid.setColumnMinimumWidth(0, 270)
|
|
||||||
else:
|
|
||||||
grid.addWidget(search, 2, 1)
|
|
||||||
grid.addWidget(name, 1, 1)
|
|
||||||
grid.addWidget(messages, 2, 0, 2, 1)
|
|
||||||
grid.addWidget(info, 1, 0)
|
|
||||||
grid.addWidget(message_buttons, 4, 0)
|
|
||||||
grid.addWidget(main_list, 3, 1, 2, 1)
|
|
||||||
grid.setColumnMinimumWidth(0, 500)
|
|
||||||
grid.setColumnMinimumWidth(1, 270)
|
|
||||||
|
|
||||||
grid.addWidget(menu, 0, 0, 1, 2)
|
|
||||||
grid.setSpacing(0)
|
|
||||||
grid.setContentsMargins(0, 0, 0, 0)
|
|
||||||
grid.setRowMinimumHeight(0, 25)
|
|
||||||
grid.setRowMinimumHeight(1, 75)
|
|
||||||
grid.setRowMinimumHeight(2, 25)
|
|
||||||
grid.setRowMinimumHeight(3, 320)
|
|
||||||
grid.setRowMinimumHeight(4, 55)
|
|
||||||
grid.setColumnStretch(1, 1)
|
|
||||||
grid.setRowStretch(3, 1)
|
|
||||||
main.setLayout(grid)
|
|
||||||
self.setCentralWidget(main)
|
|
||||||
self.messageEdit.setFocus()
|
|
||||||
self.user_info = name
|
|
||||||
self.friend_info = info
|
|
||||||
self.retranslateUi()
|
|
||||||
self.profile = Profile(tox, self)
|
|
||||||
|
|
||||||
def closeEvent(self, event):
|
|
||||||
s = Settings.get_instance()
|
|
||||||
if not s['close_to_tray'] or s.closing:
|
|
||||||
if not self._saved:
|
|
||||||
self._saved = True
|
|
||||||
self.profile.save_history()
|
|
||||||
self.profile.close()
|
|
||||||
s['x'] = self.geometry().x()
|
|
||||||
s['y'] = self.geometry().y()
|
|
||||||
s['width'] = self.width()
|
|
||||||
s['height'] = self.height()
|
|
||||||
s.save()
|
|
||||||
QtWidgets.QApplication.closeAllWindows()
|
|
||||||
event.accept()
|
|
||||||
elif QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
|
|
||||||
event.ignore()
|
|
||||||
self.hide()
|
|
||||||
|
|
||||||
def close_window(self):
|
|
||||||
Settings.get_instance().closing = True
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def resizeEvent(self, *args, **kwargs):
|
|
||||||
self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155)
|
|
||||||
self.friends_list.setGeometry(0, 0, 270, self.height() - 125)
|
|
||||||
|
|
||||||
self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 10, 50, 50))
|
|
||||||
self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 10, 50, 50))
|
|
||||||
self.typing.setGeometry(QtCore.QRect(self.width() - 450, 20, 50, 30))
|
|
||||||
|
|
||||||
self.messageEdit.setGeometry(QtCore.QRect(55, 0, self.width() - 395, 55))
|
|
||||||
self.menuButton.setGeometry(QtCore.QRect(0, 0, 55, 55))
|
|
||||||
self.sendMessageButton.setGeometry(QtCore.QRect(self.width() - 340, 0, 70, 55))
|
|
||||||
|
|
||||||
self.account_name.setGeometry(QtCore.QRect(100, 15, self.width() - 560, 25))
|
|
||||||
self.account_status.setGeometry(QtCore.QRect(100, 35, self.width() - 560, 25))
|
|
||||||
self.messageEdit.setFocus()
|
|
||||||
self.profile.update()
|
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
|
||||||
if event.key() == QtCore.Qt.Key_Escape and QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
|
|
||||||
self.hide()
|
|
||||||
elif event.key() == QtCore.Qt.Key_C and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
|
|
||||||
rows = list(map(lambda x: self.messages.row(x), self.messages.selectedItems()))
|
|
||||||
indexes = (rows[0] - self.messages.count(), rows[-1] - self.messages.count())
|
|
||||||
s = self.profile.export_history(self.profile.active_friend, True, indexes)
|
|
||||||
clipboard = QtWidgets.QApplication.clipboard()
|
|
||||||
clipboard.setText(s)
|
|
||||||
elif event.key() == QtCore.Qt.Key_Z and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
|
|
||||||
self.messages.clearSelection()
|
|
||||||
elif event.key() == QtCore.Qt.Key_F and event.modifiers() & QtCore.Qt.ControlModifier:
|
|
||||||
self.show_search_field()
|
|
||||||
else:
|
|
||||||
super(MainWindow, self).keyPressEvent(event)
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Functions which called when user click in menu
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def about_program(self):
|
|
||||||
import util
|
|
||||||
msgBox = QtWidgets.QMessageBox()
|
|
||||||
msgBox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "About"))
|
|
||||||
text = (QtWidgets.QApplication.translate("MainWindow", 'Toxygen is Tox client written on Python.<br>Version: '))
|
|
||||||
github = '<br><a href="https://github.com/toxygen-project/toxygen/">Github</a>'
|
|
||||||
submit_a_bug = '<br><a href="https://github.com/toxygen-project/toxygen/issues">Submit a bug</a>'
|
|
||||||
msgBox.setText(text + util.program_version + github + submit_a_bug)
|
|
||||||
msgBox.exec_()
|
|
||||||
|
|
||||||
def network_settings(self):
|
|
||||||
self.n_s = NetworkSettings(self.reset)
|
|
||||||
self.n_s.show()
|
|
||||||
|
|
||||||
def plugins_menu(self):
|
|
||||||
self.p_s = PluginsSettings()
|
|
||||||
self.p_s.show()
|
|
||||||
|
|
||||||
def add_contact(self, link=''):
|
|
||||||
self.a_c = AddContact(link or '')
|
|
||||||
self.a_c.show()
|
|
||||||
|
|
||||||
def create_gc(self):
|
|
||||||
self.profile.create_group_chat()
|
|
||||||
|
|
||||||
def profile_settings(self, *args):
|
|
||||||
self.p_s = ProfileSettings()
|
|
||||||
self.p_s.show()
|
|
||||||
|
|
||||||
def privacy_settings(self):
|
|
||||||
self.priv_s = PrivacySettings()
|
|
||||||
self.priv_s.show()
|
|
||||||
|
|
||||||
def notification_settings(self):
|
|
||||||
self.notif_s = NotificationsSettings()
|
|
||||||
self.notif_s.show()
|
|
||||||
|
|
||||||
def interface_settings(self):
|
|
||||||
self.int_s = InterfaceSettings()
|
|
||||||
self.int_s.show()
|
|
||||||
|
|
||||||
def audio_settings(self):
|
|
||||||
self.audio_s = AudioSettings()
|
|
||||||
self.audio_s.show()
|
|
||||||
|
|
||||||
def video_settings(self):
|
|
||||||
self.video_s = VideoSettings()
|
|
||||||
self.video_s.show()
|
|
||||||
|
|
||||||
def update_settings(self):
|
|
||||||
self.update_s = UpdateSettings()
|
|
||||||
self.update_s.show()
|
|
||||||
|
|
||||||
def reload_plugins(self):
|
|
||||||
plugin_loader = plugin_support.PluginLoader.get_instance()
|
|
||||||
if plugin_loader is not None:
|
|
||||||
plugin_loader.reload()
|
|
||||||
|
|
||||||
def import_plugin(self):
|
|
||||||
import util
|
|
||||||
directory = QtWidgets.QFileDialog.getExistingDirectory(self,
|
|
||||||
QtWidgets.QApplication.translate("MainWindow", 'Choose folder with plugin'),
|
|
||||||
util.curr_directory(),
|
|
||||||
QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
|
|
||||||
if directory:
|
|
||||||
src = directory + '/'
|
|
||||||
dest = curr_directory() + '/plugins/'
|
|
||||||
util.copy(src, dest)
|
|
||||||
msgBox = QtWidgets.QMessageBox()
|
|
||||||
msgBox.setWindowTitle(
|
|
||||||
QtWidgets.QApplication.translate("MainWindow", "Restart Toxygen"))
|
|
||||||
msgBox.setText(
|
|
||||||
QtWidgets.QApplication.translate("MainWindow", 'Plugin will be loaded after restart'))
|
|
||||||
msgBox.exec_()
|
|
||||||
|
|
||||||
def lock_app(self):
|
|
||||||
if toxes.ToxES.get_instance().has_password():
|
|
||||||
Settings.get_instance().locked = True
|
|
||||||
self.hide()
|
|
||||||
else:
|
|
||||||
msgBox = QtWidgets.QMessageBox()
|
|
||||||
msgBox.setWindowTitle(
|
|
||||||
QtWidgets.QApplication.translate("MainWindow", "Cannot lock app"))
|
|
||||||
msgBox.setText(
|
|
||||||
QtWidgets.QApplication.translate("MainWindow", 'Error. Profile password is not set.'))
|
|
||||||
msgBox.exec_()
|
|
||||||
|
|
||||||
def show_menu(self):
|
|
||||||
if not hasattr(self, 'menu'):
|
|
||||||
self.menu = DropdownMenu(self)
|
|
||||||
self.menu.setGeometry(QtCore.QRect(0 if Settings.get_instance()['mirror_mode'] else 270,
|
|
||||||
self.height() - 120,
|
|
||||||
180,
|
|
||||||
120))
|
|
||||||
self.menu.show()
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Messages, calls and file transfers
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def send_message(self):
|
|
||||||
text = self.messageEdit.toPlainText()
|
|
||||||
self.profile.send_message(text)
|
|
||||||
|
|
||||||
def send_file(self):
|
|
||||||
self.menu.hide()
|
|
||||||
if self.profile.active_friend + 1and self.profile.is_active_a_friend():
|
|
||||||
choose = QtWidgets.QApplication.translate("MainWindow", 'Choose file')
|
|
||||||
name = QtWidgets.QFileDialog.getOpenFileName(self, choose, options=QtWidgets.QFileDialog.DontUseNativeDialog)
|
|
||||||
if name[0]:
|
|
||||||
self.profile.send_file(name[0])
|
|
||||||
|
|
||||||
def send_screenshot(self, hide=False):
|
|
||||||
self.menu.hide()
|
|
||||||
if self.profile.active_friend + 1 and self.profile.is_active_a_friend():
|
|
||||||
self.sw = ScreenShotWindow(self)
|
|
||||||
self.sw.show()
|
|
||||||
if hide:
|
|
||||||
self.hide()
|
|
||||||
|
|
||||||
def send_smiley(self):
|
|
||||||
self.menu.hide()
|
|
||||||
if self.profile.active_friend + 1:
|
|
||||||
self.smiley = SmileyWindow(self)
|
|
||||||
self.smiley.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(),
|
|
||||||
self.y() + self.height() - 200,
|
|
||||||
self.smiley.width(),
|
|
||||||
self.smiley.height()))
|
|
||||||
self.smiley.show()
|
|
||||||
|
|
||||||
def send_sticker(self):
|
|
||||||
self.menu.hide()
|
|
||||||
if self.profile.active_friend + 1 and self.profile.is_active_a_friend():
|
|
||||||
self.sticker = StickerWindow(self)
|
|
||||||
self.sticker.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(),
|
|
||||||
self.y() + self.height() - 200,
|
|
||||||
self.sticker.width(),
|
|
||||||
self.sticker.height()))
|
|
||||||
self.sticker.show()
|
|
||||||
|
|
||||||
def active_call(self):
|
|
||||||
self.update_call_state('finish_call')
|
|
||||||
|
|
||||||
def incoming_call(self):
|
|
||||||
self.update_call_state('incoming_call')
|
|
||||||
|
|
||||||
def call_finished(self):
|
|
||||||
self.update_call_state('call')
|
|
||||||
|
|
||||||
def update_call_state(self, state):
|
|
||||||
os.chdir(curr_directory() + '/images/')
|
|
||||||
|
|
||||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(state))
|
|
||||||
icon = QtGui.QIcon(pixmap)
|
|
||||||
self.callButton.setIcon(icon)
|
|
||||||
self.callButton.setIconSize(QtCore.QSize(50, 50))
|
|
||||||
|
|
||||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}_video.png'.format(state))
|
|
||||||
icon = QtGui.QIcon(pixmap)
|
|
||||||
self.videocallButton.setIcon(icon)
|
|
||||||
self.videocallButton.setIconSize(QtCore.QSize(35, 35))
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Functions which called when user open context menu in friends list
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def friend_right_click(self, pos):
|
|
||||||
item = self.friends_list.itemAt(pos)
|
|
||||||
num = self.friends_list.indexFromItem(item).row()
|
|
||||||
friend = Profile.get_instance().get_friend(num)
|
|
||||||
if friend is None:
|
|
||||||
return
|
|
||||||
settings = Settings.get_instance()
|
|
||||||
allowed = friend.tox_id in settings['auto_accept_from_friends']
|
|
||||||
auto = QtWidgets.QApplication.translate("MainWindow", 'Disallow auto accept') if allowed else QtWidgets.QApplication.translate("MainWindow", 'Allow auto accept')
|
|
||||||
if item is not None:
|
|
||||||
self.listMenu = QtWidgets.QMenu()
|
|
||||||
is_friend = type(friend) is Friend
|
|
||||||
if is_friend:
|
|
||||||
set_alias_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Set alias'))
|
|
||||||
set_alias_item.triggered.connect(lambda: self.set_alias(num))
|
|
||||||
|
|
||||||
history_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Chat history'))
|
|
||||||
clear_history_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Clear history'))
|
|
||||||
export_to_text_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Export as text'))
|
|
||||||
export_to_html_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Export as HTML'))
|
|
||||||
|
|
||||||
copy_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Copy'))
|
|
||||||
copy_name_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Name'))
|
|
||||||
copy_status_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Status message'))
|
|
||||||
if is_friend:
|
|
||||||
copy_key_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Public key'))
|
|
||||||
|
|
||||||
auto_accept_item = self.listMenu.addAction(auto)
|
|
||||||
remove_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Remove friend'))
|
|
||||||
block_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Block friend'))
|
|
||||||
notes_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Notes'))
|
|
||||||
|
|
||||||
chats = self.profile.get_group_chats()
|
|
||||||
if len(chats) and self.profile.is_active_online():
|
|
||||||
invite_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Invite to group chat'))
|
|
||||||
for i in range(len(chats)):
|
|
||||||
name, number = chats[i]
|
|
||||||
item = invite_menu.addAction(name)
|
|
||||||
item.triggered.connect(lambda number=number: self.invite_friend_to_gc(num, number))
|
|
||||||
|
|
||||||
plugins_loader = plugin_support.PluginLoader.get_instance()
|
|
||||||
if plugins_loader is not None:
|
|
||||||
submenu = plugins_loader.get_menu(self.listMenu, num)
|
|
||||||
if len(submenu):
|
|
||||||
plug = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins'))
|
|
||||||
plug.addActions(submenu)
|
|
||||||
copy_key_item.triggered.connect(lambda: self.copy_friend_key(num))
|
|
||||||
remove_item.triggered.connect(lambda: self.remove_friend(num))
|
|
||||||
block_item.triggered.connect(lambda: self.block_friend(num))
|
|
||||||
auto_accept_item.triggered.connect(lambda: self.auto_accept(num, not allowed))
|
|
||||||
notes_item.triggered.connect(lambda: self.show_note(friend))
|
|
||||||
else:
|
|
||||||
leave_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Leave chat'))
|
|
||||||
set_title_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Set title'))
|
|
||||||
leave_item.triggered.connect(lambda: self.leave_gc(num))
|
|
||||||
set_title_item.triggered.connect(lambda: self.set_title(num))
|
|
||||||
clear_history_item.triggered.connect(lambda: self.clear_history(num))
|
|
||||||
copy_name_item.triggered.connect(lambda: self.copy_name(friend))
|
|
||||||
copy_status_item.triggered.connect(lambda: self.copy_status(friend))
|
|
||||||
export_to_text_item.triggered.connect(lambda: self.export_history(num))
|
|
||||||
export_to_html_item.triggered.connect(lambda: self.export_history(num, False))
|
|
||||||
parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0))
|
|
||||||
self.listMenu.move(parent_position + pos)
|
|
||||||
self.listMenu.show()
|
|
||||||
|
|
||||||
def show_note(self, friend):
|
|
||||||
s = Settings.get_instance()
|
|
||||||
note = s['notes'][friend.tox_id] if friend.tox_id in s['notes'] else ''
|
|
||||||
user = QtWidgets.QApplication.translate("MainWindow", 'Notes about user')
|
|
||||||
user = '{} {}'.format(user, friend.name)
|
|
||||||
|
|
||||||
def save_note(text):
|
|
||||||
if friend.tox_id in s['notes']:
|
|
||||||
del s['notes'][friend.tox_id]
|
|
||||||
if text:
|
|
||||||
s['notes'][friend.tox_id] = text
|
|
||||||
s.save()
|
|
||||||
self.note = MultilineEdit(user, note, save_note)
|
|
||||||
self.note.show()
|
|
||||||
|
|
||||||
def export_history(self, num, as_text=True):
|
|
||||||
s = self.profile.export_history(num, as_text)
|
|
||||||
extension = 'txt' if as_text else 'html'
|
|
||||||
file_name, _ = QtWidgets.QFileDialog.getSaveFileName(None,
|
|
||||||
QtWidgets.QApplication.translate("MainWindow",
|
|
||||||
'Choose file name'),
|
|
||||||
curr_directory(),
|
|
||||||
filter=extension,
|
|
||||||
options=QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
|
|
||||||
|
|
||||||
if file_name:
|
|
||||||
if not file_name.endswith('.' + extension):
|
|
||||||
file_name += '.' + extension
|
|
||||||
with open(file_name, 'wt') as fl:
|
|
||||||
fl.write(s)
|
|
||||||
|
|
||||||
def set_alias(self, num):
|
|
||||||
self.profile.set_alias(num)
|
|
||||||
|
|
||||||
def remove_friend(self, num):
|
|
||||||
self.profile.delete_friend(num)
|
|
||||||
|
|
||||||
def block_friend(self, num):
|
|
||||||
friend = self.profile.get_friend(num)
|
|
||||||
self.profile.block_user(friend.tox_id)
|
|
||||||
|
|
||||||
def copy_friend_key(self, num):
|
|
||||||
tox_id = self.profile.friend_public_key(num)
|
|
||||||
clipboard = QtWidgets.QApplication.clipboard()
|
|
||||||
clipboard.setText(tox_id)
|
|
||||||
|
|
||||||
def copy_name(self, friend):
|
|
||||||
clipboard = QtWidgets.QApplication.clipboard()
|
|
||||||
clipboard.setText(friend.name)
|
|
||||||
|
|
||||||
def copy_status(self, friend):
|
|
||||||
clipboard = QtWidgets.QApplication.clipboard()
|
|
||||||
clipboard.setText(friend.status_message)
|
|
||||||
|
|
||||||
def clear_history(self, num):
|
|
||||||
self.profile.clear_history(num)
|
|
||||||
|
|
||||||
def leave_gc(self, num):
|
|
||||||
self.profile.leave_gc(num)
|
|
||||||
|
|
||||||
def set_title(self, num):
|
|
||||||
self.profile.set_title(num)
|
|
||||||
|
|
||||||
def auto_accept(self, num, value):
|
|
||||||
settings = Settings.get_instance()
|
|
||||||
tox_id = self.profile.friend_public_key(num)
|
|
||||||
if value:
|
|
||||||
settings['auto_accept_from_friends'].append(tox_id)
|
|
||||||
else:
|
|
||||||
settings['auto_accept_from_friends'].remove(tox_id)
|
|
||||||
settings.save()
|
|
||||||
|
|
||||||
def invite_friend_to_gc(self, friend_number, group_number):
|
|
||||||
self.profile.invite_friend(friend_number, group_number)
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Functions which called when user click somewhere else
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def friend_click(self, index):
|
|
||||||
num = index.row()
|
|
||||||
self.profile.set_active(num)
|
|
||||||
|
|
||||||
def mouseReleaseEvent(self, event):
|
|
||||||
pos = self.connection_status.pos()
|
|
||||||
x, y = pos.x() + self.user_info.pos().x(), pos.y() + self.user_info.pos().y()
|
|
||||||
if (x < event.x() < x + 32) and (y < event.y() < y + 32):
|
|
||||||
self.profile.change_status()
|
|
||||||
else:
|
|
||||||
super(MainWindow, self).mouseReleaseEvent(event)
|
|
||||||
|
|
||||||
def show(self):
|
|
||||||
super().show()
|
|
||||||
self.profile.update()
|
|
||||||
|
|
||||||
def filtering(self):
|
|
||||||
ind = self.online_contacts.currentIndex()
|
|
||||||
d = {0: 0, 1: 1, 2: 2, 3: 4, 4: 1 | 4, 5: 2 | 4}
|
|
||||||
self.profile.filtration_and_sorting(d[ind], self.contact_name.text())
|
|
||||||
|
|
||||||
def show_search_field(self):
|
|
||||||
if hasattr(self, 'search_field') and self.search_field.isVisible():
|
|
||||||
return
|
|
||||||
if self.profile.get_curr_friend() is None:
|
|
||||||
return
|
|
||||||
self.search_field = SearchScreen(self.messages, self.messages.width(), self.messages.parent())
|
|
||||||
x, y = self.messages.x(), self.messages.y() + self.messages.height() - 40
|
|
||||||
self.search_field.setGeometry(x, y, self.messages.width(), 40)
|
|
||||||
self.messages.setGeometry(x, self.messages.y(), self.messages.width(), self.messages.height() - 40)
|
|
||||||
self.search_field.show()
|
|
1095
toxygen/menu.py
1095
toxygen/menu.py
File diff suppressed because it is too large
Load Diff
@ -1,113 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
MESSAGE_TYPE = {
|
|
||||||
'TEXT': 0,
|
|
||||||
'ACTION': 1,
|
|
||||||
'FILE_TRANSFER': 2,
|
|
||||||
'INLINE': 3,
|
|
||||||
'INFO_MESSAGE': 4,
|
|
||||||
'GC_TEXT': 5,
|
|
||||||
'GC_ACTION': 6
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Message:
|
|
||||||
|
|
||||||
def __init__(self, message_type, owner, time):
|
|
||||||
self._time = time
|
|
||||||
self._type = message_type
|
|
||||||
self._owner = owner
|
|
||||||
|
|
||||||
def get_type(self):
|
|
||||||
return self._type
|
|
||||||
|
|
||||||
def get_owner(self):
|
|
||||||
return self._owner
|
|
||||||
|
|
||||||
def mark_as_sent(self):
|
|
||||||
self._owner = 0
|
|
||||||
|
|
||||||
|
|
||||||
class TextMessage(Message):
|
|
||||||
"""
|
|
||||||
Plain text or action message
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, message, owner, time, message_type):
|
|
||||||
super(TextMessage, self).__init__(message_type, owner, time)
|
|
||||||
self._message = message
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
return self._message, self._owner, self._time, self._type
|
|
||||||
|
|
||||||
|
|
||||||
class GroupChatMessage(TextMessage):
|
|
||||||
|
|
||||||
def __init__(self, message, owner, time, message_type, name):
|
|
||||||
super().__init__(message, owner, time, message_type)
|
|
||||||
self._user_name = name
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
return self._message, self._owner, self._time, self._type, self._user_name
|
|
||||||
|
|
||||||
|
|
||||||
class TransferMessage(Message):
|
|
||||||
"""
|
|
||||||
Message with info about file transfer
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, owner, time, status, size, name, friend_number, file_number):
|
|
||||||
super(TransferMessage, self).__init__(MESSAGE_TYPE['FILE_TRANSFER'], owner, time)
|
|
||||||
self._status = status
|
|
||||||
self._size = size
|
|
||||||
self._file_name = name
|
|
||||||
self._friend_number, self._file_number = friend_number, file_number
|
|
||||||
|
|
||||||
def is_active(self, file_number):
|
|
||||||
return self._file_number == file_number and self._status not in (2, 3)
|
|
||||||
|
|
||||||
def get_friend_number(self):
|
|
||||||
return self._friend_number
|
|
||||||
|
|
||||||
def get_file_number(self):
|
|
||||||
return self._file_number
|
|
||||||
|
|
||||||
def get_status(self):
|
|
||||||
return self._status
|
|
||||||
|
|
||||||
def set_status(self, value):
|
|
||||||
self._status = value
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
return self._file_name, self._size, self._time, self._owner, self._friend_number, self._file_number, self._status
|
|
||||||
|
|
||||||
|
|
||||||
class UnsentFile(Message):
|
|
||||||
def __init__(self, path, data, time):
|
|
||||||
super(UnsentFile, self).__init__(MESSAGE_TYPE['FILE_TRANSFER'], 0, time)
|
|
||||||
self._data, self._path = data, path
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
return self._path, self._data, self._time
|
|
||||||
|
|
||||||
def get_status(self):
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class InlineImage(Message):
|
|
||||||
"""
|
|
||||||
Inline image
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, data):
|
|
||||||
super(InlineImage, self).__init__(MESSAGE_TYPE['INLINE'], None, None)
|
|
||||||
self._data = data
|
|
||||||
|
|
||||||
def get_data(self):
|
|
||||||
return self._data
|
|
||||||
|
|
||||||
|
|
||||||
class InfoMessage(TextMessage):
|
|
||||||
|
|
||||||
def __init__(self, message, time):
|
|
||||||
super(InfoMessage, self).__init__(message, None, time, MESSAGE_TYPE['INFO_MESSAGE'])
|
|
0
toxygen/messenger/__init__.py
Normal file
0
toxygen/messenger/__init__.py
Normal file
239
toxygen/messenger/messages.py
Normal file
239
toxygen/messenger/messages.py
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
from history.database import MESSAGE_AUTHOR
|
||||||
|
import os.path
|
||||||
|
from ui.messages_widgets import *
|
||||||
|
|
||||||
|
|
||||||
|
MESSAGE_TYPE = {
|
||||||
|
'TEXT': 0,
|
||||||
|
'ACTION': 1,
|
||||||
|
'FILE_TRANSFER': 2,
|
||||||
|
'INLINE': 3,
|
||||||
|
'INFO_MESSAGE': 4
|
||||||
|
}
|
||||||
|
|
||||||
|
PAGE_SIZE = 42
|
||||||
|
|
||||||
|
|
||||||
|
class MessageAuthor:
|
||||||
|
|
||||||
|
def __init__(self, author_name, author_type):
|
||||||
|
self._name = author_name
|
||||||
|
self._type = author_type
|
||||||
|
|
||||||
|
def get_name(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
name = property(get_name)
|
||||||
|
|
||||||
|
def get_type(self):
|
||||||
|
return self._type
|
||||||
|
|
||||||
|
def set_type(self, value):
|
||||||
|
self._type = value
|
||||||
|
|
||||||
|
type = property(get_type, set_type)
|
||||||
|
|
||||||
|
|
||||||
|
class Message:
|
||||||
|
|
||||||
|
MESSAGE_ID = 0
|
||||||
|
|
||||||
|
def __init__(self, message_type, author, time):
|
||||||
|
self._time = time
|
||||||
|
self._type = message_type
|
||||||
|
self._author = author
|
||||||
|
self._widget = None
|
||||||
|
self._message_id = self._get_id()
|
||||||
|
|
||||||
|
def get_type(self):
|
||||||
|
return self._type
|
||||||
|
|
||||||
|
type = property(get_type)
|
||||||
|
|
||||||
|
def get_author(self):
|
||||||
|
return self._author
|
||||||
|
|
||||||
|
author = property(get_author)
|
||||||
|
|
||||||
|
def get_time(self):
|
||||||
|
return self._time
|
||||||
|
|
||||||
|
time = property(get_time)
|
||||||
|
|
||||||
|
def get_message_id(self):
|
||||||
|
return self._message_id
|
||||||
|
|
||||||
|
message_id = property(get_message_id)
|
||||||
|
|
||||||
|
def get_widget(self, *args):
|
||||||
|
self._widget = self._create_widget(*args)
|
||||||
|
|
||||||
|
return self._widget
|
||||||
|
|
||||||
|
widget = property(get_widget)
|
||||||
|
|
||||||
|
def remove_widget(self):
|
||||||
|
self._widget = None
|
||||||
|
|
||||||
|
def mark_as_sent(self):
|
||||||
|
self._author.type = MESSAGE_AUTHOR['ME']
|
||||||
|
if self._widget is not None:
|
||||||
|
self._widget.mark_as_sent()
|
||||||
|
|
||||||
|
def _create_widget(self, *args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_id():
|
||||||
|
Message.MESSAGE_ID += 1
|
||||||
|
|
||||||
|
return int(Message.MESSAGE_ID)
|
||||||
|
|
||||||
|
|
||||||
|
class TextMessage(Message):
|
||||||
|
"""
|
||||||
|
Plain text or action message
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, message, owner, time, message_type, message_id=0):
|
||||||
|
super().__init__(message_type, owner, time)
|
||||||
|
self._message = message
|
||||||
|
self._id = message_id
|
||||||
|
|
||||||
|
def get_text(self):
|
||||||
|
return self._message
|
||||||
|
|
||||||
|
text = property(get_text)
|
||||||
|
|
||||||
|
def get_id(self):
|
||||||
|
return self._id
|
||||||
|
|
||||||
|
id = property(get_id)
|
||||||
|
|
||||||
|
def is_saved(self):
|
||||||
|
return self._id > 0
|
||||||
|
|
||||||
|
def _create_widget(self, *args):
|
||||||
|
return MessageItem(self, *args)
|
||||||
|
|
||||||
|
|
||||||
|
class OutgoingTextMessage(TextMessage):
|
||||||
|
|
||||||
|
def __init__(self, message, owner, time, message_type, tox_message_id=0):
|
||||||
|
super().__init__(message, owner, time, message_type)
|
||||||
|
self._tox_message_id = tox_message_id
|
||||||
|
|
||||||
|
def get_tox_message_id(self):
|
||||||
|
return self._tox_message_id
|
||||||
|
|
||||||
|
def set_tox_message_id(self, tox_message_id):
|
||||||
|
self._tox_message_id = tox_message_id
|
||||||
|
|
||||||
|
tox_message_id = property(get_tox_message_id, set_tox_message_id)
|
||||||
|
|
||||||
|
|
||||||
|
class GroupChatMessage(TextMessage):
|
||||||
|
|
||||||
|
def __init__(self, id, message, owner, time, message_type, name):
|
||||||
|
super().__init__(id, message, owner, time, message_type)
|
||||||
|
self._user_name = name
|
||||||
|
|
||||||
|
|
||||||
|
class TransferMessage(Message):
|
||||||
|
"""
|
||||||
|
Message with info about file transfer
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, author, time, state, size, file_name, friend_number, file_number):
|
||||||
|
super().__init__(MESSAGE_TYPE['FILE_TRANSFER'], author, time)
|
||||||
|
self._state = state
|
||||||
|
self._size = size
|
||||||
|
self._file_name = file_name
|
||||||
|
self._friend_number, self._file_number = friend_number, file_number
|
||||||
|
|
||||||
|
def is_active(self, file_number):
|
||||||
|
if self._file_number != file_number:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self._state not in (FILE_TRANSFER_STATE['FINISHED'], FILE_TRANSFER_STATE['CANCELLED'])
|
||||||
|
|
||||||
|
def get_friend_number(self):
|
||||||
|
return self._friend_number
|
||||||
|
|
||||||
|
friend_number = property(get_friend_number)
|
||||||
|
|
||||||
|
def get_file_number(self):
|
||||||
|
return self._file_number
|
||||||
|
|
||||||
|
file_number = property(get_file_number)
|
||||||
|
|
||||||
|
def get_state(self):
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
def set_state(self, value):
|
||||||
|
self._state = value
|
||||||
|
|
||||||
|
state = property(get_state, set_state)
|
||||||
|
|
||||||
|
def get_size(self):
|
||||||
|
return self._size
|
||||||
|
|
||||||
|
size = property(get_size)
|
||||||
|
|
||||||
|
def get_file_name(self):
|
||||||
|
return self._file_name
|
||||||
|
|
||||||
|
file_name = property(get_file_name)
|
||||||
|
|
||||||
|
def transfer_updated(self, state, percentage, time):
|
||||||
|
self._state = state
|
||||||
|
if self._widget is not None:
|
||||||
|
self._widget.update_transfer_state(state, percentage, time)
|
||||||
|
|
||||||
|
def _create_widget(self, *args):
|
||||||
|
return FileTransferItem(self, *args)
|
||||||
|
|
||||||
|
|
||||||
|
class UnsentFileMessage(TransferMessage):
|
||||||
|
|
||||||
|
def __init__(self, path, data, time, author, size, friend_number):
|
||||||
|
file_name = os.path.basename(path)
|
||||||
|
super().__init__(author, time, FILE_TRANSFER_STATE['UNSENT'], size, file_name, friend_number, -1)
|
||||||
|
self._data, self._path = data, path
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
return self._data
|
||||||
|
|
||||||
|
data = property(get_data)
|
||||||
|
|
||||||
|
def get_path(self):
|
||||||
|
return self._path
|
||||||
|
|
||||||
|
path = property(get_path)
|
||||||
|
|
||||||
|
def _create_widget(self, *args):
|
||||||
|
return UnsentFileItem(self, *args)
|
||||||
|
|
||||||
|
|
||||||
|
class InlineImageMessage(Message):
|
||||||
|
"""
|
||||||
|
Inline image
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, data):
|
||||||
|
super().__init__(MESSAGE_TYPE['INLINE'], None, None)
|
||||||
|
self._data = data
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
return self._data
|
||||||
|
|
||||||
|
data = property(get_data)
|
||||||
|
|
||||||
|
def _create_widget(self, *args):
|
||||||
|
return InlineImageItem(self, *args)
|
||||||
|
|
||||||
|
|
||||||
|
class InfoMessage(TextMessage):
|
||||||
|
|
||||||
|
def __init__(self, message, time):
|
||||||
|
super().__init__(message, None, time, MESSAGE_TYPE['INFO_MESSAGE'])
|
310
toxygen/messenger/messenger.py
Normal file
310
toxygen/messenger/messenger.py
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
import common.tox_save as tox_save
|
||||||
|
from messenger.messages import *
|
||||||
|
|
||||||
|
|
||||||
|
class Messenger(tox_save.ToxSave):
|
||||||
|
|
||||||
|
def __init__(self, tox, plugin_loader, screen, contacts_manager, contacts_provider, items_factory, profile,
|
||||||
|
calls_manager):
|
||||||
|
super().__init__(tox)
|
||||||
|
self._plugin_loader = plugin_loader
|
||||||
|
self._screen = screen
|
||||||
|
self._contacts_manager = contacts_manager
|
||||||
|
self._contacts_provider = contacts_provider
|
||||||
|
self._items_factory = items_factory
|
||||||
|
self._profile = profile
|
||||||
|
self._profile_name = profile.name
|
||||||
|
|
||||||
|
profile.name_changed_event.add_callback(self._on_profile_name_changed)
|
||||||
|
calls_manager.call_started_event.add_callback(self._on_call_started)
|
||||||
|
calls_manager.call_finished_event.add_callback(self._on_call_finished)
|
||||||
|
|
||||||
|
def get_last_message(self):
|
||||||
|
contact = self._contacts_manager.get_curr_contact()
|
||||||
|
if contact is None:
|
||||||
|
return str()
|
||||||
|
|
||||||
|
return contact.get_last_message_text()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Messaging - friends
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def new_message(self, friend_number, message_type, message):
|
||||||
|
"""
|
||||||
|
Current user gets new message
|
||||||
|
:param friend_number: friend_num of friend who sent message
|
||||||
|
:param message_type: message type - plain text or action message (/me)
|
||||||
|
:param message: text of message
|
||||||
|
"""
|
||||||
|
t = util.get_unix_time()
|
||||||
|
friend = self._get_friend_by_number(friend_number)
|
||||||
|
text_message = TextMessage(message, MessageAuthor(friend.name, MESSAGE_AUTHOR['FRIEND']), t, message_type)
|
||||||
|
self._add_message(text_message, friend)
|
||||||
|
|
||||||
|
def send_message(self):
|
||||||
|
text = self._screen.messageEdit.toPlainText()
|
||||||
|
|
||||||
|
plugin_command_prefix = '/plugin '
|
||||||
|
if text.startswith(plugin_command_prefix):
|
||||||
|
self._plugin_loader.command(text[len(plugin_command_prefix):])
|
||||||
|
self._screen.messageEdit.clear()
|
||||||
|
return
|
||||||
|
|
||||||
|
action_message_prefix = '/me '
|
||||||
|
if text.startswith(action_message_prefix):
|
||||||
|
message_type = TOX_MESSAGE_TYPE['ACTION']
|
||||||
|
text = text[len(action_message_prefix):]
|
||||||
|
else:
|
||||||
|
message_type = TOX_MESSAGE_TYPE['NORMAL']
|
||||||
|
|
||||||
|
if self._contacts_manager.is_active_a_friend():
|
||||||
|
self.send_message_to_friend(text, message_type)
|
||||||
|
elif self._contacts_manager.is_active_a_group():
|
||||||
|
self.send_message_to_group(text, message_type)
|
||||||
|
elif self._contacts_manager.is_active_a_group_chat_peer():
|
||||||
|
self.send_message_to_group_peer(text, message_type)
|
||||||
|
|
||||||
|
def send_message_to_friend(self, text, message_type, friend_number=None):
|
||||||
|
"""
|
||||||
|
Send message
|
||||||
|
:param text: message text
|
||||||
|
:param friend_number: number of friend
|
||||||
|
"""
|
||||||
|
if friend_number is None:
|
||||||
|
friend_number = self._contacts_manager.get_active_number()
|
||||||
|
|
||||||
|
if not text or friend_number < 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
friend = self._get_friend_by_number(friend_number)
|
||||||
|
messages = self._split_message(text.encode('utf-8'))
|
||||||
|
t = util.get_unix_time()
|
||||||
|
for message in messages:
|
||||||
|
if friend.status is not None:
|
||||||
|
message_id = self._tox.friend_send_message(friend_number, message_type, message)
|
||||||
|
else:
|
||||||
|
message_id = 0
|
||||||
|
message_author = MessageAuthor(self._profile.name, MESSAGE_AUTHOR['NOT_SENT'])
|
||||||
|
message = OutgoingTextMessage(text, message_author, t, message_type, message_id)
|
||||||
|
friend.append_message(message)
|
||||||
|
if not self._contacts_manager.is_friend_active(friend_number):
|
||||||
|
return
|
||||||
|
self._create_message_item(message)
|
||||||
|
self._screen.messageEdit.clear()
|
||||||
|
self._screen.messages.scrollToBottom()
|
||||||
|
|
||||||
|
def send_messages(self, friend_number):
|
||||||
|
"""
|
||||||
|
Send 'offline' messages to friend
|
||||||
|
"""
|
||||||
|
friend = self._get_friend_by_number(friend_number)
|
||||||
|
friend.load_corr()
|
||||||
|
messages = friend.get_unsent_messages()
|
||||||
|
try:
|
||||||
|
for message in messages:
|
||||||
|
message_id = self._tox.friend_send_message(friend_number, message.type, message.text.encode('utf-8'))
|
||||||
|
message.tox_message_id = message_id
|
||||||
|
except Exception as ex:
|
||||||
|
util.log('Sending pending messages failed with ' + str(ex))
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Messaging - groups
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def send_message_to_group(self, text, message_type, group_number=None):
|
||||||
|
if group_number is None:
|
||||||
|
group_number = self._contacts_manager.get_active_number()
|
||||||
|
|
||||||
|
if not text or group_number < 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
group = self._get_group_by_number(group_number)
|
||||||
|
messages = self._split_message(text.encode('utf-8'))
|
||||||
|
t = util.get_unix_time()
|
||||||
|
for message in messages:
|
||||||
|
self._tox.group_send_message(group_number, message_type, message)
|
||||||
|
message_author = MessageAuthor(group.get_self_name(), MESSAGE_AUTHOR['GC_PEER'])
|
||||||
|
message = OutgoingTextMessage(text, message_author, t, message_type)
|
||||||
|
group.append_message(message)
|
||||||
|
if not self._contacts_manager.is_group_active(group_number):
|
||||||
|
return
|
||||||
|
self._create_message_item(message)
|
||||||
|
self._screen.messageEdit.clear()
|
||||||
|
self._screen.messages.scrollToBottom()
|
||||||
|
|
||||||
|
def new_group_message(self, group_number, message_type, message, peer_id):
|
||||||
|
"""
|
||||||
|
Current user gets new message
|
||||||
|
:param message_type: message type - plain text or action message (/me)
|
||||||
|
:param message: text of message
|
||||||
|
"""
|
||||||
|
t = util.get_unix_time()
|
||||||
|
group = self._get_group_by_number(group_number)
|
||||||
|
peer = group.get_peer_by_id(peer_id)
|
||||||
|
text_message = TextMessage(message, MessageAuthor(peer.name, MESSAGE_AUTHOR['GC_PEER']), t, message_type)
|
||||||
|
self._add_message(text_message, group)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Messaging - group peers
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def send_message_to_group_peer(self, text, message_type, group_number=None, peer_id=None):
|
||||||
|
if group_number is None or peer_id is None:
|
||||||
|
group_peer_contact = self._contacts_manager.get_curr_contact()
|
||||||
|
peer_id = group_peer_contact.number
|
||||||
|
group = self._get_group_by_public_key(group_peer_contact.group_pk)
|
||||||
|
group_number = group.number
|
||||||
|
|
||||||
|
if not text or group_number < 0 or peer_id < 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
group_peer_contact = self._contacts_manager.get_or_create_group_peer_contact(group_number, peer_id)
|
||||||
|
group = self._get_group_by_number(group_number)
|
||||||
|
messages = self._split_message(text.encode('utf-8'))
|
||||||
|
t = util.get_unix_time()
|
||||||
|
for message in messages:
|
||||||
|
self._tox.group_send_private_message(group_number, peer_id, message_type, message)
|
||||||
|
message_author = MessageAuthor(group.get_self_name(), MESSAGE_AUTHOR['GC_PEER'])
|
||||||
|
message = OutgoingTextMessage(text, message_author, t, message_type)
|
||||||
|
group_peer_contact.append_message(message)
|
||||||
|
if not self._contacts_manager.is_contact_active(group_peer_contact):
|
||||||
|
return
|
||||||
|
self._create_message_item(message)
|
||||||
|
self._screen.messageEdit.clear()
|
||||||
|
self._screen.messages.scrollToBottom()
|
||||||
|
|
||||||
|
def new_group_private_message(self, group_number, message_type, message, peer_id):
|
||||||
|
"""
|
||||||
|
Current user gets new message
|
||||||
|
:param message: text of message
|
||||||
|
"""
|
||||||
|
t = util.get_unix_time()
|
||||||
|
group = self._get_group_by_number(group_number)
|
||||||
|
peer = group.get_peer_by_id(peer_id)
|
||||||
|
text_message = TextMessage(message, MessageAuthor(peer.name, MESSAGE_AUTHOR['GC_PEER']),
|
||||||
|
t, message_type)
|
||||||
|
group_peer_contact = self._contacts_manager.get_or_create_group_peer_contact(group_number, peer_id)
|
||||||
|
self._add_message(text_message, group_peer_contact)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Message receipts
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def receipt(self, friend_number, message_id):
|
||||||
|
friend = self._get_friend_by_number(friend_number)
|
||||||
|
friend.mark_as_sent(message_id)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Typing notifications
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def send_typing(self, typing):
|
||||||
|
"""
|
||||||
|
Send typing notification to a friend
|
||||||
|
"""
|
||||||
|
if not self._contacts_manager.can_send_typing_notification():
|
||||||
|
return
|
||||||
|
contact = self._contacts_manager.get_curr_contact()
|
||||||
|
contact.typing_notification_handler.send(self._tox, typing)
|
||||||
|
|
||||||
|
def friend_typing(self, friend_number, typing):
|
||||||
|
"""
|
||||||
|
Display incoming typing notification
|
||||||
|
"""
|
||||||
|
if self._contacts_manager.is_friend_active(friend_number):
|
||||||
|
self._screen.typing.setVisible(typing)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Contact info updated
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def new_friend_name(self, friend, old_name, new_name):
|
||||||
|
if old_name == new_name or friend.has_alias():
|
||||||
|
return
|
||||||
|
message = util_ui.tr('User {} is now known as {}')
|
||||||
|
message = message.format(old_name, new_name)
|
||||||
|
if not self._contacts_manager.is_friend_active(friend.number):
|
||||||
|
friend.actions = True
|
||||||
|
self._add_info_message(friend.number, message)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Private methods
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _split_message(message):
|
||||||
|
messages = []
|
||||||
|
while len(message) > TOX_MAX_MESSAGE_LENGTH:
|
||||||
|
size = TOX_MAX_MESSAGE_LENGTH * 4 // 5
|
||||||
|
last_part = message[size:TOX_MAX_MESSAGE_LENGTH]
|
||||||
|
if b' ' in last_part:
|
||||||
|
index = last_part.index(b' ')
|
||||||
|
elif b',' in last_part:
|
||||||
|
index = last_part.index(b',')
|
||||||
|
elif b'.' in last_part:
|
||||||
|
index = last_part.index(b'.')
|
||||||
|
else:
|
||||||
|
index = TOX_MAX_MESSAGE_LENGTH - size - 1
|
||||||
|
index += size + 1
|
||||||
|
messages.append(message[:index])
|
||||||
|
message = message[index:]
|
||||||
|
if message:
|
||||||
|
messages.append(message)
|
||||||
|
|
||||||
|
return messages
|
||||||
|
|
||||||
|
def _get_friend_by_number(self, friend_number):
|
||||||
|
return self._contacts_provider.get_friend_by_number(friend_number)
|
||||||
|
|
||||||
|
def _get_group_by_number(self, group_number):
|
||||||
|
return self._contacts_provider.get_group_by_number(group_number)
|
||||||
|
|
||||||
|
def _get_group_by_public_key(self, public_key):
|
||||||
|
return self._contacts_provider.get_group_by_public_key( public_key)
|
||||||
|
|
||||||
|
def _on_profile_name_changed(self, new_name):
|
||||||
|
if self._profile_name == new_name:
|
||||||
|
return
|
||||||
|
message = util_ui.tr('User {} is now known as {}')
|
||||||
|
message = message.format(self._profile_name, new_name)
|
||||||
|
for friend in self._contacts_provider.get_all_friends():
|
||||||
|
self._add_info_message(friend.number, message)
|
||||||
|
self._profile_name = new_name
|
||||||
|
|
||||||
|
def _on_call_started(self, friend_number, audio, video, is_outgoing):
|
||||||
|
if is_outgoing:
|
||||||
|
text = util_ui.tr("Outgoing video call") if video else util_ui.tr("Outgoing audio call")
|
||||||
|
else:
|
||||||
|
text = util_ui.tr("Incoming video call") if video else util_ui.tr("Incoming audio call")
|
||||||
|
self._add_info_message(friend_number, text)
|
||||||
|
|
||||||
|
def _on_call_finished(self, friend_number, is_declined):
|
||||||
|
text = util_ui.tr("Call declined") if is_declined else util_ui.tr("Call finished")
|
||||||
|
self._add_info_message(friend_number, text)
|
||||||
|
|
||||||
|
def _add_info_message(self, friend_number, text):
|
||||||
|
friend = self._get_friend_by_number(friend_number)
|
||||||
|
message = InfoMessage(text, util.get_unix_time())
|
||||||
|
friend.append_message(message)
|
||||||
|
if self._contacts_manager.is_friend_active(friend_number):
|
||||||
|
self._create_info_message_item(message)
|
||||||
|
|
||||||
|
def _create_info_message_item(self, message):
|
||||||
|
self._items_factory.create_message_item(message)
|
||||||
|
self._screen.messages.scrollToBottom()
|
||||||
|
|
||||||
|
def _add_message(self, text_message, contact):
|
||||||
|
if self._contacts_manager.is_contact_active(contact): # add message to list
|
||||||
|
self._create_message_item(text_message)
|
||||||
|
self._screen.messages.scrollToBottom()
|
||||||
|
self._contacts_manager.get_curr_contact().append_message(text_message)
|
||||||
|
else:
|
||||||
|
contact.inc_messages()
|
||||||
|
contact.append_message(text_message)
|
||||||
|
if not contact.visibility:
|
||||||
|
self._contacts_manager.update_filtration()
|
||||||
|
|
||||||
|
def _create_message_item(self, text_message):
|
||||||
|
# pixmap = self._contacts_manager.get_curr_contact().get_pixmap()
|
||||||
|
self._items_factory.create_message_item(text_message)
|
0
toxygen/middleware/__init__.py
Normal file
0
toxygen/middleware/__init__.py
Normal file
605
toxygen/middleware/callbacks.py
Normal file
605
toxygen/middleware/callbacks.py
Normal file
@ -0,0 +1,605 @@
|
|||||||
|
from PyQt5 import QtGui
|
||||||
|
from wrapper.toxcore_enums_and_consts import *
|
||||||
|
from wrapper.toxav_enums import *
|
||||||
|
from wrapper.tox import bin_to_string
|
||||||
|
import utils.ui as util_ui
|
||||||
|
import utils.util as util
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
from middleware.threads import invoke_in_main_thread, execute
|
||||||
|
from notifications.tray import tray_notification
|
||||||
|
from notifications.sound import *
|
||||||
|
import threading
|
||||||
|
|
||||||
|
# TODO: refactoring. Use contact provider instead of manager
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Callbacks - current user
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def self_connection_status(tox, profile):
|
||||||
|
"""
|
||||||
|
Current user changed connection status (offline, TCP, UDP)
|
||||||
|
"""
|
||||||
|
def wrapped(tox_link, connection, user_data):
|
||||||
|
print('Connection status: ', str(connection))
|
||||||
|
status = tox.self_get_status() if connection != TOX_CONNECTION['NONE'] else None
|
||||||
|
invoke_in_main_thread(profile.set_status, status)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Callbacks - friends
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def friend_status(contacts_manager, file_transfer_handler, profile, settings):
|
||||||
|
def wrapped(tox, friend_number, new_status, user_data):
|
||||||
|
"""
|
||||||
|
Check friend's status (none, busy, away)
|
||||||
|
"""
|
||||||
|
print("Friend's #{} status changed!".format(friend_number))
|
||||||
|
friend = contacts_manager.get_friend_by_number(friend_number)
|
||||||
|
if friend.status is None and settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
||||||
|
sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
|
||||||
|
invoke_in_main_thread(friend.set_status, new_status)
|
||||||
|
|
||||||
|
def set_timer():
|
||||||
|
t = threading.Timer(5, lambda: file_transfer_handler.send_files(friend_number))
|
||||||
|
t.start()
|
||||||
|
invoke_in_main_thread(set_timer)
|
||||||
|
invoke_in_main_thread(contacts_manager.update_filtration)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def friend_connection_status(contacts_manager, profile, settings, plugin_loader, file_transfer_handler,
|
||||||
|
messenger, calls_manager):
|
||||||
|
def wrapped(tox, friend_number, new_status, user_data):
|
||||||
|
"""
|
||||||
|
Check friend's connection status (offline, udp, tcp)
|
||||||
|
"""
|
||||||
|
print("Friend #{} connection status: {}".format(friend_number, new_status))
|
||||||
|
friend = contacts_manager.get_friend_by_number(friend_number)
|
||||||
|
if new_status == TOX_CONNECTION['NONE']:
|
||||||
|
invoke_in_main_thread(friend.set_status, None)
|
||||||
|
invoke_in_main_thread(file_transfer_handler.friend_exit, friend_number)
|
||||||
|
invoke_in_main_thread(contacts_manager.update_filtration)
|
||||||
|
invoke_in_main_thread(messenger.friend_typing, friend_number, False)
|
||||||
|
invoke_in_main_thread(calls_manager.friend_exit, friend_number)
|
||||||
|
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
||||||
|
sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
|
||||||
|
elif friend.status is None:
|
||||||
|
invoke_in_main_thread(file_transfer_handler.send_avatar, friend_number)
|
||||||
|
invoke_in_main_thread(plugin_loader.friend_online, friend_number)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def friend_name(contacts_provider, messenger):
|
||||||
|
def wrapped(tox, friend_number, name, size, user_data):
|
||||||
|
"""
|
||||||
|
Friend changed his name
|
||||||
|
"""
|
||||||
|
print('New name friend #' + str(friend_number))
|
||||||
|
friend = contacts_provider.get_friend_by_number(friend_number)
|
||||||
|
old_name = friend.name
|
||||||
|
new_name = str(name, 'utf-8')
|
||||||
|
invoke_in_main_thread(friend.set_name, new_name)
|
||||||
|
invoke_in_main_thread(messenger.new_friend_name, friend, old_name, new_name)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def friend_status_message(contacts_manager, messenger):
|
||||||
|
def wrapped(tox, friend_number, status_message, size, user_data):
|
||||||
|
"""
|
||||||
|
:return: function for callback friend_status_message. It updates friend's status message
|
||||||
|
and calls window repaint
|
||||||
|
"""
|
||||||
|
friend = contacts_manager.get_friend_by_number(friend_number)
|
||||||
|
invoke_in_main_thread(friend.set_status_message, str(status_message, 'utf-8'))
|
||||||
|
print('User #{} has new status message'.format(friend_number))
|
||||||
|
invoke_in_main_thread(messenger.send_messages, friend_number)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def friend_message(messenger, contacts_manager, profile, settings, window, tray):
|
||||||
|
def wrapped(tox, friend_number, message_type, message, size, user_data):
|
||||||
|
"""
|
||||||
|
New message from friend
|
||||||
|
"""
|
||||||
|
message = str(message, 'utf-8')
|
||||||
|
invoke_in_main_thread(messenger.new_message, friend_number, message_type, message)
|
||||||
|
if not window.isActiveWindow():
|
||||||
|
friend = contacts_manager.get_friend_by_number(friend_number)
|
||||||
|
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
|
||||||
|
invoke_in_main_thread(tray_notification, friend.name, message, tray, window)
|
||||||
|
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
||||||
|
sound_notification(SOUND_NOTIFICATION['MESSAGE'])
|
||||||
|
icon = os.path.join(util.get_images_directory(), 'icon_new_messages.png')
|
||||||
|
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def friend_request(contacts_manager):
|
||||||
|
def wrapped(tox, public_key, message, message_size, user_data):
|
||||||
|
"""
|
||||||
|
Called when user get new friend request
|
||||||
|
"""
|
||||||
|
print('Friend request')
|
||||||
|
key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE])
|
||||||
|
tox_id = bin_to_string(key, TOX_PUBLIC_KEY_SIZE)
|
||||||
|
invoke_in_main_thread(contacts_manager.process_friend_request, tox_id, str(message, 'utf-8'))
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def friend_typing(messenger):
|
||||||
|
def wrapped(tox, friend_number, typing, user_data):
|
||||||
|
invoke_in_main_thread(messenger.friend_typing, friend_number, typing)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def friend_read_receipt(messenger):
|
||||||
|
def wrapped(tox, friend_number, message_id, user_data):
|
||||||
|
invoke_in_main_thread(messenger.receipt, friend_number, message_id)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Callbacks - file transfers
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def tox_file_recv(window, tray, profile, file_transfer_handler, contacts_manager, settings):
|
||||||
|
"""
|
||||||
|
New incoming file
|
||||||
|
"""
|
||||||
|
def wrapped(tox, friend_number, file_number, file_type, size, file_name, file_name_size, user_data):
|
||||||
|
if file_type == TOX_FILE_KIND['DATA']:
|
||||||
|
print('File')
|
||||||
|
try:
|
||||||
|
file_name = str(file_name[:file_name_size], 'utf-8')
|
||||||
|
except:
|
||||||
|
file_name = 'toxygen_file'
|
||||||
|
invoke_in_main_thread(file_transfer_handler.incoming_file_transfer,
|
||||||
|
friend_number,
|
||||||
|
file_number,
|
||||||
|
size,
|
||||||
|
file_name)
|
||||||
|
if not window.isActiveWindow():
|
||||||
|
friend = contacts_manager.get_friend_by_number(friend_number)
|
||||||
|
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
|
||||||
|
file_from = util_ui.tr("File from")
|
||||||
|
invoke_in_main_thread(tray_notification, file_from + ' ' + friend.name, file_name, tray, window)
|
||||||
|
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
||||||
|
sound_notification(SOUND_NOTIFICATION['FILE_TRANSFER'])
|
||||||
|
icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png')
|
||||||
|
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
|
||||||
|
else: # avatar
|
||||||
|
print('Avatar')
|
||||||
|
invoke_in_main_thread(file_transfer_handler.incoming_avatar,
|
||||||
|
friend_number,
|
||||||
|
file_number,
|
||||||
|
size)
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def file_recv_chunk(file_transfer_handler):
|
||||||
|
"""
|
||||||
|
Incoming chunk
|
||||||
|
"""
|
||||||
|
def wrapped(tox, friend_number, file_number, position, chunk, length, user_data):
|
||||||
|
chunk = chunk[:length] if length else None
|
||||||
|
execute(file_transfer_handler.incoming_chunk, friend_number, file_number, position, chunk)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def file_chunk_request(file_transfer_handler):
|
||||||
|
"""
|
||||||
|
Outgoing chunk
|
||||||
|
"""
|
||||||
|
def wrapped(tox, friend_number, file_number, position, size, user_data):
|
||||||
|
execute(file_transfer_handler.outgoing_chunk, friend_number, file_number, position, size)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def file_recv_control(file_transfer_handler):
|
||||||
|
"""
|
||||||
|
Friend cancelled, paused or resumed file transfer
|
||||||
|
"""
|
||||||
|
def wrapped(tox, friend_number, file_number, file_control, user_data):
|
||||||
|
if file_control == TOX_FILE_CONTROL['CANCEL']:
|
||||||
|
file_transfer_handler.cancel_transfer(friend_number, file_number, True)
|
||||||
|
elif file_control == TOX_FILE_CONTROL['PAUSE']:
|
||||||
|
file_transfer_handler.pause_transfer(friend_number, file_number, True)
|
||||||
|
elif file_control == TOX_FILE_CONTROL['RESUME']:
|
||||||
|
file_transfer_handler.resume_transfer(friend_number, file_number, True)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Callbacks - custom packets
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def lossless_packet(plugin_loader):
|
||||||
|
def wrapped(tox, friend_number, data, length, user_data):
|
||||||
|
"""
|
||||||
|
Incoming lossless packet
|
||||||
|
"""
|
||||||
|
data = data[:length]
|
||||||
|
invoke_in_main_thread(plugin_loader.callback_lossless, friend_number, data)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def lossy_packet(plugin_loader):
|
||||||
|
def wrapped(tox, friend_number, data, length, user_data):
|
||||||
|
"""
|
||||||
|
Incoming lossy packet
|
||||||
|
"""
|
||||||
|
data = data[:length]
|
||||||
|
invoke_in_main_thread(plugin_loader.callback_lossy, friend_number, data)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Callbacks - audio
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def call_state(calls_manager):
|
||||||
|
def wrapped(toxav, friend_number, mask, user_data):
|
||||||
|
"""
|
||||||
|
New call state
|
||||||
|
"""
|
||||||
|
print(friend_number, mask)
|
||||||
|
if mask == TOXAV_FRIEND_CALL_STATE['FINISHED'] or mask == TOXAV_FRIEND_CALL_STATE['ERROR']:
|
||||||
|
invoke_in_main_thread(calls_manager.stop_call, friend_number, True)
|
||||||
|
else:
|
||||||
|
calls_manager.toxav_call_state_cb(friend_number, mask)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def call(calls_manager):
|
||||||
|
def wrapped(toxav, friend_number, audio, video, user_data):
|
||||||
|
"""
|
||||||
|
Incoming call from friend
|
||||||
|
"""
|
||||||
|
print(friend_number, audio, video)
|
||||||
|
invoke_in_main_thread(calls_manager.incoming_call, audio, video, friend_number)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def callback_audio(calls_manager):
|
||||||
|
def wrapped(toxav, friend_number, samples, audio_samples_per_channel, audio_channels_count, rate, user_data):
|
||||||
|
"""
|
||||||
|
New audio chunk
|
||||||
|
"""
|
||||||
|
calls_manager.call.audio_chunk(
|
||||||
|
bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]),
|
||||||
|
audio_channels_count,
|
||||||
|
rate)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Callbacks - video
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, ustride, vstride, user_data):
|
||||||
|
"""
|
||||||
|
Creates yuv frame from y, u, v and shows it using OpenCV
|
||||||
|
For yuv => bgr we need this YUV420 frame:
|
||||||
|
|
||||||
|
width
|
||||||
|
-------------------------
|
||||||
|
| |
|
||||||
|
| Y | height
|
||||||
|
| |
|
||||||
|
-------------------------
|
||||||
|
| | |
|
||||||
|
| U even | U odd | height // 4
|
||||||
|
| | |
|
||||||
|
-------------------------
|
||||||
|
| | |
|
||||||
|
| V even | V odd | height // 4
|
||||||
|
| | |
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
width // 2 width // 2
|
||||||
|
|
||||||
|
It can be created from initial y, u, v using slices
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
y_size = abs(max(width, abs(ystride)))
|
||||||
|
u_size = abs(max(width // 2, abs(ustride)))
|
||||||
|
v_size = abs(max(width // 2, abs(vstride)))
|
||||||
|
|
||||||
|
y = np.asarray(y[:y_size * height], dtype=np.uint8).reshape(height, y_size)
|
||||||
|
u = np.asarray(u[:u_size * height // 2], dtype=np.uint8).reshape(height // 2, u_size)
|
||||||
|
v = np.asarray(v[:v_size * height // 2], dtype=np.uint8).reshape(height // 2, v_size)
|
||||||
|
|
||||||
|
width -= width % 4
|
||||||
|
height -= height % 4
|
||||||
|
|
||||||
|
frame = np.zeros((int(height * 1.5), width), dtype=np.uint8)
|
||||||
|
|
||||||
|
frame[:height, :] = y[:height, :width]
|
||||||
|
frame[height:height * 5 // 4, :width // 2] = u[:height // 2:2, :width // 2]
|
||||||
|
frame[height:height * 5 // 4, width // 2:] = u[1:height // 2:2, :width // 2]
|
||||||
|
|
||||||
|
frame[height * 5 // 4:, :width // 2] = v[:height // 2:2, :width // 2]
|
||||||
|
frame[height * 5 // 4:, width // 2:] = v[1:height // 2:2, :width // 2]
|
||||||
|
|
||||||
|
frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420)
|
||||||
|
|
||||||
|
invoke_in_main_thread(cv2.imshow, str(friend_number), frame)
|
||||||
|
except Exception as ex:
|
||||||
|
print(ex)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Callbacks - groups
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def group_message(window, tray, tox, messenger, settings, profile):
|
||||||
|
"""
|
||||||
|
New message in group chat
|
||||||
|
"""
|
||||||
|
def wrapped(tox_link, group_number, peer_id, message_type, message, length, user_data):
|
||||||
|
message = str(message[:length], 'utf-8')
|
||||||
|
invoke_in_main_thread(messenger.new_group_message, group_number, message_type, message, peer_id)
|
||||||
|
if window.isActiveWindow():
|
||||||
|
return
|
||||||
|
bl = settings['notify_all_gc'] or profile.name in message
|
||||||
|
name = tox.group_peer_get_name(group_number, peer_id)
|
||||||
|
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and (not settings.locked) and bl:
|
||||||
|
invoke_in_main_thread(tray_notification, name, message, tray, window)
|
||||||
|
if settings['sound_notifications'] and bl and profile.status != TOX_USER_STATUS['BUSY']:
|
||||||
|
sound_notification(SOUND_NOTIFICATION['MESSAGE'])
|
||||||
|
icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png')
|
||||||
|
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def group_private_message(window, tray, tox, messenger, settings, profile):
|
||||||
|
"""
|
||||||
|
New private message in group chat
|
||||||
|
"""
|
||||||
|
def wrapped(tox_link, group_number, peer_id, message_type, message, length, user_data):
|
||||||
|
message = str(message[:length], 'utf-8')
|
||||||
|
invoke_in_main_thread(messenger.new_group_private_message, group_number, message_type, message, peer_id)
|
||||||
|
if window.isActiveWindow():
|
||||||
|
return
|
||||||
|
bl = settings['notify_all_gc'] or profile.name in message
|
||||||
|
name = tox.group_peer_get_name(group_number, peer_id)
|
||||||
|
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and (not settings.locked) and bl:
|
||||||
|
invoke_in_main_thread(tray_notification, name, message, tray, window)
|
||||||
|
if settings['sound_notifications'] and bl and profile.status != TOX_USER_STATUS['BUSY']:
|
||||||
|
sound_notification(SOUND_NOTIFICATION['MESSAGE'])
|
||||||
|
icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png')
|
||||||
|
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def group_invite(window, settings, tray, profile, groups_service, contacts_provider):
|
||||||
|
def wrapped(tox, friend_number, invite_data, length, group_name, group_name_length, user_data):
|
||||||
|
group_name = str(bytes(group_name[:group_name_length]), 'utf-8')
|
||||||
|
invoke_in_main_thread(groups_service.process_group_invite,
|
||||||
|
friend_number, group_name,
|
||||||
|
bytes(invite_data[:length]))
|
||||||
|
if window.isActiveWindow():
|
||||||
|
return
|
||||||
|
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
|
||||||
|
friend = contacts_provider.get_friend_by_number(friend_number)
|
||||||
|
title = util_ui.tr('New invite to group chat')
|
||||||
|
text = util_ui.tr('{} invites you to group "{}"').format(friend.name, group_name)
|
||||||
|
invoke_in_main_thread(tray_notification, title, text, tray, window)
|
||||||
|
icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png')
|
||||||
|
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def group_self_join(contacts_provider, contacts_manager, groups_service):
|
||||||
|
def wrapped(tox, group_number, user_data):
|
||||||
|
group = contacts_provider.get_group_by_number(group_number)
|
||||||
|
invoke_in_main_thread(group.set_status, TOX_USER_STATUS['NONE'])
|
||||||
|
invoke_in_main_thread(groups_service.update_group_info, group)
|
||||||
|
invoke_in_main_thread(contacts_manager.update_filtration)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def group_peer_join(contacts_provider, groups_service):
|
||||||
|
def wrapped(tox, group_number, peer_id, user_data):
|
||||||
|
group = contacts_provider.get_group_by_number(group_number)
|
||||||
|
group.add_peer(peer_id)
|
||||||
|
invoke_in_main_thread(groups_service.generate_peers_list)
|
||||||
|
invoke_in_main_thread(groups_service.update_group_info, group)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def group_peer_exit(contacts_provider, groups_service, contacts_manager):
|
||||||
|
def wrapped(tox, group_number, peer_id, message, length, user_data):
|
||||||
|
group = contacts_provider.get_group_by_number(group_number)
|
||||||
|
group.remove_peer(peer_id)
|
||||||
|
invoke_in_main_thread(groups_service.generate_peers_list)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def group_peer_name(contacts_provider, groups_service):
|
||||||
|
def wrapped(tox, group_number, peer_id, name, length, user_data):
|
||||||
|
group = contacts_provider.get_group_by_number(group_number)
|
||||||
|
peer = group.get_peer_by_id(peer_id)
|
||||||
|
peer.name = str(name[:length], 'utf-8')
|
||||||
|
invoke_in_main_thread(groups_service.generate_peers_list)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def group_peer_status(contacts_provider, groups_service):
|
||||||
|
def wrapped(tox, group_number, peer_id, peer_status, user_data):
|
||||||
|
group = contacts_provider.get_group_by_number(group_number)
|
||||||
|
peer = group.get_peer_by_id(peer_id)
|
||||||
|
peer.status = peer_status
|
||||||
|
invoke_in_main_thread(groups_service.generate_peers_list)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def group_topic(contacts_provider):
|
||||||
|
def wrapped(tox, group_number, peer_id, topic, length, user_data):
|
||||||
|
group = contacts_provider.get_group_by_number(group_number)
|
||||||
|
topic = str(topic[:length], 'utf-8')
|
||||||
|
invoke_in_main_thread(group.set_status_message, topic)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def group_moderation(groups_service, contacts_provider, contacts_manager, messenger):
|
||||||
|
|
||||||
|
def update_peer_role(group, mod_peer_id, peer_id, new_role):
|
||||||
|
peer = group.get_peer_by_id(peer_id)
|
||||||
|
peer.role = new_role
|
||||||
|
# TODO: add info message
|
||||||
|
|
||||||
|
def remove_peer(group, mod_peer_id, peer_id, is_ban):
|
||||||
|
contacts_manager.remove_group_peer_by_id(group, peer_id)
|
||||||
|
group.remove_peer(peer_id)
|
||||||
|
# TODO: add info message
|
||||||
|
|
||||||
|
def wrapped(tox, group_number, mod_peer_id, peer_id, event_type, user_data):
|
||||||
|
group = contacts_provider.get_group_by_number(group_number)
|
||||||
|
|
||||||
|
if event_type == TOX_GROUP_MOD_EVENT['KICK']:
|
||||||
|
remove_peer(group, mod_peer_id, peer_id, False)
|
||||||
|
elif event_type == TOX_GROUP_MOD_EVENT['BAN']:
|
||||||
|
remove_peer(group, mod_peer_id, peer_id, True)
|
||||||
|
elif event_type == TOX_GROUP_MOD_EVENT['OBSERVER']:
|
||||||
|
update_peer_role(group, mod_peer_id, peer_id, TOX_GROUP_ROLE['OBSERVER'])
|
||||||
|
elif event_type == TOX_GROUP_MOD_EVENT['USER']:
|
||||||
|
update_peer_role(group, mod_peer_id, peer_id, TOX_GROUP_ROLE['USER'])
|
||||||
|
elif event_type == TOX_GROUP_MOD_EVENT['MODERATOR']:
|
||||||
|
update_peer_role(group, mod_peer_id, peer_id, TOX_GROUP_ROLE['MODERATOR'])
|
||||||
|
|
||||||
|
invoke_in_main_thread(groups_service.generate_peers_list)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def group_password(contacts_provider):
|
||||||
|
|
||||||
|
def wrapped(tox_link, group_number, password, length, user_data):
|
||||||
|
password = str(password[:length], 'utf-8')
|
||||||
|
group = contacts_provider.get_group_by_number(group_number)
|
||||||
|
group.password = password
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def group_peer_limit(contacts_provider):
|
||||||
|
|
||||||
|
def wrapped(tox_link, group_number, peer_limit, user_data):
|
||||||
|
group = contacts_provider.get_group_by_number(group_number)
|
||||||
|
group.peer_limit = peer_limit
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def group_privacy_state(contacts_provider):
|
||||||
|
|
||||||
|
def wrapped(tox_link, group_number, privacy_state, user_data):
|
||||||
|
group = contacts_provider.get_group_by_number(group_number)
|
||||||
|
group.is_private = privacy_state == TOX_GROUP_PRIVACY_STATE['PRIVATE']
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Callbacks - initialization
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager,
|
||||||
|
calls_manager, file_transfer_handler, main_window, tray, messenger, groups_service,
|
||||||
|
contacts_provider):
|
||||||
|
"""
|
||||||
|
Initialization of all callbacks.
|
||||||
|
:param tox: Tox instance
|
||||||
|
:param profile: Profile instance
|
||||||
|
:param settings: Settings instance
|
||||||
|
:param contacts_manager: ContactsManager instance
|
||||||
|
:param contacts_manager: ContactsManager instance
|
||||||
|
:param calls_manager: CallsManager instance
|
||||||
|
:param file_transfer_handler: FileTransferHandler instance
|
||||||
|
:param plugin_loader: PluginLoader instance
|
||||||
|
:param main_window: MainWindow instance
|
||||||
|
:param tray: tray (for notifications)
|
||||||
|
:param messenger: Messenger instance
|
||||||
|
:param groups_service: GroupsService instance
|
||||||
|
:param contacts_provider: ContactsProvider instance
|
||||||
|
"""
|
||||||
|
# self callbacks
|
||||||
|
tox.callback_self_connection_status(self_connection_status(tox, profile))
|
||||||
|
|
||||||
|
# friend callbacks
|
||||||
|
tox.callback_friend_status(friend_status(contacts_manager, file_transfer_handler, profile, settings))
|
||||||
|
tox.callback_friend_message(friend_message(messenger, contacts_manager, profile, settings, main_window, tray))
|
||||||
|
tox.callback_friend_connection_status(friend_connection_status(contacts_manager, profile, settings, plugin_loader,
|
||||||
|
file_transfer_handler, messenger, calls_manager))
|
||||||
|
tox.callback_friend_name(friend_name(contacts_provider, messenger))
|
||||||
|
tox.callback_friend_status_message(friend_status_message(contacts_manager, messenger))
|
||||||
|
tox.callback_friend_request(friend_request(contacts_manager))
|
||||||
|
tox.callback_friend_typing(friend_typing(messenger))
|
||||||
|
tox.callback_friend_read_receipt(friend_read_receipt(messenger))
|
||||||
|
|
||||||
|
# file transfer
|
||||||
|
tox.callback_file_recv(tox_file_recv(main_window, tray, profile, file_transfer_handler,
|
||||||
|
contacts_manager, settings))
|
||||||
|
tox.callback_file_recv_chunk(file_recv_chunk(file_transfer_handler))
|
||||||
|
tox.callback_file_chunk_request(file_chunk_request(file_transfer_handler))
|
||||||
|
tox.callback_file_recv_control(file_recv_control(file_transfer_handler))
|
||||||
|
|
||||||
|
# av
|
||||||
|
toxav = tox.AV
|
||||||
|
toxav.callback_call_state(call_state(calls_manager), 0)
|
||||||
|
toxav.callback_call(call(calls_manager), 0)
|
||||||
|
toxav.callback_audio_receive_frame(callback_audio(calls_manager), 0)
|
||||||
|
toxav.callback_video_receive_frame(video_receive_frame, 0)
|
||||||
|
|
||||||
|
# custom packets
|
||||||
|
tox.callback_friend_lossless_packet(lossless_packet(plugin_loader))
|
||||||
|
tox.callback_friend_lossy_packet(lossy_packet(plugin_loader))
|
||||||
|
|
||||||
|
# gc callbacks
|
||||||
|
tox.callback_group_message(group_message(main_window, tray, tox, messenger, settings, profile), 0)
|
||||||
|
tox.callback_group_private_message(group_private_message(main_window, tray, tox, messenger, settings, profile), 0)
|
||||||
|
tox.callback_group_invite(group_invite(main_window, settings, tray, profile, groups_service, contacts_provider), 0)
|
||||||
|
tox.callback_group_self_join(group_self_join(contacts_provider, contacts_manager, groups_service), 0)
|
||||||
|
tox.callback_group_peer_join(group_peer_join(contacts_provider, groups_service), 0)
|
||||||
|
tox.callback_group_peer_exit(group_peer_exit(contacts_provider, groups_service, contacts_manager), 0)
|
||||||
|
tox.callback_group_peer_name(group_peer_name(contacts_provider, groups_service), 0)
|
||||||
|
tox.callback_group_peer_status(group_peer_status(contacts_provider, groups_service), 0)
|
||||||
|
tox.callback_group_topic(group_topic(contacts_provider), 0)
|
||||||
|
tox.callback_group_moderation(group_moderation(groups_service, contacts_provider, contacts_manager, messenger), 0)
|
||||||
|
tox.callback_group_password(group_password(contacts_provider), 0)
|
||||||
|
tox.callback_group_peer_limit(group_peer_limit(contacts_provider), 0)
|
||||||
|
tox.callback_group_privacy_state(group_privacy_state(contacts_provider), 0)
|
172
toxygen/middleware/threads.py
Normal file
172
toxygen/middleware/threads.py
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
from bootstrap.bootstrap import *
|
||||||
|
import threading
|
||||||
|
import queue
|
||||||
|
from utils import util
|
||||||
|
import time
|
||||||
|
from PyQt5 import QtCore
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Base threads
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class BaseThread(threading.Thread):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._stop_thread = False
|
||||||
|
|
||||||
|
def stop_thread(self):
|
||||||
|
self._stop_thread = True
|
||||||
|
self.join()
|
||||||
|
|
||||||
|
|
||||||
|
class BaseQThread(QtCore.QThread):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._stop_thread = False
|
||||||
|
|
||||||
|
def stop_thread(self):
|
||||||
|
self._stop_thread = True
|
||||||
|
self.wait()
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Toxcore threads
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class InitThread(BaseThread):
|
||||||
|
|
||||||
|
def __init__(self, tox, plugin_loader, settings, is_first_start):
|
||||||
|
super().__init__()
|
||||||
|
self._tox, self._plugin_loader, self._settings = tox, plugin_loader, settings
|
||||||
|
self._is_first_start = is_first_start
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if self._is_first_start:
|
||||||
|
# download list of nodes if needed
|
||||||
|
download_nodes_list(self._settings)
|
||||||
|
# start plugins
|
||||||
|
self._plugin_loader.load()
|
||||||
|
|
||||||
|
# bootstrap
|
||||||
|
try:
|
||||||
|
for data in generate_nodes():
|
||||||
|
if self._stop_thread:
|
||||||
|
return
|
||||||
|
self._tox.bootstrap(*data)
|
||||||
|
self._tox.add_tcp_relay(*data)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for _ in range(10):
|
||||||
|
if self._stop_thread:
|
||||||
|
return
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
while not self._tox.self_get_connection_status():
|
||||||
|
try:
|
||||||
|
for data in generate_nodes(None):
|
||||||
|
if self._stop_thread:
|
||||||
|
return
|
||||||
|
self._tox.bootstrap(*data)
|
||||||
|
self._tox.add_tcp_relay(*data)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
|
class ToxIterateThread(BaseQThread):
|
||||||
|
|
||||||
|
def __init__(self, tox):
|
||||||
|
super().__init__()
|
||||||
|
self._tox = tox
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while not self._stop_thread:
|
||||||
|
self._tox.iterate()
|
||||||
|
time.sleep(self._tox.iteration_interval() / 1000)
|
||||||
|
|
||||||
|
|
||||||
|
class ToxAVIterateThread(BaseQThread):
|
||||||
|
|
||||||
|
def __init__(self, toxav):
|
||||||
|
super().__init__()
|
||||||
|
self._toxav = toxav
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while not self._stop_thread:
|
||||||
|
self._toxav.iterate()
|
||||||
|
time.sleep(self._toxav.iteration_interval() / 1000)
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# File transfers thread
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class FileTransfersThread(BaseQThread):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._queue = queue.Queue()
|
||||||
|
self._timeout = 0.01
|
||||||
|
|
||||||
|
def execute(self, func, *args, **kwargs):
|
||||||
|
self._queue.put((func, args, kwargs))
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while not self._stop_thread:
|
||||||
|
try:
|
||||||
|
func, args, kwargs = self._queue.get(timeout=self._timeout)
|
||||||
|
func(*args, **kwargs)
|
||||||
|
except queue.Empty:
|
||||||
|
pass
|
||||||
|
except queue.Full:
|
||||||
|
util.log('Queue is full in _thread')
|
||||||
|
except Exception as ex:
|
||||||
|
util.log('Exception in _thread: ' + str(ex))
|
||||||
|
|
||||||
|
|
||||||
|
_thread = FileTransfersThread()
|
||||||
|
|
||||||
|
|
||||||
|
def start_file_transfer_thread():
|
||||||
|
_thread.start()
|
||||||
|
|
||||||
|
|
||||||
|
def stop_file_transfer_thread():
|
||||||
|
_thread.stop_thread()
|
||||||
|
|
||||||
|
|
||||||
|
def execute(func, *args, **kwargs):
|
||||||
|
_thread.execute(func, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Invoking in main thread
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class InvokeEvent(QtCore.QEvent):
|
||||||
|
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
|
||||||
|
|
||||||
|
def __init__(self, fn, *args, **kwargs):
|
||||||
|
QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
|
||||||
|
self.fn = fn
|
||||||
|
self.args = args
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
|
||||||
|
class Invoker(QtCore.QObject):
|
||||||
|
|
||||||
|
def event(self, event):
|
||||||
|
event.fn(*event.args, **event.kwargs)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
_invoker = Invoker()
|
||||||
|
|
||||||
|
|
||||||
|
def invoke_in_main_thread(fn, *args, **kwargs):
|
||||||
|
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
|
34
toxygen/middleware/tox_factory.py
Normal file
34
toxygen/middleware/tox_factory.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import user_data.settings
|
||||||
|
import wrapper.tox
|
||||||
|
import wrapper.toxcore_enums_and_consts as enums
|
||||||
|
import ctypes
|
||||||
|
|
||||||
|
|
||||||
|
def tox_factory(data=None, settings=None):
|
||||||
|
"""
|
||||||
|
:param data: user data from .tox file. None = no saved data, create new profile
|
||||||
|
:param settings: current profile settings. None = default settings will be used
|
||||||
|
:return: new tox instance
|
||||||
|
"""
|
||||||
|
if settings is None:
|
||||||
|
settings = user_data.settings.Settings.get_default_settings()
|
||||||
|
|
||||||
|
tox_options = wrapper.tox.Tox.options_new()
|
||||||
|
tox_options.contents.udp_enabled = settings['udp_enabled']
|
||||||
|
tox_options.contents.proxy_type = settings['proxy_type']
|
||||||
|
tox_options.contents.proxy_host = bytes(settings['proxy_host'], 'UTF-8')
|
||||||
|
tox_options.contents.proxy_port = settings['proxy_port']
|
||||||
|
tox_options.contents.start_port = settings['start_port']
|
||||||
|
tox_options.contents.end_port = settings['end_port']
|
||||||
|
tox_options.contents.tcp_port = settings['tcp_port']
|
||||||
|
tox_options.contents.local_discovery_enabled = settings['lan_discovery']
|
||||||
|
if data: # load existing profile
|
||||||
|
tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['TOX_SAVE']
|
||||||
|
tox_options.contents.savedata_data = ctypes.c_char_p(data)
|
||||||
|
tox_options.contents.savedata_length = len(data)
|
||||||
|
else: # create new profile
|
||||||
|
tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['NONE']
|
||||||
|
tox_options.contents.savedata_data = None
|
||||||
|
tox_options.contents.savedata_length = 0
|
||||||
|
|
||||||
|
return wrapper.tox.Tox(tox_options)
|
0
toxygen/network/__init__.py
Normal file
0
toxygen/network/__init__.py
Normal file
65
toxygen/network/tox_dns.py
Normal file
65
toxygen/network/tox_dns.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import json
|
||||||
|
import urllib.request
|
||||||
|
import utils.util as util
|
||||||
|
from PyQt5 import QtNetwork, QtCore
|
||||||
|
|
||||||
|
|
||||||
|
class ToxDns:
|
||||||
|
|
||||||
|
def __init__(self, settings):
|
||||||
|
self._settings = settings
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _send_request(url, data):
|
||||||
|
req = urllib.request.Request(url)
|
||||||
|
req.add_header('Content-Type', 'application/json')
|
||||||
|
response = urllib.request.urlopen(req, bytes(json.dumps(data), 'utf-8'))
|
||||||
|
res = json.loads(str(response.read(), 'utf-8'))
|
||||||
|
if not res['c']:
|
||||||
|
return res['tox_id']
|
||||||
|
else:
|
||||||
|
raise LookupError()
|
||||||
|
|
||||||
|
def lookup(self, email):
|
||||||
|
"""
|
||||||
|
TOX DNS 4
|
||||||
|
:param email: data like 'groupbot@toxme.io'
|
||||||
|
:return: tox id on success else None
|
||||||
|
"""
|
||||||
|
site = email.split('@')[1]
|
||||||
|
data = {"action": 3, "name": "{}".format(email)}
|
||||||
|
urls = ('https://{}/api'.format(site), 'http://{}/api'.format(site))
|
||||||
|
if not self._settings['proxy_type']: # no proxy
|
||||||
|
for url in urls:
|
||||||
|
try:
|
||||||
|
return self._send_request(url, data)
|
||||||
|
except Exception as ex:
|
||||||
|
util.log('TOX DNS ERROR: ' + str(ex))
|
||||||
|
else: # proxy
|
||||||
|
netman = QtNetwork.QNetworkAccessManager()
|
||||||
|
proxy = QtNetwork.QNetworkProxy()
|
||||||
|
if self._settings['proxy_type'] == 2:
|
||||||
|
proxy.setType(QtNetwork.QNetworkProxy.Socks5Proxy)
|
||||||
|
else:
|
||||||
|
proxy.setType(QtNetwork.QNetworkProxy.HttpProxy)
|
||||||
|
proxy.setHostName(self._settings['proxy_host'])
|
||||||
|
proxy.setPort(self._settings['proxy_port'])
|
||||||
|
netman.setProxy(proxy)
|
||||||
|
for url in urls:
|
||||||
|
try:
|
||||||
|
request = QtNetwork.QNetworkRequest()
|
||||||
|
request.setUrl(QtCore.QUrl(url))
|
||||||
|
request.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, "application/json")
|
||||||
|
reply = netman.post(request, bytes(json.dumps(data), 'utf-8'))
|
||||||
|
|
||||||
|
while not reply.isFinished():
|
||||||
|
QtCore.QThread.msleep(1)
|
||||||
|
QtCore.QCoreApplication.processEvents()
|
||||||
|
data = bytes(reply.readAll().data())
|
||||||
|
result = json.loads(str(data, 'utf-8'))
|
||||||
|
if not result['c']:
|
||||||
|
return result['tox_id']
|
||||||
|
except Exception as ex:
|
||||||
|
util.log('TOX DNS ERROR: ' + str(ex))
|
||||||
|
|
||||||
|
return None # error
|
File diff suppressed because one or more lines are too long
@ -1,71 +0,0 @@
|
|||||||
from PyQt5 import QtCore, QtWidgets
|
|
||||||
from util import curr_directory
|
|
||||||
import wave
|
|
||||||
import pyaudio
|
|
||||||
|
|
||||||
|
|
||||||
SOUND_NOTIFICATION = {
|
|
||||||
'MESSAGE': 0,
|
|
||||||
'FRIEND_CONNECTION_STATUS': 1,
|
|
||||||
'FILE_TRANSFER': 2
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def tray_notification(title, text, tray, window):
|
|
||||||
"""
|
|
||||||
Show tray notification and activate window icon
|
|
||||||
NOTE: different behaviour on different OS
|
|
||||||
:param title: Name of user who sent message or file
|
|
||||||
:param text: text of message or file info
|
|
||||||
:param tray: ref to tray icon
|
|
||||||
:param window: main window
|
|
||||||
"""
|
|
||||||
if QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
|
|
||||||
if len(text) > 30:
|
|
||||||
text = text[:27] + '...'
|
|
||||||
tray.showMessage(title, text, QtWidgets.QSystemTrayIcon.NoIcon, 3000)
|
|
||||||
QtWidgets.QApplication.alert(window, 0)
|
|
||||||
|
|
||||||
def message_clicked():
|
|
||||||
window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
|
|
||||||
window.activateWindow()
|
|
||||||
tray.messageClicked.connect(message_clicked)
|
|
||||||
|
|
||||||
|
|
||||||
class AudioFile:
|
|
||||||
chunk = 1024
|
|
||||||
|
|
||||||
def __init__(self, fl):
|
|
||||||
self.wf = wave.open(fl, 'rb')
|
|
||||||
self.p = pyaudio.PyAudio()
|
|
||||||
self.stream = self.p.open(
|
|
||||||
format=self.p.get_format_from_width(self.wf.getsampwidth()),
|
|
||||||
channels=self.wf.getnchannels(),
|
|
||||||
rate=self.wf.getframerate(),
|
|
||||||
output=True)
|
|
||||||
|
|
||||||
def play(self):
|
|
||||||
data = self.wf.readframes(self.chunk)
|
|
||||||
while data:
|
|
||||||
self.stream.write(data)
|
|
||||||
data = self.wf.readframes(self.chunk)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.stream.close()
|
|
||||||
self.p.terminate()
|
|
||||||
|
|
||||||
|
|
||||||
def sound_notification(t):
|
|
||||||
"""
|
|
||||||
Plays sound notification
|
|
||||||
:param t: type of notification
|
|
||||||
"""
|
|
||||||
if t == SOUND_NOTIFICATION['MESSAGE']:
|
|
||||||
f = curr_directory() + '/sounds/message.wav'
|
|
||||||
elif t == SOUND_NOTIFICATION['FILE_TRANSFER']:
|
|
||||||
f = curr_directory() + '/sounds/file.wav'
|
|
||||||
else:
|
|
||||||
f = curr_directory() + '/sounds/contact.wav'
|
|
||||||
a = AudioFile(f)
|
|
||||||
a.play()
|
|
||||||
a.close()
|
|
0
toxygen/notifications/__init__.py
Normal file
0
toxygen/notifications/__init__.py
Normal file
54
toxygen/notifications/sound.py
Normal file
54
toxygen/notifications/sound.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import utils.util
|
||||||
|
import wave
|
||||||
|
import pyaudio
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
|
||||||
|
SOUND_NOTIFICATION = {
|
||||||
|
'MESSAGE': 0,
|
||||||
|
'FRIEND_CONNECTION_STATUS': 1,
|
||||||
|
'FILE_TRANSFER': 2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AudioFile:
|
||||||
|
chunk = 1024
|
||||||
|
|
||||||
|
def __init__(self, fl):
|
||||||
|
self.wf = wave.open(fl, 'rb')
|
||||||
|
self.p = pyaudio.PyAudio()
|
||||||
|
self.stream = self.p.open(
|
||||||
|
format=self.p.get_format_from_width(self.wf.getsampwidth()),
|
||||||
|
channels=self.wf.getnchannels(),
|
||||||
|
rate=self.wf.getframerate(),
|
||||||
|
output=True)
|
||||||
|
|
||||||
|
def play(self):
|
||||||
|
data = self.wf.readframes(self.chunk)
|
||||||
|
while data:
|
||||||
|
self.stream.write(data)
|
||||||
|
data = self.wf.readframes(self.chunk)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.stream.close()
|
||||||
|
self.p.terminate()
|
||||||
|
|
||||||
|
|
||||||
|
def sound_notification(t):
|
||||||
|
"""
|
||||||
|
Plays sound notification
|
||||||
|
:param t: type of notification
|
||||||
|
"""
|
||||||
|
if t == SOUND_NOTIFICATION['MESSAGE']:
|
||||||
|
f = get_file_path('message.wav')
|
||||||
|
elif t == SOUND_NOTIFICATION['FILE_TRANSFER']:
|
||||||
|
f = get_file_path('file.wav')
|
||||||
|
else:
|
||||||
|
f = get_file_path('contact.wav')
|
||||||
|
a = AudioFile(f)
|
||||||
|
a.play()
|
||||||
|
a.close()
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_path(file_name):
|
||||||
|
return os.path.join(utils.util.get_sounds_directory(), file_name)
|
22
toxygen/notifications/tray.py
Normal file
22
toxygen/notifications/tray.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
|
|
||||||
|
def tray_notification(title, text, tray, window):
|
||||||
|
"""
|
||||||
|
Show tray notification and activate window icon
|
||||||
|
NOTE: different behaviour on different OS
|
||||||
|
:param title: Name of user who sent message or file
|
||||||
|
:param text: text of message or file info
|
||||||
|
:param tray: ref to tray icon
|
||||||
|
:param window: main window
|
||||||
|
"""
|
||||||
|
if QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
|
||||||
|
if len(text) > 30:
|
||||||
|
text = text[:27] + '...'
|
||||||
|
tray.showMessage(title, text, QtWidgets.QSystemTrayIcon.NoIcon, 3000)
|
||||||
|
QtWidgets.QApplication.alert(window, 0)
|
||||||
|
|
||||||
|
def message_clicked():
|
||||||
|
window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
|
||||||
|
window.activateWindow()
|
||||||
|
tray.messageClicked.connect(message_clicked)
|
0
toxygen/plugin_support/__init__.py
Normal file
0
toxygen/plugin_support/__init__.py
Normal file
@ -1,36 +1,50 @@
|
|||||||
import util
|
import utils.util as util
|
||||||
import profile
|
|
||||||
import os
|
import os
|
||||||
import importlib
|
import importlib
|
||||||
import inspect
|
import inspect
|
||||||
import plugins.plugin_super_class as pl
|
import plugins.plugin_super_class as pl
|
||||||
import toxes
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
class PluginLoader(util.Singleton):
|
class Plugin:
|
||||||
|
|
||||||
def __init__(self, tox, settings):
|
def __init__(self, plugin, is_active):
|
||||||
super().__init__()
|
self._instance = plugin
|
||||||
self._profile = profile.Profile.get_instance()
|
self._is_active = is_active
|
||||||
|
|
||||||
|
def get_instance(self):
|
||||||
|
return self._instance
|
||||||
|
|
||||||
|
instance = property(get_instance)
|
||||||
|
|
||||||
|
def get_is_active(self):
|
||||||
|
return self._is_active
|
||||||
|
|
||||||
|
def set_is_active(self, is_active):
|
||||||
|
self._is_active = is_active
|
||||||
|
|
||||||
|
is_active = property(get_is_active, set_is_active)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginLoader:
|
||||||
|
|
||||||
|
def __init__(self, settings, app):
|
||||||
self._settings = settings
|
self._settings = settings
|
||||||
self._plugins = {} # dict. key - plugin unique short name, value - tuple (plugin instance, is active)
|
self._app = app
|
||||||
self._tox = tox
|
self._plugins = {} # dict. key - plugin unique short name, value - Plugin instance
|
||||||
self._encr = toxes.ToxES.get_instance()
|
|
||||||
|
|
||||||
def set_tox(self, tox):
|
def set_tox(self, tox):
|
||||||
"""
|
"""
|
||||||
New tox instance
|
New tox instance
|
||||||
"""
|
"""
|
||||||
self._tox = tox
|
for plugin in self._plugins.values():
|
||||||
for value in self._plugins.values():
|
plugin.instance.set_tox(tox)
|
||||||
value[0].set_tox(tox)
|
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
"""
|
"""
|
||||||
Load all plugins in plugins folder
|
Load all plugins in plugins folder
|
||||||
"""
|
"""
|
||||||
path = util.curr_directory() + '/plugins/'
|
path = util.get_plugins_directory()
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
util.log('Plugin dir not found')
|
util.log('Plugin dir not found')
|
||||||
return
|
return
|
||||||
@ -52,18 +66,19 @@ class PluginLoader(util.Singleton):
|
|||||||
for elem in dir(module):
|
for elem in dir(module):
|
||||||
obj = getattr(module, elem)
|
obj = getattr(module, elem)
|
||||||
# looking for plugin class in module
|
# looking for plugin class in module
|
||||||
if inspect.isclass(obj) and hasattr(obj, 'is_plugin') and obj.is_plugin:
|
if not inspect.isclass(obj) or not hasattr(obj, 'is_plugin') or not obj.is_plugin:
|
||||||
print('Plugin', elem)
|
continue
|
||||||
try: # create instance of plugin class
|
print('Plugin', elem)
|
||||||
inst = obj(self._tox, self._profile, self._settings, self._encr)
|
try: # create instance of plugin class
|
||||||
autostart = inst.get_short_name() in self._settings['plugins']
|
instance = obj(self._app)
|
||||||
if autostart:
|
is_active = instance.get_short_name() in self._settings['plugins']
|
||||||
inst.start()
|
if is_active:
|
||||||
except Exception as ex:
|
instance.start()
|
||||||
util.log('Exception in module ' + name + ' Exception: ' + str(ex))
|
except Exception as ex:
|
||||||
continue
|
util.log('Exception in module ' + name + ' Exception: ' + str(ex))
|
||||||
self._plugins[inst.get_short_name()] = [inst, autostart] # (inst, is active)
|
continue
|
||||||
break
|
self._plugins[instance.get_short_name()] = Plugin(instance, is_active)
|
||||||
|
break
|
||||||
|
|
||||||
def callback_lossless(self, friend_number, data):
|
def callback_lossless(self, friend_number, data):
|
||||||
"""
|
"""
|
||||||
@ -71,8 +86,8 @@ class PluginLoader(util.Singleton):
|
|||||||
"""
|
"""
|
||||||
l = data[0] - pl.LOSSLESS_FIRST_BYTE
|
l = data[0] - pl.LOSSLESS_FIRST_BYTE
|
||||||
name = ''.join(chr(x) for x in data[1:l + 1])
|
name = ''.join(chr(x) for x in data[1:l + 1])
|
||||||
if name in self._plugins and self._plugins[name][1]:
|
if name in self._plugins and self._plugins[name].is_active:
|
||||||
self._plugins[name][0].lossless_packet(''.join(chr(x) for x in data[l + 1:]), friend_number)
|
self._plugins[name].instance.lossless_packet(''.join(chr(x) for x in data[l + 1:]), friend_number)
|
||||||
|
|
||||||
def callback_lossy(self, friend_number, data):
|
def callback_lossy(self, friend_number, data):
|
||||||
"""
|
"""
|
||||||
@ -80,37 +95,38 @@ class PluginLoader(util.Singleton):
|
|||||||
"""
|
"""
|
||||||
l = data[0] - pl.LOSSY_FIRST_BYTE
|
l = data[0] - pl.LOSSY_FIRST_BYTE
|
||||||
name = ''.join(chr(x) for x in data[1:l + 1])
|
name = ''.join(chr(x) for x in data[1:l + 1])
|
||||||
if name in self._plugins and self._plugins[name][1]:
|
if name in self._plugins and self._plugins[name].is_active:
|
||||||
self._plugins[name][0].lossy_packet(''.join(chr(x) for x in data[l + 1:]), friend_number)
|
self._plugins[name].instance.lossy_packet(''.join(chr(x) for x in data[l + 1:]), friend_number)
|
||||||
|
|
||||||
def friend_online(self, friend_number):
|
def friend_online(self, friend_number):
|
||||||
"""
|
"""
|
||||||
Friend with specified number is online
|
Friend with specified number is online
|
||||||
"""
|
"""
|
||||||
for elem in self._plugins.values():
|
for plugin in self._plugins.values():
|
||||||
if elem[1]:
|
if plugin.is_active:
|
||||||
elem[0].friend_connected(friend_number)
|
plugin.instance.friend_connected(friend_number)
|
||||||
|
|
||||||
def get_plugins_list(self):
|
def get_plugins_list(self):
|
||||||
"""
|
"""
|
||||||
Returns list of all plugins
|
Returns list of all plugins
|
||||||
"""
|
"""
|
||||||
result = []
|
result = []
|
||||||
for data in self._plugins.values():
|
for plugin in self._plugins.values():
|
||||||
try:
|
try:
|
||||||
result.append([data[0].get_name(), # plugin full name
|
result.append([plugin.instance.get_name(), # plugin full name
|
||||||
data[1], # is enabled
|
plugin.is_active, # is enabled
|
||||||
data[0].get_description(), # plugin description
|
plugin.instance.get_description(), # plugin description
|
||||||
data[0].get_short_name()]) # key - short unique name
|
plugin.instance.get_short_name()]) # key - short unique name
|
||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def plugin_window(self, key):
|
def plugin_window(self, key):
|
||||||
"""
|
"""
|
||||||
Return window or None for specified plugin
|
Return window or None for specified plugin
|
||||||
"""
|
"""
|
||||||
return self._plugins[key][0].get_window()
|
return self._plugins[key].instance.get_window()
|
||||||
|
|
||||||
def toggle_plugin(self, key):
|
def toggle_plugin(self, key):
|
||||||
"""
|
"""
|
||||||
@ -118,12 +134,12 @@ class PluginLoader(util.Singleton):
|
|||||||
:param key: plugin short name
|
:param key: plugin short name
|
||||||
"""
|
"""
|
||||||
plugin = self._plugins[key]
|
plugin = self._plugins[key]
|
||||||
if plugin[1]:
|
if plugin.is_active:
|
||||||
plugin[0].stop()
|
plugin.instance.stop()
|
||||||
else:
|
else:
|
||||||
plugin[0].start()
|
plugin.instance.start()
|
||||||
plugin[1] = not plugin[1]
|
plugin.is_active = not plugin.is_active
|
||||||
if plugin[1]:
|
if plugin.is_active:
|
||||||
self._settings['plugins'].append(key)
|
self._settings['plugins'].append(key)
|
||||||
else:
|
else:
|
||||||
self._settings['plugins'].remove(key)
|
self._settings['plugins'].remove(key)
|
||||||
@ -135,30 +151,32 @@ class PluginLoader(util.Singleton):
|
|||||||
"""
|
"""
|
||||||
text = text.strip()
|
text = text.strip()
|
||||||
name = text.split()[0]
|
name = text.split()[0]
|
||||||
if name in self._plugins and self._plugins[name][1]:
|
if name in self._plugins and self._plugins[name].is_active:
|
||||||
self._plugins[name][0].command(text[len(name) + 1:])
|
self._plugins[name].instance.command(text[len(name) + 1:])
|
||||||
|
|
||||||
def get_menu(self, menu, num):
|
def get_menu(self, num):
|
||||||
"""
|
"""
|
||||||
Return list of items for menu
|
Return list of items for menu
|
||||||
"""
|
"""
|
||||||
result = []
|
result = []
|
||||||
for elem in self._plugins.values():
|
for plugin in self._plugins.values():
|
||||||
if elem[1]:
|
if not plugin.is_active:
|
||||||
try:
|
continue
|
||||||
result.extend(elem[0].get_menu(menu, num))
|
try:
|
||||||
except:
|
result.extend(plugin.instance.get_menu(num))
|
||||||
continue
|
except:
|
||||||
|
continue
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_message_menu(self, menu, selected_text):
|
def get_message_menu(self, menu, selected_text):
|
||||||
result = []
|
result = []
|
||||||
for elem in self._plugins.values():
|
for plugin in self._plugins.values():
|
||||||
if elem[1]:
|
if not plugin.is_active:
|
||||||
try:
|
continue
|
||||||
result.extend(elem[0].get_message_menu(menu, selected_text))
|
try:
|
||||||
except:
|
result.extend(plugin.instance.get_message_menu(menu, selected_text))
|
||||||
continue
|
except:
|
||||||
|
pass
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
@ -166,8 +184,8 @@ class PluginLoader(util.Singleton):
|
|||||||
App is closing, stop all plugins
|
App is closing, stop all plugins
|
||||||
"""
|
"""
|
||||||
for key in list(self._plugins.keys()):
|
for key in list(self._plugins.keys()):
|
||||||
if self._plugins[key][1]:
|
if self._plugins[key].is_active:
|
||||||
self._plugins[key][0].close()
|
self._plugins[key].instance.close()
|
||||||
del self._plugins[key]
|
del self._plugins[key]
|
||||||
|
|
||||||
def reload(self):
|
def reload(self):
|
@ -1,5 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
import utils.ui as util_ui
|
||||||
|
import common.tox_save as tox_save
|
||||||
|
|
||||||
|
|
||||||
MAX_SHORT_NAME_LENGTH = 5
|
MAX_SHORT_NAME_LENGTH = 5
|
||||||
@ -26,25 +28,22 @@ def log(name, data):
|
|||||||
fl.write(str(data) + '\n')
|
fl.write(str(data) + '\n')
|
||||||
|
|
||||||
|
|
||||||
class PluginSuperClass:
|
class PluginSuperClass(tox_save.ToxSave):
|
||||||
"""
|
"""
|
||||||
Superclass for all plugins. Plugin is Python3 module with at least one class derived from PluginSuperClass.
|
Superclass for all plugins. Plugin is Python3 module with at least one class derived from PluginSuperClass.
|
||||||
"""
|
"""
|
||||||
is_plugin = True
|
is_plugin = True
|
||||||
|
|
||||||
def __init__(self, name, short_name, tox=None, profile=None, settings=None, encrypt_save=None):
|
def __init__(self, name, short_name, app):
|
||||||
"""
|
"""
|
||||||
Constructor. In plugin __init__ should take only 4 last arguments
|
Constructor. In plugin __init__ should take only 1 last argument
|
||||||
:param name: plugin full name
|
:param name: plugin full name
|
||||||
:param short_name: plugin unique short name (length of short name should not exceed MAX_SHORT_NAME_LENGTH)
|
:param short_name: plugin unique short name (length of short name should not exceed MAX_SHORT_NAME_LENGTH)
|
||||||
:param tox: tox instance
|
:param app: App instance
|
||||||
:param profile: profile instance
|
|
||||||
:param settings: profile settings
|
|
||||||
:param encrypt_save: ToxES instance.
|
|
||||||
"""
|
"""
|
||||||
self._settings = settings
|
tox = getattr(app, '_tox')
|
||||||
self._profile = profile
|
super().__init__(tox)
|
||||||
self._tox = tox
|
self._settings = getattr(app, '_settings')
|
||||||
name = name.strip()
|
name = name.strip()
|
||||||
short_name = short_name.strip()
|
short_name = short_name.strip()
|
||||||
if not name or not short_name:
|
if not name or not short_name:
|
||||||
@ -52,7 +51,6 @@ class PluginSuperClass:
|
|||||||
self._name = name
|
self._name = name
|
||||||
self._short_name = short_name[:MAX_SHORT_NAME_LENGTH]
|
self._short_name = short_name[:MAX_SHORT_NAME_LENGTH]
|
||||||
self._translator = None # translator for plugin's GUI
|
self._translator = None # translator for plugin's GUI
|
||||||
self._encrypt_save = encrypt_save
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Get methods
|
# Get methods
|
||||||
@ -76,12 +74,11 @@ class PluginSuperClass:
|
|||||||
"""
|
"""
|
||||||
return self.__doc__
|
return self.__doc__
|
||||||
|
|
||||||
def get_menu(self, menu, row_number):
|
def get_menu(self, row_number):
|
||||||
"""
|
"""
|
||||||
This method creates items for menu which called on right click in list of friends
|
This method creates items for menu which called on right click in list of friends
|
||||||
:param menu: menu instance
|
|
||||||
:param row_number: number of selected row in list of contacts
|
:param row_number: number of selected row in list of contacts
|
||||||
:return list of QAction's
|
:return list of tuples (text, handler)
|
||||||
"""
|
"""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@ -100,12 +97,6 @@ class PluginSuperClass:
|
|||||||
"""
|
"""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def set_tox(self, tox):
|
|
||||||
"""
|
|
||||||
New tox instance
|
|
||||||
"""
|
|
||||||
self._tox = tox
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Plugin was stopped, started or new command received
|
# Plugin was stopped, started or new command received
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
@ -134,11 +125,9 @@ class PluginSuperClass:
|
|||||||
:param command: string with command
|
:param command: string with command
|
||||||
"""
|
"""
|
||||||
if command == 'help':
|
if command == 'help':
|
||||||
msgbox = QtWidgets.QMessageBox()
|
text = util_ui.tr('No commands available')
|
||||||
title = QtWidgets.QApplication.translate("PluginWindow", "List of commands for plugin {}")
|
title = util_ui.tr('List of commands for plugin {}').format(self._name)
|
||||||
msgbox.setWindowTitle(title.format(self._name))
|
util_ui.message_box(text, title)
|
||||||
msgbox.setText(QtWidgets.QApplication.translate("PluginWindow", "No commands available"))
|
|
||||||
msgbox.exec_()
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Translations support
|
# Translations support
|
||||||
|
1466
toxygen/profile.py
1466
toxygen/profile.py
File diff suppressed because it is too large
Load Diff
0
toxygen/smileys/__init__.py
Normal file
0
toxygen/smileys/__init__.py
Normal file
@ -1,11 +1,11 @@
|
|||||||
import util
|
from utils import util
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore
|
||||||
|
|
||||||
|
|
||||||
class SmileyLoader(util.Singleton):
|
class SmileyLoader:
|
||||||
"""
|
"""
|
||||||
Class which loads smileys packs and insert smileys into messages
|
Class which loads smileys packs and insert smileys into messages
|
||||||
"""
|
"""
|
||||||
@ -25,7 +25,7 @@ class SmileyLoader(util.Singleton):
|
|||||||
pack_name = self._settings['smiley_pack']
|
pack_name = self._settings['smiley_pack']
|
||||||
if self._settings['smileys'] and self._curr_pack != pack_name:
|
if self._settings['smileys'] and self._curr_pack != pack_name:
|
||||||
self._curr_pack = pack_name
|
self._curr_pack = pack_name
|
||||||
path = self.get_smileys_path() + 'config.json'
|
path = util.join_path(self.get_smileys_path(), 'config.json')
|
||||||
try:
|
try:
|
||||||
with open(path, encoding='utf8') as fl:
|
with open(path, encoding='utf8') as fl:
|
||||||
self._smileys = json.loads(fl.read())
|
self._smileys = json.loads(fl.read())
|
||||||
@ -34,7 +34,7 @@ class SmileyLoader(util.Singleton):
|
|||||||
print('Smiley pack {} loaded'.format(pack_name))
|
print('Smiley pack {} loaded'.format(pack_name))
|
||||||
keys, values, self._list = [], [], []
|
keys, values, self._list = [], [], []
|
||||||
for key, value in tmp.items():
|
for key, value in tmp.items():
|
||||||
value = self.get_smileys_path() + value
|
value = util.join_path(self.get_smileys_path(), value)
|
||||||
if value not in values:
|
if value not in values:
|
||||||
keys.append(key)
|
keys.append(key)
|
||||||
values.append(value)
|
values.append(value)
|
||||||
@ -45,10 +45,11 @@ class SmileyLoader(util.Singleton):
|
|||||||
print('Smiley pack {} was not loaded. Error: {}'.format(pack_name, ex))
|
print('Smiley pack {} was not loaded. Error: {}'.format(pack_name, ex))
|
||||||
|
|
||||||
def get_smileys_path(self):
|
def get_smileys_path(self):
|
||||||
return util.curr_directory() + '/smileys/' + self._curr_pack + '/' if self._curr_pack is not None else None
|
return util.join_path(util.get_smileys_directory(), self._curr_pack) if self._curr_pack is not None else None
|
||||||
|
|
||||||
def get_packs_list(self):
|
@staticmethod
|
||||||
d = util.curr_directory() + '/smileys/'
|
def get_packs_list():
|
||||||
|
d = util.get_smileys_directory()
|
||||||
return [x[1] for x in os.walk(d)][0]
|
return [x[1] for x in os.walk(d)][0]
|
||||||
|
|
||||||
def get_smileys(self):
|
def get_smileys(self):
|
||||||
@ -71,18 +72,3 @@ class SmileyLoader(util.Singleton):
|
|||||||
if file_name.endswith('.gif'): # animated smiley
|
if file_name.endswith('.gif'): # animated smiley
|
||||||
edit.addAnimation(QtCore.QUrl(file_name), self.get_smileys_path() + file_name)
|
edit.addAnimation(QtCore.QUrl(file_name), self.get_smileys_path() + file_name)
|
||||||
return ' '.join(arr)
|
return ' '.join(arr)
|
||||||
|
|
||||||
|
|
||||||
def sticker_loader():
|
|
||||||
"""
|
|
||||||
:return list of stickers
|
|
||||||
"""
|
|
||||||
result = []
|
|
||||||
d = util.curr_directory() + '/stickers/'
|
|
||||||
keys = [x[1] for x in os.walk(d)][0]
|
|
||||||
for key in keys:
|
|
||||||
path = d + key + '/'
|
|
||||||
files = filter(lambda f: f.endswith('.png'), os.listdir(path))
|
|
||||||
files = map(lambda f: str(path + f), files)
|
|
||||||
result.extend(files)
|
|
||||||
return result
|
|
0
toxygen/stickers/__init__.py
Normal file
0
toxygen/stickers/__init__.py
Normal file
18
toxygen/stickers/stickers.py
Normal file
18
toxygen/stickers/stickers.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import os
|
||||||
|
import utils.util as util
|
||||||
|
|
||||||
|
|
||||||
|
def load_stickers():
|
||||||
|
"""
|
||||||
|
:return list of stickers
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
d = util.get_stickers_directory()
|
||||||
|
keys = [x[1] for x in os.walk(d)][0]
|
||||||
|
for key in keys:
|
||||||
|
path = util.join_path(d, key)
|
||||||
|
files = filter(lambda f: f.endswith('.png'), os.listdir(path))
|
||||||
|
files = map(lambda f: util.join_path(path, f), files)
|
||||||
|
result.extend(files)
|
||||||
|
|
||||||
|
return result
|
@ -1207,12 +1207,12 @@ MessageItem
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageEdit
|
MessageBrowser
|
||||||
{
|
{
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageEdit::focus
|
MessageBrowser::focus
|
||||||
{
|
{
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
@ -1222,7 +1222,7 @@ MessageItem::focus
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageEdit:hover
|
MessageBrowser:hover
|
||||||
{
|
{
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
@ -1243,7 +1243,7 @@ QPushButton:hover
|
|||||||
background-color: #1E90FF;
|
background-color: #1E90FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageEdit
|
MessageBrowser
|
||||||
{
|
{
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
@ -1253,7 +1253,7 @@ MessageEdit
|
|||||||
background-color: #1E90FF;
|
background-color: #1E90FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
#friends_list:item:selected
|
#friendsListWidget:item:selected
|
||||||
{
|
{
|
||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
}
|
}
|
||||||
@ -1277,7 +1277,7 @@ QListWidget > QLabel
|
|||||||
color: #A9A9A9;
|
color: #A9A9A9;
|
||||||
}
|
}
|
||||||
|
|
||||||
#contact_name
|
#searchLineEdit
|
||||||
{
|
{
|
||||||
padding-left: 22px;
|
padding-left: 22px;
|
||||||
}
|
}
|
||||||
@ -1322,3 +1322,14 @@ ClickableLabel:hover
|
|||||||
{
|
{
|
||||||
background-color: #4A4949;
|
background-color: #4A4949;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#warningLabel
|
||||||
|
{
|
||||||
|
color: #BC1C1C;
|
||||||
|
}
|
||||||
|
|
||||||
|
#groupInvitesPushButton
|
||||||
|
{
|
||||||
|
background-color: #009c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#contact_name
|
#searchLineEdit
|
||||||
{
|
{
|
||||||
padding-left: 22px;
|
padding-left: 22px;
|
||||||
}
|
}
|
||||||
@ -27,3 +27,14 @@ MessageEdit
|
|||||||
{
|
{
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#warningLabel
|
||||||
|
{
|
||||||
|
color: #BC1C1C;
|
||||||
|
}
|
||||||
|
|
||||||
|
#groupInvitesPushButton
|
||||||
|
{
|
||||||
|
background-color: #009c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
import json
|
|
||||||
import urllib.request
|
|
||||||
from util import log
|
|
||||||
import settings
|
|
||||||
from PyQt5 import QtNetwork, QtCore
|
|
||||||
|
|
||||||
|
|
||||||
def tox_dns(email):
|
|
||||||
"""
|
|
||||||
TOX DNS 4
|
|
||||||
:param email: data like 'groupbot@toxme.io'
|
|
||||||
:return: tox id on success else None
|
|
||||||
"""
|
|
||||||
site = email.split('@')[1]
|
|
||||||
data = {"action": 3, "name": "{}".format(email)}
|
|
||||||
urls = ('https://{}/api'.format(site), 'http://{}/api'.format(site))
|
|
||||||
s = settings.Settings.get_instance()
|
|
||||||
if not s['proxy_type']: # no proxy
|
|
||||||
for url in urls:
|
|
||||||
try:
|
|
||||||
return send_request(url, data)
|
|
||||||
except Exception as ex:
|
|
||||||
log('TOX DNS ERROR: ' + str(ex))
|
|
||||||
else: # proxy
|
|
||||||
netman = QtNetwork.QNetworkAccessManager()
|
|
||||||
proxy = QtNetwork.QNetworkProxy()
|
|
||||||
proxy.setType(QtNetwork.QNetworkProxy.Socks5Proxy if s['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy)
|
|
||||||
proxy.setHostName(s['proxy_host'])
|
|
||||||
proxy.setPort(s['proxy_port'])
|
|
||||||
netman.setProxy(proxy)
|
|
||||||
for url in urls:
|
|
||||||
try:
|
|
||||||
request = QtNetwork.QNetworkRequest()
|
|
||||||
request.setUrl(QtCore.QUrl(url))
|
|
||||||
request.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, "application/json")
|
|
||||||
reply = netman.post(request, bytes(json.dumps(data), 'utf-8'))
|
|
||||||
|
|
||||||
while not reply.isFinished():
|
|
||||||
QtCore.QThread.msleep(1)
|
|
||||||
QtCore.QCoreApplication.processEvents()
|
|
||||||
data = bytes(reply.readAll().data())
|
|
||||||
result = json.loads(str(data, 'utf-8'))
|
|
||||||
if not result['c']:
|
|
||||||
return result['tox_id']
|
|
||||||
except Exception as ex:
|
|
||||||
log('TOX DNS ERROR: ' + str(ex))
|
|
||||||
|
|
||||||
return None # error
|
|
||||||
|
|
||||||
|
|
||||||
def send_request(url, data):
|
|
||||||
req = urllib.request.Request(url)
|
|
||||||
req.add_header('Content-Type', 'application/json')
|
|
||||||
response = urllib.request.urlopen(req, bytes(json.dumps(data), 'utf-8'))
|
|
||||||
res = json.loads(str(response.read(), 'utf-8'))
|
|
||||||
if not res['c']:
|
|
||||||
return res['tox_id']
|
|
||||||
else:
|
|
||||||
raise LookupError()
|
|
@ -1,220 +0,0 @@
|
|||||||
TOX_USER_STATUS = {
|
|
||||||
'NONE': 0,
|
|
||||||
'AWAY': 1,
|
|
||||||
'BUSY': 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_MESSAGE_TYPE = {
|
|
||||||
'NORMAL': 0,
|
|
||||||
'ACTION': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_PROXY_TYPE = {
|
|
||||||
'NONE': 0,
|
|
||||||
'HTTP': 1,
|
|
||||||
'SOCKS5': 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_SAVEDATA_TYPE = {
|
|
||||||
'NONE': 0,
|
|
||||||
'TOX_SAVE': 1,
|
|
||||||
'SECRET_KEY': 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_OPTIONS_NEW = {
|
|
||||||
'OK': 0,
|
|
||||||
'MALLOC': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_NEW = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'MALLOC': 2,
|
|
||||||
'PORT_ALLOC': 3,
|
|
||||||
'PROXY_BAD_TYPE': 4,
|
|
||||||
'PROXY_BAD_HOST': 5,
|
|
||||||
'PROXY_BAD_PORT': 6,
|
|
||||||
'PROXY_NOT_FOUND': 7,
|
|
||||||
'LOAD_ENCRYPTED': 8,
|
|
||||||
'LOAD_BAD_FORMAT': 9,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_BOOTSTRAP = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'BAD_HOST': 2,
|
|
||||||
'BAD_PORT': 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_CONNECTION = {
|
|
||||||
'NONE': 0,
|
|
||||||
'TCP': 1,
|
|
||||||
'UDP': 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_SET_INFO = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'TOO_LONG': 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FRIEND_ADD = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'TOO_LONG': 2,
|
|
||||||
'NO_MESSAGE': 3,
|
|
||||||
'OWN_KEY': 4,
|
|
||||||
'ALREADY_SENT': 5,
|
|
||||||
'BAD_CHECKSUM': 6,
|
|
||||||
'SET_NEW_NOSPAM': 7,
|
|
||||||
'MALLOC': 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FRIEND_DELETE = {
|
|
||||||
'OK': 0,
|
|
||||||
'FRIEND_NOT_FOUND': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FRIEND_BY_PUBLIC_KEY = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'NOT_FOUND': 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FRIEND_GET_PUBLIC_KEY = {
|
|
||||||
'OK': 0,
|
|
||||||
'FRIEND_NOT_FOUND': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FRIEND_GET_LAST_ONLINE = {
|
|
||||||
'OK': 0,
|
|
||||||
'FRIEND_NOT_FOUND': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FRIEND_QUERY = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'FRIEND_NOT_FOUND': 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_SET_TYPING = {
|
|
||||||
'OK': 0,
|
|
||||||
'FRIEND_NOT_FOUND': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FRIEND_SEND_MESSAGE = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'FRIEND_NOT_FOUND': 2,
|
|
||||||
'FRIEND_NOT_CONNECTED': 3,
|
|
||||||
'SENDQ': 4,
|
|
||||||
'TOO_LONG': 5,
|
|
||||||
'EMPTY': 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_FILE_KIND = {
|
|
||||||
'DATA': 0,
|
|
||||||
'AVATAR': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_FILE_CONTROL = {
|
|
||||||
'RESUME': 0,
|
|
||||||
'PAUSE': 1,
|
|
||||||
'CANCEL': 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FILE_CONTROL = {
|
|
||||||
'OK': 0,
|
|
||||||
'FRIEND_NOT_FOUND': 1,
|
|
||||||
'FRIEND_NOT_CONNECTED': 2,
|
|
||||||
'NOT_FOUND': 3,
|
|
||||||
'NOT_PAUSED': 4,
|
|
||||||
'DENIED': 5,
|
|
||||||
'ALREADY_PAUSED': 6,
|
|
||||||
'SENDQ': 7,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FILE_SEEK = {
|
|
||||||
'OK': 0,
|
|
||||||
'FRIEND_NOT_FOUND': 1,
|
|
||||||
'FRIEND_NOT_CONNECTED': 2,
|
|
||||||
'NOT_FOUND': 3,
|
|
||||||
'DENIED': 4,
|
|
||||||
'INVALID_POSITION': 5,
|
|
||||||
'SENDQ': 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FILE_GET = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'FRIEND_NOT_FOUND': 2,
|
|
||||||
'NOT_FOUND': 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FILE_SEND = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'FRIEND_NOT_FOUND': 2,
|
|
||||||
'FRIEND_NOT_CONNECTED': 3,
|
|
||||||
'NAME_TOO_LONG': 4,
|
|
||||||
'TOO_MANY': 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FILE_SEND_CHUNK = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'FRIEND_NOT_FOUND': 2,
|
|
||||||
'FRIEND_NOT_CONNECTED': 3,
|
|
||||||
'NOT_FOUND': 4,
|
|
||||||
'NOT_TRANSFERRING': 5,
|
|
||||||
'INVALID_LENGTH': 6,
|
|
||||||
'SENDQ': 7,
|
|
||||||
'WRONG_POSITION': 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_FRIEND_CUSTOM_PACKET = {
|
|
||||||
'OK': 0,
|
|
||||||
'NULL': 1,
|
|
||||||
'FRIEND_NOT_FOUND': 2,
|
|
||||||
'FRIEND_NOT_CONNECTED': 3,
|
|
||||||
'INVALID': 4,
|
|
||||||
'EMPTY': 5,
|
|
||||||
'TOO_LONG': 6,
|
|
||||||
'SENDQ': 7,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_ERR_GET_PORT = {
|
|
||||||
'OK': 0,
|
|
||||||
'NOT_BOUND': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_CHAT_CHANGE = {
|
|
||||||
'PEER_ADD': 0,
|
|
||||||
'PEER_DEL': 1,
|
|
||||||
'PEER_NAME': 2
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_GROUPCHAT_TYPE = {
|
|
||||||
'TEXT': 0,
|
|
||||||
'AV': 1
|
|
||||||
}
|
|
||||||
|
|
||||||
TOX_PUBLIC_KEY_SIZE = 32
|
|
||||||
|
|
||||||
TOX_ADDRESS_SIZE = TOX_PUBLIC_KEY_SIZE + 6
|
|
||||||
|
|
||||||
TOX_MAX_FRIEND_REQUEST_LENGTH = 1016
|
|
||||||
|
|
||||||
TOX_MAX_MESSAGE_LENGTH = 1372
|
|
||||||
|
|
||||||
TOX_MAX_NAME_LENGTH = 128
|
|
||||||
|
|
||||||
TOX_MAX_STATUS_MESSAGE_LENGTH = 1007
|
|
||||||
|
|
||||||
TOX_SECRET_KEY_SIZE = 32
|
|
||||||
|
|
||||||
TOX_FILE_ID_LENGTH = 32
|
|
||||||
|
|
||||||
TOX_HASH_LENGTH = 32
|
|
||||||
|
|
||||||
TOX_MAX_CUSTOM_PACKET_SIZE = 1373
|
|
@ -1,28 +0,0 @@
|
|||||||
import util
|
|
||||||
import toxencryptsave
|
|
||||||
|
|
||||||
|
|
||||||
class ToxES(util.Singleton):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self._toxencryptsave = toxencryptsave.ToxEncryptSave()
|
|
||||||
self._passphrase = None
|
|
||||||
|
|
||||||
def set_password(self, passphrase):
|
|
||||||
self._passphrase = passphrase
|
|
||||||
|
|
||||||
def has_password(self):
|
|
||||||
return bool(self._passphrase)
|
|
||||||
|
|
||||||
def is_password(self, password):
|
|
||||||
return self._passphrase == password
|
|
||||||
|
|
||||||
def is_data_encrypted(self, data):
|
|
||||||
return len(data) > 0 and self._toxencryptsave.is_data_encrypted(data)
|
|
||||||
|
|
||||||
def pass_encrypt(self, data):
|
|
||||||
return self._toxencryptsave.pass_encrypt(data, self._passphrase)
|
|
||||||
|
|
||||||
def pass_decrypt(self, data):
|
|
||||||
return self._toxencryptsave.pass_decrypt(data, self._passphrase)
|
|
0
toxygen/ui/__init__.py
Normal file
0
toxygen/ui/__init__.py
Normal file
@ -1,17 +1,16 @@
|
|||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
import widgets
|
from ui import widgets
|
||||||
import profile
|
import utils.util as util
|
||||||
import util
|
|
||||||
import pyaudio
|
import pyaudio
|
||||||
import wave
|
import wave
|
||||||
import settings
|
|
||||||
from util import curr_directory
|
|
||||||
|
|
||||||
|
|
||||||
class IncomingCallWidget(widgets.CenteredWidget):
|
class IncomingCallWidget(widgets.CenteredWidget):
|
||||||
|
|
||||||
def __init__(self, friend_number, text, name):
|
def __init__(self, settings, calls_manager, friend_number, text, name):
|
||||||
super(IncomingCallWidget, self).__init__()
|
super().__init__()
|
||||||
|
self._settings = settings
|
||||||
|
self._calls_manager = calls_manager
|
||||||
self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowStaysOnTopHint)
|
self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowStaysOnTopHint)
|
||||||
self.resize(QtCore.QSize(500, 270))
|
self.resize(QtCore.QSize(500, 270))
|
||||||
self.avatar_label = QtWidgets.QLabel(self)
|
self.avatar_label = QtWidgets.QLabel(self)
|
||||||
@ -21,7 +20,7 @@ class IncomingCallWidget(widgets.CenteredWidget):
|
|||||||
self.name.setGeometry(QtCore.QRect(90, 20, 300, 25))
|
self.name.setGeometry(QtCore.QRect(90, 20, 300, 25))
|
||||||
self._friend_number = friend_number
|
self._friend_number = friend_number
|
||||||
font = QtGui.QFont()
|
font = QtGui.QFont()
|
||||||
font.setFamily(settings.Settings.get_instance()['font'])
|
font.setFamily(settings['font'])
|
||||||
font.setPointSize(16)
|
font.setPointSize(16)
|
||||||
font.setBold(True)
|
font.setBold(True)
|
||||||
self.name.setFont(font)
|
self.name.setFont(font)
|
||||||
@ -34,13 +33,13 @@ class IncomingCallWidget(widgets.CenteredWidget):
|
|||||||
self.accept_video.setGeometry(QtCore.QRect(170, 100, 150, 150))
|
self.accept_video.setGeometry(QtCore.QRect(170, 100, 150, 150))
|
||||||
self.decline = QtWidgets.QPushButton(self)
|
self.decline = QtWidgets.QPushButton(self)
|
||||||
self.decline.setGeometry(QtCore.QRect(320, 100, 150, 150))
|
self.decline.setGeometry(QtCore.QRect(320, 100, 150, 150))
|
||||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/accept_audio.png')
|
pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'accept_audio.png'))
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
self.accept_audio.setIcon(icon)
|
self.accept_audio.setIcon(icon)
|
||||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/accept_video.png')
|
pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'accept_video.png'))
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
self.accept_video.setIcon(icon)
|
self.accept_video.setIcon(icon)
|
||||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/decline_call.png')
|
pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'decline_call.png'))
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
self.decline.setIcon(icon)
|
self.decline.setIcon(icon)
|
||||||
self.accept_audio.setIconSize(QtCore.QSize(150, 150))
|
self.accept_audio.setIconSize(QtCore.QSize(150, 150))
|
||||||
@ -90,11 +89,11 @@ class IncomingCallWidget(widgets.CenteredWidget):
|
|||||||
self.stream.close()
|
self.stream.close()
|
||||||
self.p.terminate()
|
self.p.terminate()
|
||||||
|
|
||||||
self.a = AudioFile(curr_directory() + '/sounds/call.wav')
|
self.a = AudioFile(util.join_path(util.get_sounds_directory(), 'call.wav'))
|
||||||
self.a.play()
|
self.a.play()
|
||||||
self.a.close()
|
self.a.close()
|
||||||
|
|
||||||
if settings.Settings.get_instance()['calls_sound']:
|
if self._settings['calls_sound']:
|
||||||
self.thread = SoundPlay()
|
self.thread = SoundPlay()
|
||||||
self.thread.start()
|
self.thread.start()
|
||||||
else:
|
else:
|
||||||
@ -110,24 +109,21 @@ class IncomingCallWidget(widgets.CenteredWidget):
|
|||||||
if self._processing:
|
if self._processing:
|
||||||
return
|
return
|
||||||
self._processing = True
|
self._processing = True
|
||||||
pr = profile.Profile.get_instance()
|
self._calls_manager.accept_call(self._friend_number, True, False)
|
||||||
pr.accept_call(self._friend_number, True, False)
|
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|
||||||
def accept_call_with_video(self):
|
def accept_call_with_video(self):
|
||||||
if self._processing:
|
if self._processing:
|
||||||
return
|
return
|
||||||
self._processing = True
|
self._processing = True
|
||||||
pr = profile.Profile.get_instance()
|
self._calls_manager.accept_call(self._friend_number, True, True)
|
||||||
pr.accept_call(self._friend_number, True, True)
|
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|
||||||
def decline_call(self):
|
def decline_call(self):
|
||||||
if self._processing:
|
if self._processing:
|
||||||
return
|
return
|
||||||
self._processing = True
|
self._processing = True
|
||||||
pr = profile.Profile.get_instance()
|
self._calls_manager.stop_call(self._friend_number, False)
|
||||||
pr.stop_call(self._friend_number, False)
|
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|
||||||
def set_pixmap(self, pixmap):
|
def set_pixmap(self, pixmap):
|
97
toxygen/ui/contact_items.py
Normal file
97
toxygen/ui/contact_items.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
from wrapper.toxcore_enums_and_consts import *
|
||||||
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
from utils.util import *
|
||||||
|
from ui.widgets import DataLabel
|
||||||
|
|
||||||
|
|
||||||
|
class ContactItem(QtWidgets.QWidget):
|
||||||
|
"""
|
||||||
|
Contact in friends list
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, settings, parent=None):
|
||||||
|
QtWidgets.QWidget.__init__(self, parent)
|
||||||
|
mode = settings['compact_mode']
|
||||||
|
self.setBaseSize(QtCore.QSize(250, 40 if mode else 70))
|
||||||
|
self.avatar_label = QtWidgets.QLabel(self)
|
||||||
|
size = 32 if mode else 64
|
||||||
|
self.avatar_label.setGeometry(QtCore.QRect(3, 4, size, size))
|
||||||
|
self.avatar_label.setScaledContents(False)
|
||||||
|
self.avatar_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
|
self.name = DataLabel(self)
|
||||||
|
self.name.setGeometry(QtCore.QRect(50 if mode else 75, 3 if mode else 10, 150, 15 if mode else 25))
|
||||||
|
font = QtGui.QFont()
|
||||||
|
font.setFamily(settings['font'])
|
||||||
|
font.setPointSize(10 if mode else 12)
|
||||||
|
font.setBold(True)
|
||||||
|
self.name.setFont(font)
|
||||||
|
self.status_message = DataLabel(self)
|
||||||
|
self.status_message.setGeometry(QtCore.QRect(50 if mode else 75, 20 if mode else 30, 170, 15 if mode else 20))
|
||||||
|
font.setPointSize(10)
|
||||||
|
font.setBold(False)
|
||||||
|
self.status_message.setFont(font)
|
||||||
|
self.connection_status = StatusCircle(self)
|
||||||
|
self.connection_status.setGeometry(QtCore.QRect(230, -2 if mode else 5, 32, 32))
|
||||||
|
self.messages = UnreadMessagesCount(settings, self)
|
||||||
|
self.messages.setGeometry(QtCore.QRect(20 if mode else 52, 20 if mode else 50, 30, 20))
|
||||||
|
|
||||||
|
|
||||||
|
class StatusCircle(QtWidgets.QWidget):
|
||||||
|
"""
|
||||||
|
Connection status
|
||||||
|
"""
|
||||||
|
def __init__(self, parent):
|
||||||
|
QtWidgets.QWidget.__init__(self, parent)
|
||||||
|
self.setGeometry(0, 0, 32, 32)
|
||||||
|
self.label = QtWidgets.QLabel(self)
|
||||||
|
self.label.setGeometry(QtCore.QRect(0, 0, 32, 32))
|
||||||
|
self.unread = False
|
||||||
|
|
||||||
|
def update(self, status, unread_messages=None):
|
||||||
|
if unread_messages is None:
|
||||||
|
unread_messages = self.unread
|
||||||
|
else:
|
||||||
|
self.unread = unread_messages
|
||||||
|
if status == TOX_USER_STATUS['NONE']:
|
||||||
|
name = 'online'
|
||||||
|
elif status == TOX_USER_STATUS['AWAY']:
|
||||||
|
name = 'idle'
|
||||||
|
elif status == TOX_USER_STATUS['BUSY']:
|
||||||
|
name = 'busy'
|
||||||
|
else:
|
||||||
|
name = 'offline'
|
||||||
|
if unread_messages:
|
||||||
|
name += '_notification'
|
||||||
|
self.label.setGeometry(QtCore.QRect(0, 0, 32, 32))
|
||||||
|
else:
|
||||||
|
self.label.setGeometry(QtCore.QRect(2, 0, 32, 32))
|
||||||
|
pixmap = QtGui.QPixmap(join_path(get_images_directory(), '{}.png'.format(name)))
|
||||||
|
self.label.setPixmap(pixmap)
|
||||||
|
|
||||||
|
|
||||||
|
class UnreadMessagesCount(QtWidgets.QWidget):
|
||||||
|
|
||||||
|
def __init__(self, settings, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self._settings = settings
|
||||||
|
self.resize(30, 20)
|
||||||
|
self.label = QtWidgets.QLabel(self)
|
||||||
|
self.label.setGeometry(QtCore.QRect(0, 0, 30, 20))
|
||||||
|
self.label.setVisible(False)
|
||||||
|
font = QtGui.QFont()
|
||||||
|
font.setFamily(settings['font'])
|
||||||
|
font.setPointSize(12)
|
||||||
|
font.setBold(True)
|
||||||
|
self.label.setFont(font)
|
||||||
|
self.label.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignCenter)
|
||||||
|
color = settings['unread_color']
|
||||||
|
self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }')
|
||||||
|
|
||||||
|
def update(self, messages_count):
|
||||||
|
color = self._settings['unread_color']
|
||||||
|
self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }')
|
||||||
|
if messages_count:
|
||||||
|
self.label.setVisible(True)
|
||||||
|
self.label.setText(str(messages_count))
|
||||||
|
else:
|
||||||
|
self.label.setVisible(False)
|
52
toxygen/ui/create_profile_screen.py
Normal file
52
toxygen/ui/create_profile_screen.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
from ui.widgets import *
|
||||||
|
from PyQt5 import uic
|
||||||
|
import utils.util as util
|
||||||
|
import utils.ui as util_ui
|
||||||
|
|
||||||
|
|
||||||
|
class CreateProfileScreenResult:
|
||||||
|
|
||||||
|
def __init__(self, save_into_default_folder, password):
|
||||||
|
self._save_into_default_folder = save_into_default_folder
|
||||||
|
self._password = password
|
||||||
|
|
||||||
|
def get_save_into_default_folder(self):
|
||||||
|
return self._save_into_default_folder
|
||||||
|
|
||||||
|
save_into_default_folder = property(get_save_into_default_folder)
|
||||||
|
|
||||||
|
def get_password(self):
|
||||||
|
return self._password
|
||||||
|
|
||||||
|
password = property(get_password)
|
||||||
|
|
||||||
|
|
||||||
|
class CreateProfileScreen(CenteredWidget, DialogWithResult):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
CenteredWidget.__init__(self)
|
||||||
|
DialogWithResult.__init__(self)
|
||||||
|
uic.loadUi(util.get_views_path('create_profile_screen'), self)
|
||||||
|
self.center()
|
||||||
|
self.createProfile.clicked.connect(self._create_profile)
|
||||||
|
self._retranslate_ui()
|
||||||
|
|
||||||
|
def _retranslate_ui(self):
|
||||||
|
self.setWindowTitle(util_ui.tr('New profile settings'))
|
||||||
|
self.defaultFolder.setText(util_ui.tr('Save in default folder'))
|
||||||
|
self.programFolder.setText(util_ui.tr('Save in program folder'))
|
||||||
|
self.password.setPlaceholderText(util_ui.tr('Password'))
|
||||||
|
self.confirmPassword.setPlaceholderText(util_ui.tr('Confirm password'))
|
||||||
|
self.createProfile.setText(util_ui.tr('Create profile'))
|
||||||
|
self.passwordLabel.setText(util_ui.tr('Password (at least 8 symbols):'))
|
||||||
|
|
||||||
|
def _create_profile(self):
|
||||||
|
password = self.password.text()
|
||||||
|
if password != self.confirmPassword.text():
|
||||||
|
self.errorLabel.setText(util_ui.tr('Passwords do not match'))
|
||||||
|
return
|
||||||
|
if 0 < len(password) < 8:
|
||||||
|
self.errorLabel.setText(util_ui.tr('Password must be at least 8 symbols'))
|
||||||
|
return
|
||||||
|
result = CreateProfileScreenResult(self.defaultFolder.isChecked(), password)
|
||||||
|
self.close_with_result(result)
|
68
toxygen/ui/group_bans_widgets.py
Normal file
68
toxygen/ui/group_bans_widgets.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
from ui.widgets import CenteredWidget
|
||||||
|
from PyQt5 import uic, QtWidgets, QtCore
|
||||||
|
import utils.util as util
|
||||||
|
import utils.ui as util_ui
|
||||||
|
|
||||||
|
|
||||||
|
class GroupBanItem(QtWidgets.QWidget):
|
||||||
|
|
||||||
|
def __init__(self, ban, cancel_ban, can_cancel_ban, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self._ban = ban
|
||||||
|
self._cancel_ban = cancel_ban
|
||||||
|
self._can_cancel_ban = can_cancel_ban
|
||||||
|
|
||||||
|
uic.loadUi(util.get_views_path('gc_ban_item'), self)
|
||||||
|
self._update_ui()
|
||||||
|
|
||||||
|
def _update_ui(self):
|
||||||
|
self._retranslate_ui()
|
||||||
|
|
||||||
|
self.banTargetLabel.setText(self._ban.ban_target)
|
||||||
|
ban_time = self._ban.ban_time
|
||||||
|
self.banTimeLabel.setText(util.unix_time_to_long_str(ban_time))
|
||||||
|
|
||||||
|
self.cancelPushButton.clicked.connect(self._cancel_ban)
|
||||||
|
self.cancelPushButton.setEnabled(self._can_cancel_ban)
|
||||||
|
|
||||||
|
def _retranslate_ui(self):
|
||||||
|
self.cancelPushButton.setText(util_ui.tr('Cancel ban'))
|
||||||
|
|
||||||
|
def _cancel_ban(self):
|
||||||
|
self._cancel_ban(self._ban.ban_id)
|
||||||
|
|
||||||
|
|
||||||
|
class GroupBansScreen(CenteredWidget):
|
||||||
|
|
||||||
|
def __init__(self, groups_service, group):
|
||||||
|
super().__init__()
|
||||||
|
self._groups_service = groups_service
|
||||||
|
self._group = group
|
||||||
|
|
||||||
|
uic.loadUi(util.get_views_path('bans_list_screen'), self)
|
||||||
|
self._update_ui()
|
||||||
|
|
||||||
|
def _update_ui(self):
|
||||||
|
self._retranslate_ui()
|
||||||
|
|
||||||
|
self._refresh_bans_list()
|
||||||
|
|
||||||
|
def _retranslate_ui(self):
|
||||||
|
self.setWindowTitle(util_ui.tr('Bans list for group "{}"').format(self._group.name))
|
||||||
|
|
||||||
|
def _refresh_bans_list(self):
|
||||||
|
self.bansListWidget.clear()
|
||||||
|
can_cancel_ban = self._group.is_self_moderator_or_founder()
|
||||||
|
for ban in self._group.bans:
|
||||||
|
self._create_ban_item(ban, can_cancel_ban)
|
||||||
|
|
||||||
|
def _create_ban_item(self, ban, can_cancel_ban):
|
||||||
|
item = GroupBanItem(ban, self._on_ban_cancelled, can_cancel_ban, self.bansListWidget)
|
||||||
|
elem = QtWidgets.QListWidgetItem()
|
||||||
|
elem.setSizeHint(QtCore.QSize(item.width(), item.height()))
|
||||||
|
self.bansListWidget.addItem(elem)
|
||||||
|
self.bansListWidget.setItemWidget(elem, item)
|
||||||
|
|
||||||
|
def _on_ban_cancelled(self, ban_id):
|
||||||
|
self._groups_service.cancel_ban(self._group.number, ban_id)
|
||||||
|
self._refresh_bans_list()
|
127
toxygen/ui/group_invites_widgets.py
Normal file
127
toxygen/ui/group_invites_widgets.py
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
from PyQt5 import uic, QtWidgets
|
||||||
|
import utils.util as util
|
||||||
|
from ui.widgets import *
|
||||||
|
|
||||||
|
|
||||||
|
class GroupInviteItem(QtWidgets.QWidget):
|
||||||
|
|
||||||
|
def __init__(self, parent, chat_name, avatar, friend_name):
|
||||||
|
super().__init__(parent)
|
||||||
|
uic.loadUi(util.get_views_path('gc_invite_item'), self)
|
||||||
|
|
||||||
|
self.groupNameLabel.setText(chat_name)
|
||||||
|
self.friendNameLabel.setText(friend_name)
|
||||||
|
self.friendAvatarLabel.setPixmap(avatar)
|
||||||
|
|
||||||
|
def is_selected(self):
|
||||||
|
return self.selectCheckBox.isChecked()
|
||||||
|
|
||||||
|
def subscribe_checked_event(self, callback):
|
||||||
|
self.selectCheckBox.clicked.connect(callback)
|
||||||
|
|
||||||
|
|
||||||
|
class GroupInvitesScreen(CenteredWidget):
|
||||||
|
|
||||||
|
def __init__(self, groups_service, profile, contacts_provider):
|
||||||
|
super().__init__()
|
||||||
|
self._groups_service = groups_service
|
||||||
|
self._profile = profile
|
||||||
|
self._contacts_provider = contacts_provider
|
||||||
|
|
||||||
|
uic.loadUi(util.get_views_path('group_invites_screen'), self)
|
||||||
|
|
||||||
|
self._update_ui()
|
||||||
|
|
||||||
|
def _update_ui(self):
|
||||||
|
self._retranslate_ui()
|
||||||
|
|
||||||
|
self._refresh_invites_list()
|
||||||
|
|
||||||
|
self.nickLineEdit.setText(self._profile.name)
|
||||||
|
self.statusComboBox.setCurrentIndex(self._profile.status or 0)
|
||||||
|
|
||||||
|
self.nickLineEdit.textChanged.connect(self._nick_changed)
|
||||||
|
self.acceptPushButton.clicked.connect(self._accept_invites)
|
||||||
|
self.declinePushButton.clicked.connect(self._decline_invites)
|
||||||
|
|
||||||
|
self.invitesListWidget.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
|
||||||
|
self.invitesListWidget.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||||
|
|
||||||
|
self._update_buttons_state()
|
||||||
|
|
||||||
|
def _retranslate_ui(self):
|
||||||
|
self.setWindowTitle(util_ui.tr('Group chat invites'))
|
||||||
|
self.noInvitesLabel.setText(util_ui.tr('No group invites found'))
|
||||||
|
self.acceptPushButton.setText(util_ui.tr('Accept'))
|
||||||
|
self.declinePushButton.setText(util_ui.tr('Decline'))
|
||||||
|
self.statusComboBox.addItem(util_ui.tr('Online'))
|
||||||
|
self.statusComboBox.addItem(util_ui.tr('Away'))
|
||||||
|
self.statusComboBox.addItem(util_ui.tr('Busy'))
|
||||||
|
self.nickLineEdit.setPlaceholderText(util_ui.tr('Your nick in chat'))
|
||||||
|
self.passwordLineEdit.setPlaceholderText(util_ui.tr('Optional password'))
|
||||||
|
|
||||||
|
def _get_friend(self, public_key):
|
||||||
|
return self._contacts_provider.get_friend_by_public_key(public_key)
|
||||||
|
|
||||||
|
def _accept_invites(self):
|
||||||
|
nick = self.nickLineEdit.text()
|
||||||
|
password = self.passwordLineEdit.text()
|
||||||
|
status = self.statusComboBox.currentIndex()
|
||||||
|
|
||||||
|
selected_invites = self._get_selected_invites()
|
||||||
|
for invite in selected_invites:
|
||||||
|
self._groups_service.accept_group_invite(invite, nick, status, password)
|
||||||
|
|
||||||
|
self._refresh_invites_list()
|
||||||
|
self._close_window_if_needed()
|
||||||
|
|
||||||
|
def _decline_invites(self):
|
||||||
|
selected_invites = self._get_selected_invites()
|
||||||
|
for invite in selected_invites:
|
||||||
|
self._groups_service.decline_group_invite(invite)
|
||||||
|
|
||||||
|
self._refresh_invites_list()
|
||||||
|
self._close_window_if_needed()
|
||||||
|
|
||||||
|
def _get_selected_invites(self):
|
||||||
|
all_invites = self._groups_service.get_group_invites()
|
||||||
|
selected = []
|
||||||
|
items_count = len(all_invites)
|
||||||
|
for index in range(items_count):
|
||||||
|
list_item = self.invitesListWidget.item(index)
|
||||||
|
item_widget = self.invitesListWidget.itemWidget(list_item)
|
||||||
|
if item_widget.is_selected():
|
||||||
|
selected.append(all_invites[index])
|
||||||
|
|
||||||
|
return selected
|
||||||
|
|
||||||
|
def _refresh_invites_list(self):
|
||||||
|
self.invitesListWidget.clear()
|
||||||
|
invites = self._groups_service.get_group_invites()
|
||||||
|
for invite in invites:
|
||||||
|
self._create_invite_item(invite)
|
||||||
|
|
||||||
|
def _create_invite_item(self, invite):
|
||||||
|
friend = self._get_friend(invite.friend_public_key)
|
||||||
|
item = GroupInviteItem(self.invitesListWidget, invite.chat_name, friend.get_pixmap(), friend.name)
|
||||||
|
item.subscribe_checked_event(self._item_selected)
|
||||||
|
elem = QtWidgets.QListWidgetItem()
|
||||||
|
elem.setSizeHint(QtCore.QSize(item.width(), item.height()))
|
||||||
|
self.invitesListWidget.addItem(elem)
|
||||||
|
self.invitesListWidget.setItemWidget(elem, item)
|
||||||
|
|
||||||
|
def _item_selected(self):
|
||||||
|
self._update_buttons_state()
|
||||||
|
|
||||||
|
def _nick_changed(self):
|
||||||
|
self._update_buttons_state()
|
||||||
|
|
||||||
|
def _update_buttons_state(self):
|
||||||
|
nick = self.nickLineEdit.text()
|
||||||
|
selected_items = self._get_selected_invites()
|
||||||
|
self.acceptPushButton.setEnabled(bool(nick) and len(selected_items))
|
||||||
|
self.declinePushButton.setEnabled(len(selected_items) > 0)
|
||||||
|
|
||||||
|
def _close_window_if_needed(self):
|
||||||
|
if self._groups_service.group_invites_count == 0:
|
||||||
|
self.close()
|
33
toxygen/ui/group_peers_list.py
Normal file
33
toxygen/ui/group_peers_list.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
from ui.widgets import *
|
||||||
|
from wrapper.toxcore_enums_and_consts import *
|
||||||
|
|
||||||
|
|
||||||
|
class PeerItem(QtWidgets.QWidget):
|
||||||
|
|
||||||
|
def __init__(self, peer, handler, width, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.resize(QtCore.QSize(width, 34))
|
||||||
|
self.nameLabel = DataLabel(self)
|
||||||
|
self.nameLabel.setGeometry(5, 0, width - 5, 34)
|
||||||
|
name = peer.name
|
||||||
|
if peer.is_current_user:
|
||||||
|
name += util_ui.tr(' (You)')
|
||||||
|
self.nameLabel.setText(name)
|
||||||
|
if peer.status == TOX_USER_STATUS['NONE']:
|
||||||
|
style = 'QLabel {color: green}'
|
||||||
|
elif peer.status == TOX_USER_STATUS['AWAY']:
|
||||||
|
style = 'QLabel {color: yellow}'
|
||||||
|
else:
|
||||||
|
style = 'QLabel {color: red}'
|
||||||
|
self.nameLabel.setStyleSheet(style)
|
||||||
|
self.nameLabel.mousePressEvent = lambda x: handler(peer.id)
|
||||||
|
|
||||||
|
|
||||||
|
class PeerTypeItem(QtWidgets.QWidget):
|
||||||
|
|
||||||
|
def __init__(self, text, width, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.resize(QtCore.QSize(width, 34))
|
||||||
|
self.nameLabel = DataLabel(self)
|
||||||
|
self.nameLabel.setGeometry(5, 0, width - 5, 34)
|
||||||
|
self.nameLabel.setText(text)
|
77
toxygen/ui/group_settings_widgets.py
Normal file
77
toxygen/ui/group_settings_widgets.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
from ui.widgets import CenteredWidget
|
||||||
|
from PyQt5 import uic
|
||||||
|
import utils.util as util
|
||||||
|
import utils.ui as util_ui
|
||||||
|
|
||||||
|
|
||||||
|
class GroupManagementScreen(CenteredWidget):
|
||||||
|
|
||||||
|
def __init__(self, groups_service, group):
|
||||||
|
super().__init__()
|
||||||
|
self._groups_service = groups_service
|
||||||
|
self._group = group
|
||||||
|
|
||||||
|
uic.loadUi(util.get_views_path('group_management_screen'), self)
|
||||||
|
self._update_ui()
|
||||||
|
|
||||||
|
def _update_ui(self):
|
||||||
|
self._retranslate_ui()
|
||||||
|
|
||||||
|
self.passwordLineEdit.setText(self._group.password)
|
||||||
|
self.privacyStateComboBox.setCurrentIndex(1 if self._group.is_private else 0)
|
||||||
|
self.peersLimitSpinBox.setValue(self._group.peers_limit)
|
||||||
|
|
||||||
|
self.savePushButton.clicked.connect(self._save)
|
||||||
|
|
||||||
|
def _retranslate_ui(self):
|
||||||
|
self.setWindowTitle(util_ui.tr('Group "{}"').format(self._group.name))
|
||||||
|
self.passwordLabel.setText(util_ui.tr('Password:'))
|
||||||
|
self.peerLimitLabel.setText(util_ui.tr('Peer limit:'))
|
||||||
|
self.privacyStateLabel.setText(util_ui.tr('Privacy state:'))
|
||||||
|
self.savePushButton.setText(util_ui.tr('Save'))
|
||||||
|
|
||||||
|
self.privacyStateComboBox.clear()
|
||||||
|
self.privacyStateComboBox.addItem(util_ui.tr('Public'))
|
||||||
|
self.privacyStateComboBox.addItem(util_ui.tr('Private'))
|
||||||
|
|
||||||
|
def _save(self):
|
||||||
|
password = self.passwordLineEdit.text()
|
||||||
|
privacy_state = self.privacyStateComboBox.currentIndex()
|
||||||
|
peers_limit = self.peersLimitSpinBox.value()
|
||||||
|
|
||||||
|
self._groups_service.set_group_password(self._group, password)
|
||||||
|
self._groups_service.set_group_privacy_state(self._group, privacy_state)
|
||||||
|
self._groups_service.set_group_peers_limit(self._group, peers_limit)
|
||||||
|
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
|
class GroupSettingsScreen(CenteredWidget):
|
||||||
|
|
||||||
|
def __init__(self, group):
|
||||||
|
super().__init__()
|
||||||
|
self._group = group
|
||||||
|
|
||||||
|
uic.loadUi(util.get_views_path('gc_settings_screen'), self)
|
||||||
|
self._update_ui()
|
||||||
|
|
||||||
|
def _update_ui(self):
|
||||||
|
self._retranslate_ui()
|
||||||
|
|
||||||
|
self.copyPasswordPushButton.clicked.connect(self._copy_password)
|
||||||
|
self.copyPasswordPushButton.setEnabled(bool(self._group.password))
|
||||||
|
|
||||||
|
def _retranslate_ui(self):
|
||||||
|
self.setWindowTitle(util_ui.tr('Group "{}"').format(self._group.name))
|
||||||
|
if self._group.password:
|
||||||
|
password_label_text = '{} {}'.format(util_ui.tr('Password:'), self._group.password)
|
||||||
|
else:
|
||||||
|
password_label_text = util_ui.tr('Password is not set')
|
||||||
|
self.passwordLabel.setText(password_label_text)
|
||||||
|
self.peerLimitLabel.setText('{} {}'.format(util_ui.tr('Peer limit:'), self._group.peers_limit))
|
||||||
|
privacy_state = util_ui.tr('Private') if self._group.is_private else util_ui.tr('Public')
|
||||||
|
self.privacyStateLabel.setText('{} {}'.format(util_ui.tr('Privacy state:'), privacy_state))
|
||||||
|
self.copyPasswordPushButton.setText(util_ui.tr('Copy password'))
|
||||||
|
|
||||||
|
def _copy_password(self):
|
||||||
|
util_ui.copy_to_clipboard(self._group.password)
|
123
toxygen/ui/groups_widgets.py
Normal file
123
toxygen/ui/groups_widgets.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
from PyQt5 import uic
|
||||||
|
import utils.util as util
|
||||||
|
from ui.widgets import *
|
||||||
|
from wrapper.toxcore_enums_and_consts import *
|
||||||
|
|
||||||
|
|
||||||
|
class BaseGroupScreen(CenteredWidget):
|
||||||
|
|
||||||
|
def __init__(self, groups_service, profile):
|
||||||
|
super().__init__()
|
||||||
|
self._groups_service = groups_service
|
||||||
|
self._profile = profile
|
||||||
|
|
||||||
|
def _retranslate_ui(self):
|
||||||
|
self.nickLineEdit.setPlaceholderText(util_ui.tr('Your nick in chat'))
|
||||||
|
self.nickLabel.setText(util_ui.tr('Nickname:'))
|
||||||
|
self.statusLabel.setText(util_ui.tr('Status:'))
|
||||||
|
self.statusComboBox.addItem(util_ui.tr('Online'))
|
||||||
|
self.statusComboBox.addItem(util_ui.tr('Away'))
|
||||||
|
self.statusComboBox.addItem(util_ui.tr('Busy'))
|
||||||
|
|
||||||
|
|
||||||
|
class CreateGroupScreen(BaseGroupScreen):
|
||||||
|
|
||||||
|
def __init__(self, groups_service, profile):
|
||||||
|
super().__init__(groups_service, profile)
|
||||||
|
uic.loadUi(util.get_views_path('create_group_screen'), self)
|
||||||
|
self.center()
|
||||||
|
self._update_ui()
|
||||||
|
|
||||||
|
def _update_ui(self):
|
||||||
|
self._retranslate_ui()
|
||||||
|
|
||||||
|
self.statusComboBox.setCurrentIndex(self._profile.status or 0)
|
||||||
|
self.nickLineEdit.setText(self._profile.name)
|
||||||
|
|
||||||
|
self.addGroupButton.clicked.connect(self._create_group)
|
||||||
|
self.groupNameLineEdit.textChanged.connect(self._group_name_changed)
|
||||||
|
self.nickLineEdit.textChanged.connect(self._nick_changed)
|
||||||
|
|
||||||
|
def _retranslate_ui(self):
|
||||||
|
super()._retranslate_ui()
|
||||||
|
self.setWindowTitle(util_ui.tr('Create new group chat'))
|
||||||
|
self.groupNameLabel.setText(util_ui.tr('Group name:'))
|
||||||
|
self.groupTypeLabel.setText(util_ui.tr('Group type:'))
|
||||||
|
self.groupNameLineEdit.setPlaceholderText(util_ui.tr('Group\'s persistent name'))
|
||||||
|
self.addGroupButton.setText(util_ui.tr('Create group'))
|
||||||
|
self.groupTypeComboBox.addItem(util_ui.tr('Public'))
|
||||||
|
self.groupTypeComboBox.addItem(util_ui.tr('Private'))
|
||||||
|
self.groupTypeComboBox.setCurrentIndex(1)
|
||||||
|
|
||||||
|
def _create_group(self):
|
||||||
|
group_name = self.groupNameLineEdit.text()
|
||||||
|
privacy_state = self.groupTypeComboBox.currentIndex()
|
||||||
|
nick = self.nickLineEdit.text()
|
||||||
|
status = self.statusComboBox.currentIndex()
|
||||||
|
self._groups_service.create_new_gc(group_name, privacy_state, nick, status)
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def _nick_changed(self):
|
||||||
|
self._update_button_state()
|
||||||
|
|
||||||
|
def _group_name_changed(self):
|
||||||
|
self._update_button_state()
|
||||||
|
|
||||||
|
def _update_button_state(self):
|
||||||
|
is_nick_set = bool(self.nickLineEdit.text())
|
||||||
|
is_group_name_set = bool(self.groupNameLineEdit.text())
|
||||||
|
self.addGroupButton.setEnabled(is_nick_set and is_group_name_set)
|
||||||
|
|
||||||
|
|
||||||
|
class JoinGroupScreen(BaseGroupScreen):
|
||||||
|
|
||||||
|
def __init__(self, groups_service, profile):
|
||||||
|
super().__init__(groups_service, profile)
|
||||||
|
uic.loadUi(util.get_views_path('join_group_screen'), self)
|
||||||
|
self.center()
|
||||||
|
self._update_ui()
|
||||||
|
|
||||||
|
def _update_ui(self):
|
||||||
|
self._retranslate_ui()
|
||||||
|
|
||||||
|
self.statusComboBox.setCurrentIndex(self._profile.status or 0)
|
||||||
|
self.nickLineEdit.setText(self._profile.name)
|
||||||
|
|
||||||
|
self.chatIdLineEdit.textChanged.connect(self._chat_id_changed)
|
||||||
|
self.joinGroupButton.clicked.connect(self._join_group)
|
||||||
|
self.nickLineEdit.textChanged.connect(self._nick_changed)
|
||||||
|
|
||||||
|
def _retranslate_ui(self):
|
||||||
|
super()._retranslate_ui()
|
||||||
|
self.setWindowTitle(util_ui.tr('Join public group chat'))
|
||||||
|
self.chatIdLabel.setText(util_ui.tr('Group ID:'))
|
||||||
|
self.passwordLabel.setText(util_ui.tr('Password:'))
|
||||||
|
self.chatIdLineEdit.setPlaceholderText(util_ui.tr('Group\'s chat ID'))
|
||||||
|
self.joinGroupButton.setText(util_ui.tr('Join group'))
|
||||||
|
self.passwordLineEdit.setPlaceholderText(util_ui.tr('Optional password'))
|
||||||
|
|
||||||
|
def _chat_id_changed(self):
|
||||||
|
self._update_button_state()
|
||||||
|
|
||||||
|
def _nick_changed(self):
|
||||||
|
self._update_button_state()
|
||||||
|
|
||||||
|
def _update_button_state(self):
|
||||||
|
chat_id = self._get_chat_id()
|
||||||
|
is_nick_set = bool(self.nickLineEdit.text())
|
||||||
|
self.joinGroupButton.setEnabled(len(chat_id) == TOX_GROUP_CHAT_ID_SIZE * 2 and is_nick_set)
|
||||||
|
|
||||||
|
def _join_group(self):
|
||||||
|
chat_id = self._get_chat_id()
|
||||||
|
password = self.passwordLineEdit.text()
|
||||||
|
nick = self.nickLineEdit.text()
|
||||||
|
status = self.statusComboBox.currentIndex()
|
||||||
|
self._groups_service.join_gc_by_id(chat_id, password, nick, status)
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def _get_chat_id(self):
|
||||||
|
chat_id = self.chatIdLineEdit.text().strip()
|
||||||
|
if chat_id.startswith('tox:'):
|
||||||
|
chat_id = chat_id[4:]
|
||||||
|
|
||||||
|
return chat_id
|
90
toxygen/ui/items_factories.py
Normal file
90
toxygen/ui/items_factories.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
from ui.contact_items import *
|
||||||
|
from ui.messages_widgets import *
|
||||||
|
|
||||||
|
|
||||||
|
class ContactItemsFactory:
|
||||||
|
|
||||||
|
def __init__(self, settings, main_screen):
|
||||||
|
self._settings = settings
|
||||||
|
self._friends_list = main_screen.friends_list
|
||||||
|
|
||||||
|
def create_contact_item(self):
|
||||||
|
item = ContactItem(self._settings)
|
||||||
|
elem = QtWidgets.QListWidgetItem(self._friends_list)
|
||||||
|
elem.setSizeHint(QtCore.QSize(250, 40 if self._settings['compact_mode'] else 70))
|
||||||
|
self._friends_list.addItem(elem)
|
||||||
|
self._friends_list.setItemWidget(elem, item)
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
class MessagesItemsFactory:
|
||||||
|
|
||||||
|
def __init__(self, settings, plugin_loader, smiley_loader, main_screen, delete_action):
|
||||||
|
self._file_transfers_handler = None
|
||||||
|
self._settings, self._plugin_loader = settings, plugin_loader
|
||||||
|
self._smiley_loader, self._delete_action = smiley_loader, delete_action
|
||||||
|
self._messages = main_screen.messages
|
||||||
|
self._message_edit = main_screen.messageEdit
|
||||||
|
|
||||||
|
def set_file_transfers_handler(self, file_transfers_handler):
|
||||||
|
self._file_transfers_handler = file_transfers_handler
|
||||||
|
|
||||||
|
def create_message_item(self, message, append=True, pixmap=None):
|
||||||
|
item = message.get_widget(self._settings, self._create_message_browser,
|
||||||
|
self._delete_action, self._messages)
|
||||||
|
if pixmap is not None:
|
||||||
|
item.set_avatar(pixmap)
|
||||||
|
elem = QtWidgets.QListWidgetItem()
|
||||||
|
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
|
||||||
|
if append:
|
||||||
|
self._messages.addItem(elem)
|
||||||
|
else:
|
||||||
|
self._messages.insertItem(0, elem)
|
||||||
|
self._messages.setItemWidget(elem, item)
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
|
def create_inline_item(self, message, append=True, position=0):
|
||||||
|
elem = QtWidgets.QListWidgetItem()
|
||||||
|
item = InlineImageItem(message.data, self._messages.width(), elem, self._messages)
|
||||||
|
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
|
||||||
|
if append:
|
||||||
|
self._messages.addItem(elem)
|
||||||
|
else:
|
||||||
|
self._messages.insertItem(position, elem)
|
||||||
|
self._messages.setItemWidget(elem, item)
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
|
def create_unsent_file_item(self, message, append=True):
|
||||||
|
item = message.get_widget(self._file_transfers_handler, self._settings, self._messages.width(), self._messages)
|
||||||
|
elem = QtWidgets.QListWidgetItem()
|
||||||
|
elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34))
|
||||||
|
if append:
|
||||||
|
self._messages.addItem(elem)
|
||||||
|
else:
|
||||||
|
self._messages.insertItem(0, elem)
|
||||||
|
self._messages.setItemWidget(elem, item)
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
|
def create_file_transfer_item(self, message, append=True):
|
||||||
|
item = message.get_widget(self._file_transfers_handler, self._settings, self._messages.width(), self._messages)
|
||||||
|
elem = QtWidgets.QListWidgetItem()
|
||||||
|
elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34))
|
||||||
|
if append:
|
||||||
|
self._messages.addItem(elem)
|
||||||
|
else:
|
||||||
|
self._messages.insertItem(0, elem)
|
||||||
|
self._messages.setItemWidget(elem, item)
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Private methods
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _create_message_browser(self, text, width, message_type, parent=None):
|
||||||
|
return MessageBrowser(self._settings, self._message_edit, self._smiley_loader, self._plugin_loader,
|
||||||
|
text, width, message_type, parent)
|
77
toxygen/ui/login_screen.py
Normal file
77
toxygen/ui/login_screen.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
from ui.widgets import *
|
||||||
|
from PyQt5 import uic
|
||||||
|
import utils.util as util
|
||||||
|
import utils.ui as util_ui
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
|
||||||
|
class LoginScreenResult:
|
||||||
|
|
||||||
|
def __init__(self, profile_path, load_as_default, password=None):
|
||||||
|
self._profile_path = profile_path
|
||||||
|
self._load_as_default = load_as_default
|
||||||
|
self._password = password
|
||||||
|
|
||||||
|
def get_profile_path(self):
|
||||||
|
return self._profile_path
|
||||||
|
|
||||||
|
profile_path = property(get_profile_path)
|
||||||
|
|
||||||
|
def get_load_as_default(self):
|
||||||
|
return self._load_as_default
|
||||||
|
|
||||||
|
load_as_default = property(get_load_as_default)
|
||||||
|
|
||||||
|
def get_password(self):
|
||||||
|
return self._password
|
||||||
|
|
||||||
|
password = property(get_password)
|
||||||
|
|
||||||
|
def is_new_profile(self):
|
||||||
|
return not os.path.isfile(self._profile_path)
|
||||||
|
|
||||||
|
|
||||||
|
class LoginScreen(CenteredWidget, DialogWithResult):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
CenteredWidget.__init__(self)
|
||||||
|
DialogWithResult.__init__(self)
|
||||||
|
uic.loadUi(util.get_views_path('login_screen'), self)
|
||||||
|
self.center()
|
||||||
|
self._profiles = []
|
||||||
|
self._update_ui()
|
||||||
|
|
||||||
|
def update_select(self, profiles):
|
||||||
|
profiles = sorted(profiles, key=lambda p: p[1])
|
||||||
|
self._profiles = list(profiles)
|
||||||
|
self.profilesComboBox.addItems(list(map(lambda p: p[1], profiles)))
|
||||||
|
self.loadProfilePushButton.setEnabled(len(profiles) > 0)
|
||||||
|
|
||||||
|
def _update_ui(self):
|
||||||
|
self.profileNameLineEdit = LineEditWithEnterSupport(self._create_profile, self)
|
||||||
|
self.profileNameLineEdit.setGeometry(QtCore.QRect(20, 100, 160, 30))
|
||||||
|
self._retranslate_ui()
|
||||||
|
self.createProfilePushButton.clicked.connect(self._create_profile)
|
||||||
|
self.loadProfilePushButton.clicked.connect(self._load_existing_profile)
|
||||||
|
|
||||||
|
def _create_profile(self):
|
||||||
|
path = self.profileNameLineEdit.text()
|
||||||
|
load_as_default = self.defaultProfileCheckBox.isChecked()
|
||||||
|
result = LoginScreenResult(path, load_as_default)
|
||||||
|
self.close_with_result(result)
|
||||||
|
|
||||||
|
def _load_existing_profile(self):
|
||||||
|
index = self.profilesComboBox.currentIndex()
|
||||||
|
load_as_default = self.defaultProfileCheckBox.isChecked()
|
||||||
|
path = util.join_path(self._profiles[index][0], self._profiles[index][1] + '.tox')
|
||||||
|
result = LoginScreenResult(path, load_as_default)
|
||||||
|
self.close_with_result(result)
|
||||||
|
|
||||||
|
def _retranslate_ui(self):
|
||||||
|
self.setWindowTitle(util_ui.tr('Log in'))
|
||||||
|
self.profileNameLineEdit.setPlaceholderText(util_ui.tr('Profile name'))
|
||||||
|
self.createProfilePushButton.setText(util_ui.tr('Create'))
|
||||||
|
self.loadProfilePushButton.setText(util_ui.tr('Load profile'))
|
||||||
|
self.defaultProfileCheckBox.setText(util_ui.tr('Use as default'))
|
||||||
|
self.existingProfileGroupBox.setTitle(util_ui.tr('Load existing profile'))
|
||||||
|
self.newProfileGroupBox.setTitle(util_ui.tr('Create new profile'))
|
718
toxygen/ui/main_screen.py
Normal file
718
toxygen/ui/main_screen.py
Normal file
@ -0,0 +1,718 @@
|
|||||||
|
from ui.contact_items import *
|
||||||
|
from ui.widgets import MultilineEdit
|
||||||
|
from ui.main_screen_widgets import *
|
||||||
|
import utils.util as util
|
||||||
|
import utils.ui as util_ui
|
||||||
|
from PyQt5 import uic
|
||||||
|
|
||||||
|
|
||||||
|
class MainWindow(QtWidgets.QMainWindow):
|
||||||
|
|
||||||
|
def __init__(self, settings, tray):
|
||||||
|
super().__init__()
|
||||||
|
self._settings = settings
|
||||||
|
self._contacts_manager = None
|
||||||
|
self._tray = tray
|
||||||
|
self._widget_factory = None
|
||||||
|
self._modal_window = None
|
||||||
|
self._plugins_loader = None
|
||||||
|
self.setAcceptDrops(True)
|
||||||
|
self._saved = False
|
||||||
|
self._smiley_window = None
|
||||||
|
self._profile = self._toxes = self._messenger = None
|
||||||
|
self._file_transfer_handler = self._history_loader = self._groups_service = self._calls_manager = None
|
||||||
|
self._should_show_group_peers_list = False
|
||||||
|
self.initUI()
|
||||||
|
|
||||||
|
def set_dependencies(self, widget_factory, tray, contacts_manager, messenger, profile, plugins_loader,
|
||||||
|
file_transfer_handler, history_loader, calls_manager, groups_service, toxes):
|
||||||
|
self._widget_factory = widget_factory
|
||||||
|
self._tray = tray
|
||||||
|
self._contacts_manager = contacts_manager
|
||||||
|
self._profile = profile
|
||||||
|
self._plugins_loader = plugins_loader
|
||||||
|
self._file_transfer_handler = file_transfer_handler
|
||||||
|
self._history_loader = history_loader
|
||||||
|
self._calls_manager = calls_manager
|
||||||
|
self._groups_service = groups_service
|
||||||
|
self._toxes = toxes
|
||||||
|
self._messenger = messenger
|
||||||
|
self._contacts_manager.active_contact_changed.add_callback(self._new_contact_selected)
|
||||||
|
self.messageEdit.set_dependencies(messenger, contacts_manager, file_transfer_handler)
|
||||||
|
|
||||||
|
self.update_gc_invites_button_state()
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
super().show()
|
||||||
|
self._contacts_manager.update()
|
||||||
|
if self._settings['show_welcome_screen']:
|
||||||
|
self._modal_window = self._widget_factory.create_welcome_window()
|
||||||
|
|
||||||
|
def setup_menu(self, window):
|
||||||
|
self.menubar = QtWidgets.QMenuBar(window)
|
||||||
|
self.menubar.setObjectName("menubar")
|
||||||
|
self.menubar.setNativeMenuBar(False)
|
||||||
|
self.menubar.setMinimumSize(self.width(), 25)
|
||||||
|
self.menubar.setMaximumSize(self.width(), 25)
|
||||||
|
self.menubar.setBaseSize(self.width(), 25)
|
||||||
|
self.menuProfile = QtWidgets.QMenu(self.menubar)
|
||||||
|
|
||||||
|
self.menuProfile = QtWidgets.QMenu(self.menubar)
|
||||||
|
self.menuProfile.setObjectName("menuProfile")
|
||||||
|
self.menuGC = QtWidgets.QMenu(self.menubar)
|
||||||
|
self.menuSettings = QtWidgets.QMenu(self.menubar)
|
||||||
|
self.menuSettings.setObjectName("menuSettings")
|
||||||
|
self.menuPlugins = QtWidgets.QMenu(self.menubar)
|
||||||
|
self.menuPlugins.setObjectName("menuPlugins")
|
||||||
|
self.menuAbout = QtWidgets.QMenu(self.menubar)
|
||||||
|
self.menuAbout.setObjectName("menuAbout")
|
||||||
|
|
||||||
|
self.actionAdd_friend = QtWidgets.QAction(window)
|
||||||
|
self.actionAdd_friend.setObjectName("actionAdd_friend")
|
||||||
|
self.actionprofilesettings = QtWidgets.QAction(window)
|
||||||
|
self.actionprofilesettings.setObjectName("actionprofilesettings")
|
||||||
|
self.actionPrivacy_settings = QtWidgets.QAction(window)
|
||||||
|
self.actionPrivacy_settings.setObjectName("actionPrivacy_settings")
|
||||||
|
self.actionInterface_settings = QtWidgets.QAction(window)
|
||||||
|
self.actionInterface_settings.setObjectName("actionInterface_settings")
|
||||||
|
self.actionNotifications = QtWidgets.QAction(window)
|
||||||
|
self.actionNotifications.setObjectName("actionNotifications")
|
||||||
|
self.actionNetwork = QtWidgets.QAction(window)
|
||||||
|
self.actionNetwork.setObjectName("actionNetwork")
|
||||||
|
self.actionAbout_program = QtWidgets.QAction(window)
|
||||||
|
self.actionAbout_program.setObjectName("actionAbout_program")
|
||||||
|
self.updateSettings = QtWidgets.QAction(window)
|
||||||
|
self.actionSettings = QtWidgets.QAction(window)
|
||||||
|
self.actionSettings.setObjectName("actionSettings")
|
||||||
|
self.audioSettings = QtWidgets.QAction(window)
|
||||||
|
self.videoSettings = QtWidgets.QAction(window)
|
||||||
|
self.pluginData = QtWidgets.QAction(window)
|
||||||
|
self.importPlugin = QtWidgets.QAction(window)
|
||||||
|
self.reloadPlugins = QtWidgets.QAction(window)
|
||||||
|
self.lockApp = QtWidgets.QAction(window)
|
||||||
|
self.createGC = QtWidgets.QAction(window)
|
||||||
|
self.joinGC = QtWidgets.QAction(window)
|
||||||
|
self.gc_invites = QtWidgets.QAction(window)
|
||||||
|
|
||||||
|
self.menuProfile.addAction(self.actionAdd_friend)
|
||||||
|
self.menuProfile.addAction(self.actionSettings)
|
||||||
|
self.menuProfile.addAction(self.lockApp)
|
||||||
|
self.menuGC.addAction(self.createGC)
|
||||||
|
self.menuGC.addAction(self.joinGC)
|
||||||
|
self.menuGC.addAction(self.gc_invites)
|
||||||
|
self.menuSettings.addAction(self.actionPrivacy_settings)
|
||||||
|
self.menuSettings.addAction(self.actionInterface_settings)
|
||||||
|
self.menuSettings.addAction(self.actionNotifications)
|
||||||
|
self.menuSettings.addAction(self.actionNetwork)
|
||||||
|
self.menuSettings.addAction(self.audioSettings)
|
||||||
|
self.menuSettings.addAction(self.videoSettings)
|
||||||
|
self.menuSettings.addAction(self.updateSettings)
|
||||||
|
self.menuPlugins.addAction(self.pluginData)
|
||||||
|
self.menuPlugins.addAction(self.importPlugin)
|
||||||
|
self.menuPlugins.addAction(self.reloadPlugins)
|
||||||
|
self.menuAbout.addAction(self.actionAbout_program)
|
||||||
|
|
||||||
|
self.menubar.addAction(self.menuProfile.menuAction())
|
||||||
|
self.menubar.addAction(self.menuGC.menuAction())
|
||||||
|
self.menubar.addAction(self.menuSettings.menuAction())
|
||||||
|
self.menubar.addAction(self.menuPlugins.menuAction())
|
||||||
|
self.menubar.addAction(self.menuAbout.menuAction())
|
||||||
|
|
||||||
|
self.actionAbout_program.triggered.connect(self.about_program)
|
||||||
|
self.actionNetwork.triggered.connect(self.network_settings)
|
||||||
|
self.actionAdd_friend.triggered.connect(self.add_contact_triggered)
|
||||||
|
self.createGC.triggered.connect(self.create_gc)
|
||||||
|
self.joinGC.triggered.connect(self.join_gc)
|
||||||
|
self.actionSettings.triggered.connect(self.profile_settings)
|
||||||
|
self.actionPrivacy_settings.triggered.connect(self.privacy_settings)
|
||||||
|
self.actionInterface_settings.triggered.connect(self.interface_settings)
|
||||||
|
self.actionNotifications.triggered.connect(self.notification_settings)
|
||||||
|
self.audioSettings.triggered.connect(self.audio_settings)
|
||||||
|
self.videoSettings.triggered.connect(self.video_settings)
|
||||||
|
self.updateSettings.triggered.connect(self.update_settings)
|
||||||
|
self.pluginData.triggered.connect(self.plugins_menu)
|
||||||
|
self.lockApp.triggered.connect(self.lock_app)
|
||||||
|
self.importPlugin.triggered.connect(self.import_plugin)
|
||||||
|
self.reloadPlugins.triggered.connect(self.reload_plugins)
|
||||||
|
self.gc_invites.triggered.connect(self._open_gc_invites_list)
|
||||||
|
|
||||||
|
def languageChange(self, *args, **kwargs):
|
||||||
|
self.retranslateUi()
|
||||||
|
|
||||||
|
def event(self, event):
|
||||||
|
if event.type() == QtCore.QEvent.WindowActivate:
|
||||||
|
self._tray.setIcon(QtGui.QIcon(util.join_path(util.get_images_directory(), 'icon.png')))
|
||||||
|
self.messages.repaint()
|
||||||
|
return super().event(event)
|
||||||
|
|
||||||
|
def retranslateUi(self):
|
||||||
|
self.lockApp.setText(util_ui.tr("Lock"))
|
||||||
|
self.menuPlugins.setTitle(util_ui.tr("Plugins"))
|
||||||
|
self.menuGC.setTitle(util_ui.tr("Group chats"))
|
||||||
|
self.pluginData.setText(util_ui.tr("List of plugins"))
|
||||||
|
self.menuProfile.setTitle(util_ui.tr("Profile"))
|
||||||
|
self.menuSettings.setTitle(util_ui.tr("Settings"))
|
||||||
|
self.menuAbout.setTitle(util_ui.tr("About"))
|
||||||
|
self.actionAdd_friend.setText(util_ui.tr("Add contact"))
|
||||||
|
self.createGC.setText(util_ui.tr("Create group chat"))
|
||||||
|
self.joinGC.setText(util_ui.tr("Join group chat"))
|
||||||
|
self.gc_invites.setText(util_ui.tr("Group invites"))
|
||||||
|
self.actionprofilesettings.setText(util_ui.tr("Profile"))
|
||||||
|
self.actionPrivacy_settings.setText(util_ui.tr("Privacy"))
|
||||||
|
self.actionInterface_settings.setText(util_ui.tr("Interface"))
|
||||||
|
self.actionNotifications.setText(util_ui.tr("Notifications"))
|
||||||
|
self.actionNetwork.setText(util_ui.tr("Network"))
|
||||||
|
self.actionAbout_program.setText(util_ui.tr("About program"))
|
||||||
|
self.actionSettings.setText(util_ui.tr("Settings"))
|
||||||
|
self.audioSettings.setText(util_ui.tr("Audio"))
|
||||||
|
self.videoSettings.setText(util_ui.tr("Video"))
|
||||||
|
self.updateSettings.setText(util_ui.tr("Updates"))
|
||||||
|
self.importPlugin.setText(util_ui.tr("Import plugin"))
|
||||||
|
self.reloadPlugins.setText(util_ui.tr("Reload plugins"))
|
||||||
|
|
||||||
|
self.searchLineEdit.setPlaceholderText(util_ui.tr("Search"))
|
||||||
|
self.sendMessageButton.setToolTip(util_ui.tr("Send message"))
|
||||||
|
self.callButton.setToolTip(util_ui.tr("Start audio call with friend"))
|
||||||
|
self.contactsFilterComboBox.clear()
|
||||||
|
self.contactsFilterComboBox.addItem(util_ui.tr("All"))
|
||||||
|
self.contactsFilterComboBox.addItem(util_ui.tr("Online"))
|
||||||
|
self.contactsFilterComboBox.addItem(util_ui.tr("Online first"))
|
||||||
|
self.contactsFilterComboBox.addItem(util_ui.tr("Name"))
|
||||||
|
self.contactsFilterComboBox.addItem(util_ui.tr("Online and by name"))
|
||||||
|
self.contactsFilterComboBox.addItem(util_ui.tr("Online first and by name"))
|
||||||
|
|
||||||
|
def setup_right_bottom(self, Form):
|
||||||
|
Form.resize(650, 60)
|
||||||
|
self.messageEdit = MessageArea(Form, self)
|
||||||
|
self.messageEdit.setGeometry(QtCore.QRect(0, 3, 450, 55))
|
||||||
|
font = QtGui.QFont()
|
||||||
|
font.setPointSize(11)
|
||||||
|
font.setFamily(self._settings['font'])
|
||||||
|
self.messageEdit.setFont(font)
|
||||||
|
|
||||||
|
self.sendMessageButton = QtWidgets.QPushButton(Form)
|
||||||
|
self.sendMessageButton.setGeometry(QtCore.QRect(565, 3, 60, 55))
|
||||||
|
|
||||||
|
self.menuButton = MenuButton(Form, self.show_menu)
|
||||||
|
self.menuButton.setGeometry(QtCore.QRect(QtCore.QRect(455, 3, 55, 55)))
|
||||||
|
|
||||||
|
pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'send.png'))
|
||||||
|
icon = QtGui.QIcon(pixmap)
|
||||||
|
self.sendMessageButton.setIcon(icon)
|
||||||
|
self.sendMessageButton.setIconSize(QtCore.QSize(45, 60))
|
||||||
|
|
||||||
|
pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'menu.png'))
|
||||||
|
icon = QtGui.QIcon(pixmap)
|
||||||
|
self.menuButton.setIcon(icon)
|
||||||
|
self.menuButton.setIconSize(QtCore.QSize(40, 40))
|
||||||
|
|
||||||
|
self.sendMessageButton.clicked.connect(self.send_message)
|
||||||
|
|
||||||
|
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||||
|
|
||||||
|
def setup_left_column(self, left_column):
|
||||||
|
uic.loadUi(util.get_views_path('ms_left_column'), left_column)
|
||||||
|
|
||||||
|
pixmap = QtGui.QPixmap()
|
||||||
|
pixmap.load(util.join_path(util.get_images_directory(), 'search.png'))
|
||||||
|
left_column.searchLabel.setPixmap(pixmap)
|
||||||
|
|
||||||
|
self.name = DataLabel(left_column)
|
||||||
|
self.name.setGeometry(QtCore.QRect(75, 15, 150, 25))
|
||||||
|
font = QtGui.QFont()
|
||||||
|
font.setFamily(self._settings['font'])
|
||||||
|
font.setPointSize(14)
|
||||||
|
font.setBold(True)
|
||||||
|
self.name.setFont(font)
|
||||||
|
|
||||||
|
self.status_message = DataLabel(left_column)
|
||||||
|
self.status_message.setGeometry(QtCore.QRect(75, 35, 170, 25))
|
||||||
|
|
||||||
|
self.connection_status = StatusCircle(left_column)
|
||||||
|
self.connection_status.setGeometry(QtCore.QRect(230, 10, 32, 32))
|
||||||
|
|
||||||
|
left_column.contactsFilterComboBox.activated[int].connect(lambda x: self._filtering())
|
||||||
|
|
||||||
|
self.avatar_label = left_column.avatarLabel
|
||||||
|
self.searchLineEdit = left_column.searchLineEdit
|
||||||
|
self.contacts_filter = self.contactsFilterComboBox = left_column.contactsFilterComboBox
|
||||||
|
|
||||||
|
self.groupInvitesPushButton = left_column.groupInvitesPushButton
|
||||||
|
|
||||||
|
self.groupInvitesPushButton.clicked.connect(self._open_gc_invites_list)
|
||||||
|
self.avatar_label.mouseReleaseEvent = self.profile_settings
|
||||||
|
self.status_message.mouseReleaseEvent = self.profile_settings
|
||||||
|
self.name.mouseReleaseEvent = self.profile_settings
|
||||||
|
|
||||||
|
self.friends_list = left_column.friendsListWidget
|
||||||
|
self.friends_list.itemSelectionChanged.connect(self._selected_contact_changed)
|
||||||
|
self.friends_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||||
|
self.friends_list.customContextMenuRequested.connect(self._friend_right_click)
|
||||||
|
self.friends_list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||||
|
self.friends_list.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||||
|
self.friends_list.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||||
|
self.friends_list.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu)
|
||||||
|
|
||||||
|
def setup_right_top(self, Form):
|
||||||
|
Form.resize(650, 75)
|
||||||
|
self.account_avatar = QtWidgets.QLabel(Form)
|
||||||
|
self.account_avatar.setGeometry(QtCore.QRect(10, 5, 64, 64))
|
||||||
|
self.account_avatar.setScaledContents(False)
|
||||||
|
self.account_name = DataLabel(Form)
|
||||||
|
self.account_name.setGeometry(QtCore.QRect(100, 0, 400, 25))
|
||||||
|
self.account_name.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse)
|
||||||
|
font = QtGui.QFont()
|
||||||
|
font.setFamily(self._settings['font'])
|
||||||
|
font.setPointSize(14)
|
||||||
|
font.setBold(True)
|
||||||
|
self.account_name.setFont(font)
|
||||||
|
self.account_status = DataLabel(Form)
|
||||||
|
self.account_status.setGeometry(QtCore.QRect(100, 20, 400, 25))
|
||||||
|
self.account_status.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse)
|
||||||
|
font.setPointSize(12)
|
||||||
|
font.setBold(False)
|
||||||
|
self.account_status.setFont(font)
|
||||||
|
self.account_status.setObjectName("account_status")
|
||||||
|
self.callButton = QtWidgets.QPushButton(Form)
|
||||||
|
self.callButton.setGeometry(QtCore.QRect(550, 5, 50, 50))
|
||||||
|
self.callButton.setObjectName("callButton")
|
||||||
|
self.callButton.clicked.connect(lambda: self._calls_manager.call_click(True))
|
||||||
|
self.videocallButton = QtWidgets.QPushButton(Form)
|
||||||
|
self.videocallButton.setGeometry(QtCore.QRect(550, 5, 50, 50))
|
||||||
|
self.videocallButton.setObjectName("videocallButton")
|
||||||
|
self.videocallButton.clicked.connect(lambda: self._calls_manager.call_click(True, True))
|
||||||
|
self.groupMenuButton = QtWidgets.QPushButton(Form)
|
||||||
|
self.groupMenuButton.setGeometry(QtCore.QRect(470, 10, 50, 50))
|
||||||
|
self.groupMenuButton.clicked.connect(self._toggle_gc_peers_list)
|
||||||
|
self.groupMenuButton.setVisible(False)
|
||||||
|
pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'menu.png'))
|
||||||
|
icon = QtGui.QIcon(pixmap)
|
||||||
|
self.groupMenuButton.setIcon(icon)
|
||||||
|
self.groupMenuButton.setIconSize(QtCore.QSize(45, 60))
|
||||||
|
self.update_call_state('call')
|
||||||
|
self.typing = QtWidgets.QLabel(Form)
|
||||||
|
self.typing.setGeometry(QtCore.QRect(500, 25, 50, 30))
|
||||||
|
pixmap = QtGui.QPixmap(QtCore.QSize(50, 30))
|
||||||
|
pixmap.load(util.join_path(util.get_images_directory(), 'typing.png'))
|
||||||
|
self.typing.setScaledContents(False)
|
||||||
|
self.typing.setPixmap(pixmap.scaled(50, 30, QtCore.Qt.KeepAspectRatio))
|
||||||
|
self.typing.setVisible(False)
|
||||||
|
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||||
|
|
||||||
|
def setup_right_center(self, widget):
|
||||||
|
self.messages = QtWidgets.QListWidget(widget)
|
||||||
|
self.messages.setGeometry(0, 0, 620, 310)
|
||||||
|
self.messages.setObjectName("messages")
|
||||||
|
self.messages.setSpacing(1)
|
||||||
|
self.messages.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||||
|
self.messages.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||||
|
self.messages.focusOutEvent = lambda event: self.messages.clearSelection()
|
||||||
|
self.messages.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu)
|
||||||
|
|
||||||
|
def load(pos):
|
||||||
|
if not pos:
|
||||||
|
contact = self._contacts_manager.get_curr_contact()
|
||||||
|
self._history_loader.load_history(contact)
|
||||||
|
self.messages.verticalScrollBar().setValue(1)
|
||||||
|
self.messages.verticalScrollBar().valueChanged.connect(load)
|
||||||
|
self.messages.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||||
|
self.messages.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||||
|
|
||||||
|
self.peers_list = QtWidgets.QListWidget(widget)
|
||||||
|
self.peers_list.setGeometry(0, 0, 0, 0)
|
||||||
|
self.peers_list.setObjectName("peersList")
|
||||||
|
self.peers_list.setSpacing(1)
|
||||||
|
self.peers_list.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
|
||||||
|
self.peers_list.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||||
|
self.peers_list.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu)
|
||||||
|
self.peers_list.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
|
||||||
|
|
||||||
|
def initUI(self):
|
||||||
|
self.setMinimumSize(920, 500)
|
||||||
|
s = self._settings
|
||||||
|
self.setGeometry(s['x'], s['y'], s['width'], s['height'])
|
||||||
|
self.setWindowTitle('Toxygen')
|
||||||
|
menu = QtWidgets.QWidget()
|
||||||
|
main = QtWidgets.QWidget()
|
||||||
|
grid = QtWidgets.QGridLayout()
|
||||||
|
info = QtWidgets.QWidget()
|
||||||
|
left_column = QtWidgets.QWidget()
|
||||||
|
messages = QtWidgets.QWidget()
|
||||||
|
message_buttons = QtWidgets.QWidget()
|
||||||
|
self.setup_right_center(messages)
|
||||||
|
self.setup_right_top(info)
|
||||||
|
self.setup_right_bottom(message_buttons)
|
||||||
|
self.setup_left_column(left_column)
|
||||||
|
self.setup_menu(menu)
|
||||||
|
if not s['mirror_mode']:
|
||||||
|
grid.addWidget(left_column, 1, 0, 4, 1)
|
||||||
|
grid.addWidget(messages, 2, 1, 2, 1)
|
||||||
|
grid.addWidget(info, 1, 1)
|
||||||
|
grid.addWidget(message_buttons, 4, 1)
|
||||||
|
grid.setColumnMinimumWidth(1, 500)
|
||||||
|
grid.setColumnMinimumWidth(0, 270)
|
||||||
|
else:
|
||||||
|
grid.addWidget(left_column, 1, 1, 4, 1)
|
||||||
|
grid.addWidget(messages, 2, 0, 2, 1)
|
||||||
|
grid.addWidget(info, 1, 0)
|
||||||
|
grid.addWidget(message_buttons, 4, 0)
|
||||||
|
grid.setColumnMinimumWidth(0, 500)
|
||||||
|
grid.setColumnMinimumWidth(1, 270)
|
||||||
|
|
||||||
|
grid.addWidget(menu, 0, 0, 1, 2)
|
||||||
|
grid.setSpacing(0)
|
||||||
|
grid.setContentsMargins(0, 0, 0, 0)
|
||||||
|
grid.setRowMinimumHeight(0, 25)
|
||||||
|
grid.setRowMinimumHeight(1, 75)
|
||||||
|
grid.setRowMinimumHeight(2, 25)
|
||||||
|
grid.setRowMinimumHeight(3, 320)
|
||||||
|
grid.setRowMinimumHeight(4, 55)
|
||||||
|
grid.setColumnStretch(1, 1)
|
||||||
|
grid.setRowStretch(3, 1)
|
||||||
|
main.setLayout(grid)
|
||||||
|
self.setCentralWidget(main)
|
||||||
|
self.messageEdit.setFocus()
|
||||||
|
self.friend_info = info
|
||||||
|
self.retranslateUi()
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
close_setting = self._settings['close_app']
|
||||||
|
if close_setting == 0 or self._settings.closing:
|
||||||
|
if self._saved:
|
||||||
|
return
|
||||||
|
self._saved = True
|
||||||
|
self._settings['x'] = self.geometry().x()
|
||||||
|
self._settings['y'] = self.geometry().y()
|
||||||
|
self._settings['width'] = self.width()
|
||||||
|
self._settings['height'] = self.height()
|
||||||
|
self._settings.save()
|
||||||
|
util_ui.close_all_windows()
|
||||||
|
event.accept()
|
||||||
|
elif close_setting == 2 and QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
|
||||||
|
event.ignore()
|
||||||
|
self.hide()
|
||||||
|
else:
|
||||||
|
event.ignore()
|
||||||
|
self.showMinimized()
|
||||||
|
|
||||||
|
def close_window(self):
|
||||||
|
self._settings.closing = True
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def resizeEvent(self, *args, **kwargs):
|
||||||
|
width = self.width() - 270
|
||||||
|
if not self._should_show_group_peers_list:
|
||||||
|
self.messages.setGeometry(0, 0, width, self.height() - 155)
|
||||||
|
self.peers_list.setGeometry(0, 0, 0, 0)
|
||||||
|
else:
|
||||||
|
self.messages.setGeometry(0, 0, width * 3 // 4, self.height() - 155)
|
||||||
|
self.peers_list.setGeometry(width * 3 // 4, 0, width - width * 3 // 4, self.height() - 155)
|
||||||
|
|
||||||
|
invites_button_visible = self.groupInvitesPushButton.isVisible()
|
||||||
|
self.friends_list.setGeometry(0, 125 if invites_button_visible else 100,
|
||||||
|
270, self.height() - 150 if invites_button_visible else self.height() - 125)
|
||||||
|
|
||||||
|
self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 10, 50, 50))
|
||||||
|
self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 10, 50, 50))
|
||||||
|
self.groupMenuButton.setGeometry(QtCore.QRect(self.width() - 450, 10, 50, 50))
|
||||||
|
self.typing.setGeometry(QtCore.QRect(self.width() - 450, 20, 50, 30))
|
||||||
|
|
||||||
|
self.messageEdit.setGeometry(QtCore.QRect(55, 0, self.width() - 395, 55))
|
||||||
|
self.menuButton.setGeometry(QtCore.QRect(0, 0, 55, 55))
|
||||||
|
self.sendMessageButton.setGeometry(QtCore.QRect(self.width() - 340, 0, 70, 55))
|
||||||
|
|
||||||
|
self.account_name.setGeometry(QtCore.QRect(100, 15, self.width() - 560, 25))
|
||||||
|
self.account_status.setGeometry(QtCore.QRect(100, 35, self.width() - 560, 25))
|
||||||
|
self.messageEdit.setFocus()
|
||||||
|
|
||||||
|
def keyPressEvent(self, event):
|
||||||
|
key, modifiers = event.key(), event.modifiers()
|
||||||
|
if key == QtCore.Qt.Key_Escape and QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
|
||||||
|
self.hide()
|
||||||
|
elif key == QtCore.Qt.Key_C and modifiers & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
|
||||||
|
rows = list(map(lambda x: self.messages.row(x), self.messages.selectedItems()))
|
||||||
|
indexes = (rows[0] - self.messages.count(), rows[-1] - self.messages.count())
|
||||||
|
s = self._history_loader.export_history(self._contacts_manager.get_curr_friend(), True, indexes)
|
||||||
|
self.copy_text(s)
|
||||||
|
elif key == QtCore.Qt.Key_Z and modifiers & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
|
||||||
|
self.messages.clearSelection()
|
||||||
|
elif key == QtCore.Qt.Key_F and modifiers & QtCore.Qt.ControlModifier:
|
||||||
|
self.show_search_field()
|
||||||
|
else:
|
||||||
|
super().keyPressEvent(event)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Functions which called when user click in menu
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def about_program(self):
|
||||||
|
# TODO: replace with window
|
||||||
|
text = util_ui.tr('Toxygen is Tox client written on Python.\nVersion: ')
|
||||||
|
text += '' + '\nGitHub: https://github.com/toxygen-project/toxygen/'
|
||||||
|
title = util_ui.tr('About')
|
||||||
|
util_ui.message_box(text, title)
|
||||||
|
|
||||||
|
def network_settings(self):
|
||||||
|
self._modal_window = self._widget_factory.create_network_settings_window()
|
||||||
|
self._modal_window.show()
|
||||||
|
|
||||||
|
def plugins_menu(self):
|
||||||
|
self._modal_window = self._widget_factory.create_plugins_settings_window()
|
||||||
|
self._modal_window.show()
|
||||||
|
|
||||||
|
def add_contact_triggered(self, _):
|
||||||
|
self.add_contact()
|
||||||
|
|
||||||
|
def add_contact(self, link=''):
|
||||||
|
self._modal_window = self._widget_factory.create_add_contact_window(link)
|
||||||
|
self._modal_window.show()
|
||||||
|
|
||||||
|
def create_gc(self):
|
||||||
|
self._modal_window = self._widget_factory.create_group_screen_window()
|
||||||
|
self._modal_window.show()
|
||||||
|
|
||||||
|
def join_gc(self):
|
||||||
|
self._modal_window = self._widget_factory.create_join_group_screen_window()
|
||||||
|
self._modal_window.show()
|
||||||
|
|
||||||
|
def profile_settings(self, _):
|
||||||
|
self._modal_window = self._widget_factory.create_profile_settings_window()
|
||||||
|
self._modal_window.show()
|
||||||
|
|
||||||
|
def privacy_settings(self):
|
||||||
|
self._modal_window = self._widget_factory.create_privacy_settings_window()
|
||||||
|
self._modal_window.show()
|
||||||
|
|
||||||
|
def notification_settings(self):
|
||||||
|
self._modal_window = self._widget_factory.create_notification_settings_window()
|
||||||
|
self._modal_window.show()
|
||||||
|
|
||||||
|
def interface_settings(self):
|
||||||
|
self._modal_window = self._widget_factory.create_interface_settings_window()
|
||||||
|
self._modal_window.show()
|
||||||
|
|
||||||
|
def audio_settings(self):
|
||||||
|
self._modal_window = self._widget_factory.create_audio_settings_window()
|
||||||
|
self._modal_window.show()
|
||||||
|
|
||||||
|
def video_settings(self):
|
||||||
|
self._modal_window = self._widget_factory.create_video_settings_window()
|
||||||
|
self._modal_window.show()
|
||||||
|
|
||||||
|
def update_settings(self):
|
||||||
|
self._modal_window = self._widget_factory.create_update_settings_window()
|
||||||
|
self._modal_window.show()
|
||||||
|
|
||||||
|
def reload_plugins(self):
|
||||||
|
if self._plugin_loader is not None:
|
||||||
|
self._plugin_loader.reload()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def import_plugin():
|
||||||
|
directory = util_ui.directory_dialog(util_ui.tr('Choose folder with plugin'))
|
||||||
|
if directory:
|
||||||
|
src = directory + '/'
|
||||||
|
dest = util.get_plugins_directory()
|
||||||
|
util.copy(src, dest)
|
||||||
|
util_ui.message_box(util_ui.tr('Plugin will be loaded after restart'), util_ui.tr("Restart Toxygen"))
|
||||||
|
|
||||||
|
def lock_app(self):
|
||||||
|
if self._toxes.has_password():
|
||||||
|
self._settings.locked = True
|
||||||
|
self.hide()
|
||||||
|
else:
|
||||||
|
util_ui.message_box(util_ui.tr('Error. Profile password is not set.'), util_ui.tr("Cannot lock app"))
|
||||||
|
|
||||||
|
def show_menu(self):
|
||||||
|
if not hasattr(self, 'menu'):
|
||||||
|
self.menu = DropdownMenu(self)
|
||||||
|
self.menu.setGeometry(QtCore.QRect(0 if self._settings['mirror_mode'] else 270,
|
||||||
|
self.height() - 120,
|
||||||
|
180,
|
||||||
|
120))
|
||||||
|
self.menu.show()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Messages, calls and file transfers
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def send_message(self):
|
||||||
|
self._messenger.send_message()
|
||||||
|
|
||||||
|
def send_file(self):
|
||||||
|
self.menu.hide()
|
||||||
|
if self._contacts_manager.is_active_a_friend():
|
||||||
|
caption = util_ui.tr('Choose file')
|
||||||
|
name = util_ui.file_dialog(caption)
|
||||||
|
if name[0]:
|
||||||
|
self._file_transfer_handler.send_file(name[0], self._contacts_manager.get_active_number())
|
||||||
|
|
||||||
|
def send_screenshot(self, hide=False):
|
||||||
|
self.menu.hide()
|
||||||
|
if self._contacts_manager.is_active_a_friend():
|
||||||
|
self.sw = self._widget_factory.create_screenshot_window(self)
|
||||||
|
self.sw.show()
|
||||||
|
if hide:
|
||||||
|
self.hide()
|
||||||
|
|
||||||
|
def send_smiley(self):
|
||||||
|
self.menu.hide()
|
||||||
|
if self._contacts_manager.get_curr_contact() is None:
|
||||||
|
return
|
||||||
|
self._smiley_window = self._widget_factory.create_smiley_window(self)
|
||||||
|
rect = QtCore.QRect(self.menu.x(),
|
||||||
|
self.menu.y() - self.menu.height(),
|
||||||
|
self._smiley_window.width(),
|
||||||
|
self._smiley_window.height())
|
||||||
|
self._smiley_window.setGeometry(rect)
|
||||||
|
self._smiley_window.show()
|
||||||
|
|
||||||
|
def send_sticker(self):
|
||||||
|
self.menu.hide()
|
||||||
|
if self._contacts_manager.is_active_a_friend():
|
||||||
|
self.sticker = self._widget_factory.create_sticker_window()
|
||||||
|
self.sticker.setGeometry(QtCore.QRect(self.x() if self._settings['mirror_mode'] else 270 + self.x(),
|
||||||
|
self.y() + self.height() - 200,
|
||||||
|
self.sticker.width(),
|
||||||
|
self.sticker.height()))
|
||||||
|
self.sticker.show()
|
||||||
|
|
||||||
|
def active_call(self):
|
||||||
|
self.update_call_state('finish_call')
|
||||||
|
|
||||||
|
def incoming_call(self):
|
||||||
|
self.update_call_state('incoming_call')
|
||||||
|
|
||||||
|
def call_finished(self):
|
||||||
|
self.update_call_state('call')
|
||||||
|
|
||||||
|
def update_call_state(self, state):
|
||||||
|
pixmap = QtGui.QPixmap(os.path.join(util.get_images_directory(), '{}.png'.format(state)))
|
||||||
|
icon = QtGui.QIcon(pixmap)
|
||||||
|
self.callButton.setIcon(icon)
|
||||||
|
self.callButton.setIconSize(QtCore.QSize(50, 50))
|
||||||
|
|
||||||
|
pixmap = QtGui.QPixmap(os.path.join(util.get_images_directory(), '{}_video.png'.format(state)))
|
||||||
|
icon = QtGui.QIcon(pixmap)
|
||||||
|
self.videocallButton.setIcon(icon)
|
||||||
|
self.videocallButton.setIconSize(QtCore.QSize(35, 35))
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Functions which called when user open context menu in friends list
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _friend_right_click(self, pos):
|
||||||
|
item = self.friends_list.itemAt(pos)
|
||||||
|
number = self.friends_list.indexFromItem(item).row()
|
||||||
|
contact = self._contacts_manager.get_contact(number)
|
||||||
|
if contact is None or item is None:
|
||||||
|
return
|
||||||
|
generator = contact.get_context_menu_generator()
|
||||||
|
self.listMenu = generator.generate(self._plugins_loader, self._contacts_manager, self, self._settings, number,
|
||||||
|
self._groups_service, self._history_loader)
|
||||||
|
parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0))
|
||||||
|
self.listMenu.move(parent_position + pos)
|
||||||
|
self.listMenu.show()
|
||||||
|
|
||||||
|
def show_note(self, friend):
|
||||||
|
note = self._settings['notes'][friend.tox_id] if friend.tox_id in self._settings['notes'] else ''
|
||||||
|
user = util_ui.tr('Notes about user')
|
||||||
|
user = '{} {}'.format(user, friend.name)
|
||||||
|
|
||||||
|
def save_note(text):
|
||||||
|
if friend.tox_id in self._settings['notes']:
|
||||||
|
del self._settings['notes'][friend.tox_id]
|
||||||
|
if text:
|
||||||
|
self._settings['notes'][friend.tox_id] = text
|
||||||
|
self._settings.save()
|
||||||
|
self.note = MultilineEdit(user, note, save_note)
|
||||||
|
self.note.show()
|
||||||
|
|
||||||
|
def set_alias(self, num):
|
||||||
|
self._contacts_manager.set_alias(num)
|
||||||
|
|
||||||
|
def remove_friend(self, num):
|
||||||
|
self._contacts_manager.delete_friend(num)
|
||||||
|
|
||||||
|
def block_friend(self, num):
|
||||||
|
friend = self._contacts_manager.get_contact(num)
|
||||||
|
self._contacts_manager.block_user(friend.tox_id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def copy_text(text):
|
||||||
|
util_ui.copy_to_clipboard(text)
|
||||||
|
|
||||||
|
def auto_accept(self, num, value):
|
||||||
|
tox_id = self._contacts_manager.friend_public_key(num)
|
||||||
|
if value:
|
||||||
|
self._settings['auto_accept_from_friends'].append(tox_id)
|
||||||
|
else:
|
||||||
|
self._settings['auto_accept_from_friends'].remove(tox_id)
|
||||||
|
self._settings.save()
|
||||||
|
|
||||||
|
def invite_friend_to_gc(self, friend_number, group_number):
|
||||||
|
self._contacts_manager.invite_friend(friend_number, group_number)
|
||||||
|
|
||||||
|
def select_contact_row(self, row_index):
|
||||||
|
self.friends_list.setCurrentRow(row_index)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Functions which called when user click somewhere else
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _selected_contact_changed(self):
|
||||||
|
num = self.friends_list.currentRow()
|
||||||
|
if self._contacts_manager.active_contact != num:
|
||||||
|
self._contacts_manager.active_contact = num
|
||||||
|
self.groupMenuButton.setVisible(self._contacts_manager.is_active_a_group())
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, event):
|
||||||
|
pos = self.connection_status.pos()
|
||||||
|
x, y = pos.x(), pos.y() + 25
|
||||||
|
if (x < event.x() < x + 32) and (y < event.y() < y + 32):
|
||||||
|
self._profile.change_status()
|
||||||
|
else:
|
||||||
|
super().mouseReleaseEvent(event)
|
||||||
|
|
||||||
|
def _filtering(self):
|
||||||
|
index = self.contactsFilterComboBox.currentIndex()
|
||||||
|
search_text = self.searchLineEdit.text()
|
||||||
|
self._contacts_manager.filtration_and_sorting(index, search_text)
|
||||||
|
|
||||||
|
def show_search_field(self):
|
||||||
|
if hasattr(self, 'search_field') and self.search_field.isVisible():
|
||||||
|
return
|
||||||
|
if self._contacts_manager.get_curr_friend() is None:
|
||||||
|
return
|
||||||
|
self.search_field = self._widget_factory.create_search_screen(self.messages)
|
||||||
|
x, y = self.messages.x(), self.messages.y() + self.messages.height() - 40
|
||||||
|
self.search_field.setGeometry(x, y, self.messages.width(), 40)
|
||||||
|
self.messages.setGeometry(x, self.messages.y(), self.messages.width(), self.messages.height() - 40)
|
||||||
|
if self._should_show_group_peers_list:
|
||||||
|
self.peers_list.setFixedHeight(self.peers_list.height() - 40)
|
||||||
|
self.search_field.show()
|
||||||
|
|
||||||
|
def _toggle_gc_peers_list(self):
|
||||||
|
self._should_show_group_peers_list = not self._should_show_group_peers_list
|
||||||
|
self.resizeEvent()
|
||||||
|
if self._should_show_group_peers_list:
|
||||||
|
self._groups_service.generate_peers_list()
|
||||||
|
|
||||||
|
def _new_contact_selected(self, _):
|
||||||
|
if self._should_show_group_peers_list:
|
||||||
|
self._toggle_gc_peers_list()
|
||||||
|
index = self.friends_list.currentRow()
|
||||||
|
if self._contacts_manager.active_contact != index:
|
||||||
|
self.friends_list.setCurrentRow(self._contacts_manager.active_contact)
|
||||||
|
self.resizeEvent()
|
||||||
|
|
||||||
|
def _open_gc_invites_list(self):
|
||||||
|
self._modal_window = self._widget_factory.create_group_invites_window()
|
||||||
|
self._modal_window.show()
|
||||||
|
|
||||||
|
def update_gc_invites_button_state(self):
|
||||||
|
invites_count = self._groups_service.group_invites_count
|
||||||
|
self.groupInvitesPushButton.setVisible(invites_count > 0)
|
||||||
|
text = util_ui.tr('{} new invites to group chats').format(invites_count)
|
||||||
|
self.groupInvitesPushButton.setText(text)
|
||||||
|
self.resizeEvent()
|
@ -1,20 +1,27 @@
|
|||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
from widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit
|
from ui.widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit
|
||||||
from profile import Profile
|
import urllib
|
||||||
import smileys
|
import re
|
||||||
import util
|
import utils.util as util
|
||||||
import platform
|
import utils.ui as util_ui
|
||||||
|
from stickers.stickers import load_stickers
|
||||||
|
|
||||||
|
|
||||||
class MessageArea(QtWidgets.QPlainTextEdit):
|
class MessageArea(QtWidgets.QPlainTextEdit):
|
||||||
"""User types messages here"""
|
"""User types messages here"""
|
||||||
|
|
||||||
def __init__(self, parent, form):
|
def __init__(self, parent, form):
|
||||||
super(MessageArea, self).__init__(parent)
|
super().__init__(parent)
|
||||||
|
self._messenger = self._contacts_manager = self._file_transfer_handler = None
|
||||||
self.parent = form
|
self.parent = form
|
||||||
self.setAcceptDrops(True)
|
self.setAcceptDrops(True)
|
||||||
self.timer = QtCore.QTimer(self)
|
self._timer = QtCore.QTimer(self)
|
||||||
self.timer.timeout.connect(lambda: self.parent.profile.send_typing(False))
|
self._timer.timeout.connect(lambda: self._messenger.send_typing(False))
|
||||||
|
|
||||||
|
def set_dependencies(self, messenger, contacts_manager, file_transfer_handler):
|
||||||
|
self._messenger = messenger
|
||||||
|
self._contacts_manager = contacts_manager
|
||||||
|
self._file_transfer_handler = file_transfer_handler
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
if event.matches(QtGui.QKeySequence.Paste):
|
if event.matches(QtGui.QKeySequence.Paste):
|
||||||
@ -29,22 +36,29 @@ class MessageArea(QtWidgets.QPlainTextEdit):
|
|||||||
if modifiers & QtCore.Qt.ControlModifier or modifiers & QtCore.Qt.ShiftModifier:
|
if modifiers & QtCore.Qt.ControlModifier or modifiers & QtCore.Qt.ShiftModifier:
|
||||||
self.insertPlainText('\n')
|
self.insertPlainText('\n')
|
||||||
else:
|
else:
|
||||||
if self.timer.isActive():
|
if self._timer.isActive():
|
||||||
self.timer.stop()
|
self._timer.stop()
|
||||||
self.parent.profile.send_typing(False)
|
self._messenger.send_typing(False)
|
||||||
self.parent.send_message()
|
self._messenger.send_message()
|
||||||
elif event.key() == QtCore.Qt.Key_Up and not self.toPlainText():
|
elif event.key() == QtCore.Qt.Key_Up and not self.toPlainText():
|
||||||
self.appendPlainText(Profile.get_instance().get_last_message())
|
self.appendPlainText(self._messenger.get_last_message())
|
||||||
elif event.key() == QtCore.Qt.Key_Tab and not self.parent.profile.is_active_a_friend():
|
elif event.key() == QtCore.Qt.Key_Tab and self._contacts_manager.is_active_a_group():
|
||||||
text = self.toPlainText()
|
text = self.toPlainText()
|
||||||
pos = self.textCursor().position()
|
text_cursor = self.textCursor()
|
||||||
self.insertPlainText(Profile.get_instance().get_gc_peer_name(text[:pos]))
|
pos = text_cursor.position()
|
||||||
|
current_word = re.split("\s+", text[:pos])[-1]
|
||||||
|
start_index = text.rindex(current_word, 0, pos)
|
||||||
|
peer_name = self._contacts_manager.get_gc_peer_name(current_word)
|
||||||
|
self.setPlainText(text[:start_index] + peer_name + text[pos:])
|
||||||
|
new_pos = start_index + len(peer_name)
|
||||||
|
text_cursor.setPosition(new_pos, QtGui.QTextCursor.MoveAnchor)
|
||||||
|
self.setTextCursor(text_cursor)
|
||||||
else:
|
else:
|
||||||
self.parent.profile.send_typing(True)
|
self._messenger.send_typing(True)
|
||||||
if self.timer.isActive():
|
if self._timer.isActive():
|
||||||
self.timer.stop()
|
self._timer.stop()
|
||||||
self.timer.start(5000)
|
self._timer.start(5000)
|
||||||
super(MessageArea, self).keyPressEvent(event)
|
super().keyPressEvent(event)
|
||||||
|
|
||||||
def contextMenuEvent(self, event):
|
def contextMenuEvent(self, event):
|
||||||
menu = create_menu(self.createStandardContextMenu())
|
menu = create_menu(self.createStandardContextMenu())
|
||||||
@ -71,29 +85,30 @@ class MessageArea(QtWidgets.QPlainTextEdit):
|
|||||||
def pasteEvent(self, text=None):
|
def pasteEvent(self, text=None):
|
||||||
text = text or QtWidgets.QApplication.clipboard().text()
|
text = text or QtWidgets.QApplication.clipboard().text()
|
||||||
if text.startswith('file://'):
|
if text.startswith('file://'):
|
||||||
file_name = self.parse_file_name(text)
|
if not self._contacts_manager.is_active_a_friend():
|
||||||
self.parent.profile.send_file(file_name)
|
return
|
||||||
elif text:
|
friend_number = self._contacts_manager.get_active_number()
|
||||||
self.insertPlainText(text)
|
file_path = self._parse_file_path(text)
|
||||||
|
self._file_transfer_handler.send_file(file_path, friend_number)
|
||||||
else:
|
else:
|
||||||
image = QtWidgets.QApplication.clipboard().image()
|
self.insertPlainText(text)
|
||||||
if image is not None:
|
|
||||||
byte_array = QtCore.QByteArray()
|
|
||||||
buffer = QtCore.QBuffer(byte_array)
|
|
||||||
buffer.open(QtCore.QIODevice.WriteOnly)
|
|
||||||
image.save(buffer, 'PNG')
|
|
||||||
self.parent.profile.send_screenshot(bytes(byte_array.data()))
|
|
||||||
|
|
||||||
def parse_file_name(self, file_name):
|
@staticmethod
|
||||||
import urllib
|
def _parse_file_path(file_name):
|
||||||
if file_name.endswith('\r\n'):
|
if file_name.endswith('\r\n'):
|
||||||
file_name = file_name[:-2]
|
file_name = file_name[:-2]
|
||||||
file_name = urllib.parse.unquote(file_name)
|
file_name = urllib.parse.unquote(file_name)
|
||||||
return file_name[8 if platform.system() == 'Windows' else 7:]
|
|
||||||
|
return file_name[8 if util.get_platform() == 'Windows' else 7:]
|
||||||
|
|
||||||
|
|
||||||
class ScreenShotWindow(RubberBandWindow):
|
class ScreenShotWindow(RubberBandWindow):
|
||||||
|
|
||||||
|
def __init__(self, file_transfer_handler, contacts_manager, *args):
|
||||||
|
super().__init__(*args)
|
||||||
|
self._file_transfer_handler = file_transfer_handler
|
||||||
|
self._contacts_manager = contacts_manager
|
||||||
|
|
||||||
def closeEvent(self, *args):
|
def closeEvent(self, *args):
|
||||||
if self.parent.isHidden():
|
if self.parent.isHidden():
|
||||||
self.parent.show()
|
self.parent.show()
|
||||||
@ -113,7 +128,8 @@ class ScreenShotWindow(RubberBandWindow):
|
|||||||
buffer = QtCore.QBuffer(byte_array)
|
buffer = QtCore.QBuffer(byte_array)
|
||||||
buffer.open(QtCore.QIODevice.WriteOnly)
|
buffer.open(QtCore.QIODevice.WriteOnly)
|
||||||
p.save(buffer, 'PNG')
|
p.save(buffer, 'PNG')
|
||||||
Profile.get_instance().send_screenshot(bytes(byte_array.data()))
|
friend = self._contacts_manager.get_curr_contact()
|
||||||
|
self._file_transfer_handler.send_screenshot(bytes(byte_array.data()), friend.number)
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
@ -122,77 +138,81 @@ class SmileyWindow(QtWidgets.QWidget):
|
|||||||
Smiley selection window
|
Smiley selection window
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent, smiley_loader):
|
||||||
super(SmileyWindow, self).__init__()
|
super().__init__(parent)
|
||||||
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
|
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
|
||||||
inst = smileys.SmileyLoader.get_instance()
|
self._parent = parent
|
||||||
self.data = inst.get_smileys()
|
self._data = smiley_loader.get_smileys()
|
||||||
count = len(self.data)
|
|
||||||
|
count = len(self._data)
|
||||||
if not count:
|
if not count:
|
||||||
self.close()
|
self.close()
|
||||||
self.page_size = int(pow(count / 8, 0.5) + 1) * 8 # smileys per page
|
|
||||||
if count % self.page_size == 0:
|
self._page_size = int(pow(count / 8, 0.5) + 1) * 8 # smileys per page
|
||||||
self.page_count = count // self.page_size
|
if count % self._page_size == 0:
|
||||||
|
self._page_count = count // self._page_size
|
||||||
else:
|
else:
|
||||||
self.page_count = round(count / self.page_size + 0.5)
|
self._page_count = round(count / self._page_size + 0.5)
|
||||||
self.page = -1
|
self._page = -1
|
||||||
self.radio = []
|
self._radio = []
|
||||||
self.parent = parent
|
|
||||||
for i in range(self.page_count): # buttons with smileys
|
for i in range(self._page_count): # pages - radio buttons
|
||||||
elem = QtWidgets.QRadioButton(self)
|
elem = QtWidgets.QRadioButton(self)
|
||||||
elem.setGeometry(QtCore.QRect(i * 20 + 5, 180, 20, 20))
|
elem.setGeometry(QtCore.QRect(i * 20 + 5, 160, 20, 20))
|
||||||
elem.clicked.connect(lambda c, t=i: self.checked(t))
|
elem.clicked.connect(lambda c, t=i: self._checked(t))
|
||||||
self.radio.append(elem)
|
self._radio.append(elem)
|
||||||
width = max(self.page_count * 20 + 30, (self.page_size + 5) * 8 // 10)
|
|
||||||
|
width = max(self._page_count * 20 + 30, (self._page_size + 5) * 8 // 10)
|
||||||
self.setMaximumSize(width, 200)
|
self.setMaximumSize(width, 200)
|
||||||
self.setMinimumSize(width, 200)
|
self.setMinimumSize(width, 200)
|
||||||
self.buttons = []
|
self._buttons = []
|
||||||
for i in range(self.page_size): # pages - radio buttons
|
|
||||||
|
for i in range(self._page_size): # buttons with smileys
|
||||||
b = QtWidgets.QPushButton(self)
|
b = QtWidgets.QPushButton(self)
|
||||||
b.setGeometry(QtCore.QRect((i // 8) * 20 + 5, (i % 8) * 20, 20, 20))
|
b.setGeometry(QtCore.QRect((i // 8) * 20 + 5, (i % 8) * 20, 20, 20))
|
||||||
b.clicked.connect(lambda c, t=i: self.clicked(t))
|
b.clicked.connect(lambda c, t=i: self._clicked(t))
|
||||||
self.buttons.append(b)
|
self._buttons.append(b)
|
||||||
self.checked(0)
|
self._checked(0)
|
||||||
|
|
||||||
def checked(self, pos): # new page opened
|
|
||||||
self.radio[self.page].setChecked(False)
|
|
||||||
self.radio[pos].setChecked(True)
|
|
||||||
self.page = pos
|
|
||||||
start = self.page * self.page_size
|
|
||||||
for i in range(self.page_size):
|
|
||||||
try:
|
|
||||||
self.buttons[i].setVisible(True)
|
|
||||||
pixmap = QtGui.QPixmap(self.data[start + i][1])
|
|
||||||
icon = QtGui.QIcon(pixmap)
|
|
||||||
self.buttons[i].setIcon(icon)
|
|
||||||
except:
|
|
||||||
self.buttons[i].setVisible(False)
|
|
||||||
|
|
||||||
def clicked(self, pos): # smiley selected
|
|
||||||
pos += self.page * self.page_size
|
|
||||||
smiley = self.data[pos][0]
|
|
||||||
self.parent.messageEdit.insertPlainText(smiley)
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def leaveEvent(self, event):
|
def leaveEvent(self, event):
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
def _checked(self, pos): # new page opened
|
||||||
|
self._radio[self._page].setChecked(False)
|
||||||
|
self._radio[pos].setChecked(True)
|
||||||
|
self._page = pos
|
||||||
|
start = self._page * self._page_size
|
||||||
|
for i in range(self._page_size):
|
||||||
|
try:
|
||||||
|
self._buttons[i].setVisible(True)
|
||||||
|
pixmap = QtGui.QPixmap(self._data[start + i][1])
|
||||||
|
icon = QtGui.QIcon(pixmap)
|
||||||
|
self._buttons[i].setIcon(icon)
|
||||||
|
except:
|
||||||
|
self._buttons[i].setVisible(False)
|
||||||
|
|
||||||
|
def _clicked(self, pos): # smiley selected
|
||||||
|
pos += self._page * self._page_size
|
||||||
|
smiley = self._data[pos][0]
|
||||||
|
self._parent.messageEdit.insertPlainText(smiley)
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
class MenuButton(QtWidgets.QPushButton):
|
class MenuButton(QtWidgets.QPushButton):
|
||||||
|
|
||||||
def __init__(self, parent, enter):
|
def __init__(self, parent, enter):
|
||||||
super(MenuButton, self).__init__(parent)
|
super().__init__(parent)
|
||||||
self.enter = enter
|
self.enter = enter
|
||||||
|
|
||||||
def enterEvent(self, event):
|
def enterEvent(self, event):
|
||||||
self.enter()
|
self.enter()
|
||||||
super(MenuButton, self).enterEvent(event)
|
super().enterEvent(event)
|
||||||
|
|
||||||
|
|
||||||
class DropdownMenu(QtWidgets.QWidget):
|
class DropdownMenu(QtWidgets.QWidget):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super(DropdownMenu, self).__init__(parent)
|
super().__init__(parent)
|
||||||
self.installEventFilter(self)
|
self.installEventFilter(self)
|
||||||
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
|
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
|
||||||
self.setMaximumSize(120, 120)
|
self.setMaximumSize(120, 120)
|
||||||
@ -211,30 +231,30 @@ class DropdownMenu(QtWidgets.QWidget):
|
|||||||
self.stickerButton = QtWidgets.QPushButton(self)
|
self.stickerButton = QtWidgets.QPushButton(self)
|
||||||
self.stickerButton.setGeometry(QtCore.QRect(60, 0, 60, 60))
|
self.stickerButton.setGeometry(QtCore.QRect(60, 0, 60, 60))
|
||||||
|
|
||||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/file.png')
|
pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'file.png'))
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
self.fileTransferButton.setIcon(icon)
|
self.fileTransferButton.setIcon(icon)
|
||||||
self.fileTransferButton.setIconSize(QtCore.QSize(50, 50))
|
self.fileTransferButton.setIconSize(QtCore.QSize(50, 50))
|
||||||
|
|
||||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/screenshot.png')
|
pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'screenshot.png'))
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
self.screenshotButton.setIcon(icon)
|
self.screenshotButton.setIcon(icon)
|
||||||
self.screenshotButton.setIconSize(QtCore.QSize(50, 60))
|
self.screenshotButton.setIconSize(QtCore.QSize(50, 60))
|
||||||
|
|
||||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/smiley.png')
|
pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'smiley.png'))
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
self.smileyButton.setIcon(icon)
|
self.smileyButton.setIcon(icon)
|
||||||
self.smileyButton.setIconSize(QtCore.QSize(50, 50))
|
self.smileyButton.setIconSize(QtCore.QSize(50, 50))
|
||||||
|
|
||||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/sticker.png')
|
pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'sticker.png'))
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
self.stickerButton.setIcon(icon)
|
self.stickerButton.setIcon(icon)
|
||||||
self.stickerButton.setIconSize(QtCore.QSize(55, 55))
|
self.stickerButton.setIconSize(QtCore.QSize(55, 55))
|
||||||
|
|
||||||
self.screenshotButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send screenshot"))
|
self.screenshotButton.setToolTip(util_ui.tr("Send screenshot"))
|
||||||
self.fileTransferButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send file"))
|
self.fileTransferButton.setToolTip(util_ui.tr("Send file"))
|
||||||
self.smileyButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Add smiley"))
|
self.smileyButton.setToolTip(util_ui.tr("Add smiley"))
|
||||||
self.stickerButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send sticker"))
|
self.stickerButton.setToolTip(util_ui.tr("Send sticker"))
|
||||||
|
|
||||||
self.fileTransferButton.clicked.connect(parent.send_file)
|
self.fileTransferButton.clicked.connect(parent.send_file)
|
||||||
self.screenshotButton.clicked.connect(parent.send_screenshot)
|
self.screenshotButton.clicked.connect(parent.send_screenshot)
|
||||||
@ -254,7 +274,7 @@ class DropdownMenu(QtWidgets.QWidget):
|
|||||||
class StickerItem(QtWidgets.QWidget):
|
class StickerItem(QtWidgets.QWidget):
|
||||||
|
|
||||||
def __init__(self, fl):
|
def __init__(self, fl):
|
||||||
super(StickerItem, self).__init__()
|
super().__init__()
|
||||||
self._image_label = QtWidgets.QLabel(self)
|
self._image_label = QtWidgets.QLabel(self)
|
||||||
self.path = fl
|
self.path = fl
|
||||||
self.pixmap = QtGui.QPixmap()
|
self.pixmap = QtGui.QPixmap()
|
||||||
@ -268,15 +288,17 @@ class StickerItem(QtWidgets.QWidget):
|
|||||||
class StickerWindow(QtWidgets.QWidget):
|
class StickerWindow(QtWidgets.QWidget):
|
||||||
"""Sticker selection window"""
|
"""Sticker selection window"""
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, file_transfer_handler, contacts_manager):
|
||||||
super(StickerWindow, self).__init__()
|
super().__init__()
|
||||||
|
self._file_transfer_handler = file_transfer_handler
|
||||||
|
self._contacts_manager = contacts_manager
|
||||||
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
|
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
|
||||||
self.setMaximumSize(250, 200)
|
self.setMaximumSize(250, 200)
|
||||||
self.setMinimumSize(250, 200)
|
self.setMinimumSize(250, 200)
|
||||||
self.list = QtWidgets.QListWidget(self)
|
self.list = QtWidgets.QListWidget(self)
|
||||||
self.list.setGeometry(QtCore.QRect(0, 0, 250, 200))
|
self.list.setGeometry(QtCore.QRect(0, 0, 250, 200))
|
||||||
self.arr = smileys.sticker_loader()
|
self._stickers = load_stickers()
|
||||||
for sticker in self.arr:
|
for sticker in self._stickers:
|
||||||
item = StickerItem(sticker)
|
item = StickerItem(sticker)
|
||||||
elem = QtWidgets.QListWidgetItem()
|
elem = QtWidgets.QListWidgetItem()
|
||||||
elem.setSizeHint(QtCore.QSize(250, item.height()))
|
elem.setSizeHint(QtCore.QSize(250, item.height()))
|
||||||
@ -285,11 +307,11 @@ class StickerWindow(QtWidgets.QWidget):
|
|||||||
self.list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
self.list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||||
self.list.setSpacing(3)
|
self.list.setSpacing(3)
|
||||||
self.list.clicked.connect(self.click)
|
self.list.clicked.connect(self.click)
|
||||||
self.parent = parent
|
|
||||||
|
|
||||||
def click(self, index):
|
def click(self, index):
|
||||||
num = index.row()
|
num = index.row()
|
||||||
self.parent.profile.send_sticker(self.arr[num])
|
friend = self._contacts_manager.get_curr_contact()
|
||||||
|
self._file_transfer_handler.send_sticker(self._stickers[num], friend.number)
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def leaveEvent(self, event):
|
def leaveEvent(self, event):
|
||||||
@ -298,8 +320,9 @@ class StickerWindow(QtWidgets.QWidget):
|
|||||||
|
|
||||||
class WelcomeScreen(CenteredWidget):
|
class WelcomeScreen(CenteredWidget):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, settings):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self._settings = settings
|
||||||
self.setMaximumSize(250, 200)
|
self.setMaximumSize(250, 200)
|
||||||
self.setMinimumSize(250, 200)
|
self.setMinimumSize(250, 200)
|
||||||
self.center()
|
self.center()
|
||||||
@ -309,51 +332,39 @@ class WelcomeScreen(CenteredWidget):
|
|||||||
self.text.setOpenExternalLinks(True)
|
self.text.setOpenExternalLinks(True)
|
||||||
self.checkbox = QtWidgets.QCheckBox(self)
|
self.checkbox = QtWidgets.QCheckBox(self)
|
||||||
self.checkbox.setGeometry(QtCore.QRect(5, 170, 240, 30))
|
self.checkbox.setGeometry(QtCore.QRect(5, 170, 240, 30))
|
||||||
self.checkbox.setText(QtWidgets.QApplication.translate('WelcomeScreen', "Don't show again"))
|
self.checkbox.setText(util_ui.tr( "Don't show again"))
|
||||||
self.setWindowTitle(QtWidgets.QApplication.translate('WelcomeScreen', 'Tip of the day'))
|
self.setWindowTitle(util_ui.tr( 'Tip of the day'))
|
||||||
import random
|
import random
|
||||||
num = random.randint(0, 10)
|
num = random.randint(0, 10)
|
||||||
if num == 0:
|
if num == 0:
|
||||||
text = QtWidgets.QApplication.translate('WelcomeScreen', 'Press Esc if you want hide app to tray.')
|
text = util_ui.tr('Press Esc if you want hide app to tray.')
|
||||||
elif num == 1:
|
elif num == 1:
|
||||||
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
text = util_ui.tr('Right click on screenshot button hides app to tray during screenshot.')
|
||||||
'Right click on screenshot button hides app to tray during screenshot.')
|
|
||||||
elif num == 2:
|
elif num == 2:
|
||||||
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
text = util_ui.tr('You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a>')
|
||||||
'You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a>')
|
|
||||||
elif num == 3:
|
elif num == 3:
|
||||||
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
text = util_ui.tr('Use Settings -> Interface to customize interface.')
|
||||||
'Use Settings -> Interface to customize interface.')
|
|
||||||
elif num == 4:
|
elif num == 4:
|
||||||
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
text = util_ui.tr('Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.')
|
||||||
'Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.')
|
|
||||||
elif num == 5:
|
elif num == 5:
|
||||||
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
text = util_ui.tr('Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a>')
|
||||||
'Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a>')
|
|
||||||
elif num == 6:
|
elif num == 6:
|
||||||
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
text = util_ui.tr('Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.')
|
||||||
'Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.')
|
|
||||||
elif num == 7:
|
elif num == 7:
|
||||||
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
text = util_ui.tr('New in Toxygen 0.4.1:<br>Downloading nodes from tox.chat<br>Bug fixes')
|
||||||
'New in Toxygen 0.4.1:<br>Downloading nodes from tox.chat<br>Bug fixes')
|
|
||||||
elif num == 8:
|
elif num == 8:
|
||||||
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
text = util_ui.tr('Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu')
|
||||||
'Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu')
|
|
||||||
elif num == 9:
|
elif num == 9:
|
||||||
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
text = util_ui.tr( 'Use right click on inline image to save it')
|
||||||
'Use right click on inline image to save it')
|
|
||||||
else:
|
else:
|
||||||
text = QtWidgets.QApplication.translate('WelcomeScreen',
|
text = util_ui.tr('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.')
|
|
||||||
self.text.setHtml(text)
|
self.text.setHtml(text)
|
||||||
self.checkbox.stateChanged.connect(self.not_show)
|
self.checkbox.stateChanged.connect(self.not_show)
|
||||||
QtCore.QTimer.singleShot(1000, self.show)
|
QtCore.QTimer.singleShot(1000, self.show)
|
||||||
|
|
||||||
def not_show(self):
|
def not_show(self):
|
||||||
import settings
|
self._settings['show_welcome_screen'] = False
|
||||||
s = settings.Settings.get_instance()
|
self._settings.save()
|
||||||
s['show_welcome_screen'] = False
|
|
||||||
s.save()
|
|
||||||
|
|
||||||
|
|
||||||
class MainMenuButton(QtWidgets.QPushButton):
|
class MainMenuButton(QtWidgets.QPushButton):
|
||||||
@ -381,8 +392,10 @@ class ClickableLabel(QtWidgets.QLabel):
|
|||||||
|
|
||||||
class SearchScreen(QtWidgets.QWidget):
|
class SearchScreen(QtWidgets.QWidget):
|
||||||
|
|
||||||
def __init__(self, messages, width, *args):
|
def __init__(self, contacts_manager, history_loader, messages, width, *args):
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
|
self._contacts_manager = contacts_manager
|
||||||
|
self._history_loader = history_loader
|
||||||
self.setMaximumSize(width, 40)
|
self.setMaximumSize(width, 40)
|
||||||
self.setMinimumSize(width, 40)
|
self.setMinimumSize(width, 40)
|
||||||
self._messages = messages
|
self._messages = messages
|
||||||
@ -393,7 +406,7 @@ class SearchScreen(QtWidgets.QWidget):
|
|||||||
self.search_button = ClickableLabel(self)
|
self.search_button = ClickableLabel(self)
|
||||||
self.search_button.setGeometry(width - 160, 0, 40, 40)
|
self.search_button.setGeometry(width - 160, 0, 40, 40)
|
||||||
pixmap = QtGui.QPixmap()
|
pixmap = QtGui.QPixmap()
|
||||||
pixmap.load(util.curr_directory() + '/images/search.png')
|
pixmap.load(util.join_path(util.get_images_directory(), 'search.png'))
|
||||||
self.search_button.setScaledContents(False)
|
self.search_button.setScaledContents(False)
|
||||||
self.search_button.setAlignment(QtCore.Qt.AlignCenter)
|
self.search_button.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
self.search_button.setPixmap(pixmap)
|
self.search_button.setPixmap(pixmap)
|
||||||
@ -426,31 +439,31 @@ class SearchScreen(QtWidgets.QWidget):
|
|||||||
self.retranslateUi()
|
self.retranslateUi()
|
||||||
|
|
||||||
def retranslateUi(self):
|
def retranslateUi(self):
|
||||||
self.search_text.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "Search"))
|
self.search_text.setPlaceholderText(util_ui.tr('Search'))
|
||||||
|
|
||||||
def show(self):
|
def show(self):
|
||||||
super().show()
|
super().show()
|
||||||
self.search_text.setFocus()
|
self.search_text.setFocus()
|
||||||
|
|
||||||
def search(self):
|
def search(self):
|
||||||
Profile.get_instance().update()
|
self._contacts_manager.update()
|
||||||
text = self.search_text.text()
|
text = self.search_text.text()
|
||||||
friend = Profile.get_instance().get_curr_friend()
|
contact = self._contacts_manager.get_curr_contact()
|
||||||
if text and friend and util.is_re_valid(text):
|
if text and contact and util.is_re_valid(text):
|
||||||
index = friend.search_string(text)
|
index = contact.search_string(text)
|
||||||
self.load_messages(index)
|
self.load_messages(index)
|
||||||
|
|
||||||
def prev(self):
|
def prev(self):
|
||||||
friend = Profile.get_instance().get_curr_friend()
|
contact = self._contacts_manager.get_curr_contact()
|
||||||
if friend is not None:
|
if contact is not None:
|
||||||
index = friend.search_prev()
|
index = contact.search_prev()
|
||||||
self.load_messages(index)
|
self.load_messages(index)
|
||||||
|
|
||||||
def next(self):
|
def next(self):
|
||||||
friend = Profile.get_instance().get_curr_friend()
|
contact = self._contacts_manager.get_curr_contact()
|
||||||
text = self.search_text.text()
|
text = self.search_text.text()
|
||||||
if friend is not None:
|
if contact is not None:
|
||||||
index = friend.search_next()
|
index = contact.search_next()
|
||||||
if index is not None:
|
if index is not None:
|
||||||
count = self._messages.count()
|
count = self._messages.count()
|
||||||
index += count
|
index += count
|
||||||
@ -463,10 +476,9 @@ class SearchScreen(QtWidgets.QWidget):
|
|||||||
def load_messages(self, index):
|
def load_messages(self, index):
|
||||||
text = self.search_text.text()
|
text = self.search_text.text()
|
||||||
if index is not None:
|
if index is not None:
|
||||||
profile = Profile.get_instance()
|
|
||||||
count = self._messages.count()
|
count = self._messages.count()
|
||||||
while count + index < 0:
|
while count + index < 0:
|
||||||
profile.load_history()
|
self._history_loader.load_history()
|
||||||
count = self._messages.count()
|
count = self._messages.count()
|
||||||
index += count
|
index += count
|
||||||
item = self._messages.item(index)
|
item = self._messages.item(index)
|
||||||
@ -476,17 +488,9 @@ class SearchScreen(QtWidgets.QWidget):
|
|||||||
self.not_found(text)
|
self.not_found(text)
|
||||||
|
|
||||||
def closeEvent(self, *args):
|
def closeEvent(self, *args):
|
||||||
Profile.get_instance().update()
|
|
||||||
self._messages.setGeometry(0, 0, self._messages.width(), self._messages.height() + 40)
|
self._messages.setGeometry(0, 0, self._messages.width(), self._messages.height() + 40)
|
||||||
super().closeEvent(*args)
|
super().closeEvent(*args)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def not_found(text):
|
def not_found(text):
|
||||||
mbox = QtWidgets.QMessageBox()
|
util_ui.message_box(util_ui.tr('Text "{}" was not found').format(text), util_ui.tr('Not found'))
|
||||||
mbox_text = QtWidgets.QApplication.translate("MainWindow",
|
|
||||||
'Text "{}" was not found')
|
|
||||||
|
|
||||||
mbox.setText(mbox_text.format(text))
|
|
||||||
mbox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow",
|
|
||||||
'Not found'))
|
|
||||||
mbox.exec_()
|
|
680
toxygen/ui/menu.py
Normal file
680
toxygen/ui/menu.py
Normal file
@ -0,0 +1,680 @@
|
|||||||
|
from PyQt5 import QtCore, QtGui, QtWidgets, uic
|
||||||
|
from user_data.settings import *
|
||||||
|
from utils.util import *
|
||||||
|
from ui.widgets import CenteredWidget, DataLabel, LineEdit, RubberBandWindow
|
||||||
|
import pyaudio
|
||||||
|
import updater.updater as updater
|
||||||
|
import utils.ui as util_ui
|
||||||
|
import cv2
|
||||||
|
|
||||||
|
|
||||||
|
class AddContact(CenteredWidget):
|
||||||
|
"""Add contact form"""
|
||||||
|
|
||||||
|
def __init__(self, settings, contacts_manager, tox_id=''):
|
||||||
|
super().__init__()
|
||||||
|
self._settings = settings
|
||||||
|
self._contacts_manager = contacts_manager
|
||||||
|
uic.loadUi(get_views_path('add_contact_screen'), self)
|
||||||
|
self._update_ui(tox_id)
|
||||||
|
self._adding = False
|
||||||
|
|
||||||
|
def _update_ui(self, tox_id):
|
||||||
|
self.toxIdLineEdit = LineEdit(self)
|
||||||
|
self.toxIdLineEdit.setGeometry(QtCore.QRect(50, 40, 460, 30))
|
||||||
|
self.toxIdLineEdit.setText(tox_id)
|
||||||
|
|
||||||
|
self.messagePlainTextEdit.document().setPlainText(util_ui.tr('Hello! Please add me to your contact list.'))
|
||||||
|
self.addContactPushButton.clicked.connect(self._add_friend)
|
||||||
|
self._retranslate_ui()
|
||||||
|
|
||||||
|
def _add_friend(self):
|
||||||
|
if self._adding:
|
||||||
|
return
|
||||||
|
self._adding = True
|
||||||
|
tox_id = self.toxIdLineEdit.text().strip()
|
||||||
|
if tox_id.startswith('tox:'):
|
||||||
|
tox_id = tox_id[4:]
|
||||||
|
message = self.messagePlainTextEdit.toPlainText()
|
||||||
|
send = self._contacts_manager.send_friend_request(tox_id, message)
|
||||||
|
self._adding = False
|
||||||
|
if send is True:
|
||||||
|
# request was successful
|
||||||
|
self.close()
|
||||||
|
else: # print error data
|
||||||
|
self.errorLabel.setText(send)
|
||||||
|
|
||||||
|
def _retranslate_ui(self):
|
||||||
|
self.setWindowTitle(util_ui.tr('Add contact'))
|
||||||
|
self.addContactPushButton.setText(util_ui.tr('Send request'))
|
||||||
|
self.toxIdLabel.setText(util_ui.tr('TOX ID:'))
|
||||||
|
self.messageLabel.setText(util_ui.tr('Message:'))
|
||||||
|
self.toxIdLineEdit.setPlaceholderText(util_ui.tr('TOX ID or public key of contact'))
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkSettings(CenteredWidget):
|
||||||
|
"""Network settings form: UDP, Ipv6 and proxy"""
|
||||||
|
def __init__(self, settings, reset):
|
||||||
|
super().__init__()
|
||||||
|
self._settings = settings
|
||||||
|
self._reset = reset
|
||||||
|
uic.loadUi(get_views_path('network_settings_screen'), self)
|
||||||
|
self._update_ui()
|
||||||
|
|
||||||
|
def _update_ui(self):
|
||||||
|
self.ipLineEdit = LineEdit(self)
|
||||||
|
self.ipLineEdit.setGeometry(100, 280, 270, 30)
|
||||||
|
|
||||||
|
self.portLineEdit = LineEdit(self)
|
||||||
|
self.portLineEdit.setGeometry(100, 325, 270, 30)
|
||||||
|
|
||||||
|
self.restartCorePushButton.clicked.connect(self._restart_core)
|
||||||
|
self.ipv6CheckBox.setChecked(self._settings['ipv6_enabled'])
|
||||||
|
self.udpCheckBox.setChecked(self._settings['udp_enabled'])
|
||||||
|
self.proxyCheckBox.setChecked(self._settings['proxy_type'])
|
||||||
|
self.ipLineEdit.setText(self._settings['proxy_host'])
|
||||||
|
self.portLineEdit.setText(str(self._settings['proxy_port']))
|
||||||
|
self.httpProxyRadioButton.setChecked(self._settings['proxy_type'] == 1)
|
||||||
|
self.socksProxyRadioButton.setChecked(self._settings['proxy_type'] != 1)
|
||||||
|
self.downloadNodesCheckBox.setChecked(self._settings['download_nodes_list'])
|
||||||
|
self.lanCheckBox.setChecked(self._settings['lan_discovery'])
|
||||||
|
self._retranslate_ui()
|
||||||
|
self.proxyCheckBox.stateChanged.connect(lambda x: self._activate_proxy())
|
||||||
|
self._activate_proxy()
|
||||||
|
|
||||||
|
def _retranslate_ui(self):
|
||||||
|
self.setWindowTitle(util_ui.tr("Network settings"))
|
||||||
|
self.ipv6CheckBox.setText(util_ui.tr("IPv6"))
|
||||||
|
self.udpCheckBox.setText(util_ui.tr("UDP"))
|
||||||
|
self.lanCheckBox.setText(util_ui.tr("LAN"))
|
||||||
|
self.proxyCheckBox.setText(util_ui.tr("Proxy"))
|
||||||
|
self.ipLabel.setText(util_ui.tr("IP:"))
|
||||||
|
self.portLabel.setText(util_ui.tr("Port:"))
|
||||||
|
self.restartCorePushButton.setText(util_ui.tr("Restart TOX core"))
|
||||||
|
self.httpProxyRadioButton.setText(util_ui.tr("HTTP"))
|
||||||
|
self.socksProxyRadioButton.setText(util_ui.tr("Socks 5"))
|
||||||
|
self.downloadNodesCheckBox.setText(util_ui.tr("Download nodes list from tox.chat"))
|
||||||
|
self.warningLabel.setText(util_ui.tr("WARNING:\nusing proxy with enabled UDP\ncan produce IP leak"))
|
||||||
|
|
||||||
|
def _activate_proxy(self):
|
||||||
|
bl = self.proxyCheckBox.isChecked()
|
||||||
|
self.ipLineEdit.setEnabled(bl)
|
||||||
|
self.portLineEdit.setEnabled(bl)
|
||||||
|
self.httpProxyRadioButton.setEnabled(bl)
|
||||||
|
self.socksProxyRadioButton.setEnabled(bl)
|
||||||
|
self.ipLabel.setEnabled(bl)
|
||||||
|
self.portLabel.setEnabled(bl)
|
||||||
|
|
||||||
|
def _restart_core(self):
|
||||||
|
try:
|
||||||
|
self._settings['ipv6_enabled'] = self.ipv6CheckBox.isChecked()
|
||||||
|
self._settings['udp_enabled'] = self.udpCheckBox.isChecked()
|
||||||
|
proxy_enabled = self.proxyCheckBox.isChecked()
|
||||||
|
self._settings['proxy_type'] = 2 - int(self.httpProxyRadioButton.isChecked()) if proxy_enabled else 0
|
||||||
|
self._settings['proxy_host'] = str(self.ipLineEdit.text())
|
||||||
|
self._settings['proxy_port'] = int(self.portLineEdit.text())
|
||||||
|
self._settings['download_nodes_list'] = self.downloadNodesCheckBox.isChecked()
|
||||||
|
self._settings['lan_discovery'] = self.lanCheckBox.isChecked()
|
||||||
|
self._settings.save()
|
||||||
|
# recreate tox instance
|
||||||
|
self._reset()
|
||||||
|
self.close()
|
||||||
|
except Exception as ex:
|
||||||
|
log('Exception in restart: ' + str(ex))
|
||||||
|
|
||||||
|
|
||||||
|
class PrivacySettings(CenteredWidget):
|
||||||
|
"""Privacy settings form: history, typing notifications"""
|
||||||
|
|
||||||
|
def __init__(self, contacts_manager, settings):
|
||||||
|
"""
|
||||||
|
:type contacts_manager: ContactsManager
|
||||||
|
"""
|
||||||
|
super().__init__()
|
||||||
|
self._contacts_manager = contacts_manager
|
||||||
|
self._settings = settings
|
||||||
|
self.initUI()
|
||||||
|
self.center()
|
||||||
|
|
||||||
|
def initUI(self):
|
||||||
|
self.setObjectName("privacySettings")
|
||||||
|
self.resize(370, 600)
|
||||||
|
self.setMinimumSize(QtCore.QSize(370, 600))
|
||||||
|
self.setMaximumSize(QtCore.QSize(370, 600))
|
||||||
|
self.saveHistory = QtWidgets.QCheckBox(self)
|
||||||
|
self.saveHistory.setGeometry(QtCore.QRect(10, 20, 350, 22))
|
||||||
|
self.saveUnsentOnly = QtWidgets.QCheckBox(self)
|
||||||
|
self.saveUnsentOnly.setGeometry(QtCore.QRect(10, 60, 350, 22))
|
||||||
|
|
||||||
|
self.fileautoaccept = QtWidgets.QCheckBox(self)
|
||||||
|
self.fileautoaccept.setGeometry(QtCore.QRect(10, 100, 350, 22))
|
||||||
|
|
||||||
|
self.typingNotifications = QtWidgets.QCheckBox(self)
|
||||||
|
self.typingNotifications.setGeometry(QtCore.QRect(10, 140, 350, 30))
|
||||||
|
self.inlines = QtWidgets.QCheckBox(self)
|
||||||
|
self.inlines.setGeometry(QtCore.QRect(10, 180, 350, 30))
|
||||||
|
self.auto_path = QtWidgets.QLabel(self)
|
||||||
|
self.auto_path.setGeometry(QtCore.QRect(10, 230, 350, 30))
|
||||||
|
self.path = QtWidgets.QPlainTextEdit(self)
|
||||||
|
self.path.setGeometry(QtCore.QRect(10, 265, 350, 45))
|
||||||
|
self.change_path = QtWidgets.QPushButton(self)
|
||||||
|
self.change_path.setGeometry(QtCore.QRect(10, 320, 350, 30))
|
||||||
|
self.typingNotifications.setChecked(self._settings['typing_notifications'])
|
||||||
|
self.fileautoaccept.setChecked(self._settings['allow_auto_accept'])
|
||||||
|
self.saveHistory.setChecked(self._settings['save_history'])
|
||||||
|
self.inlines.setChecked(self._settings['allow_inline'])
|
||||||
|
self.saveUnsentOnly.setChecked(self._settings['save_unsent_only'])
|
||||||
|
self.saveUnsentOnly.setEnabled(self._settings['save_history'])
|
||||||
|
self.saveHistory.stateChanged.connect(self.update)
|
||||||
|
self.path.setPlainText(self._settings['auto_accept_path'] or curr_directory())
|
||||||
|
self.change_path.clicked.connect(self.new_path)
|
||||||
|
self.block_user_label = QtWidgets.QLabel(self)
|
||||||
|
self.block_user_label.setGeometry(QtCore.QRect(10, 360, 350, 30))
|
||||||
|
self.block_id = QtWidgets.QPlainTextEdit(self)
|
||||||
|
self.block_id.setGeometry(QtCore.QRect(10, 390, 350, 30))
|
||||||
|
self.block = QtWidgets.QPushButton(self)
|
||||||
|
self.block.setGeometry(QtCore.QRect(10, 430, 350, 30))
|
||||||
|
self.block.clicked.connect(lambda: self._contacts_manager.block_user(self.block_id.toPlainText()) or self.close())
|
||||||
|
self.blocked_users_label = QtWidgets.QLabel(self)
|
||||||
|
self.blocked_users_label.setGeometry(QtCore.QRect(10, 470, 350, 30))
|
||||||
|
self.comboBox = QtWidgets.QComboBox(self)
|
||||||
|
self.comboBox.setGeometry(QtCore.QRect(10, 500, 350, 30))
|
||||||
|
self.comboBox.addItems(self._settings['blocked'])
|
||||||
|
self.unblock = QtWidgets.QPushButton(self)
|
||||||
|
self.unblock.setGeometry(QtCore.QRect(10, 540, 350, 30))
|
||||||
|
self.unblock.clicked.connect(lambda: self.unblock_user())
|
||||||
|
self.retranslateUi()
|
||||||
|
QtCore.QMetaObject.connectSlotsByName(self)
|
||||||
|
|
||||||
|
def retranslateUi(self):
|
||||||
|
self.setWindowTitle(util_ui.tr("Privacy settings"))
|
||||||
|
self.saveHistory.setText(util_ui.tr("Save chat history"))
|
||||||
|
self.fileautoaccept.setText(util_ui.tr("Allow file auto accept"))
|
||||||
|
self.typingNotifications.setText(util_ui.tr("Send typing notifications"))
|
||||||
|
self.auto_path.setText(util_ui.tr("Auto accept default path:"))
|
||||||
|
self.change_path.setText(util_ui.tr("Change"))
|
||||||
|
self.inlines.setText(util_ui.tr("Allow inlines"))
|
||||||
|
self.block_user_label.setText(util_ui.tr("Block by public key:"))
|
||||||
|
self.blocked_users_label.setText(util_ui.tr("Blocked users:"))
|
||||||
|
self.unblock.setText(util_ui.tr("Unblock"))
|
||||||
|
self.block.setText(util_ui.tr("Block user"))
|
||||||
|
self.saveUnsentOnly.setText(util_ui.tr("Save unsent messages only"))
|
||||||
|
|
||||||
|
def update(self, new_state):
|
||||||
|
self.saveUnsentOnly.setEnabled(new_state)
|
||||||
|
if not new_state:
|
||||||
|
self.saveUnsentOnly.setChecked(False)
|
||||||
|
|
||||||
|
def unblock_user(self):
|
||||||
|
if not self.comboBox.count():
|
||||||
|
return
|
||||||
|
title = util_ui.tr("Add to friend list")
|
||||||
|
info = util_ui.tr("Do you want to add this user to friend list?")
|
||||||
|
reply = util_ui.question(info, title)
|
||||||
|
self._contacts_manager.unblock_user(self.comboBox.currentText(), reply)
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
self._settings['typing_notifications'] = self.typingNotifications.isChecked()
|
||||||
|
self._settings['allow_auto_accept'] = self.fileautoaccept.isChecked()
|
||||||
|
text = util_ui.tr('History will be cleaned! Continue?')
|
||||||
|
title = util_ui.tr('Chat history')
|
||||||
|
|
||||||
|
if self._settings['save_history'] and not self.saveHistory.isChecked(): # clear history
|
||||||
|
reply = util_ui.question(text, title)
|
||||||
|
if reply:
|
||||||
|
self._history_loader.clear_history()
|
||||||
|
self._settings['save_history'] = self.saveHistory.isChecked()
|
||||||
|
else:
|
||||||
|
self._settings['save_history'] = self.saveHistory.isChecked()
|
||||||
|
if self.saveUnsentOnly.isChecked() and not self._settings['save_unsent_only']:
|
||||||
|
reply = util_ui.question(text, title)
|
||||||
|
if reply:
|
||||||
|
self._history_loader.clear_history(None, True)
|
||||||
|
self._settings['save_unsent_only'] = self.saveUnsentOnly.isChecked()
|
||||||
|
else:
|
||||||
|
self._settings['save_unsent_only'] = self.saveUnsentOnly.isChecked()
|
||||||
|
self._settings['auto_accept_path'] = self.path.toPlainText()
|
||||||
|
self._settings['allow_inline'] = self.inlines.isChecked()
|
||||||
|
self._settings.save()
|
||||||
|
|
||||||
|
def new_path(self):
|
||||||
|
directory = util_ui.directory_dialog()
|
||||||
|
if directory:
|
||||||
|
self.path.setPlainText(directory)
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationsSettings(CenteredWidget):
|
||||||
|
"""Notifications settings form"""
|
||||||
|
|
||||||
|
def __init__(self, setttings):
|
||||||
|
super().__init__()
|
||||||
|
self._settings = setttings
|
||||||
|
uic.loadUi(get_views_path('notifications_settings_screen'), self)
|
||||||
|
self._update_ui()
|
||||||
|
self.center()
|
||||||
|
|
||||||
|
def closeEvent(self, *args, **kwargs):
|
||||||
|
self._settings['notifications'] = self.notificationsCheckBox.isChecked()
|
||||||
|
self._settings['sound_notifications'] = self.soundNotificationsCheckBox.isChecked()
|
||||||
|
self._settings['group_notifications'] = self.groupNotificationsCheckBox.isChecked()
|
||||||
|
self._settings['calls_sound'] = self.callsSoundCheckBox.isChecked()
|
||||||
|
self._settings.save()
|
||||||
|
|
||||||
|
def _update_ui(self):
|
||||||
|
self.notificationsCheckBox.setChecked(self._settings['notifications'])
|
||||||
|
self.soundNotificationsCheckBox.setChecked(self._settings['sound_notifications'])
|
||||||
|
self.groupNotificationsCheckBox.setChecked(self._settings['group_notifications'])
|
||||||
|
self.callsSoundCheckBox.setChecked(self._settings['calls_sound'])
|
||||||
|
self._retranslate_ui()
|
||||||
|
|
||||||
|
def _retranslate_ui(self):
|
||||||
|
self.setWindowTitle(util_ui.tr("Notifications settings"))
|
||||||
|
self.notificationsCheckBox.setText(util_ui.tr("Enable notifications"))
|
||||||
|
self.groupNotificationsCheckBox.setText(util_ui.tr("Notify about all messages in groups"))
|
||||||
|
self.callsSoundCheckBox.setText(util_ui.tr("Enable call\'s sound"))
|
||||||
|
self.soundNotificationsCheckBox.setText(util_ui.tr("Enable sound notifications"))
|
||||||
|
|
||||||
|
|
||||||
|
class InterfaceSettings(CenteredWidget):
|
||||||
|
"""Interface settings form"""
|
||||||
|
|
||||||
|
def __init__(self, settings, smiley_loader):
|
||||||
|
super().__init__()
|
||||||
|
self._settings = settings
|
||||||
|
self._smiley_loader = smiley_loader
|
||||||
|
|
||||||
|
uic.loadUi(get_views_path('interface_settings_screen'), self)
|
||||||
|
self._update_ui()
|
||||||
|
self.center()
|
||||||
|
|
||||||
|
def _update_ui(self):
|
||||||
|
themes = list(self._settings.built_in_themes().keys())
|
||||||
|
self.themeComboBox.addItems(themes)
|
||||||
|
theme = self._settings['theme']
|
||||||
|
if theme in self._settings.built_in_themes().keys():
|
||||||
|
index = themes.index(theme)
|
||||||
|
else:
|
||||||
|
index = 0
|
||||||
|
self.themeComboBox.setCurrentIndex(index)
|
||||||
|
|
||||||
|
supported_languages = sorted(Settings.supported_languages().keys(), reverse=True)
|
||||||
|
for key in supported_languages:
|
||||||
|
self.languageComboBox.insertItem(0, key)
|
||||||
|
if self._settings['language'] == key:
|
||||||
|
self.languageComboBox.setCurrentIndex(0)
|
||||||
|
|
||||||
|
smiley_packs = self._smiley_loader.get_packs_list()
|
||||||
|
self.smileysPackComboBox.addItems(smiley_packs)
|
||||||
|
try:
|
||||||
|
index = smiley_packs.index(self._settings['smiley_pack'])
|
||||||
|
except:
|
||||||
|
index = smiley_packs.index('default')
|
||||||
|
self.smileysPackComboBox.setCurrentIndex(index)
|
||||||
|
|
||||||
|
app_closing_setting = self._settings['close_app']
|
||||||
|
self.closeRadioButton.setChecked(app_closing_setting == 0)
|
||||||
|
self.hideRadioButton.setChecked(app_closing_setting == 1)
|
||||||
|
self.closeToTrayRadioButton.setChecked(app_closing_setting == 2)
|
||||||
|
|
||||||
|
self.compactModeCheckBox.setChecked(self._settings['compact_mode'])
|
||||||
|
self.showAvatarsCheckBox.setChecked(self._settings['show_avatars'])
|
||||||
|
self.smileysCheckBox.setChecked(self._settings['smileys'])
|
||||||
|
|
||||||
|
self.importSmileysPushButton.clicked.connect(self._import_smileys)
|
||||||
|
self.importStickersPushButton.clicked.connect(self._import_stickers)
|
||||||
|
|
||||||
|
self._retranslate_ui()
|
||||||
|
|
||||||
|
def _retranslate_ui(self):
|
||||||
|
self.setWindowTitle(util_ui.tr("Interface settings"))
|
||||||
|
self.showAvatarsCheckBox.setText(util_ui.tr("Show avatars in chat"))
|
||||||
|
self.themeLabel.setText(util_ui.tr("Theme:"))
|
||||||
|
self.languageLabel.setText(util_ui.tr("Language:"))
|
||||||
|
self.smileysGroupBox.setTitle(util_ui.tr("Smileys settings"))
|
||||||
|
self.smileysPackLabel.setText(util_ui.tr("Smiley pack:"))
|
||||||
|
self.smileysCheckBox.setText(util_ui.tr("Smileys"))
|
||||||
|
self.closeRadioButton.setText(util_ui.tr("Close app"))
|
||||||
|
self.hideRadioButton.setText(util_ui.tr("Hide app"))
|
||||||
|
self.closeToTrayRadioButton.setText(util_ui.tr("Close to tray"))
|
||||||
|
self.mirrorModeCheckBox.setText(util_ui.tr("Mirror mode"))
|
||||||
|
self.compactModeCheckBox.setText(util_ui.tr("Compact contact list"))
|
||||||
|
self.importSmileysPushButton.setText(util_ui.tr("Import smiley pack"))
|
||||||
|
self.importStickersPushButton.setText(util_ui.tr("Import sticker pack"))
|
||||||
|
self.appClosingGroupBox.setTitle(util_ui.tr("App closing settings"))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _import_stickers():
|
||||||
|
directory = util_ui.directory_dialog(util_ui.tr('Choose folder with sticker pack'))
|
||||||
|
if directory:
|
||||||
|
dest = join_path(get_stickers_directory(), os.path.basename(directory))
|
||||||
|
copy(directory, dest)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _import_smileys():
|
||||||
|
directory = util_ui.directory_dialog(util_ui.tr('Choose folder with smiley pack'))
|
||||||
|
if not directory:
|
||||||
|
return
|
||||||
|
src = directory + '/'
|
||||||
|
dest = join_path(get_smileys_directory(), os.path.basename(directory))
|
||||||
|
copy(src, dest)
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
app = QtWidgets.QApplication.instance()
|
||||||
|
|
||||||
|
self._settings['theme'] = str(self.themeComboBox.currentText())
|
||||||
|
try:
|
||||||
|
theme = self._settings['theme']
|
||||||
|
styles_path = join_path(get_styles_directory(), self._settings.built_in_themes()[theme])
|
||||||
|
with open(styles_path) as fl:
|
||||||
|
style = fl.read()
|
||||||
|
app.setStyleSheet(style)
|
||||||
|
except IsADirectoryError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self._settings['smileys'] = self.smileysCheckBox.isChecked()
|
||||||
|
|
||||||
|
restart = False
|
||||||
|
if self._settings['mirror_mode'] != self.mirrorModeCheckBox.isChecked():
|
||||||
|
self._settings['mirror_mode'] = self.mirrorModeCheckBox.isChecked()
|
||||||
|
restart = True
|
||||||
|
|
||||||
|
if self._settings['compact_mode'] != self.compactModeCheckBox.isChecked():
|
||||||
|
self._settings['compact_mode'] = self.compactModeCheckBox.isChecked()
|
||||||
|
restart = True
|
||||||
|
|
||||||
|
if self._settings['show_avatars'] != self.showAvatarsCheckBox.isChecked():
|
||||||
|
self._settings['show_avatars'] = self.showAvatarsCheckBox.isChecked()
|
||||||
|
restart = True
|
||||||
|
|
||||||
|
self._settings['smiley_pack'] = self.smileysPackComboBox.currentText()
|
||||||
|
self._smiley_loader.load_pack()
|
||||||
|
|
||||||
|
language = self.languageComboBox.currentText()
|
||||||
|
if self._settings['language'] != language:
|
||||||
|
self._settings['language'] = language
|
||||||
|
path = Settings.supported_languages()[language]
|
||||||
|
app.removeTranslator(app.translator)
|
||||||
|
app.translator.load(join_path(get_translations_directory(), path))
|
||||||
|
app.installTranslator(app.translator)
|
||||||
|
|
||||||
|
app_closing_setting = 0
|
||||||
|
if self.hideRadioButton.isChecked():
|
||||||
|
app_closing_setting = 1
|
||||||
|
elif self.closeToTrayRadioButton.isChecked():
|
||||||
|
app_closing_setting = 2
|
||||||
|
self._settings['close_app'] = app_closing_setting
|
||||||
|
self._settings.save()
|
||||||
|
|
||||||
|
if restart:
|
||||||
|
util_ui.message_box(util_ui.tr('Restart app to apply settings'), util_ui.tr('Restart required'))
|
||||||
|
|
||||||
|
|
||||||
|
class AudioSettings(CenteredWidget):
|
||||||
|
"""
|
||||||
|
Audio calls settings form
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, settings):
|
||||||
|
super().__init__()
|
||||||
|
self._settings = settings
|
||||||
|
self._in_indexes = self._out_indexes = None
|
||||||
|
uic.loadUi(get_views_path('audio_settings_screen'), self)
|
||||||
|
self._update_ui()
|
||||||
|
self.center()
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
self._settings.audio['input'] = self._in_indexes[self.inputDeviceComboBox.currentIndex()]
|
||||||
|
self._settings.audio['output'] = self._out_indexes[self.outputDeviceComboBox.currentIndex()]
|
||||||
|
self._settings.save()
|
||||||
|
|
||||||
|
def _update_ui(self):
|
||||||
|
p = pyaudio.PyAudio()
|
||||||
|
self._in_indexes, self._out_indexes = [], []
|
||||||
|
for i in range(p.get_device_count()):
|
||||||
|
device = p.get_device_info_by_index(i)
|
||||||
|
if device["maxInputChannels"]:
|
||||||
|
self.inputDeviceComboBox.addItem(str(device["name"]))
|
||||||
|
self._in_indexes.append(i)
|
||||||
|
if device["maxOutputChannels"]:
|
||||||
|
self.outputDeviceComboBox.addItem(str(device["name"]))
|
||||||
|
self._out_indexes.append(i)
|
||||||
|
self.inputDeviceComboBox.setCurrentIndex(self._in_indexes.index(self._settings.audio['input']))
|
||||||
|
self.outputDeviceComboBox.setCurrentIndex(self._out_indexes.index(self._settings.audio['output']))
|
||||||
|
self._retranslate_ui()
|
||||||
|
|
||||||
|
def _retranslate_ui(self):
|
||||||
|
self.setWindowTitle(util_ui.tr("Audio settings"))
|
||||||
|
self.inputDeviceLabel.setText(util_ui.tr("Input device:"))
|
||||||
|
self.outputDeviceLabel.setText(util_ui.tr("Output device:"))
|
||||||
|
|
||||||
|
|
||||||
|
class DesktopAreaSelectionWindow(RubberBandWindow):
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, event):
|
||||||
|
if self.rubberband.isVisible():
|
||||||
|
self.rubberband.hide()
|
||||||
|
rect = self.rubberband.geometry()
|
||||||
|
width, height = rect.width(), rect.height()
|
||||||
|
if width >= 8 and height >= 8:
|
||||||
|
self.parent.save(rect.x(), rect.y(), width - (width % 4), height - (height % 4))
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
|
class VideoSettings(CenteredWidget):
|
||||||
|
"""
|
||||||
|
Audio calls settings form
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, settings):
|
||||||
|
super().__init__()
|
||||||
|
self._settings = settings
|
||||||
|
uic.loadUi(get_views_path('video_settings_screen'), self)
|
||||||
|
self._devices = self._frame_max_sizes = None
|
||||||
|
self._update_ui()
|
||||||
|
self.center()
|
||||||
|
self.desktopAreaSelection = None
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
if self.deviceComboBox.currentIndex() == 0:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self._settings.video['device'] = self.devices[self.input.currentIndex()]
|
||||||
|
text = self.resolutionComboBox.currentText()
|
||||||
|
self._settings.video['width'] = int(text.split(' ')[0])
|
||||||
|
self._settings.video['height'] = int(text.split(' ')[-1])
|
||||||
|
self._settings.save()
|
||||||
|
except Exception as ex:
|
||||||
|
print('Saving video settings error: ' + str(ex))
|
||||||
|
|
||||||
|
def save(self, x, y, width, height):
|
||||||
|
self.desktopAreaSelection = None
|
||||||
|
self._settings.video['device'] = -1
|
||||||
|
self._settings.video['width'] = width
|
||||||
|
self._settings.video['height'] = height
|
||||||
|
self._settings.video['x'] = x
|
||||||
|
self._settings.video['y'] = y
|
||||||
|
self._settings.save()
|
||||||
|
|
||||||
|
def _update_ui(self):
|
||||||
|
self.deviceComboBox.currentIndexChanged.connect(self._device_changed)
|
||||||
|
self.selectRegionPushButton.clicked.connect(self._button_clicked)
|
||||||
|
self._devices = [-1]
|
||||||
|
screen = QtWidgets.QApplication.primaryScreen()
|
||||||
|
size = screen.size()
|
||||||
|
self._frame_max_sizes = [(size.width(), size.height())]
|
||||||
|
desktop = util_ui.tr("Desktop")
|
||||||
|
self.deviceComboBox.addItem(desktop)
|
||||||
|
for i in range(10):
|
||||||
|
v = cv2.VideoCapture(i)
|
||||||
|
if v.isOpened():
|
||||||
|
v.set(cv2.CAP_PROP_FRAME_WIDTH, 10000)
|
||||||
|
v.set(cv2.CAP_PROP_FRAME_HEIGHT, 10000)
|
||||||
|
|
||||||
|
width = int(v.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||||||
|
height = int(v.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
||||||
|
del v
|
||||||
|
self._devices.append(i)
|
||||||
|
self._frame_max_sizes.append((width, height))
|
||||||
|
self.deviceComboBox.addItem(util_ui.tr('Device #') + str(i))
|
||||||
|
try:
|
||||||
|
index = self._devices.index(self._settings.video['device'])
|
||||||
|
self.deviceComboBox.setCurrentIndex(index)
|
||||||
|
except:
|
||||||
|
print('Video devices error!')
|
||||||
|
self._retranslate_ui()
|
||||||
|
|
||||||
|
def _retranslate_ui(self):
|
||||||
|
self.setWindowTitle(util_ui.tr("Video settings"))
|
||||||
|
self.deviceLabel.setText(util_ui.tr("Device:"))
|
||||||
|
self.selectRegionPushButton.setText(util_ui.tr("Select region"))
|
||||||
|
|
||||||
|
def _button_clicked(self):
|
||||||
|
self.desktopAreaSelection = DesktopAreaSelectionWindow(self)
|
||||||
|
|
||||||
|
def _device_changed(self):
|
||||||
|
index = self.deviceComboBox.currentIndex()
|
||||||
|
self.selectRegionPushButton.setVisible(index == 0)
|
||||||
|
self.resolutionComboBox.setVisible(index != 0)
|
||||||
|
width, height = self._frame_max_sizes[index]
|
||||||
|
self.resolutionComboBox.clear()
|
||||||
|
dims = [
|
||||||
|
(320, 240),
|
||||||
|
(640, 360),
|
||||||
|
(640, 480),
|
||||||
|
(720, 480),
|
||||||
|
(1280, 720),
|
||||||
|
(1920, 1080),
|
||||||
|
(2560, 1440)
|
||||||
|
]
|
||||||
|
for w, h in dims:
|
||||||
|
if w <= width and h <= height:
|
||||||
|
self.resolutionComboBox.addItem(str(w) + ' * ' + str(h))
|
||||||
|
|
||||||
|
|
||||||
|
class PluginsSettings(CenteredWidget):
|
||||||
|
"""
|
||||||
|
Plugins settings form
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, plugin_loader):
|
||||||
|
super().__init__()
|
||||||
|
self._plugin_loader = plugin_loader
|
||||||
|
self._window = None
|
||||||
|
self.initUI()
|
||||||
|
self.center()
|
||||||
|
self.retranslateUi()
|
||||||
|
|
||||||
|
def initUI(self):
|
||||||
|
self.resize(400, 210)
|
||||||
|
self.setMinimumSize(QtCore.QSize(400, 210))
|
||||||
|
self.setMaximumSize(QtCore.QSize(400, 210))
|
||||||
|
self.comboBox = QtWidgets.QComboBox(self)
|
||||||
|
self.comboBox.setGeometry(QtCore.QRect(30, 10, 340, 30))
|
||||||
|
self.label = QtWidgets.QLabel(self)
|
||||||
|
self.label.setGeometry(QtCore.QRect(30, 40, 340, 90))
|
||||||
|
self.label.setWordWrap(True)
|
||||||
|
self.button = QtWidgets.QPushButton(self)
|
||||||
|
self.button.setGeometry(QtCore.QRect(30, 130, 340, 30))
|
||||||
|
self.button.clicked.connect(self.button_click)
|
||||||
|
self.open = QtWidgets.QPushButton(self)
|
||||||
|
self.open.setGeometry(QtCore.QRect(30, 170, 340, 30))
|
||||||
|
self.open.clicked.connect(self.open_plugin)
|
||||||
|
self.update_list()
|
||||||
|
self.comboBox.currentIndexChanged.connect(self.show_data)
|
||||||
|
self.show_data()
|
||||||
|
|
||||||
|
def retranslateUi(self):
|
||||||
|
self.setWindowTitle(util_ui.tr("Plugins"))
|
||||||
|
self.open.setText(util_ui.tr("Open selected plugin"))
|
||||||
|
|
||||||
|
def open_plugin(self):
|
||||||
|
ind = self.comboBox.currentIndex()
|
||||||
|
plugin = self.data[ind]
|
||||||
|
window = self.pl_loader.plugin_window(plugin[-1])
|
||||||
|
if window is not None:
|
||||||
|
self._window = window
|
||||||
|
self._window.show()
|
||||||
|
else:
|
||||||
|
util_ui.message_box(util_ui.tr('No GUI found for this plugin'), util_ui.tr('Error'))
|
||||||
|
|
||||||
|
def update_list(self):
|
||||||
|
self.comboBox.clear()
|
||||||
|
data = self._plugin_loader.get_plugins_list()
|
||||||
|
self.comboBox.addItems(list(map(lambda x: x[0], data)))
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
def show_data(self):
|
||||||
|
ind = self.comboBox.currentIndex()
|
||||||
|
if len(self.data):
|
||||||
|
plugin = self.data[ind]
|
||||||
|
descr = plugin[2] or util_ui.tr("No description available")
|
||||||
|
self.label.setText(descr)
|
||||||
|
if plugin[1]:
|
||||||
|
self.button.setText(util_ui.tr("Disable plugin"))
|
||||||
|
else:
|
||||||
|
self.button.setText(util_ui.tr("Enable plugin"))
|
||||||
|
else:
|
||||||
|
self.open.setVisible(False)
|
||||||
|
self.button.setVisible(False)
|
||||||
|
self.label.setText(util_ui.tr("No plugins found"))
|
||||||
|
|
||||||
|
def button_click(self):
|
||||||
|
ind = self.comboBox.currentIndex()
|
||||||
|
plugin = self.data[ind]
|
||||||
|
self._plugin_loader.toggle_plugin(plugin[-1])
|
||||||
|
plugin[1] = not plugin[1]
|
||||||
|
if plugin[1]:
|
||||||
|
self.button.setText(util_ui.tr("Disable plugin"))
|
||||||
|
else:
|
||||||
|
self.button.setText(util_ui.tr("Enable plugin"))
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateSettings(CenteredWidget):
|
||||||
|
"""
|
||||||
|
Updates settings form
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, settings, version):
|
||||||
|
super().__init__()
|
||||||
|
self._settings = settings
|
||||||
|
self._version = version
|
||||||
|
uic.loadUi(get_views_path('update_settings_screen'), self)
|
||||||
|
self._update_ui()
|
||||||
|
self.center()
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
self._settings['update'] = self.updateModeComboBox.currentIndex()
|
||||||
|
self._settings.save()
|
||||||
|
|
||||||
|
def _update_ui(self):
|
||||||
|
self.updatePushButton.clicked.connect(self._update_client)
|
||||||
|
self.updateModeComboBox.currentIndexChanged.connect(self._update_mode_changed)
|
||||||
|
self._retranslate_ui()
|
||||||
|
self.updateModeComboBox.setCurrentIndex(self._settings['update'])
|
||||||
|
|
||||||
|
def _update_mode_changed(self):
|
||||||
|
index = self.updateModeComboBox.currentIndex()
|
||||||
|
self.updatePushButton.setEnabled(index > 0)
|
||||||
|
|
||||||
|
def _retranslate_ui(self):
|
||||||
|
self.setWindowTitle(util_ui.tr("Update settings"))
|
||||||
|
self.updateModeLabel.setText(util_ui.tr("Select update mode:"))
|
||||||
|
self.updatePushButton.setText(util_ui.tr("Update Toxygen"))
|
||||||
|
self.updateModeComboBox.addItem(util_ui.tr("Disabled"))
|
||||||
|
self.updateModeComboBox.addItem(util_ui.tr("Manual"))
|
||||||
|
self.updateModeComboBox.addItem(util_ui.tr("Auto"))
|
||||||
|
|
||||||
|
def _update_client(self):
|
||||||
|
if not updater.connection_available():
|
||||||
|
util_ui.message_box(util_ui.tr('Problems with internet connection'), util_ui.tr("Error"))
|
||||||
|
return
|
||||||
|
if not updater.updater_available():
|
||||||
|
util_ui.message_box(util_ui.tr('Updater not found'), util_ui.tr("Error"))
|
||||||
|
return
|
||||||
|
version = updater.check_for_updates(self._version, self._settings)
|
||||||
|
if version is not None:
|
||||||
|
updater.download(version)
|
||||||
|
util_ui.close_all_windows()
|
||||||
|
else:
|
||||||
|
util_ui.message_box(util_ui.tr('Toxygen is up to date'), util_ui.tr("No updates found"))
|
@ -1,20 +1,23 @@
|
|||||||
from toxcore_enums_and_consts import *
|
from wrapper.toxcore_enums_and_consts import *
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
import ui.widgets as widgets
|
||||||
import profile
|
import utils.util as util
|
||||||
from file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR
|
import ui.menu as menu
|
||||||
from util import curr_directory, convert_time, curr_time
|
|
||||||
from widgets import DataLabel, create_menu
|
|
||||||
import html as h
|
import html as h
|
||||||
import smileys
|
|
||||||
import settings
|
|
||||||
import re
|
import re
|
||||||
|
from ui.widgets import *
|
||||||
|
from messenger.messages import MESSAGE_AUTHOR
|
||||||
|
from file_transfers.file_transfers import *
|
||||||
|
|
||||||
|
|
||||||
class MessageEdit(QtWidgets.QTextBrowser):
|
class MessageBrowser(QtWidgets.QTextBrowser):
|
||||||
|
|
||||||
def __init__(self, text, width, message_type, parent=None):
|
def __init__(self, settings, message_edit, smileys_loader, plugin_loader, text, width, message_type, parent=None):
|
||||||
super(MessageEdit, self).__init__(parent)
|
super().__init__(parent)
|
||||||
self.urls = {}
|
self.urls = {}
|
||||||
|
self._message_edit = message_edit
|
||||||
|
self._smileys_loader = smileys_loader
|
||||||
|
self._plugin_loader = plugin_loader
|
||||||
|
self._add_contact = None
|
||||||
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||||
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||||
self.setWordWrapMode(QtGui.QTextOption.WrapAtWordBoundaryOrAnywhere)
|
self.setWordWrapMode(QtGui.QTextOption.WrapAtWordBoundaryOrAnywhere)
|
||||||
@ -22,7 +25,7 @@ class MessageEdit(QtWidgets.QTextBrowser):
|
|||||||
self.setOpenExternalLinks(True)
|
self.setOpenExternalLinks(True)
|
||||||
self.setAcceptRichText(True)
|
self.setAcceptRichText(True)
|
||||||
self.setOpenLinks(False)
|
self.setOpenLinks(False)
|
||||||
path = smileys.SmileyLoader.get_instance().get_smileys_path()
|
path = smileys_loader.get_smileys_path()
|
||||||
if path is not None:
|
if path is not None:
|
||||||
self.setSearchPaths([path])
|
self.setSearchPaths([path])
|
||||||
self.document().setDefaultStyleSheet('a { color: #306EFF; }')
|
self.document().setDefaultStyleSheet('a { color: #306EFF; }')
|
||||||
@ -32,8 +35,8 @@ class MessageEdit(QtWidgets.QTextBrowser):
|
|||||||
else:
|
else:
|
||||||
self.setHtml(text)
|
self.setHtml(text)
|
||||||
font = QtGui.QFont()
|
font = QtGui.QFont()
|
||||||
font.setFamily(settings.Settings.get_instance()['font'])
|
font.setFamily(settings['font'])
|
||||||
font.setPixelSize(settings.Settings.get_instance()['message_font_size'])
|
font.setPixelSize(settings['message_font_size'])
|
||||||
font.setBold(False)
|
font.setBold(False)
|
||||||
self.setFont(font)
|
self.setFont(font)
|
||||||
self.resize(width, self.document().size().height())
|
self.resize(width, self.document().size().height())
|
||||||
@ -41,45 +44,42 @@ class MessageEdit(QtWidgets.QTextBrowser):
|
|||||||
self.anchorClicked.connect(self.on_anchor_clicked)
|
self.anchorClicked.connect(self.on_anchor_clicked)
|
||||||
|
|
||||||
def contextMenuEvent(self, event):
|
def contextMenuEvent(self, event):
|
||||||
menu = create_menu(self.createStandardContextMenu(event.pos()))
|
menu = widgets.create_menu(self.createStandardContextMenu(event.pos()))
|
||||||
quote = menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Quote selected text'))
|
quote = menu.addAction(util_ui.tr('Quote selected text'))
|
||||||
quote.triggered.connect(self.quote_text)
|
quote.triggered.connect(self.quote_text)
|
||||||
text = self.textCursor().selection().toPlainText()
|
text = self.textCursor().selection().toPlainText()
|
||||||
if not text:
|
if not text:
|
||||||
quote.setEnabled(False)
|
quote.setEnabled(False)
|
||||||
else:
|
else:
|
||||||
import plugin_support
|
sub_menu = self._plugin_loader.get_message_menu(menu, text)
|
||||||
submenu = plugin_support.PluginLoader.get_instance().get_message_menu(menu, text)
|
if len(sub_menu):
|
||||||
if len(submenu):
|
plugins_menu = menu.addMenu(util_ui.tr('Plugins'))
|
||||||
plug = menu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins'))
|
plugins_menu.addActions(sub_menu)
|
||||||
plug.addActions(submenu)
|
|
||||||
menu.popup(event.globalPos())
|
menu.popup(event.globalPos())
|
||||||
menu.exec_(event.globalPos())
|
menu.exec_(event.globalPos())
|
||||||
del menu
|
del menu
|
||||||
|
|
||||||
def quote_text(self):
|
def quote_text(self):
|
||||||
text = self.textCursor().selection().toPlainText()
|
text = self.textCursor().selection().toPlainText()
|
||||||
if text:
|
if not text:
|
||||||
import mainscreen
|
return
|
||||||
window = mainscreen.MainWindow.get_instance()
|
text = '>' + '\n>'.join(text.split('\n'))
|
||||||
text = '>' + '\n>'.join(text.split('\n'))
|
if self._message_edit.toPlainText():
|
||||||
if window.messageEdit.toPlainText():
|
text = '\n' + text
|
||||||
text = '\n' + text
|
self._message_edit.appendPlainText(text)
|
||||||
window.messageEdit.appendPlainText(text)
|
|
||||||
|
|
||||||
def on_anchor_clicked(self, url):
|
def on_anchor_clicked(self, url):
|
||||||
text = str(url.toString())
|
text = str(url.toString())
|
||||||
if text.startswith('tox:'):
|
if text.startswith('tox:'):
|
||||||
import menu
|
self._add_contact = menu.AddContact(text[4:])
|
||||||
self.add_contact = menu.AddContact(text[4:])
|
self._add_contact.show()
|
||||||
self.add_contact.show()
|
|
||||||
else:
|
else:
|
||||||
QtGui.QDesktopServices.openUrl(url)
|
QtGui.QDesktopServices.openUrl(url)
|
||||||
self.clearFocus()
|
self.clearFocus()
|
||||||
|
|
||||||
def addAnimation(self, url, fileName):
|
def addAnimation(self, url, file_name):
|
||||||
movie = QtGui.QMovie(self)
|
movie = QtGui.QMovie(self)
|
||||||
movie.setFileName(fileName)
|
movie.setFileName(file_name)
|
||||||
self.urls[movie] = url
|
self.urls[movie] = url
|
||||||
movie.frameChanged[int].connect(lambda x: self.animate(movie))
|
movie.frameChanged[int].connect(lambda x: self.animate(movie))
|
||||||
movie.start()
|
movie.start()
|
||||||
@ -115,7 +115,7 @@ class MessageEdit(QtWidgets.QTextBrowser):
|
|||||||
if arr[i].startswith('>'):
|
if arr[i].startswith('>'):
|
||||||
arr[i] = '<font color="green"><b>' + arr[i][4:] + '</b></font>'
|
arr[i] = '<font color="green"><b>' + arr[i][4:] + '</b></font>'
|
||||||
text = '<br>'.join(arr)
|
text = '<br>'.join(arr)
|
||||||
text = smileys.SmileyLoader.get_instance().add_smileys_to_text(text, self) # smileys
|
text = self._smileys_loader.add_smileys_to_text(text, self)
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
@ -123,35 +123,39 @@ class MessageItem(QtWidgets.QWidget):
|
|||||||
"""
|
"""
|
||||||
Message in messages list
|
Message in messages list
|
||||||
"""
|
"""
|
||||||
def __init__(self, text, time, user='', sent=True, message_type=TOX_MESSAGE_TYPE['NORMAL'], parent=None):
|
def __init__(self, text_message, settings, message_browser_factory_method, delete_action, parent=None):
|
||||||
QtWidgets.QWidget.__init__(self, parent)
|
QtWidgets.QWidget.__init__(self, parent)
|
||||||
self.name = DataLabel(self)
|
self._message = text_message
|
||||||
|
self._delete_action = delete_action
|
||||||
|
self.name = widgets.DataLabel(self)
|
||||||
self.name.setGeometry(QtCore.QRect(2, 2, 95, 23))
|
self.name.setGeometry(QtCore.QRect(2, 2, 95, 23))
|
||||||
self.name.setTextFormat(QtCore.Qt.PlainText)
|
self.name.setTextFormat(QtCore.Qt.PlainText)
|
||||||
font = QtGui.QFont()
|
font = QtGui.QFont()
|
||||||
font.setFamily(settings.Settings.get_instance()['font'])
|
font.setFamily(settings['font'])
|
||||||
font.setPointSize(11)
|
font.setPointSize(11)
|
||||||
font.setBold(True)
|
font.setBold(True)
|
||||||
self.name.setFont(font)
|
if text_message.author is not None:
|
||||||
self.name.setText(user)
|
self.name.setFont(font)
|
||||||
|
self.name.setText(text_message.author.name)
|
||||||
|
|
||||||
self.time = QtWidgets.QLabel(self)
|
self.time = QtWidgets.QLabel(self)
|
||||||
self.time.setGeometry(QtCore.QRect(parent.width() - 60, 0, 50, 25))
|
self.time.setGeometry(QtCore.QRect(parent.width() - 60, 0, 50, 25))
|
||||||
font.setPointSize(10)
|
font.setPointSize(10)
|
||||||
font.setBold(False)
|
font.setBold(False)
|
||||||
self.time.setFont(font)
|
self.time.setFont(font)
|
||||||
self._time = time
|
self._time = text_message.time
|
||||||
if not sent:
|
if text_message.author and text_message.author.type == MESSAGE_AUTHOR['NOT_SENT']:
|
||||||
movie = QtGui.QMovie(curr_directory() + '/images/spinner.gif')
|
movie = QtGui.QMovie(util.join_path(util.get_images_directory(), 'spinner.gif'))
|
||||||
self.time.setMovie(movie)
|
self.time.setMovie(movie)
|
||||||
movie.start()
|
movie.start()
|
||||||
self.t = True
|
self.t = True
|
||||||
else:
|
else:
|
||||||
self.time.setText(convert_time(time))
|
self.time.setText(util.convert_time(text_message.time))
|
||||||
self.t = False
|
self.t = False
|
||||||
|
|
||||||
self.message = MessageEdit(text, parent.width() - 160, message_type, self)
|
self.message = message_browser_factory_method(text_message.text, parent.width() - 160,
|
||||||
if message_type != TOX_MESSAGE_TYPE['NORMAL']:
|
text_message.type, self)
|
||||||
|
if text_message.type != TOX_MESSAGE_TYPE['NORMAL']:
|
||||||
self.name.setStyleSheet("QLabel { color: #5CB3FF; }")
|
self.name.setStyleSheet("QLabel { color: #5CB3FF; }")
|
||||||
self.message.setAlignment(QtCore.Qt.AlignCenter)
|
self.message.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
self.time.setStyleSheet("QLabel { color: #5CB3FF; }")
|
self.time.setStyleSheet("QLabel { color: #5CB3FF; }")
|
||||||
@ -161,19 +165,18 @@ class MessageItem(QtWidgets.QWidget):
|
|||||||
def mouseReleaseEvent(self, event):
|
def mouseReleaseEvent(self, event):
|
||||||
if event.button() == QtCore.Qt.RightButton and event.x() > self.time.x():
|
if event.button() == QtCore.Qt.RightButton and event.x() > self.time.x():
|
||||||
self.listMenu = QtWidgets.QMenu()
|
self.listMenu = QtWidgets.QMenu()
|
||||||
delete_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Delete message'))
|
delete_item = self.listMenu.addAction(util_ui.tr('Delete message'))
|
||||||
delete_item.triggered.connect(self.delete)
|
delete_item.triggered.connect(self.delete)
|
||||||
parent_position = self.time.mapToGlobal(QtCore.QPoint(0, 0))
|
parent_position = self.time.mapToGlobal(QtCore.QPoint(0, 0))
|
||||||
self.listMenu.move(parent_position)
|
self.listMenu.move(parent_position)
|
||||||
self.listMenu.show()
|
self.listMenu.show()
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
pr = profile.Profile.get_instance()
|
self._delete_action(self._message)
|
||||||
pr.delete_message(self._time)
|
|
||||||
|
|
||||||
def mark_as_sent(self):
|
def mark_as_sent(self):
|
||||||
if self.t:
|
if self.t:
|
||||||
self.time.setText(convert_time(self._time))
|
self.time.setText(util.convert_time(self._time))
|
||||||
self.t = False
|
self.t = False
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@ -212,153 +215,69 @@ class MessageItem(QtWidgets.QWidget):
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
class ContactItem(QtWidgets.QWidget):
|
|
||||||
"""
|
|
||||||
Contact in friends list
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
|
||||||
QtWidgets.QWidget.__init__(self, parent)
|
|
||||||
mode = settings.Settings.get_instance()['compact_mode']
|
|
||||||
self.setBaseSize(QtCore.QSize(250, 40 if mode else 70))
|
|
||||||
self.avatar_label = QtWidgets.QLabel(self)
|
|
||||||
size = 32 if mode else 64
|
|
||||||
self.avatar_label.setGeometry(QtCore.QRect(3, 4, size, size))
|
|
||||||
self.avatar_label.setScaledContents(False)
|
|
||||||
self.avatar_label.setAlignment(QtCore.Qt.AlignCenter)
|
|
||||||
self.name = DataLabel(self)
|
|
||||||
self.name.setGeometry(QtCore.QRect(50 if mode else 75, 3 if mode else 10, 150, 15 if mode else 25))
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setFamily(settings.Settings.get_instance()['font'])
|
|
||||||
font.setPointSize(10 if mode else 12)
|
|
||||||
font.setBold(True)
|
|
||||||
self.name.setFont(font)
|
|
||||||
self.status_message = DataLabel(self)
|
|
||||||
self.status_message.setGeometry(QtCore.QRect(50 if mode else 75, 20 if mode else 30, 170, 15 if mode else 20))
|
|
||||||
font.setPointSize(10)
|
|
||||||
font.setBold(False)
|
|
||||||
self.status_message.setFont(font)
|
|
||||||
self.connection_status = StatusCircle(self)
|
|
||||||
self.connection_status.setGeometry(QtCore.QRect(230, -2 if mode else 5, 32, 32))
|
|
||||||
self.messages = UnreadMessagesCount(self)
|
|
||||||
self.messages.setGeometry(QtCore.QRect(20 if mode else 52, 20 if mode else 50, 30, 20))
|
|
||||||
|
|
||||||
|
|
||||||
class StatusCircle(QtWidgets.QWidget):
|
|
||||||
"""
|
|
||||||
Connection status
|
|
||||||
"""
|
|
||||||
def __init__(self, parent):
|
|
||||||
QtWidgets.QWidget.__init__(self, parent)
|
|
||||||
self.setGeometry(0, 0, 32, 32)
|
|
||||||
self.label = QtWidgets.QLabel(self)
|
|
||||||
self.label.setGeometry(QtCore.QRect(0, 0, 32, 32))
|
|
||||||
self.unread = False
|
|
||||||
|
|
||||||
def update(self, status, unread_messages=None):
|
|
||||||
if unread_messages is None:
|
|
||||||
unread_messages = self.unread
|
|
||||||
else:
|
|
||||||
self.unread = unread_messages
|
|
||||||
if status == TOX_USER_STATUS['NONE']:
|
|
||||||
name = 'online'
|
|
||||||
elif status == TOX_USER_STATUS['AWAY']:
|
|
||||||
name = 'idle'
|
|
||||||
elif status == TOX_USER_STATUS['BUSY']:
|
|
||||||
name = 'busy'
|
|
||||||
else:
|
|
||||||
name = 'offline'
|
|
||||||
if unread_messages:
|
|
||||||
name += '_notification'
|
|
||||||
self.label.setGeometry(QtCore.QRect(0, 0, 32, 32))
|
|
||||||
else:
|
|
||||||
self.label.setGeometry(QtCore.QRect(2, 0, 32, 32))
|
|
||||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(name))
|
|
||||||
self.label.setPixmap(pixmap)
|
|
||||||
|
|
||||||
|
|
||||||
class UnreadMessagesCount(QtWidgets.QWidget):
|
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
|
||||||
super(UnreadMessagesCount, self).__init__(parent)
|
|
||||||
self.resize(30, 20)
|
|
||||||
self.label = QtWidgets.QLabel(self)
|
|
||||||
self.label.setGeometry(QtCore.QRect(0, 0, 30, 20))
|
|
||||||
self.label.setVisible(False)
|
|
||||||
font = QtGui.QFont()
|
|
||||||
font.setFamily(settings.Settings.get_instance()['font'])
|
|
||||||
font.setPointSize(12)
|
|
||||||
font.setBold(True)
|
|
||||||
self.label.setFont(font)
|
|
||||||
self.label.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignCenter)
|
|
||||||
color = settings.Settings.get_instance()['unread_color']
|
|
||||||
self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }')
|
|
||||||
|
|
||||||
def update(self, messages_count):
|
|
||||||
color = settings.Settings.get_instance()['unread_color']
|
|
||||||
self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }')
|
|
||||||
if messages_count:
|
|
||||||
self.label.setVisible(True)
|
|
||||||
self.label.setText(str(messages_count))
|
|
||||||
else:
|
|
||||||
self.label.setVisible(False)
|
|
||||||
|
|
||||||
|
|
||||||
class FileTransferItem(QtWidgets.QListWidget):
|
class FileTransferItem(QtWidgets.QListWidget):
|
||||||
|
|
||||||
def __init__(self, file_name, size, time, user, friend_number, file_number, state, width, parent=None):
|
def __init__(self, transfer_message, file_transfer_handler, settings, width, parent=None):
|
||||||
|
|
||||||
QtWidgets.QListWidget.__init__(self, parent)
|
QtWidgets.QListWidget.__init__(self, parent)
|
||||||
|
self._file_transfer_handler = file_transfer_handler
|
||||||
self.resize(QtCore.QSize(width, 34))
|
self.resize(QtCore.QSize(width, 34))
|
||||||
if state == TOX_FILE_TRANSFER_STATE['CANCELLED']:
|
if transfer_message.state == FILE_TRANSFER_STATE['CANCELLED']:
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
|
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
|
||||||
elif state in PAUSED_FILE_TRANSFERS:
|
elif transfer_message.state in PAUSED_FILE_TRANSFERS:
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
|
self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
|
||||||
else:
|
else:
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
||||||
self.state = state
|
self.state = transfer_message.state
|
||||||
|
|
||||||
self.name = DataLabel(self)
|
self.name = DataLabel(self)
|
||||||
self.name.setGeometry(QtCore.QRect(3, 7, 95, 25))
|
self.name.setGeometry(QtCore.QRect(3, 7, 95, 25))
|
||||||
self.name.setTextFormat(QtCore.Qt.PlainText)
|
self.name.setTextFormat(QtCore.Qt.PlainText)
|
||||||
font = QtGui.QFont()
|
font = QtGui.QFont()
|
||||||
font.setFamily(settings.Settings.get_instance()['font'])
|
font.setFamily(settings['font'])
|
||||||
font.setPointSize(11)
|
font.setPointSize(11)
|
||||||
font.setBold(True)
|
font.setBold(True)
|
||||||
self.name.setFont(font)
|
self.name.setFont(font)
|
||||||
self.name.setText(user)
|
self.name.setText(transfer_message.author.name)
|
||||||
|
|
||||||
self.time = QtWidgets.QLabel(self)
|
self.time = QtWidgets.QLabel(self)
|
||||||
self.time.setGeometry(QtCore.QRect(width - 60, 7, 50, 25))
|
self.time.setGeometry(QtCore.QRect(width - 60, 7, 50, 25))
|
||||||
font.setPointSize(10)
|
font.setPointSize(10)
|
||||||
font.setBold(False)
|
font.setBold(False)
|
||||||
self.time.setFont(font)
|
self.time.setFont(font)
|
||||||
self.time.setText(convert_time(time))
|
self.time.setText(util.convert_time(transfer_message.time))
|
||||||
|
|
||||||
self.cancel = QtWidgets.QPushButton(self)
|
self.cancel = QtWidgets.QPushButton(self)
|
||||||
self.cancel.setGeometry(QtCore.QRect(width - 125, 2, 30, 30))
|
self.cancel.setGeometry(QtCore.QRect(width - 125, 2, 30, 30))
|
||||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/decline.png')
|
pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'decline.png'))
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
self.cancel.setIcon(icon)
|
self.cancel.setIcon(icon)
|
||||||
self.cancel.setIconSize(QtCore.QSize(30, 30))
|
self.cancel.setIconSize(QtCore.QSize(30, 30))
|
||||||
self.cancel.setVisible(state in ACTIVE_FILE_TRANSFERS)
|
self.cancel.setVisible(transfer_message.state in ACTIVE_FILE_TRANSFERS or
|
||||||
self.cancel.clicked.connect(lambda: self.cancel_transfer(friend_number, file_number))
|
transfer_message.state == FILE_TRANSFER_STATE['UNSENT'])
|
||||||
|
self.cancel.clicked.connect(
|
||||||
|
lambda: self.cancel_transfer(transfer_message.friend_number, transfer_message.file_number))
|
||||||
self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}')
|
self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}')
|
||||||
|
|
||||||
self.accept_or_pause = QtWidgets.QPushButton(self)
|
self.accept_or_pause = QtWidgets.QPushButton(self)
|
||||||
self.accept_or_pause.setGeometry(QtCore.QRect(width - 170, 2, 30, 30))
|
self.accept_or_pause.setGeometry(QtCore.QRect(width - 170, 2, 30, 30))
|
||||||
if state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
|
if transfer_message.state == FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
|
||||||
self.accept_or_pause.setVisible(True)
|
self.accept_or_pause.setVisible(True)
|
||||||
self.button_update('accept')
|
self.button_update('accept')
|
||||||
elif state in DO_NOT_SHOW_ACCEPT_BUTTON:
|
elif transfer_message.state in DO_NOT_SHOW_ACCEPT_BUTTON:
|
||||||
self.accept_or_pause.setVisible(False)
|
self.accept_or_pause.setVisible(False)
|
||||||
elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: # setup for continue
|
elif transfer_message.state == FILE_TRANSFER_STATE['PAUSED_BY_USER']: # setup for continue
|
||||||
self.accept_or_pause.setVisible(True)
|
self.accept_or_pause.setVisible(True)
|
||||||
self.button_update('resume')
|
self.button_update('resume')
|
||||||
|
elif transfer_message.state == FILE_TRANSFER_STATE['UNSENT']:
|
||||||
|
self.accept_or_pause.setVisible(False)
|
||||||
|
self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
|
||||||
else: # pause
|
else: # pause
|
||||||
self.accept_or_pause.setVisible(True)
|
self.accept_or_pause.setVisible(True)
|
||||||
self.button_update('pause')
|
self.button_update('pause')
|
||||||
self.accept_or_pause.clicked.connect(lambda: self.accept_or_pause_transfer(friend_number, file_number, size))
|
self.accept_or_pause.clicked.connect(
|
||||||
|
lambda: self.accept_or_pause_transfer(transfer_message.friend_number, transfer_message.file_number,
|
||||||
|
transfer_message.size))
|
||||||
|
|
||||||
self.accept_or_pause.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none}')
|
self.accept_or_pause.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none}')
|
||||||
|
|
||||||
@ -366,64 +285,60 @@ class FileTransferItem(QtWidgets.QListWidget):
|
|||||||
self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20))
|
self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20))
|
||||||
self.pb.setValue(0)
|
self.pb.setValue(0)
|
||||||
self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }')
|
self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }')
|
||||||
self.pb.setVisible(state in SHOW_PROGRESS_BAR)
|
self.pb.setVisible(transfer_message.state in SHOW_PROGRESS_BAR)
|
||||||
|
|
||||||
self.file_name = DataLabel(self)
|
self.file_name = DataLabel(self)
|
||||||
self.file_name.setGeometry(QtCore.QRect(210, 7, width - 420, 20))
|
self.file_name.setGeometry(QtCore.QRect(210, 7, width - 420, 20))
|
||||||
font.setPointSize(12)
|
font.setPointSize(12)
|
||||||
self.file_name.setFont(font)
|
self.file_name.setFont(font)
|
||||||
file_size = size // 1024
|
file_size = transfer_message.size // 1024
|
||||||
if not file_size:
|
if not file_size:
|
||||||
file_size = '{}B'.format(size)
|
file_size = '{}B'.format(transfer_message.size)
|
||||||
elif file_size >= 1024:
|
elif file_size >= 1024:
|
||||||
file_size = '{}MB'.format(file_size // 1024)
|
file_size = '{}MB'.format(file_size // 1024)
|
||||||
else:
|
else:
|
||||||
file_size = '{}KB'.format(file_size)
|
file_size = '{}KB'.format(file_size)
|
||||||
file_data = '{} {}'.format(file_size, file_name)
|
file_data = '{} {}'.format(file_size, transfer_message.file_name)
|
||||||
self.file_name.setText(file_data)
|
self.file_name.setText(file_data)
|
||||||
self.file_name.setToolTip(file_name)
|
self.file_name.setToolTip(transfer_message.file_name)
|
||||||
self.saved_name = file_name
|
self.saved_name = transfer_message.file_name
|
||||||
self.time_left = QtWidgets.QLabel(self)
|
self.time_left = QtWidgets.QLabel(self)
|
||||||
self.time_left.setGeometry(QtCore.QRect(width - 92, 7, 30, 20))
|
self.time_left.setGeometry(QtCore.QRect(width - 92, 7, 30, 20))
|
||||||
font.setPointSize(10)
|
font.setPointSize(10)
|
||||||
self.time_left.setFont(font)
|
self.time_left.setFont(font)
|
||||||
self.time_left.setVisible(state == TOX_FILE_TRANSFER_STATE['RUNNING'])
|
self.time_left.setVisible(transfer_message.state == FILE_TRANSFER_STATE['RUNNING'])
|
||||||
self.setFocusPolicy(QtCore.Qt.NoFocus)
|
self.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||||
self.paused = False
|
self.paused = False
|
||||||
|
|
||||||
def cancel_transfer(self, friend_number, file_number):
|
def cancel_transfer(self, friend_number, file_number):
|
||||||
pr = profile.Profile.get_instance()
|
self._file_transfer_handler.cancel_transfer(friend_number, file_number)
|
||||||
pr.cancel_transfer(friend_number, file_number)
|
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
|
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
|
||||||
self.cancel.setVisible(False)
|
self.cancel.setVisible(False)
|
||||||
self.accept_or_pause.setVisible(False)
|
self.accept_or_pause.setVisible(False)
|
||||||
self.pb.setVisible(False)
|
self.pb.setVisible(False)
|
||||||
|
|
||||||
def accept_or_pause_transfer(self, friend_number, file_number, size):
|
def accept_or_pause_transfer(self, friend_number, file_number, size):
|
||||||
if self.state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
|
if self.state == FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
|
||||||
directory = QtWidgets.QFileDialog.getExistingDirectory(self,
|
directory = util_ui.directory_dialog(util_ui.tr('Choose folder'))
|
||||||
QtWidgets.QApplication.translate("MainWindow", 'Choose folder'),
|
|
||||||
curr_directory(),
|
|
||||||
QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
|
|
||||||
self.pb.setVisible(True)
|
self.pb.setVisible(True)
|
||||||
if directory:
|
if directory:
|
||||||
pr = profile.Profile.get_instance()
|
self._file_transfer_handler.accept_transfer(directory + '/' + self.saved_name,
|
||||||
pr.accept_transfer(self, directory + '/' + self.saved_name, friend_number, file_number, size)
|
friend_number, file_number, size)
|
||||||
self.button_update('pause')
|
self.button_update('pause')
|
||||||
elif self.state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: # resume
|
elif self.state == FILE_TRANSFER_STATE['PAUSED_BY_USER']: # resume
|
||||||
self.paused = False
|
self.paused = False
|
||||||
profile.Profile.get_instance().resume_transfer(friend_number, file_number)
|
self._file_transfer_handler.resume_transfer(friend_number, file_number)
|
||||||
self.button_update('pause')
|
self.button_update('pause')
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['RUNNING']
|
self.state = FILE_TRANSFER_STATE['RUNNING']
|
||||||
else: # pause
|
else: # pause
|
||||||
self.paused = True
|
self.paused = True
|
||||||
self.state = TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']
|
self.state = FILE_TRANSFER_STATE['PAUSED_BY_USER']
|
||||||
profile.Profile.get_instance().pause_transfer(friend_number, file_number)
|
self._file_transfer_handler.pause_transfer(friend_number, file_number)
|
||||||
self.button_update('resume')
|
self.button_update('resume')
|
||||||
self.accept_or_pause.clearFocus()
|
self.accept_or_pause.clearFocus()
|
||||||
|
|
||||||
def button_update(self, path):
|
def button_update(self, path):
|
||||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(path))
|
pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), '{}.png'.format(path)))
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
self.accept_or_pause.setIcon(icon)
|
self.accept_or_pause.setIcon(icon)
|
||||||
self.accept_or_pause.setIconSize(QtCore.QSize(30, 30))
|
self.accept_or_pause.setIconSize(QtCore.QSize(30, 30))
|
||||||
@ -434,31 +349,31 @@ class FileTransferItem(QtWidgets.QListWidget):
|
|||||||
m, s = divmod(time, 60)
|
m, s = divmod(time, 60)
|
||||||
self.time_left.setText('{0:02d}:{1:02d}'.format(m, s))
|
self.time_left.setText('{0:02d}:{1:02d}'.format(m, s))
|
||||||
if self.state != state and self.state in ACTIVE_FILE_TRANSFERS:
|
if self.state != state and self.state in ACTIVE_FILE_TRANSFERS:
|
||||||
if state == TOX_FILE_TRANSFER_STATE['CANCELLED']:
|
if state == FILE_TRANSFER_STATE['CANCELLED']:
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
|
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
|
||||||
self.cancel.setVisible(False)
|
self.cancel.setVisible(False)
|
||||||
self.accept_or_pause.setVisible(False)
|
self.accept_or_pause.setVisible(False)
|
||||||
self.pb.setVisible(False)
|
self.pb.setVisible(False)
|
||||||
self.state = state
|
self.state = state
|
||||||
self.time_left.setVisible(False)
|
self.time_left.setVisible(False)
|
||||||
elif state == TOX_FILE_TRANSFER_STATE['FINISHED']:
|
elif state == FILE_TRANSFER_STATE['FINISHED']:
|
||||||
self.accept_or_pause.setVisible(False)
|
self.accept_or_pause.setVisible(False)
|
||||||
self.pb.setVisible(False)
|
self.pb.setVisible(False)
|
||||||
self.cancel.setVisible(False)
|
self.cancel.setVisible(False)
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
||||||
self.state = state
|
self.state = state
|
||||||
self.time_left.setVisible(False)
|
self.time_left.setVisible(False)
|
||||||
elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND']:
|
elif state == FILE_TRANSFER_STATE['PAUSED_BY_FRIEND']:
|
||||||
self.accept_or_pause.setVisible(False)
|
self.accept_or_pause.setVisible(False)
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
|
self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
|
||||||
self.state = state
|
self.state = state
|
||||||
self.time_left.setVisible(False)
|
self.time_left.setVisible(False)
|
||||||
elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']:
|
elif state == FILE_TRANSFER_STATE['PAUSED_BY_USER']:
|
||||||
self.button_update('resume') # setup button continue
|
self.button_update('resume') # setup button continue
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
||||||
self.state = state
|
self.state = state
|
||||||
self.time_left.setVisible(False)
|
self.time_left.setVisible(False)
|
||||||
elif state == TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']:
|
elif state == FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']:
|
||||||
self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
|
self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
|
||||||
self.accept_or_pause.setVisible(False)
|
self.accept_or_pause.setVisible(False)
|
||||||
self.time_left.setVisible(False)
|
self.time_left.setVisible(False)
|
||||||
@ -471,31 +386,27 @@ class FileTransferItem(QtWidgets.QListWidget):
|
|||||||
self.state = state
|
self.state = state
|
||||||
self.time_left.setVisible(True)
|
self.time_left.setVisible(True)
|
||||||
|
|
||||||
def mark_as_sent(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class UnsentFileItem(FileTransferItem):
|
class UnsentFileItem(FileTransferItem):
|
||||||
|
|
||||||
def __init__(self, file_name, size, user, time, width, parent=None):
|
def __init__(self, transfer_message, file_transfer_handler, settings, width, parent=None):
|
||||||
super(UnsentFileItem, self).__init__(file_name, size, time, user, -1, -1,
|
super().__init__(transfer_message, file_transfer_handler, settings, width, parent)
|
||||||
TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'], width, parent)
|
|
||||||
self._time = time
|
self._time = time
|
||||||
self.pb.setVisible(False)
|
movie = QtGui.QMovie(util.join_path(util.get_images_directory(), 'spinner.gif'))
|
||||||
movie = QtGui.QMovie(curr_directory() + '/images/spinner.gif')
|
|
||||||
self.time.setMovie(movie)
|
self.time.setMovie(movie)
|
||||||
movie.start()
|
movie.start()
|
||||||
|
self._message_id = transfer_message.message_id
|
||||||
|
self._friend_number = transfer_message.friend_number
|
||||||
|
|
||||||
def cancel_transfer(self, *args):
|
def cancel_transfer(self, *args):
|
||||||
pr = profile.Profile.get_instance()
|
self._file_transfer_handler.cancel_not_started_transfer(self._friend_number, self._message_id)
|
||||||
pr.cancel_not_started_transfer(self._time)
|
|
||||||
|
|
||||||
|
|
||||||
class InlineImageItem(QtWidgets.QScrollArea):
|
class InlineImageItem(QtWidgets.QScrollArea):
|
||||||
|
|
||||||
def __init__(self, data, width, elem):
|
def __init__(self, data, width, elem, parent=None):
|
||||||
|
|
||||||
QtWidgets.QScrollArea.__init__(self)
|
QtWidgets.QScrollArea.__init__(self, parent)
|
||||||
self.setFocusPolicy(QtCore.Qt.NoFocus)
|
self.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||||
self._elem = elem
|
self._elem = elem
|
||||||
self._image_label = QtWidgets.QLabel(self)
|
self._image_label = QtWidgets.QLabel(self)
|
||||||
@ -532,14 +443,7 @@ class InlineImageItem(QtWidgets.QScrollArea):
|
|||||||
self._full_size = not self._full_size
|
self._full_size = not self._full_size
|
||||||
self._elem.setSizeHint(QtCore.QSize(self.width(), self.height()))
|
self._elem.setSizeHint(QtCore.QSize(self.width(), self.height()))
|
||||||
elif event.button() == QtCore.Qt.RightButton: # save inline
|
elif event.button() == QtCore.Qt.RightButton: # save inline
|
||||||
directory = QtWidgets.QFileDialog.getExistingDirectory(self,
|
directory = util_ui.directory_dialog(util_ui.tr('Choose folder'))
|
||||||
QtWidgets.QApplication.translate("MainWindow",
|
|
||||||
'Choose folder'),
|
|
||||||
curr_directory(),
|
|
||||||
QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog)
|
|
||||||
if directory:
|
if directory:
|
||||||
fl = QtCore.QFile(directory + '/toxygen_inline_' + curr_time().replace(':', '_') + '.png')
|
fl = QtCore.QFile(directory + '/toxygen_inline_' + util.curr_time().replace(':', '_') + '.png')
|
||||||
self._pixmap.save(fl, 'PNG')
|
self._pixmap.save(fl, 'PNG')
|
||||||
|
|
||||||
def mark_as_sent(self):
|
|
||||||
return False
|
|
@ -1,25 +1,27 @@
|
|||||||
from widgets import CenteredWidget, LineEdit
|
from ui.widgets import CenteredWidget, LineEdit, DialogWithResult
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
import utils.ui as util_ui
|
||||||
|
|
||||||
|
|
||||||
class PasswordArea(LineEdit):
|
class PasswordArea(LineEdit):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super(PasswordArea, self).__init__(parent)
|
super().__init__(parent)
|
||||||
self.parent = parent
|
self._parent = parent
|
||||||
self.setEchoMode(QtWidgets.QLineEdit.Password)
|
self.setEchoMode(QtWidgets.QLineEdit.Password)
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
if event.key() == QtCore.Qt.Key_Return:
|
if event.key() == QtCore.Qt.Key_Return:
|
||||||
self.parent.button_click()
|
self._parent.button_click()
|
||||||
else:
|
else:
|
||||||
super(PasswordArea, self).keyPressEvent(event)
|
super().keyPressEvent(event)
|
||||||
|
|
||||||
|
|
||||||
class PasswordScreenBase(CenteredWidget):
|
class PasswordScreenBase(CenteredWidget, DialogWithResult):
|
||||||
|
|
||||||
def __init__(self, encrypt):
|
def __init__(self, encrypt):
|
||||||
super(PasswordScreenBase, self).__init__()
|
CenteredWidget.__init__(self)
|
||||||
|
DialogWithResult.__init__(self)
|
||||||
self._encrypt = encrypt
|
self._encrypt = encrypt
|
||||||
self.initUI()
|
self.initUI()
|
||||||
|
|
||||||
@ -36,7 +38,7 @@ class PasswordScreenBase(CenteredWidget):
|
|||||||
|
|
||||||
self.button = QtWidgets.QPushButton(self)
|
self.button = QtWidgets.QPushButton(self)
|
||||||
self.button.setGeometry(QtCore.QRect(30, 90, 300, 30))
|
self.button.setGeometry(QtCore.QRect(30, 90, 300, 30))
|
||||||
self.button.setText('OK')
|
self.button.setText(util_ui.tr('OK'))
|
||||||
self.button.clicked.connect(self.button_click)
|
self.button.clicked.connect(self.button_click)
|
||||||
|
|
||||||
self.warning = QtWidgets.QLabel(self)
|
self.warning = QtWidgets.QLabel(self)
|
||||||
@ -58,28 +60,27 @@ class PasswordScreenBase(CenteredWidget):
|
|||||||
super(PasswordScreenBase, self).keyPressEvent(event)
|
super(PasswordScreenBase, self).keyPressEvent(event)
|
||||||
|
|
||||||
def retranslateUi(self):
|
def retranslateUi(self):
|
||||||
self.setWindowTitle(QtWidgets.QApplication.translate("pass", "Enter password"))
|
self.setWindowTitle(util_ui.tr('Enter password'))
|
||||||
self.enter_pass.setText(QtWidgets.QApplication.translate("pass", "Password:"))
|
self.enter_pass.setText(util_ui.tr('Password:'))
|
||||||
self.warning.setText(QtWidgets.QApplication.translate("pass", "Incorrect password"))
|
self.warning.setText(util_ui.tr('Incorrect password'))
|
||||||
|
|
||||||
|
|
||||||
class PasswordScreen(PasswordScreenBase):
|
class PasswordScreen(PasswordScreenBase):
|
||||||
|
|
||||||
def __init__(self, encrypt, data):
|
def __init__(self, encrypt, data):
|
||||||
super(PasswordScreen, self).__init__(encrypt)
|
super().__init__(encrypt)
|
||||||
self._data = data
|
self._data = data
|
||||||
|
|
||||||
def button_click(self):
|
def button_click(self):
|
||||||
if self.password.text():
|
if self.password.text():
|
||||||
try:
|
try:
|
||||||
self._encrypt.set_password(self.password.text())
|
self._encrypt.set_password(self.password.text())
|
||||||
new_data = self._encrypt.pass_decrypt(self._data[0])
|
new_data = self._encrypt.pass_decrypt(self._data)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self.warning.setVisible(True)
|
self.warning.setVisible(True)
|
||||||
print('Decryption error:', ex)
|
print('Decryption error:', ex)
|
||||||
else:
|
else:
|
||||||
self._data[0] = new_data
|
self.close_with_result(new_data)
|
||||||
self.close()
|
|
||||||
|
|
||||||
|
|
||||||
class UnlockAppScreen(PasswordScreenBase):
|
class UnlockAppScreen(PasswordScreenBase):
|
||||||
@ -129,16 +130,15 @@ class SetProfilePasswordScreen(CenteredWidget):
|
|||||||
self.warning.setStyleSheet('QLabel { color: #BC1C1C; }')
|
self.warning.setStyleSheet('QLabel { color: #BC1C1C; }')
|
||||||
|
|
||||||
def retranslateUi(self):
|
def retranslateUi(self):
|
||||||
self.setWindowTitle(QtWidgets.QApplication.translate("PasswordScreen", "Profile password"))
|
self.setWindowTitle(util_ui.tr('Profile password'))
|
||||||
self.password.setPlaceholderText(
|
self.password.setPlaceholderText(
|
||||||
QtWidgets.QApplication.translate("PasswordScreen", "Password (at least 8 symbols)"))
|
util_ui.tr('Password (at least 8 symbols)'))
|
||||||
self.confirm_password.setPlaceholderText(
|
self.confirm_password.setPlaceholderText(
|
||||||
QtWidgets.QApplication.translate("PasswordScreen", "Confirm password"))
|
util_ui.tr('Confirm password'))
|
||||||
self.set_password.setText(
|
self.set_password.setText(
|
||||||
QtWidgets.QApplication.translate("PasswordScreen", "Set password"))
|
util_ui.tr('Set password'))
|
||||||
self.not_match.setText(QtWidgets.QApplication.translate("PasswordScreen", "Passwords do not match"))
|
self.not_match.setText(util_ui.tr('Passwords do not match'))
|
||||||
self.warning.setText(
|
self.warning.setText(util_ui.tr('There is no way to recover lost passwords'))
|
||||||
QtWidgets.QApplication.translate("PasswordScreen", "There is no way to recover lost passwords"))
|
|
||||||
|
|
||||||
def new_password(self):
|
def new_password(self):
|
||||||
if self.password.text() == self.confirm_password.text():
|
if self.password.text() == self.confirm_password.text():
|
||||||
@ -146,9 +146,8 @@ class SetProfilePasswordScreen(CenteredWidget):
|
|||||||
self._encrypt.set_password(self.password.text())
|
self._encrypt.set_password(self.password.text())
|
||||||
self.close()
|
self.close()
|
||||||
else:
|
else:
|
||||||
self.not_match.setText(
|
self.not_match.setText(util_ui.tr('Password must be at least 8 symbols'))
|
||||||
QtWidgets.QApplication.translate("PasswordScreen", "Password must be at least 8 symbols"))
|
|
||||||
self.not_match.setVisible(True)
|
self.not_match.setVisible(True)
|
||||||
else:
|
else:
|
||||||
self.not_match.setText(QtWidgets.QApplication.translate("PasswordScreen", "Passwords do not match"))
|
self.not_match.setText(util_ui.tr('Passwords do not match'))
|
||||||
self.not_match.setVisible(True)
|
self.not_match.setVisible(True)
|
111
toxygen/ui/peer_screen.py
Normal file
111
toxygen/ui/peer_screen.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
from ui.widgets import CenteredWidget
|
||||||
|
from PyQt5 import uic
|
||||||
|
import utils.util as util
|
||||||
|
import utils.ui as util_ui
|
||||||
|
from ui.contact_items import *
|
||||||
|
import wrapper.toxcore_enums_and_consts as consts
|
||||||
|
|
||||||
|
|
||||||
|
class PeerScreen(CenteredWidget):
|
||||||
|
|
||||||
|
def __init__(self, contacts_manager, groups_service, group, peer_id):
|
||||||
|
super().__init__()
|
||||||
|
self._contacts_manager = contacts_manager
|
||||||
|
self._groups_service = groups_service
|
||||||
|
self._group = group
|
||||||
|
self._peer = group.get_peer_by_id(peer_id)
|
||||||
|
|
||||||
|
self._roles = {
|
||||||
|
TOX_GROUP_ROLE['FOUNDER']: util_ui.tr('Administrator'),
|
||||||
|
TOX_GROUP_ROLE['MODERATOR']: util_ui.tr('Moderator'),
|
||||||
|
TOX_GROUP_ROLE['USER']: util_ui.tr('User'),
|
||||||
|
TOX_GROUP_ROLE['OBSERVER']: util_ui.tr('Observer')
|
||||||
|
}
|
||||||
|
|
||||||
|
uic.loadUi(util.get_views_path('peer_screen'), self)
|
||||||
|
self._update_ui()
|
||||||
|
|
||||||
|
def _update_ui(self):
|
||||||
|
self.statusCircle = StatusCircle(self)
|
||||||
|
self.statusCircle.setGeometry(50, 15, 30, 30)
|
||||||
|
|
||||||
|
self.statusCircle.update(self._peer.status)
|
||||||
|
self.peerNameLabel.setText(self._peer.name)
|
||||||
|
self.ignorePeerCheckBox.setChecked(self._peer.is_muted)
|
||||||
|
self.ignorePeerCheckBox.clicked.connect(self._toggle_ignore)
|
||||||
|
self.sendPrivateMessagePushButton.clicked.connect(self._send_private_message)
|
||||||
|
self.copyPublicKeyPushButton.clicked.connect(self._copy_public_key)
|
||||||
|
self.roleNameLabel.setText(self._get_role_name())
|
||||||
|
can_change_role_or_ban = self._can_change_role_or_ban()
|
||||||
|
self.rolesComboBox.setVisible(can_change_role_or_ban)
|
||||||
|
self.roleNameLabel.setVisible(not can_change_role_or_ban)
|
||||||
|
self.banGroupBox.setEnabled(can_change_role_or_ban)
|
||||||
|
self.banPushButton.clicked.connect(self._ban_peer)
|
||||||
|
self.kickPushButton.clicked.connect(self._kick_peer)
|
||||||
|
|
||||||
|
self._retranslate_ui()
|
||||||
|
|
||||||
|
self.rolesComboBox.currentIndexChanged.connect(self._role_set)
|
||||||
|
|
||||||
|
def _retranslate_ui(self):
|
||||||
|
self.setWindowTitle(util_ui.tr('Peer details'))
|
||||||
|
self.ignorePeerCheckBox.setText(util_ui.tr('Ignore peer'))
|
||||||
|
self.roleLabel.setText(util_ui.tr('Role:'))
|
||||||
|
self.copyPublicKeyPushButton.setText(util_ui.tr('Copy public key'))
|
||||||
|
self.sendPrivateMessagePushButton.setText(util_ui.tr('Send private message'))
|
||||||
|
self.banPushButton.setText(util_ui.tr('Ban peer'))
|
||||||
|
self.kickPushButton.setText(util_ui.tr('Kick peer'))
|
||||||
|
self.banGroupBox.setTitle(util_ui.tr('Ban peer'))
|
||||||
|
self.ipBanRadioButton.setText(util_ui.tr('IP'))
|
||||||
|
self.nickBanRadioButton.setText(util_ui.tr('Nickname'))
|
||||||
|
self.pkBanRadioButton.setText(util_ui.tr('Public key'))
|
||||||
|
|
||||||
|
self.rolesComboBox.clear()
|
||||||
|
index = self._group.get_self_peer().role
|
||||||
|
roles = list(self._roles.values())
|
||||||
|
for role in roles[index + 1:]:
|
||||||
|
self.rolesComboBox.addItem(role)
|
||||||
|
self.rolesComboBox.setCurrentIndex(self._peer.role - index - 1)
|
||||||
|
|
||||||
|
def _can_change_role_or_ban(self):
|
||||||
|
self_peer = self._group.get_self_peer()
|
||||||
|
if self_peer.role > TOX_GROUP_ROLE['MODERATOR']:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self_peer.role < self._peer.role
|
||||||
|
|
||||||
|
def _role_set(self):
|
||||||
|
index = self.rolesComboBox.currentIndex()
|
||||||
|
all_roles_count = len(self._roles)
|
||||||
|
diff = all_roles_count - self.rolesComboBox.count()
|
||||||
|
self._groups_service.set_new_peer_role(self._group, self._peer, index + diff)
|
||||||
|
|
||||||
|
def _get_role_name(self):
|
||||||
|
return self._roles[self._peer.role]
|
||||||
|
|
||||||
|
def _toggle_ignore(self):
|
||||||
|
ignore = self.ignorePeerCheckBox.isChecked()
|
||||||
|
self._groups_service.toggle_ignore_peer(self._group, self._peer, ignore)
|
||||||
|
|
||||||
|
def _send_private_message(self):
|
||||||
|
self._contacts_manager.add_group_peer(self._group, self._peer)
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def _copy_public_key(self):
|
||||||
|
util_ui.copy_to_clipboard(self._peer.public_key)
|
||||||
|
|
||||||
|
def _ban_peer(self):
|
||||||
|
ban_type = self._get_ban_type()
|
||||||
|
self._groups_service.ban_peer(self._group, self._peer.id, ban_type)
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def _kick_peer(self):
|
||||||
|
self._groups_service.kick_peer(self._group, self._peer.id)
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def _get_ban_type(self):
|
||||||
|
if self.ipBanRadioButton.isChecked():
|
||||||
|
return consts.TOX_GROUP_BAN_TYPE['IP_PORT']
|
||||||
|
elif self.nickBanRadioButton.isChecked():
|
||||||
|
return consts.TOX_GROUP_BAN_TYPE['NICK']
|
||||||
|
return consts.TOX_GROUP_BAN_TYPE['PUBLIC_KEY']
|
157
toxygen/ui/profile_settings_screen.py
Normal file
157
toxygen/ui/profile_settings_screen.py
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
from ui.widgets import CenteredWidget
|
||||||
|
import utils.ui as util_ui
|
||||||
|
from utils.util import join_path, get_images_directory, get_views_path
|
||||||
|
from user_data.settings import Settings
|
||||||
|
from PyQt5 import QtGui, QtCore, uic
|
||||||
|
|
||||||
|
|
||||||
|
class ProfileSettings(CenteredWidget):
|
||||||
|
"""Form with profile settings such as name, status, TOX ID"""
|
||||||
|
def __init__(self, profile, profile_manager, settings, toxes):
|
||||||
|
super().__init__()
|
||||||
|
self._profile = profile
|
||||||
|
self._profile_manager = profile_manager
|
||||||
|
self._settings = settings
|
||||||
|
self._toxes = toxes
|
||||||
|
self._auto = False
|
||||||
|
|
||||||
|
uic.loadUi(get_views_path('profile_settings_screen'), self)
|
||||||
|
|
||||||
|
self._init_ui()
|
||||||
|
self.center()
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
self._profile.set_name(self.nameLineEdit.text())
|
||||||
|
self._profile.set_status_message(self.statusMessageLineEdit.text())
|
||||||
|
self._profile.set_status(self.statusComboBox.currentIndex())
|
||||||
|
|
||||||
|
def _init_ui(self):
|
||||||
|
self._auto = Settings.get_auto_profile() == self._profile_manager.get_path()
|
||||||
|
self.toxIdLabel.setText(self._profile.tox_id)
|
||||||
|
self.nameLineEdit.setText(self._profile.name)
|
||||||
|
self.statusMessageLineEdit.setText(self._profile.status_message)
|
||||||
|
self.defaultProfilePushButton.clicked.connect(self._toggle_auto_profile)
|
||||||
|
self.copyToxIdPushButton.clicked.connect(self._copy_tox_id)
|
||||||
|
self.copyPublicKeyPushButton.clicked.connect(self._copy_public_key)
|
||||||
|
self.changePasswordPushButton.clicked.connect(self._save_password)
|
||||||
|
self.exportProfilePushButton.clicked.connect(self._export_profile)
|
||||||
|
self.newNoSpamPushButton.clicked.connect(self._set_new_no_spam)
|
||||||
|
self.newAvatarPushButton.clicked.connect(self._set_avatar)
|
||||||
|
self.resetAvatarPushButton.clicked.connect(self._reset_avatar)
|
||||||
|
|
||||||
|
self.invalidPasswordsLabel.setVisible(False)
|
||||||
|
|
||||||
|
self._retranslate_ui()
|
||||||
|
|
||||||
|
if self._profile.status is not None:
|
||||||
|
self.statusComboBox.setCurrentIndex(self._profile.status)
|
||||||
|
else:
|
||||||
|
self.statusComboBox.setVisible(False)
|
||||||
|
|
||||||
|
def _retranslate_ui(self):
|
||||||
|
self.setWindowTitle(util_ui.tr("Profile settings"))
|
||||||
|
|
||||||
|
self.exportProfilePushButton.setText(util_ui.tr("Export profile"))
|
||||||
|
self.nameLabel.setText(util_ui.tr("Name:"))
|
||||||
|
self.statusLabel.setText(util_ui.tr("Status:"))
|
||||||
|
self.toxIdTitleLabel.setText(util_ui.tr("TOX ID:"))
|
||||||
|
self.copyToxIdPushButton.setText(util_ui.tr("Copy TOX ID"))
|
||||||
|
self.newAvatarPushButton.setText(util_ui.tr("New avatar"))
|
||||||
|
self.resetAvatarPushButton.setText(util_ui.tr("Reset avatar"))
|
||||||
|
self.newNoSpamPushButton.setText(util_ui.tr("New NoSpam"))
|
||||||
|
self.profilePasswordLabel.setText(util_ui.tr("Profile password"))
|
||||||
|
self.passwordLineEdit.setPlaceholderText(util_ui.tr("Password (at least 8 symbols)"))
|
||||||
|
self.confirmPasswordLineEdit.setPlaceholderText(util_ui.tr("Confirm password"))
|
||||||
|
self.changePasswordPushButton.setText(util_ui.tr("Set password"))
|
||||||
|
self.invalidPasswordsLabel.setText(util_ui.tr("Passwords do not match"))
|
||||||
|
self.emptyPasswordLabel.setText(util_ui.tr("Leaving blank will reset current password"))
|
||||||
|
self.warningLabel.setText(util_ui.tr("There is no way to recover lost passwords"))
|
||||||
|
self.statusComboBox.addItem(util_ui.tr("Online"))
|
||||||
|
self.statusComboBox.addItem(util_ui.tr("Away"))
|
||||||
|
self.statusComboBox.addItem(util_ui.tr("Busy"))
|
||||||
|
self.copyPublicKeyPushButton.setText(util_ui.tr("Copy public key"))
|
||||||
|
|
||||||
|
self._set_default_profile_button_text()
|
||||||
|
|
||||||
|
def _toggle_auto_profile(self):
|
||||||
|
if self._auto:
|
||||||
|
Settings.reset_auto_profile()
|
||||||
|
else:
|
||||||
|
Settings.set_auto_profile(self._profile_manager.get_path())
|
||||||
|
self._auto = not self._auto
|
||||||
|
self._set_default_profile_button_text()
|
||||||
|
|
||||||
|
def _set_default_profile_button_text(self):
|
||||||
|
if self._auto:
|
||||||
|
self.defaultProfilePushButton.setText(util_ui.tr("Mark as not default profile"))
|
||||||
|
else:
|
||||||
|
self.defaultProfilePushButton.setText(util_ui.tr("Mark as default profile"))
|
||||||
|
|
||||||
|
def _save_password(self):
|
||||||
|
password = self.passwordLineEdit.text()
|
||||||
|
confirm_password = self.confirmPasswordLineEdit.text()
|
||||||
|
if password == confirm_password:
|
||||||
|
if not len(password) or len(password) >= 8:
|
||||||
|
self._toxes.set_password(password)
|
||||||
|
self.close()
|
||||||
|
else:
|
||||||
|
self.invalidPasswordsLabel.setText(
|
||||||
|
util_ui.tr("Password must be at least 8 symbols"))
|
||||||
|
self.invalidPasswordsLabel.setVisible(True)
|
||||||
|
else:
|
||||||
|
self.invalidPasswordsLabel.setText(util_ui.tr("Passwords do not match"))
|
||||||
|
self.invalidPasswordsLabel.setVisible(True)
|
||||||
|
|
||||||
|
def _copy_tox_id(self):
|
||||||
|
util_ui.copy_to_clipboard(self._profile.tox_id)
|
||||||
|
|
||||||
|
icon = self._get_accept_icon()
|
||||||
|
self.copyToxIdPushButton.setIcon(icon)
|
||||||
|
self.copyToxIdPushButton.setIconSize(QtCore.QSize(10, 10))
|
||||||
|
|
||||||
|
def _copy_public_key(self):
|
||||||
|
util_ui.copy_to_clipboard(self._profile.tox_id[:64])
|
||||||
|
|
||||||
|
icon = self._get_accept_icon()
|
||||||
|
self.copyPublicKeyPushButton.setIcon(icon)
|
||||||
|
self.copyPublicKeyPushButton.setIconSize(QtCore.QSize(10, 10))
|
||||||
|
|
||||||
|
def _set_new_no_spam(self):
|
||||||
|
self.toxIdLabel.setText(self._profile.set_new_nospam())
|
||||||
|
|
||||||
|
def _reset_avatar(self):
|
||||||
|
self._profile.reset_avatar(self._settings['identicons'])
|
||||||
|
|
||||||
|
def _set_avatar(self):
|
||||||
|
choose = util_ui.tr("Choose avatar")
|
||||||
|
name = util_ui.file_dialog(choose, 'Images (*.png)')
|
||||||
|
if not name[0]:
|
||||||
|
return
|
||||||
|
bitmap = QtGui.QPixmap(name[0])
|
||||||
|
bitmap.scaled(QtCore.QSize(128, 128), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
|
||||||
|
|
||||||
|
byte_array = QtCore.QByteArray()
|
||||||
|
buffer = QtCore.QBuffer(byte_array)
|
||||||
|
buffer.open(QtCore.QIODevice.WriteOnly)
|
||||||
|
bitmap.save(buffer, 'PNG')
|
||||||
|
|
||||||
|
self._profile.set_avatar(bytes(byte_array.data()))
|
||||||
|
|
||||||
|
def _export_profile(self):
|
||||||
|
directory = util_ui.directory_dialog()
|
||||||
|
if not directory:
|
||||||
|
return
|
||||||
|
|
||||||
|
reply = util_ui.question(util_ui.tr('Do you want to move your profile to this location?'),
|
||||||
|
util_ui.tr('Use new path'))
|
||||||
|
|
||||||
|
self._settings.export(directory)
|
||||||
|
self._profile.export_db(directory)
|
||||||
|
self._profile_manager.export_profile(self._settings, directory, reply)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_accept_icon():
|
||||||
|
pixmap = QtGui.QPixmap(join_path(get_images_directory(), 'accept.png'))
|
||||||
|
|
||||||
|
return QtGui.QIcon(pixmap)
|
||||||
|
|
66
toxygen/ui/self_peer_screen.py
Normal file
66
toxygen/ui/self_peer_screen.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
from ui.widgets import CenteredWidget, LineEdit
|
||||||
|
from PyQt5 import uic
|
||||||
|
import utils.util as util
|
||||||
|
import utils.ui as util_ui
|
||||||
|
from ui.contact_items import *
|
||||||
|
|
||||||
|
|
||||||
|
class SelfPeerScreen(CenteredWidget):
|
||||||
|
|
||||||
|
def __init__(self, contacts_manager, groups_service, group):
|
||||||
|
super().__init__()
|
||||||
|
self._contacts_manager = contacts_manager
|
||||||
|
self._groups_service = groups_service
|
||||||
|
self._group = group
|
||||||
|
self._peer = group.get_self_peer()
|
||||||
|
self._roles = {
|
||||||
|
TOX_GROUP_ROLE['FOUNDER']: util_ui.tr('Administrator'),
|
||||||
|
TOX_GROUP_ROLE['MODERATOR']: util_ui.tr('Moderator'),
|
||||||
|
TOX_GROUP_ROLE['USER']: util_ui.tr('User'),
|
||||||
|
TOX_GROUP_ROLE['OBSERVER']: util_ui.tr('Observer')
|
||||||
|
}
|
||||||
|
|
||||||
|
uic.loadUi(util.get_views_path('self_peer_screen'), self)
|
||||||
|
self._update_ui()
|
||||||
|
|
||||||
|
def _update_ui(self):
|
||||||
|
self.lineEdit = LineEdit(self)
|
||||||
|
self.lineEdit.setGeometry(140, 40, 400, 30)
|
||||||
|
self.lineEdit.setText(self._peer.name)
|
||||||
|
self.lineEdit.textChanged.connect(self._nick_changed)
|
||||||
|
|
||||||
|
self.savePushButton.clicked.connect(self._save)
|
||||||
|
self.copyPublicKeyPushButton.clicked.connect(self._copy_public_key)
|
||||||
|
|
||||||
|
self._retranslate_ui()
|
||||||
|
|
||||||
|
self.statusComboBox.setCurrentIndex(self._peer.status)
|
||||||
|
|
||||||
|
def _retranslate_ui(self):
|
||||||
|
self.setWindowTitle(util_ui.tr('Change credentials in group'))
|
||||||
|
self.lineEdit.setPlaceholderText(util_ui.tr('Your nickname in group'))
|
||||||
|
self.nameLabel.setText(util_ui.tr('Name:'))
|
||||||
|
self.roleLabel.setText(util_ui.tr('Role:'))
|
||||||
|
self.statusLabel.setText(util_ui.tr('Status:'))
|
||||||
|
self.copyPublicKeyPushButton.setText(util_ui.tr('Copy public key'))
|
||||||
|
self.savePushButton.setText(util_ui.tr('Save'))
|
||||||
|
self.roleNameLabel.setText(self._get_role_name())
|
||||||
|
self.statusComboBox.addItem(util_ui.tr('Online'))
|
||||||
|
self.statusComboBox.addItem(util_ui.tr('Away'))
|
||||||
|
self.statusComboBox.addItem(util_ui.tr('Busy'))
|
||||||
|
|
||||||
|
def _get_role_name(self):
|
||||||
|
return self._roles[self._peer.role]
|
||||||
|
|
||||||
|
def _nick_changed(self):
|
||||||
|
nick = self.lineEdit.text()
|
||||||
|
self.savePushButton.setEnabled(bool(nick))
|
||||||
|
|
||||||
|
def _save(self):
|
||||||
|
nick = self.lineEdit.text()
|
||||||
|
status = self.statusComboBox.currentIndex()
|
||||||
|
self._groups_service.set_self_info(self._group, nick, status)
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def _copy_public_key(self):
|
||||||
|
util_ui.copy_to_clipboard(self._peer.public_key)
|
111
toxygen/ui/tray.py
Normal file
111
toxygen/ui/tray.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
from PyQt5 import QtWidgets, QtGui, QtCore
|
||||||
|
from utils.ui import tr
|
||||||
|
from utils.util import *
|
||||||
|
from ui.password_screen import UnlockAppScreen
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
|
||||||
|
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
|
||||||
|
|
||||||
|
leftClicked = QtCore.pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, icon, parent=None):
|
||||||
|
super().__init__(icon, parent)
|
||||||
|
self.activated.connect(self.icon_activated)
|
||||||
|
|
||||||
|
def icon_activated(self, reason):
|
||||||
|
if reason == QtWidgets.QSystemTrayIcon.Trigger:
|
||||||
|
self.leftClicked.emit()
|
||||||
|
|
||||||
|
|
||||||
|
class Menu(QtWidgets.QMenu):
|
||||||
|
|
||||||
|
def __init__(self, settings, profile, *args):
|
||||||
|
super().__init__(*args)
|
||||||
|
self._settings = settings
|
||||||
|
self._profile = profile
|
||||||
|
|
||||||
|
def new_status(self, status):
|
||||||
|
if not self._settings.locked:
|
||||||
|
self._profile.set_status(status)
|
||||||
|
self.about_to_show_handler()
|
||||||
|
self.hide()
|
||||||
|
|
||||||
|
def about_to_show_handler(self):
|
||||||
|
status = self._profile.status
|
||||||
|
act = self.act
|
||||||
|
if status is None or self._settings.locked:
|
||||||
|
self.actions()[1].setVisible(False)
|
||||||
|
else:
|
||||||
|
self.actions()[1].setVisible(True)
|
||||||
|
act.actions()[0].setChecked(False)
|
||||||
|
act.actions()[1].setChecked(False)
|
||||||
|
act.actions()[2].setChecked(False)
|
||||||
|
act.actions()[status].setChecked(True)
|
||||||
|
self.actions()[2].setVisible(not self._settings.locked)
|
||||||
|
|
||||||
|
def languageChange(self, *args, **kwargs):
|
||||||
|
self.actions()[0].setText(tr('Open Toxygen'))
|
||||||
|
self.actions()[1].setText(tr('Set status'))
|
||||||
|
self.actions()[2].setText(tr('Exit'))
|
||||||
|
self.act.actions()[0].setText(tr('Online'))
|
||||||
|
self.act.actions()[1].setText(tr('Away'))
|
||||||
|
self.act.actions()[2].setText(tr('Busy'))
|
||||||
|
|
||||||
|
|
||||||
|
def init_tray(profile, settings, main_screen, toxes):
|
||||||
|
icon = os.path.join(get_images_directory(), 'icon.png')
|
||||||
|
tray = SystemTrayIcon(QtGui.QIcon(icon))
|
||||||
|
|
||||||
|
menu = Menu(settings, profile)
|
||||||
|
show = menu.addAction(tr('Open Toxygen'))
|
||||||
|
sub = menu.addMenu(tr('Set status'))
|
||||||
|
online = sub.addAction(tr('Online'))
|
||||||
|
away = sub.addAction(tr('Away'))
|
||||||
|
busy = sub.addAction(tr('Busy'))
|
||||||
|
online.setCheckable(True)
|
||||||
|
away.setCheckable(True)
|
||||||
|
busy.setCheckable(True)
|
||||||
|
menu.act = sub
|
||||||
|
exit = menu.addAction(tr('Exit'))
|
||||||
|
|
||||||
|
def show_window():
|
||||||
|
def show():
|
||||||
|
if not main_screen.isActiveWindow():
|
||||||
|
main_screen.setWindowState(
|
||||||
|
main_screen.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
|
||||||
|
main_screen.activateWindow()
|
||||||
|
main_screen.show()
|
||||||
|
if not settings.locked:
|
||||||
|
show()
|
||||||
|
else:
|
||||||
|
def correct_pass():
|
||||||
|
show()
|
||||||
|
settings.locked = False
|
||||||
|
settings.unlockScreen = False
|
||||||
|
if not settings.unlockScreen:
|
||||||
|
settings.unlockScreen = True
|
||||||
|
show_window.screen = UnlockAppScreen(toxes, correct_pass)
|
||||||
|
show_window.screen.show()
|
||||||
|
|
||||||
|
def tray_activated(reason):
|
||||||
|
if reason == QtWidgets.QSystemTrayIcon.DoubleClick:
|
||||||
|
show_window()
|
||||||
|
|
||||||
|
def close_app():
|
||||||
|
if not settings.locked:
|
||||||
|
settings.closing = True
|
||||||
|
main_screen.close()
|
||||||
|
|
||||||
|
show.triggered.connect(show_window)
|
||||||
|
exit.triggered.connect(close_app)
|
||||||
|
menu.aboutToShow.connect(menu.about_to_show_handler)
|
||||||
|
online.triggered.connect(lambda: menu.new_status(0))
|
||||||
|
away.triggered.connect(lambda: menu.new_status(1))
|
||||||
|
busy.triggered.connect(lambda: menu.new_status(2))
|
||||||
|
|
||||||
|
tray.setContextMenu(menu)
|
||||||
|
tray.show()
|
||||||
|
tray.activated.connect(tray_activated)
|
||||||
|
|
||||||
|
return tray
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user