From 2de4eea35744f7e6ef6aa82b6beba35a3cae38da Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 26 Jan 2018 23:21:46 +0300 Subject: [PATCH 001/138] initial commit - rewriting. ngc wrapper added, project structure updated --- tests/tests.py | 18 +- toxygen/app.py | 263 +++++ toxygen/av/__init__.py | 0 toxygen/av/call.py | 58 ++ toxygen/{ => av}/calls.py | 66 +- toxygen/{ => av}/screen_sharing.py | 0 toxygen/bootstrap/__init__.py | 0 toxygen/{ => bootstrap}/bootstrap.py | 2 +- toxygen/{ => bootstrap}/nodes.json | 0 toxygen/callbacks.py | 12 +- toxygen/contacts/__init__.py | 0 toxygen/{ => contacts}/basecontact.py | 10 +- toxygen/{ => contacts}/contact.py | 7 +- toxygen/{ => contacts}/friend.py | 4 +- toxygen/{ => contacts}/group_chat.py | 6 +- toxygen/{ => contacts}/profile.py | 37 +- toxygen/db/__init__.py | 0 toxygen/{ => db}/history.py | 30 +- toxygen/file_transfers.py | 8 +- toxygen/login.py | 0 toxygen/main.py | 434 +------- toxygen/messenger/__init__.py | 0 toxygen/{ => messenger}/messages.py | 0 toxygen/network/__init__.py | 0 toxygen/{ => network}/tox_dns.py | 2 +- toxygen/plugin_support/__init__.py | 0 .../{ => plugin_support}/plugin_support.py | 4 +- .../{smileys.py => smileys_and_stickers.py} | 0 toxygen/threads.py | 62 ++ toxygen/toxcore_enums_and_consts.py | 220 ---- toxygen/tray.py | 83 ++ toxygen/ui/__init__.py | 0 toxygen/{ => ui}/avwidgets.py | 6 +- toxygen/{ => ui}/items_factory.py | 3 +- toxygen/{ => ui}/list_items.py | 12 +- toxygen/{ => ui}/loginscreen.py | 3 +- toxygen/{ => ui}/mainscreen.py | 13 +- toxygen/{ => ui}/mainscreen_widgets.py | 6 +- toxygen/{ => ui}/menu.py | 14 +- toxygen/{ => ui}/passwordscreen.py | 2 +- toxygen/{ => ui}/widgets.py | 0 toxygen/updater/__init__.py | 0 toxygen/{ => updater}/updater.py | 2 +- toxygen/user_data/__init__.py | 0 toxygen/user_data/profile_manager.py | 70 ++ toxygen/{ => user_data}/settings.py | 82 +- toxygen/{ => user_data}/toxes.py | 10 +- toxygen/util/__init__.py | 0 toxygen/{ => util}/util.py | 2 +- toxygen/wrapper/__init__.py | 0 toxygen/{ => wrapper}/libtox.py | 0 toxygen/{ => wrapper}/tox.py | 874 ++++++++++++++-- toxygen/{ => wrapper}/toxav.py | 4 +- toxygen/{ => wrapper}/toxav_enums.py | 0 toxygen/wrapper/toxcore_enums_and_consts.py | 944 ++++++++++++++++++ toxygen/{ => wrapper}/toxencryptsave.py | 4 +- .../toxencryptsave_enums_and_consts.py | 0 57 files changed, 2420 insertions(+), 957 deletions(-) create mode 100644 toxygen/app.py create mode 100644 toxygen/av/__init__.py create mode 100644 toxygen/av/call.py rename toxygen/{ => av}/calls.py (85%) rename toxygen/{ => av}/screen_sharing.py (100%) create mode 100644 toxygen/bootstrap/__init__.py rename toxygen/{ => bootstrap}/bootstrap.py (98%) rename toxygen/{ => bootstrap}/nodes.json (100%) create mode 100644 toxygen/contacts/__init__.py rename toxygen/{ => contacts}/basecontact.py (92%) rename toxygen/{ => contacts}/contact.py (99%) rename toxygen/{ => contacts}/friend.py (97%) rename toxygen/{ => contacts}/group_chat.py (94%) rename toxygen/{ => contacts}/profile.py (98%) create mode 100644 toxygen/db/__init__.py rename toxygen/{ => db}/history.py (88%) create mode 100644 toxygen/login.py create mode 100644 toxygen/messenger/__init__.py rename toxygen/{ => messenger}/messages.py (100%) create mode 100644 toxygen/network/__init__.py rename toxygen/{ => network}/tox_dns.py (98%) create mode 100644 toxygen/plugin_support/__init__.py rename toxygen/{ => plugin_support}/plugin_support.py (99%) rename toxygen/{smileys.py => smileys_and_stickers.py} (100%) create mode 100644 toxygen/threads.py delete mode 100644 toxygen/toxcore_enums_and_consts.py create mode 100644 toxygen/tray.py create mode 100644 toxygen/ui/__init__.py rename toxygen/{ => ui}/avwidgets.py (98%) rename toxygen/{ => ui}/items_factory.py (97%) rename toxygen/{ => ui}/list_items.py (99%) rename toxygen/{ => ui}/loginscreen.py (98%) rename toxygen/{ => ui}/mainscreen.py (99%) rename toxygen/{ => ui}/mainscreen_widgets.py (99%) rename toxygen/{ => ui}/menu.py (99%) rename toxygen/{ => ui}/passwordscreen.py (99%) rename toxygen/{ => ui}/widgets.py (100%) create mode 100644 toxygen/updater/__init__.py rename toxygen/{ => updater}/updater.py (99%) create mode 100644 toxygen/user_data/__init__.py create mode 100644 toxygen/user_data/profile_manager.py rename toxygen/{ => user_data}/settings.py (72%) rename toxygen/{ => user_data}/toxes.py (77%) create mode 100644 toxygen/util/__init__.py rename toxygen/{ => util}/util.py (98%) create mode 100644 toxygen/wrapper/__init__.py rename toxygen/{ => wrapper}/libtox.py (100%) rename toxygen/{ => wrapper}/tox.py (68%) rename toxygen/{ => wrapper}/toxav.py (99%) rename toxygen/{ => wrapper}/toxav_enums.py (100%) create mode 100644 toxygen/wrapper/toxcore_enums_and_consts.py rename toxygen/{ => wrapper}/toxencryptsave.py (97%) rename toxygen/{ => wrapper}/toxencryptsave_enums_and_consts.py (100%) diff --git a/tests/tests.py b/tests/tests.py index 27618af..cad1d1c 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,9 +1,9 @@ -from toxygen.profile import * -from toxygen.tox_dns import tox_dns -from toxygen.history import History +from contacts.profile import * +from network.tox_dns import tox_dns +from db.history import History from toxygen.smileys import SmileyLoader -from toxygen.messages import * -import toxygen.toxes as encr +from messenger.messages import * +import user_data.toxes as encr import toxygen.util as util import time @@ -23,15 +23,15 @@ class TestTox: assert tox.self_get_status_message() == str(status_message, 'utf-8') -class TestProfileHelper: +class TestProfileManager: def test_creation(self): file_name, path = 'test.tox', os.path.dirname(os.path.realpath(__file__)) + '/' data = b'test' with open(path + file_name, 'wb') as fl: fl.write(data) - ph = ProfileHelper(path, file_name[:4]) - assert ProfileHelper.get_path() == path + ph = ProfileManager(path, file_name[:4]) + assert ProfileManager.get_path() == path assert ph.open_profile() == data assert os.path.exists(path + 'avatars/') @@ -81,7 +81,7 @@ def create_singletons(): Settings._instance = Settings.get_default_settings() if not os.path.exists(folder): os.makedirs(folder) - ProfileHelper(folder, 'test') + ProfileManager(folder, 'test') def create_friend(name, status_message, number, tox_id): diff --git a/toxygen/app.py b/toxygen/app.py new file mode 100644 index 0000000..1dec36f --- /dev/null +++ b/toxygen/app.py @@ -0,0 +1,263 @@ +class App: + + def __init__(self, path_or_uri=None): + self.tox = self.ms = self.init = self.app = self.tray = self.mainloop = self.avloop = None + if path_or_uri is None: + self.uri = self.path = None + elif path_or_uri.startswith('tox:'): + self.path = None + self.uri = path_or_uri[4:] + else: + self.path = path_or_uri + self.uri = None + + def enter_pass(self, data): + """ + Show password screen + """ + tmp = [data] + p = PasswordScreen(toxes.ToxES.get_instance(), tmp) + p.show() + self.app.lastWindowClosed.connect(self.app.quit) + self.app.exec_() + if tmp[0] == data: + raise SystemExit() + else: + return tmp[0] + + def main(self): + """ + Main function of app. loads login screen if needed and starts main screen + """ + app = QtWidgets.QApplication(sys.argv) + app.setWindowIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) + self.app = app + + if platform.system() == 'Linux': + QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) + + with open(curr_directory() + '/styles/dark_style.qss') as fl: + style = fl.read() + app.setStyleSheet(style) + + encrypt_save = toxes.ToxES() + + if self.path is not None: + path = os.path.dirname(self.path) + '/' + name = os.path.basename(self.path)[:-4] + data = ProfileManager(path, name).open_profile() + if encrypt_save.is_data_encrypted(data): + data = self.enter_pass(data) + settings = Settings(name) + self.tox = profile.tox_factory(data, settings) + else: + auto_profile = Settings.get_auto_profile() + if not auto_profile[0]: + # show login screen if default profile not found + current_locale = QtCore.QLocale() + curr_lang = current_locale.languageToString(current_locale.language()) + langs = Settings.supported_languages() + if curr_lang in langs: + lang_path = langs[curr_lang] + translator = QtCore.QTranslator() + translator.load(curr_directory() + '/translations/' + lang_path) + app.installTranslator(translator) + app.translator = translator + ls = LoginScreen() + ls.setWindowIconText("Toxygen") + profiles = ProfileManager.find_profiles() + ls.update_select(map(lambda x: x[1], profiles)) + _login = self.Login(profiles) + ls.update_on_close(_login.login_screen_close) + ls.show() + app.exec_() + if not _login.t: + return + elif _login.t == 1: # create new profile + _login.name = _login.name.strip() + name = _login.name if _login.name else 'toxygen_user' + pr = map(lambda x: x[1], ProfileManager.find_profiles()) + if name in list(pr): + msgBox = QtWidgets.QMessageBox() + msgBox.setWindowTitle( + QtWidgets.QApplication.translate("MainWindow", "Error")) + text = (QtWidgets.QApplication.translate("MainWindow", + 'Profile with this name already exists')) + msgBox.setText(text) + msgBox.exec_() + return + self.tox = profile.tox_factory() + self.tox.self_set_name(bytes(_login.name, 'utf-8') if _login.name else b'Toxygen User') + self.tox.self_set_status_message(b'Toxing on Toxygen') + reply = QtWidgets.QMessageBox.question(None, + 'Profile {}'.format(name), + QtWidgets.QApplication.translate("login", + 'Do you want to set profile password?'), + QtWidgets.QMessageBox.Yes, + QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.Yes: + set_pass = SetProfilePasswordScreen(encrypt_save) + set_pass.show() + self.app.lastWindowClosed.connect(self.app.quit) + self.app.exec_() + reply = QtWidgets.QMessageBox.question(None, + 'Profile {}'.format(name), + QtWidgets.QApplication.translate("login", + 'Do you want to save profile in default folder? If no, profile will be saved in program folder'), + QtWidgets.QMessageBox.Yes, + QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.Yes: + path = Settings.get_default_path() + else: + path = curr_directory() + '/' + try: + ProfileManager(path, name).save_profile(self.tox.get_savedata()) + except Exception as ex: + print(str(ex)) + log('Profile creation exception: ' + str(ex)) + msgBox = QtWidgets.QMessageBox() + msgBox.setText(QtWidgets.QApplication.translate("login", + 'Profile saving error! Does Toxygen have permission to write to this directory?')) + msgBox.exec_() + return + path = Settings.get_default_path() + settings = Settings(name) + if curr_lang in langs: + settings['language'] = curr_lang + settings.save() + else: # load existing profile + path, name = _login.get_data() + if _login.default: + Settings.set_auto_profile(path, name) + data = ProfileManager(path, name).open_profile() + if encrypt_save.is_data_encrypted(data): + data = self.enter_pass(data) + settings = Settings(name) + self.tox = profile.tox_factory(data, settings) + else: + path, name = auto_profile + data = ProfileManager(path, name).open_profile() + if encrypt_save.is_data_encrypted(data): + data = self.enter_pass(data) + settings = Settings(name) + self.tox = profile.tox_factory(data, settings) + + if Settings.is_active_profile(path, name): # profile is in use + reply = QtWidgets.QMessageBox.question(None, + 'Profile {}'.format(name), + QtWidgets.QApplication.translate("login", 'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?'), + QtWidgets.QMessageBox.Yes, + QtWidgets.QMessageBox.No) + if reply != QtWidgets.QMessageBox.Yes: + return + else: + settings.set_active_profile() + + # application color scheme + for theme in settings.built_in_themes().keys(): + if settings['theme'] == theme: + with open(curr_directory() + settings.built_in_themes()[theme]) as fl: + style = fl.read() + app.setStyleSheet(style) + + lang = Settings.supported_languages()[settings['language']] + translator = QtCore.QTranslator() + translator.load(curr_directory() + '/translations/' + lang) + app.installTranslator(translator) + app.translator = translator + + # tray icon + + + self.ms.show() + + updating = False + if settings['update'] and updater.updater_available() and updater.connection_available(): # auto update + version = updater.check_for_updates() + if version is not None: + if settings['update'] == 2: + updater.download(version) + updating = True + else: + reply = QtWidgets.QMessageBox.question(None, + 'Toxygen', + QtWidgets.QApplication.translate("login", + 'Update for Toxygen was found. Download and install it?'), + QtWidgets.QMessageBox.Yes, + QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.Yes: + updater.download(version) + updating = True + + if updating: + data = self.tox.get_savedata() + ProfileManager.get_instance().save_profile(data) + settings.close() + del self.tox + return + + plugin_helper = PluginLoader(self.tox, settings) # plugin support + plugin_helper.load() + + start() + # init thread + self.init = self.InitThread(self.tox, self.ms, self.tray) + self.init.start() + + # starting threads for tox iterate and toxav iterate + self.mainloop = self.ToxIterateThread(self.tox) + self.mainloop.start() + self.avloop = self.ToxAVIterateThread(self.tox.AV) + self.avloop.start() + + if self.uri is not None: + self.ms.add_contact(self.uri) + + app.lastWindowClosed.connect(app.quit) + app.exec_() + + self.init.stop = True + self.mainloop.stop = True + self.avloop.stop = True + plugin_helper.stop() + stop() + self.mainloop.wait() + self.init.wait() + self.avloop.wait() + self.tray.hide() + data = self.tox.get_savedata() + ProfileManager.get_instance().save_profile(data) + settings.close() + del self.tox + + def reset(self): + """ + Create new tox instance (new network settings) + :return: tox instance + """ + self.mainloop.stop = True + self.init.stop = True + self.avloop.stop = True + self.mainloop.wait() + self.init.wait() + self.avloop.wait() + data = self.tox.get_savedata() + ProfileManager.get_instance().save_profile(data) + del self.tox + # create new tox instance + self.tox = profile.tox_factory(data, Settings.get_instance()) + # init thread + self.init = self.InitThread(self.tox, self.ms, self.tray) + self.init.start() + + # starting threads for tox iterate and toxav iterate + self.mainloop = self.ToxIterateThread(self.tox) + self.mainloop.start() + + self.avloop = self.ToxAVIterateThread(self.tox.AV) + self.avloop.start() + + plugin_helper = PluginLoader.get_instance() + plugin_helper.set_tox(self.tox) + + return self.tox \ No newline at end of file diff --git a/toxygen/av/__init__.py b/toxygen/av/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/av/call.py b/toxygen/av/call.py new file mode 100644 index 0000000..c43e264 --- /dev/null +++ b/toxygen/av/call.py @@ -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._in_video = value + + out_video = property(get_out_video, set_out_video) diff --git a/toxygen/calls.py b/toxygen/av/calls.py similarity index 85% rename from toxygen/calls.py rename to toxygen/av/calls.py index 6477c03..20684b2 100644 --- a/toxygen/calls.py +++ b/toxygen/av/calls.py @@ -1,73 +1,17 @@ import pyaudio import time import threading -import settings -from toxav_enums import * +from user_data import settings +from wrapper.toxav_enums import * import cv2 import itertools import numpy as np -import screen_sharing +from av import screen_sharing + + # TODO: play sound until outgoing call will be started or cancelled -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._in_video = value - - out_video = property(get_out_video, set_out_video) - - class AV: def __init__(self, toxav): diff --git a/toxygen/screen_sharing.py b/toxygen/av/screen_sharing.py similarity index 100% rename from toxygen/screen_sharing.py rename to toxygen/av/screen_sharing.py diff --git a/toxygen/bootstrap/__init__.py b/toxygen/bootstrap/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/bootstrap.py b/toxygen/bootstrap/bootstrap.py similarity index 98% rename from toxygen/bootstrap.py rename to toxygen/bootstrap/bootstrap.py index 0589940..6d97f25 100644 --- a/toxygen/bootstrap.py +++ b/toxygen/bootstrap/bootstrap.py @@ -1,7 +1,7 @@ import random import urllib.request from util import log, curr_directory -import settings +from user_data import settings from PyQt5 import QtNetwork, QtCore import json diff --git a/toxygen/nodes.json b/toxygen/bootstrap/nodes.json similarity index 100% rename from toxygen/nodes.json rename to toxygen/bootstrap/nodes.json diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index b59d17c..ba2994d 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -1,10 +1,10 @@ -from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5 import QtGui 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 user_data.settings import Settings +from contacts.profile import Profile +from wrapper.toxcore_enums_and_consts import * +from wrapper.toxav_enums import * +from wrapper.tox import bin_to_string from plugin_support import PluginLoader import queue import threading diff --git a/toxygen/contacts/__init__.py b/toxygen/contacts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/basecontact.py b/toxygen/contacts/basecontact.py similarity index 92% rename from toxygen/basecontact.py rename to toxygen/contacts/basecontact.py index e1243a4..6e2d55a 100644 --- a/toxygen/basecontact.py +++ b/toxygen/contacts/basecontact.py @@ -1,6 +1,6 @@ -from settings import * +from user_data.settings import * 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 class BaseContact: @@ -81,7 +81,7 @@ class BaseContact: """ Tries to load avatar of contact or uses default avatar """ - prefix = ProfileHelper.get_path() + 'avatars/' + prefix = ProfileManager.get_path() + 'avatars/' 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' @@ -92,13 +92,13 @@ class BaseContact: self._widget.avatar_label.repaint() def reset_avatar(self): - avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) + avatar_path = (ProfileManager.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) if os.path.isfile(avatar_path): os.remove(avatar_path) self.load_avatar() def set_avatar(self, avatar): - avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) + avatar_path = (ProfileManager.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) with open(avatar_path, 'wb') as f: f.write(avatar) self.load_avatar() diff --git a/toxygen/contact.py b/toxygen/contacts/contact.py similarity index 99% rename from toxygen/contact.py rename to toxygen/contacts/contact.py index 9f27a1d..ad491fd 100644 --- a/toxygen/contact.py +++ b/toxygen/contacts/contact.py @@ -1,8 +1,7 @@ -from PyQt5 import QtCore, QtGui -from history import * -import basecontact +from db.history import * +from contacts import basecontact import util -from messages import * +from messenger.messages import * import file_transfers as ft import re diff --git a/toxygen/friend.py b/toxygen/contacts/friend.py similarity index 97% rename from toxygen/friend.py rename to toxygen/contacts/friend.py index d912708..b80032a 100644 --- a/toxygen/friend.py +++ b/toxygen/contacts/friend.py @@ -1,5 +1,5 @@ -import contact -from messages import * +from contacts import contact +from messenger.messages import * import os diff --git a/toxygen/group_chat.py b/toxygen/contacts/group_chat.py similarity index 94% rename from toxygen/group_chat.py rename to toxygen/contacts/group_chat.py index f7921a1..05faaa9 100644 --- a/toxygen/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -1,9 +1,11 @@ -import contact +from contacts import contact import util from PyQt5 import QtGui, QtCore -import toxcore_enums_and_consts as constants +from wrapper import toxcore_enums_and_consts as constants +# TODO: ngc + class GroupChat(contact.Contact): def __init__(self, name, status_message, widget, tox, group_number): diff --git a/toxygen/profile.py b/toxygen/contacts/profile.py similarity index 98% rename from toxygen/profile.py rename to toxygen/contacts/profile.py index 204419a..f74d134 100644 --- a/toxygen/profile.py +++ b/toxygen/contacts/profile.py @@ -1,22 +1,21 @@ -from list_items import * -from PyQt5 import QtGui, QtWidgets -from friend import * -from settings import * -from toxcore_enums_and_consts import * +from ui.list_items import * +from PyQt5 import QtWidgets +from contacts.friend import * +from user_data.settings import * +from wrapper.toxcore_enums_and_consts import * from ctypes import * from util import log, Singleton, curr_directory -from tox_dns import tox_dns -from history import * +from network.tox_dns import tox_dns +from db.history import * from file_transfers import * import time -import calls -import avwidgets +from av import calls import plugin_support -import basecontact -import items_factory +from contacts import basecontact +from ui import items_factory, avwidgets import cv2 import threading -from group_chat import * +from contacts.group_chat import * import re @@ -283,7 +282,7 @@ class Profile(basecontact.BaseContact, Singleton): if friend.tox_id is None: avatar_path = curr_directory() + '/images/group.png' else: - avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(friend.tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) + avatar_path = (ProfileManager.get_path() + 'avatars/{}.png').format(friend.tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) if not os.path.isfile(avatar_path): # load default image avatar_path = curr_directory() + '/images/avatar.png' os.chdir(os.path.dirname(avatar_path)) @@ -752,7 +751,7 @@ class Profile(basecontact.BaseContact, Singleton): else: self.set_active(0) data = self._tox.get_savedata() - ProfileHelper.get_instance().save_profile(data) + ProfileManager.get_instance().save_profile(data) def add_friend(self, tox_id): """ @@ -785,7 +784,7 @@ class Profile(basecontact.BaseContact, Singleton): num = self._tox.friend_by_public_key(tox_id) self.delete_friend(num) data = self._tox.get_savedata() - ProfileHelper.get_instance().save_profile(data) + ProfileManager.get_instance().save_profile(data) except: # not in friend list pass @@ -801,7 +800,7 @@ class Profile(basecontact.BaseContact, Singleton): if add_to_friend_list: self.add_friend(tox_id) data = self._tox.get_savedata() - ProfileHelper.get_instance().save_profile(data) + ProfileManager.get_instance().save_profile(data) # ----------------------------------------------------------------------------------------------------------------- # Friend requests @@ -837,7 +836,7 @@ class Profile(basecontact.BaseContact, Singleton): friend = Friend(message_getter, result, tox_id, '', item, tox_id) self._contacts.append(friend) data = self._tox.get_savedata() - ProfileHelper.get_instance().save_profile(data) + ProfileManager.get_instance().save_profile(data) return True except Exception as ex: # wrong data log('Friend request failed with ' + str(ex)) @@ -857,7 +856,7 @@ class Profile(basecontact.BaseContact, Singleton): if reply == QtWidgets.QMessageBox.Yes: # accepted self.add_friend(tox_id) data = self._tox.get_savedata() - ProfileHelper.get_instance().save_profile(data) + ProfileManager.get_instance().save_profile(data) except Exception as ex: # something is wrong log('Accept friend request failed! ' + str(ex)) @@ -1180,7 +1179,7 @@ class Profile(basecontact.BaseContact, Singleton): """ :param friend_number: number of friend who should get new avatar """ - avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) + avatar_path = (ProfileManager.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) if not os.path.isfile(avatar_path): # reset image avatar_path = None sa = SendAvatar(avatar_path, self._tox, friend_number) diff --git a/toxygen/db/__init__.py b/toxygen/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/history.py b/toxygen/db/history.py similarity index 88% rename from toxygen/history.py rename to toxygen/db/history.py index 586981a..eea7457 100644 --- a/toxygen/history.py +++ b/toxygen/db/history.py @@ -1,8 +1,8 @@ from sqlite3 import connect -import settings +from user_data import settings from os import chdir import os.path -from toxes import ToxES +from user_data.toxes import ToxES PAGE_SIZE = 42 @@ -17,13 +17,15 @@ MESSAGE_OWNER = { 'NOT_SENT': 2 } +# TODO: unique message id and ngc support + class History: def __init__(self, name): self._name = name - chdir(settings.ProfileHelper.get_path()) - path = settings.ProfileHelper.get_path() + self._name + '.hstr' + chdir(settings.ProfileManager.get_path()) + path = settings.ProfileManager.get_path() + self._name + '.hstr' if os.path.exists(path): decr = ToxES.get_instance() try: @@ -45,7 +47,7 @@ class History: def save(self): encr = ToxES.get_instance() if encr.has_password(): - path = settings.ProfileHelper.get_path() + self._name + '.hstr' + path = settings.ProfileManager.get_path() + self._name + '.hstr' with open(path, 'rb') as fin: data = fin.read() data = encr.pass_encrypt(bytes(data)) @@ -53,7 +55,7 @@ class History: fout.write(data) def export(self, directory): - path = settings.ProfileHelper.get_path() + self._name + '.hstr' + path = settings.ProfileManager.get_path() + self._name + '.hstr' new_path = directory + self._name + '.hstr' with open(path, 'rb') as fin: data = fin.read() @@ -64,7 +66,7 @@ class History: fout.write(data) def add_friend_to_db(self, tox_id): - chdir(settings.ProfileHelper.get_path()) + chdir(settings.ProfileManager.get_path()) db = connect(self._name + '.hstr', timeout=TIMEOUT) try: cursor = db.cursor() @@ -84,7 +86,7 @@ class History: db.close() def delete_friend_from_db(self, tox_id): - chdir(settings.ProfileHelper.get_path()) + chdir(settings.ProfileManager.get_path()) db = connect(self._name + '.hstr', timeout=TIMEOUT) try: cursor = db.cursor() @@ -98,7 +100,7 @@ class History: db.close() def friend_exists_in_db(self, tox_id): - chdir(settings.ProfileHelper.get_path()) + chdir(settings.ProfileManager.get_path()) db = connect(self._name + '.hstr', timeout=TIMEOUT) cursor = db.cursor() cursor.execute('SELECT 0 FROM friends WHERE tox_id=?', (tox_id, )) @@ -107,7 +109,7 @@ class History: return result is not None def save_messages_to_db(self, tox_id, messages_iter): - chdir(settings.ProfileHelper.get_path()) + chdir(settings.ProfileManager.get_path()) db = connect(self._name + '.hstr', timeout=TIMEOUT) try: cursor = db.cursor() @@ -121,7 +123,7 @@ class History: db.close() def update_messages(self, tox_id, unsent_time): - chdir(settings.ProfileHelper.get_path()) + chdir(settings.ProfileManager.get_path()) db = connect(self._name + '.hstr', timeout=TIMEOUT) try: cursor = db.cursor() @@ -136,7 +138,7 @@ class History: def delete_message(self, tox_id, time): start, end = str(time - 0.01), str(time + 0.01) - chdir(settings.ProfileHelper.get_path()) + chdir(settings.ProfileManager.get_path()) db = connect(self._name + '.hstr', timeout=TIMEOUT) try: cursor = db.cursor() @@ -150,7 +152,7 @@ class History: db.close() def delete_messages(self, tox_id): - chdir(settings.ProfileHelper.get_path()) + chdir(settings.ProfileManager.get_path()) db = connect(self._name + '.hstr', timeout=TIMEOUT) try: cursor = db.cursor() @@ -174,7 +176,7 @@ class History: self._db = self._cursor = None def connect(self): - chdir(settings.ProfileHelper.get_path()) + chdir(settings.ProfileManager.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 + diff --git a/toxygen/file_transfers.py b/toxygen/file_transfers.py index 7e0b193..6c65809 100644 --- a/toxygen/file_transfers.py +++ b/toxygen/file_transfers.py @@ -1,9 +1,9 @@ -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 import remove, rename, chdir from time import time, sleep -from tox import Tox -import settings +from wrapper.tox import Tox +from user_data import settings from PyQt5 import QtCore @@ -305,7 +305,7 @@ class ReceiveAvatar(ReceiveTransfer): MAX_AVATAR_SIZE = 512 * 1024 def __init__(self, tox, friend_number, size, file_number): - path = settings.ProfileHelper.get_path() + 'avatars/{}.png'.format(tox.friend_get_public_key(friend_number)) + path = settings.ProfileManager.get_path() + 'avatars/{}.png'.format(tox.friend_get_public_key(friend_number)) super(ReceiveAvatar, self).__init__(path + '.tmp', tox, friend_number, size, file_number) if size > self.MAX_AVATAR_SIZE: self.send_control(TOX_FILE_CONTROL['CANCEL']) diff --git a/toxygen/login.py b/toxygen/login.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/main.py b/toxygen/main.py index d630bb6..4e91c50 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -1,434 +1,11 @@ import sys -from loginscreen import LoginScreen -import profile -from settings import * -from PyQt5 import QtCore, QtGui, QtWidgets -from bootstrap import generate_nodes, download_nodes_list -from mainscreen import MainWindow +from user_data.settings import * from callbacks import init_callbacks, stop, start from util import curr_directory, program_version, remove -import styles.style # reqired for styles loading -import platform -import toxes -from passwordscreen import PasswordScreen, UnlockAppScreen, SetProfilePasswordScreen -from plugin_support import PluginLoader import updater +import argparse -class Toxygen: - - def __init__(self, path_or_uri=None): - super(Toxygen, self).__init__() - self.tox = self.ms = self.init = self.app = self.tray = self.mainloop = self.avloop = None - if path_or_uri is None: - self.uri = self.path = None - elif path_or_uri.startswith('tox:'): - self.path = None - self.uri = path_or_uri[4:] - else: - self.path = path_or_uri - self.uri = None - - def enter_pass(self, data): - """ - Show password screen - """ - tmp = [data] - p = PasswordScreen(toxes.ToxES.get_instance(), tmp) - p.show() - self.app.lastWindowClosed.connect(self.app.quit) - self.app.exec_() - if tmp[0] == data: - raise SystemExit() - else: - return tmp[0] - - def main(self): - """ - Main function of app. loads login screen if needed and starts main screen - """ - app = QtWidgets.QApplication(sys.argv) - app.setWindowIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) - self.app = app - - if platform.system() == 'Linux': - QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) - - with open(curr_directory() + '/styles/dark_style.qss') as fl: - style = fl.read() - app.setStyleSheet(style) - - encrypt_save = toxes.ToxES() - - if self.path is not None: - path = os.path.dirname(self.path) + '/' - name = os.path.basename(self.path)[:-4] - data = ProfileHelper(path, name).open_profile() - if encrypt_save.is_data_encrypted(data): - data = self.enter_pass(data) - settings = Settings(name) - self.tox = profile.tox_factory(data, settings) - else: - auto_profile = Settings.get_auto_profile() - if not auto_profile[0]: - # show login screen if default profile not found - current_locale = QtCore.QLocale() - curr_lang = current_locale.languageToString(current_locale.language()) - langs = Settings.supported_languages() - if curr_lang in langs: - lang_path = langs[curr_lang] - translator = QtCore.QTranslator() - translator.load(curr_directory() + '/translations/' + lang_path) - app.installTranslator(translator) - app.translator = translator - ls = LoginScreen() - ls.setWindowIconText("Toxygen") - profiles = ProfileHelper.find_profiles() - ls.update_select(map(lambda x: x[1], profiles)) - _login = self.Login(profiles) - ls.update_on_close(_login.login_screen_close) - ls.show() - app.exec_() - if not _login.t: - return - elif _login.t == 1: # create new profile - _login.name = _login.name.strip() - name = _login.name if _login.name else 'toxygen_user' - pr = map(lambda x: x[1], ProfileHelper.find_profiles()) - if name in list(pr): - msgBox = QtWidgets.QMessageBox() - msgBox.setWindowTitle( - QtWidgets.QApplication.translate("MainWindow", "Error")) - text = (QtWidgets.QApplication.translate("MainWindow", - 'Profile with this name already exists')) - msgBox.setText(text) - msgBox.exec_() - return - self.tox = profile.tox_factory() - self.tox.self_set_name(bytes(_login.name, 'utf-8') if _login.name else b'Toxygen User') - self.tox.self_set_status_message(b'Toxing on Toxygen') - reply = QtWidgets.QMessageBox.question(None, - 'Profile {}'.format(name), - QtWidgets.QApplication.translate("login", - 'Do you want to set profile password?'), - QtWidgets.QMessageBox.Yes, - QtWidgets.QMessageBox.No) - if reply == QtWidgets.QMessageBox.Yes: - set_pass = SetProfilePasswordScreen(encrypt_save) - set_pass.show() - self.app.lastWindowClosed.connect(self.app.quit) - self.app.exec_() - reply = QtWidgets.QMessageBox.question(None, - 'Profile {}'.format(name), - QtWidgets.QApplication.translate("login", - 'Do you want to save profile in default folder? If no, profile will be saved in program folder'), - QtWidgets.QMessageBox.Yes, - QtWidgets.QMessageBox.No) - if reply == QtWidgets.QMessageBox.Yes: - path = Settings.get_default_path() - else: - path = curr_directory() + '/' - try: - ProfileHelper(path, name).save_profile(self.tox.get_savedata()) - except Exception as ex: - print(str(ex)) - log('Profile creation exception: ' + str(ex)) - msgBox = QtWidgets.QMessageBox() - msgBox.setText(QtWidgets.QApplication.translate("login", - 'Profile saving error! Does Toxygen have permission to write to this directory?')) - msgBox.exec_() - return - path = Settings.get_default_path() - settings = Settings(name) - if curr_lang in langs: - settings['language'] = curr_lang - settings.save() - else: # load existing profile - path, name = _login.get_data() - if _login.default: - Settings.set_auto_profile(path, name) - data = ProfileHelper(path, name).open_profile() - if encrypt_save.is_data_encrypted(data): - data = self.enter_pass(data) - settings = Settings(name) - self.tox = profile.tox_factory(data, settings) - else: - path, name = auto_profile - data = ProfileHelper(path, name).open_profile() - if encrypt_save.is_data_encrypted(data): - data = self.enter_pass(data) - settings = Settings(name) - self.tox = profile.tox_factory(data, settings) - - if Settings.is_active_profile(path, name): # profile is in use - reply = QtWidgets.QMessageBox.question(None, - 'Profile {}'.format(name), - QtWidgets.QApplication.translate("login", 'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?'), - QtWidgets.QMessageBox.Yes, - QtWidgets.QMessageBox.No) - if reply != QtWidgets.QMessageBox.Yes: - return - else: - settings.set_active_profile() - - # application color scheme - for theme in settings.built_in_themes().keys(): - if settings['theme'] == theme: - with open(curr_directory() + settings.built_in_themes()[theme]) as fl: - style = fl.read() - app.setStyleSheet(style) - - lang = Settings.supported_languages()[settings['language']] - translator = QtCore.QTranslator() - translator.load(curr_directory() + '/translations/' + lang) - app.installTranslator(translator) - app.translator = translator - - # tray icon - self.tray = QtWidgets.QSystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) - self.tray.setObjectName('tray') - - self.ms = MainWindow(self.tox, self.reset, self.tray) - app.aboutToQuit.connect(self.ms.close_window) - - class Menu(QtWidgets.QMenu): - - def newStatus(self, status): - if not Settings.get_instance().locked: - profile.Profile.get_instance().set_status(status) - self.aboutToShowHandler() - self.hide() - - def aboutToShowHandler(self): - status = profile.Profile.get_instance().status - act = self.act - if status is None or Settings.get_instance().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 Settings.get_instance().locked) - - def languageChange(self, *args, **kwargs): - self.actions()[0].setText(QtWidgets.QApplication.translate('tray', 'Open Toxygen')) - self.actions()[1].setText(QtWidgets.QApplication.translate('tray', 'Set status')) - self.actions()[2].setText(QtWidgets.QApplication.translate('tray', 'Exit')) - self.act.actions()[0].setText(QtWidgets.QApplication.translate('tray', 'Online')) - self.act.actions()[1].setText(QtWidgets.QApplication.translate('tray', 'Away')) - self.act.actions()[2].setText(QtWidgets.QApplication.translate('tray', 'Busy')) - - m = Menu() - show = m.addAction(QtWidgets.QApplication.translate('tray', 'Open Toxygen')) - sub = m.addMenu(QtWidgets.QApplication.translate('tray', 'Set status')) - onl = sub.addAction(QtWidgets.QApplication.translate('tray', 'Online')) - away = sub.addAction(QtWidgets.QApplication.translate('tray', 'Away')) - busy = sub.addAction(QtWidgets.QApplication.translate('tray', 'Busy')) - onl.setCheckable(True) - away.setCheckable(True) - busy.setCheckable(True) - m.act = sub - exit = m.addAction(QtWidgets.QApplication.translate('tray', 'Exit')) - - def show_window(): - s = Settings.get_instance() - - def show(): - if not self.ms.isActiveWindow(): - self.ms.setWindowState(self.ms.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) - self.ms.activateWindow() - self.ms.show() - if not s.locked: - show() - else: - def correct_pass(): - show() - s.locked = False - s.unlockScreen = False - if not s.unlockScreen: - s.unlockScreen = True - self.p = UnlockAppScreen(toxes.ToxES.get_instance(), correct_pass) - self.p.show() - - def tray_activated(reason): - if reason == QtWidgets.QSystemTrayIcon.DoubleClick: - show_window() - - def close_app(): - if not Settings.get_instance().locked: - settings.closing = True - self.ms.close() - - show.triggered.connect(show_window) - exit.triggered.connect(close_app) - m.aboutToShow.connect(lambda: m.aboutToShowHandler()) - onl.triggered.connect(lambda: m.newStatus(0)) - away.triggered.connect(lambda: m.newStatus(1)) - busy.triggered.connect(lambda: m.newStatus(2)) - - self.tray.setContextMenu(m) - self.tray.show() - self.tray.activated.connect(tray_activated) - - self.ms.show() - - updating = False - if settings['update'] and updater.updater_available() and updater.connection_available(): # auto update - version = updater.check_for_updates() - if version is not None: - if settings['update'] == 2: - updater.download(version) - updating = True - else: - reply = QtWidgets.QMessageBox.question(None, - 'Toxygen', - QtWidgets.QApplication.translate("login", - 'Update for Toxygen was found. Download and install it?'), - QtWidgets.QMessageBox.Yes, - QtWidgets.QMessageBox.No) - if reply == QtWidgets.QMessageBox.Yes: - updater.download(version) - updating = True - - if updating: - data = self.tox.get_savedata() - ProfileHelper.get_instance().save_profile(data) - settings.close() - del self.tox - return - - plugin_helper = PluginLoader(self.tox, settings) # plugin support - plugin_helper.load() - - start() - # init thread - self.init = self.InitThread(self.tox, self.ms, self.tray) - self.init.start() - - # starting threads for tox iterate and toxav iterate - self.mainloop = self.ToxIterateThread(self.tox) - self.mainloop.start() - self.avloop = self.ToxAVIterateThread(self.tox.AV) - self.avloop.start() - - if self.uri is not None: - self.ms.add_contact(self.uri) - - app.lastWindowClosed.connect(app.quit) - app.exec_() - - self.init.stop = True - self.mainloop.stop = True - self.avloop.stop = True - plugin_helper.stop() - stop() - self.mainloop.wait() - self.init.wait() - self.avloop.wait() - self.tray.hide() - data = self.tox.get_savedata() - ProfileHelper.get_instance().save_profile(data) - settings.close() - del self.tox - - def reset(self): - """ - Create new tox instance (new network settings) - :return: tox instance - """ - self.mainloop.stop = True - self.init.stop = True - self.avloop.stop = True - self.mainloop.wait() - self.init.wait() - self.avloop.wait() - data = self.tox.get_savedata() - ProfileHelper.get_instance().save_profile(data) - del self.tox - # create new tox instance - self.tox = profile.tox_factory(data, Settings.get_instance()) - # init thread - self.init = self.InitThread(self.tox, self.ms, self.tray) - self.init.start() - - # starting threads for tox iterate and toxav iterate - self.mainloop = self.ToxIterateThread(self.tox) - self.mainloop.start() - - self.avloop = self.ToxAVIterateThread(self.tox.AV) - self.avloop.start() - - plugin_helper = PluginLoader.get_instance() - plugin_helper.set_tox(self.tox) - - return self.tox - - # ----------------------------------------------------------------------------------------------------------------- - # Inner classes - # ----------------------------------------------------------------------------------------------------------------- - - class InitThread(QtCore.QThread): - - def __init__(self, tox, ms, tray): - QtCore.QThread.__init__(self) - self.tox, self.ms, self.tray = tox, ms, tray - self.stop = False - - def run(self): - # initializing callbacks - init_callbacks(self.tox, self.ms, self.tray) - # download list of nodes if needed - download_nodes_list() - # bootstrap - try: - for data in generate_nodes(): - if self.stop: - return - self.tox.bootstrap(*data) - self.tox.add_tcp_relay(*data) - except: - pass - for _ in range(10): - if self.stop: - return - self.msleep(1000) - while not self.tox.self_get_connection_status(): - try: - for data in generate_nodes(): - if self.stop: - return - self.tox.bootstrap(*data) - self.tox.add_tcp_relay(*data) - except: - pass - finally: - self.msleep(5000) - - class ToxIterateThread(QtCore.QThread): - - def __init__(self, tox): - QtCore.QThread.__init__(self) - self.tox = tox - self.stop = False - - def run(self): - while not self.stop: - self.tox.iterate() - self.msleep(self.tox.iteration_interval()) - - class ToxAVIterateThread(QtCore.QThread): - - def __init__(self, toxav): - QtCore.QThread.__init__(self) - self.toxav = toxav - self.stop = False - - def run(self): - while not self.stop: - self.toxav.iterate() - self.msleep(self.toxav.iteration_interval()) class Login: @@ -462,7 +39,12 @@ def reset(): def main(): - if len(sys.argv) == 1: + parser = argparse.ArgumentParser() + parser.add_argument('--version') + parser.add_argument('--clean') + parser.add_argument('--reset') + args = parser.parse_args() + if not len(args): toxygen = Toxygen() else: # started with argument(s) arg = sys.argv[1] diff --git a/toxygen/messenger/__init__.py b/toxygen/messenger/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/messages.py b/toxygen/messenger/messages.py similarity index 100% rename from toxygen/messages.py rename to toxygen/messenger/messages.py diff --git a/toxygen/network/__init__.py b/toxygen/network/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/tox_dns.py b/toxygen/network/tox_dns.py similarity index 98% rename from toxygen/tox_dns.py rename to toxygen/network/tox_dns.py index 26b9619..36702ee 100644 --- a/toxygen/tox_dns.py +++ b/toxygen/network/tox_dns.py @@ -1,7 +1,7 @@ import json import urllib.request from util import log -import settings +from user_data import settings from PyQt5 import QtNetwork, QtCore diff --git a/toxygen/plugin_support/__init__.py b/toxygen/plugin_support/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/plugin_support.py b/toxygen/plugin_support/plugin_support.py similarity index 99% rename from toxygen/plugin_support.py rename to toxygen/plugin_support/plugin_support.py index 0ff7421..de39e5f 100644 --- a/toxygen/plugin_support.py +++ b/toxygen/plugin_support/plugin_support.py @@ -1,10 +1,10 @@ import util -import profile +from contacts import profile import os import importlib import inspect import plugins.plugin_super_class as pl -import toxes +from user_data import toxes import sys diff --git a/toxygen/smileys.py b/toxygen/smileys_and_stickers.py similarity index 100% rename from toxygen/smileys.py rename to toxygen/smileys_and_stickers.py diff --git a/toxygen/threads.py b/toxygen/threads.py new file mode 100644 index 0000000..dae8800 --- /dev/null +++ b/toxygen/threads.py @@ -0,0 +1,62 @@ +class InitThread(QtCore.QThread): + + def __init__(self, tox, ms, tray): + QtCore.QThread.__init__(self) + self.tox, self.ms, self.tray = tox, ms, tray + self.stop = False + + def run(self): + # initializing callbacks + init_callbacks(self.tox, self.ms, self.tray) + # download list of nodes if needed + download_nodes_list() + # bootstrap + try: + for data in generate_nodes(): + if self.stop: + return + self.tox.bootstrap(*data) + self.tox.add_tcp_relay(*data) + except: + pass + for _ in range(10): + if self.stop: + return + self.msleep(1000) + while not self.tox.self_get_connection_status(): + try: + for data in generate_nodes(): + if self.stop: + return + self.tox.bootstrap(*data) + self.tox.add_tcp_relay(*data) + except: + pass + finally: + self.msleep(5000) + + +class ToxIterateThread(QtCore.QThread): + + def __init__(self, tox): + QtCore.QThread.__init__(self) + self.tox = tox + self.stop = False + + def run(self): + while not self.stop: + self.tox.iterate() + self.msleep(self.tox.iteration_interval()) + + +class ToxAVIterateThread(QtCore.QThread): + + def __init__(self, toxav): + QtCore.QThread.__init__(self) + self.toxav = toxav + self.stop = False + + def run(self): + while not self.stop: + self.toxav.iterate() + self.msleep(self.toxav.iteration_interval()) diff --git a/toxygen/toxcore_enums_and_consts.py b/toxygen/toxcore_enums_and_consts.py deleted file mode 100644 index a17d93e..0000000 --- a/toxygen/toxcore_enums_and_consts.py +++ /dev/null @@ -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 diff --git a/toxygen/tray.py b/toxygen/tray.py new file mode 100644 index 0000000..ffe9aeb --- /dev/null +++ b/toxygen/tray.py @@ -0,0 +1,83 @@ +self.tray = QtWidgets.QSystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) + self.tray.setObjectName('tray') + + class Menu(QtWidgets.QMenu): + + def newStatus(self, status): + if not Settings.get_instance().locked: + profile.Profile.get_instance().set_status(status) + self.aboutToShowHandler() + self.hide() + + def aboutToShowHandler(self): + status = profile.Profile.get_instance().status + act = self.act + if status is None or Settings.get_instance().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 Settings.get_instance().locked) + + def languageChange(self, *args, **kwargs): + self.actions()[0].setText(QtWidgets.QApplication.translate('tray', 'Open Toxygen')) + self.actions()[1].setText(QtWidgets.QApplication.translate('tray', 'Set status')) + self.actions()[2].setText(QtWidgets.QApplication.translate('tray', 'Exit')) + self.act.actions()[0].setText(QtWidgets.QApplication.translate('tray', 'Online')) + self.act.actions()[1].setText(QtWidgets.QApplication.translate('tray', 'Away')) + self.act.actions()[2].setText(QtWidgets.QApplication.translate('tray', 'Busy')) + + m = Menu() + show = m.addAction(QtWidgets.QApplication.translate('tray', 'Open Toxygen')) + sub = m.addMenu(QtWidgets.QApplication.translate('tray', 'Set status')) + onl = sub.addAction(QtWidgets.QApplication.translate('tray', 'Online')) + away = sub.addAction(QtWidgets.QApplication.translate('tray', 'Away')) + busy = sub.addAction(QtWidgets.QApplication.translate('tray', 'Busy')) + onl.setCheckable(True) + away.setCheckable(True) + busy.setCheckable(True) + m.act = sub + exit = m.addAction(QtWidgets.QApplication.translate('tray', 'Exit')) + + def show_window(): + s = Settings.get_instance() + + def show(): + if not self.ms.isActiveWindow(): + self.ms.setWindowState(self.ms.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) + self.ms.activateWindow() + self.ms.show() + if not s.locked: + show() + else: + def correct_pass(): + show() + s.locked = False + s.unlockScreen = False + if not s.unlockScreen: + s.unlockScreen = True + self.p = UnlockAppScreen(toxes.ToxES.get_instance(), correct_pass) + self.p.show() + + def tray_activated(reason): + if reason == QtWidgets.QSystemTrayIcon.DoubleClick: + show_window() + + def close_app(): + if not Settings.get_instance().locked: + settings.closing = True + self.ms.close() + + show.triggered.connect(show_window) + exit.triggered.connect(close_app) + m.aboutToShow.connect(lambda: m.aboutToShowHandler()) + onl.triggered.connect(lambda: m.newStatus(0)) + away.triggered.connect(lambda: m.newStatus(1)) + busy.triggered.connect(lambda: m.newStatus(2)) + + self.tray.setContextMenu(m) + self.tray.show() + self.tray.activated.connect(tray_activated) \ No newline at end of file diff --git a/toxygen/ui/__init__.py b/toxygen/ui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/avwidgets.py b/toxygen/ui/avwidgets.py similarity index 98% rename from toxygen/avwidgets.py rename to toxygen/ui/avwidgets.py index 8c81387..67bd04a 100644 --- a/toxygen/avwidgets.py +++ b/toxygen/ui/avwidgets.py @@ -1,10 +1,10 @@ from PyQt5 import QtCore, QtGui, QtWidgets -import widgets -import profile +from ui import widgets +from contacts import profile import util import pyaudio import wave -import settings +from user_data import settings from util import curr_directory diff --git a/toxygen/items_factory.py b/toxygen/ui/items_factory.py similarity index 97% rename from toxygen/items_factory.py rename to toxygen/ui/items_factory.py index 44a00ad..cdf127a 100644 --- a/toxygen/items_factory.py +++ b/toxygen/ui/items_factory.py @@ -1,5 +1,4 @@ -from PyQt5 import QtWidgets, QtCore -from list_items import * +from ui.list_items import * class ItemsFactory: diff --git a/toxygen/list_items.py b/toxygen/ui/list_items.py similarity index 99% rename from toxygen/list_items.py rename to toxygen/ui/list_items.py index 9b92f2a..0e4991e 100644 --- a/toxygen/list_items.py +++ b/toxygen/ui/list_items.py @@ -1,12 +1,12 @@ -from toxcore_enums_and_consts import * +from wrapper.toxcore_enums_and_consts import * from PyQt5 import QtCore, QtGui, QtWidgets -import profile +from contacts import profile from file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR from util import curr_directory, convert_time, curr_time -from widgets import DataLabel, create_menu +from ui.widgets import DataLabel, create_menu import html as h import smileys -import settings +from user_data import settings import re @@ -60,7 +60,7 @@ class MessageEdit(QtWidgets.QTextBrowser): def quote_text(self): text = self.textCursor().selection().toPlainText() if text: - import mainscreen + from ui import mainscreen window = mainscreen.MainWindow.get_instance() text = '>' + '\n>'.join(text.split('\n')) if window.messageEdit.toPlainText(): @@ -70,7 +70,7 @@ class MessageEdit(QtWidgets.QTextBrowser): def on_anchor_clicked(self, url): text = str(url.toString()) if text.startswith('tox:'): - import menu + from ui import menu self.add_contact = menu.AddContact(text[4:]) self.add_contact.show() else: diff --git a/toxygen/loginscreen.py b/toxygen/ui/loginscreen.py similarity index 98% rename from toxygen/loginscreen.py rename to toxygen/ui/loginscreen.py index 77aa5ba..cc1f45c 100644 --- a/toxygen/loginscreen.py +++ b/toxygen/ui/loginscreen.py @@ -1,5 +1,4 @@ -from PyQt5 import QtWidgets, QtCore -from widgets import * +from ui.widgets import * class NickEdit(LineEdit): diff --git a/toxygen/mainscreen.py b/toxygen/ui/mainscreen.py similarity index 99% rename from toxygen/mainscreen.py rename to toxygen/ui/mainscreen.py index c76f19b..ce828b0 100644 --- a/toxygen/mainscreen.py +++ b/toxygen/ui/mainscreen.py @@ -1,11 +1,10 @@ -from menu import * -from profile import * -from list_items import * -from widgets import MultilineEdit, ComboBox +from ui.menu import * +from contacts.profile import * +from ui.list_items import * +from ui.widgets import MultilineEdit, ComboBox import plugin_support -from mainscreen_widgets import * -import settings -import toxes +from ui.mainscreen_widgets import * +from user_data import toxes, settings class MainWindow(QtWidgets.QMainWindow, Singleton): diff --git a/toxygen/mainscreen_widgets.py b/toxygen/ui/mainscreen_widgets.py similarity index 99% rename from toxygen/mainscreen_widgets.py rename to toxygen/ui/mainscreen_widgets.py index 9955771..bbf6c5a 100644 --- a/toxygen/mainscreen_widgets.py +++ b/toxygen/ui/mainscreen_widgets.py @@ -1,6 +1,6 @@ from PyQt5 import QtCore, QtGui, QtWidgets -from widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit -from profile import Profile +from ui.widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit +from contacts.profile import Profile import smileys import util @@ -333,7 +333,7 @@ class WelcomeScreen(CenteredWidget): QtCore.QTimer.singleShot(1000, self.show) def not_show(self): - import settings + from user_data import settings s = settings.Settings.get_instance() s['show_welcome_screen'] = False s.save() diff --git a/toxygen/menu.py b/toxygen/ui/menu.py similarity index 99% rename from toxygen/menu.py rename to toxygen/ui/menu.py index 17f4e17..606c545 100644 --- a/toxygen/menu.py +++ b/toxygen/ui/menu.py @@ -1,10 +1,10 @@ from PyQt5 import QtCore, QtGui, QtWidgets -from settings import * -from profile import Profile +from user_data.settings import * +from contacts.profile import Profile from util import curr_directory, copy -from widgets import CenteredWidget, DataLabel, LineEdit, RubberBandWindow +from ui.widgets import CenteredWidget, DataLabel, LineEdit, RubberBandWindow import pyaudio -import toxes +from user_data import toxes import plugin_support import updater @@ -160,7 +160,7 @@ class ProfileSettings(CenteredWidget): self.default = QtWidgets.QPushButton(self) self.default.setGeometry(QtCore.QRect(40, 550, 620, 30)) path, name = Settings.get_auto_profile() - self.auto = path + name == ProfileHelper.get_path() + Settings.get_instance().name + self.auto = path + name == ProfileManager.get_path() + Settings.get_instance().name self.default.clicked.connect(self.auto_profile) self.retranslateUi() if profile.status is not None: @@ -199,7 +199,7 @@ class ProfileSettings(CenteredWidget): if self.auto: Settings.reset_auto_profile() else: - Settings.set_auto_profile(ProfileHelper.get_path(), Settings.get_instance().name) + Settings.set_auto_profile(ProfileManager.get_path(), Settings.get_instance().name) self.auto = not self.auto if self.auto: self.default.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as not default profile")) @@ -275,7 +275,7 @@ class ProfileSettings(CenteredWidget): settings.export(directory) profile = Profile.get_instance() profile.export_db(directory) - ProfileHelper.get_instance().export_profile(directory, reply == QtWidgets.QMessageBox.Yes) + ProfileManager.get_instance().export_profile(directory, reply == QtWidgets.QMessageBox.Yes) def closeEvent(self, event): profile = Profile.get_instance() diff --git a/toxygen/passwordscreen.py b/toxygen/ui/passwordscreen.py similarity index 99% rename from toxygen/passwordscreen.py rename to toxygen/ui/passwordscreen.py index ca721e5..9d3d523 100644 --- a/toxygen/passwordscreen.py +++ b/toxygen/ui/passwordscreen.py @@ -1,4 +1,4 @@ -from widgets import CenteredWidget, LineEdit +from ui.widgets import CenteredWidget, LineEdit from PyQt5 import QtCore, QtWidgets diff --git a/toxygen/widgets.py b/toxygen/ui/widgets.py similarity index 100% rename from toxygen/widgets.py rename to toxygen/ui/widgets.py diff --git a/toxygen/updater/__init__.py b/toxygen/updater/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/updater.py b/toxygen/updater/updater.py similarity index 99% rename from toxygen/updater.py rename to toxygen/updater/updater.py index 762892a..af80624 100644 --- a/toxygen/updater.py +++ b/toxygen/updater/updater.py @@ -1,6 +1,6 @@ import util import os -import settings +from user_data import settings import platform import urllib from PyQt5 import QtNetwork, QtCore diff --git a/toxygen/user_data/__init__.py b/toxygen/user_data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/user_data/profile_manager.py b/toxygen/user_data/profile_manager.py new file mode 100644 index 0000000..96f591c --- /dev/null +++ b/toxygen/user_data/profile_manager.py @@ -0,0 +1,70 @@ +class ProfileManager(): + """ + Class with methods for search, load and save profiles + """ + def __init__(self, path, name): + path = append_slash(path) + self._path = path + name + '.tox' + self._directory = path + # create /avatars if not exists: + directory = path + 'avatars' + if not os.path.exists(directory): + os.makedirs(directory) + + def open_profile(self): + with open(self._path, 'rb') as fl: + data = fl.read() + if data: + return data + else: + raise IOError('Save file has zero size!') + + def get_dir(self): + return self._directory + + def save_profile(self, data): + inst = ToxES.get_instance() + if inst.has_password(): + data = inst.pass_encrypt(data) + with open(self._path, 'wb') as fl: + fl.write(data) + print('Profile saved successfully') + + def export_profile(self, new_path, use_new_path): + path = new_path + os.path.basename(self._path) + with open(self._path, 'rb') as fin: + data = fin.read() + with open(path, 'wb') as fout: + fout.write(data) + print('Profile exported successfully') + copy(self._directory + 'avatars', new_path + 'avatars') + if use_new_path: + self._path = new_path + os.path.basename(self._path) + self._directory = new_path + Settings.get_instance().update_path() + + @staticmethod + def find_profiles(): + """ + Find available tox profiles + """ + path = Settings.get_default_path() + result = [] + # check default path + if not os.path.exists(path): + os.makedirs(path) + for fl in os.listdir(path): + if fl.endswith('.tox'): + name = fl[:-4] + result.append((path, name)) + path = curr_directory() + # check current directory + for fl in os.listdir(path): + if fl.endswith('.tox'): + name = fl[:-4] + result.append((path + '/', name)) + return result + + @staticmethod + def get_path(): + return ProfileManager.get_instance().get_dir() diff --git a/toxygen/settings.py b/toxygen/user_data/settings.py similarity index 72% rename from toxygen/settings.py rename to toxygen/user_data/settings.py index 431688a..45d41bd 100644 --- a/toxygen/settings.py +++ b/toxygen/user_data/settings.py @@ -3,7 +3,7 @@ import json import os from util import Singleton, curr_directory, log, copy, append_slash import pyaudio -from toxes import ToxES +from user_data.toxes import ToxES import smileys @@ -14,7 +14,7 @@ class Settings(dict, Singleton): def __init__(self, name): Singleton.__init__(self) - self.path = ProfileHelper.get_path() + str(name) + '.json' + self.path = ProfileManager.get_path() + str(name) + '.json' self.name = name if os.path.isfile(self.path): with open(self.path, 'rb') as fl: @@ -184,7 +184,7 @@ class Settings(dict, Singleton): fl.write(text) def close(self): - profile_path = ProfileHelper.get_path() + profile_path = ProfileManager.get_path() path = str(profile_path + str(self.name) + '.lock') if os.path.isfile(path): os.remove(path) @@ -193,7 +193,7 @@ class Settings(dict, Singleton): """ Mark current profile as active """ - profile_path = ProfileHelper.get_path() + profile_path = ProfileManager.get_path() path = str(profile_path + str(self.name) + '.lock') with open(path, 'w') as fl: fl.write('active') @@ -204,7 +204,7 @@ class Settings(dict, Singleton): fl.write(text) def update_path(self): - self.path = ProfileHelper.get_path() + self.name + '.json' + self.path = ProfileManager.get_path() + self.name + '.json' @staticmethod def get_global_settings_path(): @@ -219,75 +219,3 @@ class Settings(dict, Singleton): else: return os.getenv('HOME') + '/.config/tox/' - -class ProfileHelper(Singleton): - """ - Class with methods for search, load and save profiles - """ - def __init__(self, path, name): - Singleton.__init__(self) - path = append_slash(path) - self._path = path + name + '.tox' - self._directory = path - # create /avatars if not exists: - directory = path + 'avatars' - if not os.path.exists(directory): - os.makedirs(directory) - - def open_profile(self): - with open(self._path, 'rb') as fl: - data = fl.read() - if data: - return data - else: - raise IOError('Save file has zero size!') - - def get_dir(self): - return self._directory - - def save_profile(self, data): - inst = ToxES.get_instance() - if inst.has_password(): - data = inst.pass_encrypt(data) - with open(self._path, 'wb') as fl: - fl.write(data) - print('Profile saved successfully') - - def export_profile(self, new_path, use_new_path): - path = new_path + os.path.basename(self._path) - with open(self._path, 'rb') as fin: - data = fin.read() - with open(path, 'wb') as fout: - fout.write(data) - print('Profile exported successfully') - copy(self._directory + 'avatars', new_path + 'avatars') - if use_new_path: - self._path = new_path + os.path.basename(self._path) - self._directory = new_path - Settings.get_instance().update_path() - - @staticmethod - def find_profiles(): - """ - Find available tox profiles - """ - path = Settings.get_default_path() - result = [] - # check default path - if not os.path.exists(path): - os.makedirs(path) - for fl in os.listdir(path): - if fl.endswith('.tox'): - name = fl[:-4] - result.append((path, name)) - path = curr_directory() - # check current directory - for fl in os.listdir(path): - if fl.endswith('.tox'): - name = fl[:-4] - result.append((path + '/', name)) - return result - - @staticmethod - def get_path(): - return ProfileHelper.get_instance().get_dir() diff --git a/toxygen/toxes.py b/toxygen/user_data/toxes.py similarity index 77% rename from toxygen/toxes.py rename to toxygen/user_data/toxes.py index 5b7282f..5a22f61 100644 --- a/toxygen/toxes.py +++ b/toxygen/user_data/toxes.py @@ -1,12 +1,8 @@ -import util -import toxencryptsave +class ToxES: -class ToxES(util.Singleton): - - def __init__(self): - super().__init__() - self._toxencryptsave = toxencryptsave.ToxEncryptSave() + def __init__(self, toxencryptsave): + self._toxencryptsave = toxencryptsave self._passphrase = None def set_password(self, passphrase): diff --git a/toxygen/util/__init__.py b/toxygen/util/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/util.py b/toxygen/util/util.py similarity index 98% rename from toxygen/util.py rename to toxygen/util/util.py index e8d702a..bb27da6 100644 --- a/toxygen/util.py +++ b/toxygen/util/util.py @@ -5,7 +5,7 @@ import sys import re -program_version = '0.4.1' +program_version = '0.5.0' def cached(func): diff --git a/toxygen/wrapper/__init__.py b/toxygen/wrapper/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/libtox.py b/toxygen/wrapper/libtox.py similarity index 100% rename from toxygen/libtox.py rename to toxygen/wrapper/libtox.py diff --git a/toxygen/tox.py b/toxygen/wrapper/tox.py similarity index 68% rename from toxygen/tox.py rename to toxygen/wrapper/tox.py index ef4e44c..c6f5912 100644 --- a/toxygen/tox.py +++ b/toxygen/wrapper/tox.py @@ -1,7 +1,7 @@ from ctypes import * -from toxcore_enums_and_consts import * -from toxav import ToxAV -from libtox import LibToxCore +from wrapper.toxcore_enums_and_consts import * +from wrapper.toxav import ToxAV +from wrapper.libtox import LibToxCore class ToxOptions(Structure): @@ -1514,88 +1514,842 @@ class Tox: elif tox_err_get_port == TOX_ERR_GET_PORT['NOT_BOUND']: raise RuntimeError('The instance was not bound to any port.') - # ----------------------------------------------------------------------------------------------------------------- - # Group chats +# ----------------------------------------------------------------------------------------------------------------- + # Group chat instance management # ----------------------------------------------------------------------------------------------------------------- - def del_groupchat(self, groupnumber): - result = Tox.libtoxcore.tox_del_groupchat(self._tox_pointer, c_int(groupnumber), None) + def group_new(self, privacy_state, group_name): + """ + Creates a new group chat. + This function creates a new group chat object and adds it to the chats array. + The client should initiate its peer list with self info after calling this function, as + the peer_join callback will not be triggered. + :param privacy_state: The privacy state of the group. If this is set to TOX_GROUP_PRIVACY_STATE_PUBLIC, + the group will attempt to announce itself to the DHT and anyone with the Chat ID may join. + Otherwise a friend invite will be required to join the group. + :param group_name: The name of the group. The name must be non-NULL. + :return group number on success, UINT32_MAX on failure. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_new(self._tox_pointer, privacy_state, group_name, + len(group_name), byref(error)) return result - def group_peername(self, groupnumber, peernumber): - buffer = create_string_buffer(TOX_MAX_NAME_LENGTH) - result = Tox.libtoxcore.tox_group_peername(self._tox_pointer, c_int(groupnumber), c_int(peernumber), - buffer, None) - return str(buffer[:result], 'utf-8') + def group_join(self, chat_id, password): + """ + Joins a group chat with specified Chat ID. + This function creates a new group chat object, adds it to the chats array, and sends + a DHT announcement to find peers in the group associated with chat_id. Once a peer has been + found a join attempt will be initiated. + :param chat_id: The Chat ID of the group you wish to join. This must be TOX_GROUP_CHAT_ID_SIZE bytes. + :param password: The password required to join the group. Set to NULL if no password is required. + :return groupnumber on success, UINT32_MAX on failure. + """ - def invite_friend(self, friendnumber, groupnumber): - result = Tox.libtoxcore.tox_invite_friend(self._tox_pointer, c_int(friendnumber), - c_int(groupnumber), None) + error = c_int() + result = Tox.libtoxcore.tox_group_join(self._tox_pointer, string_to_bin(chat_id), + password, + len(password) if password is not None else 0, + byref(error)) return result - def join_groupchat(self, friendnumber, data): - result = Tox.libtoxcore.tox_join_groupchat(self._tox_pointer, - c_int(friendnumber), c_char_p(data), c_uint16(len(data)), None) + def group_reconnect(self, groupnumber): + """ + Reconnects to a group. + This function disconnects from all peers in the group, then attempts to reconnect with the group. + The caller's state is not changed (i.e. name, status, role, chat public key etc.) + :param groupnumber: The group number of the group we wish to reconnect to. + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_reconnect(self._tox_pointer, groupnumber, byref(error)) return result - def group_message_send(self, groupnumber, message): - result = Tox.libtoxcore.tox_group_message_send(self._tox_pointer, c_int(groupnumber), c_char_p(message), - c_uint16(len(message)), None) + def group_leave(self, groupnumber, message): + """ + Leaves a group. + This function sends a parting packet containing a custom (non-obligatory) message to all + peers in a group, and deletes the group from the chat array. All group state information is permanently + lost, including keys and role credentials. + :param groupnumber: The group number of the group we wish to leave. + :param message: The parting message to be sent to all the peers. Set to NULL if we do not wish to + send a parting message. + :return True if the group chat instance was successfully deleted. + """ + + error = c_int() + f = Tox.libtoxcore.tox_group_leave + f.restype = c_bool + result = f(self._tox_pointer, groupnumber, message, + len(message) if message is not None else 0, byref(error)) return result - def group_action_send(self, groupnumber, action): - result = Tox.libtoxcore.tox_group_action_send(self._tox_pointer, - c_int(groupnumber), c_char_p(action), - c_uint16(len(action)), None) + # ----------------------------------------------------------------------------------------------------------------- + # Group user-visible client information (nickname/status/role/public key) + # ----------------------------------------------------------------------------------------------------------------- + + def group_self_set_name(self, groupnumber, name): + """ + Set the client's nickname for the group instance designated by the given group number. + Nickname length cannot exceed TOX_MAX_NAME_LENGTH. If length is equal to zero or name is a NULL + pointer, the function call will fail. + :param name: A byte array containing the new nickname. + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_self_set_name(self._tox_pointer, groupnumber, name, len(name), byref(error)) return result - def group_set_title(self, groupnumber, title): - result = Tox.libtoxcore.tox_group_set_title(self._tox_pointer, c_int(groupnumber), - c_char_p(title), c_uint8(len(title)), None) + def group_self_get_name_size(self, groupnumber): + """ + Return the length of the client's current nickname for the group instance designated + by groupnumber as passed to tox_group_self_set_name. + If no nickname was set before calling this function, the name is empty, + and this function returns 0. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_self_get_name_size(self._tox_pointer, groupnumber, byref(error)) return result - def group_get_title(self, groupnumber): - buffer = create_string_buffer(TOX_MAX_NAME_LENGTH) - result = Tox.libtoxcore.tox_group_get_title(self._tox_pointer, - c_int(groupnumber), buffer, - c_uint32(TOX_MAX_NAME_LENGTH), None) - return str(buffer[:result], 'utf-8') + def group_self_get_name(self, groupnumber): + """ + Write the nickname set by tox_group_self_set_name to a byte array. + If no nickname was set before calling this function, the name is empty, + and this function has no effect. + Call tox_group_self_get_name_size to find out how much memory to allocate for the result. + :return nickname + """ - def group_number_peers(self, groupnumber): - result = Tox.libtoxcore.tox_group_number_peers(self._tox_pointer, c_int(groupnumber), None) + error = c_int() + size = self.group_self_get_name_size(groupnumber) + name = create_string_buffer(size) + result = Tox.libtoxcore.tox_group_self_get_name(self._tox_pointer, groupnumber, name, byref(error)) + return str(name[:size], 'utf-8') + + def group_self_set_status(self, groupnumber, status): + + """ + Set the client's status for the group instance. Status must be a TOX_USER_STATUS. + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_self_set_status(self._tox_pointer, groupnumber, status, byref(error)) return result - def add_av_groupchat(self): - result = self.AV.libtoxav.toxav_add_av_groupchat(self._tox_pointer, None, None) + def group_self_get_status(self, groupnumber): + """ + returns the client's status for the group instance on success. + return value is unspecified on failure. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_self_get_status(self._tox_pointer, groupnumber, byref(error)) return result - def join_av_groupchat(self, friendnumber, data): - result = self.AV.libtoxav.toxav_join_av_groupchat(self._tox_pointer, c_int32(friendnumber), - c_char_p(data), c_uint16(len(data)), - None, None) + def group_self_get_role(self, groupnumber): + """ + returns the client's role for the group instance on success. + return value is unspecified on failure. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_self_get_role(self._tox_pointer, groupnumber, byref(error)) return result - def callback_group_invite(self, callback, user_data=None): - c_callback = CFUNCTYPE(None, c_void_p, c_int32, c_uint8, POINTER(c_uint8), c_uint16, c_void_p) - self.group_invite_cb = c_callback(callback) - Tox.libtoxcore.tox_callback_group_invite(self._tox_pointer, self.group_invite_cb, user_data) + def group_self_get_peer_id(self, groupnumber): + """ + returns the client's peer id for the group instance on success. + return value is unspecified on failure. + """ - def callback_group_message(self, callback, user_data=None): - c_callback = CFUNCTYPE(None, c_void_p, c_int, c_int, c_char_p, c_uint16, c_void_p) + error = c_int() + result = Tox.libtoxcore.tox_group_self_get_peer_id(self._tox_pointer, groupnumber, byref(error)) + return result + + def group_self_get_public_key(self, groupnumber): + """ + Write the client's group public key designated by the given group number to a byte array. + This key will be permanently tied to the client's identity for this particular group until + the client explicitly leaves the group or gets kicked/banned. This key is the only way for + other peers to reliably identify the client across client restarts. + `public_key` should have room for at least TOX_GROUP_PEER_PUBLIC_KEY_SIZE bytes. + :return public key + """ + + error = c_int() + key = create_string_buffer(TOX_GROUP_PEER_PUBLIC_KEY_SIZE) + result = Tox.libtoxcore.tox_group_self_get_public_key(self._tox_pointer, groupnumber, + key, byref(error)) + return bin_to_string(key, TOX_GROUP_PEER_PUBLIC_KEY_SIZE) + + # ----------------------------------------------------------------------------------------------------------------- + # Peer-specific group state queries. + # ----------------------------------------------------------------------------------------------------------------- + + def group_peer_get_name_size(self, groupnumber, peer_id): + """ + Return the length of the peer's name. If the group number or ID is invalid, the + return value is unspecified. + The return value is equal to the `length` argument received by the last + `group_peer_name` callback. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_peer_get_name_size(self._tox_pointer, groupnumber, peer_id, byref(error)) + return result + + def group_peer_get_name(self, groupnumber, peer_id): + """ + Write the name of the peer designated by the given ID to a byte + array. + Call tox_group_peer_get_name_size to determine the allocation size for the `name` parameter. + The data written to `name` is equal to the data received by the last + `group_peer_name` callback. + :param groupnumber: The group number of the group we wish to query. + :param peer_id: The ID of the peer whose name we want to retrieve. + :return name. + """ + error = c_int() + size = self.group_peer_get_name_size(groupnumber, peer_id) + name = create_string_buffer(size) + result = Tox.libtoxcore.tox_group_peer_get_name(self._tox_pointer, groupnumber, peer_id, name, byref(error)) + return str(name[:], 'utf-8') + + def group_peer_get_status(self, groupnumber, peer_id): + """ + Return the peer's user status (away/busy/...). If the ID or group number is + invalid, the return value is unspecified. + The status returned is equal to the last status received through the + `group_peer_status` callback. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_peer_get_status(self._tox_pointer, groupnumber, peer_id, byref(error)) + return result + + def group_peer_get_role(self, groupnumber, peer_id): + """ + Return the peer's role (user/moderator/founder...). If the ID or group number is + invalid, the return value is unspecified. + The role returned is equal to the last role received through the + `group_moderation` callback. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_peer_get_role(self._tox_pointer, groupnumber, peer_id, byref(error)) + return result + + def group_peer_get_public_key(self, groupnumber, peer_id): + """ + Write the group public key with the designated peer_id for the designated group number to public_key. + This key will be parmanently tied to a particular peer until they explicitly leave the group or + get kicked/banned, and is the only way to reliably identify the same peer across client restarts. + `public_key` should have room for at least TOX_GROUP_PEER_PUBLIC_KEY_SIZE bytes. + :return public key + """ + + error = c_int() + key = create_string_buffer(TOX_GROUP_PEER_PUBLIC_KEY_SIZE) + result = Tox.libtoxcore.tox_group_peer_get_public_key(self._tox_pointer, groupnumber, peer_id, + key, byref(error)) + return bin_to_string(key, TOX_GROUP_PEER_PUBLIC_KEY_SIZE) + + def callback_group_peer_name(self, callback, user_data): + """ + Set the callback for the `group_peer_name` event. Pass NULL to unset. + This event is triggered when a peer changes their nickname. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_char_p, c_size_t, c_void_p) + self.group_peer_name_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_peer_name(self._tox_pointer, self.group_peer_name_cb, user_data) + + def callback_group_peer_status(self, callback, user_data): + """ + Set the callback for the `group_peer_status` event. Pass NULL to unset. + This event is triggered when a peer changes their status. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_int, c_void_p) + self.group_peer_status_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_peer_status(self._tox_pointer, self.group_peer_status_cb, user_data) + + # ----------------------------------------------------------------------------------------------------------------- + # Group chat state queries and events. + # ----------------------------------------------------------------------------------------------------------------- + + def group_set_topic(self, groupnumber, topic): + """ + Set the group topic and broadcast it to the rest of the group. + topic length cannot be longer than TOX_GROUP_MAX_TOPIC_LENGTH. If length is equal to zero or + topic is set to NULL, the topic will be unset. + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_set_topic(self._tox_pointer, groupnumber, topic, len(topic), byref(error)) + return result + + def group_get_topic_size(self, groupnumber): + """ + Return the length of the group topic. If the group number is invalid, the + return value is unspecified. + The return value is equal to the `length` argument received by the last + `group_topic` callback. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_get_topic_size(self._tox_pointer, groupnumber, byref(error)) + return result + + def group_get_topic(self, groupnumber): + """ + Write the topic designated by the given group number to a byte array. + Call tox_group_get_topic_size to determine the allocation size for the `topic` parameter. + The data written to `topic` is equal to the data received by the last + `group_topic` callback. + :return topic + """ + + error = c_int() + size = self.group_get_topic_size(groupnumber) + topic = create_string_buffer(size) + result = Tox.libtoxcore.tox_group_get_topic(self._tox_pointer, groupnumber, topic, byref(error)) + return str(topic[:size], 'utf-8') + + def group_get_name_size(self, groupnumber): + """ + Return the length of the group name. If the group number is invalid, the + return value is unspecified. + """ + error = c_int() + result = Tox.libtoxcore.tox_group_get_name_size(self._tox_pointer, groupnumber, byref(error)) + return result + + def group_get_name(self, groupnumber): + """ + Write the name of the group designated by the given group number to a byte array. + Call tox_group_get_name_size to determine the allocation size for the `name` parameter. + :return true on success. + """ + + error = c_int() + size = self.group_get_name_size(groupnumber) + name = create_string_buffer(size) + result = Tox.libtoxcore.tox_group_get_name(self._tox_pointer, groupnumber, + name, byref(error)) + return str(name[:size], 'utf-8') + + def group_get_chat_id(self, groupnumber): + """ + Write the Chat ID designated by the given group number to a byte array. + `chat_id` should have room for at least TOX_GROUP_CHAT_ID_SIZE bytes. + :return chat id. + """ + + error = c_int() + buff = create_string_buffer(TOX_GROUP_CHAT_ID_SIZE) + result = Tox.libtoxcore.tox_group_get_chat_id(self._tox_pointer, groupnumber, + buff, byref(error)) + return bin_to_string(buff, TOX_GROUP_CHAT_ID_SIZE) + + def group_get_number_groups(self): + """ + Return the number of groups in the Tox chats array. + """ + + result = Tox.libtoxcore.tox_group_get_number_groups(self._tox_pointer) + return result + + def group_get_privacy_state(self, groupnumber): + """ + Return the privacy state of the group designated by the given group number. If group number + is invalid, the return value is unspecified. + The value returned is equal to the data received by the last + `group_privacy_state` callback. + see the `Group chat founder controls` section for the respective set function. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_get_privacy_state(self._tox_pointer, groupnumber, byref(error)) + return result + + def group_get_peer_limit(self, groupnumber): + """ + Return the maximum number of peers allowed for the group designated by the given group number. + If the group number is invalid, the return value is unspecified. + The value returned is equal to the data received by the last + `group_peer_limit` callback. + see the `Group chat founder controls` section for the respective set function. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_get_peer_limit(self._tox_pointer, groupnumber, byref(error)) + return result + + def group_get_password_size(self, groupnumber): + """ + Return the length of the group password. If the group number is invalid, the + return value is unspecified. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_get_password_size(self._tox_pointer, groupnumber, byref(error)) + return result + + def group_get_password(self, groupnumber): + """ + Write the password for the group designated by the given group number to a byte array. + Call tox_group_get_password_size to determine the allocation size for the `password` parameter. + The data received is equal to the data received by the last + `group_password` callback. + see the `Group chat founder controls` section for the respective set function. + :return password + """ + + error = c_int() + size = self.group_get_password_size(groupnumber) + password = create_string_buffer(size) + result = Tox.libtoxcore.tox_group_get_password(self._tox_pointer, groupnumber, + password, byref(error)) + return str(password[:size], 'utf-8') + + def callback_group_topic(self, callback, user_data): + """ + Set the callback for the `group_topic` event. Pass NULL to unset. + This event is triggered when a peer changes the group topic. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_char_p, c_size_t, c_void_p) + self.group_topic_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_topic(self._tox_pointer, self.group_topic_cb, user_data) + + def callback_group_privacy_state(self, callback, user_data): + """ + Set the callback for the `group_privacy_state` event. Pass NULL to unset. + This event is triggered when the group founder changes the privacy state. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_int, c_void_p) + self.group_privacy_state_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_privacy_state(self._tox_pointer, self.group_privacy_state_cb, user_data) + + def callback_group_peer_limit(self, callback, user_data): + """ + Set the callback for the `group_peer_limit` event. Pass NULL to unset. + This event is triggered when the group founder changes the maximum peer limit. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_void_p) + self.group_peer_limit_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_peer_limit(self._tox_pointer, self.group_peer_limit_cb, user_data) + + def callback_group_password(self, callback, user_data): + """ + Set the callback for the `group_password` event. Pass NULL to unset. + This event is triggered when the group founder changes the group password. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_char_p, c_size_t, c_void_p) + self.group_password_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_password(self._tox_pointer, self.group_password_cb, user_data) + + # ----------------------------------------------------------------------------------------------------------------- + # Group message sending + # ----------------------------------------------------------------------------------------------------------------- + + def group_send_custom_packet(self, groupnumber, lossless, data): + """ + Send a custom packet to the group. + If lossless is true the packet will be lossless. Lossless packet behaviour is comparable + to TCP (reliability, arrive in order) but with packets instead of a stream. + If lossless is false, the packet will be lossy. Lossy packets behave like UDP packets, + meaning they might never reach the other side or might arrive more than once (if someone + is messing with the connection) or might arrive in the wrong order. + Unless latency is an issue or message reliability is not important, it is recommended that you use + lossless custom packets. + :param groupnumber: The group number of the group the message is intended for. + :param lossless: True if the packet should be lossless. + :param data A byte array containing the packet data. + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_send_custom_packet(self._tox_pointer, groupnumber, lossless, data, + len(data), byref(error)) + return result + + def group_send_private_message(self, groupnumber, peer_id, message): + """ + Send a text chat message to the specified peer in the specified group. + This function creates a group private message packet and pushes it into the send + queue. + The message length may not exceed TOX_MAX_MESSAGE_LENGTH. Larger messages + must be split by the client and sent as separate messages. Other clients can + then reassemble the fragments. Messages may not be empty. + :param groupnumber: The group number of the group the message is intended for. + :param peer_id: The ID of the peer the message is intended for. + :param message: A non-NULL pointer to the first element of a byte array containing the message text. + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_send_private_message(self._tox_pointer, groupnumber, peer_id, message, + len(message), byref(error)) + return result + + def group_send_message(self, groupnumber, type, message): + """ + Send a text chat message to the group. + This function creates a group message packet and pushes it into the send + queue. + The message length may not exceed TOX_MAX_MESSAGE_LENGTH. Larger messages + must be split by the client and sent as separate messages. Other clients can + then reassemble the fragments. Messages may not be empty. + :param groupnumber: The group number of the group the message is intended for. + :param type: Message type (normal, action, ...). + :param message: A non-NULL pointer to the first element of a byte array containing the message text. + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_send_message(self._tox_pointer, groupnumber, type, message, len(message), + byref(error)) + return result + + # ----------------------------------------------------------------------------------------------------------------- + # Group message receiving + # ----------------------------------------------------------------------------------------------------------------- + + def callback_group_message(self, callback, user_data): + """ + Set the callback for the `group_message` event. Pass NULL to unset. + This event is triggered when the client receives a group message. + Callback: python function with params: + tox Tox* instance + groupnumber The group number of the group the message is intended for. + peer_id The ID of the peer who sent the message. + type The type of message (normal, action, ...). + message The message data. + length The length of the message. + user_data - user data + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_int, c_char_p, c_size_t, c_void_p) self.group_message_cb = c_callback(callback) Tox.libtoxcore.tox_callback_group_message(self._tox_pointer, self.group_message_cb, user_data) - def callback_group_action(self, callback, user_data=None): - c_callback = CFUNCTYPE(None, c_void_p, c_int, c_int, c_char_p, c_uint16, c_void_p) - self.group_action_cb = c_callback(callback) - Tox.libtoxcore.tox_callback_group_action(self._tox_pointer, self.group_action_cb, user_data) + def callback_group_private_message(self, callback, user_data): + """ + Set the callback for the `group_private_message` event. Pass NULL to unset. + This event is triggered when the client receives a private message. + """ - def callback_group_title(self, callback, user_data=None): - c_callback = CFUNCTYPE(None, c_void_p, c_int, c_int, c_char_p, c_uint8, c_void_p) - self.group_title_cb = c_callback(callback) - Tox.libtoxcore.tox_callback_group_title(self._tox_pointer, self.group_title_cb, user_data) + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_char_p, c_size_t, c_void_p) + self.group_private_message_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_private_message(self._tox_pointer, self.group_private_message_cb, user_data) - def callback_group_namelist_change(self, callback, user_data=None): - c_callback = CFUNCTYPE(None, c_void_p, c_int, c_int, c_uint8, c_void_p) - self.group_namelist_change_cb = c_callback(callback) - Tox.libtoxcore.tox_callback_group_namelist_change(self._tox_pointer, self.group_namelist_change_cb, user_data) + def callback_group_custom_packet(self, callback, user_data): + """ + Set the callback for the `group_custom_packet` event. Pass NULL to unset. + This event is triggered when the client receives a custom packet. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, POINTER(c_uint8), c_void_p) + self.group_custom_packet_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_custom_packet(self._tox_pointer, self.group_custom_packet_cb, user_data) + + # ----------------------------------------------------------------------------------------------------------------- + # Group chat inviting and join/part events + # ----------------------------------------------------------------------------------------------------------------- + + def group_invite_friend(self, groupnumber, friend_number): + """ + Invite a friend to a group. + This function creates an invite request packet and pushes it to the send queue. + :param groupnumber: The group number of the group the message is intended for. + :param friend_number: The friend number of the friend the invite is intended for. + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_invite_friend(self._tox_pointer, groupnumber, friend_number, byref(error)) + return result + + def group_invite_accept(self, invite_data, friend_number, password=None): + """ + Accept an invite to a group chat that the client previously received from a friend. The invite + is only valid while the inviter is present in the group. + :param invite_data: The invite data received from the `group_invite` event. + :param password: The password required to join the group. Set to NULL if no password is required. + :return the groupnumber on success, UINT32_MAX on failure. + """ + + error = c_int() + f = Tox.libtoxcore.tox_group_invite_accept + f.restype = c_uint32 + result = f(self._tox_pointer, friend_number, invite_data, len(invite_data), password, + len(password) if password is not None else 0, byref(error)) + print('Invite accept. Result:', result, 'Error:', error.value) + return result + + def callback_group_invite(self, callback, user_data): + """ + Set the callback for the `group_invite` event. Pass NULL to unset. + This event is triggered when the client receives a group invite from a friend. The client must store + invite_data which is used to join the group via tox_group_invite_accept. + Callback: python function with params: + tox - Tox* + friend_number The friend number of the contact who sent the invite. + invite_data The invite data. + length The length of invite_data. + user_data - user data + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t, c_void_p) + self.group_invite_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_invite(self._tox_pointer, self.group_invite_cb, user_data) + + def callback_group_peer_join(self, callback, user_data): + """ + Set the callback for the `group_peer_join` event. Pass NULL to unset. + This event is triggered when a peer other than self joins the group. + Callback: python function with params: + tox - Tox* + group_number - group number + peer_id - peer id + user_data - user data + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_void_p) + self.group_peer_join_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_peer_join(self._tox_pointer, self.group_peer_join_cb, user_data) + + def callback_group_peer_exit(self, callback, user_data): + """ + Set the callback for the `group_peer_exit` event. Pass NULL to unset. + This event is triggered when a peer other than self exits the group. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_char_p, c_size_t, c_void_p) + self.group_peer_exit_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_peer_exit(self._tox_pointer, self.group_peer_exit_cb, user_data) + + def callback_group_self_join(self, callback, user_data): + """ + Set the callback for the `group_self_join` event. Pass NULL to unset. + This event is triggered when the client has successfully joined a group. Use this to initialize + any group information the client may need. + Callback: python fucntion with params: + tox - *Tox + group_number - group number + user_data - user data + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_void_p) + self.group_self_join_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_self_join(self._tox_pointer, self.group_self_join_cb, user_data) + + def callback_group_join_fail(self, callback, user_data): + """ + Set the callback for the `group_join_fail` event. Pass NULL to unset. + This event is triggered when the client fails to join a group. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_int, c_void_p) + self.group_join_fail_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_join_fail(self._tox_pointer, self.group_join_fail_cb, user_data) + + # ----------------------------------------------------------------------------------------------------------------- + # Group chat founder controls (these only work for the group founder) + # ----------------------------------------------------------------------------------------------------------------- + + def group_founder_set_password(self, groupnumber, password): + """ + Set or unset the group password. + This function sets the groups password, creates a new group shared state including the change, + and distributes it to the rest of the group. + :param groupnumber: The group number of the group for which we wish to set the password. + :param password: The password we want to set. Set password to NULL to unset the password. + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_founder_set_password(self._tox_pointer, groupnumber, password, + len(password), byref(error)) + return result + + def group_founder_set_privacy_state(self, groupnumber, privacy_state): + """ + Set the group privacy state. + This function sets the group's privacy state, creates a new group shared state + including the change, and distributes it to the rest of the group. + If an attempt is made to set the privacy state to the same state that the group is already + in, the function call will be successful and no action will be taken. + :param groupnumber: The group number of the group for which we wish to change the privacy state. + :param privacy_state: The privacy state we wish to set the group to. + :return true on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_founder_set_privacy_state(self._tox_pointer, groupnumber, privacy_state, + byref(error)) + return result + + def group_founder_set_peer_limit(self, groupnumber, max_peers): + """ + Set the group peer limit. + This function sets a limit for the number of peers who may be in the group, creates a new + group shared state including the change, and distributes it to the rest of the group. + :param groupnumber: The group number of the group for which we wish to set the peer limit. + :param max_peers: The maximum number of peers to allow in the group. + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_founder_set_peer_limit(self._tox_pointer, groupnumber, + max_peers, byref(error)) + return result + + # ----------------------------------------------------------------------------------------------------------------- + # Group chat moderation + # ----------------------------------------------------------------------------------------------------------------- + + def group_toggle_ignore(self, groupnumber, peer_id, ignore): + """ + Ignore or unignore a peer. + :param groupnumber: The group number of the group the in which you wish to ignore a peer. + :param peer_id: The ID of the peer who shall be ignored or unignored. + :param ignore: True to ignore the peer, false to unignore the peer. + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_toggle_ignore(self._tox_pointer, groupnumber, peer_id, ignore, byref(error)) + return result + + def group_mod_set_role(self, groupnumber, peer_id, role): + """ + Set a peer's role. + This function will first remove the peer's previous role and then assign them a new role. + It will also send a packet to the rest of the group, requesting that they perform + the role reassignment. Note: peers cannot be set to the founder role. + :param groupnumber: The group number of the group the in which you wish set the peer's role. + :param peer_id: The ID of the peer whose role you wish to set. + :param role: The role you wish to set the peer to. + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_mod_set_role(self._tox_pointer, groupnumber, peer_id, role, byref(error)) + return result + + def group_mod_remove_peer(self, groupnumber, peer_id, set_ban): + """ + Kick/ban a peer. + This function will remove a peer from the caller's peer list and optionally add their IP address + to the ban list. It will also send a packet to all group members requesting them + to do the same. + :param groupnumber: The group number of the group the ban is intended for. + :param peer_id: The ID of the peer who will be kicked and/or added to the ban list. + :param set_ban: Set to true if a ban shall be set on the peer's IP address. + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_mod_remove_peer(self._tox_pointer, groupnumber, peer_id, + set_ban, byref(error)) + return result + + def group_mod_remove_ban(self, groupnumber, ban_id): + """ + Removes a ban. + This function removes a ban entry from the ban list, and sends a packet to the rest of + the group requesting that they do the same. + :param groupnumber: The group number of the group in which the ban is to be removed. + :param ban_id: The ID of the ban entry that shall be removed. + :return True on success + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_mod_remove_ban(self._tox_pointer, groupnumber, ban_id, byref(error)) + return result + + def callback_group_moderation(self, callback, user_data): + """ + Set the callback for the `group_moderation` event. Pass NULL to unset. + This event is triggered when a moderator or founder executes a moderation event. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint32, c_int, c_void_p) + self.group_moderation_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_moderation(self._tox_pointer, self.group_moderation_cb, user_data) + + # ----------------------------------------------------------------------------------------------------------------- + # Group chat ban list queries + # ----------------------------------------------------------------------------------------------------------------- + + def group_ban_get_list_size(self, groupnumber): + """ + Return the number of entries in the ban list for the group designated by + the given group number. If the group number is invalid, the return value is unspecified. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_ban_get_list_size(self._tox_pointer, groupnumber, byref(error)) + return result + + def group_ban_get_list(self, groupnumber): + """ + Copy a list of valid ban list ID's into an array. + Call tox_group_ban_get_list_size to determine the number of elements to allocate. + return true on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_ban_get_list(self._tox_pointer, groupnumber, POINTER(c_uint32)( + create_string_buffer(sizeof(c_uint32) * self.group_ban_get_list_size(groupnumber)), byref(error))) + return result + + def group_ban_get_name_size(self, groupnumber, ban_id): + """ + Return the length of the name for the ban list entry designated by ban_id, in the + group designated by the given group number. If either groupnumber or ban_id is invalid, + the return value is unspecified. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_ban_get_name_size(self._tox_pointer, groupnumber, ban_id, byref(error)) + return result + + def group_ban_get_name(self, groupnumber, ban_id): + """ + Write the name of the ban entry designated by ban_id in the group designated by the + given group number to a byte array. + Call tox_group_ban_get_name_size to find out how much memory to allocate for the result. + :return name + """ + + error = c_int() + size = self.group_ban_get_name_size(groupnumber, ban_id) + name = create_string_buffer() + + result = Tox.libtoxcore.tox_group_ban_get_name(self._tox_pointer, groupnumber, ban_id, + name, byref(error)) + return str(name[:size], 'utf-8') + + def group_ban_get_time_set(self, groupnumber, ban_id): + """ + Return a time stamp indicating the time the ban was set, for the ban list entry + designated by ban_id, in the group designated by the given group number. + If either groupnumber or ban_id is invalid, the return value is unspecified. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_ban_get_time_set(self._tox_pointer, groupnumber, ban_id, byref(error)) + return result diff --git a/toxygen/toxav.py b/toxygen/wrapper/toxav.py similarity index 99% rename from toxygen/toxav.py rename to toxygen/wrapper/toxav.py index 0ab891c..78ab11f 100644 --- a/toxygen/toxav.py +++ b/toxygen/wrapper/toxav.py @@ -1,7 +1,7 @@ from ctypes import c_int, POINTER, c_void_p, byref, ArgumentError, c_uint32, CFUNCTYPE, c_size_t, c_uint8, c_uint16 from ctypes import c_char_p, c_int32, c_bool, cast -from libtox import LibToxAV -from toxav_enums import * +from wrapper.libtox import LibToxAV +from wrapper.toxav_enums import * class ToxAV: diff --git a/toxygen/toxav_enums.py b/toxygen/wrapper/toxav_enums.py similarity index 100% rename from toxygen/toxav_enums.py rename to toxygen/wrapper/toxav_enums.py diff --git a/toxygen/wrapper/toxcore_enums_and_consts.py b/toxygen/wrapper/toxcore_enums_and_consts.py new file mode 100644 index 0000000..6942d3a --- /dev/null +++ b/toxygen/wrapper/toxcore_enums_and_consts.py @@ -0,0 +1,944 @@ +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_GROUP_PRIVACY_STATE = { + + # + # The group is considered to be public. Anyone may join the group using the Chat ID. + # + # If the group is in this state, even if the Chat ID is never explicitly shared + # with someone outside of the group, information including the Chat ID, IP addresses, + # and peer ID's (but not Tox ID's) is visible to anyone with access to a node + # storing a DHT entry for the given group. + # + 'TOX_GROUP_PRIVACY_STATE_PUBLIC': 0, + + # + # The group is considered to be private. The only way to join the group is by having + # someone in your contact list send you an invite. + # + # If the group is in this state, no group information (mentioned above) is present in the DHT; + # the DHT is not used for any purpose at all. If a public group is set to private, + # all DHT information related to the group will expire shortly. + # + 'TOX_GROUP_PRIVACY_STATE_PRIVATE': 1 +} + +TOX_GROUP_ROLE = { + + # + # May kick and ban all other peers as well as set their role to anything (except founder). + # Founders may also set the group password, toggle the privacy state, and set the peer limit. + # + 'TOX_GROUP_ROLE_FOUNDER': 0, + + # + # May kick, ban and set the user and observer roles for peers below this role. + # May also set the group topic. + # + 'TOX_GROUP_ROLE_MODERATOR': 1, + + # + # May communicate with other peers normally. + # + 'TOX_GROUP_ROLE_USER': 2, + + # + # May observe the group and ignore peers; may not communicate with other peers or with the group. + # + 'TOX_GROUP_ROLE_OBSERVER': 3 +} + +TOX_ERR_GROUP_NEW = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_NEW_OK': 0, + + # + # The group name exceeded TOX_GROUP_MAX_GROUP_NAME_LENGTH. + # + 'TOX_ERR_GROUP_NEW_TOO_LONG': 1, + + # + # group_name is NULL or length is zero. + # + 'TOX_ERR_GROUP_NEW_EMPTY': 2, + + # + # TOX_GROUP_PRIVACY_STATE is an invalid type. + # + 'TOX_ERR_GROUP_NEW_PRIVACY': 3, + + # + # The group instance failed to initialize. + # + 'TOX_ERR_GROUP_NEW_INIT': 4, + + # + # The group state failed to initialize. This usually indicates that something went wrong + # related to cryptographic signing. + # + 'TOX_ERR_GROUP_NEW_STATE': 5, + + # + # The group failed to announce to the DHT. This indicates a network related error. + # + 'TOX_ERR_GROUP_NEW_ANNOUNCE': 6, +} + +TOX_ERR_GROUP_JOIN = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_JOIN_OK': 0, + + # + # The group instance failed to initialize. + # + 'TOX_ERR_GROUP_JOIN_INIT': 1, + + # + # The chat_id pointer is set to NULL or a group with chat_id already exists. This usually + # happens if the client attempts to create multiple sessions for the same group. + # + 'TOX_ERR_GROUP_JOIN_BAD_CHAT_ID': 2, + + # + # Password length exceeded TOX_GROUP_MAX_PASSWORD_SIZE. + # + 'TOX_ERR_GROUP_JOIN_TOO_LONG': 3, +} + +TOX_ERR_GROUP_RECONNECT = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_RECONNECT_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_RECONNECT_GROUP_NOT_FOUND': 1, +} + +TOX_ERR_GROUP_LEAVE = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_LEAVE_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_LEAVE_GROUP_NOT_FOUND': 1, + + # + # Message length exceeded 'TOX_GROUP_MAX_PART_LENGTH. + # + 'TOX_ERR_GROUP_LEAVE_TOO_LONG': 2, + + # + # The parting packet failed to send. + # + 'TOX_ERR_GROUP_LEAVE_FAIL_SEND': 3, + + # + # The group chat instance failed to be deleted. This may occur due to memory related errors. + # + 'TOX_ERR_GROUP_LEAVE_DELETE_FAIL': 4, +} + +TOX_ERR_GROUP_SELF_QUERY = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_SELF_QUERY_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_SELF_QUERY_GROUP_NOT_FOUND': 1, +} + + +TOX_ERR_GROUP_SELF_NAME_SET = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_SELF_NAME_SET_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_SELF_NAME_SET_GROUP_NOT_FOUND': 1, + + # + # Name length exceeded 'TOX_MAX_NAME_LENGTH. + # + 'TOX_ERR_GROUP_SELF_NAME_SET_TOO_LONG': 2, + + # + # The length given to the set function is zero or name is a NULL pointer. + # + 'TOX_ERR_GROUP_SELF_NAME_SET_INVALID': 3, + + # + # The name is already taken by another peer in the group. + # + 'TOX_ERR_GROUP_SELF_NAME_SET_TAKEN': 4, + + # + # The packet failed to send. + # + 'TOX_ERR_GROUP_SELF_NAME_SET_FAIL_SEND': 5 +} + +TOX_ERR_GROUP_SELF_STATUS_SET = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_SELF_STATUS_SET_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_SELF_STATUS_SET_GROUP_NOT_FOUND': 1, + + # + # An invalid type was passed to the set function. + # + 'TOX_ERR_GROUP_SELF_STATUS_SET_INVALID': 2, + + # + # The packet failed to send. + # + 'TOX_ERR_GROUP_SELF_STATUS_SET_FAIL_SEND': 3 +} + +TOX_ERR_GROUP_PEER_QUERY = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_PEER_QUERY_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_PEER_QUERY_GROUP_NOT_FOUND': 1, + + # + # The ID passed did not designate a valid peer. + # + 'TOX_ERR_GROUP_PEER_QUERY_PEER_NOT_FOUND': 2 +} + +TOX_ERR_GROUP_STATE_QUERIES = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_STATE_QUERIES_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_STATE_QUERIES_GROUP_NOT_FOUND': 1 +} + + +TOX_ERR_GROUP_TOPIC_SET = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_TOPIC_SET_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_TOPIC_SET_GROUP_NOT_FOUND': 1, + + # + # Topic length exceeded 'TOX_GROUP_MAX_TOPIC_LENGTH. + # + 'TOX_ERR_GROUP_TOPIC_SET_TOO_LONG': 2, + + # + # The caller does not have the required permissions to set the topic. + # + 'TOX_ERR_GROUP_TOPIC_SET_PERMISSIONS': 3, + + # + # The packet could not be created. This error is usually related to cryptographic signing. + # + 'TOX_ERR_GROUP_TOPIC_SET_FAIL_CREATE': 4, + + # + # The packet failed to send. + # + 'TOX_ERR_GROUP_TOPIC_SET_FAIL_SEND': 5 +} + +TOX_ERR_GROUP_SEND_MESSAGE = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_SEND_MESSAGE_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_SEND_MESSAGE_GROUP_NOT_FOUND': 1, + + # + # Message length exceeded 'TOX_MAX_MESSAGE_LENGTH. + # + 'TOX_ERR_GROUP_SEND_MESSAGE_TOO_LONG': 2, + + # + # The message pointer is null or length is zero. + # + 'TOX_ERR_GROUP_SEND_MESSAGE_EMPTY': 3, + + # + # The message type is invalid. + # + 'TOX_ERR_GROUP_SEND_MESSAGE_BAD_TYPE': 4, + + # + # The caller does not have the required permissions to send group messages. + # + 'TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS': 5, + + # + # Packet failed to send. + # + 'TOX_ERR_GROUP_SEND_MESSAGE_FAIL_SEND': 6 +} + +TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_GROUP_NOT_FOUND': 1, + + # + # The ID passed did not designate a valid peer. + # + 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_PEER_NOT_FOUND': 2, + + # + # Message length exceeded 'TOX_MAX_MESSAGE_LENGTH. + # + 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_TOO_LONG': 3, + + # + # The message pointer is null or length is zero. + # + 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_EMPTY': 4, + + # + # The caller does not have the required permissions to send group messages. + # + 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_PERMISSIONS': 5, + + # + # Packet failed to send. + # + 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_FAIL_SEND': 6 +} + +TOX_ERR_GROUP_SEND_CUSTOM_PACKET = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_GROUP_NOT_FOUND': 1, + + # + # Message length exceeded 'TOX_MAX_MESSAGE_LENGTH. + # + 'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_TOO_LONG': 2, + + # + # The message pointer is null or length is zero. + # + 'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_EMPTY': 3, + + # + # The caller does not have the required permissions to send group messages. + # + 'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_PERMISSIONS': 4 +} + +TOX_ERR_GROUP_INVITE_FRIEND = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_INVITE_FRIEND_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_INVITE_FRIEND_GROUP_NOT_FOUND': 1, + + # + # The friend number passed did not designate a valid friend. + # + 'TOX_ERR_GROUP_INVITE_FRIEND_FRIEND_NOT_FOUND': 2, + + # + # Creation of the invite packet failed. This indicates a network related error. + # + 'TOX_ERR_GROUP_INVITE_FRIEND_INVITE_FAIL': 3, + + # + # Packet failed to send. + # + 'TOX_ERR_GROUP_INVITE_FRIEND_FAIL_SEND': 4 +} + +TOX_ERR_GROUP_INVITE_ACCEPT = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_INVITE_ACCEPT_OK': 0, + + # + # The invite data is not in the expected format. + # + 'TOX_ERR_GROUP_INVITE_ACCEPT_BAD_INVITE': 1, + + # + # The group instance failed to initialize. + # + 'TOX_ERR_GROUP_INVITE_ACCEPT_INIT_FAILED': 2, + + # + # Password length exceeded 'TOX_GROUP_MAX_PASSWORD_SIZE. + # + 'TOX_ERR_GROUP_INVITE_ACCEPT_TOO_LONG': 3 +} + +TOX_GROUP_JOIN_FAIL = { + + # + # You are using the same nickname as someone who is already in the group. + # + 'TOX_GROUP_JOIN_FAIL_NAME_TAKEN': 0, + + # + # The group peer limit has been reached. + # + 'TOX_GROUP_JOIN_FAIL_PEER_LIMIT': 1, + + # + # You have supplied an invalid password. + # + 'TOX_GROUP_JOIN_FAIL_INVALID_PASSWORD': 2, + + # + # The join attempt failed due to an unspecified error. This often occurs when the group is + # not found in the DHT. + # + 'TOX_GROUP_JOIN_FAIL_UNKNOWN': 3 +} + +TOX_ERR_GROUP_FOUNDER_SET_PASSWORD = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_GROUP_NOT_FOUND': 1, + + # + # The caller does not have the required permissions to set the password. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_PERMISSIONS': 2, + + # + # Password length exceeded 'TOX_GROUP_MAX_PASSWORD_SIZE. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_TOO_LONG': 3, + + # + # The packet failed to send. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_FAIL_SEND': 4 +} + +TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_GROUP_NOT_FOUND': 1, + + # + # 'TOX_GROUP_PRIVACY_STATE is an invalid type. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_INVALID': 2, + + # + # The caller does not have the required permissions to set the privacy state. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_PERMISSIONS': 3, + + # + # The privacy state could not be set. This may occur due to an error related to + # cryptographic signing of the new shared state. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_FAIL_SET': 4, + + # + # The packet failed to send. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_FAIL_SEND': 5 +} + +TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_GROUP_NOT_FOUND': 1, + + # + # The caller does not have the required permissions to set the peer limit. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_PERMISSIONS': 2, + + # + # The peer limit could not be set. This may occur due to an error related to + # cryptographic signing of the new shared state. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_FAIL_SET': 3, + + # + # The packet failed to send. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_FAIL_SEND': 4 +} + +TOX_ERR_GROUP_TOGGLE_IGNORE = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_TOGGLE_IGNORE_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_TOGGLE_IGNORE_GROUP_NOT_FOUND': 1, + + # + # The ID passed did not designate a valid peer. + # + 'TOX_ERR_GROUP_TOGGLE_IGNORE_PEER_NOT_FOUND': 2 +} + +TOX_ERR_GROUP_MOD_SET_ROLE = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_MOD_SET_ROLE_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_MOD_SET_ROLE_GROUP_NOT_FOUND': 1, + + # + # The ID passed did not designate a valid peer. Note: you cannot set your own role. + # + 'TOX_ERR_GROUP_MOD_SET_ROLE_PEER_NOT_FOUND': 2, + + # + # The caller does not have the required permissions for this action. + # + 'TOX_ERR_GROUP_MOD_SET_ROLE_PERMISSIONS': 3, + + # + # The role assignment is invalid. This will occur if you try to set a peer's role to + # the role they already have. + # + 'TOX_ERR_GROUP_MOD_SET_ROLE_ASSIGNMENT': 4, + + # + # The role was not successfully set. This may occur if something goes wrong with role setting': , + # or if the packet fails to send. + # + 'TOX_ERR_GROUP_MOD_SET_ROLE_FAIL_ACTION': 5 +} + +TOX_ERR_GROUP_MOD_REMOVE_PEER = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_MOD_REMOVE_PEER_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_MOD_REMOVE_PEER_GROUP_NOT_FOUND': 1, + + # + # The ID passed did not designate a valid peer. + # + 'TOX_ERR_GROUP_MOD_REMOVE_PEER_PEER_NOT_FOUND': 2, + + # + # The caller does not have the required permissions for this action. + # + 'TOX_ERR_GROUP_MOD_REMOVE_PEER_PERMISSIONS': 3, + + # + # The peer could not be removed from the group. + # + # If a ban was set': , this error indicates that the ban entry could not be created. + # This is usually due to the peer's IP address already occurring in the ban list. It may also + # be due to the entry containing invalid peer information': , or a failure to cryptographically + # authenticate the entry. + # + 'TOX_ERR_GROUP_MOD_REMOVE_PEER_FAIL_ACTION': 4, + + # + # The packet failed to send. + # + 'TOX_ERR_GROUP_MOD_REMOVE_PEER_FAIL_SEND': 5 +} + +TOX_ERR_GROUP_MOD_REMOVE_BAN = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_MOD_REMOVE_BAN_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_MOD_REMOVE_BAN_GROUP_NOT_FOUND': 1, + + # + # The caller does not have the required permissions for this action. + # + 'TOX_ERR_GROUP_MOD_REMOVE_BAN_PERMISSIONS': 2, + + # + # The ban entry could not be removed. This may occur if ban_id does not designate + # a valid ban entry. + # + 'TOX_ERR_GROUP_MOD_REMOVE_BAN_FAIL_ACTION': 3, + + # + # The packet failed to send. + # + 'TOX_ERR_GROUP_MOD_REMOVE_BAN_FAIL_SEND': 4 +} + +TOX_GROUP_MOD_EVENT = { + + # + # A peer has been kicked from the group. + # + 'TOX_GROUP_MOD_EVENT_KICK': 0, + + # + # A peer has been banned from the group. + # + 'TOX_GROUP_MOD_EVENT_BAN': 1, + + # + # A peer as been given the observer role. + # + 'TOX_GROUP_MOD_EVENT_OBSERVER': 2, + + # + # A peer has been given the user role. + # + 'TOX_GROUP_MOD_EVENT_USER': 3, + + # + # A peer has been given the moderator role. + # + 'TOX_GROUP_MOD_EVENT_MODERATOR': 4, +} + +TOX_ERR_GROUP_BAN_QUERY = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_BAN_QUERY_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_BAN_QUERY_GROUP_NOT_FOUND': 1, + + # + # The ban_id does not designate a valid ban list entry. + # + 'TOX_ERR_GROUP_BAN_QUERY_BAD_ID': 2, +} + +TOX_PUBLIC_KEY_SIZE = 32 + +TOX_ADDRESS_SIZE = TOX_PUBLIC_KEY_SIZE + 6 + +TOX_MAX_FRIEND_REQUEST_LENGTH = 1016 + +TOX_MAX_MESSAGE_LENGTH = 1372 + +TOX_GROUP_MAX_TOPIC_LENGTH = 512 + +TOX_GROUP_MAX_PART_LENGTH = 128 + +TOX_GROUP_MAX_GROUP_NAME_LENGTH = 48 + +TOX_GROUP_MAX_PASSWORD_SIZE = 32 + +TOX_GROUP_CHAT_ID_SIZE = 32 + +TOX_GROUP_PEER_PUBLIC_KEY_SIZE = 32 + +TOX_MAX_NAME_LENGTH = 128 + +TOX_MAX_STATUS_MESSAGE_LENGTH = 1007 + +TOX_SECRET_KEY_SIZE = 32 + +TOX_FILE_ID_LENGTH = 32 + +TOX_HASH_LENGTH = 32 + +TOX_MAX_CUSTOM_PACKET_SIZE = 1373 diff --git a/toxygen/toxencryptsave.py b/toxygen/wrapper/toxencryptsave.py similarity index 97% rename from toxygen/toxencryptsave.py rename to toxygen/wrapper/toxencryptsave.py index b579e21..31de085 100644 --- a/toxygen/toxencryptsave.py +++ b/toxygen/wrapper/toxencryptsave.py @@ -1,6 +1,6 @@ -import libtox +from wrapper import libtox from ctypes import c_size_t, create_string_buffer, byref, c_int, ArgumentError, c_char_p, c_bool -from toxencryptsave_enums_and_consts import * +from wrapper.toxencryptsave_enums_and_consts import * class ToxEncryptSave: diff --git a/toxygen/toxencryptsave_enums_and_consts.py b/toxygen/wrapper/toxencryptsave_enums_and_consts.py similarity index 100% rename from toxygen/toxencryptsave_enums_and_consts.py rename to toxygen/wrapper/toxencryptsave_enums_and_consts.py From 593e25efe51fab25a3745c2105bee0e692904dd7 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 14 Feb 2018 20:36:59 +0300 Subject: [PATCH 002/138] more project structure updates --- toxygen/communication/__init__.py | 0 toxygen/{ => communication}/callbacks.py | 67 +------- toxygen/communication/tox_factory.py | 27 ++++ toxygen/contacts/contact.py | 2 +- toxygen/contacts/profile.py | 153 +----------------- toxygen/file_tansfers/__init__.py | 0 toxygen/{ => file_tansfers}/file_transfers.py | 0 .../file_tansfers/file_transfers_handler.py | 0 toxygen/login.py | 19 +++ toxygen/main.py | 21 --- toxygen/threads.py | 65 ++++++++ toxygen/ui/list_items.py | 2 +- toxygen/user_data/toxes.py | 20 +-- 13 files changed, 125 insertions(+), 251 deletions(-) create mode 100644 toxygen/communication/__init__.py rename toxygen/{ => communication}/callbacks.py (90%) create mode 100644 toxygen/communication/tox_factory.py create mode 100644 toxygen/file_tansfers/__init__.py rename toxygen/{ => file_tansfers}/file_transfers.py (100%) create mode 100644 toxygen/file_tansfers/file_transfers_handler.py diff --git a/toxygen/communication/__init__.py b/toxygen/communication/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/callbacks.py b/toxygen/communication/callbacks.py similarity index 90% rename from toxygen/callbacks.py rename to toxygen/communication/callbacks.py index ba2994d..0e7345c 100644 --- a/toxygen/callbacks.py +++ b/toxygen/communication/callbacks.py @@ -12,72 +12,7 @@ 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() +# TODO: use closures # ----------------------------------------------------------------------------------------------------------------- # Callbacks - current user diff --git a/toxygen/communication/tox_factory.py b/toxygen/communication/tox_factory.py new file mode 100644 index 0000000..a39bb15 --- /dev/null +++ b/toxygen/communication/tox_factory.py @@ -0,0 +1,27 @@ + + +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 = Settings.get_default_settings() + tox_options = Tox.options_new() + tox_options.contents.udp_enabled = settings['udp_enabled'] + tox_options.contents.proxy_type = settings['proxy_type'] + tox_options.contents.proxy_host = 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'] + if data: # load existing profile + tox_options.contents.savedata_type = TOX_SAVEDATA_TYPE['TOX_SAVE'] + tox_options.contents.savedata_data = c_char_p(data) + tox_options.contents.savedata_length = len(data) + else: # create new profile + tox_options.contents.savedata_type = TOX_SAVEDATA_TYPE['NONE'] + tox_options.contents.savedata_data = None + tox_options.contents.savedata_length = 0 + return Tox(tox_options) diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index ad491fd..4b221e3 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -2,7 +2,7 @@ from db.history import * from contacts import basecontact import util from messenger.messages import * -import file_transfers as ft +from file_tansfers import file_transfers as ft import re diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index f74d134..9c585c2 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -7,7 +7,7 @@ from ctypes import * from util import log, Singleton, curr_directory from network.tox_dns import tox_dns from db.history import * -from file_transfers import * +from file_tansfers.file_transfers import * import time from av import calls import plugin_support @@ -1299,154 +1299,3 @@ class Profile(basecontact.BaseContact, Singleton): if friend_number == self.get_active_number(): self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) self._messages.scrollToBottom() - - # ----------------------------------------------------------------------------------------------------------------- - # GC support - # ----------------------------------------------------------------------------------------------------------------- - - def is_active_a_friend(self): - return type(self.get_curr_friend()) is Friend - - def get_group_by_number(self, number): - groups = filter(lambda x: type(x) is GroupChat and x.number == number, self._contacts) - return list(groups)[0] - - def add_gc(self, number): - widget = self.create_friend_item() - gc = GroupChat('Group chat #' + str(number), '', widget, self._tox, number) - self._contacts.append(gc) - - def create_group_chat(self): - number = self._tox.add_av_groupchat() - self.add_gc(number) - - def leave_gc(self, num): - gc = self._contacts[num] - self._tox.del_groupchat(gc.number) - del self._contacts[num] - self._screen.friends_list.takeItem(num) - if num == self._active_friend: # active friend was deleted - if not len(self._contacts): # last friend was deleted - self.set_active(-1) - else: - self.set_active(0) - - def group_invite(self, friend_number, gc_type, data): - text = QtWidgets.QApplication.translate('MainWindow', 'User {} invites you to group chat. Accept?') - title = QtWidgets.QApplication.translate('MainWindow', 'Group chat invite') - friend = self.get_friend_by_number(friend_number) - reply = QtWidgets.QMessageBox.question(None, title, text.format(friend.name), QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) - if reply == QtWidgets.QMessageBox.Yes: # accepted - if gc_type == TOX_GROUPCHAT_TYPE['TEXT']: - number = self._tox.join_groupchat(friend_number, data) - else: - number = self._tox.join_av_groupchat(friend_number, data) - self.add_gc(number) - - def new_gc_message(self, group_number, peer_number, message_type, message): - name = self._tox.group_peername(group_number, peer_number) - message_type += 5 - if group_number == self.get_active_number() and not self.is_active_a_friend(): # add message to list - t = time.time() - self.create_gc_message_item(message, t, MESSAGE_OWNER['FRIEND'], name, message_type) - self._messages.scrollToBottom() - self.get_curr_friend().append_message( - GroupChatMessage(message, MESSAGE_OWNER['FRIEND'], t, message_type, name)) - else: - gc = self.get_group_by_number(group_number) - gc.inc_messages() - gc.append_message( - GroupChatMessage(message, MESSAGE_OWNER['FRIEND'], time.time(), message_type, name)) - if not gc.visibility: - self.update_filtration() - - def new_gc_title(self, group_number, title): - gc = self.get_group_by_number(group_number) - gc.new_title(title) - if not self.is_active_a_friend() and self.get_active_number() == group_number: - self.update() - - def update_gc(self, group_number): - count = self._tox.group_number_peers(group_number) - gc = self.get_group_by_number(group_number) - text = QtWidgets.QApplication.translate('MainWindow', '{} users in chat') - gc.status_message = text.format(str(count)).encode('utf-8') - if not self.is_active_a_friend() and self.get_active_number() == group_number: - self.update() - - def send_gc_message(self, text): - group_number = self.get_active_number() - if text.startswith('/me '): - text = text[4:] - self._tox.group_action_send(group_number, text.encode('utf-8')) - else: - self._tox.group_message_send(group_number, text.encode('utf-8')) - self._screen.messageEdit.clear() - - def set_title(self, num): - """ - Set new title for gc - """ - gc = self._contacts[num] - name = gc.name - dialog = QtWidgets.QApplication.translate('MainWindow', - "Enter new title for group {}:") - dialog = dialog.format(name) - title = QtWidgets.QApplication.translate('MainWindow', - 'Set title') - text, ok = QtWidgets.QInputDialog.getText(None, - title, - dialog, - QtWidgets.QLineEdit.Normal, - name) - if ok: - text = text.encode('utf-8') - self._tox.group_set_title(gc.number, text) - self.new_gc_title(gc.number, text) - - def get_group_chats(self): - chats = filter(lambda x: type(x) is GroupChat, self._contacts) - chats = map(lambda c: (c.name, c.number), chats) - return list(chats) - - def invite_friend(self, friend_num, group_number): - friend = self._contacts[friend_num] - self._tox.invite_friend(friend.number, group_number) - - def get_gc_peer_name(self, text): - gc = self.get_curr_friend() - if type(gc) is not GroupChat: - return '\t' - names = gc.get_names() - name = re.split("\s+", text)[-1] - suggested_names = list(filter(lambda x: x.startswith(name), names)) - if not len(suggested_names): - return '\t' - return suggested_names[0][len(name):] + ': ' - - -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 = Settings.get_default_settings() - tox_options = Tox.options_new() - tox_options.contents.udp_enabled = settings['udp_enabled'] - tox_options.contents.proxy_type = settings['proxy_type'] - tox_options.contents.proxy_host = 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'] - if data: # load existing profile - tox_options.contents.savedata_type = TOX_SAVEDATA_TYPE['TOX_SAVE'] - tox_options.contents.savedata_data = c_char_p(data) - tox_options.contents.savedata_length = len(data) - else: # create new profile - tox_options.contents.savedata_type = TOX_SAVEDATA_TYPE['NONE'] - tox_options.contents.savedata_data = None - tox_options.contents.savedata_length = 0 - return Tox(tox_options) diff --git a/toxygen/file_tansfers/__init__.py b/toxygen/file_tansfers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/file_transfers.py b/toxygen/file_tansfers/file_transfers.py similarity index 100% rename from toxygen/file_transfers.py rename to toxygen/file_tansfers/file_transfers.py diff --git a/toxygen/file_tansfers/file_transfers_handler.py b/toxygen/file_tansfers/file_transfers_handler.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/login.py b/toxygen/login.py index e69de29..d68629a 100644 --- a/toxygen/login.py +++ b/toxygen/login.py @@ -0,0 +1,19 @@ +class Login: + + def __init__(self, arr): + self.arr = arr + + def login_screen_close(self, t, number=-1, default=False, name=None): + """ Function which processes data from login screen + :param t: 0 - window was closed, 1 - new profile was created, 2 - profile loaded + :param number: num of chosen profile in list (-1 by default) + :param default: was or not chosen profile marked as default + :param name: name of new profile + """ + self.t = t + self.num = number + self.default = default + self.name = name + + def get_data(self): + return self.arr[self.num] \ No newline at end of file diff --git a/toxygen/main.py b/toxygen/main.py index 4e91c50..c8f18f6 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -1,31 +1,10 @@ import sys from user_data.settings import * -from callbacks import init_callbacks, stop, start from util import curr_directory, program_version, remove -import updater import argparse - class Login: - - def __init__(self, arr): - self.arr = arr - - def login_screen_close(self, t, number=-1, default=False, name=None): - """ Function which processes data from login screen - :param t: 0 - window was closed, 1 - new profile was created, 2 - profile loaded - :param number: num of chosen profile in list (-1 by default) - :param default: was or not chosen profile marked as default - :param name: name of new profile - """ - self.t = t - self.num = number - self.default = default - self.name = name - - def get_data(self): - return self.arr[self.num] def clean(): diff --git a/toxygen/threads.py b/toxygen/threads.py index dae8800..a79297a 100644 --- a/toxygen/threads.py +++ b/toxygen/threads.py @@ -60,3 +60,68 @@ class ToxAVIterateThread(QtCore.QThread): while not self.stop: self.toxav.iterate() self.msleep(self.toxav.iteration_interval()) + + +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() + + + + +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)) diff --git a/toxygen/ui/list_items.py b/toxygen/ui/list_items.py index 0e4991e..f6ab154 100644 --- a/toxygen/ui/list_items.py +++ b/toxygen/ui/list_items.py @@ -1,7 +1,7 @@ from wrapper.toxcore_enums_and_consts import * from PyQt5 import QtCore, QtGui, QtWidgets from contacts import profile -from file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR +from file_tansfers.file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR from util import curr_directory, convert_time, curr_time from ui.widgets import DataLabel, create_menu import html as h diff --git a/toxygen/user_data/toxes.py b/toxygen/user_data/toxes.py index 5a22f61..982f287 100644 --- a/toxygen/user_data/toxes.py +++ b/toxygen/user_data/toxes.py @@ -1,24 +1,24 @@ class ToxES: - def __init__(self, toxencryptsave): - self._toxencryptsave = toxencryptsave - self._passphrase = None + def __init__(self, tox_encrypt_save): + self._tox_encrypt_save = tox_encrypt_save + self._password = None - def set_password(self, passphrase): - self._passphrase = passphrase + def set_password(self, password): + self._password = password def has_password(self): - return bool(self._passphrase) + return bool(self._password) def is_password(self, password): - return self._passphrase == password + return self._password == password def is_data_encrypted(self, data): - return len(data) > 0 and self._toxencryptsave.is_data_encrypted(data) + return len(data) > 0 and self._tox_encrypt_save.is_data_encrypted(data) def pass_encrypt(self, data): - return self._toxencryptsave.pass_encrypt(data, self._passphrase) + return self._tox_encrypt_save.pass_encrypt(data, self._password) def pass_decrypt(self, data): - return self._toxencryptsave.pass_decrypt(data, self._passphrase) + return self._tox_encrypt_save.pass_decrypt(data, self._password) From 20bb694c7ec63326567876a2db7b21186467edf5 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 10 Mar 2018 18:42:53 +0300 Subject: [PATCH 003/138] refactoring - correct namespaces, logic from profile.py moved to different files, calbacks partially fixed --- tests/tests.py | 2 +- toxygen/app.py | 10 +- toxygen/av/calls_manager.py | 104 +++ toxygen/bootstrap/bootstrap.py | 2 +- toxygen/communication/callbacks.py | 148 ++-- toxygen/communication/tox_factory.py | 18 +- toxygen/contacts/contact.py | 4 +- toxygen/contacts/contacts_manager.py | 402 +++++++++ toxygen/contacts/profile.py | 808 +----------------- toxygen/db/{history.py => database.py} | 22 +- .../file_tansfers/file_transfers_handler.py | 0 .../__init__.py | 0 .../file_transfers.py | 0 .../file_transfers/file_transfers_handler.py | 311 +++++++ toxygen/main.py | 10 +- toxygen/notifications.py | 2 +- toxygen/smileys_and_stickers.py | 2 +- toxygen/threads.py | 21 +- toxygen/tray.py | 154 ++-- toxygen/ui/list_items.py | 2 +- toxygen/user_data/profile_manager.py | 2 +- toxygen/util/ui.py | 8 + toxygen/util/util.py | 5 +- 23 files changed, 1071 insertions(+), 966 deletions(-) create mode 100644 toxygen/av/calls_manager.py create mode 100644 toxygen/contacts/contacts_manager.py rename toxygen/db/{history.py => database.py} (92%) delete mode 100644 toxygen/file_tansfers/file_transfers_handler.py rename toxygen/{file_tansfers => file_transfers}/__init__.py (100%) rename toxygen/{file_tansfers => file_transfers}/file_transfers.py (100%) create mode 100644 toxygen/file_transfers/file_transfers_handler.py create mode 100644 toxygen/util/ui.py diff --git a/tests/tests.py b/tests/tests.py index cad1d1c..b21854c 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,6 +1,6 @@ from contacts.profile import * from network.tox_dns import tox_dns -from db.history import History +from db.database import History from toxygen.smileys import SmileyLoader from messenger.messages import * import user_data.toxes as encr diff --git a/toxygen/app.py b/toxygen/app.py index 1dec36f..7f789eb 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -1,3 +1,7 @@ +import threads + + + class App: def __init__(self, path_or_uri=None): @@ -247,14 +251,14 @@ class App: # create new tox instance self.tox = profile.tox_factory(data, Settings.get_instance()) # init thread - self.init = self.InitThread(self.tox, self.ms, self.tray) + self.init = threads.InitThread(self.tox, self.ms, self.tray) self.init.start() # starting threads for tox iterate and toxav iterate - self.mainloop = self.ToxIterateThread(self.tox) + self.mainloop = threads.ToxIterateThread(self.tox) self.mainloop.start() - self.avloop = self.ToxAVIterateThread(self.tox.AV) + self.avloop = threads.ToxAVIterateThread(self.tox.AV) self.avloop.start() plugin_helper = PluginLoader.get_instance() diff --git a/toxygen/av/calls_manager.py b/toxygen/av/calls_manager.py new file mode 100644 index 0000000..6cfe543 --- /dev/null +++ b/toxygen/av/calls_manager.py @@ -0,0 +1,104 @@ +import threading +import cv2 +import av.calls +from PyQt5 import QtWidgets +from messenger.messages import * +import time + + +class CallsManager: + + def __init__(self, tox): + self._call = av.calls.AV(tox.AV) # object with data about calls + self._call_widgets = {} # dict of incoming call widgets + self._incoming_calls = set() + + + # ----------------------------------------------------------------------------------------------------------------- + # AV support + # ----------------------------------------------------------------------------------------------------------------- + + def get_call(self): + return self._call + + call = property(get_call) + + def call_click(self, audio=True, video=False): + """User clicked audio button in main window""" + num = self.get_active_number() + if not self.is_active_a_friend(): + return + if num not in self._call and self.is_active_online(): # start call + if not Settings.get_instance().audio['enabled']: + return + self._call(num, audio, video) + self._screen.active_call() + if video: + text = QtWidgets.QApplication.translate("incoming_call", "Outgoing video call") + else: + text = QtWidgets.QApplication.translate("incoming_call", "Outgoing audio call") + self.get_curr_friend().append_message(InfoMessage(text, time.time())) + self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) + self._messages.scrollToBottom() + elif num in self._call: # finish or cancel call if you call with active friend + self.stop_call(num, False) + + def incoming_call(self, audio, video, friend_number): + """ + Incoming call from friend. + """ + if not Settings.get_instance().audio['enabled']: + return + friend = self.get_friend_by_number(friend_number) + if video: + text = QtWidgets.QApplication.translate("incoming_call", "Incoming video call") + else: + text = QtWidgets.QApplication.translate("incoming_call", "Incoming audio call") + friend.append_message(InfoMessage(text, time.time())) + self._incoming_calls.add(friend_number) + if friend_number == self.get_active_number(): + self._screen.incoming_call() + self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) + self._messages.scrollToBottom() + else: + friend.actions = True + self._call_widgets[friend_number] = avwidgets.IncomingCallWidget(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) + text = QtWidgets.QApplication.translate("incoming_call", "Call declined") + else: + text = QtWidgets.QApplication.translate("incoming_call", "Call finished") + 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 hasattr(self, '_call_widget'): + self._call_widget[friend_number].close() + del self._call_widget[friend_number] + + def destroy_window(): + if is_video: + cv2.destroyWindow(str(friend_number)) + + threading.Timer(2.0, destroy_window).start() + friend = self.get_friend_by_number(friend_number) + friend.append_message(InfoMessage(text, time.time())) + if friend_number == self.get_active_number(): + self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) + self._messages.scrollToBottom() diff --git a/toxygen/bootstrap/bootstrap.py b/toxygen/bootstrap/bootstrap.py index 6d97f25..87c6d9c 100644 --- a/toxygen/bootstrap/bootstrap.py +++ b/toxygen/bootstrap/bootstrap.py @@ -1,6 +1,6 @@ import random import urllib.request -from util import log, curr_directory +from util.util import log, curr_directory from user_data import settings from PyQt5 import QtNetwork, QtCore import json diff --git a/toxygen/communication/callbacks.py b/toxygen/communication/callbacks.py index 0e7345c..e27bd74 100644 --- a/toxygen/communication/callbacks.py +++ b/toxygen/communication/callbacks.py @@ -11,6 +11,7 @@ import threading import util import cv2 import numpy as np +from threads import invoke_in_main_thread, execute # TODO: use closures @@ -19,15 +20,14 @@ import numpy as np # ----------------------------------------------------------------------------------------------------------------- -def self_connection_status(tox_link): +def self_connection_status(tox, profile): """ Current user changed connection status (offline, UDP, TCP) """ - def wrapped(tox, connection, user_data): + def wrapped(tox_link, connection, user_data): print('Connection status: ', str(connection)) - profile = Profile.get_instance() if profile.status is None: - status = tox_link.self_get_status() + status = tox.self_get_status() invoke_in_main_thread(profile.set_status, status) elif connection == TOX_CONNECTION['NONE']: invoke_in_main_thread(profile.set_status, None) @@ -39,67 +39,73 @@ def self_connection_status(tox_link): # ----------------------------------------------------------------------------------------------------------------- -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']: +def friend_status(profile, settings): + def wrapped(tox, friend_num, new_status, user_data): + """ + Check friend's status (none, busy, away) + """ + print("Friend's #{} status changed!".format(friend_num)) + friend = profile.get_friend_by_number(friend_num) + if friend.status is None and 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(profile.send_avatar, friend_num) - invoke_in_main_thread(PluginLoader.get_instance().friend_online, friend_num) + 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) + + return wrapped -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_connection_status(profile, settings, plugin_loader): + def wrapped(tox, friend_num, new_status, user_data): + """ + Check friend's connection status (offline, udp, tcp) + """ + print("Friend #{} connection status: {}".format(friend_num, new_status)) + 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['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(plugin_loader.friend_online, friend_num) + + return wrapped -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_name(profile): + def wrapped(tox, friend_num, name, size, user_data): + """ + Friend changed his name + """ + print('New name friend #' + str(friend_num)) + invoke_in_main_thread(profile.new_name, friend_num, name) + + return wrapped -def friend_message(window, tray): +def friend_status_message(profile): + def wrapped(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 + """ + 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) + + return wrapped + + +def friend_message(profile, settings, 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(): @@ -109,6 +115,7 @@ def friend_message(window, tray): 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 @@ -174,31 +181,40 @@ def tox_file_recv(window, tray): return wrapped -def file_recv_chunk(tox, friend_number, file_number, position, chunk, length, user_data): +def file_recv_chunk(file_transfer_handler): """ Incoming chunk """ - _thread.execute(Profile.get_instance().incoming_chunk, friend_number, file_number, position, - chunk[:length] if length else None) + def wrapped(tox, friend_number, file_number, position, chunk, length, user_data): + execute(file_transfer_handler.incoming_chunk, friend_number, file_number, position, + chunk[:length] if length else None) + + return wrapped -def file_chunk_request(tox, friend_number, file_number, position, size, user_data): +def file_chunk_request(file_transfer_handler): """ Outgoing chunk """ - Profile.get_instance().outgoing_chunk(friend_number, file_number, position, size) + 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(tox, friend_number, file_number, file_control, user_data): +def file_recv_control(file_transfer_handler): """ 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) + 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 diff --git a/toxygen/communication/tox_factory.py b/toxygen/communication/tox_factory.py index a39bb15..588bd30 100644 --- a/toxygen/communication/tox_factory.py +++ b/toxygen/communication/tox_factory.py @@ -1,3 +1,7 @@ +import user_data.settings +import wrapper.tox +import wrapper.toxcore_enums_and_consts as enums +import ctypes def tox_factory(data=None, settings=None): @@ -7,8 +11,9 @@ def tox_factory(data=None, settings=None): :return: new tox instance """ if settings is None: - settings = Settings.get_default_settings() - tox_options = Tox.options_new() + 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') @@ -17,11 +22,12 @@ def tox_factory(data=None, settings=None): tox_options.contents.end_port = settings['end_port'] tox_options.contents.tcp_port = settings['tcp_port'] if data: # load existing profile - tox_options.contents.savedata_type = TOX_SAVEDATA_TYPE['TOX_SAVE'] - tox_options.contents.savedata_data = c_char_p(data) + 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 = TOX_SAVEDATA_TYPE['NONE'] + tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['NONE'] tox_options.contents.savedata_data = None tox_options.contents.savedata_length = 0 - return Tox(tox_options) + + return wrapper.tox.Tox(tox_options) diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index 4b221e3..96ff804 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -1,8 +1,8 @@ -from db.history import * +from db.database import * from contacts import basecontact import util from messenger.messages import * -from file_tansfers import file_transfers as ft +from file_transfers import file_transfers as ft import re diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py new file mode 100644 index 0000000..cbac1e1 --- /dev/null +++ b/toxygen/contacts/contacts_manager.py @@ -0,0 +1,402 @@ + + +class ContactsManager: + + def __init__(self, tox, settings, screen): + self._tox = tox + self._settings = settings + self._contacts, self._active_friend = [], -1 + self._sorting = settings['sorting'] + data = tox.self_get_friend_list() + self._filter_string = '' + self._friend_item_height = 40 if settings['compact_mode'] else 70 + screen.online_contacts.setCurrentIndex(int(self._sorting)) + aliases = settings['friends_aliases'] + for i in data: # creates list of friends + tox_id = tox.friend_get_public_key(i) + try: + alias = list(filter(lambda x: x[0] == tox_id, aliases))[0][1] + except: + alias = '' + item = self.create_friend_item() + name = alias or tox.friend_get_name(i) or tox_id + status_message = tox.friend_get_status_message(i) + if not self._history.friend_exists_in_db(tox_id): + self._history.add_friend_to_db(tox_id) + message_getter = self._history.messages_getter(tox_id) + friend = Friend(message_getter, i, name, status_message, item, tox_id) + friend.set_alias(alias) + self._contacts.append(friend) + if len(self._contacts): + self.set_active(0) + self.filtration_and_sorting(self._sorting) + + + def get_friend(self, num): + if num < 0 or num >= len(self._contacts): + return None + return self._contacts[num] + + def get_curr_friend(self): + return self._contacts[self._active_friend] if self._active_friend + 1 else None + + # ----------------------------------------------------------------------------------------------------------------- + # Work with active friend + # ----------------------------------------------------------------------------------------------------------------- + + def get_active(self): + return self._active_friend + + def set_active(self, value=None): + """ + Change current active friend or update info + :param value: number of new active friend in friend's list or None to update active user's data + """ + if value is None and self._active_friend == -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_friend = -1 + self._screen.account_avatar.setHidden(True) + self._messages.clear() + self._screen.messageEdit.clear() + return + try: + self.send_typing(False) + self._screen.typing.setVisible(False) + if value is not None: + if self._active_friend + 1 and self._active_friend != value: + try: + self.get_curr_friend().curr_text = self._screen.messageEdit.toPlainText() + except: + pass + friend = self._contacts[value] + friend.remove_invalid_unsent_files() + if self._active_friend != value: + self._screen.messageEdit.setPlainText(friend.curr_text) + self._active_friend = value + friend.reset_messages() + if not Settings.get_instance()['save_history']: + friend.delete_old_messages() + self._messages.clear() + friend.load_corr() + messages = friend.get_corr()[-PAGE_SIZE:] + self._load_history = False + for message in messages: + if message.get_type() <= 1: + data = message.get_data() + self.create_message_item(data[0], + data[2], + data[1], + data[3]) + elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: + if message.get_status() is None: + self.create_unsent_file_item(message) + continue + item = self.create_file_transfer_item(message) + if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer + try: + ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())] + ft.set_state_changed_handler(item.update_transfer_state) + ft.signal() + except: + print('Incoming not started transfer - no info found') + elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline + self.create_inline_item(message.get_data()) + elif message.get_type() < 5: # info message + data = message.get_data() + self.create_message_item(data[0], + data[2], + '', + data[3]) + else: + data = message.get_data() + self.create_gc_message_item(data[0], data[2], data[1], data[4], data[3]) + self._messages.scrollToBottom() + self._load_history = True + if value in self._call: + self._screen.active_call() + elif value in self._incoming_calls: + self._screen.incoming_call() + else: + self._screen.call_finished() + else: + friend = self.get_curr_friend() + + self._screen.account_name.setText(friend.name) + self._screen.account_status.setText(friend.status_message) + self._screen.account_status.setToolTip(friend.get_full_status()) + if friend.tox_id is None: + avatar_path = curr_directory() + '/images/group.png' + else: + avatar_path = (ProfileManager.get_path() + 'avatars/{}.png').format(friend.tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) + if not os.path.isfile(avatar_path): # load default image + avatar_path = curr_directory() + '/images/avatar.png' + os.chdir(os.path.dirname(avatar_path)) + pixmap = QtGui.QPixmap(avatar_path) + self._screen.account_avatar.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation)) + except Exception as ex: # no friend found. ignore + log('Friend value: ' + str(value)) + log('Error in set active: ' + str(ex)) + raise + + def set_active_by_number_and_type(self, number, is_friend): + for i in range(len(self._contacts)): + c = self._contacts[i] + if c.number == number and (type(c) is Friend == is_friend): + self._active_friend = i + break + + active_friend = property(get_active, set_active) + + # ----------------------------------------------------------------------------------------------------------------- + # Filtration + # ----------------------------------------------------------------------------------------------------------------- + + def filtration_and_sorting(self, sorting=0, filter_str=''): + """ + Filtration of friends list + :param sorting: 0 - no sort, 1 - online only, 2 - online first, 4 - by name + :param filter_str: show contacts which name contains this substring + """ + filter_str = filter_str.lower() + settings = Settings.get_instance() + number = self.get_active_number() + is_friend = self.is_active_a_friend() + if sorting > 1: + if sorting & 2: + self._contacts = sorted(self._contacts, key=lambda x: int(x.status is not None), reverse=True) + if sorting & 4: + if not sorting & 2: + self._contacts = sorted(self._contacts, key=lambda x: x.name.lower()) + else: # save results of prev sorting + online_friends = filter(lambda x: x.status is not None, self._contacts) + count = len(list(online_friends)) + part1 = self._contacts[:count] + part2 = self._contacts[count:] + part1 = sorted(part1, key=lambda x: x.name.lower()) + part2 = sorted(part2, key=lambda x: x.name.lower()) + self._contacts = part1 + part2 + else: # sort by number + online_friends = filter(lambda x: x.status is not None, self._contacts) + count = len(list(online_friends)) + part1 = self._contacts[:count] + part2 = self._contacts[count:] + part1 = sorted(part1, key=lambda x: x.number) + part2 = sorted(part2, key=lambda x: x.number) + self._contacts = part1 + part2 + self._screen.friends_list.clear() + for contact in self._contacts: + contact.set_widget(self.create_friend_item()) + for index, friend in enumerate(self._contacts): + friend.visibility = (friend.status is not None or not (sorting & 1)) and (filter_str in friend.name.lower()) + friend.visibility = friend.visibility or friend.messages or friend.actions + if friend.visibility: + self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, self._friend_item_height)) + else: + self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, 0)) + self._sorting, self._filter_string = sorting, filter_str + settings['sorting'] = self._sorting + settings.save() + self.set_active_by_number_and_type(number, is_friend) + + def update_filtration(self): + """ + Update list of contacts when 1 of friends change connection status + """ + self.filtration_and_sorting(self._sorting, self._filter_string) + + + def create_friend_item(self): + """ + Method-factory + :return: new widget for friend instance + """ + return self._factory.friend_item() + + + + # ----------------------------------------------------------------------------------------------------------------- + # 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 + dialog = QtWidgets.QApplication.translate('MainWindow', + "Enter new alias for friend {} or leave empty to use friend's name:") + dialog = dialog.format(name) + title = QtWidgets.QApplication.translate('MainWindow', + 'Set alias') + text, ok = QtWidgets.QInputDialog.getText(None, + title, + dialog, + QtWidgets.QLineEdit.Normal, + name) + if ok: + settings = Settings.get_instance() + aliases = settings['friends_aliases'] + if text: + friend.name = bytes(text, 'utf-8') + 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 = bytes(self._tox.friend_get_name(friend.number), 'utf-8') + friend.set_alias('') + try: + index = list(map(lambda x: x[0], aliases)).index(friend.tox_id) + del aliases[index] + except: + pass + settings.save() + if num == self.get_active_number() and self.is_active_a_friend(): + self.update() + + 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] + settings = Settings.get_instance() + try: + index = list(map(lambda x: x[0], settings['friends_aliases'])).index(friend.tox_id) + del settings['friends_aliases'][index] + except: + pass + if friend.tox_id in settings['notes']: + del settings['notes'][friend.tox_id] + settings.save() + self.clear_history(num) + if self._history.friend_exists_in_db(friend.tox_id): + self._history.delete_friend_from_db(friend.tox_id) + self._tox.friend_delete(friend.number) + del self._contacts[num] + self._screen.friends_list.takeItem(num) + if num == self._active_friend: # active friend was deleted + if not len(self._contacts): # last friend was deleted + self.set_active(-1) + else: + self.set_active(0) + data = self._tox.get_savedata() + ProfileManager.get_instance().save_profile(data) + + def add_friend(self, tox_id): + """ + Adds friend to list + """ + num = self._tox.friend_add_norequest(tox_id) # num - friend number + item = self.create_friend_item() + try: + if not self._history.friend_exists_in_db(tox_id): + self._history.add_friend_to_db(tox_id) + message_getter = self._history.messages_getter(tox_id) + except Exception as ex: # something is wrong + log('Accept friend request failed! ' + str(ex)) + message_getter = None + friend = Friend(message_getter, num, tox_id, '', item, tox_id) + self._contacts.append(friend) + + 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_id[:TOX_PUBLIC_KEY_SIZE * 2]: + return + settings = Settings.get_instance() + if tox_id not in settings['blocked']: + settings['blocked'].append(tox_id) + settings.save() + try: + num = self._tox.friend_by_public_key(tox_id) + self.delete_friend(num) + data = self._tox.get_savedata() + ProfileManager.get_instance().save_profile(data) + 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 + """ + s = Settings.get_instance() + s['blocked'].remove(tox_id) + s.save() + if add_to_friend_list: + self.add_friend(tox_id) + data = self._tox.get_savedata() + ProfileManager.get_instance().save_profile(data) + + # ----------------------------------------------------------------------------------------------------------------- + # 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 = tox_dns(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) + msgBox = QtWidgets.QMessageBox() + msgBox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "Friend added")) + text = (QtWidgets.QApplication.translate("MainWindow", 'Friend added without sending friend request')) + msgBox.setText(text) + msgBox.exec_() + else: + result = self._tox.friend_add(tox_id, message.encode('utf-8')) + tox_id = tox_id[:TOX_PUBLIC_KEY_SIZE * 2] + item = self.create_friend_item() + if not self._history.friend_exists_in_db(tox_id): + self._history.add_friend_to_db(tox_id) + message_getter = self._history.messages_getter(tox_id) + friend = Friend(message_getter, result, tox_id, '', item, tox_id) + self._contacts.append(friend) + data = self._tox.get_savedata() + ProfileManager.get_instance().save_profile(data) + return True + except Exception as ex: # wrong data + 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 + """ + try: + text = QtWidgets.QApplication.translate('MainWindow', 'User {} wants to add you to contact list. Message:\n{}') + info = text.format(tox_id, message) + fr_req = QtWidgets.QApplication.translate('MainWindow', 'Friend request') + reply = QtWidgets.QMessageBox.question(None, fr_req, info, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.Yes: # accepted + self.add_friend(tox_id) + data = self._tox.get_savedata() + ProfileManager.get_instance().save_profile(data) + except Exception as ex: # something is wrong + log('Accept friend request failed! ' + str(ex)) diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 9c585c2..483d96b 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -6,8 +6,8 @@ from wrapper.toxcore_enums_and_consts import * from ctypes import * from util import log, Singleton, curr_directory from network.tox_dns import tox_dns -from db.history import * -from file_tansfers.file_transfers import * +from db.database import * +from file_transfers.file_transfers import * import time from av import calls import plugin_support @@ -38,42 +38,15 @@ class Profile(basecontact.BaseContact, Singleton): self._messages = screen.messages self._tox = tox self._file_transfers = {} # dict of file transfers. key - tuple (friend_number, file_number) - self._call = calls.AV(tox.AV) # object with data about calls - self._call_widgets = {} # dict of incoming call widgets - self._incoming_calls = set() self._load_history = True self._waiting_for_reconnection = False self._factory = items_factory.ItemsFactory(self._screen.friends_list, self._messages) settings = Settings.get_instance() - self._sorting = settings['sorting'] self._show_avatars = settings['show_avatars'] - self._filter_string = '' - self._friend_item_height = 40 if settings['compact_mode'] else 70 self._paused_file_transfers = dict(settings['paused_file_transfers']) # key - file id, value: [path, friend number, is incoming, start position] - screen.online_contacts.setCurrentIndex(int(self._sorting)) - aliases = settings['friends_aliases'] - data = tox.self_get_friend_list() self._history = History(tox.self_get_public_key()) # connection to db - self._contacts, self._active_friend = [], -1 - for i in data: # creates list of friends - tox_id = tox.friend_get_public_key(i) - try: - alias = list(filter(lambda x: x[0] == tox_id, aliases))[0][1] - except: - alias = '' - item = self.create_friend_item() - name = alias or tox.friend_get_name(i) or tox_id - status_message = tox.friend_get_status_message(i) - if not self._history.friend_exists_in_db(tox_id): - self._history.add_friend_to_db(tox_id) - message_getter = self._history.messages_getter(tox_id) - friend = Friend(message_getter, i, name, status_message, item, tox_id) - friend.set_alias(alias) - self._contacts.append(friend) - if len(self._contacts): - self.set_active(0) - self.filtration_and_sorting(self._sorting) + # ----------------------------------------------------------------------------------------------------------------- # Edit current user's data @@ -119,190 +92,12 @@ class Profile(basecontact.BaseContact, Singleton): self._tox_id = self._tox.self_get_address() return self._tox_id - # ----------------------------------------------------------------------------------------------------------------- - # Filtration - # ----------------------------------------------------------------------------------------------------------------- - - def filtration_and_sorting(self, sorting=0, filter_str=''): - """ - Filtration of friends list - :param sorting: 0 - no sort, 1 - online only, 2 - online first, 4 - by name - :param filter_str: show contacts which name contains this substring - """ - filter_str = filter_str.lower() - settings = Settings.get_instance() - number = self.get_active_number() - is_friend = self.is_active_a_friend() - if sorting > 1: - if sorting & 2: - self._contacts = sorted(self._contacts, key=lambda x: int(x.status is not None), reverse=True) - if sorting & 4: - if not sorting & 2: - self._contacts = sorted(self._contacts, key=lambda x: x.name.lower()) - else: # save results of prev sorting - online_friends = filter(lambda x: x.status is not None, self._contacts) - count = len(list(online_friends)) - part1 = self._contacts[:count] - part2 = self._contacts[count:] - part1 = sorted(part1, key=lambda x: x.name.lower()) - part2 = sorted(part2, key=lambda x: x.name.lower()) - self._contacts = part1 + part2 - else: # sort by number - online_friends = filter(lambda x: x.status is not None, self._contacts) - count = len(list(online_friends)) - part1 = self._contacts[:count] - part2 = self._contacts[count:] - part1 = sorted(part1, key=lambda x: x.number) - part2 = sorted(part2, key=lambda x: x.number) - self._contacts = part1 + part2 - self._screen.friends_list.clear() - for contact in self._contacts: - contact.set_widget(self.create_friend_item()) - for index, friend in enumerate(self._contacts): - friend.visibility = (friend.status is not None or not (sorting & 1)) and (filter_str in friend.name.lower()) - friend.visibility = friend.visibility or friend.messages or friend.actions - if friend.visibility: - self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, self._friend_item_height)) - else: - self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, 0)) - self._sorting, self._filter_string = sorting, filter_str - settings['sorting'] = self._sorting - settings.save() - self.set_active_by_number_and_type(number, is_friend) - - def update_filtration(self): - """ - Update list of contacts when 1 of friends change connection status - """ - self.filtration_and_sorting(self._sorting, self._filter_string) - # ----------------------------------------------------------------------------------------------------------------- # Friend getters # ----------------------------------------------------------------------------------------------------------------- def get_friend_by_number(self, num): return list(filter(lambda x: x.number == num and type(x) is Friend, self._contacts))[0] - - def get_friend(self, num): - if num < 0 or num >= len(self._contacts): - return None - return self._contacts[num] - - def get_curr_friend(self): - return self._contacts[self._active_friend] if self._active_friend + 1 else None - - # ----------------------------------------------------------------------------------------------------------------- - # Work with active friend - # ----------------------------------------------------------------------------------------------------------------- - - def get_active(self): - return self._active_friend - - def set_active(self, value=None): - """ - Change current active friend or update info - :param value: number of new active friend in friend's list or None to update active user's data - """ - if value is None and self._active_friend == -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_friend = -1 - self._screen.account_avatar.setHidden(True) - self._messages.clear() - self._screen.messageEdit.clear() - return - try: - self.send_typing(False) - self._screen.typing.setVisible(False) - if value is not None: - if self._active_friend + 1 and self._active_friend != value: - try: - self.get_curr_friend().curr_text = self._screen.messageEdit.toPlainText() - except: - pass - friend = self._contacts[value] - friend.remove_invalid_unsent_files() - if self._active_friend != value: - self._screen.messageEdit.setPlainText(friend.curr_text) - self._active_friend = value - friend.reset_messages() - if not Settings.get_instance()['save_history']: - friend.delete_old_messages() - self._messages.clear() - friend.load_corr() - messages = friend.get_corr()[-PAGE_SIZE:] - self._load_history = False - for message in messages: - if message.get_type() <= 1: - data = message.get_data() - self.create_message_item(data[0], - data[2], - data[1], - data[3]) - elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: - if message.get_status() is None: - self.create_unsent_file_item(message) - continue - item = self.create_file_transfer_item(message) - if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer - try: - ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())] - ft.set_state_changed_handler(item.update_transfer_state) - ft.signal() - except: - print('Incoming not started transfer - no info found') - elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline - self.create_inline_item(message.get_data()) - elif message.get_type() < 5: # info message - data = message.get_data() - self.create_message_item(data[0], - data[2], - '', - data[3]) - else: - data = message.get_data() - self.create_gc_message_item(data[0], data[2], data[1], data[4], data[3]) - self._messages.scrollToBottom() - self._load_history = True - if value in self._call: - self._screen.active_call() - elif value in self._incoming_calls: - self._screen.incoming_call() - else: - self._screen.call_finished() - else: - friend = self.get_curr_friend() - - self._screen.account_name.setText(friend.name) - self._screen.account_status.setText(friend.status_message) - self._screen.account_status.setToolTip(friend.get_full_status()) - if friend.tox_id is None: - avatar_path = curr_directory() + '/images/group.png' - else: - avatar_path = (ProfileManager.get_path() + 'avatars/{}.png').format(friend.tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) - if not os.path.isfile(avatar_path): # load default image - avatar_path = curr_directory() + '/images/avatar.png' - os.chdir(os.path.dirname(avatar_path)) - pixmap = QtGui.QPixmap(avatar_path) - self._screen.account_avatar.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio, - QtCore.Qt.SmoothTransformation)) - except Exception as ex: # no friend found. ignore - log('Friend value: ' + str(value)) - log('Error in set active: ' + str(ex)) - raise - - def set_active_by_number_and_type(self, number, is_friend): - for i in range(len(self._contacts)): - c = self._contacts[i] - if c.number == number and (type(c) is Friend == is_friend): - self._active_friend = i - break - - active_friend = property(get_active, set_active) - def get_last_message(self): if self._active_friend + 1: return self.get_curr_friend().get_last_message_text() @@ -428,6 +223,25 @@ class Profile(basecontact.BaseContact, Singleton): except Exception as ex: log('Sending pending messages failed with ' + str(ex)) + def split_message(self, 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 ' ' in last_part: + index = last_part.index(' ') + elif ',' in last_part: + index = last_part.index(',') + elif '.' in last_part: + index = last_part.index('.') + else: + index = TOX_MAX_MESSAGE_LENGTH - size - 1 + index += size + 1 + messages.append(message[:index]) + message = message[index:] + + return messages + def split_and_send(self, number, message_type, message): """ Message splitting. Message length cannot be > TOX_MAX_MESSAGE_LENGTH @@ -629,12 +443,6 @@ class Profile(basecontact.BaseContact, Singleton): # Friend, message and file transfer items creation # ----------------------------------------------------------------------------------------------------------------- - def create_friend_item(self): - """ - Method-factory - :return: new widget for friend instance - """ - return self._factory.friend_item() def create_message_item(self, text, time, owner, message_type, append=True): if message_type == MESSAGE_TYPE['INFO_MESSAGE']: @@ -678,188 +486,6 @@ class Profile(basecontact.BaseContact, Singleton): def create_inline_item(self, data, append=True): return self._factory.inline_item(data, append) - # ----------------------------------------------------------------------------------------------------------------- - # 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 - dialog = QtWidgets.QApplication.translate('MainWindow', - "Enter new alias for friend {} or leave empty to use friend's name:") - dialog = dialog.format(name) - title = QtWidgets.QApplication.translate('MainWindow', - 'Set alias') - text, ok = QtWidgets.QInputDialog.getText(None, - title, - dialog, - QtWidgets.QLineEdit.Normal, - name) - if ok: - settings = Settings.get_instance() - aliases = settings['friends_aliases'] - if text: - friend.name = bytes(text, 'utf-8') - 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 = bytes(self._tox.friend_get_name(friend.number), 'utf-8') - friend.set_alias('') - try: - index = list(map(lambda x: x[0], aliases)).index(friend.tox_id) - del aliases[index] - except: - pass - settings.save() - if num == self.get_active_number() and self.is_active_a_friend(): - self.update() - - 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] - settings = Settings.get_instance() - try: - index = list(map(lambda x: x[0], settings['friends_aliases'])).index(friend.tox_id) - del settings['friends_aliases'][index] - except: - pass - if friend.tox_id in settings['notes']: - del settings['notes'][friend.tox_id] - settings.save() - self.clear_history(num) - if self._history.friend_exists_in_db(friend.tox_id): - self._history.delete_friend_from_db(friend.tox_id) - self._tox.friend_delete(friend.number) - del self._contacts[num] - self._screen.friends_list.takeItem(num) - if num == self._active_friend: # active friend was deleted - if not len(self._contacts): # last friend was deleted - self.set_active(-1) - else: - self.set_active(0) - data = self._tox.get_savedata() - ProfileManager.get_instance().save_profile(data) - - def add_friend(self, tox_id): - """ - Adds friend to list - """ - num = self._tox.friend_add_norequest(tox_id) # num - friend number - item = self.create_friend_item() - try: - if not self._history.friend_exists_in_db(tox_id): - self._history.add_friend_to_db(tox_id) - message_getter = self._history.messages_getter(tox_id) - except Exception as ex: # something is wrong - log('Accept friend request failed! ' + str(ex)) - message_getter = None - friend = Friend(message_getter, num, tox_id, '', item, tox_id) - self._contacts.append(friend) - - 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_id[:TOX_PUBLIC_KEY_SIZE * 2]: - return - settings = Settings.get_instance() - if tox_id not in settings['blocked']: - settings['blocked'].append(tox_id) - settings.save() - try: - num = self._tox.friend_by_public_key(tox_id) - self.delete_friend(num) - data = self._tox.get_savedata() - ProfileManager.get_instance().save_profile(data) - 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 - """ - s = Settings.get_instance() - s['blocked'].remove(tox_id) - s.save() - if add_to_friend_list: - self.add_friend(tox_id) - data = self._tox.get_savedata() - ProfileManager.get_instance().save_profile(data) - - # ----------------------------------------------------------------------------------------------------------------- - # 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 = tox_dns(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) - msgBox = QtWidgets.QMessageBox() - msgBox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "Friend added")) - text = (QtWidgets.QApplication.translate("MainWindow", 'Friend added without sending friend request')) - msgBox.setText(text) - msgBox.exec_() - else: - result = self._tox.friend_add(tox_id, message.encode('utf-8')) - tox_id = tox_id[:TOX_PUBLIC_KEY_SIZE * 2] - item = self.create_friend_item() - if not self._history.friend_exists_in_db(tox_id): - self._history.add_friend_to_db(tox_id) - message_getter = self._history.messages_getter(tox_id) - friend = Friend(message_getter, result, tox_id, '', item, tox_id) - self._contacts.append(friend) - data = self._tox.get_savedata() - ProfileManager.get_instance().save_profile(data) - return True - except Exception as ex: # wrong data - 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 - """ - try: - text = QtWidgets.QApplication.translate('MainWindow', 'User {} wants to add you to contact list. Message:\n{}') - info = text.format(tox_id, message) - fr_req = QtWidgets.QApplication.translate('MainWindow', 'Friend request') - reply = QtWidgets.QMessageBox.question(None, fr_req, info, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) - if reply == QtWidgets.QMessageBox.Yes: # accepted - self.add_friend(tox_id) - data = self._tox.get_savedata() - ProfileManager.get_instance().save_profile(data) - except Exception as ex: # something is wrong - log('Accept friend request failed! ' + str(ex)) - # ----------------------------------------------------------------------------------------------------------------- # Reset # ----------------------------------------------------------------------------------------------------------------- @@ -900,307 +526,6 @@ class Profile(basecontact.BaseContact, Singleton): s['paused_file_transfers'] = dict(self._paused_file_transfers) if s['resend_files'] else {} s.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 - """ - settings = Settings.get_instance() - friend = self.get_friend_by_number(friend_number) - auto = settings['allow_auto_accept'] and friend.tox_id in settings['auto_accept_from_friends'] - inline = is_inline(file_name) and 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: - data = self._paused_file_transfers[file_id] - pos = data[-1] if os.path.exists(data[0]) else 0 - if pos >= size: - self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL']) - return - self._tox.file_seek(friend_number, file_number, pos) - self.accept_transfer(None, data[0], friend_number, file_number, size, False, pos) - tm = TransferMessage(MESSAGE_OWNER['FRIEND'], - time.time(), - TOX_FILE_TRANSFER_STATE['RUNNING'], - size, - file_name, - friend_number, - file_number) - elif inline and size < 1024 * 1024: - self.accept_transfer(None, '', friend_number, file_number, size, True) - tm = TransferMessage(MESSAGE_OWNER['FRIEND'], - time.time(), - TOX_FILE_TRANSFER_STATE['RUNNING'], - size, - file_name, - friend_number, - file_number) - - elif auto: - path = settings['auto_accept_path'] or curr_directory() - self.accept_transfer(None, path + '/' + file_name, friend_number, file_number, size) - tm = TransferMessage(MESSAGE_OWNER['FRIEND'], - time.time(), - TOX_FILE_TRANSFER_STATE['RUNNING'], - size, - file_name, - friend_number, - file_number) - else: - tm = TransferMessage(MESSAGE_OWNER['FRIEND'], - time.time(), - TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED'], - size, - file_name, - friend_number, - file_number) - accepted = False - if friend_number == self.get_active_number() and self.is_active_a_friend(): - item = self.create_file_transfer_item(tm) - if accepted: - self._file_transfers[(friend_number, file_number)].set_state_changed_handler(item.update_transfer_state) - self._messages.scrollToBottom() - else: - friend.actions = True - - friend.append_message(tm) - - 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 - """ - i = self.get_friend_by_number(friend_number).update_transfer_data(file_number, - TOX_FILE_TRANSFER_STATE['CANCELLED']) - 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)] - else: - if not already_cancelled: - self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL']) - if friend_number == self.get_active_number() and self.is_active_a_friend(): - tmp = self._messages.count() + i - if tmp >= 0: - self._messages.itemWidget( - self._messages.item(tmp)).update_transfer_state(TOX_FILE_TRANSFER_STATE['CANCELLED'], - 0, -1) - - def cancel_not_started_transfer(self, cancel_time): - self.get_curr_friend().delete_one_unsent_file(cancel_time) - self.update() - - 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) - t = TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'] if by_friend else TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER'] - self.get_friend_by_number(friend_number).update_transfer_data(file_number, t) - - def resume_transfer(self, friend_number, file_number, by_friend=False): - """ - Resume transfer with specified data - """ - self.get_friend_by_number(friend_number).update_transfer_data(file_number, - TOX_FILE_TRANSFER_STATE['RUNNING']) - tr = self._file_transfers[(friend_number, file_number)] - if by_friend: - tr.state = TOX_FILE_TRANSFER_STATE['RUNNING'] - tr.signal() - else: - tr.send_control(TOX_FILE_CONTROL['RESUME']) - - def accept_transfer(self, item, path, friend_number, file_number, size, inline=False, from_position=0): - """ - :param item: transfer item. - :param path: path for saving - :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, file_name = os.path.split(path) - new_file_name, i = file_name, 1 - if not from_position: - while os.path.isfile(path + '/' + new_file_name): # file with same name already exists - if '.' in file_name: # has extension - d = file_name.rindex('.') - else: # no extension - d = len(file_name) - new_file_name = file_name[:d] + ' ({})'.format(i) + file_name[d:] - i += 1 - path = os.path.join(path, new_file_name) - if not inline: - rt = ReceiveTransfer(path, self._tox, friend_number, size, file_number, from_position) - else: - rt = ReceiveToBuffer(self._tox, friend_number, size, file_number) - rt.set_transfer_finished_handler(self.transfer_finished) - self._file_transfers[(friend_number, file_number)] = rt - self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['RESUME']) - if item is not None: - rt.set_state_changed_handler(item.update_transfer_state) - self.get_friend_by_number(friend_number).update_transfer_data(file_number, - TOX_FILE_TRANSFER_STATE['RUNNING']) - - def send_screenshot(self, data): - """ - Send screenshot to current active friend - :param data: raw data - png - """ - self.send_inline(data, 'toxygen_inline.png') - self._messages.repaint() - - def send_sticker(self, path): - with open(path, 'rb') as fl: - data = fl.read() - self.send_inline(data, 'sticker.png') - - def send_inline(self, data, file_name, friend_number=None, is_resend=False): - friend_number = friend_number or self.get_active_number() - friend = self.get_friend_by_number(friend_number) - if friend.status is None and not is_resend: - m = UnsentFile(file_name, data, time.time()) - friend.append_message(m) - self.update() - return - elif friend.status is None and is_resend: - raise RuntimeError() - st = SendFromBuffer(self._tox, friend.number, data, file_name) - st.set_transfer_finished_handler(self.transfer_finished) - self._file_transfers[(friend.number, st.get_file_number())] = st - tm = TransferMessage(MESSAGE_OWNER['ME'], - time.time(), - TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'], - len(data), - file_name, - friend.number, - st.get_file_number()) - item = self.create_file_transfer_item(tm) - friend.append_message(tm) - st.set_state_changed_handler(item.update_transfer_state) - self._messages.scrollToBottom() - - def send_file(self, path, number=None, is_resend=False, file_id=None): - """ - Send file to current active friend - :param path: file path - :param number: friend_number - :param is_resend: is 'offline' message - :param file_id: file id of transfer - """ - friend_number = self.get_active_number() if number is None else number - friend = self.get_friend_by_number(friend_number) - if friend.status is None and not is_resend: - m = UnsentFile(path, None, time.time()) - friend.append_message(m) - self.update() - return - elif friend.status is None and is_resend: - print('Error in sending') - raise RuntimeError() - st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id) - st.set_transfer_finished_handler(self.transfer_finished) - self._file_transfers[(friend_number, st.get_file_number())] = st - tm = TransferMessage(MESSAGE_OWNER['ME'], - time.time(), - TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'], - os.path.getsize(path), - os.path.basename(path), - friend_number, - st.get_file_number()) - if friend_number == self.get_active_number(): - item = self.create_file_transfer_item(tm) - st.set_state_changed_handler(item.update_transfer_state) - self._messages.scrollToBottom() - self._contacts[friend_number].append_message(tm) - - 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() - if friend_number == self.get_active_number() and self.is_active_a_friend(): - self.set_active(None) - elif t is ReceiveToBuffer or (t is SendFromBuffer and Settings.get_instance()['allow_inline']): # inline image - print('inline') - inline = InlineImage(transfer.get_data()) - i = self.get_friend_by_number(friend_number).update_transfer_data(file_number, - TOX_FILE_TRANSFER_STATE['FINISHED'], - inline) - if friend_number == self.get_active_number() and self.is_active_a_friend(): - count = self._messages.count() - if count + i + 1 >= 0: - elem = QtWidgets.QListWidgetItem() - item = InlineImageItem(transfer.get_data(), self._messages.width(), elem) - elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height())) - self._messages.insertItem(count + i + 1, elem) - self._messages.setItemWidget(elem, item) - self._messages.scrollToBottom() - elif t is not SendAvatar: - self.get_friend_by_number(friend_number).update_transfer_data(file_number, - TOX_FILE_TRANSFER_STATE['FINISHED']) - del self._file_transfers[(friend_number, file_number)] - del transfer - - # ----------------------------------------------------------------------------------------------------------------- - # Avatars support - # ----------------------------------------------------------------------------------------------------------------- - - def send_avatar(self, friend_number): - """ - :param friend_number: number of friend who should get new avatar - """ - avatar_path = (ProfileManager.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) - if not os.path.isfile(avatar_path): # reset image - avatar_path = None - sa = SendAvatar(avatar_path, self._tox, friend_number) - self._file_transfers[(friend_number, sa.get_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) - """ - ra = ReceiveAvatar(self._tox, friend_number, size, file_number) - if ra.state != TOX_FILE_TRANSFER_STATE['CANCELLED']: - self._file_transfers[(friend_number, file_number)] = ra - ra.set_transfer_finished_handler(self.transfer_finished) - else: - self.get_friend_by_number(friend_number).load_avatar() - if self.get_active_number() == friend_number and self.is_active_a_friend(): - self.set_active(None) - def reset_avatar(self): super(Profile, self).reset_avatar() for friend in filter(lambda x: x.status is not None, self._contacts): @@ -1210,92 +535,3 @@ class Profile(basecontact.BaseContact, Singleton): super(Profile, self).set_avatar(data) for friend in filter(lambda x: x.status is not None, self._contacts): self.send_avatar(friend.number) - - # ----------------------------------------------------------------------------------------------------------------- - # AV support - # ----------------------------------------------------------------------------------------------------------------- - - def get_call(self): - return self._call - - call = property(get_call) - - def call_click(self, audio=True, video=False): - """User clicked audio button in main window""" - num = self.get_active_number() - if not self.is_active_a_friend(): - return - if num not in self._call and self.is_active_online(): # start call - if not Settings.get_instance().audio['enabled']: - return - self._call(num, audio, video) - self._screen.active_call() - if video: - text = QtWidgets.QApplication.translate("incoming_call", "Outgoing video call") - else: - text = QtWidgets.QApplication.translate("incoming_call", "Outgoing audio call") - self.get_curr_friend().append_message(InfoMessage(text, time.time())) - self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) - self._messages.scrollToBottom() - elif num in self._call: # finish or cancel call if you call with active friend - self.stop_call(num, False) - - def incoming_call(self, audio, video, friend_number): - """ - Incoming call from friend. - """ - if not Settings.get_instance().audio['enabled']: - return - friend = self.get_friend_by_number(friend_number) - if video: - text = QtWidgets.QApplication.translate("incoming_call", "Incoming video call") - else: - text = QtWidgets.QApplication.translate("incoming_call", "Incoming audio call") - friend.append_message(InfoMessage(text, time.time())) - self._incoming_calls.add(friend_number) - if friend_number == self.get_active_number(): - self._screen.incoming_call() - self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) - self._messages.scrollToBottom() - else: - friend.actions = True - self._call_widgets[friend_number] = avwidgets.IncomingCallWidget(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) - text = QtWidgets.QApplication.translate("incoming_call", "Call declined") - else: - text = QtWidgets.QApplication.translate("incoming_call", "Call finished") - 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 hasattr(self, '_call_widget'): - self._call_widget[friend_number].close() - del self._call_widget[friend_number] - - def destroy_window(): - if is_video: - cv2.destroyWindow(str(friend_number)) - - threading.Timer(2.0, destroy_window).start() - friend = self.get_friend_by_number(friend_number) - friend.append_message(InfoMessage(text, time.time())) - if friend_number == self.get_active_number(): - self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) - self._messages.scrollToBottom() diff --git a/toxygen/db/history.py b/toxygen/db/database.py similarity index 92% rename from toxygen/db/history.py rename to toxygen/db/database.py index eea7457..b414e8b 100644 --- a/toxygen/db/history.py +++ b/toxygen/db/database.py @@ -17,22 +17,22 @@ MESSAGE_OWNER = { 'NOT_SENT': 2 } -# TODO: unique message id and ngc support +# TODO: unique message id and ngc support, db name as profile name -class History: +class Database: - def __init__(self, name): + def __init__(self, name, toxes): self._name = name + self._toxes = toxes chdir(settings.ProfileManager.get_path()) path = settings.ProfileManager.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) + if toxes.is_data_encrypted(data): + data = toxes.pass_decrypt(data) with open(path, 'wb') as fout: fout.write(data) except: @@ -45,12 +45,11 @@ class History: db.close() def save(self): - encr = ToxES.get_instance() - if encr.has_password(): + if self._toxes.has_password(): path = settings.ProfileManager.get_path() + self._name + '.hstr' with open(path, 'rb') as fin: data = fin.read() - data = encr.pass_encrypt(bytes(data)) + data = self._toxes.pass_encrypt(bytes(data)) with open(path, 'wb') as fout: fout.write(data) @@ -136,8 +135,7 @@ class History: finally: db.close() - def delete_message(self, tox_id, time): - start, end = str(time - 0.01), str(time + 0.01) + def delete_message(self, tox_id, message_id): chdir(settings.ProfileManager.get_path()) db = connect(self._name + '.hstr', timeout=TIMEOUT) try: @@ -165,7 +163,7 @@ class History: db.close() def messages_getter(self, tox_id): - return History.MessageGetter(self._name, tox_id) + return Database.MessageGetter(self._name, tox_id) class MessageGetter: diff --git a/toxygen/file_tansfers/file_transfers_handler.py b/toxygen/file_tansfers/file_transfers_handler.py deleted file mode 100644 index e69de29..0000000 diff --git a/toxygen/file_tansfers/__init__.py b/toxygen/file_transfers/__init__.py similarity index 100% rename from toxygen/file_tansfers/__init__.py rename to toxygen/file_transfers/__init__.py diff --git a/toxygen/file_tansfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py similarity index 100% rename from toxygen/file_tansfers/file_transfers.py rename to toxygen/file_transfers/file_transfers.py diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py new file mode 100644 index 0000000..2706d97 --- /dev/null +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -0,0 +1,311 @@ +from file_transfers.file_transfers import * +from messenger.messages import * +import os + + +class FileTransfersHandler: + + def __init__(self, tox, settings): + self._tox = tox + self._settings = settings + self._file_transfers = {} + + # ----------------------------------------------------------------------------------------------------------------- + # 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 settings['auto_accept_from_friends'] + inline = is_inline(file_name) and 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: + data = self._paused_file_transfers[file_id] + pos = data[-1] if os.path.exists(data[0]) else 0 + if pos >= size: + self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL']) + return + self._tox.file_seek(friend_number, file_number, pos) + self.accept_transfer(None, data[0], friend_number, file_number, size, False, pos) + tm = TransferMessage(MESSAGE_OWNER['FRIEND'], + time.time(), + TOX_FILE_TRANSFER_STATE['RUNNING'], + size, + file_name, + friend_number, + file_number) + elif inline and size < 1024 * 1024: + self.accept_transfer(None, '', friend_number, file_number, size, True) + tm = TransferMessage(MESSAGE_OWNER['FRIEND'], + time.time(), + TOX_FILE_TRANSFER_STATE['RUNNING'], + size, + file_name, + friend_number, + file_number) + + elif auto: + path = settings['auto_accept_path'] or curr_directory() + self.accept_transfer(None, path + '/' + file_name, friend_number, file_number, size) + tm = TransferMessage(MESSAGE_OWNER['FRIEND'], + time.time(), + TOX_FILE_TRANSFER_STATE['RUNNING'], + size, + file_name, + friend_number, + file_number) + else: + tm = TransferMessage(MESSAGE_OWNER['FRIEND'], + time.time(), + TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED'], + size, + file_name, + friend_number, + file_number) + accepted = False + if friend_number == self.get_active_number() and self.is_active_a_friend(): + item = self.create_file_transfer_item(tm) + if accepted: + self._file_transfers[(friend_number, file_number)].set_state_changed_handler(item.update_transfer_state) + self._messages.scrollToBottom() + else: + friend.actions = True + + friend.append_message(tm) + + 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 + """ + i = self.get_friend_by_number(friend_number).update_transfer_data(file_number, + TOX_FILE_TRANSFER_STATE['CANCELLED']) + 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)] + else: + if not already_cancelled: + self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL']) + if friend_number == self.get_active_number() and self.is_active_a_friend(): + tmp = self._messages.count() + i + if tmp >= 0: + self._messages.itemWidget( + self._messages.item(tmp)).update_transfer_state(TOX_FILE_TRANSFER_STATE['CANCELLED'], + 0, -1) + + def cancel_not_started_transfer(self, cancel_time): + self.get_curr_friend().delete_one_unsent_file(cancel_time) + self.update() + + 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) + t = TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'] if by_friend else TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER'] + self.get_friend_by_number(friend_number).update_transfer_data(file_number, t) + + def resume_transfer(self, friend_number, file_number, by_friend=False): + """ + Resume transfer with specified data + """ + self.get_friend_by_number(friend_number).update_transfer_data(file_number, + TOX_FILE_TRANSFER_STATE['RUNNING']) + tr = self._file_transfers[(friend_number, file_number)] + if by_friend: + tr.state = TOX_FILE_TRANSFER_STATE['RUNNING'] + tr.signal() + else: + tr.send_control(TOX_FILE_CONTROL['RESUME']) + + def accept_transfer(self, item, path, friend_number, file_number, size, inline=False, from_position=0): + """ + :param item: transfer item. + :param path: path for saving + :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, file_name = os.path.split(path) + new_file_name, i = file_name, 1 + if not from_position: + while os.path.isfile(path + '/' + new_file_name): # file with same name already exists + if '.' in file_name: # has extension + d = file_name.rindex('.') + else: # no extension + d = len(file_name) + new_file_name = file_name[:d] + ' ({})'.format(i) + file_name[d:] + i += 1 + path = os.path.join(path, new_file_name) + if not inline: + rt = ReceiveTransfer(path, self._tox, friend_number, size, file_number, from_position) + else: + rt = ReceiveToBuffer(self._tox, friend_number, size, file_number) + rt.set_transfer_finished_handler(self.transfer_finished) + self._file_transfers[(friend_number, file_number)] = rt + self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['RESUME']) + if item is not None: + rt.set_state_changed_handler(item.update_transfer_state) + self.get_friend_by_number(friend_number).update_transfer_data(file_number, + TOX_FILE_TRANSFER_STATE['RUNNING']) + + def send_screenshot(self, data): + """ + Send screenshot to current active friend + :param data: raw data - png + """ + self.send_inline(data, 'toxygen_inline.png') + self._messages.repaint() + + def send_sticker(self, path): + with open(path, 'rb') as fl: + data = fl.read() + self.send_inline(data, 'sticker.png') + + def send_inline(self, data, file_name, friend_number=None, is_resend=False): + friend_number = friend_number or self.get_active_number() + friend = self.get_friend_by_number(friend_number) + if friend.status is None and not is_resend: + m = UnsentFile(file_name, data, time.time()) + friend.append_message(m) + self.update() + return + elif friend.status is None and is_resend: + raise RuntimeError() + st = SendFromBuffer(self._tox, friend.number, data, file_name) + st.set_transfer_finished_handler(self.transfer_finished) + self._file_transfers[(friend.number, st.get_file_number())] = st + tm = TransferMessage(MESSAGE_OWNER['ME'], + time.time(), + TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'], + len(data), + file_name, + friend.number, + st.get_file_number()) + item = self.create_file_transfer_item(tm) + friend.append_message(tm) + st.set_state_changed_handler(item.update_transfer_state) + self._messages.scrollToBottom() + + def send_file(self, path, number=None, is_resend=False, file_id=None): + """ + Send file to current active friend + :param path: file path + :param number: friend_number + :param is_resend: is 'offline' message + :param file_id: file id of transfer + """ + friend_number = self.get_active_number() if number is None else number + friend = self.get_friend_by_number(friend_number) + if friend.status is None and not is_resend: + m = UnsentFile(path, None, time.time()) + friend.append_message(m) + self.update() + return + elif friend.status is None and is_resend: + print('Error in sending') + raise RuntimeError() + st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id) + st.set_transfer_finished_handler(self.transfer_finished) + self._file_transfers[(friend_number, st.get_file_number())] = st + tm = TransferMessage(MESSAGE_OWNER['ME'], + time.time(), + TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'], + os.path.getsize(path), + os.path.basename(path), + friend_number, + st.get_file_number()) + if friend_number == self.get_active_number(): + item = self.create_file_transfer_item(tm) + st.set_state_changed_handler(item.update_transfer_state) + self._messages.scrollToBottom() + self._contacts[friend_number].append_message(tm) + + 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() + if friend_number == self.get_active_number() and self.is_active_a_friend(): + self.set_active(None) + elif t is ReceiveToBuffer or (t is SendFromBuffer and Settings.get_instance()['allow_inline']): # inline image + print('inline') + inline = InlineImage(transfer.get_data()) + i = self.get_friend_by_number(friend_number).update_transfer_data(file_number, + TOX_FILE_TRANSFER_STATE['FINISHED'], + inline) + if friend_number == self.get_active_number() and self.is_active_a_friend(): + count = self._messages.count() + if count + i + 1 >= 0: + elem = QtWidgets.QListWidgetItem() + item = InlineImageItem(transfer.get_data(), self._messages.width(), elem) + elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height())) + self._messages.insertItem(count + i + 1, elem) + self._messages.setItemWidget(elem, item) + self._messages.scrollToBottom() + elif t is not SendAvatar: + self.get_friend_by_number(friend_number).update_transfer_data(file_number, + TOX_FILE_TRANSFER_STATE['FINISHED']) + del self._file_transfers[(friend_number, file_number)] + del transfer + + # ----------------------------------------------------------------------------------------------------------------- + # Avatars support + # ----------------------------------------------------------------------------------------------------------------- + + def send_avatar(self, friend_number): + """ + :param friend_number: number of friend who should get new avatar + """ + avatar_path = (ProfileManager.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) + if not os.path.isfile(avatar_path): # reset image + avatar_path = None + sa = SendAvatar(avatar_path, self._tox, friend_number) + self._file_transfers[(friend_number, sa.get_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) + """ + ra = ReceiveAvatar(self._tox, friend_number, size, file_number) + if ra.state != TOX_FILE_TRANSFER_STATE['CANCELLED']: + self._file_transfers[(friend_number, file_number)] = ra + ra.set_transfer_finished_handler(self.transfer_finished) + else: + self.get_friend_by_number(friend_number).load_avatar() + if self.get_active_number() == friend_number and self.is_active_a_friend(): + self.set_active(None) diff --git a/toxygen/main.py b/toxygen/main.py index c8f18f6..e8a4694 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -1,12 +1,10 @@ import sys +import app from user_data.settings import * -from util import curr_directory, program_version, remove +from util.util import curr_directory, program_version, remove import argparse - - - def clean(): """Removes all windows libs from libs folder""" d = curr_directory() + '/libs/' @@ -24,7 +22,7 @@ def main(): parser.add_argument('--reset') args = parser.parse_args() if not len(args): - toxygen = Toxygen() + toxygen = app.App() else: # started with argument(s) arg = sys.argv[1] if arg == '--version': @@ -40,7 +38,7 @@ def main(): reset() return else: - toxygen = Toxygen(arg) + toxygen = app.App(arg) toxygen.main() diff --git a/toxygen/notifications.py b/toxygen/notifications.py index 26a29ec..cf926d1 100644 --- a/toxygen/notifications.py +++ b/toxygen/notifications.py @@ -1,5 +1,5 @@ from PyQt5 import QtCore, QtWidgets -from util import curr_directory +from util.util import curr_directory import wave import pyaudio diff --git a/toxygen/smileys_and_stickers.py b/toxygen/smileys_and_stickers.py index 52cb603..7294185 100644 --- a/toxygen/smileys_and_stickers.py +++ b/toxygen/smileys_and_stickers.py @@ -1,4 +1,4 @@ -import util +from util import util import json import os from collections import OrderedDict diff --git a/toxygen/threads.py b/toxygen/threads.py index a79297a..f437d5a 100644 --- a/toxygen/threads.py +++ b/toxygen/threads.py @@ -1,3 +1,11 @@ +from PyQt5 import QtCore +from communication.callbacks import init_callbacks +from bootstrap.bootstrap import * +import threading +import queue +from util import util + + class InitThread(QtCore.QThread): def __init__(self, tox, ms, tray): @@ -70,8 +78,8 @@ class FileTransfersThread(threading.Thread): self._continue = True super().__init__() - def execute(self, function, *args, **kwargs): - self._queue.put((function, args, kwargs)) + def execute(self, func, *args, **kwargs): + self._queue.put((func, args, kwargs)) def stop(self): self._continue = False @@ -79,12 +87,12 @@ class FileTransfersThread(threading.Thread): def run(self): while self._continue: try: - function, args, kwargs = self._queue.get(timeout=self._timeout) - function(*args, **kwargs) + 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') + util.log('Queue is full in _thread') except Exception as ex: util.log('Exception in _thread: ' + str(ex)) @@ -101,6 +109,8 @@ def stop(): _thread.join() +def execute(func, *args, **kwargs): + _thread.execute(func, *args, **kwargs) class InvokeEvent(QtCore.QEvent): @@ -125,3 +135,4 @@ _invoker = Invoker() def invoke_in_main_thread(fn, *args, **kwargs): QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs)) + diff --git a/toxygen/tray.py b/toxygen/tray.py index ffe9aeb..bc22ed4 100644 --- a/toxygen/tray.py +++ b/toxygen/tray.py @@ -1,83 +1,95 @@ -self.tray = QtWidgets.QSystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) - self.tray.setObjectName('tray') +from PyQt5 import QtWidgets, QtGui, QtCore +from util.ui import tr +from util.util import curr_directory - class Menu(QtWidgets.QMenu): - def newStatus(self, status): - if not Settings.get_instance().locked: - profile.Profile.get_instance().set_status(status) - self.aboutToShowHandler() - self.hide() +class Menu(QtWidgets.QMenu): - def aboutToShowHandler(self): - status = profile.Profile.get_instance().status - act = self.act - if status is None or Settings.get_instance().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 Settings.get_instance().locked) + def __init__(self, settings, profile, *args): + super().__init__(*args) + self._settings = settings + self._profile = profile - def languageChange(self, *args, **kwargs): - self.actions()[0].setText(QtWidgets.QApplication.translate('tray', 'Open Toxygen')) - self.actions()[1].setText(QtWidgets.QApplication.translate('tray', 'Set status')) - self.actions()[2].setText(QtWidgets.QApplication.translate('tray', 'Exit')) - self.act.actions()[0].setText(QtWidgets.QApplication.translate('tray', 'Online')) - self.act.actions()[1].setText(QtWidgets.QApplication.translate('tray', 'Away')) - self.act.actions()[2].setText(QtWidgets.QApplication.translate('tray', 'Busy')) + def newStatus(self, status): + if not self._settings.locked: + self._profile.Profile.get_instance().set_status(status) + self.aboutToShowHandler() + self.hide() - m = Menu() - show = m.addAction(QtWidgets.QApplication.translate('tray', 'Open Toxygen')) - sub = m.addMenu(QtWidgets.QApplication.translate('tray', 'Set status')) - onl = sub.addAction(QtWidgets.QApplication.translate('tray', 'Online')) - away = sub.addAction(QtWidgets.QApplication.translate('tray', 'Away')) - busy = sub.addAction(QtWidgets.QApplication.translate('tray', 'Busy')) - onl.setCheckable(True) - away.setCheckable(True) - busy.setCheckable(True) - m.act = sub - exit = m.addAction(QtWidgets.QApplication.translate('tray', 'Exit')) + def aboutToShowHandler(self): + status = self._profile.status + act = self.act + if status is None or self._ettings.get_instance().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 show_window(): - s = Settings.get_instance() + 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 show(): - if not self.ms.isActiveWindow(): - self.ms.setWindowState(self.ms.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) - self.ms.activateWindow() - self.ms.show() - if not s.locked: + +def init_tray(profile, settings, main_screen): + tray = QtWidgets.QSystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) + tray.setObjectName('tray') + + m = Menu(settings, profile) + show = m.addAction(tr('Open Toxygen')) + sub = m.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) + m.act = sub + exit = m.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() - else: - def correct_pass(): - show() - s.locked = False - s.unlockScreen = False - if not s.unlockScreen: - s.unlockScreen = True - self.p = UnlockAppScreen(toxes.ToxES.get_instance(), correct_pass) - self.p.show() + settings.locked = False + settings.unlockScreen = False + if not settings.unlockScreen: + settings.unlockScreen = True + self.p = UnlockAppScreen(toxes.ToxES.get_instance(), correct_pass) + self.p.show() - def tray_activated(reason): - if reason == QtWidgets.QSystemTrayIcon.DoubleClick: - show_window() + def tray_activated(reason): + if reason == QtWidgets.QSystemTrayIcon.DoubleClick: + show_window() - def close_app(): - if not Settings.get_instance().locked: - settings.closing = True - self.ms.close() + def close_app(): + if not settings.locked: + settings.closing = True + main_screen.close() - show.triggered.connect(show_window) - exit.triggered.connect(close_app) - m.aboutToShow.connect(lambda: m.aboutToShowHandler()) - onl.triggered.connect(lambda: m.newStatus(0)) - away.triggered.connect(lambda: m.newStatus(1)) - busy.triggered.connect(lambda: m.newStatus(2)) + show.triggered.connect(show_window) + exit.triggered.connect(close_app) + m.aboutToShow.connect(lambda: m.aboutToShowHandler()) + online.triggered.connect(lambda: m.newStatus(0)) + away.triggered.connect(lambda: m.newStatus(1)) + busy.triggered.connect(lambda: m.newStatus(2)) - self.tray.setContextMenu(m) - self.tray.show() - self.tray.activated.connect(tray_activated) \ No newline at end of file + tray.setContextMenu(m) + tray.show() + tray.activated.connect(tray_activated) + + return tray diff --git a/toxygen/ui/list_items.py b/toxygen/ui/list_items.py index f6ab154..64b4a2e 100644 --- a/toxygen/ui/list_items.py +++ b/toxygen/ui/list_items.py @@ -1,7 +1,7 @@ from wrapper.toxcore_enums_and_consts import * from PyQt5 import QtCore, QtGui, QtWidgets from contacts import profile -from file_tansfers.file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR +from file_transfers.file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR from util import curr_directory, convert_time, curr_time from ui.widgets import DataLabel, create_menu import html as h diff --git a/toxygen/user_data/profile_manager.py b/toxygen/user_data/profile_manager.py index 96f591c..12ce185 100644 --- a/toxygen/user_data/profile_manager.py +++ b/toxygen/user_data/profile_manager.py @@ -1,4 +1,4 @@ -class ProfileManager(): +class ProfileManager: """ Class with methods for search, load and save profiles """ diff --git a/toxygen/util/ui.py b/toxygen/util/ui.py new file mode 100644 index 0000000..a0d950d --- /dev/null +++ b/toxygen/util/ui.py @@ -0,0 +1,8 @@ +import PyQt5 + + +def tr(s): + return PyQt5.QtWidgets.QApplication.translate('Toxygen', s) + + +# TODO: move all dialogs here diff --git a/toxygen/util/util.py b/toxygen/util/util.py index bb27da6..33b4758 100644 --- a/toxygen/util/util.py +++ b/toxygen/util/util.py @@ -29,9 +29,8 @@ def log(data): pass -@cached -def curr_directory(): - return os.path.dirname(os.path.realpath(__file__)) +def curr_directory(current_file=None): + return os.path.dirname(os.path.realpath(current_file or __file__)) def curr_time(): From 1bead7d55d3b2a2c8a90e2c9df0560792d8ecbf7 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 12 Mar 2018 00:32:46 +0300 Subject: [PATCH 004/138] history improvements --- toxygen/communication/callbacks.py | 27 ++--- toxygen/contacts/contact.py | 6 +- toxygen/contacts/contacts_manager.py | 6 + toxygen/contacts/profile.py | 169 ++------------------------- toxygen/{db => history}/__init__.py | 0 toxygen/{db => history}/database.py | 2 +- toxygen/history/history_loader.py | 134 +++++++++++++++++++++ toxygen/messenger/messages.py | 34 +++--- 8 files changed, 189 insertions(+), 189 deletions(-) rename toxygen/{db => history}/__init__.py (100%) rename toxygen/{db => history}/database.py (99%) create mode 100644 toxygen/history/history_loader.py diff --git a/toxygen/communication/callbacks.py b/toxygen/communication/callbacks.py index e27bd74..ac93173 100644 --- a/toxygen/communication/callbacks.py +++ b/toxygen/communication/callbacks.py @@ -102,10 +102,10 @@ def friend_status_message(profile): def friend_message(profile, settings, window, tray): - """ - New message from friend - """ def wrapped(tox, friend_number, message_type, message, size, user_data): + """ + New message from friend + """ message = str(message, 'utf-8') invoke_in_main_thread(profile.new_message, friend_number, message_type, message) if not window.isActiveWindow(): @@ -119,16 +119,17 @@ def friend_message(profile, settings, window, tray): 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_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(tox, friend_number, typing, user_data): diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index 96ff804..22191a7 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -1,4 +1,4 @@ -from db.database import * +from history.database import * from contacts import basecontact import util from messenger.messages import * @@ -124,8 +124,8 @@ class Contact(basecontact.BaseContact): # Message deletion # ----------------------------------------------------------------------------------------------------------------- - def delete_message(self, time): - elem = list(filter(lambda x: type(x) in (TextMessage, GroupChatMessage) and x.get_data()[2] == time, self._corr))[0] + def delete_message(self, message_id): + elem = list(filter(lambda x: type(x) in (TextMessage, GroupChatMessage) and x.message_id == message_id, self._corr))[0] tmp = list(filter(lambda x: x.get_type() <= 1, self._corr)) if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages: self._unsaved_messages -= 1 diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index cbac1e1..7653b56 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -152,6 +152,10 @@ class ContactsManager: active_friend = property(get_active, set_active) + def update(self): + if self._active_friend + 1: + self.set_active(self._active_friend) + # ----------------------------------------------------------------------------------------------------------------- # Filtration # ----------------------------------------------------------------------------------------------------------------- @@ -389,6 +393,8 @@ class ContactsManager: :param tox_id: tox id of contact :param message: message """ + if tox_id in self._settings['blocked']: + return try: text = QtWidgets.QApplication.translate('MainWindow', 'User {} wants to add you to contact list. Message:\n{}') info = text.format(tox_id, message) diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 483d96b..9d33f19 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -45,7 +45,6 @@ class Profile(basecontact.BaseContact, Singleton): self._show_avatars = settings['show_avatars'] self._paused_file_transfers = dict(settings['paused_file_transfers']) # key - file id, value: [path, friend number, is incoming, start position] - self._history = History(tox.self_get_public_key()) # connection to db # ----------------------------------------------------------------------------------------------------------------- @@ -128,10 +127,6 @@ class Profile(basecontact.BaseContact, Singleton): self._messages.scrollToBottom() self.set_active(None) - def update(self): - if self._active_friend + 1: - self.set_active(self._active_friend) - # ----------------------------------------------------------------------------------------------------------------- # Friend connection status callbacks # ----------------------------------------------------------------------------------------------------------------- @@ -242,29 +237,6 @@ class Profile(basecontact.BaseContact, Singleton): return messages - def split_and_send(self, number, message_type, message): - """ - Message splitting. Message length cannot be > TOX_MAX_MESSAGE_LENGTH - :param number: friend's number - :param message_type: type of message - :param message: message text - """ - 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 - self._tox.friend_send_message(number, message_type, message[:index]) - message = message[index:] - self._tox.friend_send_message(number, message_type, message) - def new_message(self, friend_num, message_type, message): """ Current user gets new message @@ -286,30 +258,29 @@ class Profile(basecontact.BaseContact, Singleton): if not friend.visibility: self.update_filtration() - def send_message(self, text, friend_num=None): + def send_message_to_friend(self, text, friend_number=None): """ Send message :param text: message text - :param friend_num: num of friend + :param friend_number: number of friend """ - if not self.is_active_a_friend(): - self.send_gc_message(text) - return - if friend_num is None: - friend_num = self.get_active_number() + if friend_number is None: + friend_number = self.get_active_number() if text.startswith('/plugin '): - plugin_support.PluginLoader.get_instance().command(text[8:]) + self._plugin_loader.command(text[8:]) self._screen.messageEdit.clear() - elif text and friend_num + 1: + elif text and friend_number >= 0: if text.startswith('/me '): message_type = TOX_MESSAGE_TYPE['ACTION'] text = text[4:] else: message_type = TOX_MESSAGE_TYPE['NORMAL'] - friend = self.get_friend_by_number(friend_num) + friend = self.get_friend_by_number(friend_number) friend.inc_receipts() if friend.status is not None: - self.split_and_send(friend.number, message_type, text.encode('utf-8')) + messages = self.split_message(text.encode('utf-8')) + for message in messages: + self._tox.friend_send_message(friend_number, message_type, message) t = time.time() if friend.number == self.get_active_number() and self.is_active_a_friend(): self.create_message_item(text, t, MESSAGE_OWNER['NOT_SENT'], message_type) @@ -317,128 +288,12 @@ class Profile(basecontact.BaseContact, Singleton): self._messages.scrollToBottom() friend.append_message(TextMessage(text, MESSAGE_OWNER['NOT_SENT'], t, message_type)) - def delete_message(self, time): + def delete_message(self, message_id): friend = self.get_curr_friend() friend.delete_message(time) - self._history.delete_message(friend.tox_id, time) + self._history.delete_message(friend.tox_id, message_id) self.update() - # ----------------------------------------------------------------------------------------------------------------- - # History support - # ----------------------------------------------------------------------------------------------------------------- - - def save_history(self): - """ - Save history to db - """ - s = Settings.get_instance() - if hasattr(self, '_history'): - if s['save_history']: - for friend in filter(lambda x: type(x) is Friend, self._contacts): - if not self._history.friend_exists_in_db(friend.tox_id): - self._history.add_friend_to_db(friend.tox_id) - if not s['save_unsent_only']: - messages = friend.get_corr_for_saving() - else: - messages = friend.get_unsent_messages_for_saving() - self._history.delete_messages(friend.tox_id) - self._history.save_messages_to_db(friend.tox_id, messages) - unsent_messages = friend.get_unsent_messages() - unsent_time = unsent_messages[0].get_data()[2] if len(unsent_messages) else time.time() + 1 - self._history.update_messages(friend.tox_id, unsent_time) - self._history.save() - del self._history - - def clear_history(self, num=None, save_unsent=False): - """ - Clear chat history - """ - if num is not None: - friend = self._contacts[num] - friend.clear_corr(save_unsent) - if self._history.friend_exists_in_db(friend.tox_id): - self._history.delete_messages(friend.tox_id) - self._history.delete_friend_from_db(friend.tox_id) - else: # clear all history - for number in range(len(self._contacts)): - self.clear_history(number, save_unsent) - if num is None or num == self.get_active_number(): - self.update() - - def load_history(self): - """ - Tries to load next part of messages - """ - if not self._load_history: - return - self._load_history = False - friend = self.get_curr_friend() - friend.load_corr(False) - data = friend.get_corr() - if not data: - return - data.reverse() - data = data[self._messages.count():self._messages.count() + PAGE_SIZE] - for message in data: - if message.get_type() <= 1: # text message - data = message.get_data() - self.create_message_item(data[0], - data[2], - data[1], - data[3], - False) - elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: # file transfer - if message.get_status() is None: - self.create_unsent_file_item(message) - continue - item = self.create_file_transfer_item(message, False) - if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer - try: - ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())] - ft.set_state_changed_handler(item.update_transfer_state) - ft.signal() - except: - print('Incoming not started transfer - no info found') - elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline image - self.create_inline_item(message.get_data(), False) - else: # info message - data = message.get_data() - self.create_message_item(data[0], - data[2], - '', - data[3], - False) - self._load_history = True - - def export_db(self, directory): - self._history.export(directory) - - def export_history(self, num, as_text=True, _range=None): - friend = self._contacts[num] - if _range is None: - friend.load_all_corr() - corr = friend.get_corr() - elif _range[1] + 1: - corr = friend.get_corr()[_range[0]:_range[1] + 1] - else: - corr = friend.get_corr()[_range[0]:] - arr = [] - new_line = '\n' if as_text else '
' - for message in corr: - if type(message) is TextMessage: - data = message.get_data() - if as_text: - x = '[{}] {}: {}\n' - else: - x = '[{}] {}: {}
' - arr.append(x.format(convert_time(data[2]) if data[1] != MESSAGE_OWNER['NOT_SENT'] else 'Unsent', - friend.name if data[1] == MESSAGE_OWNER['FRIEND'] else self.name, - data[0])) - s = new_line.join(arr) - if not as_text: - s = '{}{}'.format(friend.name, s) - return s - # ----------------------------------------------------------------------------------------------------------------- # Friend, message and file transfer items creation # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/db/__init__.py b/toxygen/history/__init__.py similarity index 100% rename from toxygen/db/__init__.py rename to toxygen/history/__init__.py diff --git a/toxygen/db/database.py b/toxygen/history/database.py similarity index 99% rename from toxygen/db/database.py rename to toxygen/history/database.py index b414e8b..ff9ce8e 100644 --- a/toxygen/db/database.py +++ b/toxygen/history/database.py @@ -9,7 +9,7 @@ PAGE_SIZE = 42 TIMEOUT = 11 -SAVE_MESSAGES = 250 +SAVE_MESSAGES = 500 MESSAGE_OWNER = { 'ME': 0, diff --git a/toxygen/history/history_loader.py b/toxygen/history/history_loader.py new file mode 100644 index 0000000..86e61c5 --- /dev/null +++ b/toxygen/history/history_loader.py @@ -0,0 +1,134 @@ +from messenger.messages import * + + +class HistoryLoader: + + def __init__(self, db, settings): + self._db = db + self._settings = settings + + # ----------------------------------------------------------------------------------------------------------------- + # History support + # ----------------------------------------------------------------------------------------------------------------- + + def save_history(self): + """ + Save history to db + """ + if hasattr(self, '_history'): + if self._settings['save_history']: + for friend in filter(lambda x: type(x) is Friend, self._contacts): + if not self._history.friend_exists_in_db(friend.tox_id): + self._history.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._history.delete_messages(friend.tox_id) + self._history.save_messages_to_db(friend.tox_id, messages) + unsent_messages = friend.get_unsent_messages() + unsent_time = unsent_messages[0].get_data()[2] if len(unsent_messages) else time.time() + 1 + self._history.update_messages(friend.tox_id, unsent_time) + self._history.save() + del self._history + + def clear_history(self, friend, save_unsent=False): + """ + Clear chat history + """ + friend.clear_corr(save_unsent) + if self._db.friend_exists_in_db(friend.tox_id): + self._db.delete_messages(friend.tox_id) + self._db.delete_friend_from_db(friend.tox_id) + + def load_history(self): + """ + Tries to load next part of messages + """ + if not self._load_history: + return + self._load_history = False + friend = self.get_curr_friend() + friend.load_corr(False) + data = friend.get_corr() + if not data: + return + data.reverse() + data = data[self._messages.count():self._messages.count() + PAGE_SIZE] + for message in data: + if message.get_type() <= 1: # text message + data = message.get_data() + self.create_message_item(data[0], + data[2], + data[1], + data[3], + False) + elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: # file transfer + if message.get_status() is None: + self.create_unsent_file_item(message) + continue + item = self.create_file_transfer_item(message, False) + if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer + try: + ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())] + ft.set_state_changed_handler(item.update_transfer_state) + ft.signal() + except: + print('Incoming not started transfer - no info found') + elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline image + self.create_inline_item(message.get_data(), False) + else: # info message + data = message.get_data() + self.create_message_item(data[0], + data[2], + '', + data[3], + False) + self._load_history = True + + def export_history(self, friend, as_text=True, _range=None): + if _range is None: + friend.load_all_corr() + corr = friend.get_corr() + elif _range[1] + 1: + corr = friend.get_corr()[_range[0]:_range[1] + 1] + else: + corr = friend.get_corr()[_range[0]:] + + generator = TextHistoryGenerator() if as_text else HtmlHistoryGenerator + + return generator.generate(corr) + + +class HtmlHistoryGenerator: + + def generate(self, corr): + arr = [] + for message in corr: + if type(message) is TextMessage: + data = message.get_data() + x = '[{}] {}: {}
' + arr.append(x.format(convert_time(data[2]) if data[1] != MESSAGE_OWNER['NOT_SENT'] else 'Unsent', + friend.name if data[1] == MESSAGE_OWNER['FRIEND'] else self.name, + data[0])) + s = '
'.join(arr) + s = '{}{}'.format(friend.name, + s) + return s + + +class TextHistoryGenerator: + + def generate(self, corr): + arr = [] + for message in corr: + if type(message) is TextMessage: + data = message.get_data() + x = '[{}] {}: {}\n' + arr.append(x.format(convert_time(data[2]) if data[1] != MESSAGE_OWNER['NOT_SENT'] else 'Unsent', + friend.name if data[1] == MESSAGE_OWNER['FRIEND'] else self.name, + data[0])) + s = '\n'.join(arr) + + return s + diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index 8d9f4a3..4baa29a 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -5,18 +5,17 @@ MESSAGE_TYPE = { 'ACTION': 1, 'FILE_TRANSFER': 2, 'INLINE': 3, - 'INFO_MESSAGE': 4, - 'GC_TEXT': 5, - 'GC_ACTION': 6 + 'INFO_MESSAGE': 4 } class Message: - def __init__(self, message_type, owner, time): + def __init__(self, message_id, message_type, owner, time): self._time = time self._type = message_type self._owner = owner + self._message_id = message_id def get_type(self): return self._type @@ -27,14 +26,19 @@ class Message: def mark_as_sent(self): self._owner = 0 + def get_message_id(self): + return self._message_id + + message_id = property(get_message_id) + class TextMessage(Message): """ Plain text or action message """ - def __init__(self, message, owner, time, message_type): - super(TextMessage, self).__init__(message_type, owner, time) + def __init__(self, id, message, owner, time, message_type): + super(TextMessage, self).__init__(id, message_type, owner, time) self._message = message def get_data(self): @@ -43,8 +47,8 @@ class TextMessage(Message): class GroupChatMessage(TextMessage): - def __init__(self, message, owner, time, message_type, name): - super().__init__(message, owner, time, message_type) + def __init__(self, id, message, owner, time, message_type, name): + super().__init__(id, message, owner, time, message_type) self._user_name = name def get_data(self): @@ -56,7 +60,7 @@ class TransferMessage(Message): Message with info about file transfer """ - def __init__(self, owner, time, status, size, name, friend_number, file_number): + def __init__(self, id, 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 @@ -83,8 +87,8 @@ class TransferMessage(Message): class UnsentFile(Message): - def __init__(self, path, data, time): - super(UnsentFile, self).__init__(MESSAGE_TYPE['FILE_TRANSFER'], 0, time) + def __init__(self, id, path, data, time): + super(UnsentFile, self).__init__(id, MESSAGE_TYPE['FILE_TRANSFER'], 0, time) self._data, self._path = data, path def get_data(self): @@ -99,8 +103,8 @@ class InlineImage(Message): Inline image """ - def __init__(self, data): - super(InlineImage, self).__init__(MESSAGE_TYPE['INLINE'], None, None) + def __init__(self, id, data): + super(InlineImage, self).__init__(id, MESSAGE_TYPE['INLINE'], None, None) self._data = data def get_data(self): @@ -109,5 +113,5 @@ class InlineImage(Message): class InfoMessage(TextMessage): - def __init__(self, message, time): - super(InfoMessage, self).__init__(message, None, time, MESSAGE_TYPE['INFO_MESSAGE']) + def __init__(self, id, message, time): + super(InfoMessage, self).__init__(id, message, None, time, MESSAGE_TYPE['INFO_MESSAGE']) From 85467e1885daa22c95918b10ac790e5be386bfe1 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 16 Apr 2018 00:11:51 +0300 Subject: [PATCH 005/138] refactoring - login screen, incorrect refs --- toxygen/app.py | 74 ++++--- toxygen/communication/callbacks.py | 201 ++++++++---------- toxygen/contacts/profile.py | 11 +- .../file_transfers/file_transfers_handler.py | 2 + toxygen/main.py | 48 +++-- toxygen/network/tox_dns.py | 2 +- toxygen/plugin_support/plugin_support.py | 14 +- toxygen/smileys_and_stickers.py | 2 +- toxygen/threads.py | 2 +- toxygen/tray.py | 15 +- toxygen/ui/avwidgets.py | 2 +- toxygen/ui/list_items.py | 6 +- .../ui/{loginscreen.py => login_screen.py} | 64 ++++-- toxygen/ui/{mainscreen.py => main_screen.py} | 2 +- ...reen_widgets.py => main_screen_widgets.py} | 0 .../{passwordscreen.py => password_screen.py} | 12 +- toxygen/ui/widgets.py | 16 ++ toxygen/user_data/profile_manager.py | 25 ++- toxygen/user_data/settings.py | 29 ++- toxygen/util/util.py | 27 +-- toxygen/wrapper/libtox.py | 2 +- 21 files changed, 302 insertions(+), 254 deletions(-) rename toxygen/ui/{loginscreen.py => login_screen.py} (69%) rename toxygen/ui/{mainscreen.py => main_screen.py} (99%) rename toxygen/ui/{mainscreen_widgets.py => main_screen_widgets.py} (100%) rename toxygen/ui/{passwordscreen.py => password_screen.py} (95%) diff --git a/toxygen/app.py b/toxygen/app.py index 7f789eb..b4c5d55 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -1,50 +1,60 @@ +import communication.callbacks import threads - +from PyQt5 import QtWidgets, QtGui, QtCore +import ui.password_screen as passwordscreen +from util.util import curr_directory, get_platform, get_images_directory, get_styles_directory, log +import updater.updater as updater +import os +from communication.tox_factory import tox_factory +import wrapper.libtox as libtox +import user_data.toxes +from user_data.settings import Settings +from ui.login_screen import LoginScreen +from user_data.profile_manager import ProfileManager class App: - def __init__(self, path_or_uri=None): + def __init__(self, path_to_profile=None, uri=None): self.tox = self.ms = self.init = self.app = self.tray = self.mainloop = self.avloop = None - if path_or_uri is None: - self.uri = self.path = None - elif path_or_uri.startswith('tox:'): - self.path = None - self.uri = path_or_uri[4:] - else: - self.path = path_or_uri - self.uri = None + self.uri = self.path = self.toxes = None + if uri is not None and uri.startswith('tox:'): + self.uri = uri[4:] + if path_to_profile is not None: + self.path = path_to_profile def enter_pass(self, data): """ Show password screen """ - tmp = [data] - p = PasswordScreen(toxes.ToxES.get_instance(), tmp) + p = passwordscreen.PasswordScreen(self.toxes, data) p.show() self.app.lastWindowClosed.connect(self.app.quit) self.app.exec_() - if tmp[0] == data: + result = p.result + if result is None: raise SystemExit() else: - return tmp[0] + return result def main(self): """ Main function of app. loads login screen if needed and starts main screen """ - app = QtWidgets.QApplication(sys.argv) - app.setWindowIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) + app = QtWidgets.QApplication([]) + icon_file = os.path.join(get_images_directory(), 'icon.png') + app.setWindowIcon(QtGui.QIcon(icon_file)) self.app = app - if platform.system() == 'Linux': + if get_platform() == 'Linux': QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) - with open(curr_directory() + '/styles/dark_style.qss') as fl: + with open(os.path.join(get_styles_directory(), 'dark_style.qss')) as fl: style = fl.read() app.setStyleSheet(style) - encrypt_save = toxes.ToxES() + encrypt_save = libtox.LibToxEncryptSave() + toxes = user_data.toxes.ToxES(encrypt_save) if self.path is not None: path = os.path.dirname(self.path) + '/' @@ -70,15 +80,13 @@ class App: ls = LoginScreen() ls.setWindowIconText("Toxygen") profiles = ProfileManager.find_profiles() - ls.update_select(map(lambda x: x[1], profiles)) - _login = self.Login(profiles) - ls.update_on_close(_login.login_screen_close) + ls.update_select(profiles) ls.show() app.exec_() - if not _login.t: + result = ls.result + if result is None: return - elif _login.t == 1: # create new profile - _login.name = _login.name.strip() + elif result.is_new_profile(): # create new profile name = _login.name if _login.name else 'toxygen_user' pr = map(lambda x: x[1], ProfileManager.find_profiles()) if name in list(pr): @@ -130,14 +138,14 @@ class App: settings['language'] = curr_lang settings.save() else: # load existing profile - path, name = _login.get_data() - if _login.default: - Settings.set_auto_profile(path, name) - data = ProfileManager(path, name).open_profile() - if encrypt_save.is_data_encrypted(data): + path = result.profile_path + if result.load_as_default: + Settings.set_auto_profile(path) + settings = Settings(toxes, path) + data = ProfileManager(settings, toxes, path).open_profile() + if toxes.is_data_encrypted(data): data = self.enter_pass(data) - settings = Settings(name) - self.tox = profile.tox_factory(data, settings) + self.tox = communication.tox_factory.tox_factory(data, settings) else: path, name = auto_profile data = ProfileManager(path, name).open_profile() @@ -264,4 +272,4 @@ class App: plugin_helper = PluginLoader.get_instance() plugin_helper.set_tox(self.tox) - return self.tox \ No newline at end of file + return self.tox diff --git a/toxygen/communication/callbacks.py b/toxygen/communication/callbacks.py index ac93173..0566325 100644 --- a/toxygen/communication/callbacks.py +++ b/toxygen/communication/callbacks.py @@ -5,10 +5,7 @@ from contacts.profile import Profile from wrapper.toxcore_enums_and_consts import * from wrapper.toxav_enums import * from wrapper.tox import bin_to_string -from plugin_support import PluginLoader -import queue -import threading -import util +from plugin_support.plugin_support import PluginLoader import cv2 import numpy as np from threads import invoke_in_main_thread, execute @@ -31,6 +28,7 @@ def self_connection_status(tox, profile): invoke_in_main_thread(profile.set_status, status) elif connection == TOX_CONNECTION['NONE']: invoke_in_main_thread(profile.set_status, None) + return wrapped @@ -132,41 +130,45 @@ def friend_request(contacts_manager): return wrapped -def friend_typing(tox, friend_number, typing, user_data): - invoke_in_main_thread(Profile.get_instance().friend_typing, friend_number, typing) +def friend_typing(contacts_manager): + def wrapped(tox, friend_number, typing, user_data): + invoke_in_main_thread(contacts_manager.friend_typing, friend_number, typing) + + return wrapped -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) +def friend_read_receipt(contacts_manager): + def wrapped(tox, friend_number, message_id, user_data): + contacts_manager.get_friend_by_number(friend_number).dec_receipt() + if friend_number == contacts_manager.get_active_number(): + invoke_in_main_thread(contacts_manager.receipt) + + return wrapped + # ----------------------------------------------------------------------------------------------------------------- # Callbacks - file transfers # ----------------------------------------------------------------------------------------------------------------- -def tox_file_recv(window, tray): +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): - 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, + invoke_in_main_thread(file_transfer_handler.incoming_file_transfer, friend_number, file_number, size, file_name) if not window.isActiveWindow(): - friend = profile.get_friend_by_number(friend_number) + 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 = QtWidgets.QApplication.translate("Callback", "File from") invoke_in_main_thread(tray_notification, file_from + ' ' + friend.name, file_name, tray, window) @@ -175,7 +177,7 @@ def tox_file_recv(window, tray): 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, + invoke_in_main_thread(file_transfer_handler.incoming_avatar, friend_number, file_number, size) @@ -222,55 +224,69 @@ def file_recv_control(file_transfer_handler): # ----------------------------------------------------------------------------------------------------------------- -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 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(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) +def lossy_packet(plugin_loader): + def wrapped(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) + + return wrapped # ----------------------------------------------------------------------------------------------------------------- # 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_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(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 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(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) +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 @@ -333,11 +349,6 @@ def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, u # ----------------------------------------------------------------------------------------------------------------- -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() @@ -350,72 +361,40 @@ def show_gc_notification(window, tray, message, group_number, peer_number): 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): +def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, + calls_manager, file_transfer_handler, 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_self_connection_status(self_connection_status(tox, profile), 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_friend_status(friend_status(profile, settings), 0) + tox.callback_friend_message(friend_message(profile, settings, window, tray), 0) + tox.callback_friend_connection_status(friend_connection_status(profile, settings, plugin_loader), 0) + tox.callback_friend_name(friend_name(profile), 0) + tox.callback_friend_status_message(friend_status_message(profile), 0) + tox.callback_friend_request(friend_request(contacts_manager), 0) + tox.callback_friend_typing(friend_typing(contacts_manager), 0) + tox.callback_friend_read_receipt(friend_read_receipt(contacts_manager), 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) + tox.callback_file_recv(tox_file_recv(window, tray, profile, file_transfer_handler, contacts_manager, settings), 0) + tox.callback_file_recv_chunk(file_recv_chunk(file_transfer_handler), 0) + tox.callback_file_chunk_request(file_chunk_request(file_transfer_handler), 0) + tox.callback_file_recv_control(file_recv_control(file_transfer_handler), 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_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) - tox.callback_friend_lossless_packet(lossless_packet, 0) - tox.callback_friend_lossy_packet(lossy_packet, 0) + tox.callback_friend_lossless_packet(lossless_packet(plugin_loader), 0) + tox.callback_friend_lossy_packet(lossy_packet(plugin_loader), 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) diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 9d33f19..eb69044 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -3,10 +3,9 @@ from PyQt5 import QtWidgets from contacts.friend import * from user_data.settings import * from wrapper.toxcore_enums_and_consts import * -from ctypes import * -from util import log, Singleton, curr_directory +from util.util import log, curr_directory from network.tox_dns import tox_dns -from db.database import * +from history.database import * from file_transfers.file_transfers import * import time from av import calls @@ -19,7 +18,7 @@ from contacts.group_chat import * import re -class Profile(basecontact.BaseContact, Singleton): +class Profile(basecontact.BaseContact): """ Profile of current toxygen user. Contains friends list, tox instance """ @@ -33,7 +32,6 @@ class Profile(basecontact.BaseContact, Singleton): tox.self_get_status_message(), screen.user_info, tox.self_get_address()) - Singleton.__init__(self) self._screen = screen self._messages = screen.messages self._tox = tox @@ -43,8 +41,6 @@ class Profile(basecontact.BaseContact, Singleton): self._factory = items_factory.ItemsFactory(self._screen.friends_list, self._messages) settings = Settings.get_instance() self._show_avatars = settings['show_avatars'] - self._paused_file_transfers = dict(settings['paused_file_transfers']) - # key - file id, value: [path, friend number, is incoming, start position] # ----------------------------------------------------------------------------------------------------------------- @@ -97,6 +93,7 @@ class Profile(basecontact.BaseContact, Singleton): def get_friend_by_number(self, num): return list(filter(lambda x: x.number == num and type(x) is Friend, self._contacts))[0] + def get_last_message(self): if self._active_friend + 1: return self.get_curr_friend().get_last_message_text() diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index 2706d97..6431fe4 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -9,6 +9,8 @@ class FileTransfersHandler: self._tox = tox self._settings = settings self._file_transfers = {} + self._paused_file_transfers = dict(settings['paused_file_transfers']) + # key - file id, value: [path, friend number, is incoming, start position] # ----------------------------------------------------------------------------------------------------------------- # File transfers support diff --git a/toxygen/main.py b/toxygen/main.py index e8a4694..42e8b76 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -1,9 +1,12 @@ import sys import app from user_data.settings import * -from util.util import curr_directory, program_version, remove +from util.util import curr_directory, remove import argparse +__maintainer__ = 'Ingvar' +__version__ = '0.5.0' + def clean(): """Removes all windows libs from libs folder""" @@ -15,30 +18,31 @@ def reset(): Settings.reset_auto_profile() +def print_toxygen_version(): + print('Toxygen v' + __version__) + + def main(): parser = argparse.ArgumentParser() - parser.add_argument('--version') - parser.add_argument('--clean') - parser.add_argument('--reset') + parser.add_argument('--version', help='Prints Toxygen version') + parser.add_argument('--clean', help='Deletes toxcore libs from libs folder') + parser.add_argument('--reset', help='Resets default profile') + parser.add_argument('profile_path', nargs='?', default=None, help='Resets default profile') args = parser.parse_args() - if not len(args): - toxygen = app.App() - else: # started with argument(s) - arg = sys.argv[1] - if arg == '--version': - print('Toxygen v' + program_version) - return - elif arg == '--help': - print('Usage:\ntoxygen path_to_profile\ntoxygen tox_id\ntoxygen --version\ntoxygen --reset') - return - elif arg == '--clean': - clean() - return - elif arg == '--reset': - reset() - return - else: - toxygen = app.App(arg) + + if args.version: + print_toxygen_version() + return + + if args.clean: + clean() + return + + if args.reset: + reset() + return + + toxygen = app.App(path_to_profile=args.profile_path) toxygen.main() diff --git a/toxygen/network/tox_dns.py b/toxygen/network/tox_dns.py index 36702ee..d3d48a1 100644 --- a/toxygen/network/tox_dns.py +++ b/toxygen/network/tox_dns.py @@ -1,6 +1,6 @@ import json import urllib.request -from util import log +from util.util import log from user_data import settings from PyQt5 import QtNetwork, QtCore diff --git a/toxygen/plugin_support/plugin_support.py b/toxygen/plugin_support/plugin_support.py index de39e5f..6f2af9c 100644 --- a/toxygen/plugin_support/plugin_support.py +++ b/toxygen/plugin_support/plugin_support.py @@ -1,4 +1,4 @@ -import util +import util.util as util from contacts import profile import os import importlib @@ -8,15 +8,15 @@ from user_data import toxes import sys -class PluginLoader(util.Singleton): +class PluginLoader(): - def __init__(self, tox, settings): + def __init__(self, tox, toxes, profile, settings): super().__init__() - self._profile = profile.Profile.get_instance() + self._profile = profile self._settings = settings self._plugins = {} # dict. key - plugin unique short name, value - tuple (plugin instance, is active) self._tox = tox - self._encr = toxes.ToxES.get_instance() + self._toxes = toxes def set_tox(self, tox): """ @@ -55,7 +55,7 @@ class PluginLoader(util.Singleton): if inspect.isclass(obj) and hasattr(obj, 'is_plugin') and obj.is_plugin: print('Plugin', elem) try: # create instance of plugin class - inst = obj(self._tox, self._profile, self._settings, self._encr) + inst = obj(self._tox, self._profile, self._settings, self._toxes) autostart = inst.get_short_name() in self._settings['plugins'] if autostart: inst.start() @@ -158,7 +158,7 @@ class PluginLoader(util.Singleton): try: result.extend(elem[0].get_message_menu(menu, selected_text)) except: - continue + pass return result def stop(self): diff --git a/toxygen/smileys_and_stickers.py b/toxygen/smileys_and_stickers.py index 7294185..dd72fd9 100644 --- a/toxygen/smileys_and_stickers.py +++ b/toxygen/smileys_and_stickers.py @@ -5,7 +5,7 @@ from collections import OrderedDict from PyQt5 import QtCore -class SmileyLoader(util.Singleton): +class SmileyLoader: """ Class which loads smileys packs and insert smileys into messages """ diff --git a/toxygen/threads.py b/toxygen/threads.py index f437d5a..4a129d1 100644 --- a/toxygen/threads.py +++ b/toxygen/threads.py @@ -1,11 +1,11 @@ from PyQt5 import QtCore -from communication.callbacks import init_callbacks from bootstrap.bootstrap import * import threading import queue from util import util + class InitThread(QtCore.QThread): def __init__(self, tox, ms, tray): diff --git a/toxygen/tray.py b/toxygen/tray.py index bc22ed4..0c474b4 100644 --- a/toxygen/tray.py +++ b/toxygen/tray.py @@ -3,6 +3,19 @@ from util.ui import tr from util.util import curr_directory +class SystemTrayIcon(QtWidgets.QSystemTrayIcon): + + leftClicked = QtCore.pyqtSignal() + + def __init__(self, icon, parent=None): + super().__init__(self, icon, parent) + self.activated.connect(self.iconActivated) + + def iconActivated(self, reason): + if reason == QtGui.QSystemTrayIcon.Trigger: + self.leftClicked.emit() + + class Menu(QtWidgets.QMenu): def __init__(self, settings, profile, *args): @@ -39,7 +52,7 @@ class Menu(QtWidgets.QMenu): def init_tray(profile, settings, main_screen): - tray = QtWidgets.QSystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) + tray = SystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) tray.setObjectName('tray') m = Menu(settings, profile) diff --git a/toxygen/ui/avwidgets.py b/toxygen/ui/avwidgets.py index 67bd04a..a783eca 100644 --- a/toxygen/ui/avwidgets.py +++ b/toxygen/ui/avwidgets.py @@ -5,7 +5,7 @@ import util import pyaudio import wave from user_data import settings -from util import curr_directory +from util.util import curr_directory class IncomingCallWidget(widgets.CenteredWidget): diff --git a/toxygen/ui/list_items.py b/toxygen/ui/list_items.py index 64b4a2e..76e6185 100644 --- a/toxygen/ui/list_items.py +++ b/toxygen/ui/list_items.py @@ -2,7 +2,7 @@ from wrapper.toxcore_enums_and_consts import * from PyQt5 import QtCore, QtGui, QtWidgets from contacts import profile from file_transfers.file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR -from util import curr_directory, convert_time, curr_time +from util.util import curr_directory, convert_time, curr_time from ui.widgets import DataLabel, create_menu import html as h import smileys @@ -60,8 +60,8 @@ class MessageEdit(QtWidgets.QTextBrowser): def quote_text(self): text = self.textCursor().selection().toPlainText() if text: - from ui import mainscreen - window = mainscreen.MainWindow.get_instance() + from ui import main_screen + window = main_screen.MainWindow.get_instance() text = '>' + '\n>'.join(text.split('\n')) if window.messageEdit.toPlainText(): text = '\n' + text diff --git a/toxygen/ui/loginscreen.py b/toxygen/ui/login_screen.py similarity index 69% rename from toxygen/ui/loginscreen.py rename to toxygen/ui/login_screen.py index cc1f45c..a5c91e1 100644 --- a/toxygen/ui/loginscreen.py +++ b/toxygen/ui/login_screen.py @@ -1,4 +1,5 @@ from ui.widgets import * +import os.path class NickEdit(LineEdit): @@ -14,12 +15,40 @@ class NickEdit(LineEdit): super(NickEdit, self).keyPressEvent(event) -class LoginScreen(CenteredWidget): +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): - super(LoginScreen, self).__init__() + CenteredWidget.__init__(self) + DialogWithResult.__init__(self) self.initUI() self.center() + self._profiles = [] def initUI(self): self.resize(400, 200) @@ -34,7 +63,7 @@ class LoginScreen(CenteredWidget): 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.load_profile.clicked.connect(self.load_existing_profile) self.default = QtWidgets.QCheckBox(self) self.default.setGeometry(QtCore.QRect(220, 110, 131, 22)) self.groupBox = QtWidgets.QGroupBox(self) @@ -44,6 +73,7 @@ class LoginScreen(CenteredWidget): self.groupBox_2 = QtWidgets.QGroupBox(self) self.groupBox_2.setGeometry(QtCore.QRect(10, 40, 191, 151)) self.toxygen = QtWidgets.QLabel(self) + self.toxygen.setGeometry(QtCore.QRect(160, 8, 90, 25)) self.groupBox.raise_() self.groupBox_2.raise_() self.comboBox.raise_() @@ -51,16 +81,11 @@ class LoginScreen(CenteredWidget): 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) @@ -80,19 +105,18 @@ class LoginScreen(CenteredWidget): 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 load_existing_profile(self): + index = self.comboBox.currentIndex() + load_as_default = self.default.isChecked() + path = os.path.join(self._profiles[index][0], self._profiles[index][1] + '.tox') + result = LoginScreenResult(path, load_as_default) + self.close_with_result(result) - 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_select(self, profiles): + profiles = sorted(profiles, key=lambda p: p[1]) + self._profiles = list(profiles) + self.comboBox.addItems(list(map(lambda p: p[1], profiles))) + self.load_profile.setEnabled(len(profiles) > 0) def update_on_close(self, func): self.onclose = func diff --git a/toxygen/ui/mainscreen.py b/toxygen/ui/main_screen.py similarity index 99% rename from toxygen/ui/mainscreen.py rename to toxygen/ui/main_screen.py index ce828b0..c2912d6 100644 --- a/toxygen/ui/mainscreen.py +++ b/toxygen/ui/main_screen.py @@ -3,7 +3,7 @@ from contacts.profile import * from ui.list_items import * from ui.widgets import MultilineEdit, ComboBox import plugin_support -from ui.mainscreen_widgets import * +from ui.main_screen_widgets import * from user_data import toxes, settings diff --git a/toxygen/ui/mainscreen_widgets.py b/toxygen/ui/main_screen_widgets.py similarity index 100% rename from toxygen/ui/mainscreen_widgets.py rename to toxygen/ui/main_screen_widgets.py diff --git a/toxygen/ui/passwordscreen.py b/toxygen/ui/password_screen.py similarity index 95% rename from toxygen/ui/passwordscreen.py rename to toxygen/ui/password_screen.py index 9d3d523..bd6c146 100644 --- a/toxygen/ui/passwordscreen.py +++ b/toxygen/ui/password_screen.py @@ -1,4 +1,4 @@ -from ui.widgets import CenteredWidget, LineEdit +from ui.widgets import CenteredWidget, LineEdit, DialogWithResult from PyQt5 import QtCore, QtWidgets @@ -16,10 +16,11 @@ class PasswordArea(LineEdit): super(PasswordArea, self).keyPressEvent(event) -class PasswordScreenBase(CenteredWidget): +class PasswordScreenBase(CenteredWidget, DialogWithResult): def __init__(self, encrypt): - super(PasswordScreenBase, self).__init__() + CenteredWidget.__init__(self) + DialogWithResult.__init__(self) self._encrypt = encrypt self.initUI() @@ -73,13 +74,12 @@ class PasswordScreen(PasswordScreenBase): if self.password.text(): try: 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: self.warning.setVisible(True) print('Decryption error:', ex) else: - self._data[0] = new_data - self.close() + self.close_with_result(new_data) class UnlockAppScreen(PasswordScreenBase): diff --git a/toxygen/ui/widgets.py b/toxygen/ui/widgets.py index b63deb0..7b811e7 100644 --- a/toxygen/ui/widgets.py +++ b/toxygen/ui/widgets.py @@ -32,6 +32,22 @@ class CenteredWidget(QtWidgets.QWidget): self.move(qr.topLeft()) +class DialogWithResult(QtWidgets.QWidget): + + def __init__(self, parent=None): + super().__init__(parent) + self._result = None + + def get_result(self): + return self._result + + result = property(get_result) + + def close_with_result(self, result): + self._result = result + self.close() + + class LineEdit(QtWidgets.QLineEdit): def __init__(self, parent=None): diff --git a/toxygen/user_data/profile_manager.py b/toxygen/user_data/profile_manager.py index 12ce185..a810105 100644 --- a/toxygen/user_data/profile_manager.py +++ b/toxygen/user_data/profile_manager.py @@ -1,11 +1,17 @@ +import util.util as util +import os +from user_data.settings import Settings + + class ProfileManager: """ Class with methods for search, load and save profiles """ - def __init__(self, path, name): - path = append_slash(path) - self._path = path + name + '.tox' - self._directory = path + def __init__(self, settings, toxes, path): + self._settings = settings + self._toxes = toxes + self._path = path + self._directory = os.path.basename(path) # create /avatars if not exists: directory = path + 'avatars' if not os.path.exists(directory): @@ -23,9 +29,8 @@ class ProfileManager: return self._directory def save_profile(self, data): - inst = ToxES.get_instance() - if inst.has_password(): - data = inst.pass_encrypt(data) + if self._toxes.has_password(): + data = self._toxes.pass_encrypt(data) with open(self._path, 'wb') as fl: fl.write(data) print('Profile saved successfully') @@ -37,11 +42,11 @@ class ProfileManager: with open(path, 'wb') as fout: fout.write(data) print('Profile exported successfully') - copy(self._directory + 'avatars', new_path + 'avatars') + util.copy(self._directory + 'avatars', new_path + 'avatars') if use_new_path: self._path = new_path + os.path.basename(self._path) self._directory = new_path - Settings.get_instance().update_path() + self._settings.update_path() @staticmethod def find_profiles(): @@ -57,7 +62,7 @@ class ProfileManager: if fl.endswith('.tox'): name = fl[:-4] result.append((path, name)) - path = curr_directory() + path = util.get_base_directory(__file__) # check current directory for fl in os.listdir(path): if fl.endswith('.tox'): diff --git a/toxygen/user_data/settings.py b/toxygen/user_data/settings.py index 45d41bd..f879e53 100644 --- a/toxygen/user_data/settings.py +++ b/toxygen/user_data/settings.py @@ -1,28 +1,26 @@ from platform import system import json import os -from util import Singleton, curr_directory, log, copy, append_slash +from util.util import log, curr_directory, append_slash import pyaudio from user_data.toxes import ToxES -import smileys +import smileys_and_stickers as smileys -class Settings(dict, Singleton): +class Settings(dict): """ Settings of current profile + global app settings """ - def __init__(self, name): - Singleton.__init__(self) - self.path = ProfileManager.get_path() + str(name) + '.json' - self.name = name - if os.path.isfile(self.path): - with open(self.path, 'rb') as fl: + def __init__(self, toxes, path): + self._path = path + self._toxes = toxes + if os.path.isfile(path): + with open(path, 'rb') as fl: data = fl.read() - inst = ToxES.get_instance() try: - if inst.is_data_encrypted(data): - data = inst.pass_decrypt(data) + if toxes.is_data_encrypted(data): + data = toxes.pass_decrypt(data) info = json.loads(str(data, 'utf-8')) except Exception as ex: info = Settings.get_default_settings() @@ -175,12 +173,11 @@ class Settings(dict, Singleton): def save(self): text = json.dumps(self) - inst = ToxES.get_instance() - if inst.has_password(): - text = bytes(inst.pass_encrypt(bytes(text, 'utf-8'))) + if self._toxes.has_password(): + text = bytes(self._toxes.pass_encrypt(bytes(text, 'utf-8'))) else: text = bytes(text, 'utf-8') - with open(self.path, 'wb') as fl: + with open(self._path, 'wb') as fl: fl.write(text) def close(self): diff --git a/toxygen/util/util.py b/toxygen/util/util.py index 33b4758..45bc345 100644 --- a/toxygen/util/util.py +++ b/toxygen/util/util.py @@ -3,9 +3,7 @@ import time import shutil import sys import re - - -program_version = '0.5.0' +import platform def cached(func): @@ -33,6 +31,18 @@ def curr_directory(current_file=None): return os.path.dirname(os.path.realpath(current_file or __file__)) +def get_base_directory(current_file=None): + return os.path.dirname(curr_directory()) + + +def get_images_directory(): + return os.path.join(get_base_directory(), 'images') + + +def get_styles_directory(): + return os.path.join(get_base_directory(), 'styles') + + def curr_time(): return time.strftime('%H:%M') @@ -95,12 +105,5 @@ def is_re_valid(regex): return True -class Singleton: - _instance = None - - def __init__(self): - self.__class__._instance = self - - @classmethod - def get_instance(cls): - return cls._instance +def get_platform(): + return platform.system() diff --git a/toxygen/wrapper/libtox.py b/toxygen/wrapper/libtox.py index 752798f..a04746c 100644 --- a/toxygen/wrapper/libtox.py +++ b/toxygen/wrapper/libtox.py @@ -1,6 +1,6 @@ from platform import system from ctypes import CDLL -import util +import util.util as util class LibToxCore: From 91d3f885c0fe0dabc74968ab0b962c26101ca090 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 16 Apr 2018 23:35:55 +0300 Subject: [PATCH 006/138] create profile screen, main screen opens now --- .gitignore | 1 - toxygen/app.py | 131 ++++++++++------------ toxygen/contacts/basecontact.py | 1 + toxygen/contacts/profile.py | 3 +- toxygen/main.py | 2 +- toxygen/ui/create_profile_screen.py | 43 +++++++ toxygen/ui/login_screen.py | 10 +- toxygen/ui/main_screen.py | 31 +++-- toxygen/ui/menu.py | 2 +- toxygen/ui/views/create_profile_screen.ui | 106 +++++++++++++++++ toxygen/updater/updater.py | 41 +++++-- toxygen/user_data/profile_manager.py | 3 - toxygen/user_data/settings.py | 11 +- toxygen/util/ui.py | 6 + toxygen/util/util.py | 13 ++- 15 files changed, 286 insertions(+), 118 deletions(-) create mode 100644 toxygen/ui/create_profile_screen.py create mode 100644 toxygen/ui/views/create_profile_screen.ui diff --git a/.gitignore b/.gitignore index 78c26f5..f31ca4c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ *.pyc *.pyo -*.ui toxygen/toxcore tests/tests tests/libs diff --git a/toxygen/app.py b/toxygen/app.py index b4c5d55..8a7c799 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -2,20 +2,24 @@ import communication.callbacks import threads from PyQt5 import QtWidgets, QtGui, QtCore import ui.password_screen as passwordscreen -from util.util import curr_directory, get_platform, get_images_directory, get_styles_directory, log +from util.util import * import updater.updater as updater import os from communication.tox_factory import tox_factory -import wrapper.libtox as libtox +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 class App: - def __init__(self, path_to_profile=None, uri=None): + def __init__(self, version, path_to_profile=None, uri=None): + self._version = version + self._app = None self.tox = self.ms = self.init = self.app = self.tray = self.mainloop = self.avloop = None self.uri = self.path = self.toxes = None if uri is not None and uri.startswith('tox:'): @@ -44,7 +48,7 @@ class App: app = QtWidgets.QApplication([]) icon_file = os.path.join(get_images_directory(), 'icon.png') app.setWindowIcon(QtGui.QIcon(icon_file)) - self.app = app + self._app = app if get_platform() == 'Linux': QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) @@ -53,17 +57,18 @@ class App: style = fl.read() app.setStyleSheet(style) - encrypt_save = libtox.LibToxEncryptSave() - toxes = user_data.toxes.ToxES(encrypt_save) + encrypt_save = tox_encrypt_save.ToxEncryptSave() + self._toxes = user_data.toxes.ToxES(encrypt_save) if self.path is not None: path = os.path.dirname(self.path) + '/' name = os.path.basename(self.path)[:-4] - data = ProfileManager(path, name).open_profile() + self._settings = Settings(self._toxes, self.path.replace('.tox', '.json')) + self._profile_manager = ProfileManager(self._settings, self._toxes, path) + data = self._profile_manager.open_profile() if encrypt_save.is_data_encrypted(data): data = self.enter_pass(data) - settings = Settings(name) - self.tox = profile.tox_factory(data, settings) + self.tox = tox_factory(data, self._settings) else: auto_profile = Settings.get_auto_profile() if not auto_profile[0]: @@ -87,7 +92,7 @@ class App: if result is None: return elif result.is_new_profile(): # create new profile - name = _login.name if _login.name else 'toxygen_user' + name = get_profile_name_from_path(result.profile_path) or 'toxygen_user' pr = map(lambda x: x[1], ProfileManager.find_profiles()) if name in list(pr): msgBox = QtWidgets.QMessageBox() @@ -98,8 +103,8 @@ class App: msgBox.setText(text) msgBox.exec_() return - self.tox = profile.tox_factory() - self.tox.self_set_name(bytes(_login.name, 'utf-8') if _login.name else b'Toxygen User') + self.tox = tox_factory() + self.tox.self_set_name(bytes(name, 'utf-8') if name else b'Toxygen User') self.tox.self_set_status_message(b'Toxing on Toxygen') reply = QtWidgets.QMessageBox.question(None, 'Profile {}'.format(name), @@ -133,28 +138,30 @@ class App: msgBox.exec_() return path = Settings.get_default_path() - settings = Settings(name) + self._settings = Settings() if curr_lang in langs: - settings['language'] = curr_lang - settings.save() + self._settings['language'] = curr_lang + self._settings.save() else: # load existing profile path = result.profile_path if result.load_as_default: Settings.set_auto_profile(path) - settings = Settings(toxes, path) - data = ProfileManager(settings, toxes, path).open_profile() - if toxes.is_data_encrypted(data): + self._settings = Settings(self._toxes, path.replace('.tox', '.json')) + self._profile_manager = ProfileManager(self._settings, self._toxes, path) + data = self._profile_manager.open_profile() + if self._toxes.is_data_encrypted(data): data = self.enter_pass(data) - self.tox = communication.tox_factory.tox_factory(data, settings) + self._tox = tox_factory(data, self._settings) else: path, name = auto_profile - data = ProfileManager(path, name).open_profile() + self._settings = Settings(self._toxes, path + name + '.json') + self._profile_manager = ProfileManager(self._settings, self._toxes, path) + data = self._profile_manager.open_profile() if encrypt_save.is_data_encrypted(data): data = self.enter_pass(data) - settings = Settings(name) - self.tox = profile.tox_factory(data, settings) + self.tox = tox_factory(data, self._settings) - if Settings.is_active_profile(path, name): # profile is in use + if Settings.is_active_profile(path, get_profile_name_from_path(path)): # profile is in use reply = QtWidgets.QMessageBox.question(None, 'Profile {}'.format(name), QtWidgets.QApplication.translate("login", 'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?'), @@ -163,63 +170,36 @@ class App: if reply != QtWidgets.QMessageBox.Yes: return else: - settings.set_active_profile() + self._settings.set_active_profile() - # application color scheme - for theme in settings.built_in_themes().keys(): - if settings['theme'] == theme: - with open(curr_directory() + settings.built_in_themes()[theme]) as fl: - style = fl.read() - app.setStyleSheet(style) - - lang = Settings.supported_languages()[settings['language']] - translator = QtCore.QTranslator() - translator.load(curr_directory() + '/translations/' + lang) - app.installTranslator(translator) - app.translator = translator + self.load_app_styles() + self.load_app_translations() # tray icon - + self.ms = MainWindow(self._settings, self._tox, self.reset, self.tray) + self._profile = self.ms.profile self.ms.show() - updating = False - if settings['update'] and updater.updater_available() and updater.connection_available(): # auto update - version = updater.check_for_updates() - if version is not None: - if settings['update'] == 2: - updater.download(version) - updating = True - else: - reply = QtWidgets.QMessageBox.question(None, - 'Toxygen', - QtWidgets.QApplication.translate("login", - 'Update for Toxygen was found. Download and install it?'), - QtWidgets.QMessageBox.Yes, - QtWidgets.QMessageBox.No) - if reply == QtWidgets.QMessageBox.Yes: - updater.download(version) - updating = True - + updating = updater.start_update_if_needed(self._version, self._settings) if updating: data = self.tox.get_savedata() - ProfileManager.get_instance().save_profile(data) - settings.close() + self._profile_manager.save_profile(data) + self._settings.close() del self.tox return - plugin_helper = PluginLoader(self.tox, settings) # plugin support + plugin_helper = PluginLoader(self._tox, self._toxes, self._profile, self._settings) # plugin support plugin_helper.load() - start() # init thread - self.init = self.InitThread(self.tox, self.ms, self.tray) + self.init = threads.InitThread(self.tox, self.ms, self.tray) self.init.start() # starting threads for tox iterate and toxav iterate - self.mainloop = self.ToxIterateThread(self.tox) + self.mainloop = threads.ToxIterateThread(self._tox) self.mainloop.start() - self.avloop = self.ToxAVIterateThread(self.tox.AV) + self.avloop = threads.ToxAVIterateThread(self._tox.AV) self.avloop.start() if self.uri is not None: @@ -232,14 +212,13 @@ class App: self.mainloop.stop = True self.avloop.stop = True plugin_helper.stop() - stop() self.mainloop.wait() self.init.wait() self.avloop.wait() self.tray.hide() data = self.tox.get_savedata() - ProfileManager.get_instance().save_profile(data) - settings.close() + self._profile_manager.save_profile(data) + self._settings.close() del self.tox def reset(self): @@ -254,10 +233,10 @@ class App: self.init.wait() self.avloop.wait() data = self.tox.get_savedata() - ProfileManager.get_instance().save_profile(data) + self._profile_manager.save_profile(data) del self.tox # create new tox instance - self.tox = profile.tox_factory(data, Settings.get_instance()) + self.tox = tox_factory(data, self._settings) # init thread self.init = threads.InitThread(self.tox, self.ms, self.tray) self.init.start() @@ -269,7 +248,21 @@ class App: self.avloop = threads.ToxAVIterateThread(self.tox.AV) self.avloop.start() - plugin_helper = PluginLoader.get_instance() - plugin_helper.set_tox(self.tox) + self._plugin_loader.set_tox(self.tox) return self.tox + + def load_app_styles(self): + # application color scheme + for theme in self._settings.built_in_themes().keys(): + if self._settings['theme'] == theme: + with open(curr_directory(__file__) + self._settings.built_in_themes()[theme]) as fl: + style = fl.read() + self._app.setStyleSheet(style) + + def load_app_translations(self): + lang = Settings.supported_languages()[self._settings['language']] + translator = QtCore.QTranslator() + translator.load(curr_directory(__file__) + '/translations/' + lang) + self._app.installTranslator(translator) + self._app.translator = translator diff --git a/toxygen/contacts/basecontact.py b/toxygen/contacts/basecontact.py index 6e2d55a..34c100c 100644 --- a/toxygen/contacts/basecontact.py +++ b/toxygen/contacts/basecontact.py @@ -81,6 +81,7 @@ class BaseContact: """ Tries to load avatar of contact or uses default avatar """ + return prefix = ProfileManager.get_path() + 'avatars/' 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 diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index eb69044..ae7767a 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -39,8 +39,7 @@ class Profile(basecontact.BaseContact): self._load_history = True self._waiting_for_reconnection = False self._factory = items_factory.ItemsFactory(self._screen.friends_list, self._messages) - settings = Settings.get_instance() - self._show_avatars = settings['show_avatars'] + #self._show_avatars = settings['show_avatars'] # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/main.py b/toxygen/main.py index 42e8b76..4230668 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -42,7 +42,7 @@ def main(): reset() return - toxygen = app.App(path_to_profile=args.profile_path) + toxygen = app.App(__version__, path_to_profile=args.profile_path) toxygen.main() diff --git a/toxygen/ui/create_profile_screen.py b/toxygen/ui/create_profile_screen.py new file mode 100644 index 0000000..f461719 --- /dev/null +++ b/toxygen/ui/create_profile_screen.py @@ -0,0 +1,43 @@ +from ui.widgets import * +from PyQt5 import uic +import util.util as util +import util.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.center() + self.createProfile.clicked.connect(self.create_profile) + + def retranslateUi(self): + self.defaultFolder.setPlaceholderText(util_ui.tr('Save in default folder')) + self.programFolder.setPlaceholderText(util_ui.tr('Save in program folder')) + self.createProfile.setText(util_ui.tr('Create profile')) + self.passwordLabel.setText(util_ui.tr('Password:')) + + def create_profile(self): + if self.password.text() != self.confirmPassword.text(): + return # TODO : error + result = CreateProfileScreenResult(self.defaultFolder.isChecked(), self.password.text()) + self.close_with_result(result) diff --git a/toxygen/ui/login_screen.py b/toxygen/ui/login_screen.py index a5c91e1..bbafef9 100644 --- a/toxygen/ui/login_screen.py +++ b/toxygen/ui/login_screen.py @@ -5,7 +5,7 @@ import os.path class NickEdit(LineEdit): def __init__(self, parent): - super(NickEdit, self).__init__(parent) + super().__init__(parent) self.parent = parent def keyPressEvent(self, event): @@ -108,7 +108,7 @@ class LoginScreen(CenteredWidget, DialogWithResult): def load_existing_profile(self): index = self.comboBox.currentIndex() load_as_default = self.default.isChecked() - path = os.path.join(self._profiles[index][0], self._profiles[index][1] + '.tox') + path = os.path.join(self._profiles[index][0], self._profiles[index][1] + '.tox') result = LoginScreenResult(path, load_as_default) self.close_with_result(result) @@ -118,9 +118,3 @@ class LoginScreen(CenteredWidget, DialogWithResult): self.comboBox.addItems(list(map(lambda p: p[1], profiles))) self.load_profile.setEnabled(len(profiles) > 0) - 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() diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index c2912d6..d3896be 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -5,19 +5,20 @@ from ui.widgets import MultilineEdit, ComboBox import plugin_support from ui.main_screen_widgets import * from user_data import toxes, settings +import util.util as util -class MainWindow(QtWidgets.QMainWindow, Singleton): +class MainWindow(QtWidgets.QMainWindow): - def __init__(self, tox, reset, tray): + def __init__(self, settings, tox, reset, tray): super().__init__() - Singleton.__init__(self) + self._settings = settings self.reset = reset self.tray = tray self.setAcceptDrops(True) self.initUI(tox) self._saved = False - if settings.Settings.get_instance()['show_welcome_screen']: + if settings['show_welcome_screen']: self.ws = WelcomeScreen() def setup_menu(self, window): @@ -137,7 +138,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): 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'] + ind = self._settings['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")) @@ -150,7 +151,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): self.messageEdit.setObjectName("messageEdit") font = QtGui.QFont() font.setPointSize(11) - font.setFamily(settings.Settings.get_instance()['font']) + font.setFamily(self._settings['font']) self.messageEdit.setFont(font) self.sendMessageButton = QtWidgets.QPushButton(Form) @@ -207,7 +208,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): 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.setFamily(self._settings['font']) font.setPointSize(14) font.setBold(True) Form.name.setFont(font) @@ -235,7 +236,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): 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.setFamily(self._settings['font']) font.setPointSize(14) font.setBold(True) self.account_name.setFont(font) @@ -297,10 +298,9 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): def initUI(self, tox): self.setMinimumSize(920, 500) - s = Settings.get_instance() + s = self._settings 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() @@ -317,7 +317,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): self.setup_right_bottom(message_buttons) self.setup_left_center(main_list) self.setup_menu(menu) - if not Settings.get_instance()['mirror_mode']: + if not self._settings['mirror_mode']: grid.addWidget(search, 2, 0) grid.addWidget(name, 1, 0) grid.addWidget(messages, 2, 1, 2, 1) @@ -391,7 +391,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): 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() + #self.profile.update() def keyPressEvent(self, event): if event.key() == QtCore.Qt.Key_Escape and QtWidgets.QSystemTrayIcon.isSystemTrayAvailable(): @@ -561,14 +561,13 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): 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)) + 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(curr_directory() + '/images/{}_video.png'.format(state)) + 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)) @@ -732,7 +731,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): def show(self): super().show() - self.profile.update() + #self.profile.update() def filtering(self): ind = self.online_contacts.currentIndex() diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index 606c545..1aa12dc 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -1,7 +1,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets from user_data.settings import * from contacts.profile import Profile -from util import curr_directory, copy +from util.util import curr_directory, copy from ui.widgets import CenteredWidget, DataLabel, LineEdit, RubberBandWindow import pyaudio from user_data import toxes diff --git a/toxygen/ui/views/create_profile_screen.ui b/toxygen/ui/views/create_profile_screen.ui new file mode 100644 index 0000000..f9d3241 --- /dev/null +++ b/toxygen/ui/views/create_profile_screen.ui @@ -0,0 +1,106 @@ + + + Form + + + + 0 + 0 + 400 + 300 + + + + + 400 + 300 + + + + + 400 + 300 + + + + Form + + + + + 30 + 240 + 341 + 51 + + + + PushButton + + + + + + 30 + 190 + 341 + 41 + + + + + + + 30 + 140 + 341 + 41 + + + + + + + 30 + 100 + 67 + 17 + + + + TextLabel + + + + + + 30 + 10 + 112 + 23 + + + + RadioButton + + + true + + + + + + 30 + 50 + 112 + 23 + + + + RadioButton + + + + + + diff --git a/toxygen/updater/updater.py b/toxygen/updater/updater.py index af80624..575dbfe 100644 --- a/toxygen/updater/updater.py +++ b/toxygen/updater/updater.py @@ -1,4 +1,5 @@ -import util +import util.util as util +import util.ui as util_ui import os from user_data import settings import platform @@ -24,12 +25,11 @@ def updater_available(): return os.path.exists(util.curr_directory() + '/toxygen_updater') -def check_for_updates(): - current_version = util.program_version +def check_for_updates(current_version, settings): major, minor, patch = list(map(lambda x: int(x), current_version.split('.'))) versions = generate_versions(major, minor, patch) for version in versions: - if send_request(version): + if send_request(version, settings): return version return None # no new version was found @@ -79,14 +79,13 @@ def download(version): util.log('Exception: running updater failed with ' + str(ex)) -def send_request(version): - s = settings.Settings.get_instance() +def send_request(version, settings): netman = QtNetwork.QNetworkAccessManager() proxy = QtNetwork.QNetworkProxy() - if s['proxy_type']: - proxy.setType(QtNetwork.QNetworkProxy.Socks5Proxy if s['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy) - proxy.setHostName(s['proxy_host']) - proxy.setPort(s['proxy_port']) + if settings['proxy_type']: + proxy.setType(QtNetwork.QNetworkProxy.Socks5Proxy if settings['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy) + proxy.setHostName(settings['proxy_host']) + proxy.setPort(settings['proxy_port']) netman.setProxy(proxy) url = test_url(version) try: @@ -108,3 +107,25 @@ def generate_versions(major, minor, patch): new_minor = '.'.join([str(major), str(minor + 1), '0']) new_patch = '.'.join([str(major), str(minor), str(patch + 1)]) return new_major, new_minor, new_patch + + +def start_update_if_needed(version, settings): + updating = False + if settings['update'] and updater_available() and connection_available(): # auto update + version = check_for_updates(version, settings) + if version is not None: + if settings['update'] == 2: + download(version) + updating = True + else: + reply = QtWidgets.QMessageBox.question(None, + 'Toxygen', + QtWidgets.QApplication.translate("login", + 'Update for Toxygen was found. Download and install it?'), + QtWidgets.QMessageBox.Yes, + QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.Yes: + download(version) + updating = True + + return updating diff --git a/toxygen/user_data/profile_manager.py b/toxygen/user_data/profile_manager.py index a810105..b897fc3 100644 --- a/toxygen/user_data/profile_manager.py +++ b/toxygen/user_data/profile_manager.py @@ -70,6 +70,3 @@ class ProfileManager: result.append((path + '/', name)) return result - @staticmethod - def get_path(): - return ProfileManager.get_instance().get_dir() diff --git a/toxygen/user_data/settings.py b/toxygen/user_data/settings.py index f879e53..39fd333 100644 --- a/toxygen/user_data/settings.py +++ b/toxygen/user_data/settings.py @@ -14,6 +14,7 @@ class Settings(dict): def __init__(self, toxes, path): self._path = path + self._profile_path = path.replace('.json', '.tox') self._toxes = toxes if os.path.isfile(path): with open(path, 'rb') as fl: @@ -25,10 +26,10 @@ class Settings(dict): except Exception as ex: info = Settings.get_default_settings() log('Parsing settings error: ' + str(ex)) - super(Settings, self).__init__(info) + super().__init__(info) self.upgrade() else: - super(Settings, self).__init__(Settings.get_default_settings()) + super().__init__(Settings.get_default_settings()) self.save() smileys.SmileyLoader(self) self.locked = False @@ -181,8 +182,7 @@ class Settings(dict): fl.write(text) def close(self): - profile_path = ProfileManager.get_path() - path = str(profile_path + str(self.name) + '.lock') + path = self._profile_path + '.lock' if os.path.isfile(path): os.remove(path) @@ -190,8 +190,7 @@ class Settings(dict): """ Mark current profile as active """ - profile_path = ProfileManager.get_path() - path = str(profile_path + str(self.name) + '.lock') + path = self._profile_path + '.lock' with open(path, 'w') as fl: fl.write('active') diff --git a/toxygen/util/ui.py b/toxygen/util/ui.py index a0d950d..be613cb 100644 --- a/toxygen/util/ui.py +++ b/toxygen/util/ui.py @@ -5,4 +5,10 @@ def tr(s): return PyQt5.QtWidgets.QApplication.translate('Toxygen', s) +def question(text): + reply = PyQt5.QtWidgets.QMessageBox.question(None, 'Toxygen', text, + PyQt5.QtWidgets.QMessageBox.Yes, + PyQt5.QtWidgets.QMessageBox.No) + return reply == PyQt5.QtWidgets.QMessageBox.Yes + # TODO: move all dialogs here diff --git a/toxygen/util/util.py b/toxygen/util/util.py index 45bc345..91a4aec 100644 --- a/toxygen/util/util.py +++ b/toxygen/util/util.py @@ -32,7 +32,7 @@ def curr_directory(current_file=None): def get_base_directory(current_file=None): - return os.path.dirname(curr_directory()) + return os.path.dirname(curr_directory(current_file or __file__)) def get_images_directory(): @@ -43,6 +43,17 @@ def get_styles_directory(): return os.path.join(get_base_directory(), 'styles') +def get_profile_name_from_path(path): + return os.path.basename(path)[:-4] + + +def get_views_path(view_name): + ui_folder = os.path.join(get_base_directory(), 'ui') + views_folder = os.path.join(ui_folder, 'views') + + return os.path.join(views_folder, view_name + '.ui') + + def curr_time(): return time.strftime('%H:%M') From 8a2665ed4d9ddeabdc80c1b23191c27bc9c6d70f Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 17 Apr 2018 15:14:05 +0300 Subject: [PATCH 007/138] refactoring - app.py, files moved to different folders --- setup.py | 4 +- tests/tests.py | 2 + toxygen/app.py | 214 ++++++++---------- toxygen/contacts/profile.py | 2 +- toxygen/login.py | 19 -- toxygen/main.py | 1 + .../{communication => middleware}/__init__.py | 0 .../callbacks.py | 3 +- toxygen/{ => middleware}/threads.py | 66 +++--- .../tox_factory.py | 0 toxygen/notifications.py | 71 ------ toxygen/notifications/__init__.py | 0 toxygen/notifications/sound.py | 54 +++++ toxygen/notifications/tray.py | 22 ++ toxygen/smileys/__init__.py | 0 .../smileys.py} | 16 +- toxygen/stickers/__init__.py | 0 toxygen/stickers/stickers.py | 18 ++ toxygen/ui/{avwidgets.py => av_widgets.py} | 0 toxygen/ui/main_screen.py | 11 +- toxygen/ui/main_screen_widgets.py | 4 +- toxygen/{ => ui}/tray.py | 34 +-- toxygen/updater/updater.py | 10 +- toxygen/user_data/settings.py | 2 +- toxygen/util/ui.py | 11 +- toxygen/util/util.py | 25 +- 26 files changed, 287 insertions(+), 302 deletions(-) delete mode 100644 toxygen/login.py rename toxygen/{communication => middleware}/__init__.py (100%) rename toxygen/{communication => middleware}/callbacks.py (99%) rename toxygen/{ => middleware}/threads.py (71%) rename toxygen/{communication => middleware}/tox_factory.py (100%) delete mode 100644 toxygen/notifications.py create mode 100644 toxygen/notifications/__init__.py create mode 100644 toxygen/notifications/sound.py create mode 100644 toxygen/notifications/tray.py create mode 100644 toxygen/smileys/__init__.py rename toxygen/{smileys_and_stickers.py => smileys/smileys.py} (87%) create mode 100644 toxygen/stickers/__init__.py create mode 100644 toxygen/stickers/stickers.py rename toxygen/ui/{avwidgets.py => av_widgets.py} (100%) rename toxygen/{ => ui}/tray.py (79%) diff --git a/setup.py b/setup.py index 746163e..f586ec3 100644 --- a/setup.py +++ b/setup.py @@ -2,11 +2,11 @@ from setuptools import setup from setuptools.command.install import install from platform import system from subprocess import call -from toxygen.util import program_version +import main import sys -version = program_version + '.0' +version = main.__version__ + '.0' if system() == 'Windows': diff --git a/tests/tests.py b/tests/tests.py index b21854c..b61553e 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -8,6 +8,8 @@ import toxygen.util as util import time +# TODO: fic + class TestTox: def test_creation(self): diff --git a/toxygen/app.py b/toxygen/app.py index 8a7c799..3668c4b 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -1,11 +1,10 @@ -import communication.callbacks -import threads +from middleware import threads from PyQt5 import QtWidgets, QtGui, QtCore import ui.password_screen as passwordscreen from util.util import * import updater.updater as updater import os -from communication.tox_factory import tox_factory +from middleware.tox_factory import tox_factory import wrapper.toxencryptsave as tox_encrypt_save import user_data.toxes from user_data.settings import Settings @@ -13,6 +12,8 @@ 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 util.ui as util_ui class App: @@ -20,21 +21,20 @@ class App: def __init__(self, version, path_to_profile=None, uri=None): self._version = version self._app = None - self.tox = self.ms = self.init = self.app = self.tray = self.mainloop = self.avloop = None - self.uri = self.path = self.toxes = None + self._tox = self._ms = self._init = self._app = self.tray = self._main_loop = self._av_loop = None + self.uri = self._toxes = self._tray = None if uri is not None and uri.startswith('tox:'): self.uri = uri[4:] - if path_to_profile is not None: - self.path = path_to_profile + self._path = path_to_profile def enter_pass(self, data): """ Show password screen """ - p = passwordscreen.PasswordScreen(self.toxes, data) + p = passwordscreen.PasswordScreen(self._toxes, data) p.show() - self.app.lastWindowClosed.connect(self.app.quit) - self.app.exec_() + self._app.lastWindowClosed.connect(self._app.quit) + self._app.exec_() result = p.result if result is None: raise SystemExit() @@ -45,30 +45,29 @@ class App: """ Main function of app. loads login screen if needed and starts main screen """ - app = QtWidgets.QApplication([]) + self._app= QtWidgets.QApplication([]) icon_file = os.path.join(get_images_directory(), 'icon.png') - app.setWindowIcon(QtGui.QIcon(icon_file)) - self._app = app + self._app.setWindowIcon(QtGui.QIcon(icon_file)) if get_platform() == 'Linux': QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) with open(os.path.join(get_styles_directory(), 'dark_style.qss')) as fl: style = fl.read() - app.setStyleSheet(style) + self._app.setStyleSheet(style) encrypt_save = tox_encrypt_save.ToxEncryptSave() self._toxes = user_data.toxes.ToxES(encrypt_save) - if self.path is not None: - path = os.path.dirname(self.path) + '/' - name = os.path.basename(self.path)[:-4] - self._settings = Settings(self._toxes, self.path.replace('.tox', '.json')) + if self._path is not None: + path = os.path.dirname(self._path) + '/' + name = os.path.basename(self._path)[:-4] + self._settings = Settings(self._toxes, self._path.replace('.tox', '.json')) self._profile_manager = ProfileManager(self._settings, self._toxes, path) data = self._profile_manager.open_profile() if encrypt_save.is_data_encrypted(data): data = self.enter_pass(data) - self.tox = tox_factory(data, self._settings) + self._tox = self.create_tox(data) else: auto_profile = Settings.get_auto_profile() if not auto_profile[0]: @@ -79,15 +78,15 @@ class App: if curr_lang in langs: lang_path = langs[curr_lang] translator = QtCore.QTranslator() - translator.load(curr_directory() + '/translations/' + lang_path) - app.installTranslator(translator) - app.translator = translator + translator.load(get_translations_directory() + lang_path) + self._app.installTranslator(translator) + self._app.translator = translator ls = LoginScreen() ls.setWindowIconText("Toxygen") profiles = ProfileManager.find_profiles() ls.update_select(profiles) ls.show() - app.exec_() + self._app.exec_() result = ls.result if result is None: return @@ -95,47 +94,22 @@ class App: name = get_profile_name_from_path(result.profile_path) or 'toxygen_user' pr = map(lambda x: x[1], ProfileManager.find_profiles()) if name in list(pr): - msgBox = QtWidgets.QMessageBox() - msgBox.setWindowTitle( - QtWidgets.QApplication.translate("MainWindow", "Error")) - text = (QtWidgets.QApplication.translate("MainWindow", - 'Profile with this name already exists')) - msgBox.setText(text) - msgBox.exec_() + util_ui.message_box(util_ui.tr('Profile with this name already exists'), + util_ui.tr('Error')) return - self.tox = tox_factory() - self.tox.self_set_name(bytes(name, 'utf-8') if name else b'Toxygen User') - self.tox.self_set_status_message(b'Toxing on Toxygen') - reply = QtWidgets.QMessageBox.question(None, - 'Profile {}'.format(name), - QtWidgets.QApplication.translate("login", - 'Do you want to set profile password?'), - QtWidgets.QMessageBox.Yes, - QtWidgets.QMessageBox.No) - if reply == QtWidgets.QMessageBox.Yes: - set_pass = SetProfilePasswordScreen(encrypt_save) - set_pass.show() - self.app.lastWindowClosed.connect(self.app.quit) - self.app.exec_() - reply = QtWidgets.QMessageBox.question(None, - 'Profile {}'.format(name), - QtWidgets.QApplication.translate("login", - 'Do you want to save profile in default folder? If no, profile will be saved in program folder'), - QtWidgets.QMessageBox.Yes, - QtWidgets.QMessageBox.No) - if reply == QtWidgets.QMessageBox.Yes: - path = Settings.get_default_path() - else: - path = curr_directory() + '/' + self._tox = tox_factory() + self._tox.self_set_name(bytes(name, 'utf-8') if name else b'Toxygen User') + self._tox.self_set_status_message(b'Toxing on Toxygen') + # TODO: set profile password + path = result.profile_path + self._profile_manager = ProfileManager(self._toxes, path) try: - ProfileManager(path, name).save_profile(self.tox.get_savedata()) + self._profile_manager.save_profile(self._tox.get_savedata()) except Exception as ex: print(str(ex)) log('Profile creation exception: ' + str(ex)) - msgBox = QtWidgets.QMessageBox() - msgBox.setText(QtWidgets.QApplication.translate("login", - 'Profile saving error! Does Toxygen have permission to write to this directory?')) - msgBox.exec_() + 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 path = Settings.get_default_path() self._settings = Settings() @@ -151,7 +125,7 @@ class App: data = self._profile_manager.open_profile() if self._toxes.is_data_encrypted(data): data = self.enter_pass(data) - self._tox = tox_factory(data, self._settings) + self._tox = self.create_tox(data) else: path, name = auto_profile self._settings = Settings(self._toxes, path + name + '.json') @@ -159,15 +133,13 @@ class App: data = self._profile_manager.open_profile() if encrypt_save.is_data_encrypted(data): data = self.enter_pass(data) - self.tox = tox_factory(data, self._settings) + self.tox = self.create_tox(data) if Settings.is_active_profile(path, get_profile_name_from_path(path)): # profile is in use - reply = QtWidgets.QMessageBox.question(None, - 'Profile {}'.format(name), - QtWidgets.QApplication.translate("login", 'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?'), - QtWidgets.QMessageBox.Yes, - QtWidgets.QMessageBox.No) - if reply != QtWidgets.QMessageBox.Yes: + title = util_ui.tr('Profile {}').format(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 else: self._settings.set_active_profile() @@ -175,82 +147,49 @@ class App: self.load_app_styles() self.load_app_translations() - # tray icon - - self.ms = MainWindow(self._settings, self._tox, self.reset, self.tray) - self._profile = self.ms.profile - self.ms.show() - - updating = updater.start_update_if_needed(self._version, self._settings) - if updating: - data = self.tox.get_savedata() - self._profile_manager.save_profile(data) - self._settings.close() - del self.tox + if self.try_to_update(): return - plugin_helper = PluginLoader(self._tox, self._toxes, self._profile, self._settings) # plugin support - plugin_helper.load() + self._ms = MainWindow(self._settings, self._tox, self.reset, self._tray) + self._profile = self._ms.profile + self._ms.show() - # init thread - self.init = threads.InitThread(self.tox, self.ms, self.tray) - self.init.start() + self._tray = tray.init_tray(self._profile, self._settings, self._ms) + self._tray.show() - # starting threads for tox iterate and toxav iterate - self.mainloop = threads.ToxIterateThread(self._tox) - self.mainloop.start() - self.avloop = threads.ToxAVIterateThread(self._tox.AV) - self.avloop.start() + self._plugin_loader = PluginLoader(self._tox, self._toxes, self._profile, self._settings) # plugins support + self._plugin_loader.load() # TODO; move to separate thread? if self.uri is not None: - self.ms.add_contact(self.uri) + self._ms.add_contact(self.uri) - app.lastWindowClosed.connect(app.quit) - app.exec_() + self._app.lastWindowClosed.connect(self._app.quit) + self._app.exec_() - self.init.stop = True - self.mainloop.stop = True - self.avloop.stop = True - plugin_helper.stop() - self.mainloop.wait() - self.init.wait() - self.avloop.wait() - self.tray.hide() - data = self.tox.get_savedata() + self._plugin_loader.stop() + self.stop_threads() + self._tray.hide() + data = self._tox.get_savedata() self._profile_manager.save_profile(data) self._settings.close() - del self.tox + del self._tox def reset(self): """ Create new tox instance (new network settings) :return: tox instance """ - self.mainloop.stop = True - self.init.stop = True - self.avloop.stop = True - self.mainloop.wait() - self.init.wait() - self.avloop.wait() - data = self.tox.get_savedata() + self.stop_threads() + data = self._tox.get_savedata() self._profile_manager.save_profile(data) - del self.tox + del self._tox # create new tox instance - self.tox = tox_factory(data, self._settings) - # init thread - self.init = threads.InitThread(self.tox, self.ms, self.tray) - self.init.start() + self._tox = tox_factory(data, self._settings) + self.start_threads() - # starting threads for tox iterate and toxav iterate - self.mainloop = threads.ToxIterateThread(self.tox) - self.mainloop.start() + self._plugin_loader.set_tox(self._tox) - self.avloop = threads.ToxAVIterateThread(self.tox.AV) - self.avloop.start() - - self._plugin_loader.set_tox(self.tox) - - return self.tox + return self._tox def load_app_styles(self): # application color scheme @@ -266,3 +205,32 @@ class App: translator.load(curr_directory(__file__) + '/translations/' + lang) self._app.installTranslator(translator) self._app.translator = translator + + def try_to_update(self): + updating = updater.start_update_if_needed(self._version, self._settings) + if updating: + data = self._tox.get_savedata() + self._profile_manager.save_profile(data) + self._settings.close() + del self._tox + return updating + + def start_threads(self): + # init thread + self._init = threads.InitThread(self._tox, self._ms, self._tray) + 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() + + def stop_threads(self): + self._init.stop_thread() + + self._main_loop.stop_thread() + self._av_loop.stop_thread() + + def create_tox(self, data): + return tox_factory(data, self._settings) diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index ae7767a..ef26a51 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -11,7 +11,7 @@ import time from av import calls import plugin_support from contacts import basecontact -from ui import items_factory, avwidgets +from ui import items_factory, av_widgets import cv2 import threading from contacts.group_chat import * diff --git a/toxygen/login.py b/toxygen/login.py deleted file mode 100644 index d68629a..0000000 --- a/toxygen/login.py +++ /dev/null @@ -1,19 +0,0 @@ -class Login: - - def __init__(self, arr): - self.arr = arr - - def login_screen_close(self, t, number=-1, default=False, name=None): - """ Function which processes data from login screen - :param t: 0 - window was closed, 1 - new profile was created, 2 - profile loaded - :param number: num of chosen profile in list (-1 by default) - :param default: was or not chosen profile marked as default - :param name: name of new profile - """ - self.t = t - self.num = number - self.default = default - self.name = name - - def get_data(self): - return self.arr[self.num] \ No newline at end of file diff --git a/toxygen/main.py b/toxygen/main.py index 4230668..97a5363 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -4,6 +4,7 @@ from user_data.settings import * from util.util import curr_directory, remove import argparse + __maintainer__ = 'Ingvar' __version__ = '0.5.0' diff --git a/toxygen/communication/__init__.py b/toxygen/middleware/__init__.py similarity index 100% rename from toxygen/communication/__init__.py rename to toxygen/middleware/__init__.py diff --git a/toxygen/communication/callbacks.py b/toxygen/middleware/callbacks.py similarity index 99% rename from toxygen/communication/callbacks.py rename to toxygen/middleware/callbacks.py index 0566325..4db161e 100644 --- a/toxygen/communication/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -1,5 +1,4 @@ from PyQt5 import QtGui -from notifications import * from user_data.settings import Settings from contacts.profile import Profile from wrapper.toxcore_enums_and_consts import * @@ -8,7 +7,7 @@ from wrapper.tox import bin_to_string from plugin_support.plugin_support import PluginLoader import cv2 import numpy as np -from threads import invoke_in_main_thread, execute +from middleware.threads import invoke_in_main_thread, execute # TODO: use closures diff --git a/toxygen/threads.py b/toxygen/middleware/threads.py similarity index 71% rename from toxygen/threads.py rename to toxygen/middleware/threads.py index 4a129d1..eb5a9d7 100644 --- a/toxygen/threads.py +++ b/toxygen/middleware/threads.py @@ -1,17 +1,26 @@ -from PyQt5 import QtCore from bootstrap.bootstrap import * import threading import queue from util import util +import time +class BaseThread(threading.Thread): -class InitThread(QtCore.QThread): + def __init__(self): + super().__init__() + self._stop = False + + def stop_thread(self): + self._stop = True + self.join() + + +class InitThread(BaseThread): def __init__(self, tox, ms, tray): - QtCore.QThread.__init__(self) + super().__init__() self.tox, self.ms, self.tray = tox, ms, tray - self.stop = False def run(self): # initializing callbacks @@ -21,71 +30,65 @@ class InitThread(QtCore.QThread): # bootstrap try: for data in generate_nodes(): - if self.stop: + if self._stop: return self.tox.bootstrap(*data) self.tox.add_tcp_relay(*data) except: pass for _ in range(10): - if self.stop: + if self._stop: return - self.msleep(1000) + time.sleep(1) while not self.tox.self_get_connection_status(): try: for data in generate_nodes(): - if self.stop: + if self._stop: return self.tox.bootstrap(*data) self.tox.add_tcp_relay(*data) except: pass finally: - self.msleep(5000) + time.sleep(5) -class ToxIterateThread(QtCore.QThread): +class ToxIterateThread(BaseThread): def __init__(self, tox): - QtCore.QThread.__init__(self) - self.tox = tox - self.stop = False + super().__init__() + self._tox = tox def run(self): - while not self.stop: - self.tox.iterate() - self.msleep(self.tox.iteration_interval()) + while not self._stop: + self._tox.iterate() + time.sleep(self._tox.iteration_interval() / 1000) -class ToxAVIterateThread(QtCore.QThread): +class ToxAVIterateThread(BaseThread): def __init__(self, toxav): - QtCore.QThread.__init__(self) - self.toxav = toxav - self.stop = False + super().__init__() + self._toxav = toxav def run(self): - while not self.stop: - self.toxav.iterate() - self.msleep(self.toxav.iteration_interval()) + while not self._stop: + self._toxav.iterate() + time.sleep(self._toxav.iteration_interval() / 1000) -class FileTransfersThread(threading.Thread): +class FileTransfersThread(BaseThread): def __init__(self): + super().__init__() self._queue = queue.Queue() self._timeout = 0.01 - self._continue = True - super().__init__() def execute(self, func, *args, **kwargs): self._queue.put((func, args, kwargs)) - def stop(self): - self._continue = False - def run(self): - while self._continue: + while not self._stop: try: func, args, kwargs = self._queue.get(timeout=self._timeout) func(*args, **kwargs) @@ -105,8 +108,7 @@ def start(): def stop(): - _thread.stop() - _thread.join() + _thread.stop_thread() def execute(func, *args, **kwargs): diff --git a/toxygen/communication/tox_factory.py b/toxygen/middleware/tox_factory.py similarity index 100% rename from toxygen/communication/tox_factory.py rename to toxygen/middleware/tox_factory.py diff --git a/toxygen/notifications.py b/toxygen/notifications.py deleted file mode 100644 index cf926d1..0000000 --- a/toxygen/notifications.py +++ /dev/null @@ -1,71 +0,0 @@ -from PyQt5 import QtCore, QtWidgets -from util.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() diff --git a/toxygen/notifications/__init__.py b/toxygen/notifications/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/notifications/sound.py b/toxygen/notifications/sound.py new file mode 100644 index 0000000..a0b93f0 --- /dev/null +++ b/toxygen/notifications/sound.py @@ -0,0 +1,54 @@ +import util.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(util.util.get_sounds_directory(), file_name) diff --git a/toxygen/notifications/tray.py b/toxygen/notifications/tray.py new file mode 100644 index 0000000..4232253 --- /dev/null +++ b/toxygen/notifications/tray.py @@ -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) diff --git a/toxygen/smileys/__init__.py b/toxygen/smileys/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/smileys_and_stickers.py b/toxygen/smileys/smileys.py similarity index 87% rename from toxygen/smileys_and_stickers.py rename to toxygen/smileys/smileys.py index dd72fd9..abf6990 100644 --- a/toxygen/smileys_and_stickers.py +++ b/toxygen/smileys/smileys.py @@ -47,6 +47,7 @@ class SmileyLoader: def get_smileys_path(self): return util.curr_directory() + '/smileys/' + self._curr_pack + '/' if self._curr_pack is not None else None + @staticmethod def get_packs_list(self): d = util.curr_directory() + '/smileys/' return [x[1] for x in os.walk(d)][0] @@ -71,18 +72,3 @@ class SmileyLoader: if file_name.endswith('.gif'): # animated smiley edit.addAnimation(QtCore.QUrl(file_name), self.get_smileys_path() + file_name) 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 diff --git a/toxygen/stickers/__init__.py b/toxygen/stickers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/stickers/stickers.py b/toxygen/stickers/stickers.py new file mode 100644 index 0000000..5ad6aa1 --- /dev/null +++ b/toxygen/stickers/stickers.py @@ -0,0 +1,18 @@ +import os +import util.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 = 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 diff --git a/toxygen/ui/avwidgets.py b/toxygen/ui/av_widgets.py similarity index 100% rename from toxygen/ui/avwidgets.py rename to toxygen/ui/av_widgets.py diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index d3896be..55e7c8e 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -6,6 +6,7 @@ import plugin_support from ui.main_screen_widgets import * from user_data import toxes, settings import util.util as util +import util.ui as util_ui class MainWindow(QtWidgets.QMainWindow): @@ -414,12 +415,10 @@ class MainWindow(QtWidgets.QMainWindow): # ----------------------------------------------------------------------------------------------------------------- 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.\nVersion: ')) - msgBox.setText(text + util.program_version + '\nGitHub: https://github.com/toxygen-project/toxygen/') - msgBox.exec_() + 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.n_s = NetworkSettings(self.reset) diff --git a/toxygen/ui/main_screen_widgets.py b/toxygen/ui/main_screen_widgets.py index bbf6c5a..319b3df 100644 --- a/toxygen/ui/main_screen_widgets.py +++ b/toxygen/ui/main_screen_widgets.py @@ -2,7 +2,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets from ui.widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit from contacts.profile import Profile import smileys -import util +import util.util as util class MessageArea(QtWidgets.QPlainTextEdit): @@ -194,7 +194,7 @@ class DropdownMenu(QtWidgets.QWidget): self.stickerButton = QtWidgets.QPushButton(self) self.stickerButton.setGeometry(QtCore.QRect(60, 0, 60, 60)) - pixmap = QtGui.QPixmap(util.curr_directory() + '/images/file.png') + pixmap = QtGui.QPixmap(util.get_images_directory() + 'file.png') icon = QtGui.QIcon(pixmap) self.fileTransferButton.setIcon(icon) self.fileTransferButton.setIconSize(QtCore.QSize(50, 50)) diff --git a/toxygen/tray.py b/toxygen/ui/tray.py similarity index 79% rename from toxygen/tray.py rename to toxygen/ui/tray.py index 0c474b4..ebdbd33 100644 --- a/toxygen/tray.py +++ b/toxygen/ui/tray.py @@ -1,6 +1,7 @@ from PyQt5 import QtWidgets, QtGui, QtCore from util.ui import tr -from util.util import curr_directory +from util.util import get_images_directory +import os.path class SystemTrayIcon(QtWidgets.QSystemTrayIcon): @@ -8,11 +9,11 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon): leftClicked = QtCore.pyqtSignal() def __init__(self, icon, parent=None): - super().__init__(self, icon, parent) - self.activated.connect(self.iconActivated) + super().__init__(icon, parent) + self.activated.connect(self.icon_activated) - def iconActivated(self, reason): - if reason == QtGui.QSystemTrayIcon.Trigger: + def icon_activated(self, reason): + if reason == QtWidgets.QSystemTrayIcon.Trigger: self.leftClicked.emit() @@ -52,20 +53,21 @@ class Menu(QtWidgets.QMenu): def init_tray(profile, settings, main_screen): - tray = SystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) + icon = os.path.join(get_images_directory(), 'icon.png') + tray = SystemTrayIcon(QtGui.QIcon(icon)) tray.setObjectName('tray') - m = Menu(settings, profile) - show = m.addAction(tr('Open Toxygen')) - sub = m.addMenu(tr('Set status')) + 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) - m.act = sub - exit = m.addAction(tr('Exit')) + menu.act = sub + exit = menu.addAction(tr('Exit')) def show_window(): def show(): @@ -96,12 +98,12 @@ def init_tray(profile, settings, main_screen): show.triggered.connect(show_window) exit.triggered.connect(close_app) - m.aboutToShow.connect(lambda: m.aboutToShowHandler()) - online.triggered.connect(lambda: m.newStatus(0)) - away.triggered.connect(lambda: m.newStatus(1)) - busy.triggered.connect(lambda: m.newStatus(2)) + menu.aboutToShow.connect(lambda: menu.aboutToShowHandler()) + online.triggered.connect(lambda: menu.newStatus(0)) + away.triggered.connect(lambda: menu.newStatus(1)) + busy.triggered.connect(lambda: menu.newStatus(2)) - tray.setContextMenu(m) + tray.setContextMenu(menu) tray.show() tray.activated.connect(tray_activated) diff --git a/toxygen/updater/updater.py b/toxygen/updater/updater.py index 575dbfe..f274161 100644 --- a/toxygen/updater/updater.py +++ b/toxygen/updater/updater.py @@ -118,14 +118,8 @@ def start_update_if_needed(version, settings): download(version) updating = True else: - reply = QtWidgets.QMessageBox.question(None, - 'Toxygen', - QtWidgets.QApplication.translate("login", - 'Update for Toxygen was found. Download and install it?'), - QtWidgets.QMessageBox.Yes, - QtWidgets.QMessageBox.No) - if reply == QtWidgets.QMessageBox.Yes: + reply = util_ui.question(util_ui.tr('Update for Toxygen was found. Download and install it?')) + if reply: download(version) updating = True - return updating diff --git a/toxygen/user_data/settings.py b/toxygen/user_data/settings.py index 39fd333..9226a81 100644 --- a/toxygen/user_data/settings.py +++ b/toxygen/user_data/settings.py @@ -4,7 +4,7 @@ import os from util.util import log, curr_directory, append_slash import pyaudio from user_data.toxes import ToxES -import smileys_and_stickers as smileys +import smileys.smileys as smileys class Settings(dict): diff --git a/toxygen/util/ui.py b/toxygen/util/ui.py index be613cb..c485de4 100644 --- a/toxygen/util/ui.py +++ b/toxygen/util/ui.py @@ -5,10 +5,17 @@ def tr(s): return PyQt5.QtWidgets.QApplication.translate('Toxygen', s) -def question(text): - reply = PyQt5.QtWidgets.QMessageBox.question(None, 'Toxygen', text, +def question(text, title=None): + reply = PyQt5.QtWidgets.QMessageBox.question(None, title or 'Toxygen', text, PyQt5.QtWidgets.QMessageBox.Yes, PyQt5.QtWidgets.QMessageBox.No) return reply == PyQt5.QtWidgets.QMessageBox.Yes + +def message_box(text, title=None): + m_box = PyQt5.QtWidgets.QMessageBox() + m_box.setText(tr(text)) + m_box.setWindowTitle(title or 'Toxygen') + m_box.exec_() + # TODO: move all dialogs here diff --git a/toxygen/util/util.py b/toxygen/util/util.py index 91a4aec..5bf5f99 100644 --- a/toxygen/util/util.py +++ b/toxygen/util/util.py @@ -35,12 +35,33 @@ def get_base_directory(current_file=None): return os.path.dirname(curr_directory(current_file or __file__)) +@cached def get_images_directory(): - return os.path.join(get_base_directory(), 'images') + return get_app_directory('images') +@cached def get_styles_directory(): - return os.path.join(get_base_directory(), 'styles') + return get_app_directory('styles') + + +@cached +def get_sounds_directory(): + return get_app_directory('sounds') + + +@cached +def get_stickers_directory(): + return get_app_directory('stickers') + + +@cached +def get_translations_directory(): + return get_app_directory('translations') + + +def get_app_directory(directory_name): + return os.path.join(get_base_directory(), directory_name) def get_profile_name_from_path(path): From 0ba1aadf70087ed815fcea6e5ce1410545e6eb95 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 17 Apr 2018 21:08:22 +0300 Subject: [PATCH 008/138] app.py and main.py refactoring and fixes --- toxygen/app.py | 194 +++++++++++++++++--------------- toxygen/main.py | 14 +-- toxygen/middleware/callbacks.py | 38 ++++--- toxygen/middleware/threads.py | 38 +++---- toxygen/user_data/settings.py | 18 ++- 5 files changed, 160 insertions(+), 142 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index 3668c4b..85da363 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -1,6 +1,7 @@ from middleware import threads +import middleware.callbacks as callbacks from PyQt5 import QtWidgets, QtGui, QtCore -import ui.password_screen as passwordscreen +import ui.password_screen as password_screen from util.util import * import updater.updater as updater import os @@ -20,32 +21,30 @@ class App: def __init__(self, version, path_to_profile=None, uri=None): self._version = version - self._app = None - self._tox = self._ms = self._init = self._app = self.tray = self._main_loop = self._av_loop = None - self.uri = self._toxes = self._tray = None + self._app = self._settings = self._profile_manager = self._plugin_loader = None + self._tox = self._ms = self._init = self.tray = self._main_loop = self._av_loop = None + self._uri = self._toxes = self._tray = None if uri is not None and uri.startswith('tox:'): - self.uri = uri[4:] + self._uri = uri[4:] self._path = path_to_profile def enter_pass(self, data): """ Show password screen """ - p = passwordscreen.PasswordScreen(self._toxes, data) + p = password_screen.PasswordScreen(self._toxes, data) p.show() self._app.lastWindowClosed.connect(self._app.quit) self._app.exec_() - result = p.result - if result is None: - raise SystemExit() - else: - return result + if p.result is not None: + return p.result + raise SystemExit() def main(self): """ Main function of app. loads login screen if needed and starts main screen """ - self._app= QtWidgets.QApplication([]) + self._app = QtWidgets.QApplication([]) icon_file = os.path.join(get_images_directory(), 'icon.png') self._app.setWindowIcon(QtGui.QIcon(icon_file)) @@ -59,28 +58,13 @@ class App: encrypt_save = tox_encrypt_save.ToxEncryptSave() self._toxes = user_data.toxes.ToxES(encrypt_save) - if self._path is not None: - path = os.path.dirname(self._path) + '/' - name = os.path.basename(self._path)[:-4] - self._settings = Settings(self._toxes, self._path.replace('.tox', '.json')) - self._profile_manager = ProfileManager(self._settings, self._toxes, path) - data = self._profile_manager.open_profile() - if encrypt_save.is_data_encrypted(data): - data = self.enter_pass(data) - self._tox = self.create_tox(data) + 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 not auto_profile[0]: + if auto_profile is None: # no default profile # show login screen if default profile not found - current_locale = QtCore.QLocale() - curr_lang = current_locale.languageToString(current_locale.language()) - langs = Settings.supported_languages() - if curr_lang in langs: - lang_path = langs[curr_lang] - translator = QtCore.QTranslator() - translator.load(get_translations_directory() + lang_path) - self._app.installTranslator(translator) - self._app.translator = translator + self.load_login_screen_translations() ls = LoginScreen() ls.setWindowIconText("Toxygen") profiles = ProfileManager.find_profiles() @@ -90,59 +74,25 @@ class App: result = ls.result if result is None: return - elif result.is_new_profile(): # create new profile - name = get_profile_name_from_path(result.profile_path) or 'toxygen_user' - pr = map(lambda x: x[1], ProfileManager.find_profiles()) - if name in list(pr): - util_ui.message_box(util_ui.tr('Profile with this name already exists'), - util_ui.tr('Error')) - return - self._tox = tox_factory() - self._tox.self_set_name(bytes(name, 'utf-8') if name else b'Toxygen User') - self._tox.self_set_status_message(b'Toxing on Toxygen') - # TODO: set profile password - path = result.profile_path - self._profile_manager = ProfileManager(self._toxes, path) - try: - self._profile_manager.save_profile(self._tox.get_savedata()) - except Exception as ex: - print(str(ex)) - 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 - path = Settings.get_default_path() - self._settings = Settings() - if curr_lang in langs: - self._settings['language'] = curr_lang - self._settings.save() + if result.is_new_profile(): # create new profile + self.create_new_profile(result.profile_path) else: # load existing profile - path = result.profile_path - if result.load_as_default: - Settings.set_auto_profile(path) - self._settings = Settings(self._toxes, path.replace('.tox', '.json')) - self._profile_manager = ProfileManager(self._settings, self._toxes, path) - data = self._profile_manager.open_profile() - if self._toxes.is_data_encrypted(data): - data = self.enter_pass(data) - self._tox = self.create_tox(data) - else: + self.load_existing_profile(result.profile_path) + self._path = result.profile_path + else: # default profile path, name = auto_profile - self._settings = Settings(self._toxes, path + name + '.json') - self._profile_manager = ProfileManager(self._settings, self._toxes, path) - data = self._profile_manager.open_profile() - if encrypt_save.is_data_encrypted(data): - data = self.enter_pass(data) - self.tox = self.create_tox(data) + self._path = os.path.join(path, name + '.tox') + self.load_existing_profile(self._path) - if Settings.is_active_profile(path, get_profile_name_from_path(path)): # profile is in use - title = util_ui.tr('Profile {}').format(name) + if Settings.is_active_profile(self._path): # profile is in use + profile_name = 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 - else: - self._settings.set_active_profile() + + self._settings.set_active_profile() self.load_app_styles() self.load_app_translations() @@ -151,17 +101,21 @@ class App: return self._ms = MainWindow(self._settings, self._tox, self.reset, self._tray) - self._profile = self._ms.profile + profile = self._ms.profile self._ms.show() - self._tray = tray.init_tray(self._profile, self._settings, self._ms) + self._tray = tray.init_tray(profile, self._settings, self._ms) self._tray.show() - self._plugin_loader = PluginLoader(self._tox, self._toxes, self._profile, self._settings) # plugins support - self._plugin_loader.load() # TODO; move to separate thread? + self._plugin_loader = PluginLoader(self._tox, self._toxes, profile, self._settings) # plugins support + self.start_threads() - if self.uri is not None: - self._ms.add_contact(self.uri) + # callbacks initialization + callbacks.init_callbacks(self._tox, profile, self._settings, self._plugin_loader, None, None, None, + self._ms, self._tray) + + if self._uri is not None: + self._ms.add_contact(self._uri) self._app.lastWindowClosed.connect(self._app.quit) self._app.exec_() @@ -169,8 +123,7 @@ class App: self._plugin_loader.stop() self.stop_threads() self._tray.hide() - data = self._tox.get_savedata() - self._profile_manager.save_profile(data) + self.save_profile() self._settings.close() del self._tox @@ -181,10 +134,10 @@ class App: """ self.stop_threads() data = self._tox.get_savedata() - self._profile_manager.save_profile(data) + self.save_profile(data) del self._tox # create new tox instance - self._tox = tox_factory(data, self._settings) + self._tox = self.create_tox(data) self.start_threads() self._plugin_loader.set_tox(self._tox) @@ -199,25 +152,41 @@ class App: style = fl.read() self._app.setStyleSheet(style) + def load_login_screen_translations(self): + current_language, supported_languages = self.get_languages() + if current_language in supported_languages: + lang_path = supported_languages[current_language] + translator = QtCore.QTranslator() + translator.load(get_translations_directory() + lang_path) + self._app.installTranslator(translator) + self._app.translator = translator + + @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(curr_directory(__file__) + '/translations/' + lang) + translator.load(os.path.join(get_translations_directory(), lang)) self._app.installTranslator(translator) self._app.translator = translator def try_to_update(self): updating = updater.start_update_if_needed(self._version, self._settings) if updating: - data = self._tox.get_savedata() - self._profile_manager.save_profile(data) + self.save_profile() self._settings.close() del self._tox return updating def start_threads(self): # init thread - self._init = threads.InitThread(self._tox, self._ms, self._tray) + self._init = threads.InitThread(self._tox, self._plugin_loader) self._init.start() # starting threads for tox iterate and toxav iterate @@ -226,11 +195,52 @@ class App: self._av_loop = threads.ToxAVIterateThread(self._tox.AV) self._av_loop.start() + threads.start_file_transfer_thread() + def stop_threads(self): self._init.stop_thread() - self._main_loop.stop_thread() self._av_loop.stop_thread() + self._main_loop.stop_thread() + + threads.stop_file_transfer_thread() def create_tox(self, data): return tox_factory(data, self._settings) + + def load_existing_profile(self, profile_path): + self._settings = Settings(self._toxes, profile_path.replace('.tox', '.json')) + self._profile_manager = ProfileManager(self._settings, self._toxes, profile_path) + data = self._profile_manager.open_profile() + if self._toxes.is_data_encrypted(data): + data = self.enter_pass(data) + self._tox = self.create_tox(data) + + def create_new_profile(self, profile_path): + name = get_profile_name_from_path(profile_path) or 'toxygen_user' + if os.path.isfile(profile_path): + util_ui.message_box(util_ui.tr('Profile with this name already exists'), + util_ui.tr('Error')) + return + self._tox = tox_factory() + self._tox.self_set_name(bytes(name, 'utf-8') if name else b'Toxygen User') + self._tox.self_set_status_message(b'Toxing on Toxygen') + # TODO: set profile password + self._settings = Settings(self._toxes, self._path.replace('.tox', '.json')) + self._profile_manager = ProfileManager(self._settings, self._toxes, profile_path) + try: + self.save_profile() + except Exception as ex: + print(ex) + 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 + current_language, supported_languages = self.get_languages() + if current_language in supported_languages: + self._settings['language'] = current_language + self._settings.save() + + def save_profile(self, data=None): + data = data or self._tox.get_savedata() + self._profile_manager.save_profile(data) diff --git a/toxygen/main.py b/toxygen/main.py index 97a5363..cdaa7e5 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -1,4 +1,3 @@ -import sys import app from user_data.settings import * from util.util import curr_directory, remove @@ -11,7 +10,7 @@ __version__ = '0.5.0' def clean(): """Removes all windows libs from libs folder""" - d = curr_directory() + '/libs/' + d = os.path.join(curr_directory(__file__), 'libs') remove(d) @@ -25,10 +24,11 @@ def print_toxygen_version(): def main(): parser = argparse.ArgumentParser() - parser.add_argument('--version', help='Prints Toxygen version') - parser.add_argument('--clean', help='Deletes toxcore libs from libs folder') - parser.add_argument('--reset', help='Resets default profile') - parser.add_argument('profile_path', nargs='?', default=None, help='Resets default profile') + parser.add_argument('--version', action='store_true', help='Prints Toxygen version') + parser.add_argument('--clean', action='store_true', help='Deletes toxcore libs from libs folder') + parser.add_argument('--reset', action='store_true', help='Resets default profile') + parser.add_argument('--uri', help='Adds specified TOX ID to friends') + parser.add_argument('profile', nargs='?', default=None, help='Path to TOX profile') args = parser.parse_args() if args.version: @@ -43,7 +43,7 @@ def main(): reset() return - toxygen = app.App(__version__, path_to_profile=args.profile_path) + toxygen = app.App(__version__, args.profile, args.uri) toxygen.main() diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 4db161e..926c7af 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -1,15 +1,18 @@ -from PyQt5 import QtGui +from PyQt5 import QtGui, QtCore from user_data.settings import Settings from contacts.profile import Profile from wrapper.toxcore_enums_and_consts import * from wrapper.toxav_enums import * from wrapper.tox import bin_to_string -from plugin_support.plugin_support import PluginLoader +import util.ui as util_ui +import util.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 * -# TODO: use closures +# TODO: gc callbacks and refactoring # ----------------------------------------------------------------------------------------------------------------- # Callbacks - current user @@ -111,7 +114,8 @@ def friend_message(profile, settings, window, tray): 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')) + icon = os.path.join(util.get_images_directory(), 'icon_new_messages.png') + invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon)) return wrapped @@ -169,11 +173,12 @@ def tox_file_recv(window, tray, profile, file_transfer_handler, contacts_manager 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 = QtWidgets.QApplication.translate("Callback", "File from") + 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']) - invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png')) + icon = os.path.join(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, @@ -240,8 +245,7 @@ def lossy_packet(plugin_loader): Incoming lossy packet """ data = data[:length] - plugin = PluginLoader.get_instance() - invoke_in_main_thread(plugin.callback_lossy, friend_number, data) + invoke_in_main_thread(plugin_loader.callback_lossy, friend_number, data) return wrapped @@ -358,7 +362,8 @@ def show_gc_notification(window, tray, message, group_number, peer_number): 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')) + icon = os.path.join(util.get_images_directory(), 'icon_new_messages.png') + invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon)) # ----------------------------------------------------------------------------------------------------------------- # Callbacks - initialization @@ -366,17 +371,20 @@ def show_gc_notification(window, tray, message, group_number, peer_number): def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, - calls_manager, file_transfer_handler, window, tray): + calls_manager, file_transfer_handler, main_window, tray): """ Initialization of all callbacks. - :param tox: tox instance - :param window: main window + :param tox: Tox instance + :param profile: Profile instance + :param settings: Settings instance + :param plugin_loader: PluginLoader instance + :param main_window: main window screen :param tray: tray (for notifications) """ tox.callback_self_connection_status(self_connection_status(tox, profile), 0) tox.callback_friend_status(friend_status(profile, settings), 0) - tox.callback_friend_message(friend_message(profile, settings, window, tray), 0) + tox.callback_friend_message(friend_message(profile, settings, main_window, tray), 0) tox.callback_friend_connection_status(friend_connection_status(profile, settings, plugin_loader), 0) tox.callback_friend_name(friend_name(profile), 0) tox.callback_friend_status_message(friend_status_message(profile), 0) @@ -384,11 +392,13 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, tox.callback_friend_typing(friend_typing(contacts_manager), 0) tox.callback_friend_read_receipt(friend_read_receipt(contacts_manager), 0) - tox.callback_file_recv(tox_file_recv(window, tray, profile, file_transfer_handler, contacts_manager, settings), 0) + tox.callback_file_recv(tox_file_recv(main_window, tray, profile, file_transfer_handler, + contacts_manager, settings), 0) tox.callback_file_recv_chunk(file_recv_chunk(file_transfer_handler), 0) tox.callback_file_chunk_request(file_chunk_request(file_transfer_handler), 0) tox.callback_file_recv_control(file_recv_control(file_transfer_handler), 0) + 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) diff --git a/toxygen/middleware/threads.py b/toxygen/middleware/threads.py index eb5a9d7..2d7bdf4 100644 --- a/toxygen/middleware/threads.py +++ b/toxygen/middleware/threads.py @@ -9,44 +9,44 @@ class BaseThread(threading.Thread): def __init__(self): super().__init__() - self._stop = False + self._stop_thread = False def stop_thread(self): - self._stop = True + self._stop_thread = True self.join() class InitThread(BaseThread): - def __init__(self, tox, ms, tray): + def __init__(self, tox, plugin_loader): super().__init__() - self.tox, self.ms, self.tray = tox, ms, tray + self._tox, self._plugin_loader = tox, plugin_loader def run(self): - # initializing callbacks - init_callbacks(self.tox, self.ms, self.tray) # download list of nodes if needed download_nodes_list() + # start plugins + self._plugin_loader.load() # bootstrap try: for data in generate_nodes(): - if self._stop: + if self._stop_thread: return - self.tox.bootstrap(*data) - self.tox.add_tcp_relay(*data) + self._tox.bootstrap(*data) + self._tox.add_tcp_relay(*data) except: pass for _ in range(10): - if self._stop: + if self._stop_thread: return time.sleep(1) - while not self.tox.self_get_connection_status(): + while not self._tox.self_get_connection_status(): try: for data in generate_nodes(): - if self._stop: + if self._stop_thread: return - self.tox.bootstrap(*data) - self.tox.add_tcp_relay(*data) + self._tox.bootstrap(*data) + self._tox.add_tcp_relay(*data) except: pass finally: @@ -60,7 +60,7 @@ class ToxIterateThread(BaseThread): self._tox = tox def run(self): - while not self._stop: + while not self._stop_thread: self._tox.iterate() time.sleep(self._tox.iteration_interval() / 1000) @@ -72,7 +72,7 @@ class ToxAVIterateThread(BaseThread): self._toxav = toxav def run(self): - while not self._stop: + while not self._stop_thread: self._toxav.iterate() time.sleep(self._toxav.iteration_interval() / 1000) @@ -88,7 +88,7 @@ class FileTransfersThread(BaseThread): self._queue.put((func, args, kwargs)) def run(self): - while not self._stop: + while not self._stop_thread: try: func, args, kwargs = self._queue.get(timeout=self._timeout) func(*args, **kwargs) @@ -103,11 +103,11 @@ class FileTransfersThread(BaseThread): _thread = FileTransfersThread() -def start(): +def start_file_transfer_thread(): _thread.start() -def stop(): +def stop_file_transfer_thread(): _thread.stop_thread() diff --git a/toxygen/user_data/settings.py b/toxygen/user_data/settings.py index 9226a81..2831799 100644 --- a/toxygen/user_data/settings.py +++ b/toxygen/user_data/settings.py @@ -1,9 +1,7 @@ -from platform import system import json import os -from util.util import log, curr_directory, append_slash +from util.util import log, get_base_directory, append_slash, get_platform import pyaudio -from user_data.toxes import ToxES import smileys.smileys as smileys @@ -60,7 +58,7 @@ class Settings(dict): name = str(auto['name']) if os.path.isfile(append_slash(path) + name + '.tox'): return path, name - return '', '' + return None @staticmethod def set_auto_profile(path, name): @@ -92,9 +90,8 @@ class Settings(dict): fl.write(json.dumps(data)) @staticmethod - def is_active_profile(path, name): - path = path + name + '.lock' - return os.path.isfile(path) + def is_active_profile(profile_path): + return os.path.isfile(profile_path + '.lock') @staticmethod def get_default_settings(): @@ -204,13 +201,14 @@ class Settings(dict): @staticmethod def get_global_settings_path(): - return curr_directory() + '/toxygen.json' + return os.path.join(get_base_directory(), 'toxygen.json') @staticmethod def get_default_path(): - if system() == 'Windows': + system = get_platform() + if system == 'Windows': return os.getenv('APPDATA') + '/Tox/' - elif system() == 'Darwin': + elif system == 'Darwin': return os.getenv('HOME') + '/Library/Application Support/Tox/' else: return os.getenv('HOME') + '/.config/tox/' From dec4990d32a093e247b7e476d9f0da4aa7903148 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 18 Apr 2018 23:55:51 +0300 Subject: [PATCH 009/138] contacts minor refactoring --- toxygen/app.py | 22 +-- toxygen/av/calls.py | 16 +-- toxygen/av/calls_manager.py | 11 +- toxygen/contacts/basecontact.py | 25 ++-- toxygen/contacts/contact.py | 6 +- toxygen/contacts/contacts_manager.py | 201 +++++++++++++++++---------- toxygen/contacts/friend.py | 4 +- toxygen/contacts/group_chat.py | 16 +-- toxygen/contacts/profile.py | 64 +-------- toxygen/user_data/profile_manager.py | 2 +- toxygen/util/ui.py | 21 ++- toxygen/util/util.py | 4 + 12 files changed, 204 insertions(+), 188 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index 85da363..e038980 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -22,7 +22,7 @@ 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 = None - self._tox = self._ms = self._init = self.tray = self._main_loop = self._av_loop = None + self._tox = self._ms = self._init = self._main_loop = self._av_loop = None self._uri = self._toxes = self._tray = None if uri is not None and uri.startswith('tox:'): self._uri = uri[4:] @@ -63,15 +63,7 @@ class App: else: auto_profile = Settings.get_auto_profile() if auto_profile is None: # no default profile - # show login screen if default profile not found - self.load_login_screen_translations() - ls = LoginScreen() - ls.setWindowIconText("Toxygen") - profiles = ProfileManager.find_profiles() - ls.update_select(profiles) - ls.show() - self._app.exec_() - result = ls.result + result = self.select_profile() if result is None: return if result.is_new_profile(): # create new profile @@ -208,6 +200,16 @@ class App: def create_tox(self, data): return tox_factory(data, self._settings) + 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._settings = Settings(self._toxes, profile_path.replace('.tox', '.json')) self._profile_manager = ProfileManager(self._settings, self._toxes, profile_path) diff --git a/toxygen/av/calls.py b/toxygen/av/calls.py index 20684b2..3782318 100644 --- a/toxygen/av/calls.py +++ b/toxygen/av/calls.py @@ -1,12 +1,12 @@ import pyaudio import time import threading -from user_data import settings from wrapper.toxav_enums import * import cv2 import itertools import numpy as np from av import screen_sharing +from av.call import Call # TODO: play sound until outgoing call will be started or cancelled @@ -14,8 +14,9 @@ from av import screen_sharing class AV: - def __init__(self, toxav): + def __init__(self, toxav, settings): self._toxav = toxav + self._settings = settings self._running = True self._calls = {} # dict: key - friend number, value - Call instance @@ -118,7 +119,7 @@ class AV: rate=self._audio_rate, channels=self._audio_channels, 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) self._audio_thread = threading.Thread(target=self.send_audio) @@ -147,15 +148,14 @@ class AV: return self._video_running = True - s = settings.Settings.get_instance() self._video_width = s.video['width'] self._video_height = s.video['height'] if s.video['device'] == -1: - self._video = screen_sharing.DesktopGrabber(s.video['x'], s.video['y'], - s.video['width'], s.video['height']) + self._video = screen_sharing.DesktopGrabber(self._settings.video['x'], self._settings.video['y'], + self._settings.video['width'], self._settings.video['height']) 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_FRAME_WIDTH, self._video_width) self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._video_height) @@ -185,7 +185,7 @@ class AV: self._out_stream = self._audio.open(format=pyaudio.paInt16, channels=channels_count, rate=rate, - output_device_index=settings.Settings.get_instance().audio['output'], + output_device_index=self._settings.audio['output'], output=True) self._out_stream.write(samples) diff --git a/toxygen/av/calls_manager.py b/toxygen/av/calls_manager.py index 6cfe543..8060881 100644 --- a/toxygen/av/calls_manager.py +++ b/toxygen/av/calls_manager.py @@ -4,15 +4,16 @@ import av.calls from PyQt5 import QtWidgets from messenger.messages import * import time +from ui import av_widgets class CallsManager: - def __init__(self, tox): + def __init__(self, tox, settings): self._call = av.calls.AV(tox.AV) # object with data about calls self._call_widgets = {} # dict of incoming call widgets self._incoming_calls = set() - + self._settings = settings # ----------------------------------------------------------------------------------------------------------------- # AV support @@ -29,7 +30,7 @@ class CallsManager: if not self.is_active_a_friend(): return if num not in self._call and self.is_active_online(): # start call - if not Settings.get_instance().audio['enabled']: + if not self._settings.audio['enabled']: return self._call(num, audio, video) self._screen.active_call() @@ -47,7 +48,7 @@ class CallsManager: """ Incoming call from friend. """ - if not Settings.get_instance().audio['enabled']: + if not self._settings.audio['enabled']: return friend = self.get_friend_by_number(friend_number) if video: @@ -62,7 +63,7 @@ class CallsManager: self._messages.scrollToBottom() else: friend.actions = True - self._call_widgets[friend_number] = avwidgets.IncomingCallWidget(friend_number, text, friend.name) + self._call_widgets[friend_number] = av_widgets.IncomingCallWidget(friend_number, text, friend.name) self._call_widgets[friend_number].set_pixmap(friend.get_pixmap()) self._call_widgets[friend_number].show() diff --git a/toxygen/contacts/basecontact.py b/toxygen/contacts/basecontact.py index 34c100c..da992de 100644 --- a/toxygen/contacts/basecontact.py +++ b/toxygen/contacts/basecontact.py @@ -1,6 +1,7 @@ from user_data.settings import * from PyQt5 import QtCore, QtGui from wrapper.toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE +import util.util as util class BaseContact: @@ -11,13 +12,14 @@ class BaseContact: 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 status_message: status message, example: 'Toxing on Toxygen' :param widget: ContactItem instance :param tox_id: tox id of contact """ + self._profile_manager = profile_manager self._name, self._status_message = name, status_message self._status, self._widget = None, widget self._tox_id = tox_id @@ -81,11 +83,7 @@ class BaseContact: """ Tries to load avatar of contact or uses default avatar """ - return - prefix = ProfileManager.get_path() + 'avatars/' - 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' + avatar_path = self.get_avatar_path() width = self._widget.avatar_label.width() pixmap = QtGui.QPixmap(avatar_path) self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio, @@ -93,13 +91,13 @@ class BaseContact: self._widget.avatar_label.repaint() def reset_avatar(self): - avatar_path = (ProfileManager.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): os.remove(avatar_path) self.load_avatar() def set_avatar(self, avatar): - avatar_path = (ProfileManager.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) + avatar_path = self.get_avatar_path() with open(avatar_path, 'wb') as f: f.write(avatar) self.load_avatar() @@ -107,6 +105,17 @@ class BaseContact: def get_pixmap(self): return self._widget.avatar_label.pixmap() + def get_avatar_path(self): + directory = util.join_path(self._profile_manager.get_path(), 'avatars') + avatar_path = util.join_path(directory, '{}.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 = util.join_path(util.get_images_directory(), self.get_default_avatar_name()) + + return avatar_path + + @staticmethod + def get_default_avatar_name(): + return 'avatar.png' # ----------------------------------------------------------------------------------------------------------------- # Widgets # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index 22191a7..ecaeec2 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -1,6 +1,6 @@ from history.database import * from contacts import basecontact -import util +import util.util as util from messenger.messages import * from file_transfers import file_transfers as ft import re @@ -12,12 +12,12 @@ class Contact(basecontact.BaseContact): 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, message_getter, number, profile_manager, name, status_message, widget, tox_id): """ :param message_getter: gets messages from db :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._new_messages = False self._visible = True diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 7653b56..122db5c 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -1,36 +1,56 @@ +import util.util as util +import util.ui as util_ui +from contacts.friend import Friend +import os +from PyQt5 import QtCore, QtGui, QtWidgets +from messenger.messages import * +from wrapper.toxcore_enums_and_consts import * +from network.tox_dns import tox_dns class ContactsManager: - def __init__(self, tox, settings, screen): + def __init__(self, tox, settings, screen, profile_manager): self._tox = tox self._settings = settings + self._screen = screen + self._profile_manager = profile_manager + self._messages = screen.messages self._contacts, self._active_friend = [], -1 self._sorting = settings['sorting'] - data = tox.self_get_friend_list() self._filter_string = '' self._friend_item_height = 40 if settings['compact_mode'] else 70 screen.online_contacts.setCurrentIndex(int(self._sorting)) - aliases = settings['friends_aliases'] - for i in data: # creates list of friends - tox_id = tox.friend_get_public_key(i) + self.load_contacts() + + def load_contacts(self): + self.load_friends() + self.load_groups() + if len(self._contacts): + self.set_active(0) + self.filtration_and_sorting(self._sorting) + + def load_friends(self): + aliases = self._settings['friends_aliases'] + friend_numbers = self._tox.self_get_friend_list() + for friend_number in friend_numbers: # creates list of friends + 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 tox.friend_get_name(i) or tox_id - status_message = tox.friend_get_status_message(i) + name = alias or self._tox.friend_get_name(friend_number) or tox_id + status_message = self._tox.friend_get_status_message(i) if not self._history.friend_exists_in_db(tox_id): self._history.add_friend_to_db(tox_id) message_getter = self._history.messages_getter(tox_id) - friend = Friend(message_getter, i, name, status_message, item, tox_id) + friend = Friend(self._profile_manager, message_getter, friend_number, name, status_message, item, tox_id) friend.set_alias(alias) self._contacts.append(friend) - if len(self._contacts): - self.set_active(0) - self.filtration_and_sorting(self._sorting) + def load_groups(self): + pass def get_friend(self, num): if num < 0 or num >= len(self._contacts): @@ -40,6 +60,10 @@ class ContactsManager: def get_curr_friend(self): return self._contacts[self._active_friend] if self._active_friend + 1 else None + def save_profile(self): + data = self._tox.get_savedata() + self._profile_manager.save_profile(data) + # ----------------------------------------------------------------------------------------------------------------- # Work with active friend # ----------------------------------------------------------------------------------------------------------------- @@ -78,7 +102,7 @@ class ContactsManager: self._screen.messageEdit.setPlainText(friend.curr_text) self._active_friend = value friend.reset_messages() - if not Settings.get_instance()['save_history']: + if not self._settings['save_history']: friend.delete_old_messages() self._messages.clear() friend.load_corr() @@ -128,19 +152,14 @@ class ContactsManager: self._screen.account_name.setText(friend.name) self._screen.account_status.setText(friend.status_message) self._screen.account_status.setToolTip(friend.get_full_status()) - if friend.tox_id is None: - avatar_path = curr_directory() + '/images/group.png' - else: - avatar_path = (ProfileManager.get_path() + 'avatars/{}.png').format(friend.tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) - if not os.path.isfile(avatar_path): # load default image - avatar_path = curr_directory() + '/images/avatar.png' + avatar_path = friend.get_avatar_path() os.chdir(os.path.dirname(avatar_path)) pixmap = QtGui.QPixmap(avatar_path) self._screen.account_avatar.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)) except Exception as ex: # no friend found. ignore - log('Friend value: ' + str(value)) - log('Error in set active: ' + str(ex)) + util.log('Friend value: ' + str(value)) + util.log('Error in set active: ' + str(ex)) raise def set_active_by_number_and_type(self, number, is_friend): @@ -167,7 +186,6 @@ class ContactsManager: :param filter_str: show contacts which name contains this substring """ filter_str = filter_str.lower() - settings = Settings.get_instance() number = self.get_active_number() is_friend = self.is_active_a_friend() if sorting > 1: @@ -203,8 +221,8 @@ class ContactsManager: else: self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, 0)) self._sorting, self._filter_string = sorting, filter_str - settings['sorting'] = self._sorting - settings.save() + self._settings['sorting'] = self._sorting + self._settings.save() self.set_active_by_number_and_type(number, is_friend) def update_filtration(self): @@ -213,7 +231,6 @@ class ContactsManager: """ self.filtration_and_sorting(self._sorting, self._filter_string) - def create_friend_item(self): """ Method-factory @@ -221,7 +238,42 @@ class ContactsManager: """ return self._factory.friend_item() + # ----------------------------------------------------------------------------------------------------------------- + # Friend getters + # ----------------------------------------------------------------------------------------------------------------- + def get_friend_by_number(self, num): + return list(filter(lambda x: x.number == num and type(x) is Friend, self._contacts))[0] + + def get_last_message(self): + if self._active_friend + 1: + return self.get_curr_friend().get_last_message_text() + else: + return '' + + def get_active_number(self): + return self.get_curr_friend().number if self._active_friend + 1 else -1 + + def get_active_name(self): + return self.get_curr_friend().name if self._active_friend + 1 else '' + + def is_active_online(self): + return self._active_friend + 1 and self.get_curr_friend().status is not None + + def new_name(self, number, name): + friend = self.get_friend_by_number(number) + tmp = friend.name + friend.set_name(name) + name = str(name, 'utf-8') + if friend.name == name and tmp != name: + message = QtWidgets.QApplication.translate("MainWindow", 'User {} is now known as {}') + message = message.format(tmp, name) + friend.append_message(InfoMessage(message, time.time())) + friend.actions = True + if number == self.get_active_number(): + self.create_message_item(message, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) + self._messages.scrollToBottom() + self.set_active(None) # ----------------------------------------------------------------------------------------------------------------- # Work with friends (remove, block, set alias, get public key) @@ -233,19 +285,11 @@ class ContactsManager: """ friend = self._contacts[num] name = friend.name - dialog = QtWidgets.QApplication.translate('MainWindow', - "Enter new alias for friend {} or leave empty to use friend's name:") - dialog = dialog.format(name) - title = QtWidgets.QApplication.translate('MainWindow', - 'Set alias') - text, ok = QtWidgets.QInputDialog.getText(None, - title, - dialog, - QtWidgets.QLineEdit.Normal, - 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 ok: - settings = Settings.get_instance() - aliases = settings['friends_aliases'] + aliases = self._settings['friends_aliases'] if text: friend.name = bytes(text, 'utf-8') try: @@ -262,7 +306,7 @@ class ContactsManager: del aliases[index] except: pass - settings.save() + self._settings.save() if num == self.get_active_number() and self.is_active_a_friend(): self.update() @@ -275,15 +319,14 @@ class ContactsManager: :param num: number of friend in list """ friend = self._contacts[num] - settings = Settings.get_instance() try: - index = list(map(lambda x: x[0], settings['friends_aliases'])).index(friend.tox_id) - del settings['friends_aliases'][index] + index = list(map(lambda x: x[0], self._settings['friends_aliases'])).index(friend.tox_id) + del self._settings['friends_aliases'][index] except: pass - if friend.tox_id in settings['notes']: - del settings['notes'][friend.tox_id] - settings.save() + if friend.tox_id in self._settings['notes']: + del self._settings['notes'][friend.tox_id] + self._settings.save() self.clear_history(num) if self._history.friend_exists_in_db(friend.tox_id): self._history.delete_friend_from_db(friend.tox_id) @@ -296,7 +339,7 @@ class ContactsManager: else: self.set_active(0) data = self._tox.get_savedata() - ProfileManager.get_instance().save_profile(data) + self._profile_manager.save_profile(data) def add_friend(self, tox_id): """ @@ -309,7 +352,7 @@ class ContactsManager: self._history.add_friend_to_db(tox_id) message_getter = self._history.messages_getter(tox_id) except Exception as ex: # something is wrong - log('Accept friend request failed! ' + str(ex)) + util.log('Accept friend request failed! ' + str(ex)) message_getter = None friend = Friend(message_getter, num, tox_id, '', item, tox_id) self._contacts.append(friend) @@ -319,17 +362,15 @@ class ContactsManager: 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_id[:TOX_PUBLIC_KEY_SIZE * 2]: + if tox_id == self._tox.self_get_address[:TOX_PUBLIC_KEY_SIZE * 2]: return - settings = Settings.get_instance() - if tox_id not in settings['blocked']: - settings['blocked'].append(tox_id) - settings.save() + 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) - data = self._tox.get_savedata() - ProfileManager.get_instance().save_profile(data) + self.save_profile() except: # not in friend list pass @@ -339,13 +380,11 @@ class ContactsManager: :param tox_id: tox id of contact :param add_to_friend_list: add this contact to friend list or not """ - s = Settings.get_instance() - s['blocked'].remove(tox_id) - s.save() + self._settings['blocked'].remove(tox_id) + self._settings.save() if add_to_friend_list: self.add_friend(tox_id) - data = self._tox.get_savedata() - ProfileManager.get_instance().save_profile(data) + self.save_profile() # ----------------------------------------------------------------------------------------------------------------- # Friend requests @@ -366,11 +405,9 @@ class ContactsManager: raise Exception('TOX DNS lookup failed') if len(tox_id) == TOX_PUBLIC_KEY_SIZE * 2: # public key self.add_friend(tox_id) - msgBox = QtWidgets.QMessageBox() - msgBox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "Friend added")) - text = (QtWidgets.QApplication.translate("MainWindow", 'Friend added without sending friend request')) - msgBox.setText(text) - msgBox.exec_() + title = util_ui.tr('Friend added') + text = util_ui.tr('Friend added without sending friend request') + util_ui.message_box(text, title) else: result = self._tox.friend_add(tox_id, message.encode('utf-8')) tox_id = tox_id[:TOX_PUBLIC_KEY_SIZE * 2] @@ -380,11 +417,10 @@ class ContactsManager: message_getter = self._history.messages_getter(tox_id) friend = Friend(message_getter, result, tox_id, '', item, tox_id) self._contacts.append(friend) - data = self._tox.get_savedata() - ProfileManager.get_instance().save_profile(data) + self.save_profile() return True except Exception as ex: # wrong data - log('Friend request failed with ' + str(ex)) + util.log('Friend request failed with ' + str(ex)) return str(ex) def process_friend_request(self, tox_id, message): @@ -396,13 +432,34 @@ class ContactsManager: if tox_id in self._settings['blocked']: return try: - text = QtWidgets.QApplication.translate('MainWindow', 'User {} wants to add you to contact list. Message:\n{}') - info = text.format(tox_id, message) - fr_req = QtWidgets.QApplication.translate('MainWindow', 'Friend request') - reply = QtWidgets.QMessageBox.question(None, fr_req, info, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) - if reply == QtWidgets.QMessageBox.Yes: # accepted + 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() - ProfileManager.get_instance().save_profile(data) + self._profile_manager.save_profile(data) except Exception as ex: # something is wrong - log('Accept friend request failed! ' + str(ex)) + util.log('Accept friend request failed! ' + str(ex)) + + # ----------------------------------------------------------------------------------------------------------------- + # Typing notifications + # ----------------------------------------------------------------------------------------------------------------- + + def send_typing(self, typing): + """ + Send typing notification to a friend + """ + if self._settings['typing_notifications'] and self._active_friend + 1: + try: + friend = self.get_curr_friend() + if friend.status is not None: + self._tox.self_set_typing(friend.number, typing) + except: + pass + + def friend_typing(self, friend_number, typing): + """ + Display incoming typing notification + """ + if friend_number == self.get_active_number() and self.is_active_a_friend(): + self._screen.typing.setVisible(typing) diff --git a/toxygen/contacts/friend.py b/toxygen/contacts/friend.py index b80032a..4a22329 100644 --- a/toxygen/contacts/friend.py +++ b/toxygen/contacts/friend.py @@ -8,8 +8,8 @@ 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) + 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 # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index 05faaa9..b247281 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -1,5 +1,5 @@ from contacts import contact -import util +import util.util as util from PyQt5 import QtGui, QtCore from wrapper import toxcore_enums_and_consts as constants @@ -8,8 +8,8 @@ from wrapper 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) + def __init__(self, profile_manager, name, status_message, widget, tox, group_number): + super().__init__(None, group_number, profile_manager, name, status_message, widget, None) self._tox = tox self.set_status(constants.TOX_USER_STATUS['NONE']) @@ -23,13 +23,9 @@ class GroupChat(contact.Contact): 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() + @staticmethod + def get_default_avatar_name(): + return 'group.png' def remove_invalid_unsent_files(self): pass diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index ef26a51..a107602 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -22,12 +22,13 @@ class Profile(basecontact.BaseContact): """ Profile of current toxygen user. Contains friends list, tox instance """ - def __init__(self, tox, screen): + def __init__(self, profile_manager, tox, screen): """ :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.user_info, @@ -86,43 +87,6 @@ class Profile(basecontact.BaseContact): self._tox_id = self._tox.self_get_address() return self._tox_id - # ----------------------------------------------------------------------------------------------------------------- - # Friend getters - # ----------------------------------------------------------------------------------------------------------------- - - def get_friend_by_number(self, num): - return list(filter(lambda x: x.number == num and type(x) is Friend, self._contacts))[0] - - def get_last_message(self): - if self._active_friend + 1: - return self.get_curr_friend().get_last_message_text() - else: - return '' - - def get_active_number(self): - return self.get_curr_friend().number if self._active_friend + 1 else -1 - - def get_active_name(self): - return self.get_curr_friend().name if self._active_friend + 1 else '' - - def is_active_online(self): - return self._active_friend + 1 and self.get_curr_friend().status is not None - - def new_name(self, number, name): - friend = self.get_friend_by_number(number) - tmp = friend.name - friend.set_name(name) - name = str(name, 'utf-8') - if friend.name == name and tmp != name: - message = QtWidgets.QApplication.translate("MainWindow", 'User {} is now known as {}') - message = message.format(tmp, name) - friend.append_message(InfoMessage(message, time.time())) - friend.actions = True - if number == self.get_active_number(): - self.create_message_item(message, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) - self._messages.scrollToBottom() - self.set_active(None) - # ----------------------------------------------------------------------------------------------------------------- # Friend connection status callbacks # ----------------------------------------------------------------------------------------------------------------- @@ -168,29 +132,6 @@ class Profile(basecontact.BaseContact): self._paused_file_transfers[ft.get_id()] = [ft.get_path(), friend_num, True, ft.total_size()] self.cancel_transfer(friend_num, file_num, True) - # ----------------------------------------------------------------------------------------------------------------- - # Typing notifications - # ----------------------------------------------------------------------------------------------------------------- - - def send_typing(self, typing): - """ - Send typing notification to a friend - """ - if Settings.get_instance()['typing_notifications'] and self._active_friend + 1: - try: - friend = self.get_curr_friend() - if friend.status is not None: - self._tox.self_set_typing(friend.number, typing) - except: - pass - - def friend_typing(self, friend_number, typing): - """ - Display incoming typing notification - """ - if friend_number == self.get_active_number() and self.is_active_a_friend(): - self._screen.typing.setVisible(typing) - # ----------------------------------------------------------------------------------------------------------------- # Private messages # ----------------------------------------------------------------------------------------------------------------- @@ -294,7 +235,6 @@ class Profile(basecontact.BaseContact): # Friend, message and file transfer items creation # ----------------------------------------------------------------------------------------------------------------- - def create_message_item(self, text, time, owner, message_type, append=True): if message_type == MESSAGE_TYPE['INFO_MESSAGE']: name = '' diff --git a/toxygen/user_data/profile_manager.py b/toxygen/user_data/profile_manager.py index b897fc3..823dbc0 100644 --- a/toxygen/user_data/profile_manager.py +++ b/toxygen/user_data/profile_manager.py @@ -13,7 +13,7 @@ class ProfileManager: self._path = path self._directory = os.path.basename(path) # create /avatars if not exists: - directory = path + 'avatars' + directory = util.join_path(self._directory, 'avatars') if not os.path.exists(directory): os.makedirs(directory) diff --git a/toxygen/util/ui.py b/toxygen/util/ui.py index c485de4..4b9e806 100644 --- a/toxygen/util/ui.py +++ b/toxygen/util/ui.py @@ -1,21 +1,28 @@ -import PyQt5 +from PyQt5 import QtWidgets def tr(s): - return PyQt5.QtWidgets.QApplication.translate('Toxygen', s) + return QtWidgets.QApplication.translate('Toxygen', s) def question(text, title=None): - reply = PyQt5.QtWidgets.QMessageBox.question(None, title or 'Toxygen', text, - PyQt5.QtWidgets.QMessageBox.Yes, - PyQt5.QtWidgets.QMessageBox.No) - return reply == PyQt5.QtWidgets.QMessageBox.Yes + reply = QtWidgets.QMessageBox.question(None, title or 'Toxygen', text, + QtWidgets.QMessageBox.Yes, + QtWidgets.QMessageBox.No) + return reply == QtWidgets.QMessageBox.Yes def message_box(text, title=None): - m_box = PyQt5.QtWidgets.QMessageBox() + m_box = QtWidgets.QMessageBox() m_box.setText(tr(text)) m_box.setWindowTitle(title or 'Toxygen') m_box.exec_() + +def text_dialog(text, title='', default_value=''): + text, ok = QtWidgets.QInputDialog.getText(None, title, text, QtWidgets.QLineEdit.Normal, default_value) + + return text, ok + + # TODO: move all dialogs here diff --git a/toxygen/util/util.py b/toxygen/util/util.py index 5bf5f99..61df479 100644 --- a/toxygen/util/util.py +++ b/toxygen/util/util.py @@ -79,6 +79,10 @@ def curr_time(): return time.strftime('%H:%M') +def join_path(a, b): + return os.path.join(a, b) + + def copy(src, dest): if not os.path.exists(dest): os.makedirs(dest) From a9d2d3d8093ed4793fe0873b724faf33df074d60 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 26 Apr 2018 23:54:39 +0300 Subject: [PATCH 010/138] more refacrtoring - contact provider, deps creation --- toxygen/app.py | 29 ++- toxygen/bootstrap/bootstrap.py | 13 +- toxygen/contacts/basecontact.py | 2 +- toxygen/contacts/contact_provider.py | 38 ++++ toxygen/contacts/friend_factory.py | 38 ++++ toxygen/contacts/profile.py | 8 +- toxygen/file_transfers/file_transfers.py | 27 +-- .../file_transfers/file_transfers_handler.py | 30 +-- toxygen/history/database.py | 11 +- toxygen/messenger/messages.py | 52 ++++- toxygen/middleware/threads.py | 6 +- toxygen/smileys/smileys.py | 4 +- toxygen/ui/items_factory.py | 6 +- toxygen/ui/list_items.py | 202 ----------------- toxygen/ui/main_screen.py | 138 ++++++------ toxygen/ui/menu.py | 1 + toxygen/ui/messages_widgets.py | 212 ++++++++++++++++++ toxygen/ui/widgets.py | 6 +- toxygen/user_data/profile_manager.py | 8 +- toxygen/util/util.py | 14 ++ 20 files changed, 500 insertions(+), 345 deletions(-) create mode 100644 toxygen/contacts/contact_provider.py create mode 100644 toxygen/contacts/friend_factory.py create mode 100644 toxygen/ui/messages_widgets.py diff --git a/toxygen/app.py b/toxygen/app.py index e038980..8dd4ea8 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -15,6 +15,11 @@ from plugin_support.plugin_support import PluginLoader from ui.main_screen import MainWindow from ui import tray import util.ui as util_ui +import util.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 class App: @@ -23,7 +28,7 @@ class App: self._version = version self._app = self._settings = self._profile_manager = self._plugin_loader = None self._tox = self._ms = self._init = self._main_loop = self._av_loop = None - self._uri = self._toxes = self._tray = None + self._uri = self._toxes = self._tray = self._file_transfer_handler = self._contacts_provider = None if uri is not None and uri.startswith('tox:'): self._uri = uri[4:] self._path = path_to_profile @@ -93,7 +98,11 @@ class App: return self._ms = MainWindow(self._settings, self._tox, self.reset, self._tray) - profile = self._ms.profile + self._friend_factory = FriendFactory(None, self._profile_manager, self._settings, self._tox) + self._contacts_provider = ContactProvider(self._tox, self._friend_factory) + self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider) + profile = Profile(self._profile_manager, self._tox, self._ms, self._file_transfer_handler) + self._ms.profile = profile self._ms.show() self._tray = tray.init_tray(profile, self._settings, self._ms) @@ -110,7 +119,17 @@ class App: self._ms.add_contact(self._uri) self._app.lastWindowClosed.connect(self._app.quit) - self._app.exec_() + # main + while True: + try: + self._app.exec_() + except KeyboardInterrupt: + print('Closing Toxygen...') + break + except Exception as ex: + util.log('Unhandled exception: ' + str(ex)) + else: + break self._plugin_loader.stop() self.stop_threads() @@ -132,7 +151,7 @@ class App: self._tox = self.create_tox(data) self.start_threads() - self._plugin_loader.set_tox(self._tox) + # TODO: foreach in list of tox savers set_tox return self._tox @@ -178,7 +197,7 @@ class App: def start_threads(self): # init thread - self._init = threads.InitThread(self._tox, self._plugin_loader) + self._init = threads.InitThread(self._tox, self._plugin_loader, self._settings) self._init.start() # starting threads for tox iterate and toxav iterate diff --git a/toxygen/bootstrap/bootstrap.py b/toxygen/bootstrap/bootstrap.py index 87c6d9c..aa6f863 100644 --- a/toxygen/bootstrap/bootstrap.py +++ b/toxygen/bootstrap/bootstrap.py @@ -38,13 +38,12 @@ def save_nodes(nodes): fl.write(nodes) -def download_nodes_list(): +def download_nodes_list(settings): url = 'https://nodes.tox.chat/json' - s = settings.Settings.get_instance() - if not s['download_nodes_list']: + if not settings['download_nodes_list']: return - if not s['proxy_type']: # no proxy + if not settings['proxy_type']: # no proxy try: req = urllib.request.Request(url) req.add_header('Content-Type', 'application/json') @@ -57,9 +56,9 @@ def download_nodes_list(): 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']) + QtNetwork.QNetworkProxy.Socks5Proxy if settings['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy) + proxy.setHostName(settings['proxy_host']) + proxy.setPort(settings['proxy_port']) netman.setProxy(proxy) try: request = QtNetwork.QNetworkRequest() diff --git a/toxygen/contacts/basecontact.py b/toxygen/contacts/basecontact.py index da992de..71690dd 100644 --- a/toxygen/contacts/basecontact.py +++ b/toxygen/contacts/basecontact.py @@ -106,7 +106,7 @@ class BaseContact: return self._widget.avatar_label.pixmap() def get_avatar_path(self): - directory = util.join_path(self._profile_manager.get_path(), 'avatars') + directory = util.join_path(self._profile_manager.get_dir(), 'avatars') avatar_path = util.join_path(directory, '{}.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 = util.join_path(util.get_images_directory(), self.get_default_avatar_name()) diff --git a/toxygen/contacts/contact_provider.py b/toxygen/contacts/contact_provider.py new file mode 100644 index 0000000..10b778b --- /dev/null +++ b/toxygen/contacts/contact_provider.py @@ -0,0 +1,38 @@ +import util.util as util + + +class ContactProvider(util.ToxSave): + + def __init__(self, tox, friend_factory): + super().__init__(tox) + self._friend_factory = friend_factory + self._cache = {} # key - contact's public key, value - contact instance + + 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_gc_by_number(self): + pass + + def get_gc_by_public_key(self): + pass + + def clear_cache(self): + self._cache.clear() + + 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 diff --git a/toxygen/contacts/friend_factory.py b/toxygen/contacts/friend_factory.py new file mode 100644 index 0000000..556f2d4 --- /dev/null +++ b/toxygen/contacts/friend_factory.py @@ -0,0 +1,38 @@ +from contacts.friend import Friend + + +class FriendFactory: + + def __init__(self, history, profile_manager, settings, tox): + self._history, self._profile_manager = history, profile_manager + self._settings, self._tox = settings, tox + + 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) + if not self._history.friend_exists_in_db(tox_id): + self._history.add_friend_to_db(tox_id) + message_getter = self._history.messages_getter(tox_id) + friend = Friend(self._profile_manager, message_getter, friend_number, name, status_message, item, tox_id) + friend.set_alias(alias) + + return friend + + 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_item(self): + """ + Method-factory + :return: new widget for friend instance + """ + return self._factory.friend_item() diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 4181753..0bfc764 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -40,7 +40,8 @@ class Profile(basecontact.BaseContact): self._file_transfers = {} # dict of file transfers. key - tuple (friend_number, file_number) self._load_history = True self._waiting_for_reconnection = False - self._factory = items_factory.ItemsFactory(self._screen.friends_list, self._messages) + #self._factory = items_factory.ItemsFactory(self._screen.friends_list, self._messages) + self._contacts_manager = None #self._show_avatars = settings['show_avatars'] # ----------------------------------------------------------------------------------------------------------------- @@ -77,7 +78,7 @@ class Profile(basecontact.BaseContact): self._messages.scrollToBottom() def set_status_message(self, value): - super(Profile, self).set_status_message(value) + super().set_status_message(value) self._tox.self_set_status_message(self._status_message.encode('utf-8')) def new_nospam(self): @@ -85,6 +86,7 @@ class Profile(basecontact.BaseContact): import random self._tox.self_set_nospam(random.randint(0, 4294967295)) # no spam - uint32 self._tox_id = self._tox.self_get_address() + return self._tox_id # ----------------------------------------------------------------------------------------------------------------- @@ -296,7 +298,7 @@ class Profile(basecontact.BaseContact): self.status = None for friend in self._contacts: friend.number = self._tox.friend_by_public_key(friend.tox_id) # numbers update - self.update_filtration() + self._contacts_manager.update_filtration() def reconnect(self): self._waiting_for_reconnection = False diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py index 6c65809..5aab6f9 100644 --- a/toxygen/file_transfers/file_transfers.py +++ b/toxygen/file_transfers/file_transfers.py @@ -48,7 +48,7 @@ class FileTransfer(QtCore.QObject): """ def __init__(self, path, tox, friend_number, size, file_number=None): - QtCore.QObject.__init__(self) + super().__init__(self) self._path = path self._tox = tox self._friend_number = friend_number @@ -134,7 +134,7 @@ class SendTransfer(FileTransfer): size = getsize(path) else: 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_number = tox.file_send(friend_number, kind, size, file_id, bytes(basename(path), 'utf-8') if path else b'') @@ -168,11 +168,11 @@ class SendAvatar(SendTransfer): def __init__(self, path, tox, friend_number): if path is None: - hash = None + avatar_hash = None else: with open(path, 'rb') as fl: - hash = Tox.hash(fl.read()) - super(SendAvatar, self).__init__(path, tox, friend_number, TOX_FILE_KIND['AVATAR'], hash) + avatar_hash = Tox.hash(fl.read()) + super(SendAvatar, self).__init__(path, tox, friend_number, TOX_FILE_KIND['AVATAR'], avatar_hash) class SendFromBuffer(FileTransfer): @@ -181,7 +181,7 @@ class SendFromBuffer(FileTransfer): """ 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._data = data self._file_number = tox.file_send(friend_number, TOX_FILE_KIND['DATA'], @@ -206,10 +206,10 @@ class SendFromBuffer(FileTransfer): class SendFromFileBuffer(SendTransfer): def __init__(self, *args): - super(SendFromFileBuffer, self).__init__(*args) + super().__init__(*args) def send_chunk(self, position, size): - super(SendFromFileBuffer, self).send_chunk(position, size) + super().send_chunk(position, size) if not size: chdir(dirname(self._path)) remove(self._path) @@ -222,7 +222,7 @@ class SendFromFileBuffer(SendTransfer): class ReceiveTransfer(FileTransfer): 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_size = position self._file.truncate(position) @@ -231,11 +231,12 @@ class ReceiveTransfer(FileTransfer): self._done = position def cancel(self): - super(ReceiveTransfer, self).cancel() + super().cancel() remove(self._path) def total_size(self): self._missed.add(self._file_size) + return min(self._missed) def write_chunk(self, position, data): @@ -273,7 +274,7 @@ class ReceiveToBuffer(FileTransfer): """ def __init__(self, tox, friend_number, size, file_number): - super(ReceiveToBuffer, self).__init__(None, tox, friend_number, size, file_number) + super().__init__(None, tox, friend_number, size, file_number) self._data = bytes() self._data_size = 0 @@ -306,7 +307,7 @@ class ReceiveAvatar(ReceiveTransfer): def __init__(self, tox, friend_number, size, file_number): path = settings.ProfileManager.get_path() + 'avatars/{}.png'.format(tox.friend_get_public_key(friend_number)) - super(ReceiveAvatar, self).__init__(path + '.tmp', tox, friend_number, size, file_number) + super().__init__(path + '.tmp', tox, friend_number, size, file_number) if size > self.MAX_AVATAR_SIZE: self.send_control(TOX_FILE_CONTROL['CANCEL']) self._file.close() @@ -333,7 +334,7 @@ class ReceiveAvatar(ReceiveTransfer): self.send_control(TOX_FILE_CONTROL['RESUME']) def write_chunk(self, position, data): - super(ReceiveAvatar, self).write_chunk(position, data) + super().write_chunk(position, data) if self.state: avatar_path = self._path[:-4] if exists(avatar_path): diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index 6431fe4..11c47c5 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -1,14 +1,18 @@ from file_transfers.file_transfers import * from messenger.messages import * +from history.database import MESSAGE_OWNER import os +import util.util as util class FileTransfersHandler: - def __init__(self, tox, settings): + def __init__(self, tox, settings, contact_provider): self._tox = tox self._settings = settings + self._contact_provider = contact_provider 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] @@ -24,9 +28,9 @@ class FileTransfersHandler: :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 settings['auto_accept_from_friends'] - inline = is_inline(file_name) and settings['allow_inline'] + 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: @@ -55,7 +59,7 @@ class FileTransfersHandler: file_number) elif auto: - path = settings['auto_accept_path'] or curr_directory() + path = self._settings['auto_accept_path'] or util.curr_directory() self.accept_transfer(None, path + '/' + file_name, friend_number, file_number, size) tm = TransferMessage(MESSAGE_OWNER['FRIEND'], time.time(), @@ -90,7 +94,7 @@ class FileTransfersHandler: :param file_number: file number :param already_cancelled: was cancelled by friend """ - i = self.get_friend_by_number(friend_number).update_transfer_data(file_number, + i = self._get_friend_by_number(friend_number).update_transfer_data(file_number, TOX_FILE_TRANSFER_STATE['CANCELLED']) if (friend_number, file_number) in self._file_transfers: tr = self._file_transfers[(friend_number, file_number)] @@ -128,8 +132,8 @@ class FileTransfersHandler: """ Resume transfer with specified data """ - self.get_friend_by_number(friend_number).update_transfer_data(file_number, - TOX_FILE_TRANSFER_STATE['RUNNING']) + # self.get_friend_by_number(friend_number).update_transfer_data(file_number, + # TOX_FILE_TRANSFER_STATE['RUNNING']) tr = self._file_transfers[(friend_number, file_number)] if by_friend: tr.state = TOX_FILE_TRANSFER_STATE['RUNNING'] @@ -261,7 +265,7 @@ class FileTransfersHandler: self.get_friend_by_number(friend_number).load_avatar() if friend_number == self.get_active_number() and self.is_active_a_friend(): self.set_active(None) - elif t is ReceiveToBuffer or (t is SendFromBuffer and Settings.get_instance()['allow_inline']): # inline image + elif t is ReceiveToBuffer or (t is SendFromBuffer and self._settings.get_instance()['allow_inline']): # inline image print('inline') inline = InlineImage(transfer.get_data()) i = self.get_friend_by_number(friend_number).update_transfer_data(file_number, @@ -286,13 +290,10 @@ class FileTransfersHandler: # Avatars support # ----------------------------------------------------------------------------------------------------------------- - def send_avatar(self, friend_number): + def send_avatar(self, friend_number, avatar_path=None): """ :param friend_number: number of friend who should get new avatar """ - avatar_path = (ProfileManager.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) - if not os.path.isfile(avatar_path): # reset image - avatar_path = None sa = SendAvatar(avatar_path, self._tox, friend_number) self._file_transfers[(friend_number, sa.get_file_number())] = sa @@ -311,3 +312,6 @@ class FileTransfersHandler: self.get_friend_by_number(friend_number).load_avatar() if self.get_active_number() == friend_number and self.is_active_a_friend(): self.set_active(None) + + def _get_friend_by_number(self, friend_number): + return self._contact_provider.get_friend_by_number(friend_number) diff --git a/toxygen/history/database.py b/toxygen/history/database.py index ff9ce8e..9960b54 100644 --- a/toxygen/history/database.py +++ b/toxygen/history/database.py @@ -2,7 +2,6 @@ from sqlite3 import connect from user_data import settings from os import chdir import os.path -from user_data.toxes import ToxES PAGE_SIZE = 42 @@ -14,10 +13,11 @@ SAVE_MESSAGES = 500 MESSAGE_OWNER = { 'ME': 0, 'FRIEND': 1, - 'NOT_SENT': 2 + 'NOT_SENT': 2, + 'GC_PEER': 3 } -# TODO: unique message id and ngc support, db name as profile name +# TODO: unique message id and ngc support, profile name as db name class Database: @@ -58,9 +58,8 @@ class Database: 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) + if self._toxes.has_password(): + data = self._toxes.pass_encrypt(data) with open(new_path, 'wb') as fout: fout.write(data) diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index 4baa29a..e8abec7 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -1,3 +1,4 @@ +from history.database import MESSAGE_OWNER MESSAGE_TYPE = { @@ -9,28 +10,54 @@ MESSAGE_TYPE = { } +class MessageAuthor: + + def __init__(self, author_name, author_type): + self.name = author_name + self.type = author_type + + class Message: - def __init__(self, message_id, message_type, owner, time): + def __init__(self, message_id, message_type, author, time): self._time = time self._type = message_type - self._owner = owner + self._author = author self._message_id = message_id + self._widget = None def get_type(self): return self._type - def get_owner(self): - return self._owner + type = property(get_type) - def mark_as_sent(self): - self._owner = 0 + def get_author(self): + return self._author + + author = property(get_author) def get_message_id(self): return self._message_id message_id = property(get_message_id) + def get_widget(self): + if self._widget is None: + self._widget = self._create_widget() + + return self._widget + + widget = property(get_widget) + + def remove_widget(self): + self._widget = None + + def mark_as_sent(self): + self._author.author_type = MESSAGE_OWNER['ME'] + + def _create_widget(self): + pass + class TextMessage(Message): """ @@ -38,12 +65,15 @@ class TextMessage(Message): """ def __init__(self, id, message, owner, time, message_type): - super(TextMessage, self).__init__(id, message_type, owner, time) + super().__init__(id, message_type, owner, time) self._message = message def get_data(self): return self._message, self._owner, self._time, self._type + def _create_widget(self): + return + class GroupChatMessage(TextMessage): @@ -61,7 +91,7 @@ class TransferMessage(Message): """ def __init__(self, id, owner, time, status, size, name, friend_number, file_number): - super(TransferMessage, self).__init__(MESSAGE_TYPE['FILE_TRANSFER'], owner, time) + super().__init__(MESSAGE_TYPE['FILE_TRANSFER'], owner, time) self._status = status self._size = size self._file_name = name @@ -88,7 +118,7 @@ class TransferMessage(Message): class UnsentFile(Message): def __init__(self, id, path, data, time): - super(UnsentFile, self).__init__(id, MESSAGE_TYPE['FILE_TRANSFER'], 0, time) + super().__init__(id, MESSAGE_TYPE['FILE_TRANSFER'], 0, time) self._data, self._path = data, path def get_data(self): @@ -104,7 +134,7 @@ class InlineImage(Message): """ def __init__(self, id, data): - super(InlineImage, self).__init__(id, MESSAGE_TYPE['INLINE'], None, None) + super().__init__(id, MESSAGE_TYPE['INLINE'], None, None) self._data = data def get_data(self): @@ -114,4 +144,4 @@ class InlineImage(Message): class InfoMessage(TextMessage): def __init__(self, id, message, time): - super(InfoMessage, self).__init__(id, message, None, time, MESSAGE_TYPE['INFO_MESSAGE']) + super().__init__(id, message, None, time, MESSAGE_TYPE['INFO_MESSAGE']) diff --git a/toxygen/middleware/threads.py b/toxygen/middleware/threads.py index 2d7bdf4..5d722f0 100644 --- a/toxygen/middleware/threads.py +++ b/toxygen/middleware/threads.py @@ -18,13 +18,13 @@ class BaseThread(threading.Thread): class InitThread(BaseThread): - def __init__(self, tox, plugin_loader): + def __init__(self, tox, plugin_loader, settings): super().__init__() - self._tox, self._plugin_loader = tox, plugin_loader + self._tox, self._plugin_loader, self._settings = tox, plugin_loader, settings def run(self): # download list of nodes if needed - download_nodes_list() + download_nodes_list(self._settings) # start plugins self._plugin_loader.load() # bootstrap diff --git a/toxygen/smileys/smileys.py b/toxygen/smileys/smileys.py index abf6990..c20d1a7 100644 --- a/toxygen/smileys/smileys.py +++ b/toxygen/smileys/smileys.py @@ -25,7 +25,7 @@ class SmileyLoader: pack_name = self._settings['smiley_pack'] if self._settings['smileys'] and 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: with open(path, encoding='utf8') as fl: self._smileys = json.loads(fl.read()) @@ -45,7 +45,7 @@ class SmileyLoader: print('Smiley pack {} was not loaded. Error: {}'.format(pack_name, ex)) 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 @staticmethod def get_packs_list(self): diff --git a/toxygen/ui/items_factory.py b/toxygen/ui/items_factory.py index cdf127a..386e762 100644 --- a/toxygen/ui/items_factory.py +++ b/toxygen/ui/items_factory.py @@ -3,9 +3,9 @@ from ui.list_items import * class ItemsFactory: - def __init__(self, friends_list, messages): - self._friends = friends_list - self._messages = messages + def __init__(self, settings, plugin_loader, smiley_loader, main_screen): + self._settings, self._plugin_loader = settings, plugin_loader + self._smiley_loader, self._main_screen = smiley_loader, main_screen def friend_item(self): item = ContactItem() diff --git a/toxygen/ui/list_items.py b/toxygen/ui/list_items.py index 76e6185..1891d45 100644 --- a/toxygen/ui/list_items.py +++ b/toxygen/ui/list_items.py @@ -10,208 +10,6 @@ from user_data import settings import re -class MessageEdit(QtWidgets.QTextBrowser): - - def __init__(self, text, width, message_type, parent=None): - super(MessageEdit, self).__init__(parent) - self.urls = {} - self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.setWordWrapMode(QtGui.QTextOption.WrapAtWordBoundaryOrAnywhere) - self.document().setTextWidth(width) - self.setOpenExternalLinks(True) - self.setAcceptRichText(True) - self.setOpenLinks(False) - path = smileys.SmileyLoader.get_instance().get_smileys_path() - if path is not None: - self.setSearchPaths([path]) - self.document().setDefaultStyleSheet('a { color: #306EFF; }') - text = self.decoratedText(text) - if message_type != TOX_MESSAGE_TYPE['NORMAL']: - self.setHtml('

' + text + '

') - else: - self.setHtml(text) - font = QtGui.QFont() - font.setFamily(settings.Settings.get_instance()['font']) - font.setPixelSize(settings.Settings.get_instance()['message_font_size']) - font.setBold(False) - self.setFont(font) - self.resize(width, self.document().size().height()) - self.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse | QtCore.Qt.LinksAccessibleByMouse) - self.anchorClicked.connect(self.on_anchor_clicked) - - def contextMenuEvent(self, event): - menu = create_menu(self.createStandardContextMenu(event.pos())) - quote = menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Quote selected text')) - quote.triggered.connect(self.quote_text) - text = self.textCursor().selection().toPlainText() - if not text: - quote.setEnabled(False) - else: - import plugin_support - submenu = plugin_support.PluginLoader.get_instance().get_message_menu(menu, text) - if len(submenu): - plug = menu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins')) - plug.addActions(submenu) - menu.popup(event.globalPos()) - menu.exec_(event.globalPos()) - del menu - - def quote_text(self): - text = self.textCursor().selection().toPlainText() - if text: - from ui import main_screen - window = main_screen.MainWindow.get_instance() - text = '>' + '\n>'.join(text.split('\n')) - if window.messageEdit.toPlainText(): - text = '\n' + text - window.messageEdit.appendPlainText(text) - - def on_anchor_clicked(self, url): - text = str(url.toString()) - if text.startswith('tox:'): - from ui import menu - self.add_contact = menu.AddContact(text[4:]) - self.add_contact.show() - else: - QtGui.QDesktopServices.openUrl(url) - self.clearFocus() - - def addAnimation(self, url, fileName): - movie = QtGui.QMovie(self) - movie.setFileName(fileName) - self.urls[movie] = url - movie.frameChanged[int].connect(lambda x: self.animate(movie)) - movie.start() - - def animate(self, movie): - self.document().addResource(QtGui.QTextDocument.ImageResource, - self.urls[movie], - movie.currentPixmap()) - self.setLineWrapColumnOrWidth(self.lineWrapColumnOrWidth()) - - def decoratedText(self, text): - text = h.escape(text) # replace < and > - exp = QtCore.QRegExp( - '(' - '(?:\\b)((www\\.)|(http[s]?|ftp)://)' - '\\w+\\S+)' - '|(?:\\b)(file:///)([\\S| ]*)' - '|(?:\\b)(tox:[a-zA-Z\\d]{76}$)' - '|(?:\\b)(mailto:\\S+@\\S+\\.\\S+)' - '|(?:\\b)(tox:\\S+@\\S+)') - offset = exp.indexIn(text, 0) - while offset != -1: # add links - url = exp.cap() - if exp.cap(2) == 'www.': - html = '{0}'.format(url) - else: - html = '{0}'.format(url) - text = text[:offset] + html + text[offset + len(exp.cap()):] - offset += len(html) - offset = exp.indexIn(text, offset) - arr = text.split('\n') - for i in range(len(arr)): # quotes - if arr[i].startswith('>'): - arr[i] = '' + arr[i][4:] + '' - text = '
'.join(arr) - text = smileys.SmileyLoader.get_instance().add_smileys_to_text(text, self) # smileys - return text - - -class MessageItem(QtWidgets.QWidget): - """ - Message in messages list - """ - def __init__(self, text, time, user='', sent=True, message_type=TOX_MESSAGE_TYPE['NORMAL'], parent=None): - QtWidgets.QWidget.__init__(self, parent) - self.name = DataLabel(self) - self.name.setGeometry(QtCore.QRect(2, 2, 95, 23)) - self.name.setTextFormat(QtCore.Qt.PlainText) - font = QtGui.QFont() - font.setFamily(settings.Settings.get_instance()['font']) - font.setPointSize(11) - font.setBold(True) - self.name.setFont(font) - self.name.setText(user) - - self.time = QtWidgets.QLabel(self) - self.time.setGeometry(QtCore.QRect(parent.width() - 60, 0, 50, 25)) - font.setPointSize(10) - font.setBold(False) - self.time.setFont(font) - self._time = time - if not sent: - movie = QtGui.QMovie(curr_directory() + '/images/spinner.gif') - self.time.setMovie(movie) - movie.start() - self.t = True - else: - self.time.setText(convert_time(time)) - self.t = False - - self.message = MessageEdit(text, parent.width() - 160, message_type, self) - if message_type != TOX_MESSAGE_TYPE['NORMAL']: - self.name.setStyleSheet("QLabel { color: #5CB3FF; }") - self.message.setAlignment(QtCore.Qt.AlignCenter) - self.time.setStyleSheet("QLabel { color: #5CB3FF; }") - self.message.setGeometry(QtCore.QRect(100, 0, parent.width() - 160, self.message.height())) - self.setFixedHeight(self.message.height()) - - def mouseReleaseEvent(self, event): - if event.button() == QtCore.Qt.RightButton and event.x() > self.time.x(): - self.listMenu = QtWidgets.QMenu() - delete_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Delete message')) - delete_item.triggered.connect(self.delete) - parent_position = self.time.mapToGlobal(QtCore.QPoint(0, 0)) - self.listMenu.move(parent_position) - self.listMenu.show() - - def delete(self): - pr = profile.Profile.get_instance() - pr.delete_message(self._time) - - def mark_as_sent(self): - if self.t: - self.time.setText(convert_time(self._time)) - self.t = False - return True - return False - - def set_avatar(self, pixmap): - self.name.setAlignment(QtCore.Qt.AlignCenter) - self.message.setAlignment(QtCore.Qt.AlignVCenter) - self.setFixedHeight(max(self.height(), 36)) - self.name.setFixedHeight(self.height()) - self.message.setFixedHeight(self.height()) - self.name.setPixmap(pixmap.scaled(30, 30, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)) - - def select_text(self, text): - tmp = self.message.toHtml() - text = h.escape(text) - strings = re.findall(text, tmp, flags=re.IGNORECASE) - for s in strings: - tmp = self.replace_all(tmp, s) - self.message.setHtml(tmp) - - @staticmethod - def replace_all(text, substring): - i, l = 0, len(substring) - while i < len(text) - l + 1: - index = text[i:].find(substring) - if index == -1: - break - i += index - lgt, rgt = text[i:].find('<'), text[i:].find('>') - if rgt < lgt: - i += rgt + 1 - continue - sub = '{}'.format(substring) - text = text[:i] + sub + text[i + l:] - i += len(sub) - return text - - class ContactItem(QtWidgets.QWidget): """ Contact in friends list diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 223e6dd..3852867 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -21,6 +21,7 @@ class MainWindow(QtWidgets.QMainWindow): self._saved = False if settings['show_welcome_screen']: self.ws = WelcomeScreen() + self.profile = None def setup_menu(self, window): self.menubar = QtWidgets.QMenuBar(window) @@ -108,42 +109,42 @@ class MainWindow(QtWidgets.QMainWindow): 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) + return super().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.lockApp.setText(util_ui.tr("Lock")) + self.menuPlugins.setTitle(util_ui.tr("Plugins")) + 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.actionAdd_gc.setText(util_ui.tr("Create group chat")) + 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.contact_name.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.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")) + self.online_contacts.addItem(util_ui.tr("All")) + self.online_contacts.addItem(util_ui.tr("Online")) + self.online_contacts.addItem(util_ui.tr("Online first")) + self.online_contacts.addItem(util_ui.tr("Name")) + self.online_contacts.addItem(util_ui.tr("Online and by name")) + self.online_contacts.addItem(util_ui.tr("Online first and by name")) ind = self._settings['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")) + self.importPlugin.setText(util_ui.tr("Import plugin")) + self.reloadPlugins.setText(util_ui.tr("Reload plugins")) def setup_right_bottom(self, Form): Form.resize(650, 60) @@ -353,28 +354,27 @@ class MainWindow(QtWidgets.QMainWindow): 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() + if not self._settings['close_to_tray'] or self._settings.closing: + if self._saved: + return + self._saved = True + self.profile.save_history() + self.profile.close() + 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() + QtWidgets.QApplication.closeAllWindows() + event.accept() elif QtWidgets.QSystemTrayIcon.isSystemTrayAvailable(): event.ignore() self.hide() def close_window(self): - Settings.get_instance().closing = True + self._settings.closing = True self.close() def resizeEvent(self, *args, **kwargs): @@ -471,7 +471,7 @@ class MainWindow(QtWidgets.QMainWindow): def import_plugin(self): import util directory = QtWidgets.QFileDialog.getExistingDirectory(self, - QtWidgets.QApplication.translate("MainWindow", 'Choose folder with plugin'), + util_ui.tr('Choose folder with plugin'), util.curr_directory(), QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) if directory: @@ -480,9 +480,9 @@ class MainWindow(QtWidgets.QMainWindow): util.copy(src, dest) msgBox = QtWidgets.QMessageBox() msgBox.setWindowTitle( - QtWidgets.QApplication.translate("MainWindow", "Restart Toxygen")) + util_ui.tr("Restart Toxygen")) msgBox.setText( - QtWidgets.QApplication.translate("MainWindow", 'Plugin will be loaded after restart')) + util_ui.tr('Plugin will be loaded after restart')) msgBox.exec_() def lock_app(self): @@ -492,9 +492,9 @@ class MainWindow(QtWidgets.QMainWindow): else: msgBox = QtWidgets.QMessageBox() msgBox.setWindowTitle( - QtWidgets.QApplication.translate("MainWindow", "Cannot lock app")) + util_ui.tr("Cannot lock app")) msgBox.setText( - QtWidgets.QApplication.translate("MainWindow", 'Error. Profile password is not set.')) + util_ui.tr('Error. Profile password is not set.')) msgBox.exec_() def show_menu(self): @@ -517,7 +517,7 @@ class MainWindow(QtWidgets.QMainWindow): 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') + choose = util_ui.tr('Choose file') name = QtWidgets.QFileDialog.getOpenFileName(self, choose, options=QtWidgets.QFileDialog.DontUseNativeDialog) if name[0]: self.profile.send_file(name[0]) @@ -583,33 +583,33 @@ class MainWindow(QtWidgets.QMainWindow): 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') + auto = util_ui.tr('Disallow auto accept') if allowed else util_ui.tr('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 = self.listMenu.addAction(util_ui.tr('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')) + history_menu = self.listMenu.addMenu(util_ui.tr('Chat history')) + clear_history_item = history_menu.addAction(util_ui.tr('Clear history')) + export_to_text_item = history_menu.addAction(util_ui.tr('Export as text')) + export_to_html_item = history_menu.addAction(util_ui.tr('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')) + copy_menu = self.listMenu.addMenu(util_ui.tr('Copy')) + copy_name_item = copy_menu.addAction(util_ui.tr('Name')) + copy_status_item = copy_menu.addAction(util_ui.tr('Status message')) if is_friend: - copy_key_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Public key')) + copy_key_item = copy_menu.addAction(util_ui.tr('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')) + remove_item = self.listMenu.addAction(util_ui.tr('Remove friend')) + block_item = self.listMenu.addAction(util_ui.tr('Block friend')) + notes_item = self.listMenu.addAction(util_ui.tr('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')) + invite_menu = self.listMenu.addMenu(util_ui.tr('Invite to group chat')) for i in range(len(chats)): name, number = chats[i] item = invite_menu.addAction(name) @@ -619,7 +619,7 @@ class MainWindow(QtWidgets.QMainWindow): 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 = self.listMenu.addMenu(util_ui.tr('Plugins')) plug.addActions(submenu) copy_key_item.triggered.connect(lambda: self.copy_friend_key(num)) remove_item.triggered.connect(lambda: self.remove_friend(num)) @@ -627,8 +627,8 @@ class MainWindow(QtWidgets.QMainWindow): 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 = self.listMenu.addAction(util_ui.tr('Leave chat')) + set_title_item = self.listMenu.addAction(util_ui.tr('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)) @@ -643,7 +643,7 @@ class MainWindow(QtWidgets.QMainWindow): 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 = util_ui.tr('Notes about user') user = '{} {}'.format(user, friend.name) def save_note(text): diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index 1aa12dc..9dcf180 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -16,6 +16,7 @@ class AddContact(CenteredWidget): super(AddContact, self).__init__() self.initUI(tox_id) self._adding = False + self.setAttribute(QtCore.Qt.WA_DeleteOnClose) def initUI(self, tox_id): self.setObjectName('AddContact') diff --git a/toxygen/ui/messages_widgets.py b/toxygen/ui/messages_widgets.py new file mode 100644 index 0000000..ae21f8a --- /dev/null +++ b/toxygen/ui/messages_widgets.py @@ -0,0 +1,212 @@ +from PyQt5 import QtWidgets, QtGui, QtCore +from wrapper.toxcore_enums_and_consts import * +import ui.widgets as widgets +import util.ui as util_ui +import util.util as util +import ui.menu as menu +import html as h +import re + + +class MessageEdit(QtWidgets.QTextBrowser): + + def __init__(self, settings, message_edit, smileys_loader, plugin_loader, text, width, message_type, parent=None): + super().__init__(parent) + 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.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.setWordWrapMode(QtGui.QTextOption.WrapAtWordBoundaryOrAnywhere) + self.document().setTextWidth(width) + self.setOpenExternalLinks(True) + self.setAcceptRichText(True) + self.setOpenLinks(False) + path = smileys_loader.get_smileys_path() + if path is not None: + self.setSearchPaths([path]) + self.document().setDefaultStyleSheet('a { color: #306EFF; }') + text = self.decoratedText(text) + if message_type != TOX_MESSAGE_TYPE['NORMAL']: + self.setHtml('

' + text + '

') + else: + self.setHtml(text) + font = QtGui.QFont() + font.setFamily(settings['font']) + font.setPixelSize(settings['message_font_size']) + font.setBold(False) + self.setFont(font) + self.resize(width, self.document().size().height()) + self.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse | QtCore.Qt.LinksAccessibleByMouse) + self.anchorClicked.connect(self.on_anchor_clicked) + + def contextMenuEvent(self, event): + menu = widgets.create_menu(self.createStandardContextMenu(event.pos())) + quote = menu.addAction(util_ui.tr('Quote selected text')) + quote.triggered.connect(self.quote_text) + text = self.textCursor().selection().toPlainText() + if not text: + quote.setEnabled(False) + else: + sub_menu = self._plugin_loader.get_message_menu(menu, text) + if len(sub_menu): + plugins_menu = menu.addMenu(util_ui.tr('Plugins')) + plugins_menu.addActions(sub_menu) + menu.popup(event.globalPos()) + menu.exec_(event.globalPos()) + del menu + + def quote_text(self): + text = self.textCursor().selection().toPlainText() + if not text: + return + text = '>' + '\n>'.join(text.split('\n')) + if self._message_edit.toPlainText(): + text = '\n' + text + self._message_edit.appendPlainText(text) + + def on_anchor_clicked(self, url): + text = str(url.toString()) + if text.startswith('tox:'): + self._add_contact = menu.AddContact(text[4:]) + self._add_contact.show() + else: + QtGui.QDesktopServices.openUrl(url) + self.clearFocus() + + def addAnimation(self, url, file_name): + movie = QtGui.QMovie(self) + movie.setFileName(file_name) + self.urls[movie] = url + movie.frameChanged[int].connect(lambda x: self.animate(movie)) + movie.start() + + def animate(self, movie): + self.document().addResource(QtGui.QTextDocument.ImageResource, + self.urls[movie], + movie.currentPixmap()) + self.setLineWrapColumnOrWidth(self.lineWrapColumnOrWidth()) + + def decoratedText(self, text): + text = h.escape(text) # replace < and > + exp = QtCore.QRegExp( + '(' + '(?:\\b)((www\\.)|(http[s]?|ftp)://)' + '\\w+\\S+)' + '|(?:\\b)(file:///)([\\S| ]*)' + '|(?:\\b)(tox:[a-zA-Z\\d]{76}$)' + '|(?:\\b)(mailto:\\S+@\\S+\\.\\S+)' + '|(?:\\b)(tox:\\S+@\\S+)') + offset = exp.indexIn(text, 0) + while offset != -1: # add links + url = exp.cap() + if exp.cap(2) == 'www.': + html = '{0}'.format(url) + else: + html = '{0}'.format(url) + text = text[:offset] + html + text[offset + len(exp.cap()):] + offset += len(html) + offset = exp.indexIn(text, offset) + arr = text.split('\n') + for i in range(len(arr)): # quotes + if arr[i].startswith('>'): + arr[i] = '' + arr[i][4:] + '' + text = '
'.join(arr) + text = self._smileys_loader.add_smileys_to_text(text, self) # smileys + return text + + +class MessageItem(QtWidgets.QWidget): + """ + Message in messages list + """ + def __init__(self, settings, message_edit_factory, text_message, parent=None): + QtWidgets.QWidget.__init__(self, parent) + self.name = widgets.DataLabel(self) + self.name.setGeometry(QtCore.QRect(2, 2, 95, 23)) + self.name.setTextFormat(QtCore.Qt.PlainText) + font = QtGui.QFont() + font.setFamily(settings['font']) + font.setPointSize(11) + font.setBold(True) + self.name.setFont(font) + self.name.setText(text_message.user) + + self.time = QtWidgets.QLabel(self) + self.time.setGeometry(QtCore.QRect(parent.width() - 60, 0, 50, 25)) + font.setPointSize(10) + font.setBold(False) + self.time.setFont(font) + self._time = time + if not sent: + movie = QtGui.QMovie(util.join_path(util.get_images_directory(), 'spinner.gif')) + self.time.setMovie(movie) + movie.start() + self.t = True + else: + self.time.setText(util.convert_time(time)) + self.t = False + + self.message = MessageEdit(text, parent.width() - 160, message_type, self) + if message_type != TOX_MESSAGE_TYPE['NORMAL']: + self.name.setStyleSheet("QLabel { color: #5CB3FF; }") + self.message.setAlignment(QtCore.Qt.AlignCenter) + self.time.setStyleSheet("QLabel { color: #5CB3FF; }") + self.message.setGeometry(QtCore.QRect(100, 0, parent.width() - 160, self.message.height())) + self.setFixedHeight(self.message.height()) + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.RightButton and event.x() > self.time.x(): + self.listMenu = QtWidgets.QMenu() + delete_item = self.listMenu.addAction(util_ui.tr('Delete message')) + delete_item.triggered.connect(self.delete) + parent_position = self.time.mapToGlobal(QtCore.QPoint(0, 0)) + self.listMenu.move(parent_position) + self.listMenu.show() + + def delete(self): + pr = profile.Profile.get_instance() + pr.delete_message(self._time) + + def mark_as_sent(self): + if self.t: + self.time.setText(convert_time(self._time)) + self.t = False + return True + return False + + def set_avatar(self, pixmap): + self.name.setAlignment(QtCore.Qt.AlignCenter) + self.message.setAlignment(QtCore.Qt.AlignVCenter) + self.setFixedHeight(max(self.height(), 36)) + self.name.setFixedHeight(self.height()) + self.message.setFixedHeight(self.height()) + self.name.setPixmap(pixmap.scaled(30, 30, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)) + + def select_text(self, text): + tmp = self.message.toHtml() + text = h.escape(text) + strings = re.findall(text, tmp, flags=re.IGNORECASE) + for s in strings: + tmp = self.replace_all(tmp, s) + self.message.setHtml(tmp) + + @staticmethod + def replace_all(text, substring): + i, l = 0, len(substring) + while i < len(text) - l + 1: + index = text[i:].find(substring) + if index == -1: + break + i += index + lgt, rgt = text[i:].find('<'), text[i:].find('>') + if rgt < lgt: + i += rgt + 1 + continue + sub = '{}'.format(substring) + text = text[:i] + sub + text[i + l:] + i += len(sub) + return text + diff --git a/toxygen/ui/widgets.py b/toxygen/ui/widgets.py index 7b811e7..aab027a 100644 --- a/toxygen/ui/widgets.py +++ b/toxygen/ui/widgets.py @@ -66,14 +66,14 @@ class QRightClickButton(QtWidgets.QPushButton): rightClicked = QtCore.pyqtSignal() - def __init__(self, parent): - super(QRightClickButton, self).__init__(parent) + def __init__(self, parent=None): + super().__init__(parent) def mousePressEvent(self, event): if event.button() == QtCore.Qt.RightButton: self.rightClicked.emit() else: - super(QRightClickButton, self).mousePressEvent(event) + super().mousePressEvent(event) class RubberBand(QtWidgets.QRubberBand): diff --git a/toxygen/user_data/profile_manager.py b/toxygen/user_data/profile_manager.py index 823dbc0..6107d43 100644 --- a/toxygen/user_data/profile_manager.py +++ b/toxygen/user_data/profile_manager.py @@ -11,11 +11,11 @@ class ProfileManager: self._settings = settings self._toxes = toxes self._path = path - self._directory = os.path.basename(path) + self._directory = os.path.dirname(path) # create /avatars if not exists: - directory = util.join_path(self._directory, 'avatars') - if not os.path.exists(directory): - os.makedirs(directory) + avatars_directory = util.join_path(self._directory, 'avatars') + if not os.path.exists(avatars_directory): + os.makedirs(avatars_directory) def open_profile(self): with open(self._path, 'rb') as fl: diff --git a/toxygen/util/util.py b/toxygen/util/util.py index 61df479..b93a45a 100644 --- a/toxygen/util/util.py +++ b/toxygen/util/util.py @@ -55,6 +55,11 @@ def get_stickers_directory(): return get_app_directory('stickers') +@cached +def get_smileys_directory(): + return get_app_directory('smileys') + + @cached def get_translations_directory(): return get_app_directory('translations') @@ -143,3 +148,12 @@ def is_re_valid(regex): def get_platform(): return platform.system() + + +class ToxSave: + + def __init__(self, tox): + self._tox = tox + + def set_tox(self, tox): + self._tox = tox From e9272eee2a04e682fb2f396390e2120d208f8ba1 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 29 Apr 2018 00:52:42 +0300 Subject: [PATCH 011/138] deps creation - improvements. db and history fixes. widgets creation - factory --- toxygen/app.py | 46 +++-- toxygen/av/calls_manager.py | 4 +- toxygen/contacts/contact_provider.py | 46 ++++- toxygen/contacts/contacts_manager.py | 82 +++++---- toxygen/contacts/friend_factory.py | 9 +- toxygen/contacts/profile.py | 11 +- .../file_transfers/file_transfers_handler.py | 26 +-- toxygen/history/database.py | 131 +++++++------- toxygen/history/history_loader.py | 84 +++++---- toxygen/messenger/messages.py | 6 +- toxygen/middleware/callbacks.py | 4 + toxygen/ui/av_widgets.py | 2 +- toxygen/ui/main_screen.py | 3 +- toxygen/ui/main_screen_widgets.py | 86 ++++----- toxygen/ui/menu.py | 163 +++++++++--------- toxygen/ui/widgets.py | 2 +- toxygen/ui/widgets_factory.py | 27 +++ toxygen/util/ui.py | 6 + 18 files changed, 419 insertions(+), 319 deletions(-) create mode 100644 toxygen/ui/widgets_factory.py diff --git a/toxygen/app.py b/toxygen/app.py index 8dd4ea8..423ae5f 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -20,6 +20,9 @@ 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.contacts_manager import ContactsManager +from av.calls_manager import CallsManager +from history.database import Database class App: @@ -29,6 +32,7 @@ class App: self._app = self._settings = self._profile_manager = self._plugin_loader = 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 = None if uri is not None and uri.startswith('tox:'): self._uri = uri[4:] self._path = path_to_profile @@ -97,24 +101,9 @@ class App: if self.try_to_update(): return - self._ms = MainWindow(self._settings, self._tox, self.reset, self._tray) - self._friend_factory = FriendFactory(None, self._profile_manager, self._settings, self._tox) - self._contacts_provider = ContactProvider(self._tox, self._friend_factory) - self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider) - profile = Profile(self._profile_manager, self._tox, self._ms, self._file_transfer_handler) - self._ms.profile = profile - self._ms.show() - - self._tray = tray.init_tray(profile, self._settings, self._ms) - self._tray.show() - - self._plugin_loader = PluginLoader(self._tox, self._toxes, profile, self._settings) # plugins support + self.create_dependencies() self.start_threads() - # callbacks initialization - callbacks.init_callbacks(self._tox, profile, self._settings, self._plugin_loader, None, None, None, - self._ms, self._tray) - if self._uri is not None: self._ms.add_contact(self._uri) @@ -131,6 +120,9 @@ class App: else: break + self.stop_app() + + def stop_app(self): self._plugin_loader.stop() self.stop_threads() self._tray.hide() @@ -155,6 +147,28 @@ class App: return self._tox + def create_dependencies(self): + self._ms = MainWindow(self._settings, self._tox, self.reset, self._tray) + db = Database(self._path.replace('.tox', '.db'), self._toxes) + self._friend_factory = FriendFactory(self._profile_manager, self._settings, self._tox, db) + self._contacts_provider = ContactProvider(self._tox, self._friend_factory) + self._contacts_manager = ContactsManager(self._tox, self._settings, self._ms, self._profile_manager, + self._contacts_provider, db) + self._calls_manager = CallsManager(self._tox.AV, self._settings) + self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider) + profile = Profile(self._profile_manager, self._tox, self._ms, self._file_transfer_handler) + self._ms.profile = profile + self._ms.show() + + self._tray = tray.init_tray(profile, self._settings, self._ms) + self._tray.show() + + self._plugin_loader = PluginLoader(self._tox, self._toxes, profile, self._settings) # plugins support + + # callbacks initialization + callbacks.init_callbacks(self._tox, profile, self._settings, self._plugin_loader, self._contacts_manager, + self._calls_manager, self._file_transfer_handler, self._ms, self._tray) + def load_app_styles(self): # application color scheme for theme in self._settings.built_in_themes().keys(): diff --git a/toxygen/av/calls_manager.py b/toxygen/av/calls_manager.py index 8060881..03d5c53 100644 --- a/toxygen/av/calls_manager.py +++ b/toxygen/av/calls_manager.py @@ -9,8 +9,8 @@ from ui import av_widgets class CallsManager: - def __init__(self, tox, settings): - self._call = av.calls.AV(tox.AV) # object with data about calls + def __init__(self, toxAV, settings): + 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 diff --git a/toxygen/contacts/contact_provider.py b/toxygen/contacts/contact_provider.py index 10b778b..52da937 100644 --- a/toxygen/contacts/contact_provider.py +++ b/toxygen/contacts/contact_provider.py @@ -8,6 +8,20 @@ class ContactProvider(util.ToxSave): self._friend_factory = friend_factory self._cache = {} # key - contact's public key, value - contact instance + # ----------------------------------------------------------------------------------------------------------------- + # 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 + + # ----------------------------------------------------------------------------------------------------------------- + # Friends + # ----------------------------------------------------------------------------------------------------------------- + def get_friend_by_number(self, friend_number): public_key = self._tox.friend_get_public_key(friend_number) @@ -22,17 +36,39 @@ class ContactProvider(util.ToxSave): 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) + + # ----------------------------------------------------------------------------------------------------------------- + # GC + # ----------------------------------------------------------------------------------------------------------------- + + def get_all_gc(self): + return [] + def get_gc_by_number(self): pass def get_gc_by_public_key(self): pass + # ----------------------------------------------------------------------------------------------------------------- + # All contacts + # ----------------------------------------------------------------------------------------------------------------- + + def get_all(self): + return self.get_all_friends() + self.get_all_gc() + + # ----------------------------------------------------------------------------------------------------------------- + # Caching + # ----------------------------------------------------------------------------------------------------------------- + def clear_cache(self): self._cache.clear() - 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 remove_friend_from_cache(self, friend_public_key): + if friend_public_key in self._cache: + del self._cache[friend_public_key] diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 122db5c..510dbb3 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -6,31 +6,40 @@ from PyQt5 import QtCore, QtGui, QtWidgets from messenger.messages import * from wrapper.toxcore_enums_and_consts import * from network.tox_dns import tox_dns +from history.history_loader import HistoryLoader class ContactsManager: - def __init__(self, tox, settings, screen, profile_manager): + def __init__(self, tox, settings, screen, profile_manager, contact_provider, db): self._tox = tox self._settings = settings self._screen = screen self._profile_manager = profile_manager + self._contact_provider = contact_provider self._messages = screen.messages - self._contacts, self._active_friend = [], -1 + self._contacts, self._active_contact = [], -1 self._sorting = settings['sorting'] self._filter_string = '' self._friend_item_height = 40 if settings['compact_mode'] else 70 screen.online_contacts.setCurrentIndex(int(self._sorting)) - self.load_contacts() + self._history = HistoryLoader(contact_provider, db, settings) + self._load_contacts() - def load_contacts(self): - self.load_friends() - self.load_groups() - if len(self._contacts): - self.set_active(0) + def __del__(self): + del self._history + + def _is_active_a_friend(self): + return type(self.get_curr_contact()) is Friend + + def _load_contacts(self): + self._load_friends() + self._load_groups() + # if len(self._contacts): + # self.set_active(0) self.filtration_and_sorting(self._sorting) - def load_friends(self): + def _load_friends(self): aliases = self._settings['friends_aliases'] friend_numbers = self._tox.self_get_friend_list() for friend_number in friend_numbers: # creates list of friends @@ -41,15 +50,14 @@ class ContactsManager: 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(i) - if not self._history.friend_exists_in_db(tox_id): - self._history.add_friend_to_db(tox_id) - message_getter = self._history.messages_getter(tox_id) + status_message = self._tox.friend_get_status_message(friend_number) + self._history.get_message_getter(tox_id) + message_getter = self._history.get_message_getter(tox_id) friend = Friend(self._profile_manager, message_getter, friend_number, name, status_message, item, tox_id) friend.set_alias(alias) self._contacts.append(friend) - def load_groups(self): + def _load_groups(self): pass def get_friend(self, num): @@ -57,8 +65,8 @@ class ContactsManager: return None return self._contacts[num] - def get_curr_friend(self): - return self._contacts[self._active_friend] if self._active_friend + 1 else None + 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() @@ -69,20 +77,20 @@ class ContactsManager: # ----------------------------------------------------------------------------------------------------------------- def get_active(self): - return self._active_friend + return self._active_contact def set_active(self, value=None): """ Change current active friend or update info :param value: number of new active friend in friend's list or None to update active user's data """ - if value is None and self._active_friend == -1: # nothing to update + 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_friend = -1 + self._active_contact = -1 self._screen.account_avatar.setHidden(True) self._messages.clear() self._screen.messageEdit.clear() @@ -91,16 +99,16 @@ class ContactsManager: self.send_typing(False) self._screen.typing.setVisible(False) if value is not None: - if self._active_friend + 1 and self._active_friend != value: + if self._active_contact + 1 and self._active_contact != value: try: self.get_curr_friend().curr_text = self._screen.messageEdit.toPlainText() except: pass friend = self._contacts[value] friend.remove_invalid_unsent_files() - if self._active_friend != value: + if self._active_contact != value: self._screen.messageEdit.setPlainText(friend.curr_text) - self._active_friend = value + self._active_contact = value friend.reset_messages() if not self._settings['save_history']: friend.delete_old_messages() @@ -166,14 +174,14 @@ class ContactsManager: for i in range(len(self._contacts)): c = self._contacts[i] if c.number == number and (type(c) is Friend == is_friend): - self._active_friend = i + self._active_contact = i break active_friend = property(get_active, set_active) def update(self): - if self._active_friend + 1: - self.set_active(self._active_friend) + if self._active_contact + 1: + self.set_active(self._active_contact) # ----------------------------------------------------------------------------------------------------------------- # Filtration @@ -187,7 +195,7 @@ class ContactsManager: """ filter_str = filter_str.lower() number = self.get_active_number() - is_friend = self.is_active_a_friend() + is_friend = self._is_active_a_friend() if sorting > 1: if sorting & 2: self._contacts = sorted(self._contacts, key=lambda x: int(x.status is not None), reverse=True) @@ -216,10 +224,10 @@ class ContactsManager: for index, friend in enumerate(self._contacts): friend.visibility = (friend.status is not None or not (sorting & 1)) and (filter_str in friend.name.lower()) friend.visibility = friend.visibility or friend.messages or friend.actions - if friend.visibility: - self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, self._friend_item_height)) - else: - self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, 0)) + # if friend.visibility: + # self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, self._friend_item_height)) + # else: + # self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, 0)) self._sorting, self._filter_string = sorting, filter_str self._settings['sorting'] = self._sorting self._settings.save() @@ -236,7 +244,7 @@ class ContactsManager: Method-factory :return: new widget for friend instance """ - return self._factory.friend_item() + return None #self._factory.friend_item() # ----------------------------------------------------------------------------------------------------------------- # Friend getters @@ -246,19 +254,19 @@ class ContactsManager: return list(filter(lambda x: x.number == num and type(x) is Friend, self._contacts))[0] def get_last_message(self): - if self._active_friend + 1: + if self._active_contact + 1: return self.get_curr_friend().get_last_message_text() else: return '' def get_active_number(self): - return self.get_curr_friend().number if self._active_friend + 1 else -1 + return self.get_curr_friend().number if self._active_contact + 1 else -1 def get_active_name(self): - return self.get_curr_friend().name if self._active_friend + 1 else '' + return self.get_curr_friend().name if self._active_contact + 1 else '' def is_active_online(self): - return self._active_friend + 1 and self.get_curr_friend().status is not None + return self._active_contact + 1 and self.get_curr_friend().status is not None def new_name(self, number, name): friend = self.get_friend_by_number(number) @@ -333,7 +341,7 @@ class ContactsManager: self._tox.friend_delete(friend.number) del self._contacts[num] self._screen.friends_list.takeItem(num) - if num == self._active_friend: # active friend was deleted + if num == self._active_contact: # active friend was deleted if not len(self._contacts): # last friend was deleted self.set_active(-1) else: @@ -449,7 +457,7 @@ class ContactsManager: """ Send typing notification to a friend """ - if self._settings['typing_notifications'] and self._active_friend + 1: + if self._settings['typing_notifications'] and self._active_contact + 1: try: friend = self.get_curr_friend() if friend.status is not None: diff --git a/toxygen/contacts/friend_factory.py b/toxygen/contacts/friend_factory.py index 556f2d4..23966f8 100644 --- a/toxygen/contacts/friend_factory.py +++ b/toxygen/contacts/friend_factory.py @@ -3,9 +3,10 @@ from contacts.friend import Friend class FriendFactory: - def __init__(self, history, profile_manager, settings, tox): - self._history, self._profile_manager = history, profile_manager + def __init__(self, profile_manager, settings, tox, db): + self._profile_manager = profile_manager self._settings, self._tox = settings, tox + self._db = db def create_friend_by_number(self, friend_number): aliases = self._settings['friends_aliases'] @@ -17,9 +18,7 @@ class FriendFactory: 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) - if not self._history.friend_exists_in_db(tox_id): - self._history.add_friend_to_db(tox_id) - message_getter = self._history.messages_getter(tox_id) + 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) diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 0bfc764..70a6f7e 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -16,6 +16,7 @@ import cv2 import threading from contacts.group_chat import * import re +import util.ui as util_ui class Profile(basecontact.BaseContact): @@ -69,7 +70,7 @@ class Profile(basecontact.BaseContact): tmp = self.name super(Profile, self).set_name(value.encode('utf-8')) self._tox.self_set_name(self._name.encode('utf-8')) - message = QtWidgets.QApplication.translate("MainWindow", 'User {} is now known as {}') + message = util_ui.tr('User {} is now known as {}') message = message.format(tmp, value) for friend in self._contacts: friend.append_message(InfoMessage(message, time.time())) @@ -290,14 +291,9 @@ class Profile(basecontact.BaseContact): """ for friend in self._contacts: self.friend_exit(friend.number) - self._call.stop() - del self._call del self._tox self._tox = restart() - self._call = calls.AV(self._tox.AV) self.status = None - for friend in self._contacts: - friend.number = self._tox.friend_by_public_key(friend.tox_id) # numbers update self._contacts_manager.update_filtration() def reconnect(self): @@ -315,9 +311,6 @@ class Profile(basecontact.BaseContact): if hasattr(self, '_call'): self._call.stop() del self._call - s = Settings.get_instance() - s['paused_file_transfers'] = dict(self._paused_file_transfers) if s['resend_files'] else {} - s.save() def reset_avatar(self): super().reset_avatar() diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index 11c47c5..826adc0 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -1,6 +1,6 @@ from file_transfers.file_transfers import * from messenger.messages import * -from history.database import MESSAGE_OWNER +from history.database import MESSAGE_AUTHOR import os import util.util as util @@ -15,6 +15,13 @@ class FileTransfersHandler: # 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] + + def __del__(self): + self._settings['paused_file_transfers'] = self._paused_file_transfers if self._settings['resend_files'] else {} + self._settings.save() + + def _get_friend_by_number(self, friend_number): + return self._contact_provider.get_friend_by_number(friend_number) # ----------------------------------------------------------------------------------------------------------------- # File transfers support @@ -41,7 +48,7 @@ class FileTransfersHandler: return self._tox.file_seek(friend_number, file_number, pos) self.accept_transfer(None, data[0], friend_number, file_number, size, False, pos) - tm = TransferMessage(MESSAGE_OWNER['FRIEND'], + tm = TransferMessage(MESSAGE_AUTHOR['FRIEND'], time.time(), TOX_FILE_TRANSFER_STATE['RUNNING'], size, @@ -50,7 +57,7 @@ class FileTransfersHandler: file_number) elif inline and size < 1024 * 1024: self.accept_transfer(None, '', friend_number, file_number, size, True) - tm = TransferMessage(MESSAGE_OWNER['FRIEND'], + tm = TransferMessage(MESSAGE_AUTHOR['FRIEND'], time.time(), TOX_FILE_TRANSFER_STATE['RUNNING'], size, @@ -61,7 +68,7 @@ class FileTransfersHandler: elif auto: path = self._settings['auto_accept_path'] or util.curr_directory() self.accept_transfer(None, path + '/' + file_name, friend_number, file_number, size) - tm = TransferMessage(MESSAGE_OWNER['FRIEND'], + tm = TransferMessage(MESSAGE_AUTHOR['FRIEND'], time.time(), TOX_FILE_TRANSFER_STATE['RUNNING'], size, @@ -69,7 +76,7 @@ class FileTransfersHandler: friend_number, file_number) else: - tm = TransferMessage(MESSAGE_OWNER['FRIEND'], + tm = TransferMessage(MESSAGE_AUTHOR['FRIEND'], time.time(), TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED'], size, @@ -180,7 +187,6 @@ class FileTransfersHandler: :param data: raw data - png """ self.send_inline(data, 'toxygen_inline.png') - self._messages.repaint() def send_sticker(self, path): with open(path, 'rb') as fl: @@ -200,7 +206,7 @@ class FileTransfersHandler: st = SendFromBuffer(self._tox, friend.number, data, file_name) st.set_transfer_finished_handler(self.transfer_finished) self._file_transfers[(friend.number, st.get_file_number())] = st - tm = TransferMessage(MESSAGE_OWNER['ME'], + tm = TransferMessage(MESSAGE_AUTHOR['ME'], time.time(), TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'], len(data), @@ -233,7 +239,7 @@ class FileTransfersHandler: st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id) st.set_transfer_finished_handler(self.transfer_finished) self._file_transfers[(friend_number, st.get_file_number())] = st - tm = TransferMessage(MESSAGE_OWNER['ME'], + tm = TransferMessage(MESSAGE_AUTHOR['ME'], time.time(), TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'], os.path.getsize(path), @@ -293,6 +299,7 @@ class FileTransfersHandler: 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.get_file_number())] = sa @@ -312,6 +319,3 @@ class FileTransfersHandler: self.get_friend_by_number(friend_number).load_avatar() if self.get_active_number() == friend_number and self.is_active_a_friend(): self.set_active(None) - - def _get_friend_by_number(self, friend_number): - return self._contact_provider.get_friend_by_number(friend_number) diff --git a/toxygen/history/database.py b/toxygen/history/database.py index 9960b54..d7c10eb 100644 --- a/toxygen/history/database.py +++ b/toxygen/history/database.py @@ -1,32 +1,31 @@ from sqlite3 import connect -from user_data import settings -from os import chdir import os.path +import util.util as util -PAGE_SIZE = 42 - TIMEOUT = 11 SAVE_MESSAGES = 500 -MESSAGE_OWNER = { +MESSAGE_AUTHOR = { 'ME': 0, 'FRIEND': 1, 'NOT_SENT': 2, 'GC_PEER': 3 } -# TODO: unique message id and ngc support, profile name as db name +CONTACT_TYPE = { + 'FRIEND': 0, + 'GC_PEER': 1, + 'GC_PEER_PRIVATE': 2 +} class Database: - def __init__(self, name, toxes): - self._name = name - self._toxes = toxes - chdir(settings.ProfileManager.get_path()) - path = settings.ProfileManager.get_path() + self._name + '.hstr' + 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: @@ -35,28 +34,35 @@ class Database: data = toxes.pass_decrypt(data) with open(path, 'wb') as fout: fout.write(data) - except: + except Exception as ex: + util.log('Db reading error: ' + str(ex)) os.remove(path) - db = connect(name + '.hstr', timeout=TIMEOUT) + db = self._connect() cursor = db.cursor() - cursor.execute('CREATE TABLE IF NOT EXISTS friends(' - ' tox_id TEXT PRIMARY KEY' + cursor.execute('CREATE TABLE IF NOT EXISTS contacts (' + ' tox_id TEXT PRIMARY KEY,' + ' contact_type INTEGER' ')') db.close() + def _connect(self): + return connect(self._path, timeout=TIMEOUT) + + # ----------------------------------------------------------------------------------------------------------------- + # Public methods + # ----------------------------------------------------------------------------------------------------------------- + def save(self): if self._toxes.has_password(): - path = settings.ProfileManager.get_path() + self._name + '.hstr' - with open(path, 'rb') as fin: + with open(self._path, 'rb') as fin: data = fin.read() data = self._toxes.pass_encrypt(bytes(data)) - with open(path, 'wb') as fout: + with open(self._path, 'wb') as fout: fout.write(data) def export(self, directory): - path = settings.ProfileManager.get_path() + self._name + '.hstr' - new_path = directory + self._name + '.hstr' - with open(path, 'rb') as fin: + 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) @@ -64,15 +70,16 @@ class Database: fout.write(data) def add_friend_to_db(self, tox_id): - chdir(settings.ProfileManager.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) + db = self._connect() try: cursor = db.cursor() - cursor.execute('INSERT INTO friends VALUES (?);', (tox_id, )) + cursor.execute('INSERT INTO contacts VALUES (?);', (tox_id, )) cursor.execute('CREATE TABLE id' + tox_id + '(' ' id INTEGER PRIMARY KEY,' + ' message_id INTEGER,' + ' author_name TEXT,' ' message TEXT,' - ' owner INTEGER,' + ' author INTEGER,' ' unix_time REAL,' ' message_type INTEGER' ')') @@ -84,11 +91,10 @@ class Database: db.close() def delete_friend_from_db(self, tox_id): - chdir(settings.ProfileManager.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) + db = self._connect() try: cursor = db.cursor() - cursor.execute('DELETE FROM friends WHERE tox_id=?;', (tox_id, )) + cursor.execute('DELETE FROM contacts WHERE tox_id=?;', (tox_id, )) cursor.execute('DROP TABLE id' + tox_id + ';') db.commit() except: @@ -98,21 +104,20 @@ class Database: db.close() def friend_exists_in_db(self, tox_id): - chdir(settings.ProfileManager.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) + db = self._connect() cursor = db.cursor() - cursor.execute('SELECT 0 FROM friends WHERE tox_id=?', (tox_id, )) + cursor.execute('SELECT 1 FROM contacts 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.ProfileManager.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) + db = self._connect() try: cursor = db.cursor() - cursor.executemany('INSERT INTO id' + tox_id + '(message, owner, unix_time, message_type) ' - 'VALUES (?, ?, ?, ?);', messages_iter) + cursor.executemany('INSERT INTO id' + tox_id + + '(message, message_id, author_name, author, unix_time, message_type) ' + + 'VALUES (?, ?, ?, ?, ?, ?);', messages_iter) db.commit() except: print('Database is locked!') @@ -120,13 +125,12 @@ class Database: finally: db.close() - def update_messages(self, tox_id, unsent_time): - chdir(settings.ProfileManager.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) + def update_messages(self, tox_id, message_id): + db = self._connect() try: cursor = db.cursor() - cursor.execute('UPDATE id' + tox_id + ' SET owner = 0 ' - 'WHERE unix_time < ' + str(unsent_time) + ' AND owner = 2;') + cursor.execute('UPDATE id' + tox_id + ' SET author = 0 ' + 'WHERE message_id = ' + str(message_id) + ' AND author = 2;') db.commit() except: print('Database is locked!') @@ -134,13 +138,11 @@ class Database: finally: db.close() - def delete_message(self, tox_id, message_id): - chdir(settings.ProfileManager.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) + def delete_message(self, tox_id, unique_id): + db = self._connect() try: cursor = db.cursor() - cursor.execute('DELETE FROM id' + tox_id + ' WHERE unix_time < ' + end + ' AND unix_time > ' + - start + ';') + cursor.execute('DELETE FROM id' + tox_id + ' WHERE id = ' + str(unique_id) + ';') db.commit() except: print('Database is locked!') @@ -149,8 +151,7 @@ class Database: db.close() def delete_messages(self, tox_id): - chdir(settings.ProfileManager.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) + db = self._connect() try: cursor = db.cursor() cursor.execute('DELETE FROM id' + tox_id + ';') @@ -162,46 +163,44 @@ class Database: db.close() def messages_getter(self, tox_id): - return Database.MessageGetter(self._name, tox_id) + return Database.MessageGetter(self._path, tox_id) + + # ----------------------------------------------------------------------------------------------------------------- + # Messages loading + # ----------------------------------------------------------------------------------------------------------------- class MessageGetter: - def __init__(self, name, tox_id): + def __init__(self, path, tox_id): self._count = 0 - self._name = name + self._path = path self._tox_id = tox_id self._db = self._cursor = None - def connect(self): - chdir(settings.ProfileManager.get_path()) - self._db = connect(self._name + '.hstr', timeout=TIMEOUT) + def _connect(self): + self._db = connect(self._path, 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;') + self._cursor.execute('SELECT id, message_id, message, author, unix_time, message_type FROM id' + + self._tox_id + ' ORDER BY unix_time DESC;') - def disconnect(self): + 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 + return self.get(1) def get_all(self): - self.connect() + self._connect() data = self._cursor.fetchall() - self.disconnect() + self._disconnect() self._count = len(data) return data def get(self, count): - self.connect() + self._connect() self.skip() data = self._cursor.fetchmany(count) - self.disconnect() + self._disconnect() self._count += len(data) return data diff --git a/toxygen/history/history_loader.py b/toxygen/history/history_loader.py index 86e61c5..801125b 100644 --- a/toxygen/history/history_loader.py +++ b/toxygen/history/history_loader.py @@ -1,11 +1,17 @@ from messenger.messages import * +# TODO: fix history loading and saving + class HistoryLoader: - def __init__(self, db, settings): + def __init__(self, contact_provider, db, settings): + self._contact_provider = contact_provider self._db = db self._settings = settings + + def __del__(self): + del self._db # ----------------------------------------------------------------------------------------------------------------- # History support @@ -15,22 +21,20 @@ class HistoryLoader: """ Save history to db """ - if hasattr(self, '_history'): - if self._settings['save_history']: - for friend in filter(lambda x: type(x) is Friend, self._contacts): - if not self._history.friend_exists_in_db(friend.tox_id): - self._history.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._history.delete_messages(friend.tox_id) - self._history.save_messages_to_db(friend.tox_id, messages) - unsent_messages = friend.get_unsent_messages() - unsent_time = unsent_messages[0].get_data()[2] if len(unsent_messages) else time.time() + 1 - self._history.update_messages(friend.tox_id, unsent_time) - self._history.save() - del self._history + if self._settings['save_db']: + for friend in self._contact_provider.get_all_friends(): + if not self._db.friend_exists_in_db(friend.tox_id): + 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) + self._db.save_messages_to_db(friend.tox_id, messages) + unsent_messages = friend.get_unsent_messages() + # unsent_time = unsent_messages[0].get_data()[2] if len(unsent_messages) else time.time() + 1 + # self._db.update_messages(friend.tox_id, unsent_time) + self._db.save() def clear_history(self, friend, save_unsent=False): """ @@ -45,9 +49,9 @@ class HistoryLoader: """ Tries to load next part of messages """ - if not self._load_history: + if not self._load_db: return - self._load_history = False + self._load_db = False friend = self.get_curr_friend() friend.load_corr(False) data = friend.get_corr() @@ -84,9 +88,16 @@ class HistoryLoader: '', data[3], False) - self._load_history = True + self._load_db = True - def export_history(self, friend, as_text=True, _range=None): + def get_message_getter(self, friend_public_key): + if not self._db.friend_exists_in_db(friend_public_key): + self._db.add_friend_to_db(friend_public_key) + + return self._db.messages_getter(friend_public_key) + + @staticmethod + def export_history(friend, as_text=True, _range=None): if _range is None: friend.load_all_corr() corr = friend.get_corr() @@ -95,16 +106,28 @@ class HistoryLoader: else: corr = friend.get_corr()[_range[0]:] - generator = TextHistoryGenerator() if as_text else HtmlHistoryGenerator + generator = TextHistoryGenerator(corr) if as_text else HtmlHistoryGenerator(corr) - return generator.generate(corr) + return generator.generate() -class HtmlHistoryGenerator: +class HistoryLogsGenerator: - def generate(self, corr): + def __init__(self, history): + self._history = history + + def generate(self): + return str() + + +class HtmlHistoryGenerator(HistoryLogsGenerator): + + def __init__(self, history): + super().__init__(history) + + def generate(self): arr = [] - for message in corr: + for message in self._history: if type(message) is TextMessage: data = message.get_data() x = '[{}] {}: {}
' @@ -117,11 +140,14 @@ class HtmlHistoryGenerator: return s -class TextHistoryGenerator: +class TextHistoryGenerator(HistoryLogsGenerator): - def generate(self, corr): + def __init__(self, history): + super().__init__(history) + + def generate(self): arr = [] - for message in corr: + for message in self._history: if type(message) is TextMessage: data = message.get_data() x = '[{}] {}: {}\n' diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index e8abec7..023a2e7 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -1,4 +1,4 @@ -from history.database import MESSAGE_OWNER +from history.database import MESSAGE_AUTHOR MESSAGE_TYPE = { @@ -9,6 +9,8 @@ MESSAGE_TYPE = { 'INFO_MESSAGE': 4 } +PAGE_SIZE = 42 + class MessageAuthor: @@ -53,7 +55,7 @@ class Message: self._widget = None def mark_as_sent(self): - self._author.author_type = MESSAGE_OWNER['ME'] + self._author.author_type = MESSAGE_AUTHOR['ME'] def _create_widget(self): pass diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 926c7af..153f341 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -377,6 +377,10 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, :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: main window screen :param tray: tray (for notifications) diff --git a/toxygen/ui/av_widgets.py b/toxygen/ui/av_widgets.py index a783eca..676df78 100644 --- a/toxygen/ui/av_widgets.py +++ b/toxygen/ui/av_widgets.py @@ -11,7 +11,7 @@ from util.util import curr_directory class IncomingCallWidget(widgets.CenteredWidget): def __init__(self, friend_number, text, name): - super(IncomingCallWidget, self).__init__() + super().__init__() self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowStaysOnTopHint) self.resize(QtCore.QSize(500, 270)) self.avatar_label = QtWidgets.QLabel(self) diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 3852867..73f4d4e 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -360,7 +360,6 @@ class MainWindow(QtWidgets.QMainWindow): if self._saved: return self._saved = True - self.profile.save_history() self.profile.close() self._settings['x'] = self.geometry().x() self._settings['y'] = self.geometry().y() @@ -500,7 +499,7 @@ class MainWindow(QtWidgets.QMainWindow): 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.menu.setGeometry(QtCore.QRect(0 if self._settings['mirror_mode'] else 270, self.height() - 120, 180, 120)) diff --git a/toxygen/ui/main_screen_widgets.py b/toxygen/ui/main_screen_widgets.py index 064c745..fe66a68 100644 --- a/toxygen/ui/main_screen_widgets.py +++ b/toxygen/ui/main_screen_widgets.py @@ -2,14 +2,16 @@ from PyQt5 import QtCore, QtGui, QtWidgets from ui.widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit from contacts.profile import Profile import smileys +import urllib import util.util as util +import util.ui as util_ui class MessageArea(QtWidgets.QPlainTextEdit): """User types messages here""" def __init__(self, parent, form): - super(MessageArea, self).__init__(parent) + super().__init__(parent) self.parent = form self.setAcceptDrops(True) self.timer = QtCore.QTimer(self) @@ -76,15 +78,18 @@ class MessageArea(QtWidgets.QPlainTextEdit): self.insertPlainText(text) def parse_file_name(self, file_name): - import urllib if file_name.endswith('\r\n'): file_name = file_name[:-2] 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): + def __init__(self, file_transfer_handler, *args): + super().__init__(*args) + self._file_transfer_handler = file_transfer_handler + def closeEvent(self, *args): if self.parent.isHidden(): self.parent.show() @@ -104,7 +109,7 @@ class ScreenShotWindow(RubberBandWindow): buffer = QtCore.QBuffer(byte_array) buffer.open(QtCore.QIODevice.WriteOnly) p.save(buffer, 'PNG') - Profile.get_instance().send_screenshot(bytes(byte_array.data())) + self._file_transfer_handler.send_screenshot(bytes(byte_array.data())) self.close() @@ -113,11 +118,10 @@ class SmileyWindow(QtWidgets.QWidget): Smiley selection window """ - def __init__(self, parent): - super(SmileyWindow, self).__init__() + def __init__(self, parent, smiley_loader): + super().__init__(parent) self.setWindowFlags(QtCore.Qt.FramelessWindowHint) - inst = smileys.SmileyLoader.get_instance() - self.data = inst.get_smileys() + self.data = smiley_loader.get_smileys() count = len(self.data) if not count: self.close() @@ -172,18 +176,18 @@ class SmileyWindow(QtWidgets.QWidget): class MenuButton(QtWidgets.QPushButton): def __init__(self, parent, enter): - super(MenuButton, self).__init__(parent) + super().__init__(parent) self.enter = enter def enterEvent(self, event): self.enter() - super(MenuButton, self).enterEvent(event) + super().enterEvent(event) class DropdownMenu(QtWidgets.QWidget): def __init__(self, parent): - super(DropdownMenu, self).__init__(parent) + super().__init__(parent) self.installEventFilter(self) self.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.setMaximumSize(120, 120) @@ -245,7 +249,7 @@ class DropdownMenu(QtWidgets.QWidget): class StickerItem(QtWidgets.QWidget): def __init__(self, fl): - super(StickerItem, self).__init__() + super().__init__() self._image_label = QtWidgets.QLabel(self) self.path = fl self.pixmap = QtGui.QPixmap() @@ -260,7 +264,7 @@ class StickerWindow(QtWidgets.QWidget): """Sticker selection window""" def __init__(self, parent): - super(StickerWindow, self).__init__() + super().__init__() self.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.setMaximumSize(250, 200) self.setMinimumSize(250, 200) @@ -289,8 +293,9 @@ class StickerWindow(QtWidgets.QWidget): class WelcomeScreen(CenteredWidget): - def __init__(self): + def __init__(self, settings): super().__init__() + self._settings = settings self.setMaximumSize(250, 200) self.setMinimumSize(250, 200) self.center() @@ -300,51 +305,39 @@ class WelcomeScreen(CenteredWidget): self.text.setOpenExternalLinks(True) self.checkbox = QtWidgets.QCheckBox(self) self.checkbox.setGeometry(QtCore.QRect(5, 170, 240, 30)) - self.checkbox.setText(QtWidgets.QApplication.translate('WelcomeScreen', "Don't show again")) - self.setWindowTitle(QtWidgets.QApplication.translate('WelcomeScreen', 'Tip of the day')) + self.checkbox.setText(util_ui.tr( "Don't show again")) + self.setWindowTitle(util_ui.tr( 'Tip of the day')) import random num = random.randint(0, 10) 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: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Right click on screenshot button hides app to tray during screenshot.') + text = util_ui.tr('Right click on screenshot button hides app to tray during screenshot.') elif num == 2: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'You can use Tox over Tor. For more info read this post') + text = util_ui.tr('You can use Tox over Tor. For more info read this post') elif num == 3: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Use Settings -> Interface to customize interface.') + text = util_ui.tr('Use Settings -> Interface to customize interface.') elif num == 4: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.') + text = util_ui.tr('Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.') elif num == 5: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Since v0.1.3 Toxygen supports plugins. Read more') + text = util_ui.tr('Since v0.1.3 Toxygen supports plugins. Read more') elif num == 6: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.') + 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.') elif num == 7: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'New in Toxygen 0.4.1:
Downloading nodes from tox.chat
Bug fixes') + text = util_ui.tr('New in Toxygen 0.4.1:
Downloading nodes from tox.chat
Bug fixes') elif num == 8: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu') + text = util_ui.tr('Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu') elif num == 9: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Use right click on inline image to save it') + text = util_ui.tr( 'Use right click on inline image to save it') else: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.') + text = util_ui.tr('Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.') self.text.setHtml(text) self.checkbox.stateChanged.connect(self.not_show) QtCore.QTimer.singleShot(1000, self.show) def not_show(self): - from user_data import settings - s = settings.Settings.get_instance() - s['show_welcome_screen'] = False - s.save() + self._settings['show_welcome_screen'] = False + self._settings.save() class MainMenuButton(QtWidgets.QPushButton): @@ -417,7 +410,7 @@ class SearchScreen(QtWidgets.QWidget): self.retranslateUi() def retranslateUi(self): - self.search_text.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "Search")) + self.search_text.setPlaceholderText(util_ui.tr('Search')) def show(self): super().show() @@ -473,11 +466,4 @@ class SearchScreen(QtWidgets.QWidget): @staticmethod def not_found(text): - mbox = QtWidgets.QMessageBox() - mbox_text = QtWidgets.QApplication.translate("MainWindow", - 'Text "{}" was not found') - - mbox.setText(mbox_text.format(text)) - mbox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", - 'Not found')) - mbox.exec_() + util_ui.message_box(util_ui.tr('Text "{}" was not found').format(text), util_ui.tr('Not found')) diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index 9dcf180..c509359 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -7,13 +7,15 @@ import pyaudio from user_data import toxes import plugin_support import updater +import util.ui as util_ui class AddContact(CenteredWidget): """Add contact form""" - def __init__(self, tox_id=''): - super(AddContact, self).__init__() + def __init__(self, contacts_manager, tox_id=''): + super().__init__() + self._contacts_manager = contacts_manager self.initUI(tox_id) self._adding = False self.setAttribute(QtCore.Qt.WA_DeleteOnClose) @@ -61,8 +63,10 @@ class AddContact(CenteredWidget): if self._adding: return self._adding = True - profile = Profile.get_instance() - send = profile.send_friend_request(self.tox_id.text().strip(), self.message_edit.toPlainText()) + tox_id = self.tox_id.text().strip() + if tox_id.startswith('tox:'): + tox_id = tox_id[4:] + send = self._contacts_manager.send_friend_request(tox_id, self.message_edit.toPlainText()) self._adding = False if send is True: # request was successful @@ -71,17 +75,18 @@ class AddContact(CenteredWidget): self.error_label.setText(send) def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate('AddContact', "Add contact")) - self.sendRequestButton.setText(QtWidgets.QApplication.translate("Form", "Send request")) - self.label.setText(QtWidgets.QApplication.translate('AddContact', "TOX ID:")) - self.message.setText(QtWidgets.QApplication.translate('AddContact', "Message:")) - self.tox_id.setPlaceholderText(QtWidgets.QApplication.translate('AddContact', "TOX ID or public key of contact")) + self.setWindowTitle(util_ui.tr('Add contact')) + self.sendRequestButton.setText(util_ui.tr('Send request')) + self.label.setText(util_ui.tr('TOX ID:')) + self.message.setText(util_ui.tr('Message:')) + self.tox_id.setPlaceholderText(util_ui.tr('TOX ID or public key of contact')) class ProfileSettings(CenteredWidget): """Form with profile settings such as name, status, TOX ID""" - def __init__(self): - super(ProfileSettings, self).__init__() + def __init__(self, profile): + super().__init__() + self._profile = profile self.initUI() self.center() @@ -91,13 +96,12 @@ class ProfileSettings(CenteredWidget): self.setMaximumSize(QtCore.QSize(700, 600)) self.nick = LineEdit(self) self.nick.setGeometry(QtCore.QRect(30, 60, 350, 27)) - profile = Profile.get_instance() - self.nick.setText(profile.name) + self.nick.setText(self._profile.name) self.status = QtWidgets.QComboBox(self) self.status.setGeometry(QtCore.QRect(400, 60, 200, 27)) self.status_message = LineEdit(self) self.status_message.setGeometry(QtCore.QRect(30, 130, 350, 27)) - self.status_message.setText(profile.status_message) + self.status_message.setText(self._profile.status_message) self.label = QtWidgets.QLabel(self) self.label.setGeometry(QtCore.QRect(40, 30, 91, 25)) font = QtGui.QFont() @@ -164,37 +168,37 @@ class ProfileSettings(CenteredWidget): self.auto = path + name == ProfileManager.get_path() + Settings.get_instance().name self.default.clicked.connect(self.auto_profile) self.retranslateUi() - if profile.status is not None: - self.status.setCurrentIndex(profile.status) + if self._profile.status is not None: + self.status.setCurrentIndex(self._profile.status) else: self.status.setVisible(False) QtCore.QMetaObject.connectSlotsByName(self) def retranslateUi(self): - self.export.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Export profile")) - self.setWindowTitle(QtWidgets.QApplication.translate("ProfileSettingsForm", "Profile settings")) - self.label.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Name:")) - self.label_2.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Status:")) - self.label_3.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "TOX ID:")) - self.copyId.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Copy TOX ID")) - self.new_avatar.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "New avatar")) - self.delete_avatar.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Reset avatar")) - self.new_nospam.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "New NoSpam")) - self.profilepass.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Profile password")) - self.password.setPlaceholderText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Password (at least 8 symbols)")) - self.confirm_password.setPlaceholderText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Confirm password")) - self.set_password.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Set password")) - self.not_match.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Passwords do not match")) - self.leave_blank.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Leaving blank will reset current password")) - self.warning.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "There is no way to recover lost passwords")) - self.status.addItem(QtWidgets.QApplication.translate("ProfileSettingsForm", "Online")) - self.status.addItem(QtWidgets.QApplication.translate("ProfileSettingsForm", "Away")) - self.status.addItem(QtWidgets.QApplication.translate("ProfileSettingsForm", "Busy")) - self.copy_pk.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Copy public key")) + self.export.setText(util_ui.tr("Export profile")) + self.setWindowTitle(util_ui.tr("Profile settings")) + self.label.setText(util_ui.tr("Name:")) + self.label_2.setText(util_ui.tr("Status:")) + self.label_3.setText(util_ui.tr("TOX ID:")) + self.copyId.setText(util_ui.tr("Copy TOX ID")) + self.new_avatar.setText(util_ui.tr("New avatar")) + self.delete_avatar.setText(util_ui.tr("Reset avatar")) + self.new_nospam.setText(util_ui.tr("New NoSpam")) + self.profilepass.setText(util_ui.tr("Profile password")) + self.password.setPlaceholderText(util_ui.tr("Password (at least 8 symbols)")) + self.confirm_password.setPlaceholderText(util_ui.tr("Confirm password")) + self.set_password.setText(util_ui.tr("Set password")) + self.not_match.setText(util_ui.tr("Passwords do not match")) + self.leave_blank.setText(util_ui.tr("Leaving blank will reset current password")) + self.warning.setText(util_ui.tr("There is no way to recover lost passwords")) + self.status.addItem(util_ui.tr("Online")) + self.status.addItem(util_ui.tr("Away")) + self.status.addItem(util_ui.tr("Busy")) + self.copy_pk.setText(util_ui.tr("Copy public key")) if self.auto: - self.default.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as not default profile")) + self.default.setText(util_ui.tr("Mark as not default profile")) else: - self.default.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as default profile")) + self.default.setText(util_ui.tr("Mark as default profile")) def auto_profile(self): if self.auto: @@ -203,10 +207,10 @@ class ProfileSettings(CenteredWidget): Settings.set_auto_profile(ProfileManager.get_path(), Settings.get_instance().name) self.auto = not self.auto if self.auto: - self.default.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as not default profile")) + self.default.setText(util_ui.tr("Mark as not default profile")) else: self.default.setText( - QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as default profile")) + util_ui.tr("Mark as default profile")) def new_password(self): if self.password.text() == self.confirm_password.text(): @@ -216,10 +220,10 @@ class ProfileSettings(CenteredWidget): self.close() else: self.not_match.setText( - QtWidgets.QApplication.translate("ProfileSettingsForm", "Password must be at least 8 symbols")) + util_ui.tr("Password must be at least 8 symbols")) self.not_match.setVisible(True) else: - self.not_match.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Passwords do not match")) + self.not_match.setText(util_ui.tr("Passwords do not match")) self.not_match.setVisible(True) def copy(self): @@ -244,10 +248,10 @@ class ProfileSettings(CenteredWidget): self.tox_id.setText(Profile.get_instance().new_nospam()) def reset_avatar(self): - Profile.get_instance().reset_avatar() + self._profile.reset_avatar() def set_avatar(self): - choose = QtWidgets.QApplication.translate("ProfileSettingsForm", "Choose avatar") + choose = util_ui.tr("Choose avatar") name = QtWidgets.QFileDialog.getOpenFileName(self, choose, None, 'Images (*.png)', options=QtWidgets.QFileDialog.DontUseNativeDialog) if name[0]: @@ -262,21 +266,15 @@ class ProfileSettings(CenteredWidget): Profile.get_instance().set_avatar(bytes(byte_array.data())) def export_profile(self): - directory = QtWidgets.QFileDialog.getExistingDirectory(self, '', curr_directory(), - QtWidgets.QFileDialog.DontUseNativeDialog) + '/' + directory = util_ui.directory_dialog() + '/' if directory != '/': - reply = QtWidgets.QMessageBox.question(None, - QtWidgets.QApplication.translate("ProfileSettingsForm", - 'Use new path'), - QtWidgets.QApplication.translate("ProfileSettingsForm", - 'Do you want to move your profile to this location?'), - QtWidgets.QMessageBox.Yes, - QtWidgets.QMessageBox.No) + reply = util_ui.question(util_ui.tr('Do you want to move your profile to this location?'), + util_ui.tr('Use new path')) settings = Settings.get_instance() settings.export(directory) profile = Profile.get_instance() profile.export_db(directory) - ProfileManager.get_instance().export_profile(directory, reply == QtWidgets.QMessageBox.Yes) + ProfileManager.get_instance().export_profile(directory, reply) def closeEvent(self, event): profile = Profile.get_instance() @@ -287,8 +285,9 @@ class ProfileSettings(CenteredWidget): class NetworkSettings(CenteredWidget): """Network settings form: UDP, Ipv6 and proxy""" - def __init__(self, reset): - super(NetworkSettings, self).__init__() + def __init__(self, settings, reset): + super().__init__() + self._settings = settings self.reset = reset self.initUI() self.center() @@ -323,35 +322,34 @@ class NetworkSettings(CenteredWidget): self.reconnect = QtWidgets.QPushButton(self) self.reconnect.setGeometry(QtCore.QRect(40, 230, 231, 30)) self.reconnect.clicked.connect(self.restart_core) - settings = Settings.get_instance() - self.ipv.setChecked(settings['ipv6_enabled']) - self.udp.setChecked(settings['udp_enabled']) - self.proxy.setChecked(settings['proxy_type']) - self.proxyip.setText(settings['proxy_host']) - self.proxyport.setText(str(settings['proxy_port'])) - self.http.setChecked(settings['proxy_type'] == 1) + self.ipv.setChecked(self._settings['ipv6_enabled']) + self.udp.setChecked(self._settings['udp_enabled']) + self.proxy.setChecked(self._settings['proxy_type']) + self.proxyip.setText(self._settings['proxy_host']) + self.proxyport.setText(str(self._settings['proxy_port'])) + self.http.setChecked(self._settings['proxy_type'] == 1) self.warning = QtWidgets.QLabel(self) self.warning.setGeometry(QtCore.QRect(5, 270, 290, 60)) self.warning.setStyleSheet('QLabel { color: #BC1C1C; }') self.nodes = QtWidgets.QCheckBox(self) self.nodes.setGeometry(QtCore.QRect(20, 350, 270, 22)) - self.nodes.setChecked(settings['download_nodes_list']) + self.nodes.setChecked(self._settings['download_nodes_list']) self.retranslateUi() self.proxy.stateChanged.connect(lambda x: self.activate()) self.activate() QtCore.QMetaObject.connectSlotsByName(self) def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate("NetworkSettings", "Network settings")) - self.ipv.setText(QtWidgets.QApplication.translate("Form", "IPv6")) - self.udp.setText(QtWidgets.QApplication.translate("Form", "UDP")) - self.proxy.setText(QtWidgets.QApplication.translate("Form", "Proxy")) - self.label.setText(QtWidgets.QApplication.translate("Form", "IP:")) - self.label_2.setText(QtWidgets.QApplication.translate("Form", "Port:")) - self.reconnect.setText(QtWidgets.QApplication.translate("NetworkSettings", "Restart TOX core")) - self.http.setText(QtWidgets.QApplication.translate("Form", "HTTP")) - self.nodes.setText(QtWidgets.QApplication.translate("Form", "Download nodes list from tox.chat")) - self.warning.setText(QtWidgets.QApplication.translate("Form", "WARNING:\nusing proxy with enabled UDP\ncan produce IP leak")) + self.setWindowTitle(util_ui.tr("Network settings")) + self.ipv.setText(util_ui.tr("IPv6")) + self.udp.setText(util_ui.tr("UDP")) + self.proxy.setText(util_ui.tr("Proxy")) + self.label.setText(util_ui.tr("IP:")) + self.label_2.setText(util_ui.tr("Port:")) + self.reconnect.setText(util_ui.tr("Restart TOX core")) + self.http.setText(util_ui.tr("HTTP")) + self.nodes.setText(util_ui.tr("Download nodes list from tox.chat")) + self.warning.setText(util_ui.tr("WARNING:\nusing proxy with enabled UDP\ncan produce IP leak")) def activate(self): bl = self.proxy.isChecked() @@ -361,14 +359,13 @@ class NetworkSettings(CenteredWidget): def restart_core(self): try: - settings = Settings.get_instance() - settings['ipv6_enabled'] = self.ipv.isChecked() - settings['udp_enabled'] = self.udp.isChecked() - settings['proxy_type'] = 2 - int(self.http.isChecked()) if self.proxy.isChecked() else 0 - settings['proxy_host'] = str(self.proxyip.text()) - settings['proxy_port'] = int(self.proxyport.text()) - settings['download_nodes_list'] = self.nodes.isChecked() - settings.save() + self._settings['ipv6_enabled'] = self.ipv.isChecked() + self._settings['udp_enabled'] = self.udp.isChecked() + self._settings['proxy_type'] = 2 - int(self.http.isChecked()) if self.proxy.isChecked() else 0 + self._settings['proxy_host'] = str(self.proxyip.text()) + self._settings['proxy_port'] = int(self.proxyport.text()) + self._settings['download_nodes_list'] = self.nodes.isChecked() + self._settings.save() # recreate tox instance Profile.get_instance().reset(self.reset) self.close() @@ -380,7 +377,7 @@ class PrivacySettings(CenteredWidget): """Privacy settings form: history, typing notifications""" def __init__(self): - super(PrivacySettings, self).__init__() + super().__init__() self.initUI() self.center() diff --git a/toxygen/ui/widgets.py b/toxygen/ui/widgets.py index aab027a..735ad39 100644 --- a/toxygen/ui/widgets.py +++ b/toxygen/ui/widgets.py @@ -79,7 +79,7 @@ class QRightClickButton(QtWidgets.QPushButton): class RubberBand(QtWidgets.QRubberBand): def __init__(self): - super(RubberBand, self).__init__(QtWidgets.QRubberBand.Rectangle, None) + super().__init__(QtWidgets.QRubberBand.Rectangle, None) self.setPalette(QtGui.QPalette(QtCore.Qt.transparent)) self.pen = QtGui.QPen(QtCore.Qt.blue, 4) self.pen.setStyle(QtCore.Qt.SolidLine) diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py new file mode 100644 index 0000000..60ae8c3 --- /dev/null +++ b/toxygen/ui/widgets_factory.py @@ -0,0 +1,27 @@ +from ui.main_screen_widgets import * +from ui.menu import * + + +class WidgetsFactory: + + def __init__(self, settings, profile, contacts_manager, file_transfer_handler, smiley_loader): + self._settings = settings + self._profile = profile + self._contacts_manager = contacts_manager + self._file_transfer_handler = file_transfer_handler + self._smiley_loader = smiley_loader + + def create_screenshot_window(self, *args): + return ScreenShotWindow(self._file_transfer_handler, *args) + + def create_smiley_window(self, parent): + return SmileyWindow(parent, self._smiley_loader) + + def create_welcome_window(self): + return WelcomeScreen(self._settings) + + def create_profile_settings_window(self): + return ProfileSettings(self._profile) + + def create_network_settings_window(self): + return NetworkSettings(self._settings, self._profile.reset) diff --git a/toxygen/util/ui.py b/toxygen/util/ui.py index 4b9e806..a117202 100644 --- a/toxygen/util/ui.py +++ b/toxygen/util/ui.py @@ -1,4 +1,5 @@ from PyQt5 import QtWidgets +import util.util as util def tr(s): @@ -25,4 +26,9 @@ def text_dialog(text, title='', default_value=''): return text, ok +def directory_dialog(caption=''): + return QtWidgets.QFileDialog.getExistingDirectory(None, caption, util.curr_directory(), + QtWidgets.QFileDialog.DontUseNativeDialog) + + # TODO: move all dialogs here From 5ebfa702ec06136431e1a8ead6fd6b9a800f9bbd Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 30 Apr 2018 00:33:25 +0300 Subject: [PATCH 012/138] screens creation improvements. bug fixes --- toxygen/app.py | 10 +- toxygen/bootstrap/bootstrap.py | 21 +- toxygen/contacts/profile.py | 11 +- toxygen/middleware/callbacks.py | 36 +-- toxygen/middleware/threads.py | 3 +- toxygen/smileys/smileys.py | 4 +- toxygen/ui/main_screen.py | 131 +++++----- toxygen/ui/menu.py | 424 ++++++++++++++------------------ toxygen/ui/widgets_factory.py | 31 ++- toxygen/util/ui.py | 17 +- toxygen/util/util.py | 11 +- 11 files changed, 338 insertions(+), 361 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index 423ae5f..17c760e 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -23,6 +23,8 @@ from contacts.friend_factory import FriendFactory 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 class App: @@ -148,16 +150,20 @@ class App: return self._tox def create_dependencies(self): - self._ms = MainWindow(self._settings, self._tox, self.reset, self._tray) + self._ms = MainWindow(self._settings, self._tox, self._tray) db = Database(self._path.replace('.tox', '.db'), self._toxes) self._friend_factory = FriendFactory(self._profile_manager, self._settings, self._tox, db) self._contacts_provider = ContactProvider(self._tox, self._friend_factory) + profile = Profile(self._profile_manager, self._tox, self._ms, self._file_transfer_handler) + self._smiley_loader = SmileyLoader(self._settings) + widgets_factory = WidgetsFactory(self._settings, profile, self._contacts_manager, self._file_transfer_handler, + self._smiley_loader, self._plugin_loader) self._contacts_manager = ContactsManager(self._tox, self._settings, self._ms, self._profile_manager, self._contacts_provider, db) self._calls_manager = CallsManager(self._tox.AV, self._settings) self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider) - profile = Profile(self._profile_manager, self._tox, self._ms, self._file_transfer_handler) self._ms.profile = profile + self._ms.set_widget_factory(widgets_factory) self._ms.show() self._tray = tray.init_tray(profile, self._settings, self._ms) diff --git a/toxygen/bootstrap/bootstrap.py b/toxygen/bootstrap/bootstrap.py index aa6f863..ccce1c7 100644 --- a/toxygen/bootstrap/bootstrap.py +++ b/toxygen/bootstrap/bootstrap.py @@ -1,11 +1,13 @@ import random import urllib.request -from util.util import log, curr_directory -from user_data import settings +from util.util import log, curr_directory, join_path from PyQt5 import QtNetwork, QtCore import json +DEFAULT_NODES_COUNT = 4 + + class Node: def __init__(self, node): @@ -21,11 +23,18 @@ class Node: return bytes(self._ip, 'utf-8'), self._port, self._tox_key -def generate_nodes(): - with open(curr_directory() + '/nodes.json', 'rt') as fl: +def _get_nodes_path(): + return join_path(curr_directory(__file__), 'nodes.json') + + +def generate_nodes(nodes_count=DEFAULT_NODES_COUNT): + with open(_get_nodes_path(), 'rt') as fl: json_nodes = json.loads(fl.read())['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: yield node.get_data() @@ -34,7 +43,7 @@ def save_nodes(nodes): if not nodes: return print('Saving nodes...') - with open(curr_directory() + '/nodes.json', 'wb') as fl: + with open(_get_nodes_path(), 'wb') as fl: fl.write(nodes) diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 70a6f7e..642cc04 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -1,21 +1,12 @@ -from ui.list_items import * -from PyQt5 import QtWidgets from contacts.friend import * from user_data.settings import * from wrapper.toxcore_enums_and_consts import * -from util.util import log, curr_directory -from network.tox_dns import tox_dns +from util.util import log from history.database import * from file_transfers.file_transfers import * import time -from av import calls -import plugin_support from contacts import basecontact -from ui import items_factory, av_widgets -import cv2 -import threading from contacts.group_chat import * -import re import util.ui as util_ui diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 153f341..e139883 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -40,62 +40,62 @@ def self_connection_status(tox, profile): def friend_status(profile, settings): - def wrapped(tox, friend_num, new_status, user_data): + def wrapped(tox, friend_number, new_status, user_data): """ Check friend's status (none, busy, away) """ - print("Friend's #{} status changed!".format(friend_num)) - friend = profile.get_friend_by_number(friend_num) + print("Friend's #{} status changed!".format(friend_number)) + friend = profile.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) - invoke_in_main_thread(QtCore.QTimer.singleShot, 5000, lambda: profile.send_files(friend_num)) + invoke_in_main_thread(QtCore.QTimer.singleShot, 5000, lambda: profile.send_files(friend_number)) invoke_in_main_thread(profile.update_filtration) return wrapped def friend_connection_status(profile, settings, plugin_loader): - def wrapped(tox, friend_num, new_status, user_data): + def wrapped(tox, friend_number, new_status, user_data): """ Check friend's connection status (offline, udp, tcp) """ - print("Friend #{} connection status: {}".format(friend_num, new_status)) - friend = profile.get_friend_by_number(friend_num) + print("Friend #{} connection status: {}".format(friend_number, new_status)) + friend = profile.get_friend_by_number(friend_number) if new_status == TOX_CONNECTION['NONE']: - invoke_in_main_thread(profile.friend_exit, friend_num) + invoke_in_main_thread(profile.friend_exit, friend_number) invoke_in_main_thread(profile.update_filtration) 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(profile.send_avatar, friend_num) - invoke_in_main_thread(plugin_loader.friend_online, friend_num) + invoke_in_main_thread(profile.send_avatar, friend_number) + invoke_in_main_thread(plugin_loader.friend_online, friend_number) return wrapped def friend_name(profile): - def wrapped(tox, friend_num, name, size, user_data): + def wrapped(tox, friend_number, name, size, user_data): """ Friend changed his name """ - print('New name friend #' + str(friend_num)) - invoke_in_main_thread(profile.new_name, friend_num, name) + print('New name friend #' + str(friend_number)) + invoke_in_main_thread(profile.new_name, friend_number, name) return wrapped def friend_status_message(profile): - def wrapped(tox, friend_num, status_message, size, user_data): + 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 = profile.get_friend_by_number(friend_num) + friend = profile.get_friend_by_number(friend_number) 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: + print('User #{} has new status'.format(friend_number)) + invoke_in_main_thread(profile.send_messages, friend_number) + if profile.get_active_number() == friend_number: invoke_in_main_thread(profile.set_active) return wrapped diff --git a/toxygen/middleware/threads.py b/toxygen/middleware/threads.py index 5d722f0..e654a66 100644 --- a/toxygen/middleware/threads.py +++ b/toxygen/middleware/threads.py @@ -42,7 +42,7 @@ class InitThread(BaseThread): time.sleep(1) while not self._tox.self_get_connection_status(): try: - for data in generate_nodes(): + for data in generate_nodes(None): if self._stop_thread: return self._tox.bootstrap(*data) @@ -137,4 +137,3 @@ _invoker = Invoker() def invoke_in_main_thread(fn, *args, **kwargs): QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs)) - diff --git a/toxygen/smileys/smileys.py b/toxygen/smileys/smileys.py index c20d1a7..fc40a69 100644 --- a/toxygen/smileys/smileys.py +++ b/toxygen/smileys/smileys.py @@ -48,8 +48,8 @@ class SmileyLoader: return util.join_path(util.get_smileys_directory(), self._curr_pack) if self._curr_pack is not None else None @staticmethod - def get_packs_list(self): - d = util.curr_directory() + '/smileys/' + def get_packs_list(): + d = util.get_smileys_directory() return [x[1] for x in os.walk(d)][0] def get_smileys(self): diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 73f4d4e..41c7c38 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -2,27 +2,32 @@ from ui.menu import * from contacts.profile import * from ui.list_items import * from ui.widgets import MultilineEdit, ComboBox -import plugin_support from ui.main_screen_widgets import * -from user_data import toxes, settings import util.util as util import util.ui as util_ui class MainWindow(QtWidgets.QMainWindow): - def __init__(self, settings, tox, reset, tray): + def __init__(self, settings, tox, tray): super().__init__() self._settings = settings - self.reset = reset self.tray = tray + self._widget_factory = None + self._modal_window = None self.setAcceptDrops(True) self.initUI(tox) self._saved = False - if settings['show_welcome_screen']: - self.ws = WelcomeScreen() self.profile = None + def set_widget_factory(self, widget_factory): + self._widget_factory = widget_factory + + def show(self): + super().show() + 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") @@ -366,7 +371,7 @@ class MainWindow(QtWidgets.QMainWindow): self._settings['width'] = self.width() self._settings['height'] = self.height() self._settings.save() - QtWidgets.QApplication.closeAllWindows() + util_ui.close_all_windows() event.accept() elif QtWidgets.QSystemTrayIcon.isSystemTrayAvailable(): event.ignore() @@ -407,7 +412,7 @@ class MainWindow(QtWidgets.QMainWindow): elif event.key() == QtCore.Qt.Key_F and event.modifiers() & QtCore.Qt.ControlModifier: self.show_search_field() else: - super(MainWindow, self).keyPressEvent(event) + super().keyPressEvent(event) # ----------------------------------------------------------------------------------------------------------------- # Functions which called when user click in menu @@ -420,81 +425,66 @@ class MainWindow(QtWidgets.QMainWindow): util_ui.message_box(text, title) def network_settings(self): - self.n_s = NetworkSettings(self.reset) - self.n_s.show() + self._modal_window = self._widget_factory.create_network_settings_window() + self._modal_window.show() def plugins_menu(self): - self.p_s = PluginsSettings() - self.p_s.show() + self._modal_window = self._widget_factory.create_plugins_settings_window() + self._modal_window.show() def add_contact(self, link=''): - self.a_c = AddContact(link or '') - self.a_c.show() + self._modal_window = self._widget_factory.create_add_contact_window(link or '') + self._modal_window.show() def create_gc(self): self.profile.create_group_chat() def profile_settings(self, *args): - self.p_s = ProfileSettings() - self.p_s.show() + self._modal_window = self._widget_factory.create_profile_settings_window() + self._modal_window.show() def privacy_settings(self): - self.priv_s = PrivacySettings() - self.priv_s.show() + self._modal_window = self._widget_factory.create_privacy_settings_window() + self._modal_window.show() def notification_settings(self): - self.notif_s = NotificationsSettings() - self.notif_s.show() + self._modal_window = self._widget_factory.create_notification_settings_window() + self._modal_window.show() def interface_settings(self): - self.int_s = InterfaceSettings() - self.int_s.show() + self._modal_window = self._widget_factory.create_interface_settings_window() + self._modal_window.show() def audio_settings(self): - self.audio_s = AudioSettings() - self.audio_s.show() + self._modal_window = self._widget_factory.create_audio_settings_window() + self._modal_window.show() def video_settings(self): - self.video_s = VideoSettings() - self.video_s.show() + self._modal_window = self._widget_factory.create_video_settings_window() + self._modal_window.show() def update_settings(self): - self.update_s = UpdateSettings() - self.update_s.show() + self._modal_window = self._widget_factory.create_update_settings_window() + self._modal_window.show() def reload_plugins(self): - plugin_loader = plugin_support.PluginLoader.get_instance() - if plugin_loader is not None: - plugin_loader.reload() + if self._plugin_loader is not None: + self._plugin_loader.reload() def import_plugin(self): - import util - directory = QtWidgets.QFileDialog.getExistingDirectory(self, - util_ui.tr('Choose folder with plugin'), - util.curr_directory(), - QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) + directory = util_ui.directory_dialog(util_ui.tr('Choose folder with plugin')) if directory: src = directory + '/' - dest = curr_directory() + '/plugins/' + dest = util.get_plugins_directory() util.copy(src, dest) - msgBox = QtWidgets.QMessageBox() - msgBox.setWindowTitle( - util_ui.tr("Restart Toxygen")) - msgBox.setText( - util_ui.tr('Plugin will be loaded after restart')) - msgBox.exec_() + util_ui.message_box(util_ui.tr('Plugin will be loaded after restart'), util_ui.tr("Restart Toxygen")) def lock_app(self): - if toxes.ToxES.get_instance().has_password(): - Settings.get_instance().locked = True + if self._toxes.has_password(): + self._settings.locked = True self.hide() else: - msgBox = QtWidgets.QMessageBox() - msgBox.setWindowTitle( - util_ui.tr("Cannot lock app")) - msgBox.setText( - util_ui.tr('Error. Profile password is not set.')) - msgBox.exec_() + 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'): @@ -516,8 +506,8 @@ class MainWindow(QtWidgets.QMainWindow): def send_file(self): self.menu.hide() if self.profile.active_friend + 1and self.profile.is_active_a_friend(): - choose = util_ui.tr('Choose file') - name = QtWidgets.QFileDialog.getOpenFileName(self, choose, options=QtWidgets.QFileDialog.DontUseNativeDialog) + caption = util_ui.tr('Choose file') + name = util_ui.file_dialog(caption) if name[0]: self.profile.send_file(name[0]) @@ -533,7 +523,7 @@ class MainWindow(QtWidgets.QMainWindow): 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.smiley.setGeometry(QtCore.QRect(self.x() if self._settings['mirror_mode'] else 270 + self.x(), self.y() + self.height() - 200, self.smiley.width(), self.smiley.height())) @@ -543,7 +533,7 @@ class MainWindow(QtWidgets.QMainWindow): 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.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())) @@ -580,7 +570,6 @@ class MainWindow(QtWidgets.QMainWindow): 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 = util_ui.tr('Disallow auto accept') if allowed else util_ui.tr('Allow auto accept') if item is not None: @@ -614,9 +603,8 @@ class MainWindow(QtWidgets.QMainWindow): item = invite_menu.addAction(name) item.triggered.connect(lambda: 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 self._plugins_loader is not None: + submenu = self._plugins_loader.get_menu(self.listMenu, num) if len(submenu): plug = self.listMenu.addMenu(util_ui.tr('Plugins')) plug.addActions(submenu) @@ -640,29 +628,23 @@ class MainWindow(QtWidgets.QMainWindow): 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 '' + note = self._settings['notes'][friend.tox_id] if friend.tox_id in s['notes'] else '' user = util_ui.tr('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 friend.tox_id in self._settings['notes']: + del self._settings['notes'][friend.tox_id] if text: - s['notes'][friend.tox_id] = text - s.save() + self._settings['notes'][friend.tox_id] = text + self._settings.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) + file_name, _ = util_ui.save_file_dialog(util_ui.tr('Choose file name'), extension) if file_name: if not file_name.endswith('.' + extension): @@ -703,13 +685,12 @@ class MainWindow(QtWidgets.QMainWindow): 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) + self._settings['auto_accept_from_friends'].append(tox_id) else: - settings['auto_accept_from_friends'].remove(tox_id) - settings.save() + self._settings['auto_accept_from_friends'].remove(tox_id) + self._settings.save() def invite_friend_to_gc(self, friend_number, group_number): self.profile.invite_friend(friend_number, group_number) diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index c509359..9369fdd 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -5,8 +5,7 @@ from util.util import curr_directory, copy from ui.widgets import CenteredWidget, DataLabel, LineEdit, RubberBandWindow import pyaudio from user_data import toxes -import plugin_support -import updater +import updater.updater as updater import util.ui as util_ui @@ -228,8 +227,7 @@ class ProfileSettings(CenteredWidget): def copy(self): clipboard = QtWidgets.QApplication.clipboard() - profile = Profile.get_instance() - clipboard.setText(profile.tox_id) + clipboard.setText(self._profile.tox_id) pixmap = QtGui.QPixmap(curr_directory() + '/images/accept.png') icon = QtGui.QIcon(pixmap) self.copyId.setIcon(icon) @@ -237,8 +235,7 @@ class ProfileSettings(CenteredWidget): def copy_public_key(self): clipboard = QtWidgets.QApplication.clipboard() - profile = Profile.get_instance() - clipboard.setText(profile.tox_id[:64]) + clipboard.setText(self._profile.tox_id[:64]) pixmap = QtGui.QPixmap(curr_directory() + '/images/accept.png') icon = QtGui.QIcon(pixmap) self.copy_pk.setIcon(icon) @@ -252,8 +249,7 @@ class ProfileSettings(CenteredWidget): def set_avatar(self): choose = util_ui.tr("Choose avatar") - name = QtWidgets.QFileDialog.getOpenFileName(self, choose, None, 'Images (*.png)', - options=QtWidgets.QFileDialog.DontUseNativeDialog) + name = util_ui.file_dialog(choose, 'Images (*.png)') if name[0]: bitmap = QtGui.QPixmap(name[0]) bitmap.scaled(QtCore.QSize(128, 128), aspectRatioMode=QtCore.Qt.KeepAspectRatio, @@ -263,24 +259,21 @@ class ProfileSettings(CenteredWidget): buffer = QtCore.QBuffer(byte_array) buffer.open(QtCore.QIODevice.WriteOnly) bitmap.save(buffer, 'PNG') - Profile.get_instance().set_avatar(bytes(byte_array.data())) + self._profile.set_avatar(bytes(byte_array.data())) def export_profile(self): directory = util_ui.directory_dialog() + '/' if directory != '/': reply = util_ui.question(util_ui.tr('Do you want to move your profile to this location?'), util_ui.tr('Use new path')) - settings = Settings.get_instance() settings.export(directory) - profile = Profile.get_instance() - profile.export_db(directory) + self._profile.export_db(directory) ProfileManager.get_instance().export_profile(directory, reply) def closeEvent(self, event): - profile = Profile.get_instance() - profile.set_name(self.nick.text()) - profile.set_status_message(self.status_message.text().encode('utf-8')) - profile.set_status(self.status.currentIndex()) + self._profile.set_name(self.nick.text()) + self._profile.set_status_message(self.status_message.text().encode('utf-8')) + self._profile.set_status(self.status.currentIndex()) class NetworkSettings(CenteredWidget): @@ -367,7 +360,7 @@ class NetworkSettings(CenteredWidget): self._settings['download_nodes_list'] = self.nodes.isChecked() self._settings.save() # recreate tox instance - Profile.get_instance().reset(self.reset) + self._profile.reset() self.close() except Exception as ex: log('Exception in restart: ' + str(ex)) @@ -376,8 +369,13 @@ class NetworkSettings(CenteredWidget): class PrivacySettings(CenteredWidget): """Privacy settings form: history, typing notifications""" - def __init__(self): + def __init__(self, contacts_manager, settings): + """ + :type contacts_manager: ContactsManager + """ super().__init__() + self._contacts_manager = contacts_manager + self._settings = settings self.initUI() self.center() @@ -404,15 +402,14 @@ class PrivacySettings(CenteredWidget): 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)) - settings = Settings.get_instance() - self.typingNotifications.setChecked(settings['typing_notifications']) - self.fileautoaccept.setChecked(settings['allow_auto_accept']) - self.saveHistory.setChecked(settings['save_history']) - self.inlines.setChecked(settings['allow_inline']) - self.saveUnsentOnly.setChecked(settings['save_unsent_only']) - self.saveUnsentOnly.setEnabled(settings['save_history']) + self.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(settings['auto_accept_path'] or curr_directory()) + 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)) @@ -420,12 +417,12 @@ class PrivacySettings(CenteredWidget): 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: Profile.get_instance().block_user(self.block_id.toPlainText()) or self.close()) + 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(settings['blocked']) + 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()) @@ -433,18 +430,18 @@ class PrivacySettings(CenteredWidget): QtCore.QMetaObject.connectSlotsByName(self) def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate("privacySettings", "Privacy settings")) - self.saveHistory.setText(QtWidgets.QApplication.translate("privacySettings", "Save chat history")) - self.fileautoaccept.setText(QtWidgets.QApplication.translate("privacySettings", "Allow file auto accept")) - self.typingNotifications.setText(QtWidgets.QApplication.translate("privacySettings", "Send typing notifications")) - self.auto_path.setText(QtWidgets.QApplication.translate("privacySettings", "Auto accept default path:")) - self.change_path.setText(QtWidgets.QApplication.translate("privacySettings", "Change")) - self.inlines.setText(QtWidgets.QApplication.translate("privacySettings", "Allow inlines")) - self.block_user_label.setText(QtWidgets.QApplication.translate("privacySettings", "Block by public key:")) - self.blocked_users_label.setText(QtWidgets.QApplication.translate("privacySettings", "Blocked users:")) - self.unblock.setText(QtWidgets.QApplication.translate("privacySettings", "Unblock")) - self.block.setText(QtWidgets.QApplication.translate("privacySettings", "Block user")) - self.saveUnsentOnly.setText(QtWidgets.QApplication.translate("privacySettings", "Save unsent messages only")) + 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) @@ -454,58 +451,48 @@ class PrivacySettings(CenteredWidget): def unblock_user(self): if not self.comboBox.count(): return - title = QtWidgets.QApplication.translate("privacySettings", "Add to friend list") - info = QtWidgets.QApplication.translate("privacySettings", "Do you want to add this user to friend list?") - reply = QtWidgets.QMessageBox.question(None, title, info, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) - Profile.get_instance().unblock_user(self.comboBox.currentText(), reply == QtWidgets.QMessageBox.Yes) + 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): - settings = Settings.get_instance() - settings['typing_notifications'] = self.typingNotifications.isChecked() - settings['allow_auto_accept'] = self.fileautoaccept.isChecked() + 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 settings['save_history'] and not self.saveHistory.isChecked(): # clear history - reply = QtWidgets.QMessageBox.question(None, - QtWidgets.QApplication.translate("privacySettings", - 'Chat history'), - QtWidgets.QApplication.translate("privacySettings", - 'History will be cleaned! Continue?'), - QtWidgets.QMessageBox.Yes, - QtWidgets.QMessageBox.No) - if reply == QtWidgets.QMessageBox.Yes: - Profile.get_instance().clear_history() - settings['save_history'] = self.saveHistory.isChecked() + 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: - settings['save_history'] = self.saveHistory.isChecked() - if self.saveUnsentOnly.isChecked() and not settings['save_unsent_only']: - reply = QtWidgets.QMessageBox.question(None, - QtWidgets.QApplication.translate("privacySettings", - 'Chat history'), - QtWidgets.QApplication.translate("privacySettings", - 'History will be cleaned! Continue?'), - QtWidgets.QMessageBox.Yes, - QtWidgets.QMessageBox.No) - if reply == QtWidgets.QMessageBox.Yes: - Profile.get_instance().clear_history(None, True) - settings['save_unsent_only'] = self.saveUnsentOnly.isChecked() + 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: - settings['save_unsent_only'] = self.saveUnsentOnly.isChecked() - settings['auto_accept_path'] = self.path.toPlainText() - settings['allow_inline'] = self.inlines.isChecked() - settings.save() + 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 = QtWidgets.QFileDialog.getExistingDirectory(options=QtWidgets.QFileDialog.DontUseNativeDialog) + '/' - if directory != '/': + directory = util_ui.directory_dialog() + if directory: self.path.setPlainText(directory) class NotificationsSettings(CenteredWidget): """Notifications settings form""" - def __init__(self): - super(NotificationsSettings, self).__init__() + def __init__(self, setttings): + super().__init__() + self._settings = setttings self.initUI() self.center() @@ -523,40 +510,40 @@ class NotificationsSettings(CenteredWidget): self.groupNotifications = QtWidgets.QCheckBox(self) self.groupNotifications.setGeometry(QtCore.QRect(10, 120, 340, 18)) font = QtGui.QFont() - s = Settings.get_instance() - font.setFamily(s['font']) + font.setFamily(self._settings['font']) font.setPointSize(12) self.callsSound.setFont(font) self.soundNotifications.setFont(font) self.enableNotifications.setFont(font) self.groupNotifications.setFont(font) - self.enableNotifications.setChecked(s['notifications']) - self.soundNotifications.setChecked(s['sound_notifications']) - self.groupNotifications.setChecked(s['group_notifications']) - self.callsSound.setChecked(s['calls_sound']) + self.enableNotifications.setChecked(self._settings['notifications']) + self.soundNotifications.setChecked(self._settings['sound_notifications']) + self.groupNotifications.setChecked(self._settings['group_notifications']) + self.callsSound.setChecked(self._settings['calls_sound']) self.retranslateUi() QtCore.QMetaObject.connectSlotsByName(self) def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate("notificationsForm", "Notification settings")) - self.enableNotifications.setText(QtWidgets.QApplication.translate("notificationsForm", "Enable notifications")) - self.groupNotifications.setText(QtWidgets.QApplication.translate("notificationsForm", "Notify about all messages in groups")) - self.callsSound.setText(QtWidgets.QApplication.translate("notificationsForm", "Enable call\'s sound")) - self.soundNotifications.setText(QtWidgets.QApplication.translate("notificationsForm", "Enable sound notifications")) + self.setWindowTitle(util_ui.tr("Notification settings")) + self.enableNotifications.setText(util_ui.tr("Enable notifications")) + self.groupNotifications.setText(util_ui.tr("Notify about all messages in groups")) + self.callsSound.setText(util_ui.tr("Enable call\'s sound")) + self.soundNotifications.setText(util_ui.tr("Enable sound notifications")) def closeEvent(self, *args, **kwargs): - settings = Settings.get_instance() - settings['notifications'] = self.enableNotifications.isChecked() - settings['sound_notifications'] = self.soundNotifications.isChecked() - settings['group_notifications'] = self.groupNotifications.isChecked() - settings['calls_sound'] = self.callsSound.isChecked() - settings.save() + self._settings['notifications'] = self.enableNotifications.isChecked() + self._settings['sound_notifications'] = self.soundNotifications.isChecked() + self._settings['group_notifications'] = self.groupNotifications.isChecked() + self._settings['calls_sound'] = self.callsSound.isChecked() + self._settings.save() class InterfaceSettings(CenteredWidget): """Interface settings form""" - def __init__(self): - super(InterfaceSettings, self).__init__() + def __init__(self, settings, smiley_loader): + super().__init__() + self._settings = settings + self._smiley_loader = smiley_loader self.initUI() self.center() @@ -566,18 +553,17 @@ class InterfaceSettings(CenteredWidget): self.setMaximumSize(QtCore.QSize(400, 650)) self.label = QtWidgets.QLabel(self) self.label.setGeometry(QtCore.QRect(30, 10, 370, 20)) - settings = Settings.get_instance() font = QtGui.QFont() font.setPointSize(14) font.setBold(True) - font.setFamily(settings['font']) + font.setFamily(self._settings['font']) self.label.setFont(font) self.themeSelect = QtWidgets.QComboBox(self) self.themeSelect.setGeometry(QtCore.QRect(30, 40, 120, 30)) - self.themeSelect.addItems(list(settings.built_in_themes().keys())) - theme = settings['theme'] - if theme in settings.built_in_themes().keys(): - index = list(settings.built_in_themes().keys()).index(theme) + self.themeSelect.addItems(list(self._settings.built_in_themes().keys())) + theme = self._settings['theme'] + if theme in self._settings.built_in_themes().keys(): + index = list(self._settings.built_in_themes().keys()).index(theme) else: index = 0 self.themeSelect.setCurrentIndex(index) @@ -586,28 +572,27 @@ class InterfaceSettings(CenteredWidget): supported = sorted(Settings.supported_languages().keys(), reverse=True) for key in supported: self.lang_choose.insertItem(0, key) - if settings['language'] == key: + if self._settings['language'] == key: self.lang_choose.setCurrentIndex(0) self.lang = QtWidgets.QLabel(self) self.lang.setGeometry(QtCore.QRect(30, 80, 370, 20)) self.lang.setFont(font) self.mirror_mode = QtWidgets.QCheckBox(self) self.mirror_mode.setGeometry(QtCore.QRect(30, 160, 370, 20)) - self.mirror_mode.setChecked(settings['mirror_mode']) + self.mirror_mode.setChecked(self._settings['mirror_mode']) self.smileys = QtWidgets.QCheckBox(self) self.smileys.setGeometry(QtCore.QRect(30, 190, 120, 20)) - self.smileys.setChecked(settings['smileys']) + self.smileys.setChecked(self._settings['smileys']) self.smiley_pack_label = QtWidgets.QLabel(self) self.smiley_pack_label.setGeometry(QtCore.QRect(30, 230, 370, 20)) self.smiley_pack_label.setFont(font) self.smiley_pack = QtWidgets.QComboBox(self) self.smiley_pack.setGeometry(QtCore.QRect(30, 260, 160, 30)) - sm = smileys.SmileyLoader.get_instance() - self.smiley_pack.addItems(sm.get_packs_list()) + self.smiley_pack.addItems(self._smiley_loader.get_packs_list()) try: - ind = sm.get_packs_list().index(settings['smiley_pack']) + ind = self._smiley_loader.get_packs_list().index(self._settings['smiley_pack']) except: - ind = sm.get_packs_list().index('default') + ind = self._smiley_loader.get_packs_list().index('default') self.smiley_pack.setCurrentIndex(ind) self.messages_font_size_label = QtWidgets.QLabel(self) self.messages_font_size_label.setGeometry(QtCore.QRect(30, 300, 370, 20)) @@ -615,7 +600,7 @@ class InterfaceSettings(CenteredWidget): self.messages_font_size = QtWidgets.QComboBox(self) self.messages_font_size.setGeometry(QtCore.QRect(30, 330, 160, 30)) self.messages_font_size.addItems([str(x) for x in range(10, 25)]) - self.messages_font_size.setCurrentIndex(settings['message_font_size'] - 10) + self.messages_font_size.setCurrentIndex(self._settings['message_font_size'] - 10) self.unread = QtWidgets.QPushButton(self) self.unread.setGeometry(QtCore.QRect(30, 470, 340, 30)) @@ -623,15 +608,15 @@ class InterfaceSettings(CenteredWidget): self.compact_mode = QtWidgets.QCheckBox(self) self.compact_mode.setGeometry(QtCore.QRect(30, 380, 370, 20)) - self.compact_mode.setChecked(settings['compact_mode']) + self.compact_mode.setChecked(self._settings['compact_mode']) self.close_to_tray = QtWidgets.QCheckBox(self) self.close_to_tray.setGeometry(QtCore.QRect(30, 410, 370, 20)) - self.close_to_tray.setChecked(settings['close_to_tray']) + self.close_to_tray.setChecked(self._settings['close_to_tray']) self.show_avatars = QtWidgets.QCheckBox(self) self.show_avatars.setGeometry(QtCore.QRect(30, 440, 370, 20)) - self.show_avatars.setChecked(settings['show_avatars']) + self.show_avatars.setChecked(self._settings['show_avatars']) self.choose_font = QtWidgets.QPushButton(self) self.choose_font.setGeometry(QtCore.QRect(30, 510, 340, 30)) @@ -649,54 +634,43 @@ class InterfaceSettings(CenteredWidget): QtCore.QMetaObject.connectSlotsByName(self) def retranslateUi(self): - self.show_avatars.setText(QtWidgets.QApplication.translate("interfaceForm", "Show avatars in chat")) - self.setWindowTitle(QtWidgets.QApplication.translate("interfaceForm", "Interface settings")) - self.label.setText(QtWidgets.QApplication.translate("interfaceForm", "Theme:")) - self.lang.setText(QtWidgets.QApplication.translate("interfaceForm", "Language:")) - self.smileys.setText(QtWidgets.QApplication.translate("interfaceForm", "Smileys")) - self.smiley_pack_label.setText(QtWidgets.QApplication.translate("interfaceForm", "Smiley pack:")) - self.mirror_mode.setText(QtWidgets.QApplication.translate("interfaceForm", "Mirror mode")) - self.messages_font_size_label.setText(QtWidgets.QApplication.translate("interfaceForm", "Messages font size:")) - self.unread.setText(QtWidgets.QApplication.translate("interfaceForm", "Select unread messages notification color")) - self.compact_mode.setText(QtWidgets.QApplication.translate("interfaceForm", "Compact contact list")) - self.import_smileys.setText(QtWidgets.QApplication.translate("interfaceForm", "Import smiley pack")) - self.import_stickers.setText(QtWidgets.QApplication.translate("interfaceForm", "Import sticker pack")) - self.close_to_tray.setText(QtWidgets.QApplication.translate("interfaceForm", "Close to tray")) - self.choose_font.setText(QtWidgets.QApplication.translate("interfaceForm", "Select font")) + self.show_avatars.setText(util_ui.tr("Show avatars in chat")) + self.setWindowTitle(util_ui.tr("Interface settings")) + self.label.setText(util_ui.tr("Theme:")) + self.lang.setText(util_ui.tr("Language:")) + self.smileys.setText(util_ui.tr("Smileys")) + self.smiley_pack_label.setText(util_ui.tr("Smiley pack:")) + self.mirror_mode.setText(util_ui.tr("Mirror mode")) + self.messages_font_size_label.setText(util_ui.tr("Messages font size:")) + self.unread.setText(util_ui.tr("Select unread messages notification color")) + self.compact_mode.setText(util_ui.tr("Compact contact list")) + self.import_smileys.setText(util_ui.tr("Import smiley pack")) + self.import_stickers.setText(util_ui.tr("Import sticker pack")) + self.close_to_tray.setText(util_ui.tr("Close to tray")) + self.choose_font.setText(util_ui.tr("Select font")) def import_st(self): - directory = QtWidgets.QFileDialog.getExistingDirectory(self, - QtWidgets.QApplication.translate("MainWindow", - 'Choose folder with sticker pack'), - curr_directory(), - QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) - + directory = util_ui.directory_dialog(util_ui.tr('Choose folder with sticker pack')) if directory: src = directory + '/' dest = curr_directory() + '/stickers/' + os.path.basename(directory) + '/' copy(src, dest) def import_sm(self): - directory = QtWidgets.QFileDialog.getExistingDirectory(self, - QtWidgets.QApplication.translate("MainWindow", - 'Choose folder with smiley pack'), - curr_directory(), - QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) - + directory = util_ui.directory_dialog(util_ui.tr('Choose folder with smiley pack')) if directory: src = directory + '/' dest = curr_directory() + '/smileys/' + os.path.basename(directory) + '/' copy(src, dest) def new_font(self): - settings = Settings.get_instance() - font, ok = QtWidgets.QFontDialog.getFont(QtGui.QFont(settings['font'], 10), self) + font, ok = QtWidgets.QFontDialog.getFont(QtGui.QFont(self._settings['font'], 10), self) if ok: - settings['font'] = font.family() - settings.save() + self._settings['font'] = font.family() + self._settings.save() msgBox = QtWidgets.QMessageBox() - text = QtWidgets.QApplication.translate("interfaceForm", 'Restart app to apply settings') - msgBox.setWindowTitle(QtWidgets.QApplication.translate("interfaceForm", 'Restart required')) + text = util_ui.tr('Restart app to apply settings') + msgBox.setWindowTitle(util_ui.tr('Restart required')) msgBox.setText(text) msgBox.exec_() @@ -747,11 +721,7 @@ class InterfaceSettings(CenteredWidget): Profile.get_instance().update() settings.save() if restart: - msgBox = QtWidgets.QMessageBox() - text = QtWidgets.QApplication.translate("interfaceForm", 'Restart app to apply settings') - msgBox.setWindowTitle(QtWidgets.QApplication.translate("interfaceForm", 'Restart required')) - msgBox.setText(text) - msgBox.exec_() + util_ui.message_box(util_ui.tr('Restart app to apply settings'), util_ui.tr('Restart required')) class AudioSettings(CenteredWidget): @@ -759,8 +729,9 @@ class AudioSettings(CenteredWidget): Audio calls settings form """ - def __init__(self): - super(AudioSettings, self).__init__() + def __init__(self, settings): + super().__init__() + self._settings = settings self.initUI() self.retranslateUi() self.center() @@ -774,11 +745,10 @@ class AudioSettings(CenteredWidget): self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20)) self.out_label = QtWidgets.QLabel(self) self.out_label.setGeometry(QtCore.QRect(25, 65, 350, 20)) - settings = Settings.get_instance() font = QtGui.QFont() font.setPointSize(16) font.setBold(True) - font.setFamily(settings['font']) + font.setFamily(self._settings['font']) self.in_label.setFont(font) self.out_label.setFont(font) self.input = QtWidgets.QComboBox(self) @@ -795,20 +765,19 @@ class AudioSettings(CenteredWidget): if device["maxOutputChannels"]: self.output.addItem(str(device["name"])) self.out_indexes.append(i) - self.input.setCurrentIndex(self.in_indexes.index(settings.audio['input'])) - self.output.setCurrentIndex(self.out_indexes.index(settings.audio['output'])) + self.input.setCurrentIndex(self.in_indexes.index(self._settings.audio['input'])) + self.output.setCurrentIndex(self.out_indexes.index(self._settings.audio['output'])) QtCore.QMetaObject.connectSlotsByName(self) def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate("audioSettingsForm", "Audio settings")) - self.in_label.setText(QtWidgets.QApplication.translate("audioSettingsForm", "Input device:")) - self.out_label.setText(QtWidgets.QApplication.translate("audioSettingsForm", "Output device:")) + self.setWindowTitle(util_ui.tr("Audio settings")) + self.in_label.setText(util_ui.tr("Input device:")) + self.out_label.setText(util_ui.tr("Output device:")) def closeEvent(self, event): - settings = Settings.get_instance() - settings.audio['input'] = self.in_indexes[self.input.currentIndex()] - settings.audio['output'] = self.out_indexes[self.output.currentIndex()] - settings.save() + self._settings.audio['input'] = self.in_indexes[self.input.currentIndex()] + self._settings.audio['output'] = self.out_indexes[self.output.currentIndex()] + self._settings.save() class DesktopAreaSelectionWindow(RubberBandWindow): @@ -828,8 +797,9 @@ class VideoSettings(CenteredWidget): Audio calls settings form """ - def __init__(self): + def __init__(self, settings): super().__init__() + self._settings = settings self.initUI() self.retranslateUi() self.center() @@ -842,11 +812,10 @@ class VideoSettings(CenteredWidget): self.setMaximumSize(QtCore.QSize(400, 120)) self.in_label = QtWidgets.QLabel(self) self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20)) - settings = Settings.get_instance() font = QtGui.QFont() font.setPointSize(16) font.setBold(True) - font.setFamily(settings['font']) + font.setFamily(self._settings['font']) self.in_label.setFont(font) self.video_size = QtWidgets.QComboBox(self) self.video_size.setGeometry(QtCore.QRect(25, 70, 350, 30)) @@ -861,7 +830,7 @@ class VideoSettings(CenteredWidget): screen = QtWidgets.QApplication.primaryScreen() size = screen.size() self.frame_max_sizes = [(size.width(), size.height())] - desktop = QtWidgets.QApplication.translate("videoSettingsForm", "Desktop") + desktop = util_ui.tr("Desktop") self.input.addItem(desktop) for i in range(10): v = cv2.VideoCapture(i) @@ -876,15 +845,15 @@ class VideoSettings(CenteredWidget): self.frame_max_sizes.append((width, height)) self.input.addItem('Device #' + str(i)) try: - index = self.devices.index(settings.video['device']) + index = self.devices.index(self._settings.video['device']) self.input.setCurrentIndex(index) except: print('Video devices error!') def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate("videoSettingsForm", "Video settings")) - self.in_label.setText(QtWidgets.QApplication.translate("videoSettingsForm", "Device:")) - self.button.setText(QtWidgets.QApplication.translate("videoSettingsForm", "Select region")) + self.setWindowTitle(util_ui.tr("Video settings")) + self.in_label.setText(util_ui.tr("Device:")) + self.button.setText(util_ui.tr("Select region")) def button_clicked(self): self.desktopAreaSelection = DesktopAreaSelectionWindow(self) @@ -893,24 +862,22 @@ class VideoSettings(CenteredWidget): if self.input.currentIndex() == 0: return try: - settings = Settings.get_instance() - settings.video['device'] = self.devices[self.input.currentIndex()] + self._settings.video['device'] = self.devices[self.input.currentIndex()] text = self.video_size.currentText() - settings.video['width'] = int(text.split(' ')[0]) - settings.video['height'] = int(text.split(' ')[-1]) - settings.save() + 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 - settings = Settings.get_instance() - settings.video['device'] = -1 - settings.video['width'] = width - settings.video['height'] = height - settings.video['x'] = x - settings.video['y'] = y - settings.save() + 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 selectionChanged(self): if self.input.currentIndex() == 0: @@ -940,8 +907,10 @@ class PluginsSettings(CenteredWidget): Plugins settings form """ - def __init__(self): - super(PluginsSettings, self).__init__() + def __init__(self, plugin_loader): + super().__init__() + self._plugin_loader = plugin_loader + self._window = None self.initUI() self.center() self.retranslateUi() @@ -961,32 +930,27 @@ class PluginsSettings(CenteredWidget): self.open = QtWidgets.QPushButton(self) self.open.setGeometry(QtCore.QRect(30, 170, 340, 30)) self.open.clicked.connect(self.open_plugin) - self.pl_loader = plugin_support.PluginLoader.get_instance() self.update_list() self.comboBox.currentIndexChanged.connect(self.show_data) self.show_data() def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate('PluginsForm', "Plugins")) - self.open.setText(QtWidgets.QApplication.translate('PluginsForm', "Open selected plugin")) + 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() + self._window = window + self._window.show() else: - msgBox = QtWidgets.QMessageBox() - text = QtWidgets.QApplication.translate("PluginsForm", 'No GUI found for this plugin') - msgBox.setWindowTitle(QtWidgets.QApplication.translate("PluginsForm", 'Error')) - msgBox.setText(text) - msgBox.exec_() + 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.pl_loader.get_plugins_list() + data = self._plugin_loader.get_plugins_list() self.comboBox.addItems(list(map(lambda x: x[0], data))) self.data = data @@ -994,26 +958,26 @@ class PluginsSettings(CenteredWidget): ind = self.comboBox.currentIndex() if len(self.data): plugin = self.data[ind] - descr = plugin[2] or QtWidgets.QApplication.translate("PluginsForm", "No description available") + descr = plugin[2] or util_ui.tr("No description available") self.label.setText(descr) if plugin[1]: - self.button.setText(QtWidgets.QApplication.translate("PluginsForm", "Disable plugin")) + self.button.setText(util_ui.tr("Disable plugin")) else: - self.button.setText(QtWidgets.QApplication.translate("PluginsForm", "Enable plugin")) + self.button.setText(util_ui.tr("Enable plugin")) else: self.open.setVisible(False) self.button.setVisible(False) - self.label.setText(QtWidgets.QApplication.translate("PluginsForm", "No plugins found")) + self.label.setText(util_ui.tr("No plugins found")) def button_click(self): ind = self.comboBox.currentIndex() plugin = self.data[ind] - self.pl_loader.toggle_plugin(plugin[-1]) + self._plugin_loader.toggle_plugin(plugin[-1]) plugin[1] = not plugin[1] if plugin[1]: - self.button.setText(QtWidgets.QApplication.translate("PluginsForm", "Disable plugin")) + self.button.setText(util_ui.tr("Disable plugin")) else: - self.button.setText(QtWidgets.QApplication.translate("PluginsForm", "Enable plugin")) + self.button.setText(util_ui.tr("Enable plugin")) class UpdateSettings(CenteredWidget): @@ -1021,8 +985,9 @@ class UpdateSettings(CenteredWidget): Updates settings form """ - def __init__(self): - super(UpdateSettings, self).__init__() + def __init__(self, settings): + super().__init__() + self._settings = settings self.initUI() self.center() @@ -1033,61 +998,44 @@ class UpdateSettings(CenteredWidget): self.setMaximumSize(QtCore.QSize(400, 120)) self.in_label = QtWidgets.QLabel(self) self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20)) - settings = Settings.get_instance() font = QtGui.QFont() font.setPointSize(16) font.setBold(True) - font.setFamily(settings['font']) + font.setFamily(self._settings['font']) self.in_label.setFont(font) self.autoupdate = QtWidgets.QComboBox(self) self.autoupdate.setGeometry(QtCore.QRect(25, 30, 350, 30)) self.button = QtWidgets.QPushButton(self) self.button.setGeometry(QtCore.QRect(25, 70, 350, 30)) - self.button.setEnabled(settings['update']) + self.button.setEnabled(self._settings['update']) self.button.clicked.connect(self.update_client) self.retranslateUi() - self.autoupdate.setCurrentIndex(settings['update']) + self.autoupdate.setCurrentIndex(self._settings['update']) QtCore.QMetaObject.connectSlotsByName(self) def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate("updateSettingsForm", "Update settings")) - self.in_label.setText(QtWidgets.QApplication.translate("updateSettingsForm", "Select update mode:")) - self.button.setText(QtWidgets.QApplication.translate("updateSettingsForm", "Update Toxygen")) - self.autoupdate.addItem(QtWidgets.QApplication.translate("updateSettingsForm", "Disabled")) - self.autoupdate.addItem(QtWidgets.QApplication.translate("updateSettingsForm", "Manual")) - self.autoupdate.addItem(QtWidgets.QApplication.translate("updateSettingsForm", "Auto")) + self.setWindowTitle(util_ui.tr("Update settings")) + self.in_label.setText(util_ui.tr("Select update mode:")) + self.button.setText(util_ui.tr("Update Toxygen")) + self.autoupdate.addItem(util_ui.tr("Disabled")) + self.autoupdate.addItem(util_ui.tr("Manual")) + self.autoupdate.addItem(util_ui.tr("Auto")) def closeEvent(self, event): - settings = Settings.get_instance() - settings['update'] = self.autoupdate.currentIndex() - settings.save() + self._settings['update'] = self.autoupdate.currentIndex() + self._settings.save() def update_client(self): if not updater.connection_available(): - msgBox = QtWidgets.QMessageBox() - msgBox.setWindowTitle( - QtWidgets.QApplication.translate("updateSettingsForm", "Error")) - text = (QtWidgets.QApplication.translate("updateSettingsForm", 'Problems with internet connection')) - msgBox.setText(text) - msgBox.exec_() + util_ui.message_box(util_ui.tr('Problems with internet connection'), util_ui.tr("Error")) return if not updater.updater_available(): - msgBox = QtWidgets.QMessageBox() - msgBox.setWindowTitle( - QtWidgets.QApplication.translate("updateSettingsForm", "Error")) - text = (QtWidgets.QApplication.translate("updateSettingsForm", 'Updater not found')) - msgBox.setText(text) - msgBox.exec_() + util_ui.message_box(util_ui.tr('Updater not found'), util_ui.tr("Error")) return version = updater.check_for_updates() if version is not None: updater.download(version) - QtWidgets.QApplication.closeAllWindows() + util_ui.close_all_windows() else: - msgBox = QtWidgets.QMessageBox() - msgBox.setWindowTitle( - QtWidgets.QApplication.translate("updateSettingsForm", "No updates found")) - text = (QtWidgets.QApplication.translate("updateSettingsForm", 'Toxygen is up to date')) - msgBox.setText(text) - msgBox.exec_() + util_ui.message_box(util_ui.tr('Toxygen is up to date'), util_ui.tr("No updates found")) diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py index 60ae8c3..46c7c5c 100644 --- a/toxygen/ui/widgets_factory.py +++ b/toxygen/ui/widgets_factory.py @@ -4,12 +4,13 @@ from ui.menu import * class WidgetsFactory: - def __init__(self, settings, profile, contacts_manager, file_transfer_handler, smiley_loader): + def __init__(self, settings, profile, contacts_manager, file_transfer_handler, smiley_loader, plugin_loader): self._settings = settings self._profile = profile self._contacts_manager = contacts_manager self._file_transfer_handler = file_transfer_handler self._smiley_loader = smiley_loader + self._plugin_loader = plugin_loader def create_screenshot_window(self, *args): return ScreenShotWindow(self._file_transfer_handler, *args) @@ -25,3 +26,31 @@ class WidgetsFactory: def create_network_settings_window(self): return NetworkSettings(self._settings, self._profile.reset) + + def create_audio_settings_window(self): + return AudioSettings(self._settings) + + def create_video_settings_window(self): + return VideoSettings(self._settings) + + def create_update_settings_window(self): + return UpdateSettings(self._settings) + + def create_plugins_settings_window(self): + return PluginsSettings(self._plugin_loader) + + def create_add_contact_window(self, tox_id): + return AddContact(self._contacts_manager, tox_id) + + def create_welcome_window(self): + return WelcomeScreen(self._settings) + + def create_privacy_settings_window(self): + return PrivacySettings(self._contacts_manager, self._settings) + + def create_interface_settings_window(self): + return InterfaceSettings(self._settings, self._smiley_loader) + + def create_notification_settings_window(self): + return NotificationsSettings(self._settings) + diff --git a/toxygen/util/ui.py b/toxygen/util/ui.py index a117202..b2b5712 100644 --- a/toxygen/util/ui.py +++ b/toxygen/util/ui.py @@ -31,4 +31,19 @@ def directory_dialog(caption=''): QtWidgets.QFileDialog.DontUseNativeDialog) -# TODO: move all dialogs here +def file_dialog(caption, file_filter=None): + return QtWidgets.QFileDialog.getOpenFileName(None, caption, util.curr_directory(), file_filter, + options=QtWidgets.QFileDialog.DontUseNativeDialog) + + +def save_file_dialog(caption, filter=None): + return QtWidgets.QFileDialog.getSaveFileName(None, caption, util.curr_directory(), + filter=filter, + options=QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) + + +def close_all_windows(): + QtWidgets.QApplication.closeAllWindows() + + +# TODO: all dialogs diff --git a/toxygen/util/util.py b/toxygen/util/util.py index b93a45a..f7ac9ff 100644 --- a/toxygen/util/util.py +++ b/toxygen/util/util.py @@ -65,6 +65,11 @@ def get_translations_directory(): return get_app_directory('translations') +@cached +def get_plugins_directory(): + return get_app_directory('plugins') + + def get_app_directory(directory_name): return os.path.join(get_base_directory(), directory_name) @@ -126,12 +131,6 @@ def time_offset(): return result -def append_slash(s): - if len(s) and s[-1] not in ('\\', '/'): - s += '/' - return s - - @cached def is_64_bit(): return sys.maxsize > 2 ** 32 From c81d9a3696d0d0b547683482757742326b1c6eed Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 30 Apr 2018 20:46:44 +0300 Subject: [PATCH 013/138] images path fixes, all screens loading fixed --- toxygen/app.py | 6 +-- toxygen/ui/main_screen.py | 16 +++---- toxygen/ui/main_screen_widgets.py | 28 +++++++------ toxygen/ui/menu.py | 69 +++++++++++++++---------------- toxygen/ui/widgets.py | 21 +++++----- toxygen/ui/widgets_factory.py | 8 ++-- toxygen/user_data/settings.py | 4 +- 7 files changed, 79 insertions(+), 73 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index 17c760e..c8f7f14 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -156,8 +156,9 @@ class App: self._contacts_provider = ContactProvider(self._tox, self._friend_factory) profile = Profile(self._profile_manager, self._tox, self._ms, self._file_transfer_handler) self._smiley_loader = SmileyLoader(self._settings) + self._plugin_loader = PluginLoader(self._tox, self._toxes, profile, self._settings) # plugins support widgets_factory = WidgetsFactory(self._settings, profile, self._contacts_manager, self._file_transfer_handler, - self._smiley_loader, self._plugin_loader) + self._smiley_loader, self._plugin_loader, self._toxes) self._contacts_manager = ContactsManager(self._tox, self._settings, self._ms, self._profile_manager, self._contacts_provider, db) self._calls_manager = CallsManager(self._tox.AV, self._settings) @@ -167,10 +168,9 @@ class App: self._ms.show() self._tray = tray.init_tray(profile, self._settings, self._ms) + self._ms.set_tray(self._tray) self._tray.show() - self._plugin_loader = PluginLoader(self._tox, self._toxes, profile, self._settings) # plugins support - # callbacks initialization callbacks.init_callbacks(self._tox, profile, self._settings, self._plugin_loader, self._contacts_manager, self._calls_manager, self._file_transfer_handler, self._ms, self._tray) diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 41c7c38..7f2a1c0 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -1,4 +1,3 @@ -from ui.menu import * from contacts.profile import * from ui.list_items import * from ui.widgets import MultilineEdit, ComboBox @@ -12,7 +11,7 @@ class MainWindow(QtWidgets.QMainWindow): def __init__(self, settings, tox, tray): super().__init__() self._settings = settings - self.tray = tray + self._tray = tray self._widget_factory = None self._modal_window = None self.setAcceptDrops(True) @@ -23,6 +22,9 @@ class MainWindow(QtWidgets.QMainWindow): def set_widget_factory(self, widget_factory): self._widget_factory = widget_factory + def set_tray(self, tray): + self._tray = tray + def show(self): super().show() if self._settings['show_welcome_screen']: @@ -112,7 +114,7 @@ class MainWindow(QtWidgets.QMainWindow): def event(self, event): if event.type() == QtCore.QEvent.WindowActivate: - self.tray.setIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) + self._tray.setIcon(QtGui.QIcon(util.join_path(util.get_images_directory(), 'icon.png'))) self.messages.repaint() return super().event(event) @@ -168,12 +170,12 @@ class MainWindow(QtWidgets.QMainWindow): self.menuButton = MenuButton(Form, self.show_menu) self.menuButton.setGeometry(QtCore.QRect(QtCore.QRect(455, 3, 55, 55))) - pixmap = QtGui.QPixmap('send.png') + 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('menu.png') + 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)) @@ -187,7 +189,7 @@ class MainWindow(QtWidgets.QMainWindow): 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') + pixmap.load(util.join_path(util.get_images_directory(), 'search.png')) self.search_label.setScaledContents(False) self.search_label.setPixmap(pixmap) @@ -267,7 +269,7 @@ class MainWindow(QtWidgets.QMainWindow): 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') + 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) diff --git a/toxygen/ui/main_screen_widgets.py b/toxygen/ui/main_screen_widgets.py index fe66a68..390a55a 100644 --- a/toxygen/ui/main_screen_widgets.py +++ b/toxygen/ui/main_screen_widgets.py @@ -5,6 +5,7 @@ import smileys import urllib import util.util as util import util.ui as util_ui +from stickers.stickers import load_stickers class MessageArea(QtWidgets.QPlainTextEdit): @@ -206,30 +207,30 @@ class DropdownMenu(QtWidgets.QWidget): self.stickerButton = QtWidgets.QPushButton(self) self.stickerButton.setGeometry(QtCore.QRect(60, 0, 60, 60)) - pixmap = QtGui.QPixmap(util.get_images_directory() + 'file.png') + pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'file.png')) icon = QtGui.QIcon(pixmap) self.fileTransferButton.setIcon(icon) 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) self.screenshotButton.setIcon(icon) 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) self.smileyButton.setIcon(icon) 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) self.stickerButton.setIcon(icon) self.stickerButton.setIconSize(QtCore.QSize(55, 55)) - self.screenshotButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send screenshot")) - self.fileTransferButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send file")) - self.smileyButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Add smiley")) - self.stickerButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send sticker")) + self.screenshotButton.setToolTip(util_ui.tr("Send screenshot")) + self.fileTransferButton.setToolTip(util_ui.tr("Send file")) + self.smileyButton.setToolTip(util_ui.tr("Add smiley")) + self.stickerButton.setToolTip(util_ui.tr("Send sticker")) self.fileTransferButton.clicked.connect(parent.send_file) self.screenshotButton.clicked.connect(parent.send_screenshot) @@ -263,15 +264,16 @@ class StickerItem(QtWidgets.QWidget): class StickerWindow(QtWidgets.QWidget): """Sticker selection window""" - def __init__(self, parent): + def __init__(self, parent, file_transfer_handler): super().__init__() + self._file_transfer_handler = file_transfer_handler self.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.setMaximumSize(250, 200) self.setMinimumSize(250, 200) self.list = QtWidgets.QListWidget(self) self.list.setGeometry(QtCore.QRect(0, 0, 250, 200)) - self.arr = smileys.sticker_loader() - for sticker in self.arr: + self._stickers = load_stickers() + for sticker in self._stickers: item = StickerItem(sticker) elem = QtWidgets.QListWidgetItem() elem.setSizeHint(QtCore.QSize(250, item.height())) @@ -284,7 +286,7 @@ class StickerWindow(QtWidgets.QWidget): def click(self, index): num = index.row() - self.parent.profile.send_sticker(self.arr[num]) + self._file_transfer_handler.send_sticker(self._stickers[num]) self.close() def leaveEvent(self, event): @@ -377,7 +379,7 @@ class SearchScreen(QtWidgets.QWidget): self.search_button = ClickableLabel(self) self.search_button.setGeometry(width - 160, 0, 40, 40) pixmap = QtGui.QPixmap() - pixmap.load(util.curr_directory() + '/images/search.png') + pixmap.load(util.join_path(util.get_images_directory(), 'search.png')) self.search_button.setScaledContents(False) self.search_button.setAlignment(QtCore.Qt.AlignCenter) self.search_button.setPixmap(pixmap) diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index 9369fdd..b694574 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -1,7 +1,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets from user_data.settings import * from contacts.profile import Profile -from util.util import curr_directory, copy +from util.util import curr_directory, copy, get_stickers_directory, join_path from ui.widgets import CenteredWidget, DataLabel, LineEdit, RubberBandWindow import pyaudio from user_data import toxes @@ -12,8 +12,9 @@ import util.ui as util_ui class AddContact(CenteredWidget): """Add contact form""" - def __init__(self, contacts_manager, tox_id=''): + def __init__(self, settings, contacts_manager, tox_id=''): super().__init__() + self._settings = settings self._contacts_manager = contacts_manager self.initUI(tox_id) self._adding = False @@ -37,7 +38,7 @@ class AddContact(CenteredWidget): self.error_label = DataLabel(self) self.error_label.setGeometry(QtCore.QRect(120, 10, 420, 20)) font = QtGui.QFont() - font.setFamily(Settings.get_instance()['font']) + font.setFamily(self._settings['font']) font.setPointSize(10) font.setWeight(30) self.error_label.setFont(font) @@ -83,9 +84,11 @@ class AddContact(CenteredWidget): class ProfileSettings(CenteredWidget): """Form with profile settings such as name, status, TOX ID""" - def __init__(self, profile): + def __init__(self, profile, settings, toxes): super().__init__() self._profile = profile + self._settings = settings + self._toxes = toxes self.initUI() self.center() @@ -104,7 +107,7 @@ class ProfileSettings(CenteredWidget): self.label = QtWidgets.QLabel(self) self.label.setGeometry(QtCore.QRect(40, 30, 91, 25)) font = QtGui.QFont() - font.setFamily(Settings.get_instance()['font']) + font.setFamily(self._settings['font']) font.setPointSize(18) font.setWeight(75) font.setBold(True) @@ -119,8 +122,7 @@ class ProfileSettings(CenteredWidget): self.tox_id.setGeometry(QtCore.QRect(15, 210, 685, 21)) font.setPointSize(10) self.tox_id.setFont(font) - s = profile.tox_id - self.tox_id.setText(s) + self.tox_id.setText(self._profile.tox_id) self.copyId = QtWidgets.QPushButton(self) self.copyId.setGeometry(QtCore.QRect(40, 250, 180, 30)) self.copyId.clicked.connect(self.copy) @@ -163,7 +165,7 @@ class ProfileSettings(CenteredWidget): self.warning.setStyleSheet('QLabel { color: #BC1C1C; }') self.default = QtWidgets.QPushButton(self) self.default.setGeometry(QtCore.QRect(40, 550, 620, 30)) - path, name = Settings.get_auto_profile() + auto_profile = Settings.get_auto_profile() self.auto = path + name == ProfileManager.get_path() + Settings.get_instance().name self.default.clicked.connect(self.auto_profile) self.retranslateUi() @@ -214,8 +216,7 @@ class ProfileSettings(CenteredWidget): def new_password(self): if self.password.text() == self.confirm_password.text(): if not len(self.password.text()) or len(self.password.text()) >= 8: - e = toxes.ToxES.get_instance() - e.set_password(self.password.text()) + self._toxes.set_password(self.password.text()) self.close() else: self.not_match.setText( @@ -266,7 +267,7 @@ class ProfileSettings(CenteredWidget): if directory != '/': reply = util_ui.question(util_ui.tr('Do you want to move your profile to this location?'), util_ui.tr('Use new path')) - settings.export(directory) + self._settings.export(directory) self._profile.export_db(directory) ProfileManager.get_instance().export_profile(directory, reply) @@ -652,9 +653,8 @@ class InterfaceSettings(CenteredWidget): def import_st(self): directory = util_ui.directory_dialog(util_ui.tr('Choose folder with sticker pack')) if directory: - src = directory + '/' - dest = curr_directory() + '/stickers/' + os.path.basename(directory) + '/' - copy(src, dest) + dest = join_path(get_stickers_directory(), os.path.basename(directory)) + copy(directory, dest) def import_sm(self): directory = util_ui.directory_dialog(util_ui.tr('Choose folder with smiley pack')) @@ -668,6 +668,7 @@ class InterfaceSettings(CenteredWidget): if ok: self._settings['font'] = font.family() self._settings.save() + util_ui.question() msgBox = QtWidgets.QMessageBox() text = util_ui.tr('Restart app to apply settings') msgBox.setWindowTitle(util_ui.tr('Restart required')) @@ -675,51 +676,49 @@ class InterfaceSettings(CenteredWidget): msgBox.exec_() def select_color(self): - settings = Settings.get_instance() - col = QtWidgets.QColorDialog.getColor(QtGui.QColor(settings['unread_color'])) + col = QtWidgets.QColorDialog.getColor(QtGui.QColor(self._settings['unread_color'])) if col.isValid(): name = col.name() - settings['unread_color'] = name - settings.save() + self._settings['unread_color'] = name + self._settings.save() def closeEvent(self, event): - settings = Settings.get_instance() - settings['theme'] = str(self.themeSelect.currentText()) + self._settings['theme'] = str(self.themeSelect.currentText()) try: - theme = settings['theme'] + theme = self._settings['theme'] app = QtWidgets.QApplication.instance() - with open(curr_directory() + settings.built_in_themes()[theme]) as fl: + with open(curr_directory() + self._settings.built_in_themes()[theme]) as fl: style = fl.read() app.setStyleSheet(style) except IsADirectoryError: app.setStyleSheet('') # for default style - settings['smileys'] = self.smileys.isChecked() + self._settings['smileys'] = self.smileys.isChecked() restart = False - if settings['mirror_mode'] != self.mirror_mode.isChecked(): - settings['mirror_mode'] = self.mirror_mode.isChecked() + if self._settings['mirror_mode'] != self.mirror_mode.isChecked(): + self._settings['mirror_mode'] = self.mirror_mode.isChecked() restart = True - if settings['compact_mode'] != self.compact_mode.isChecked(): - settings['compact_mode'] = self.compact_mode.isChecked() + if self._settings['compact_mode'] != self.compact_mode.isChecked(): + self._settings['compact_mode'] = self.compact_mode.isChecked() restart = True - if settings['show_avatars'] != self.show_avatars.isChecked(): - settings['show_avatars'] = self.show_avatars.isChecked() + if self._settings['show_avatars'] != self.show_avatars.isChecked(): + self._settings['show_avatars'] = self.show_avatars.isChecked() restart = True - settings['smiley_pack'] = self.smiley_pack.currentText() - settings['close_to_tray'] = self.close_to_tray.isChecked() + self._settings['smiley_pack'] = self.smiley_pack.currentText() + self._settings['close_to_tray'] = self.close_to_tray.isChecked() smileys.SmileyLoader.get_instance().load_pack() language = self.lang_choose.currentText() - if settings['language'] != language: - settings['language'] = language + if self._settings['language'] != language: + self._settings['language'] = language text = self.lang_choose.currentText() path = Settings.supported_languages()[text] app = QtWidgets.QApplication.instance() app.removeTranslator(app.translator) app.translator.load(curr_directory() + '/translations/' + path) app.installTranslator(app.translator) - settings['message_font_size'] = self.messages_font_size.currentIndex() + 10 + self._settings['message_font_size'] = self.messages_font_size.currentIndex() + 10 Profile.get_instance().update() - settings.save() + self._settings.save() if restart: util_ui.message_box(util_ui.tr('Restart app to apply settings'), util_ui.tr('Restart required')) diff --git a/toxygen/ui/widgets.py b/toxygen/ui/widgets.py index 735ad39..7585f06 100644 --- a/toxygen/ui/widgets.py +++ b/toxygen/ui/widgets.py @@ -1,4 +1,5 @@ from PyQt5 import QtCore, QtGui, QtWidgets +import util.ui as util_ui class DataLabel(QtWidgets.QLabel): @@ -22,7 +23,7 @@ class ComboBox(QtWidgets.QComboBox): class CenteredWidget(QtWidgets.QWidget): def __init__(self): - super(CenteredWidget, self).__init__() + super().__init__() self.center() def center(self): @@ -137,21 +138,21 @@ def create_menu(menu): text = action.text() if 'Link Location' in text: text = text.replace('Copy &Link Location', - QtWidgets.QApplication.translate("MainWindow", "Copy link location")) + util_ui.tr("Copy link location")) elif '&Copy' in text: - text = text.replace('&Copy', QtWidgets.QApplication.translate("MainWindow", "Copy")) + text = text.replace('&Copy', util_ui.tr("Copy")) elif 'All' in text: - text = text.replace('Select All', QtWidgets.QApplication.translate("MainWindow", "Select all")) + text = text.replace('Select All', util_ui.tr("Select all")) elif 'Delete' in text: - text = text.replace('Delete', QtWidgets.QApplication.translate("MainWindow", "Delete")) + text = text.replace('Delete', util_ui.tr("Delete")) elif '&Paste' in text: - text = text.replace('&Paste', QtWidgets.QApplication.translate("MainWindow", "Paste")) + text = text.replace('&Paste', util_ui.tr("Paste")) elif 'Cu&t' in text: - text = text.replace('Cu&t', QtWidgets.QApplication.translate("MainWindow", "Cut")) + text = text.replace('Cu&t', util_ui.tr("Cut")) elif '&Undo' in text: - text = text.replace('&Undo', QtWidgets.QApplication.translate("MainWindow", "Undo")) + text = text.replace('&Undo', util_ui.tr("Undo")) elif '&Redo' in text: - text = text.replace('&Redo', QtWidgets.QApplication.translate("MainWindow", "Redo")) + text = text.replace('&Redo', util_ui.tr("Redo")) else: menu.removeAction(action) continue @@ -172,7 +173,7 @@ class MultilineEdit(CenteredWidget): self.edit.setText(text) self.button = QtWidgets.QPushButton(self) self.button.setGeometry(QtCore.QRect(0, 150, 350, 50)) - self.button.setText(QtWidgets.QApplication.translate("MainWindow", "Save")) + self.button.setText(util_ui.tr("Save")) self.button.clicked.connect(self.button_click) self.center() self.save = save diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py index 46c7c5c..a9be90a 100644 --- a/toxygen/ui/widgets_factory.py +++ b/toxygen/ui/widgets_factory.py @@ -4,13 +4,15 @@ from ui.menu import * class WidgetsFactory: - def __init__(self, settings, profile, contacts_manager, file_transfer_handler, smiley_loader, plugin_loader): + def __init__(self, settings, profile, contacts_manager, file_transfer_handler, smiley_loader, plugin_loader, + toxes): self._settings = settings self._profile = profile self._contacts_manager = contacts_manager self._file_transfer_handler = file_transfer_handler self._smiley_loader = smiley_loader self._plugin_loader = plugin_loader + self._toxes = toxes def create_screenshot_window(self, *args): return ScreenShotWindow(self._file_transfer_handler, *args) @@ -22,7 +24,7 @@ class WidgetsFactory: return WelcomeScreen(self._settings) def create_profile_settings_window(self): - return ProfileSettings(self._profile) + return ProfileSettings(self._profile, self._settings, self._toxes) def create_network_settings_window(self): return NetworkSettings(self._settings, self._profile.reset) @@ -40,7 +42,7 @@ class WidgetsFactory: return PluginsSettings(self._plugin_loader) def create_add_contact_window(self, tox_id): - return AddContact(self._contacts_manager, tox_id) + return AddContact(self._settings, self._contacts_manager, tox_id) def create_welcome_window(self): return WelcomeScreen(self._settings) diff --git a/toxygen/user_data/settings.py b/toxygen/user_data/settings.py index e387722..09fe373 100644 --- a/toxygen/user_data/settings.py +++ b/toxygen/user_data/settings.py @@ -1,6 +1,6 @@ import json import os -from util.util import log, get_base_directory, append_slash, get_platform +from util.util import log, get_base_directory, get_platform, join_path import pyaudio import smileys.smileys as smileys @@ -56,7 +56,7 @@ class Settings(dict): if 'path' in auto and 'name' in auto: path = str(auto['path']) name = str(auto['name']) - if os.path.isfile(append_slash(path) + name + '.tox'): + if os.path.isfile(join_path(path, name + '.tox')): return path, name return None From ddf6cd832826fd8d559ae016e211e58d636e6e91 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 30 Apr 2018 22:28:33 +0300 Subject: [PATCH 014/138] contact list loading --- toxygen/app.py | 401 +++++++++++++++------------ toxygen/contacts/contact.py | 2 +- toxygen/contacts/contact_provider.py | 6 +- toxygen/contacts/contacts_manager.py | 147 +++++----- toxygen/contacts/friend_factory.py | 3 +- toxygen/main.py | 6 +- toxygen/ui/items_factory.py | 8 +- toxygen/ui/list_items.py | 19 +- toxygen/util/util.py | 5 + 9 files changed, 310 insertions(+), 287 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index c8f7f14..432be07 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -25,6 +25,7 @@ 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_factory import ItemsFactory class App: @@ -34,83 +35,16 @@ class App: self._app = self._settings = self._profile_manager = self._plugin_loader = 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 = None + self._friend_factory = self._calls_manager = self._contacts_manager = self._smiley_loader = None if uri is not None and uri.startswith('tox:'): self._uri = uri[4:] self._path = path_to_profile - def enter_pass(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 - raise SystemExit() + # ----------------------------------------------------------------------------------------------------------------- + # App executing + # ----------------------------------------------------------------------------------------------------------------- - def main(self): - """ - Main function of app. loads login screen if needed and starts main screen - """ - self._app = QtWidgets.QApplication([]) - icon_file = os.path.join(get_images_directory(), 'icon.png') - self._app.setWindowIcon(QtGui.QIcon(icon_file)) - - if get_platform() == 'Linux': - QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) - - with open(os.path.join(get_styles_directory(), 'dark_style.qss')) as fl: - style = fl.read() - self._app.setStyleSheet(style) - - 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 - if result.is_new_profile(): # create new profile - self.create_new_profile(result.profile_path) - else: # load existing profile - self.load_existing_profile(result.profile_path) - self._path = result.profile_path - else: # default profile - path, name = auto_profile - self._path = os.path.join(path, name + '.tox') - self.load_existing_profile(self._path) - - if Settings.is_active_profile(self._path): # profile is in use - profile_name = 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 - - self._settings.set_active_profile() - - self.load_app_styles() - self.load_app_translations() - - if self.try_to_update(): - return - - 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) - # main + def _execute_app(self): while True: try: self._app.exec_() @@ -122,41 +56,177 @@ class App: else: break - self.stop_app() - - def stop_app(self): + def _stop_app(self): self._plugin_loader.stop() - self.stop_threads() + self._stop_threads() self._tray.hide() - self.save_profile() + self._save_profile() self._settings.close() del self._tox - def reset(self): + # ----------------------------------------------------------------------------------------------------------------- + # App loading + # ----------------------------------------------------------------------------------------------------------------- + + def _load_base_style(self): + with open(join_path(get_styles_directory(), 'dark_style.qss')) as fl: + style = fl.read() + self._app.setStyleSheet(style) + + def _load_app_styles(self): + # application color scheme + for theme in self._settings.built_in_themes().keys(): + if self._settings['theme'] == theme: + with open(curr_directory(__file__) + self._settings.built_in_themes()[theme]) as fl: + style = fl.read() + self._app.setStyleSheet(style) + + def _load_login_screen_translations(self): + current_language, supported_languages = self._get_languages() + if current_language in supported_languages: + lang_path = supported_languages[current_language] + translator = QtCore.QTranslator() + translator.load(get_translations_directory() + lang_path) + self._app.installTranslator(translator) + self._app.translator = translator + + def _load_icon(self): + icon_file = os.path.join(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(get_translations_directory(), lang)) + self._app.installTranslator(translator) + self._app.translator = translator + + # ----------------------------------------------------------------------------------------------------------------- + # Threads + # ----------------------------------------------------------------------------------------------------------------- + + def _start_threads(self): + # init thread + self._init = threads.InitThread(self._tox, self._plugin_loader, self._settings) + 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() + + threads.start_file_transfer_thread() + + def _stop_threads(self): + self._init.stop_thread() + + self._av_loop.stop_thread() + self._main_loop.stop_thread() + + 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._settings = Settings(self._toxes, profile_path.replace('.tox', '.json')) + self._profile_manager = ProfileManager(self._settings, self._toxes, profile_path) + data = self._profile_manager.open_profile() + if self._toxes.is_data_encrypted(data): + data = self._enter_pass(data) + self._tox = self._create_tox(data) + + def _create_new_profile(self, profile_path): + name = get_profile_name_from_path(profile_path) or 'toxygen_user' + if os.path.isfile(profile_path): + util_ui.message_box(util_ui.tr('Profile with this name already exists'), + util_ui.tr('Error')) + return + self._tox = tox_factory() + self._tox.self_set_name(bytes(name, 'utf-8') if name else b'Toxygen User') + self._tox.self_set_status_message(b'Toxing on Toxygen') + # TODO: set profile password + self._settings = Settings(self._toxes, self._path.replace('.tox', '.json')) + self._profile_manager = ProfileManager(self._settings, self._toxes, profile_path) + try: + self._save_profile() + except Exception as ex: + print(ex) + 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 + current_language, supported_languages = self._get_languages() + if current_language in supported_languages: + self._settings['language'] = current_language + self._settings.save() + + def _save_profile(self, data=None): + data = data or self._tox.get_savedata() + self._profile_manager.save_profile(data) + + # ----------------------------------------------------------------------------------------------------------------- + # Other private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _enter_pass(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 + raise SystemExit() + + def _reset(self): """ Create new tox instance (new network settings) :return: tox instance """ - self.stop_threads() + self._stop_threads() data = self._tox.get_savedata() - self.save_profile(data) + self._save_profile(data) del self._tox # create new tox instance - self._tox = self.create_tox(data) - self.start_threads() + self._tox = self._create_tox(data) + self._start_threads() # TODO: foreach in list of tox savers set_tox return self._tox - def create_dependencies(self): + def _create_dependencies(self): + self._smiley_loader = SmileyLoader(self._settings) self._ms = MainWindow(self._settings, self._tox, self._tray) db = Database(self._path.replace('.tox', '.db'), self._toxes) - self._friend_factory = FriendFactory(self._profile_manager, self._settings, self._tox, db) - self._contacts_provider = ContactProvider(self._tox, self._friend_factory) profile = Profile(self._profile_manager, self._tox, self._ms, self._file_transfer_handler) - self._smiley_loader = SmileyLoader(self._settings) self._plugin_loader = PluginLoader(self._tox, self._toxes, profile, self._settings) # plugins support + items_factory = ItemsFactory(self._settings, self._plugin_loader, self._smiley_loader, self._ms) + self._friend_factory = FriendFactory(self._profile_manager, self._settings, self._tox, db, items_factory) + self._contacts_provider = ContactProvider(self._tox, self._friend_factory) widgets_factory = WidgetsFactory(self._settings, profile, self._contacts_manager, self._file_transfer_handler, self._smiley_loader, self._plugin_loader, self._toxes) self._contacts_manager = ContactsManager(self._tox, self._settings, self._ms, self._profile_manager, @@ -175,113 +245,78 @@ class App: callbacks.init_callbacks(self._tox, profile, self._settings, self._plugin_loader, self._contacts_manager, self._calls_manager, self._file_transfer_handler, self._ms, self._tray) - def load_app_styles(self): - # application color scheme - for theme in self._settings.built_in_themes().keys(): - if self._settings['theme'] == theme: - with open(curr_directory(__file__) + self._settings.built_in_themes()[theme]) as fl: - style = fl.read() - self._app.setStyleSheet(style) - - def load_login_screen_translations(self): - current_language, supported_languages = self.get_languages() - if current_language in supported_languages: - lang_path = supported_languages[current_language] - translator = QtCore.QTranslator() - translator.load(get_translations_directory() + lang_path) - self._app.installTranslator(translator) - self._app.translator = translator - - @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(get_translations_directory(), lang)) - self._app.installTranslator(translator) - self._app.translator = translator - - def try_to_update(self): + def _try_to_update(self): updating = updater.start_update_if_needed(self._version, self._settings) if updating: - self.save_profile() + self._save_profile() self._settings.close() del self._tox return updating - def start_threads(self): - # init thread - self._init = threads.InitThread(self._tox, self._plugin_loader, self._settings) - 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() - - threads.start_file_transfer_thread() - - def stop_threads(self): - self._init.stop_thread() - - self._av_loop.stop_thread() - self._main_loop.stop_thread() - - threads.stop_file_transfer_thread() - - def create_tox(self, data): + def _create_tox(self, data): return tox_factory(data, self._settings) - def select_profile(self): - self.load_login_screen_translations() - ls = LoginScreen() - profiles = ProfileManager.find_profiles() - ls.update_select(profiles) - ls.show() - self._app.exec_() + # ----------------------------------------------------------------------------------------------------------------- + # Public methods + # ----------------------------------------------------------------------------------------------------------------- - return ls.result + def main(self): + """ + Main function of app. loads login screen if needed and starts main screen + """ + self._app = QtWidgets.QApplication([]) + self._load_icon() - def load_existing_profile(self, profile_path): - self._settings = Settings(self._toxes, profile_path.replace('.tox', '.json')) - self._profile_manager = ProfileManager(self._settings, self._toxes, profile_path) - data = self._profile_manager.open_profile() - if self._toxes.is_data_encrypted(data): - data = self.enter_pass(data) - self._tox = self.create_tox(data) + if get_platform() == 'Linux': + QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) - def create_new_profile(self, profile_path): - name = get_profile_name_from_path(profile_path) or 'toxygen_user' - if os.path.isfile(profile_path): - util_ui.message_box(util_ui.tr('Profile with this name already exists'), - util_ui.tr('Error')) + self._load_base_style() + + 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 + if result.is_new_profile(): # create new profile + self._create_new_profile(result.profile_path) + else: # load existing profile + self._load_existing_profile(result.profile_path) + self._path = result.profile_path + else: # default profile + path, name = auto_profile + self._path = os.path.join(path, name + '.tox') + self._load_existing_profile(self._path) + + if Settings.is_active_profile(self._path): # profile is in use + profile_name = 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 + + self._settings.set_active_profile() + + self._load_app_styles() + self._load_app_translations() + + if self._try_to_update(): return - self._tox = tox_factory() - self._tox.self_set_name(bytes(name, 'utf-8') if name else b'Toxygen User') - self._tox.self_set_status_message(b'Toxing on Toxygen') - # TODO: set profile password - self._settings = Settings(self._toxes, self._path.replace('.tox', '.json')) - self._profile_manager = ProfileManager(self._settings, self._toxes, profile_path) - try: - self.save_profile() - except Exception as ex: - print(ex) - 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 - current_language, supported_languages = self.get_languages() - if current_language in supported_languages: - self._settings['language'] = current_language - self._settings.save() - def save_profile(self, data=None): - data = data or self._tox.get_savedata() - self._profile_manager.save_profile(data) + 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() diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index ecaeec2..84844b6 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -12,7 +12,7 @@ class Contact(basecontact.BaseContact): Properties: number, message getter, history etc. Base class for friend and gc classes """ - def __init__(self, message_getter, number, profile_manager, 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 number: number of friend. diff --git a/toxygen/contacts/contact_provider.py b/toxygen/contacts/contact_provider.py index 52da937..aca10ad 100644 --- a/toxygen/contacts/contact_provider.py +++ b/toxygen/contacts/contact_provider.py @@ -46,13 +46,13 @@ class ContactProvider(util.ToxSave): # GC # ----------------------------------------------------------------------------------------------------------------- - def get_all_gc(self): + def get_all_groups(self): return [] - def get_gc_by_number(self): + def get_group_by_number(self): pass - def get_gc_by_public_key(self): + def get_group_by_public_key(self): pass # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 510dbb3..6c6f817 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -2,7 +2,7 @@ import util.util as util import util.ui as util_ui from contacts.friend import Friend import os -from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5 import QtCore, QtGui from messenger.messages import * from wrapper.toxcore_enums_and_consts import * from network.tox_dns import tox_dns @@ -28,6 +28,10 @@ class ContactsManager: def __del__(self): del self._history + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- def _is_active_a_friend(self): return type(self.get_curr_contact()) is Friend @@ -35,30 +39,15 @@ class ContactsManager: def _load_contacts(self): self._load_friends() self._load_groups() - # if len(self._contacts): - # self.set_active(0) + if len(self._contacts): + self.set_active(0) self.filtration_and_sorting(self._sorting) def _load_friends(self): - aliases = self._settings['friends_aliases'] - friend_numbers = self._tox.self_get_friend_list() - for friend_number in friend_numbers: # creates list of friends - 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) - self._history.get_message_getter(tox_id) - message_getter = self._history.get_message_getter(tox_id) - friend = Friend(self._profile_manager, message_getter, friend_number, name, status_message, item, tox_id) - friend.set_alias(alias) - self._contacts.append(friend) + self._contacts.extend(self._contact_provider.get_all_friends()) def _load_groups(self): - pass + self._contacts.extend(self._contact_provider.get_all_groups()) def get_friend(self, num): if num < 0 or num >= len(self._contacts): @@ -101,7 +90,7 @@ class ContactsManager: if value is not None: if self._active_contact + 1 and self._active_contact != value: try: - self.get_curr_friend().curr_text = self._screen.messageEdit.toPlainText() + self.get_curr_contact().curr_text = self._screen.messageEdit.toPlainText() except: pass friend = self._contacts[value] @@ -113,49 +102,49 @@ class ContactsManager: if not self._settings['save_history']: friend.delete_old_messages() self._messages.clear() - friend.load_corr() - messages = friend.get_corr()[-PAGE_SIZE:] - self._load_history = False - for message in messages: - if message.get_type() <= 1: - data = message.get_data() - self.create_message_item(data[0], - data[2], - data[1], - data[3]) - elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: - if message.get_status() is None: - self.create_unsent_file_item(message) - continue - item = self.create_file_transfer_item(message) - if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer - try: - ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())] - ft.set_state_changed_handler(item.update_transfer_state) - ft.signal() - except: - print('Incoming not started transfer - no info found') - elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline - self.create_inline_item(message.get_data()) - elif message.get_type() < 5: # info message - data = message.get_data() - self.create_message_item(data[0], - data[2], - '', - data[3]) - else: - data = message.get_data() - self.create_gc_message_item(data[0], data[2], data[1], data[4], data[3]) - self._messages.scrollToBottom() - self._load_history = True - if value in self._call: - self._screen.active_call() - elif value in self._incoming_calls: - self._screen.incoming_call() - else: - self._screen.call_finished() + # friend.load_corr() + # messages = friend.get_corr()[-PAGE_SIZE:] + # self._load_history = False + # for message in messages: + # if message.get_type() <= 1: + # data = message.get_data() + # self.create_message_item(data[0], + # data[2], + # data[1], + # data[3]) + # elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: + # if message.get_status() is None: + # self.create_unsent_file_item(message) + # continue + # item = self.create_file_transfer_item(message) + # if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer + # try: + # ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())] + # ft.set_state_changed_handler(item.update_transfer_state) + # ft.signal() + # except: + # print('Incoming not started transfer - no info found') + # elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline + # self.create_inline_item(message.get_data()) + # elif message.get_type() < 5: # info message + # data = message.get_data() + # self.create_message_item(data[0], + # data[2], + # '', + # data[3]) + # else: + # data = message.get_data() + # self.create_gc_message_item(data[0], data[2], data[1], data[4], data[3]) + # self._messages.scrollToBottom() + # self._load_history = True + # if value in self._call: + # self._screen.active_call() + # elif value in self._incoming_calls: + # self._screen.incoming_call() + # else: + # self._screen.call_finished() else: - friend = self.get_curr_friend() + friend = self.get_curr_contact() self._screen.account_name.setText(friend.name) self._screen.account_status.setText(friend.status_message) @@ -239,12 +228,12 @@ class ContactsManager: """ self.filtration_and_sorting(self._sorting, self._filter_string) - def create_friend_item(self): + def _create_friend_item(self): """ Method-factory :return: new widget for friend instance """ - return None #self._factory.friend_item() + return self._factory.friend_item() # ----------------------------------------------------------------------------------------------------------------- # Friend getters @@ -255,18 +244,18 @@ class ContactsManager: def get_last_message(self): if self._active_contact + 1: - return self.get_curr_friend().get_last_message_text() + return self.get_current_contact().get_last_message_text() else: return '' def get_active_number(self): - return self.get_curr_friend().number if self._active_contact + 1 else -1 + return self.get_curr_contact().number if self._active_contact + 1 else -1 def get_active_name(self): - return self.get_curr_friend().name if self._active_contact + 1 else '' + return self.get_current_contact().name if self._active_contact + 1 else '' def is_active_online(self): - return self._active_contact + 1 and self.get_curr_friend().status is not None + return self._active_contact + 1 and self.get_current_contact().status is not None def new_name(self, number, name): friend = self.get_friend_by_number(number) @@ -274,7 +263,7 @@ class ContactsManager: friend.set_name(name) name = str(name, 'utf-8') if friend.name == name and tmp != name: - message = QtWidgets.QApplication.translate("MainWindow", 'User {} is now known as {}') + message = util_ui.tr('User {} is now known as {}') message = message.format(tmp, name) friend.append_message(InfoMessage(message, time.time())) friend.actions = True @@ -353,16 +342,8 @@ class ContactsManager: """ Adds friend to list """ - num = self._tox.friend_add_norequest(tox_id) # num - friend number - item = self.create_friend_item() - try: - if not self._history.friend_exists_in_db(tox_id): - self._history.add_friend_to_db(tox_id) - message_getter = self._history.messages_getter(tox_id) - except Exception as ex: # something is wrong - util.log('Accept friend request failed! ' + str(ex)) - message_getter = None - friend = Friend(message_getter, num, tox_id, '', item, tox_id) + self._tox.friend_add_norequest(tox_id) + friend = self._contact_provider.get_friend_by_public_key(tox_id) self._contacts.append(friend) def block_user(self, tox_id): @@ -459,9 +440,9 @@ class ContactsManager: """ if self._settings['typing_notifications'] and self._active_contact + 1: try: - friend = self.get_curr_friend() - if friend.status is not None: - self._tox.self_set_typing(friend.number, typing) + contact = self.get_curr_contact() + if contact.status is not None and self._is_active_a_friend(): # TODO: fix - no type check + self._tox.self_set_typing(contact.number, typing) except: pass diff --git a/toxygen/contacts/friend_factory.py b/toxygen/contacts/friend_factory.py index 23966f8..76273de 100644 --- a/toxygen/contacts/friend_factory.py +++ b/toxygen/contacts/friend_factory.py @@ -3,10 +3,11 @@ from contacts.friend import Friend class FriendFactory: - def __init__(self, profile_manager, settings, tox, db): + def __init__(self, profile_manager, settings, tox, db, items_factory): self._profile_manager = profile_manager self._settings, self._tox = settings, tox self._db = db + self._factory = items_factory def create_friend_by_number(self, friend_number): aliases = self._settings['friends_aliases'] diff --git a/toxygen/main.py b/toxygen/main.py index cdaa7e5..1171aac 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -1,6 +1,6 @@ import app from user_data.settings import * -from util.util import curr_directory, remove +from util.util import remove, get_libs_directory import argparse @@ -10,8 +10,8 @@ __version__ = '0.5.0' def clean(): """Removes all windows libs from libs folder""" - d = os.path.join(curr_directory(__file__), 'libs') - remove(d) + directory = get_libs_directory() + remove(directory) def reset(): diff --git a/toxygen/ui/items_factory.py b/toxygen/ui/items_factory.py index 386e762..b4a0b24 100644 --- a/toxygen/ui/items_factory.py +++ b/toxygen/ui/items_factory.py @@ -8,11 +8,11 @@ class ItemsFactory: self._smiley_loader, self._main_screen = smiley_loader, main_screen def friend_item(self): - item = ContactItem() - elem = QtWidgets.QListWidgetItem(self._friends) + item = ContactItem(self._settings) + elem = QtWidgets.QListWidgetItem(self._main_screen.friends_list) elem.setSizeHint(QtCore.QSize(250, item.height())) - self._friends.addItem(elem) - self._friends.setItemWidget(elem, item) + self._main_screen.friends_list.addItem(elem) + self._main_screen.friends_list.setItemWidget(elem, item) return item def message_item(self, text, time, name, sent, message_type, append, pixmap): diff --git a/toxygen/ui/list_items.py b/toxygen/ui/list_items.py index 1891d45..a947919 100644 --- a/toxygen/ui/list_items.py +++ b/toxygen/ui/list_items.py @@ -15,9 +15,9 @@ class ContactItem(QtWidgets.QWidget): Contact in friends list """ - def __init__(self, parent=None): + def __init__(self, settings, parent=None): QtWidgets.QWidget.__init__(self, parent) - mode = settings.Settings.get_instance()['compact_mode'] + 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 @@ -27,7 +27,7 @@ class ContactItem(QtWidgets.QWidget): 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.setFamily(settings['font']) font.setPointSize(10 if mode else 12) font.setBold(True) self.name.setFont(font) @@ -38,7 +38,7 @@ class ContactItem(QtWidgets.QWidget): 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 = UnreadMessagesCount(settings, self) self.messages.setGeometry(QtCore.QRect(20 if mode else 52, 20 if mode else 50, 30, 20)) @@ -77,23 +77,24 @@ class StatusCircle(QtWidgets.QWidget): class UnreadMessagesCount(QtWidgets.QWidget): - def __init__(self, parent=None): - super(UnreadMessagesCount, self).__init__(parent) + 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.Settings.get_instance()['font']) + 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.Settings.get_instance()['unread_color'] + color = settings['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'] + color = self._settings['unread_color'] self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }') if messages_count: self.label.setVisible(True) diff --git a/toxygen/util/util.py b/toxygen/util/util.py index f7ac9ff..afa6a91 100644 --- a/toxygen/util/util.py +++ b/toxygen/util/util.py @@ -70,6 +70,11 @@ def get_plugins_directory(): return get_app_directory('plugins') +@cached +def get_libs_directory(): + return get_app_directory('libs') + + def get_app_directory(directory_name): return os.path.join(get_base_directory(), directory_name) From 6ebafbda447625cdc1fd82a0c3bddb3a085f7122 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 1 May 2018 16:39:09 +0300 Subject: [PATCH 015/138] messenger created. callbacks fixes. contacts refactoring --- toxygen/app.py | 150 +++++++++--------- toxygen/contacts/common.py | 24 +++ toxygen/contacts/contact.py | 11 +- toxygen/contacts/contact_provider.py | 20 +-- toxygen/contacts/contacts_manager.py | 67 ++++---- toxygen/contacts/friend.py | 10 +- toxygen/contacts/friend_factory.py | 20 ++- toxygen/contacts/profile.py | 76 +-------- .../file_transfers/file_transfers_handler.py | 10 +- toxygen/messenger/messenger.py | 128 +++++++++++++++ toxygen/middleware/callbacks.py | 47 +++--- toxygen/ui/items_factory.py | 11 +- toxygen/ui/main_screen.py | 77 ++++----- toxygen/ui/main_screen_widgets.py | 33 ++-- toxygen/ui/messages_widgets.py | 2 +- toxygen/ui/widgets_factory.py | 5 + toxygen/util/util.py | 4 + 17 files changed, 406 insertions(+), 289 deletions(-) create mode 100644 toxygen/contacts/common.py create mode 100644 toxygen/messenger/messenger.py diff --git a/toxygen/app.py b/toxygen/app.py index 432be07..82d1618 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -26,13 +26,14 @@ from history.database import Database from ui.widgets_factory import WidgetsFactory from smileys.smileys import SmileyLoader from ui.items_factory import ItemsFactory +from messenger.messenger import Messenger 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 = None + 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 @@ -40,6 +41,71 @@ class App: 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 get_platform() == 'Linux': + QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) + + self._load_base_style() + + 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 + if result.is_new_profile(): # create new profile + self._create_new_profile(result.profile_path) + else: # load existing profile + self._load_existing_profile(result.profile_path) + self._path = result.profile_path + else: # default profile + path, name = auto_profile + self._path = os.path.join(path, name + '.tox') + self._load_existing_profile(self._path) + + if Settings.is_active_profile(self._path): # profile is in use + profile_name = 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 + + self._settings.set_active_profile() + + self._load_app_styles() + self._load_app_translations() + + if self._try_to_update(): + return + + 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 # ----------------------------------------------------------------------------------------------------------------- @@ -220,7 +286,7 @@ class App: def _create_dependencies(self): self._smiley_loader = SmileyLoader(self._settings) - self._ms = MainWindow(self._settings, self._tox, self._tray) + self._ms = MainWindow(self._settings, self._tray) db = Database(self._path.replace('.tox', '.db'), self._toxes) profile = Profile(self._profile_manager, self._tox, self._ms, self._file_transfer_handler) self._plugin_loader = PluginLoader(self._tox, self._toxes, profile, self._settings) # plugins support @@ -231,19 +297,20 @@ class App: self._smiley_loader, self._plugin_loader, self._toxes) self._contacts_manager = ContactsManager(self._tox, self._settings, self._ms, self._profile_manager, self._contacts_provider, db) + self._messenger = Messenger(self._tox, self._plugin_loader, self._ms, self._contacts_manager, + self._contacts_provider) + self._tray = tray.init_tray(profile, self._settings, self._ms) + self._ms.set_dependencies(widgets_factory, self._tray, self._contacts_manager, self._messenger) self._calls_manager = CallsManager(self._tox.AV, self._settings) self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider) - self._ms.profile = profile - self._ms.set_widget_factory(widgets_factory) - self._ms.show() - - self._tray = tray.init_tray(profile, self._settings, self._ms) - self._ms.set_tray(self._tray) self._tray.show() + self._ms.show() + # callbacks initialization callbacks.init_callbacks(self._tox, profile, self._settings, self._plugin_loader, self._contacts_manager, - self._calls_manager, self._file_transfer_handler, self._ms, self._tray) + self._calls_manager, self._file_transfer_handler, self._ms, self._tray, + self._messenger) def _try_to_update(self): updating = updater.start_update_if_needed(self._version, self._settings) @@ -255,68 +322,3 @@ class App: def _create_tox(self, data): return tox_factory(data, self._settings) - - # ----------------------------------------------------------------------------------------------------------------- - # 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 get_platform() == 'Linux': - QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) - - self._load_base_style() - - 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 - if result.is_new_profile(): # create new profile - self._create_new_profile(result.profile_path) - else: # load existing profile - self._load_existing_profile(result.profile_path) - self._path = result.profile_path - else: # default profile - path, name = auto_profile - self._path = os.path.join(path, name + '.tox') - self._load_existing_profile(self._path) - - if Settings.is_active_profile(self._path): # profile is in use - profile_name = 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 - - self._settings.set_active_profile() - - self._load_app_styles() - self._load_app_translations() - - if self._try_to_update(): - return - - 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() diff --git a/toxygen/contacts/common.py b/toxygen/contacts/common.py new file mode 100644 index 0000000..584afc3 --- /dev/null +++ b/toxygen/contacts/common.py @@ -0,0 +1,24 @@ + + +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() diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index 84844b6..c9274ac 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -1,5 +1,5 @@ from history.database import * -from contacts import basecontact +from contacts import basecontact, common import util.util as util from messenger.messages import * from file_transfers import file_transfers as ft @@ -285,3 +285,12 @@ class Contact(basecontact.BaseContact): self._number = value 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) diff --git a/toxygen/contacts/contact_provider.py b/toxygen/contacts/contact_provider.py index aca10ad..82cf6e7 100644 --- a/toxygen/contacts/contact_provider.py +++ b/toxygen/contacts/contact_provider.py @@ -8,16 +8,6 @@ class ContactProvider(util.ToxSave): self._friend_factory = friend_factory self._cache = {} # key - contact's public key, value - contact instance - # ----------------------------------------------------------------------------------------------------------------- - # 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 - # ----------------------------------------------------------------------------------------------------------------- # Friends # ----------------------------------------------------------------------------------------------------------------- @@ -72,3 +62,13 @@ class ContactProvider(util.ToxSave): def remove_friend_from_cache(self, friend_public_key): if friend_public_key in self._cache: del self._cache[friend_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 diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 6c6f817..0f7b4bc 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -9,6 +9,9 @@ from network.tox_dns import tox_dns from history.history_loader import HistoryLoader +# TODO: move messaging and typing notifications to other class + + class ContactsManager: def __init__(self, tox, settings, screen, profile_manager, contact_provider, db): @@ -29,26 +32,6 @@ class ContactsManager: def __del__(self): del self._history - # ----------------------------------------------------------------------------------------------------------------- - # Private methods - # ----------------------------------------------------------------------------------------------------------------- - - def _is_active_a_friend(self): - return type(self.get_curr_contact()) is Friend - - def _load_contacts(self): - self._load_friends() - self._load_groups() - if len(self._contacts): - self.set_active(0) - self.filtration_and_sorting(self._sorting) - - 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()) - def get_friend(self, num): if num < 0 or num >= len(self._contacts): return None @@ -61,6 +44,12 @@ class ContactsManager: 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 + # ----------------------------------------------------------------------------------------------------------------- # Work with active friend # ----------------------------------------------------------------------------------------------------------------- @@ -85,7 +74,7 @@ class ContactsManager: self._screen.messageEdit.clear() return try: - self.send_typing(False) + # self.send_typing(False) # TODO: fix self._screen.typing.setVisible(False) if value is not None: if self._active_contact + 1 and self._active_contact != value: @@ -430,25 +419,25 @@ class ContactsManager: 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 self._active_contact != -1 + # ----------------------------------------------------------------------------------------------------------------- - # Typing notifications + # Private methods # ----------------------------------------------------------------------------------------------------------------- - def send_typing(self, typing): - """ - Send typing notification to a friend - """ - if self._settings['typing_notifications'] and self._active_contact + 1: - try: - contact = self.get_curr_contact() - if contact.status is not None and self._is_active_a_friend(): # TODO: fix - no type check - self._tox.self_set_typing(contact.number, typing) - except: - pass + def _is_active_a_friend(self): + return type(self.get_curr_contact()) is Friend - def friend_typing(self, friend_number, typing): - """ - Display incoming typing notification - """ - if friend_number == self.get_active_number() and self.is_active_a_friend(): - self._screen.typing.setVisible(typing) + def _load_contacts(self): + self._load_friends() + self._load_groups() + if len(self._contacts): + self.set_active(0) + self.filtration_and_sorting(self._sorting) + + 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()) diff --git a/toxygen/contacts/friend.py b/toxygen/contacts/friend.py index 4a22329..2e65e53 100644 --- a/toxygen/contacts/friend.py +++ b/toxygen/contacts/friend.py @@ -1,4 +1,4 @@ -from contacts import contact +from contacts import contact, common from messenger.messages import * import os @@ -11,6 +11,7 @@ class Friend(contact.Contact): 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 @@ -73,3 +74,10 @@ class Friend(contact.Contact): def get_full_status(self): return self._status_message + + # ----------------------------------------------------------------------------------------------------------------- + # Typing notifications + # ----------------------------------------------------------------------------------------------------------------- + + def get_typing_notification_handler(self): + return self._typing_notification_handler diff --git a/toxygen/contacts/friend_factory.py b/toxygen/contacts/friend_factory.py index 76273de..6803c61 100644 --- a/toxygen/contacts/friend_factory.py +++ b/toxygen/contacts/friend_factory.py @@ -7,7 +7,12 @@ class FriendFactory: self._profile_manager = profile_manager self._settings, self._tox = settings, tox self._db = db - self._factory = items_factory + 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'] @@ -16,7 +21,7 @@ class FriendFactory: alias = list(filter(lambda x: x[0] == tox_id, aliases))[0][1] except: alias = '' - item = self.create_friend_item() + 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) @@ -25,14 +30,13 @@ class FriendFactory: return friend - def create_friend_by_public_key(self, public_key): - friend_number = self._tox.friend_by_public_key(public_key) + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- - return self.create_friend_by_number(friend_number) - - def create_friend_item(self): + def _create_friend_item(self): """ Method-factory :return: new widget for friend instance """ - return self._factory.friend_item() + return self._items_factory.friend_item() diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 642cc04..4949175 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -32,9 +32,7 @@ class Profile(basecontact.BaseContact): self._file_transfers = {} # dict of file transfers. key - tuple (friend_number, file_number) self._load_history = True self._waiting_for_reconnection = False - #self._factory = items_factory.ItemsFactory(self._screen.friends_list, self._messages) self._contacts_manager = None - #self._show_avatars = settings['show_avatars'] # ----------------------------------------------------------------------------------------------------------------- # Edit current user's data @@ -48,7 +46,7 @@ class Profile(basecontact.BaseContact): self.set_status((self._status + 1) % 3) def set_status(self, status): - super(Profile, self).set_status(status) + super().set_status(status) if status is not None: self._tox.self_set_status(status) elif not self._waiting_for_reconnection: @@ -59,7 +57,7 @@ class Profile(basecontact.BaseContact): if self.name == value: return tmp = self.name - super(Profile, self).set_name(value.encode('utf-8')) + super().set_name(value.encode('utf-8')) self._tox.self_set_name(self._name.encode('utf-8')) message = util_ui.tr('User {} is now known as {}') message = message.format(tmp, value) @@ -149,76 +147,6 @@ class Profile(basecontact.BaseContact): except Exception as ex: log('Sending pending messages failed with ' + str(ex)) - def split_message(self, 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 ' ' in last_part: - index = last_part.index(' ') - elif ',' in last_part: - index = last_part.index(',') - elif '.' in last_part: - index = last_part.index('.') - else: - index = TOX_MAX_MESSAGE_LENGTH - size - 1 - index += size + 1 - messages.append(message[:index]) - message = message[index:] - - return messages - - def new_message(self, friend_num, message_type, message): - """ - Current user gets new message - :param friend_num: friend_num of friend who sent message - :param message_type: message type - plain text or action message (/me) - :param message: text of message - """ - if friend_num == self.get_active_number()and self.is_active_a_friend(): # add message to list - t = time.time() - self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type) - self._messages.scrollToBottom() - self.get_curr_friend().append_message( - TextMessage(message, MESSAGE_OWNER['FRIEND'], t, message_type)) - else: - friend = self.get_friend_by_number(friend_num) - friend.inc_messages() - friend.append_message( - TextMessage(message, MESSAGE_OWNER['FRIEND'], time.time(), message_type)) - if not friend.visibility: - self.update_filtration() - - def send_message_to_friend(self, text, friend_number=None): - """ - Send message - :param text: message text - :param friend_number: number of friend - """ - if friend_number is None: - friend_number = self.get_active_number() - if text.startswith('/plugin '): - self._plugin_loader.command(text[8:]) - self._screen.messageEdit.clear() - elif text and friend_number >= 0: - if text.startswith('/me '): - message_type = TOX_MESSAGE_TYPE['ACTION'] - text = text[4:] - else: - message_type = TOX_MESSAGE_TYPE['NORMAL'] - friend = self.get_friend_by_number(friend_number) - friend.inc_receipts() - if friend.status is not None: - messages = self.split_message(text.encode('utf-8')) - for message in messages: - self._tox.friend_send_message(friend_number, message_type, message) - t = time.time() - if friend.number == self.get_active_number() and self.is_active_a_friend(): - self.create_message_item(text, t, MESSAGE_OWNER['NOT_SENT'], message_type) - self._screen.messageEdit.clear() - self._messages.scrollToBottom() - friend.append_message(TextMessage(text, MESSAGE_OWNER['NOT_SENT'], t, message_type)) - def delete_message(self, message_id): friend = self.get_curr_friend() friend.delete_message(time) diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index 826adc0..e7a0308 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -20,9 +20,6 @@ class FileTransfersHandler: self._settings['paused_file_transfers'] = self._paused_file_transfers if self._settings['resend_files'] else {} self._settings.save() - def _get_friend_by_number(self, friend_number): - return self._contact_provider.get_friend_by_number(friend_number) - # ----------------------------------------------------------------------------------------------------------------- # File transfers support # ----------------------------------------------------------------------------------------------------------------- @@ -319,3 +316,10 @@ class FileTransfersHandler: self.get_friend_by_number(friend_number).load_avatar() if self.get_active_number() == friend_number and self.is_active_a_friend(): self.set_active(None) + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _get_friend_by_number(self, friend_number): + return self._contact_provider.get_friend_by_number(friend_number) diff --git a/toxygen/messenger/messenger.py b/toxygen/messenger/messenger.py new file mode 100644 index 0000000..77cb431 --- /dev/null +++ b/toxygen/messenger/messenger.py @@ -0,0 +1,128 @@ +import util.util as util +from wrapper.toxcore_enums_and_consts import * +from messenger.messages import * + + +class Messenger(util.ToxSave): + + def __init__(self, tox, plugin_loader, screen, contacts_manager, contacts_provider): + super().__init__(tox) + self._plugin_loader = plugin_loader + self._screen = screen + self._contacts_manager = contacts_manager + self._contacts_provider = contacts_provider + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _create_message_item(self): + pass + + # ----------------------------------------------------------------------------------------------------------------- + # Messaging + # ----------------------------------------------------------------------------------------------------------------- + + 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() + + if self._contacts_manager.is_friend_active(friend_number): # add message to list + self.create_message_item(message, t, MESSAGE_AUTHOR['FRIEND'], message_type) + self._screen.messages.scrollToBottom() + self._contacts_manager.get_curr_contact().append_message( + TextMessage(message, MESSAGE_AUTHOR['FRIEND'], t, message_type)) + else: + friend = self.get_friend_by_number(friend_number) + friend.inc_messages() + friend.append_message( + TextMessage(message, MESSAGE_AUTHOR['FRIEND'], t, message_type)) + if not friend.visibility: + self._contacts_manager.update_filtration() + + def send_message(self): + self.send_message_to_friend(self._screen.messageEdit.toPlainText()) + + def send_message_to_friend(self, text, 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 text.startswith('/plugin '): + self._plugin_loader.command(text[8:]) + self._screen.messageEdit.clear() + elif text and friend_number >= 0: + if text.startswith('/me '): + message_type = TOX_MESSAGE_TYPE['ACTION'] + text = text[4:] + else: + message_type = TOX_MESSAGE_TYPE['NORMAL'] + friend = self.get_friend_by_number(friend_number) + friend.inc_receipts() + if friend.status is not None: + messages = self._split_message(text.encode('utf-8')) + t = util.get_unix_time() + for message in messages: + message_id = self._tox.friend_send_message(friend_number, message_type, message) + friend.append_message(TextMessage(message_id, text, MESSAGE_AUTHOR['NOT_SENT'], t, message_type)) + if self._contacts_manager.is_friend_active(friend_number): + self.create_message_item(text, t, MESSAGE_AUTHOR['NOT_SENT'], message_type) + self._screen.messageEdit.clear() + self._screen.messages.scrollToBottom() + + # ----------------------------------------------------------------------------------------------------------------- + # Typing notifications + # ----------------------------------------------------------------------------------------------------------------- + + def send_typing(self, typing): + """ + Send typing notification to a friend + """ + if self._contacts_manager.can_send_typing_notification(): + try: + contact = self._contacts_manager.get_curr_contact() + contact.typing_notification_handler.send(self._tox, typing) + except: + pass + + 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) + + # ----------------------------------------------------------------------------------------------------------------- + # 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 ' ' in last_part: + index = last_part.index(' ') + elif ',' in last_part: + index = last_part.index(',') + elif '.' in last_part: + index = last_part.index('.') + else: + index = TOX_MAX_MESSAGE_LENGTH - size - 1 + index += size + 1 + messages.append(message[:index]) + message = message[index:] + + return messages + + def get_friend_by_number(self, friend_number): + return self._contacts_provider.get_friend_by_number(friend_number) diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index e139883..69a7f68 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -39,18 +39,18 @@ def self_connection_status(tox, profile): # ----------------------------------------------------------------------------------------------------------------- -def friend_status(profile, settings): +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 = profile.get_friend_by_number(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) - invoke_in_main_thread(QtCore.QTimer.singleShot, 5000, lambda: profile.send_files(friend_number)) - invoke_in_main_thread(profile.update_filtration) + invoke_in_main_thread(QtCore.QTimer.singleShot, 5000, lambda: file_transfer_handler.send_files(friend_number)) + invoke_in_main_thread(contacts_manager.update_filtration) return wrapped @@ -74,42 +74,42 @@ def friend_connection_status(profile, settings, plugin_loader): return wrapped -def friend_name(profile): +def friend_name(contacts_manager): def wrapped(tox, friend_number, name, size, user_data): """ Friend changed his name """ print('New name friend #' + str(friend_number)) - invoke_in_main_thread(profile.new_name, friend_number, name) + invoke_in_main_thread(contacts_manager.new_name, friend_number, name) return wrapped -def friend_status_message(profile): +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 = profile.get_friend_by_number(friend_number) + friend = contacts_manager.get_friend_by_number(friend_number) invoke_in_main_thread(friend.set_status_message, status_message) print('User #{} has new status'.format(friend_number)) - invoke_in_main_thread(profile.send_messages, friend_number) - if profile.get_active_number() == friend_number: - invoke_in_main_thread(profile.set_active) + invoke_in_main_thread(messenger.send_messages, friend_number) + if contacts_manager.is_friend_active(friend_number): + invoke_in_main_thread(contacts_manager.set_active) return wrapped -def friend_message(profile, settings, window, tray): +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(profile.new_message, friend_number, message_type, message) + invoke_in_main_thread(messenger.new_message, friend_number, message_type, message) if not window.isActiveWindow(): - friend = profile.get_friend_by_number(friend_number) + 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']: @@ -371,7 +371,7 @@ def show_gc_notification(window, tray, message, group_number, peer_number): def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, - calls_manager, file_transfer_handler, main_window, tray): + calls_manager, file_transfer_handler, main_window, tray, messenger): """ Initialization of all callbacks. :param tox: Tox instance @@ -382,32 +382,37 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, :param calls_manager: CallsManager instance :param file_transfer_handler: FileTransferHandler instance :param plugin_loader: PluginLoader instance - :param main_window: main window screen + :param main_window: MainWindow instance :param tray: tray (for notifications) + :param messenger: Messenger instance """ + # self callbacks tox.callback_self_connection_status(self_connection_status(tox, profile), 0) - tox.callback_friend_status(friend_status(profile, settings), 0) - tox.callback_friend_message(friend_message(profile, settings, main_window, tray), 0) + # friend callbacks + tox.callback_friend_status(friend_status(contacts_manager, file_transfer_handler, profile, settings), 0) + tox.callback_friend_message(friend_message(messenger, contacts_manager, profile, settings, main_window, tray), 0) tox.callback_friend_connection_status(friend_connection_status(profile, settings, plugin_loader), 0) - tox.callback_friend_name(friend_name(profile), 0) - tox.callback_friend_status_message(friend_status_message(profile), 0) + tox.callback_friend_name(friend_name(contacts_manager), 0) + tox.callback_friend_status_message(friend_status_message(contacts_manager, messenger), 0) tox.callback_friend_request(friend_request(contacts_manager), 0) tox.callback_friend_typing(friend_typing(contacts_manager), 0) tox.callback_friend_read_receipt(friend_read_receipt(contacts_manager), 0) + # file transfer tox.callback_file_recv(tox_file_recv(main_window, tray, profile, file_transfer_handler, contacts_manager, settings), 0) tox.callback_file_recv_chunk(file_recv_chunk(file_transfer_handler), 0) tox.callback_file_chunk_request(file_chunk_request(file_transfer_handler), 0) tox.callback_file_recv_control(file_recv_control(file_transfer_handler), 0) + # 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), 0) tox.callback_friend_lossy_packet(lossy_packet(plugin_loader), 0) - diff --git a/toxygen/ui/items_factory.py b/toxygen/ui/items_factory.py index b4a0b24..347424d 100644 --- a/toxygen/ui/items_factory.py +++ b/toxygen/ui/items_factory.py @@ -1,18 +1,21 @@ from ui.list_items import * +from ui.messages_widgets import * class ItemsFactory: def __init__(self, settings, plugin_loader, smiley_loader, main_screen): self._settings, self._plugin_loader = settings, plugin_loader - self._smiley_loader, self._main_screen = smiley_loader, main_screen + self._smiley_loader = smiley_loader + self._messages = main_screen.messages + self._friends_list = main_screen.friends_list def friend_item(self): item = ContactItem(self._settings) - elem = QtWidgets.QListWidgetItem(self._main_screen.friends_list) + elem = QtWidgets.QListWidgetItem(self._friends_list) elem.setSizeHint(QtCore.QSize(250, item.height())) - self._main_screen.friends_list.addItem(elem) - self._main_screen.friends_list.setItemWidget(elem, item) + self._friends_list.addItem(elem) + self._friends_list.setItemWidget(elem, item) return item def message_item(self, text, time, name, sent, message_type, append, pixmap): diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 7f2a1c0..c059b1f 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -8,22 +8,23 @@ import util.ui as util_ui class MainWindow(QtWidgets.QMainWindow): - def __init__(self, settings, tox, tray): + 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.setAcceptDrops(True) - self.initUI(tox) self._saved = False self.profile = None + self.initUI() - def set_widget_factory(self, widget_factory): + def set_dependencies(self, widget_factory, tray, contacts_manager, messenger): self._widget_factory = widget_factory - - def set_tray(self, tray): self._tray = tray + self._contacts_manager = contacts_manager + self.messageEdit.set_messenger(messenger) def show(self): super().show() @@ -305,7 +306,7 @@ class MainWindow(QtWidgets.QMainWindow): self.messages.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) self.messages.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - def initUI(self, tox): + def initUI(self): self.setMinimumSize(920, 500) s = self._settings self.setGeometry(s['x'], s['y'], s['width'], s['height']) @@ -326,7 +327,7 @@ class MainWindow(QtWidgets.QMainWindow): self.setup_right_bottom(message_buttons) self.setup_left_center(main_list) self.setup_menu(menu) - if not self._settings['mirror_mode']: + if not s['mirror_mode']: grid.addWidget(search, 2, 0) grid.addWidget(name, 1, 0) grid.addWidget(messages, 2, 1, 2, 1) @@ -367,7 +368,6 @@ class MainWindow(QtWidgets.QMainWindow): if self._saved: return self._saved = True - self.profile.close() self._settings['x'] = self.geometry().x() self._settings['y'] = self.geometry().y() self._settings['width'] = self.width() @@ -441,7 +441,7 @@ class MainWindow(QtWidgets.QMainWindow): def create_gc(self): self.profile.create_group_chat() - def profile_settings(self, *args): + def profile_settings(self): self._modal_window = self._widget_factory.create_profile_settings_window() self._modal_window.show() @@ -473,7 +473,8 @@ class MainWindow(QtWidgets.QMainWindow): if self._plugin_loader is not None: self._plugin_loader.reload() - def import_plugin(self): + @staticmethod + def import_plugin(): directory = util_ui.directory_dialog(util_ui.tr('Choose folder with plugin')) if directory: src = directory + '/' @@ -502,20 +503,19 @@ class MainWindow(QtWidgets.QMainWindow): # ----------------------------------------------------------------------------------------------------------------- def send_message(self): - text = self.messageEdit.toPlainText() - self.profile.send_message(text) + self._messenger.send_message() def send_file(self): self.menu.hide() - if self.profile.active_friend + 1and self.profile.is_active_a_friend(): + if self._contacts_manager.active_friend + 1 and self._contacts_manager.is_active_a_friend(): caption = util_ui.tr('Choose file') name = util_ui.file_dialog(caption) if name[0]: - self.profile.send_file(name[0]) + self._contacts_manager.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(): + if self._contacts_manager.active_friend + 1 and self._contacts_manager.is_active_a_friend(): self.sw = ScreenShotWindow(self) self.sw.show() if hide: @@ -523,8 +523,8 @@ class MainWindow(QtWidgets.QMainWindow): def send_smiley(self): self.menu.hide() - if self.profile.active_friend + 1: - self.smiley = SmileyWindow(self) + if self._contacts_manager.active_friend + 1: + self.smiley = self._widget_factory.create_smiley_window(self) self.smiley.setGeometry(QtCore.QRect(self.x() if self._settings['mirror_mode'] else 270 + self.x(), self.y() + self.height() - 200, self.smiley.width(), @@ -533,8 +533,8 @@ class MainWindow(QtWidgets.QMainWindow): def send_sticker(self): self.menu.hide() - if self.profile.active_friend + 1 and self.profile.is_active_a_friend(): - self.sticker = StickerWindow(self) + if self._contacts_manager.active_friend + 1 and self._contacts_manager.is_active_a_friend(): + self.sticker = self._widget_factory.create_sticker_window(self) self.sticker.setGeometry(QtCore.QRect(self.x() if self._settings['mirror_mode'] else 270 + self.x(), self.y() + self.height() - 200, self.sticker.width(), @@ -551,7 +551,6 @@ class MainWindow(QtWidgets.QMainWindow): 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) @@ -569,10 +568,10 @@ class MainWindow(QtWidgets.QMainWindow): 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) + friend = self._contacts_manager.get_friend(num) if friend is None: return - allowed = friend.tox_id in settings['auto_accept_from_friends'] + allowed = friend.tox_id in self._settings['auto_accept_from_friends'] auto = util_ui.tr('Disallow auto accept') if allowed else util_ui.tr('Allow auto accept') if item is not None: self.listMenu = QtWidgets.QMenu() @@ -597,7 +596,7 @@ class MainWindow(QtWidgets.QMainWindow): block_item = self.listMenu.addAction(util_ui.tr('Block friend')) notes_item = self.listMenu.addAction(util_ui.tr('Notes')) - chats = self.profile.get_group_chats() + chats = self._contacts_manager.get_group_chats() if len(chats) and self.profile.is_active_online(): invite_menu = self.listMenu.addMenu(util_ui.tr('Invite to group chat')) for i in range(len(chats)): @@ -630,7 +629,7 @@ class MainWindow(QtWidgets.QMainWindow): self.listMenu.show() def show_note(self, friend): - note = self._settings['notes'][friend.tox_id] if friend.tox_id in s['notes'] else '' + 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) @@ -644,7 +643,7 @@ class MainWindow(QtWidgets.QMainWindow): self.note.show() def export_history(self, num, as_text=True): - s = self.profile.export_history(num, as_text) + s = self._history_loader.export_history(num, as_text) extension = 'txt' if as_text else 'html' file_name, _ = util_ui.save_file_dialog(util_ui.tr('Choose file name'), extension) @@ -655,30 +654,32 @@ class MainWindow(QtWidgets.QMainWindow): fl.write(s) def set_alias(self, num): - self.profile.set_alias(num) + self._contacts_manager.set_alias(num) def remove_friend(self, num): - self.profile.delete_friend(num) + self._contacts_manager.delete_friend(num) def block_friend(self, num): friend = self.profile.get_friend(num) - self.profile.block_user(friend.tox_id) + self._contacts_manager.block_user(friend.tox_id) def copy_friend_key(self, num): - tox_id = self.profile.friend_public_key(num) + tox_id = self._contacts_manager.friend_public_key(num) clipboard = QtWidgets.QApplication.clipboard() clipboard.setText(tox_id) - def copy_name(self, friend): + @staticmethod + def copy_name(friend): clipboard = QtWidgets.QApplication.clipboard() clipboard.setText(friend.name) - def copy_status(self, friend): + @staticmethod + def copy_status(friend): clipboard = QtWidgets.QApplication.clipboard() clipboard.setText(friend.status_message) def clear_history(self, num): - self.profile.clear_history(num) + self._contacts_manager.clear_history(num) def leave_gc(self, num): self.profile.leave_gc(num) @@ -687,7 +688,7 @@ class MainWindow(QtWidgets.QMainWindow): self.profile.set_title(num) def auto_accept(self, num, value): - tox_id = self.profile.friend_public_key(num) + tox_id = self._contacts_manager.friend_public_key(num) if value: self._settings['auto_accept_from_friends'].append(tox_id) else: @@ -695,7 +696,7 @@ class MainWindow(QtWidgets.QMainWindow): self._settings.save() def invite_friend_to_gc(self, friend_number, group_number): - self.profile.invite_friend(friend_number, group_number) + self._contacts_manager.invite_friend(friend_number, group_number) # ----------------------------------------------------------------------------------------------------------------- # Functions which called when user click somewhere else @@ -703,7 +704,7 @@ class MainWindow(QtWidgets.QMainWindow): def friend_click(self, index): num = index.row() - self.profile.set_active(num) + self._contacts_manager.set_active(num) def mouseReleaseEvent(self, event): pos = self.connection_status.pos() @@ -715,17 +716,17 @@ class MainWindow(QtWidgets.QMainWindow): def show(self): super().show() - #self.profile.update() + #self._contacts_manager.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()) + self._contacts_manager.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: + if self._c4.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 diff --git a/toxygen/ui/main_screen_widgets.py b/toxygen/ui/main_screen_widgets.py index 390a55a..665e5dc 100644 --- a/toxygen/ui/main_screen_widgets.py +++ b/toxygen/ui/main_screen_widgets.py @@ -1,7 +1,6 @@ from PyQt5 import QtCore, QtGui, QtWidgets from ui.widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit from contacts.profile import Profile -import smileys import urllib import util.util as util import util.ui as util_ui @@ -13,10 +12,14 @@ class MessageArea(QtWidgets.QPlainTextEdit): def __init__(self, parent, form): super().__init__(parent) + self._messenger = None self.parent = form self.setAcceptDrops(True) - self.timer = QtCore.QTimer(self) - self.timer.timeout.connect(lambda: self.parent.profile.send_typing(False)) + self._timer = QtCore.QTimer(self) + self._timer.timeout.connect(lambda: self._messenger.send_typing(False)) + + def set_messenger(self, messenger): + self._messenger = messenger def keyPressEvent(self, event): if event.matches(QtGui.QKeySequence.Paste): @@ -31,22 +34,22 @@ class MessageArea(QtWidgets.QPlainTextEdit): if modifiers & QtCore.Qt.ControlModifier or modifiers & QtCore.Qt.ShiftModifier: self.insertPlainText('\n') else: - if self.timer.isActive(): - self.timer.stop() - self.parent.profile.send_typing(False) - self.parent.send_message() + if self._timer.isActive(): + self._timer.stop() + self._messenger.send_typing(False) + self._messenger.send_message() elif event.key() == QtCore.Qt.Key_Up and not self.toPlainText(): - self.appendPlainText(Profile.get_instance().get_last_message()) - elif event.key() == QtCore.Qt.Key_Tab and not self.parent.profile.is_active_a_friend(): + self.appendPlainText(self._messenger.get_last_message()) + elif event.key() == QtCore.Qt.Key_Tab and not self._messenger.is_active_a_friend(): text = self.toPlainText() pos = self.textCursor().position() - self.insertPlainText(Profile.get_instance().get_gc_peer_name(text[:pos])) + self.insertPlainText(self._messenger.get_gc_peer_name(text[:pos])) else: - self.parent.profile.send_typing(True) - if self.timer.isActive(): - self.timer.stop() - self.timer.start(5000) - super(MessageArea, self).keyPressEvent(event) + self._messenger.send_typing(True) + if self._timer.isActive(): + self._timer.stop() + self._timer.start(5000) + super().keyPressEvent(event) def contextMenuEvent(self, event): menu = create_menu(self.createStandardContextMenu()) diff --git a/toxygen/ui/messages_widgets.py b/toxygen/ui/messages_widgets.py index ae21f8a..0d65c8a 100644 --- a/toxygen/ui/messages_widgets.py +++ b/toxygen/ui/messages_widgets.py @@ -122,7 +122,7 @@ class MessageItem(QtWidgets.QWidget): """ Message in messages list """ - def __init__(self, settings, message_edit_factory, text_message, parent=None): + def __init__(self, settings, text_message, parent=None): QtWidgets.QWidget.__init__(self, parent) self.name = widgets.DataLabel(self) self.name.setGeometry(QtCore.QRect(2, 2, 95, 23)) diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py index a9be90a..4051656 100644 --- a/toxygen/ui/widgets_factory.py +++ b/toxygen/ui/widgets_factory.py @@ -56,3 +56,8 @@ class WidgetsFactory: def create_notification_settings_window(self): return NotificationsSettings(self._settings) + def create_smiley_window(self, parent): + return SmileyWindow(parent, self._smiley_loader) + + def create_sticker_window(self, parent): + return StickerWindow(parent, self._file_transfer_handler) diff --git a/toxygen/util/util.py b/toxygen/util/util.py index afa6a91..09e6877 100644 --- a/toxygen/util/util.py +++ b/toxygen/util/util.py @@ -94,6 +94,10 @@ def curr_time(): return time.strftime('%H:%M') +def get_unix_time(): + return int(time.time()) + + def join_path(a, b): return os.path.join(a, b) From ad351030d928d012ae557ab9cc140bb950bf70fc Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 1 May 2018 21:40:29 +0300 Subject: [PATCH 016/138] messenger fixes, refactoring (history) --- toxygen/app.py | 10 +-- toxygen/contacts/contacts_manager.py | 68 ++++++++----------- toxygen/history/history_loader.py | 76 +++++----------------- toxygen/history/history_logs_generators.py | 48 ++++++++++++++ toxygen/messenger/messages.py | 10 +++ toxygen/messenger/messenger.py | 33 ++++++---- toxygen/ui/items_factory.py | 4 +- toxygen/ui/main_screen.py | 33 +++++----- toxygen/ui/widgets_factory.py | 3 +- 9 files changed, 150 insertions(+), 135 deletions(-) create mode 100644 toxygen/history/history_logs_generators.py diff --git a/toxygen/app.py b/toxygen/app.py index 82d1618..53c4dff 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -141,6 +141,8 @@ class App: 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: with open(curr_directory(__file__) + self._settings.built_in_themes()[theme]) as fl: @@ -294,17 +296,17 @@ class App: self._friend_factory = FriendFactory(self._profile_manager, self._settings, self._tox, db, items_factory) self._contacts_provider = ContactProvider(self._tox, self._friend_factory) widgets_factory = WidgetsFactory(self._settings, profile, self._contacts_manager, self._file_transfer_handler, - self._smiley_loader, self._plugin_loader, self._toxes) + self._smiley_loader, self._plugin_loader, self._toxes, self._version) self._contacts_manager = ContactsManager(self._tox, self._settings, self._ms, self._profile_manager, self._contacts_provider, db) self._messenger = Messenger(self._tox, self._plugin_loader, self._ms, self._contacts_manager, - self._contacts_provider) + self._contacts_provider, items_factory) self._tray = tray.init_tray(profile, self._settings, self._ms) - self._ms.set_dependencies(widgets_factory, self._tray, self._contacts_manager, self._messenger) + self._ms.set_dependencies(widgets_factory, self._tray, self._contacts_manager, self._messenger, profile) self._calls_manager = CallsManager(self._tox.AV, self._settings) self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider) - self._tray.show() + self._tray.show() self._ms.show() # callbacks initialization diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 0f7b4bc..e468ecc 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -1,7 +1,6 @@ import util.util as util import util.ui as util_ui from contacts.friend import Friend -import os from PyQt5 import QtCore, QtGui from messenger.messages import * from wrapper.toxcore_enums_and_consts import * @@ -9,10 +8,10 @@ from network.tox_dns import tox_dns from history.history_loader import HistoryLoader -# TODO: move messaging and typing notifications to other class - - class ContactsManager: + """ + Represents contacts list. + """ def __init__(self, tox, settings, screen, profile_manager, contact_provider, db): self._tox = tox @@ -134,12 +133,11 @@ class ContactsManager: # self._screen.call_finished() else: friend = self.get_curr_contact() - + # TODO: to separate method self._screen.account_name.setText(friend.name) self._screen.account_status.setText(friend.status_message) self._screen.account_status.setToolTip(friend.get_full_status()) avatar_path = friend.get_avatar_path() - os.chdir(os.path.dirname(avatar_path)) pixmap = QtGui.QPixmap(avatar_path) self._screen.account_avatar.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)) @@ -171,6 +169,7 @@ class ContactsManager: :param sorting: 0 - no sort, 1 - online only, 2 - online first, 4 - by name :param filter_str: show contacts which name contains this substring """ + # TODO: simplify? filter_str = filter_str.lower() number = self.get_active_number() is_friend = self._is_active_a_friend() @@ -196,16 +195,16 @@ class ContactsManager: part1 = sorted(part1, key=lambda x: x.number) part2 = sorted(part2, key=lambda x: x.number) self._contacts = part1 + part2 - self._screen.friends_list.clear() - for contact in self._contacts: - contact.set_widget(self.create_friend_item()) + # self._screen.friends_list.clear() + # for contact in self._contacts: + # contact.set_widget(self.create_friend_item()) for index, friend in enumerate(self._contacts): friend.visibility = (friend.status is not None or not (sorting & 1)) and (filter_str in friend.name.lower()) friend.visibility = friend.visibility or friend.messages or friend.actions - # if friend.visibility: - # self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, self._friend_item_height)) - # else: - # self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, 0)) + if friend.visibility: + self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, self._friend_item_height)) + else: + self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, 0)) self._sorting, self._filter_string = sorting, filter_str self._settings['sorting'] = self._sorting self._settings.save() @@ -217,13 +216,6 @@ class ContactsManager: """ self.filtration_and_sorting(self._sorting, self._filter_string) - def _create_friend_item(self): - """ - Method-factory - :return: new widget for friend instance - """ - return self._factory.friend_item() - # ----------------------------------------------------------------------------------------------------------------- # Friend getters # ----------------------------------------------------------------------------------------------------------------- @@ -233,7 +225,7 @@ class ContactsManager: def get_last_message(self): if self._active_contact + 1: - return self.get_current_contact().get_last_message_text() + return self.get_curr_contact().get_last_message_text() else: return '' @@ -241,12 +233,13 @@ class ContactsManager: return self.get_curr_contact().number if self._active_contact + 1 else -1 def get_active_name(self): - return self.get_current_contact().name if self._active_contact + 1 else '' + 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_current_contact().status is not None + return self._active_contact + 1 and self.get_curr_contact().status is not None def new_name(self, number, name): + # TODO: move to somewhere else? friend = self.get_friend_by_number(number) tmp = friend.name friend.set_name(name) @@ -254,7 +247,7 @@ class ContactsManager: if friend.name == name and tmp != name: message = util_ui.tr('User {} is now known as {}') message = message.format(tmp, name) - friend.append_message(InfoMessage(message, time.time())) + friend.append_message(InfoMessage(0, message, util.get_unix_time())) friend.actions = True if number == self.get_active_number(): self.create_message_item(message, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) @@ -293,12 +286,16 @@ class ContactsManager: except: pass self._settings.save() - if num == self.get_active_number() and self.is_active_a_friend(): - self.update() + # if num == self.get_active_number() and self.is_active_a_friend(): + # self.update() def friend_public_key(self, num): return self._contacts[num].tox_id + def export_history(self, num, as_text): + contact = self._contacts[num] + return self._history.export_history(contact, as_text) + def delete_friend(self, num): """ Removes friend from contact list @@ -313,17 +310,12 @@ class ContactsManager: if friend.tox_id in self._settings['notes']: del self._settings['notes'][friend.tox_id] self._settings.save() - self.clear_history(num) - if self._history.friend_exists_in_db(friend.tox_id): - self._history.delete_friend_from_db(friend.tox_id) + self._history.delete_history(friend) self._tox.friend_delete(friend.number) del self._contacts[num] self._screen.friends_list.takeItem(num) if num == self._active_contact: # active friend was deleted - if not len(self._contacts): # last friend was deleted - self.set_active(-1) - else: - self.set_active(0) + self.set_active(0 if len(self._contacts) else -1) data = self._tox.get_savedata() self._profile_manager.save_profile(data) @@ -387,13 +379,9 @@ class ContactsManager: text = util_ui.tr('Friend added without sending friend request') util_ui.message_box(text, title) else: - result = self._tox.friend_add(tox_id, message.encode('utf-8')) + self._tox.friend_add(tox_id, message.encode('utf-8')) tox_id = tox_id[:TOX_PUBLIC_KEY_SIZE * 2] - item = self.create_friend_item() - if not self._history.friend_exists_in_db(tox_id): - self._history.add_friend_to_db(tox_id) - message_getter = self._history.messages_getter(tox_id) - friend = Friend(message_getter, result, tox_id, '', item, tox_id) + friend = self._contact_provider.get_friend_by_public_key(tox_id) self._contacts.append(friend) self.save_profile() return True @@ -420,7 +408,7 @@ class ContactsManager: util.log('Accept friend request failed! ' + str(ex)) def can_send_typing_notification(self): - return self._settings['typing_notifications'] and self._active_contact != -1 + return self._settings['typing_notifications'] and self._active_contact + 1 # ----------------------------------------------------------------------------------------------------------------- # Private methods diff --git a/toxygen/history/history_loader.py b/toxygen/history/history_loader.py index 801125b..a380ddb 100644 --- a/toxygen/history/history_loader.py +++ b/toxygen/history/history_loader.py @@ -1,4 +1,4 @@ -from messenger.messages import * +from history.history_logs_generators import * # TODO: fix history loading and saving @@ -96,65 +96,21 @@ class HistoryLoader: return self._db.messages_getter(friend_public_key) - @staticmethod - def export_history(friend, as_text=True, _range=None): - if _range is None: - friend.load_all_corr() - corr = friend.get_corr() - elif _range[1] + 1: - corr = friend.get_corr()[_range[0]:_range[1] + 1] - else: - corr = friend.get_corr()[_range[0]:] + def delete_history(self, friend): + self.clear_history(friend) + if self._db.friend_exists_in_db(friend.tox_id): + self._db.delete_friend_from_db(friend.tox_id) - generator = TextHistoryGenerator(corr) if as_text else HtmlHistoryGenerator(corr) + @staticmethod + def export_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() - - -class HistoryLogsGenerator: - - def __init__(self, history): - self._history = history - - def generate(self): - return str() - - -class HtmlHistoryGenerator(HistoryLogsGenerator): - - def __init__(self, history): - super().__init__(history) - - def generate(self): - arr = [] - for message in self._history: - if type(message) is TextMessage: - data = message.get_data() - x = '[{}] {}: {}
' - arr.append(x.format(convert_time(data[2]) if data[1] != MESSAGE_OWNER['NOT_SENT'] else 'Unsent', - friend.name if data[1] == MESSAGE_OWNER['FRIEND'] else self.name, - data[0])) - s = '
'.join(arr) - s = '{}{}'.format(friend.name, - s) - return s - - -class TextHistoryGenerator(HistoryLogsGenerator): - - def __init__(self, history): - super().__init__(history) - - def generate(self): - arr = [] - for message in self._history: - if type(message) is TextMessage: - data = message.get_data() - x = '[{}] {}: {}\n' - arr.append(x.format(convert_time(data[2]) if data[1] != MESSAGE_OWNER['NOT_SENT'] else 'Unsent', - friend.name if data[1] == MESSAGE_OWNER['FRIEND'] else self.name, - data[0])) - s = '\n'.join(arr) - - return s - diff --git a/toxygen/history/history_logs_generators.py b/toxygen/history/history_logs_generators.py new file mode 100644 index 0000000..3e46562 --- /dev/null +++ b/toxygen/history/history_logs_generators.py @@ -0,0 +1,48 @@ +from messenger.messages import * +import util.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 = '[{}] {}: {}
' + arr.append(x.format(self._get_message_time(message), message.author.name, message.text)) + s = '
'.join(arr) + 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) diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index 023a2e7..41c046d 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -38,6 +38,11 @@ class Message: author = property(get_author) + def get_time(self): + return self._time + + time = property(get_time) + def get_message_id(self): return self._message_id @@ -70,6 +75,11 @@ class TextMessage(Message): super().__init__(id, message_type, owner, time) self._message = message + def get_text(self): + return self._message + + text = property(get_text) + def get_data(self): return self._message, self._owner, self._time, self._type diff --git a/toxygen/messenger/messenger.py b/toxygen/messenger/messenger.py index 77cb431..8cb25e1 100644 --- a/toxygen/messenger/messenger.py +++ b/toxygen/messenger/messenger.py @@ -5,19 +5,21 @@ from messenger.messages import * class Messenger(util.ToxSave): - def __init__(self, tox, plugin_loader, screen, contacts_manager, contacts_provider): + def __init__(self, tox, plugin_loader, screen, contacts_manager, contacts_provider, items_factory): 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 # ----------------------------------------------------------------------------------------------------------------- # Private methods # ----------------------------------------------------------------------------------------------------------------- - def _create_message_item(self): - pass + def _create_message_item(self, text_message): + # pixmap = self._contacts_manager.get_curr_contact().get_pixmap() + self._items_factory.message_item(text_message) # ----------------------------------------------------------------------------------------------------------------- # Messaging @@ -66,17 +68,20 @@ class Messenger(util.ToxSave): else: message_type = TOX_MESSAGE_TYPE['NORMAL'] friend = self.get_friend_by_number(friend_number) - friend.inc_receipts() - if friend.status is not None: - messages = self._split_message(text.encode('utf-8')) - t = util.get_unix_time() - for message in messages: + 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) - friend.append_message(TextMessage(message_id, text, MESSAGE_AUTHOR['NOT_SENT'], t, message_type)) - if self._contacts_manager.is_friend_active(friend_number): - self.create_message_item(text, t, MESSAGE_AUTHOR['NOT_SENT'], message_type) - self._screen.messageEdit.clear() - self._screen.messages.scrollToBottom() + friend.inc_receipts() + else: + message_id = 0 + message = TextMessage(message_id, text, MESSAGE_AUTHOR['NOT_SENT'], t, message_type) + friend.append_message(message) + if self._contacts_manager.is_friend_active(friend_number): + self._create_message_item(message) + self._screen.messageEdit.clear() + self._screen.messages.scrollToBottom() # ----------------------------------------------------------------------------------------------------------------- # Typing notifications @@ -121,6 +126,8 @@ class Messenger(util.ToxSave): index += size + 1 messages.append(message[:index]) message = message[index:] + if message: + messages.append(message) return messages diff --git a/toxygen/ui/items_factory.py b/toxygen/ui/items_factory.py index 347424d..10ca02b 100644 --- a/toxygen/ui/items_factory.py +++ b/toxygen/ui/items_factory.py @@ -18,8 +18,8 @@ class ItemsFactory: self._friends_list.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) + def message_item(self, message, pixmap=None): + item = MessageItem(message, self._messages) if pixmap is not None: item.set_avatar(pixmap) elem = QtWidgets.QListWidgetItem() diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index c059b1f..05007f9 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -17,13 +17,14 @@ class MainWindow(QtWidgets.QMainWindow): self._modal_window = None self.setAcceptDrops(True) self._saved = False - self.profile = None + self._profile = None self.initUI() - def set_dependencies(self, widget_factory, tray, contacts_manager, messenger): + def set_dependencies(self, widget_factory, tray, contacts_manager, messenger, profile): self._widget_factory = widget_factory self._tray = tray self._contacts_manager = contacts_manager + self._profile = profile self.messageEdit.set_messenger(messenger) def show(self): @@ -401,17 +402,18 @@ class MainWindow(QtWidgets.QMainWindow): #self.profile.update() def keyPressEvent(self, event): - if event.key() == QtCore.Qt.Key_Escape and QtWidgets.QSystemTrayIcon.isSystemTrayAvailable(): + key, modifiers = event.key(), event.modifiers() + if 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(): + 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.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(): + elif key == QtCore.Qt.Key_Z and modifiers & QtCore.Qt.ControlModifier and self.messages.selectedIndexes(): self.messages.clearSelection() - elif event.key() == QtCore.Qt.Key_F and event.modifiers() & QtCore.Qt.ControlModifier: + elif key == QtCore.Qt.Key_F and modifiers & QtCore.Qt.ControlModifier: self.show_search_field() else: super().keyPressEvent(event) @@ -421,6 +423,7 @@ class MainWindow(QtWidgets.QMainWindow): # ----------------------------------------------------------------------------------------------------------------- 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') @@ -435,7 +438,7 @@ class MainWindow(QtWidgets.QMainWindow): self._modal_window.show() def add_contact(self, link=''): - self._modal_window = self._widget_factory.create_add_contact_window(link or '') + self._modal_window = self._widget_factory.create_add_contact_window(link) self._modal_window.show() def create_gc(self): @@ -507,7 +510,7 @@ class MainWindow(QtWidgets.QMainWindow): def send_file(self): self.menu.hide() - if self._contacts_manager.active_friend + 1 and self._contacts_manager.is_active_a_friend(): + if self._contacts_manager.is_active_a_friend(): caption = util_ui.tr('Choose file') name = util_ui.file_dialog(caption) if name[0]: @@ -515,8 +518,8 @@ class MainWindow(QtWidgets.QMainWindow): def send_screenshot(self, hide=False): self.menu.hide() - if self._contacts_manager.active_friend + 1 and self._contacts_manager.is_active_a_friend(): - self.sw = ScreenShotWindow(self) + if self._contacts_manager.is_active_a_friend(): + self.sw = self._widget_factory.create_screenshot_window(self) self.sw.show() if hide: self.hide() @@ -533,7 +536,7 @@ class MainWindow(QtWidgets.QMainWindow): def send_sticker(self): self.menu.hide() - if self._contacts_manager.active_friend + 1 and self._contacts_manager.is_active_a_friend(): + if self._contacts_manager.is_active_a_friend(): self.sticker = self._widget_factory.create_sticker_window(self) self.sticker.setGeometry(QtCore.QRect(self.x() if self._settings['mirror_mode'] else 270 + self.x(), self.y() + self.height() - 200, @@ -643,7 +646,7 @@ class MainWindow(QtWidgets.QMainWindow): self.note.show() def export_history(self, num, as_text=True): - s = self._history_loader.export_history(num, as_text) + s = self._contacts_manager.export_history(num, as_text) extension = 'txt' if as_text else 'html' file_name, _ = util_ui.save_file_dialog(util_ui.tr('Choose file name'), extension) @@ -712,11 +715,11 @@ class MainWindow(QtWidgets.QMainWindow): if (x < event.x() < x + 32) and (y < event.y() < y + 32): self.profile.change_status() else: - super(MainWindow, self).mouseReleaseEvent(event) + super().mouseReleaseEvent(event) def show(self): super().show() - #self._contacts_manager.update() + self._contacts_manager.update() def filtering(self): ind = self.online_contacts.currentIndex() @@ -726,7 +729,7 @@ class MainWindow(QtWidgets.QMainWindow): def show_search_field(self): if hasattr(self, 'search_field') and self.search_field.isVisible(): return - if self._c4.get_curr_friend() is None: + if self._contacts_manager.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 diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py index 4051656..1bba4a7 100644 --- a/toxygen/ui/widgets_factory.py +++ b/toxygen/ui/widgets_factory.py @@ -5,7 +5,7 @@ from ui.menu import * class WidgetsFactory: def __init__(self, settings, profile, contacts_manager, file_transfer_handler, smiley_loader, plugin_loader, - toxes): + toxes, version): self._settings = settings self._profile = profile self._contacts_manager = contacts_manager @@ -13,6 +13,7 @@ class WidgetsFactory: self._smiley_loader = smiley_loader self._plugin_loader = plugin_loader self._toxes = toxes + self._version = version def create_screenshot_window(self, *args): return ScreenShotWindow(self._file_transfer_handler, *args) From c8443b56ddfae359e3cb070d1e57827dc40dd803 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 4 May 2018 00:17:48 +0300 Subject: [PATCH 017/138] messages - minimal working version --- toxygen/app.py | 15 +-- toxygen/bootstrap/bootstrap.py | 2 +- toxygen/contacts/contacts_manager.py | 1 - toxygen/contacts/profile.py | 43 +------ toxygen/file_transfers/file_transfers.py | 2 +- .../file_transfers/file_transfers_handler.py | 46 ++++++-- toxygen/history/database.py | 3 + toxygen/main.py | 6 +- toxygen/messenger/messages.py | 5 + toxygen/messenger/messenger.py | 37 ++++-- toxygen/middleware/callbacks.py | 17 +-- toxygen/network/tox_dns.py | 106 +++++++++--------- toxygen/ui/av_widgets.py | 2 +- toxygen/ui/items_factory.py | 11 +- toxygen/ui/list_items.py | 2 +- toxygen/ui/menu.py | 2 +- toxygen/ui/messages_widgets.py | 18 +-- toxygen/ui/tray.py | 2 +- toxygen/user_data/settings.py | 2 +- 19 files changed, 174 insertions(+), 148 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index 53c4dff..87d3c60 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -289,22 +289,23 @@ class App: def _create_dependencies(self): self._smiley_loader = SmileyLoader(self._settings) self._ms = MainWindow(self._settings, self._tray) + self._calls_manager = CallsManager(self._tox.AV, self._settings) db = Database(self._path.replace('.tox', '.db'), self._toxes) - profile = Profile(self._profile_manager, self._tox, self._ms, self._file_transfer_handler) - self._plugin_loader = PluginLoader(self._tox, self._toxes, profile, self._settings) # plugins support + + profile = Profile(self._profile_manager, self._tox, self._ms) + self._plugin_loader = PluginLoader(self._tox, self._toxes, profile, self._settings) items_factory = ItemsFactory(self._settings, self._plugin_loader, self._smiley_loader, self._ms) self._friend_factory = FriendFactory(self._profile_manager, self._settings, self._tox, db, items_factory) self._contacts_provider = ContactProvider(self._tox, self._friend_factory) - widgets_factory = WidgetsFactory(self._settings, profile, self._contacts_manager, self._file_transfer_handler, - self._smiley_loader, self._plugin_loader, self._toxes, self._version) self._contacts_manager = ContactsManager(self._tox, self._settings, self._ms, self._profile_manager, self._contacts_provider, db) + self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider) + widgets_factory = WidgetsFactory(self._settings, profile, self._contacts_manager, self._file_transfer_handler, + self._smiley_loader, self._plugin_loader, self._toxes, self._version) self._messenger = Messenger(self._tox, self._plugin_loader, self._ms, self._contacts_manager, - self._contacts_provider, items_factory) + self._contacts_provider, items_factory, profile) self._tray = tray.init_tray(profile, self._settings, self._ms) self._ms.set_dependencies(widgets_factory, self._tray, self._contacts_manager, self._messenger, profile) - self._calls_manager = CallsManager(self._tox.AV, self._settings) - self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider) self._tray.show() self._ms.show() diff --git a/toxygen/bootstrap/bootstrap.py b/toxygen/bootstrap/bootstrap.py index ccce1c7..2e5d3dc 100644 --- a/toxygen/bootstrap/bootstrap.py +++ b/toxygen/bootstrap/bootstrap.py @@ -1,6 +1,6 @@ import random import urllib.request -from util.util import log, curr_directory, join_path +from util.util import * from PyQt5 import QtNetwork, QtCore import json diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index e468ecc..8fb1b92 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -4,7 +4,6 @@ from contacts.friend import Friend from PyQt5 import QtCore, QtGui from messenger.messages import * from wrapper.toxcore_enums_and_consts import * -from network.tox_dns import tox_dns from history.history_loader import HistoryLoader diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 4949175..cfeae94 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -1,7 +1,5 @@ from contacts.friend import * from user_data.settings import * -from wrapper.toxcore_enums_and_consts import * -from util.util import log from history.database import * from file_transfers.file_transfers import * import time @@ -14,7 +12,7 @@ class Profile(basecontact.BaseContact): """ Profile of current toxygen user. Contains friends list, tox instance """ - def __init__(self, profile_manager, tox, screen, file_transfer_handler): + def __init__(self, profile_manager, tox, screen): """ :param tox: tox instance :param screen: ref to main screen @@ -25,7 +23,6 @@ class Profile(basecontact.BaseContact): tox.self_get_status_message(), screen.user_info, tox.self_get_address()) - self._file_transfer_handler = file_transfer_handler self._screen = screen self._messages = screen.messages self._tox = tox @@ -83,30 +80,6 @@ class Profile(basecontact.BaseContact): # Friend connection status callbacks # ----------------------------------------------------------------------------------------------------------------- - 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 = fl.get_data() - if data[1] is not None: - self.send_inline(data[1], data[0], friend_number, True) - else: - self.send_file(data[0], friend_number, True) - friend.clear_unsent_files() - for key in list(self._paused_file_transfers.keys()): - data = self._paused_file_transfers[key] - if not os.path.exists(data[0]): - del self._paused_file_transfers[key] - elif data[1] == friend_number and not data[2]: - self.send_file(data[0], friend_number, True, key) - del self._paused_file_transfers[key] - if friend_number == self.get_active_number() and self.is_active_a_friend(): - self.update() - except Exception as ex: - print('Exception in file sending: ' + str(ex)) - def friend_exit(self, friend_number): """ Friend with specified number quit @@ -133,20 +106,6 @@ class Profile(basecontact.BaseContact): while i < self._messages.count() and not self._messages.itemWidget(self._messages.item(i)).mark_as_sent(): i += 1 - 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: - self.split_and_send(friend_number, message.get_data()[-1], message.get_data()[0].encode('utf-8')) - friend.inc_receipts() - except Exception as ex: - log('Sending pending messages failed with ' + str(ex)) - def delete_message(self, message_id): friend = self.get_curr_friend() friend.delete_message(time) diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py index 5aab6f9..937ab12 100644 --- a/toxygen/file_transfers/file_transfers.py +++ b/toxygen/file_transfers/file_transfers.py @@ -172,7 +172,7 @@ class SendAvatar(SendTransfer): else: with open(path, 'rb') as fl: avatar_hash = Tox.hash(fl.read()) - super(SendAvatar, self).__init__(path, tox, friend_number, TOX_FILE_KIND['AVATAR'], avatar_hash) + super().__init__(path, tox, friend_number, TOX_FILE_KIND['AVATAR'], avatar_hash) class SendFromBuffer(FileTransfer): diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index e7a0308..12bc60d 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -2,6 +2,8 @@ from file_transfers.file_transfers import * from messenger.messages import * from history.database import MESSAGE_AUTHOR import os +from ui.list_items import * +from PyQt5 import QtWidgets import util.util as util @@ -99,7 +101,7 @@ class FileTransfersHandler: :param already_cancelled: was cancelled by friend """ i = self._get_friend_by_number(friend_number).update_transfer_data(file_number, - TOX_FILE_TRANSFER_STATE['CANCELLED']) + TOX_FILE_TRANSFER_STATE['CANCELLED']) if (friend_number, file_number) in self._file_transfers: tr = self._file_transfers[(friend_number, file_number)] if not already_cancelled: @@ -130,7 +132,7 @@ class FileTransfersHandler: tr = self._file_transfers[(friend_number, file_number)] tr.pause(by_friend) t = TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'] if by_friend else TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER'] - self.get_friend_by_number(friend_number).update_transfer_data(file_number, t) + self._get_friend_by_number(friend_number).update_transfer_data(file_number, t) def resume_transfer(self, friend_number, file_number, by_friend=False): """ @@ -175,7 +177,7 @@ class FileTransfersHandler: self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['RESUME']) if item is not None: rt.set_state_changed_handler(item.update_transfer_state) - self.get_friend_by_number(friend_number).update_transfer_data(file_number, + self._get_friend_by_number(friend_number).update_transfer_data(file_number, TOX_FILE_TRANSFER_STATE['RUNNING']) def send_screenshot(self, data): @@ -192,7 +194,7 @@ class FileTransfersHandler: def send_inline(self, data, file_name, friend_number=None, is_resend=False): friend_number = friend_number or self.get_active_number() - friend = self.get_friend_by_number(friend_number) + friend = self._get_friend_by_number(friend_number) if friend.status is None and not is_resend: m = UnsentFile(file_name, data, time.time()) friend.append_message(m) @@ -224,7 +226,7 @@ class FileTransfersHandler: :param file_id: file id of transfer """ friend_number = self.get_active_number() if number is None else number - friend = self.get_friend_by_number(friend_number) + friend = self._get_friend_by_number(friend_number) if friend.status is None and not is_resend: m = UnsentFile(path, None, time.time()) friend.append_message(m) @@ -265,13 +267,13 @@ class FileTransfersHandler: transfer = self._file_transfers[(friend_number, file_number)] t = type(transfer) if t is ReceiveAvatar: - self.get_friend_by_number(friend_number).load_avatar() + self._get_friend_by_number(friend_number).load_avatar() if friend_number == self.get_active_number() and self.is_active_a_friend(): self.set_active(None) - elif t is ReceiveToBuffer or (t is SendFromBuffer and self._settings.get_instance()['allow_inline']): # inline image + elif t is ReceiveToBuffer or (t is SendFromBuffer and self._settings['allow_inline']): # inline image print('inline') inline = InlineImage(transfer.get_data()) - i = self.get_friend_by_number(friend_number).update_transfer_data(file_number, + i = self._get_friend_by_number(friend_number).update_transfer_data(file_number, TOX_FILE_TRANSFER_STATE['FINISHED'], inline) if friend_number == self.get_active_number() and self.is_active_a_friend(): @@ -284,11 +286,35 @@ class FileTransfersHandler: self._messages.setItemWidget(elem, item) self._messages.scrollToBottom() elif t is not SendAvatar: - self.get_friend_by_number(friend_number).update_transfer_data(file_number, + self._get_friend_by_number(friend_number).update_transfer_data(file_number, TOX_FILE_TRANSFER_STATE['FINISHED']) del self._file_transfers[(friend_number, file_number)] del transfer + 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 = fl.get_data() + if data[1] is not None: + self.send_inline(data[1], data[0], friend_number, True) + else: + self.send_file(data[0], friend_number, True) + friend.clear_unsent_files() + for key in list(self._paused_file_transfers.keys()): + data = self._paused_file_transfers[key] + if not os.path.exists(data[0]): + del self._paused_file_transfers[key] + elif data[1] == friend_number and not data[2]: + self.send_file(data[0], friend_number, True, key) + del self._paused_file_transfers[key] + if friend_number == self.get_active_number() and self.is_active_a_friend(): + self.update() + except Exception as ex: + print('Exception in file sending: ' + str(ex)) + # ----------------------------------------------------------------------------------------------------------------- # Avatars support # ----------------------------------------------------------------------------------------------------------------- @@ -313,7 +339,7 @@ class FileTransfersHandler: self._file_transfers[(friend_number, file_number)] = ra ra.set_transfer_finished_handler(self.transfer_finished) else: - self.get_friend_by_number(friend_number).load_avatar() + self._get_friend_by_number(friend_number).load_avatar() if self.get_active_number() == friend_number and self.is_active_a_friend(): self.set_active(None) diff --git a/toxygen/history/database.py b/toxygen/history/database.py index d7c10eb..188e85b 100644 --- a/toxygen/history/database.py +++ b/toxygen/history/database.py @@ -163,6 +163,9 @@ class Database: db.close() def messages_getter(self, tox_id): + if not self.friend_exists_in_db(tox_id): + self.add_friend_to_db(tox_id) + return Database.MessageGetter(self._path, tox_id) # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/main.py b/toxygen/main.py index 1171aac..5ecbd77 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -1,6 +1,6 @@ import app from user_data.settings import * -from util.util import remove, get_libs_directory +import util.util as util import argparse @@ -10,8 +10,8 @@ __version__ = '0.5.0' def clean(): """Removes all windows libs from libs folder""" - directory = get_libs_directory() - remove(directory) + directory = util.get_libs_directory() + util.remove(directory) def reset(): diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index 41c046d..830e72e 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -48,6 +48,11 @@ class Message: message_id = property(get_message_id) + def get_type(self): + return self._type + + type = property(get_type) + def get_widget(self): if self._widget is None: self._widget = self._create_widget() diff --git a/toxygen/messenger/messenger.py b/toxygen/messenger/messenger.py index 8cb25e1..cb17398 100644 --- a/toxygen/messenger/messenger.py +++ b/toxygen/messenger/messenger.py @@ -5,13 +5,14 @@ from messenger.messages import * class Messenger(util.ToxSave): - def __init__(self, tox, plugin_loader, screen, contacts_manager, contacts_provider, items_factory): + def __init__(self, tox, plugin_loader, screen, contacts_manager, contacts_provider, items_factory, profile): 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 # ----------------------------------------------------------------------------------------------------------------- # Private methods @@ -33,17 +34,16 @@ class Messenger(util.ToxSave): :param message: text of message """ t = util.get_unix_time() + friend = self._get_friend_by_number(friend_number) + text_message = TextMessage(0, message, MessageAuthor(friend.name, MESSAGE_AUTHOR['FRIEND']), t, message_type) if self._contacts_manager.is_friend_active(friend_number): # add message to list - self.create_message_item(message, t, MESSAGE_AUTHOR['FRIEND'], message_type) + self._create_message_item(text_message) self._screen.messages.scrollToBottom() - self._contacts_manager.get_curr_contact().append_message( - TextMessage(message, MESSAGE_AUTHOR['FRIEND'], t, message_type)) + self._contacts_manager.get_curr_contact().append_message(text_message) else: - friend = self.get_friend_by_number(friend_number) friend.inc_messages() - friend.append_message( - TextMessage(message, MESSAGE_AUTHOR['FRIEND'], t, message_type)) + friend.append_message(text_message) if not friend.visibility: self._contacts_manager.update_filtration() @@ -67,7 +67,7 @@ class Messenger(util.ToxSave): text = text[4:] else: message_type = TOX_MESSAGE_TYPE['NORMAL'] - friend = self.get_friend_by_number(friend_number) + 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: @@ -76,13 +76,30 @@ class Messenger(util.ToxSave): friend.inc_receipts() else: message_id = 0 - message = TextMessage(message_id, text, MESSAGE_AUTHOR['NOT_SENT'], t, message_type) + message_author = MessageAuthor(self._profile.name, MESSAGE_AUTHOR['NOT_SENT']) + message = TextMessage(message_id, text, message_author, t, message_type) friend.append_message(message) if self._contacts_manager.is_friend_active(friend_number): 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: + tox_messages = self._split_message(message.text) + for tox_message in tox_messages: + self._tox.friend_send_message(friend_number, message.message_type, tox_message) + friend.inc_receipts() + except Exception as ex: + util.log('Sending pending messages failed with ' + str(ex)) + # ----------------------------------------------------------------------------------------------------------------- # Typing notifications # ----------------------------------------------------------------------------------------------------------------- @@ -131,5 +148,5 @@ class Messenger(util.ToxSave): return messages - def get_friend_by_number(self, friend_number): + def _get_friend_by_number(self, friend_number): return self._contacts_provider.get_friend_by_number(friend_number) diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 69a7f68..2d09c82 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -55,20 +55,20 @@ def friend_status(contacts_manager, file_transfer_handler, profile, settings): return wrapped -def friend_connection_status(profile, settings, plugin_loader): +def friend_connection_status(contacts_manager, profile, settings, plugin_loader, file_transfer_handler): 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 = profile.get_friend_by_number(friend_number) + friend = contacts_manager.get_friend_by_number(friend_number) if new_status == TOX_CONNECTION['NONE']: invoke_in_main_thread(profile.friend_exit, friend_number) - invoke_in_main_thread(profile.update_filtration) + invoke_in_main_thread(contacts_manager.update_filtration) 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(profile.send_avatar, friend_number) + invoke_in_main_thread(file_transfer_handler.send_avatar, friend_number) invoke_in_main_thread(plugin_loader.friend_online, friend_number) return wrapped @@ -133,9 +133,9 @@ def friend_request(contacts_manager): return wrapped -def friend_typing(contacts_manager): +def friend_typing(messenger): def wrapped(tox, friend_number, typing, user_data): - invoke_in_main_thread(contacts_manager.friend_typing, friend_number, typing) + invoke_in_main_thread(messenger.friend_typing, friend_number, typing) return wrapped @@ -392,11 +392,12 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, # friend callbacks tox.callback_friend_status(friend_status(contacts_manager, file_transfer_handler, profile, settings), 0) tox.callback_friend_message(friend_message(messenger, contacts_manager, profile, settings, main_window, tray), 0) - tox.callback_friend_connection_status(friend_connection_status(profile, settings, plugin_loader), 0) + tox.callback_friend_connection_status(friend_connection_status(contacts_manager, profile, settings, plugin_loader, + file_transfer_handler), 0) tox.callback_friend_name(friend_name(contacts_manager), 0) tox.callback_friend_status_message(friend_status_message(contacts_manager, messenger), 0) tox.callback_friend_request(friend_request(contacts_manager), 0) - tox.callback_friend_typing(friend_typing(contacts_manager), 0) + tox.callback_friend_typing(friend_typing(messenger), 0) tox.callback_friend_read_receipt(friend_read_receipt(contacts_manager), 0) # file transfer diff --git a/toxygen/network/tox_dns.py b/toxygen/network/tox_dns.py index d3d48a1..1a525bc 100644 --- a/toxygen/network/tox_dns.py +++ b/toxygen/network/tox_dns.py @@ -1,59 +1,65 @@ import json import urllib.request -from util.util import log -from user_data import settings +import util.util as util 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')) +class ToxDns: - 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)) + def __init__(self, settings): + self._settings = settings - return None # error + @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')) -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() + 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 diff --git a/toxygen/ui/av_widgets.py b/toxygen/ui/av_widgets.py index 676df78..6fe3f13 100644 --- a/toxygen/ui/av_widgets.py +++ b/toxygen/ui/av_widgets.py @@ -5,7 +5,7 @@ import util import pyaudio import wave from user_data import settings -from util.util import curr_directory +from util.util import * class IncomingCallWidget(widgets.CenteredWidget): diff --git a/toxygen/ui/items_factory.py b/toxygen/ui/items_factory.py index 10ca02b..fe3e4e4 100644 --- a/toxygen/ui/items_factory.py +++ b/toxygen/ui/items_factory.py @@ -1,6 +1,8 @@ from ui.list_items import * from ui.messages_widgets import * +# rename methods + class ItemsFactory: @@ -9,6 +11,11 @@ class ItemsFactory: self._smiley_loader = smiley_loader self._messages = main_screen.messages self._friends_list = main_screen.friends_list + self._message_edit = main_screen.messageEdit + + 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) def friend_item(self): item = ContactItem(self._settings) @@ -18,8 +25,8 @@ class ItemsFactory: self._friends_list.setItemWidget(elem, item) return item - def message_item(self, message, pixmap=None): - item = MessageItem(message, self._messages) + def message_item(self, message, append=True, pixmap=None): + item = MessageItem(self._settings, self._create_message_browser, message, self._messages) if pixmap is not None: item.set_avatar(pixmap) elem = QtWidgets.QListWidgetItem() diff --git a/toxygen/ui/list_items.py b/toxygen/ui/list_items.py index a947919..84ba1b4 100644 --- a/toxygen/ui/list_items.py +++ b/toxygen/ui/list_items.py @@ -2,7 +2,7 @@ from wrapper.toxcore_enums_and_consts import * from PyQt5 import QtCore, QtGui, QtWidgets from contacts import profile from file_transfers.file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR -from util.util import curr_directory, convert_time, curr_time +from util.util import * from ui.widgets import DataLabel, create_menu import html as h import smileys diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index b694574..b4fc083 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -1,7 +1,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets from user_data.settings import * from contacts.profile import Profile -from util.util import curr_directory, copy, get_stickers_directory, join_path +from util.util import * from ui.widgets import CenteredWidget, DataLabel, LineEdit, RubberBandWindow import pyaudio from user_data import toxes diff --git a/toxygen/ui/messages_widgets.py b/toxygen/ui/messages_widgets.py index 0d65c8a..fbd3154 100644 --- a/toxygen/ui/messages_widgets.py +++ b/toxygen/ui/messages_widgets.py @@ -6,9 +6,10 @@ import util.util as util import ui.menu as menu import html as h import re +from messenger.messages import MESSAGE_AUTHOR -class MessageEdit(QtWidgets.QTextBrowser): +class MessageBrowser(QtWidgets.QTextBrowser): def __init__(self, settings, message_edit, smileys_loader, plugin_loader, text, width, message_type, parent=None): super().__init__(parent) @@ -122,7 +123,7 @@ class MessageItem(QtWidgets.QWidget): """ Message in messages list """ - def __init__(self, settings, text_message, parent=None): + def __init__(self, settings, message_browser_factory_method, text_message, parent=None): QtWidgets.QWidget.__init__(self, parent) self.name = widgets.DataLabel(self) self.name.setGeometry(QtCore.QRect(2, 2, 95, 23)) @@ -132,25 +133,26 @@ class MessageItem(QtWidgets.QWidget): font.setPointSize(11) font.setBold(True) self.name.setFont(font) - self.name.setText(text_message.user) + self.name.setText(text_message.author.name) self.time = QtWidgets.QLabel(self) self.time.setGeometry(QtCore.QRect(parent.width() - 60, 0, 50, 25)) font.setPointSize(10) font.setBold(False) self.time.setFont(font) - self._time = time - if not sent: + self._time = text_message.time + if text_message.author.type == MESSAGE_AUTHOR['NOT_SENT']: movie = QtGui.QMovie(util.join_path(util.get_images_directory(), 'spinner.gif')) self.time.setMovie(movie) movie.start() self.t = True else: - self.time.setText(util.convert_time(time)) + self.time.setText(util.convert_time(text_message.time)) self.t = False - self.message = MessageEdit(text, parent.width() - 160, message_type, self) - if message_type != TOX_MESSAGE_TYPE['NORMAL']: + self.message = message_browser_factory_method(text_message.text, parent.width() - 160, + text_message.type, self) + if text_message.type != TOX_MESSAGE_TYPE['NORMAL']: self.name.setStyleSheet("QLabel { color: #5CB3FF; }") self.message.setAlignment(QtCore.Qt.AlignCenter) self.time.setStyleSheet("QLabel { color: #5CB3FF; }") diff --git a/toxygen/ui/tray.py b/toxygen/ui/tray.py index ebdbd33..859eade 100644 --- a/toxygen/ui/tray.py +++ b/toxygen/ui/tray.py @@ -1,6 +1,6 @@ from PyQt5 import QtWidgets, QtGui, QtCore from util.ui import tr -from util.util import get_images_directory +from util.util import * import os.path diff --git a/toxygen/user_data/settings.py b/toxygen/user_data/settings.py index 09fe373..8952956 100644 --- a/toxygen/user_data/settings.py +++ b/toxygen/user_data/settings.py @@ -1,6 +1,6 @@ import json import os -from util.util import log, get_base_directory, get_platform, join_path +from util.util import * import pyaudio import smileys.smileys as smileys From ae903cf405556161b959a12f7aebcf0f58a50b00 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 5 May 2018 00:09:33 +0300 Subject: [PATCH 018/138] statuses fixed. events added. --- toxygen/app.py | 14 +++--- toxygen/common/__init__.py | 0 toxygen/common/event.py | 22 ++++++++++ toxygen/contacts/basecontact.py | 43 +++++++++++++++---- toxygen/contacts/contacts_manager.py | 42 ++++++++++++------ .../file_transfers/file_transfers_handler.py | 1 - toxygen/messenger/messenger.py | 9 ++++ toxygen/middleware/callbacks.py | 10 ++--- toxygen/ui/items_factory.py | 8 ++-- toxygen/ui/list_items.py | 6 ++- toxygen/ui/main_screen.py | 2 +- toxygen/ui/menu.py | 7 +-- toxygen/ui/widgets_factory.py | 7 +-- toxygen/util/util.py | 2 + 14 files changed, 126 insertions(+), 47 deletions(-) create mode 100644 toxygen/common/__init__.py create mode 100644 toxygen/common/event.py diff --git a/toxygen/app.py b/toxygen/app.py index 87d3c60..7215a7a 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -27,6 +27,7 @@ from ui.widgets_factory import WidgetsFactory from smileys.smileys import SmileyLoader from ui.items_factory import ItemsFactory from messenger.messenger import Messenger +from network.tox_dns import ToxDns class App: @@ -36,7 +37,7 @@ class App: 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._friend_factory = self._calls_manager = self._contacts_manager = self._smiley_loader = self._tox_dns = None if uri is not None and uri.startswith('tox:'): self._uri = uri[4:] self._path = path_to_profile @@ -114,9 +115,6 @@ class App: while True: try: self._app.exec_() - except KeyboardInterrupt: - print('Closing Toxygen...') - break except Exception as ex: util.log('Unhandled exception: ' + str(ex)) else: @@ -288,6 +286,7 @@ class App: def _create_dependencies(self): self._smiley_loader = SmileyLoader(self._settings) + self._tox_dns = ToxDns(self._settings) self._ms = MainWindow(self._settings, self._tray) self._calls_manager = CallsManager(self._tox.AV, self._settings) db = Database(self._path.replace('.tox', '.db'), self._toxes) @@ -298,10 +297,11 @@ class App: self._friend_factory = FriendFactory(self._profile_manager, self._settings, self._tox, db, items_factory) self._contacts_provider = ContactProvider(self._tox, self._friend_factory) self._contacts_manager = ContactsManager(self._tox, self._settings, self._ms, self._profile_manager, - self._contacts_provider, db) + self._contacts_provider, db, self._tox_dns) self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider) - widgets_factory = WidgetsFactory(self._settings, profile, self._contacts_manager, self._file_transfer_handler, - self._smiley_loader, self._plugin_loader, self._toxes, self._version) + widgets_factory = WidgetsFactory(self._settings, profile, self._profile_manager, self._contacts_manager, + self._file_transfer_handler, self._smiley_loader, self._plugin_loader, + self._toxes, self._version) self._messenger = Messenger(self._tox, self._plugin_loader, self._ms, self._contacts_manager, self._contacts_provider, items_factory, profile) self._tray = tray.init_tray(profile, self._settings, self._ms) diff --git a/toxygen/common/__init__.py b/toxygen/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/common/event.py b/toxygen/common/event.py new file mode 100644 index 0000000..75c29a5 --- /dev/null +++ b/toxygen/common/event.py @@ -0,0 +1,22 @@ + + +class Event: + + def __init__(self): + self._callbacks = set() + + def __iadd__(self, callback): + self._callbacks.add(callback) + + def __isub__(self, callback): + self.remove_callback(callback) + + 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) diff --git a/toxygen/contacts/basecontact.py b/toxygen/contacts/basecontact.py index 71690dd..fd084d7 100644 --- a/toxygen/contacts/basecontact.py +++ b/toxygen/contacts/basecontact.py @@ -2,6 +2,7 @@ from user_data.settings import * from PyQt5 import QtCore, QtGui from wrapper.toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE import util.util as util +import common.event as event class BaseContact: @@ -24,6 +25,9 @@ class BaseContact: self._status, self._widget = None, widget self._tox_id = tox_id self.init_widget() + self._name_changed_event = event.Event() + self._status_message_changed_event = event.Event() + self._status_changed_event = event.Event() # ----------------------------------------------------------------------------------------------------------------- # Name - current name or alias of user @@ -33,12 +37,20 @@ class BaseContact: return self._name def set_name(self, value): - self._name = str(value, 'utf-8') - self._widget.name.setText(self._name) - self._widget.name.repaint() + value = str(value, 'utf-8') + if self._name != value: + self._name = value + self._widget.name.setText(self._name) + self._widget.name.repaint() + self._name_changed_event(self._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 # ----------------------------------------------------------------------------------------------------------------- @@ -47,12 +59,20 @@ class BaseContact: return self._status_message def set_status_message(self, value): - self._status_message = str(value, 'utf-8') - self._widget.status_message.setText(self._status_message) - self._widget.status_message.repaint() + value = str(value, 'utf-8') + if self._status_message != value: + self._status_message = value + self._widget.status_message.setText(self._status_message) + self._widget.status_message.repaint() + self._status_message_changed_event(self._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 # ----------------------------------------------------------------------------------------------------------------- @@ -61,11 +81,18 @@ class BaseContact: return self._status def set_status(self, value): - self._status = value - self._widget.connection_status.update(value) + if self._status != value: + self._status = value + self._widget.connection_status.update(value) + self._status_changed_event(self._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 # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 8fb1b92..818ae88 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -12,12 +12,13 @@ class ContactsManager: Represents contacts list. """ - def __init__(self, tox, settings, screen, profile_manager, contact_provider, db): + def __init__(self, tox, settings, screen, profile_manager, contact_provider, db, tox_dns): self._tox = tox self._settings = settings self._screen = screen self._profile_manager = profile_manager self._contact_provider = contact_provider + self._tox_dns = tox_dns self._messages = screen.messages self._contacts, self._active_contact = [], -1 self._sorting = settings['sorting'] @@ -74,13 +75,17 @@ class ContactsManager: try: # self.send_typing(False) # TODO: fix self._screen.typing.setVisible(False) + current_contact = self.get_curr_contact() + if current_contact is not None: + self._unsubscribe_from_events(current_contact) if value is not None: if self._active_contact + 1 and self._active_contact != value: try: - self.get_curr_contact().curr_text = self._screen.messageEdit.toPlainText() + current_contact.curr_text = self._screen.messageEdit.toPlainText() except: pass friend = self._contacts[value] + self._subscribe_to_events(friend) friend.remove_invalid_unsent_files() if self._active_contact != value: self._screen.messageEdit.setPlainText(friend.curr_text) @@ -133,8 +138,7 @@ class ContactsManager: else: friend = self.get_curr_contact() # TODO: to separate method - self._screen.account_name.setText(friend.name) - self._screen.account_status.setText(friend.status_message) + self._screen.account_status.setToolTip(friend.get_full_status()) avatar_path = friend.get_avatar_path() pixmap = QtGui.QPixmap(avatar_path) @@ -244,14 +248,15 @@ class ContactsManager: friend.set_name(name) name = str(name, 'utf-8') if friend.name == name and tmp != name: + # TODO: move to friend? message = util_ui.tr('User {} is now known as {}') - message = message.format(tmp, name) - friend.append_message(InfoMessage(0, message, util.get_unix_time())) - friend.actions = True - if number == self.get_active_number(): - self.create_message_item(message, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) - self._messages.scrollToBottom() - self.set_active(None) + # message = message.format(tmp, name) + # friend.append_message(InfoMessage(0, message, util.get_unix_time())) + # friend.actions = True + # if number == self.get_active_number(): + # self.create_message_item(message, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) + # self._messages.scrollToBottom() + # self.set_active(None) # ----------------------------------------------------------------------------------------------------------------- # Work with friends (remove, block, set alias, get public key) @@ -369,7 +374,7 @@ class ContactsManager: try: message = message or 'Hello! Add me to your contact list please' if '@' in tox_id: # value like groupbot@toxme.io - tox_id = tox_dns(tox_id) + 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 @@ -428,3 +433,16 @@ class ContactsManager: 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) + + def _unsubscribe_from_events(self, contact): + contact.name_changed_event.remove_callback(self._current_contact_name_changed) + + def _current_contact_name_changed(self, name): + self._screen.account_name.setText(name) diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index 12bc60d..5d9b052 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -1,7 +1,6 @@ from file_transfers.file_transfers import * from messenger.messages import * from history.database import MESSAGE_AUTHOR -import os from ui.list_items import * from PyQt5 import QtWidgets import util.util as util diff --git a/toxygen/messenger/messenger.py b/toxygen/messenger/messenger.py index cb17398..1021629 100644 --- a/toxygen/messenger/messenger.py +++ b/toxygen/messenger/messenger.py @@ -3,6 +3,8 @@ from wrapper.toxcore_enums_and_consts import * from messenger.messages import * +# TODO: sub profile name changed event? + class Messenger(util.ToxSave): def __init__(self, tox, plugin_loader, screen, contacts_manager, contacts_provider, items_factory, profile): @@ -100,6 +102,13 @@ class Messenger(util.ToxSave): except Exception as ex: util.log('Sending pending messages failed with ' + str(ex)) + # ----------------------------------------------------------------------------------------------------------------- + # Message receipts + # ----------------------------------------------------------------------------------------------------------------- + + def receipt(self, friend_number, message_id): + pass # TODO: process + # ----------------------------------------------------------------------------------------------------------------- # Typing notifications # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 2d09c82..912696b 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -12,7 +12,7 @@ from middleware.threads import invoke_in_main_thread, execute from notifications.tray import tray_notification from notifications.sound import * -# TODO: gc callbacks and refactoring +# TODO: gc callbacks and refactoring. Use contact provider instead of manager # ----------------------------------------------------------------------------------------------------------------- # Callbacks - current user @@ -140,11 +140,9 @@ def friend_typing(messenger): return wrapped -def friend_read_receipt(contacts_manager): +def friend_read_receipt(messenger): def wrapped(tox, friend_number, message_id, user_data): - contacts_manager.get_friend_by_number(friend_number).dec_receipt() - if friend_number == contacts_manager.get_active_number(): - invoke_in_main_thread(contacts_manager.receipt) + invoke_in_main_thread(messenger.receipt, friend_number, message_id) return wrapped @@ -398,7 +396,7 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, tox.callback_friend_status_message(friend_status_message(contacts_manager, messenger), 0) tox.callback_friend_request(friend_request(contacts_manager), 0) tox.callback_friend_typing(friend_typing(messenger), 0) - tox.callback_friend_read_receipt(friend_read_receipt(contacts_manager), 0) + tox.callback_friend_read_receipt(friend_read_receipt(messenger), 0) # file transfer tox.callback_file_recv(tox_file_recv(main_window, tray, profile, file_transfer_handler, diff --git a/toxygen/ui/items_factory.py b/toxygen/ui/items_factory.py index fe3e4e4..1c4e7a9 100644 --- a/toxygen/ui/items_factory.py +++ b/toxygen/ui/items_factory.py @@ -13,10 +13,6 @@ class ItemsFactory: self._friends_list = main_screen.friends_list self._message_edit = main_screen.messageEdit - 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) - def friend_item(self): item = ContactItem(self._settings) elem = QtWidgets.QListWidgetItem(self._friends_list) @@ -75,3 +71,7 @@ class ItemsFactory: self._messages.insertItem(0, elem) self._messages.setItemWidget(elem, item) return item + + 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) diff --git a/toxygen/ui/list_items.py b/toxygen/ui/list_items.py index 84ba1b4..d075391 100644 --- a/toxygen/ui/list_items.py +++ b/toxygen/ui/list_items.py @@ -71,7 +71,7 @@ class StatusCircle(QtWidgets.QWidget): 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)) + pixmap = QtGui.QPixmap(join_path(get_images_directory(), '{}.png'.format(name))) self.label.setPixmap(pixmap) @@ -121,6 +121,7 @@ class FileTransferItem(QtWidgets.QListWidget): self.name.setGeometry(QtCore.QRect(3, 7, 95, 25)) self.name.setTextFormat(QtCore.Qt.PlainText) font = QtGui.QFont() + # FIXME font.setFamily(settings.Settings.get_instance()['font']) font.setPointSize(11) font.setBold(True) @@ -281,7 +282,7 @@ class UnsentFileItem(FileTransferItem): TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'], width, parent) self._time = time self.pb.setVisible(False) - movie = QtGui.QMovie(curr_directory() + '/images/spinner.gif') + movie = QtGui.QMovie(join_path(get_images_directory(), 'spinner.gif')) self.time.setMovie(movie) movie.start() @@ -331,6 +332,7 @@ class InlineImageItem(QtWidgets.QScrollArea): self._full_size = not self._full_size self._elem.setSizeHint(QtCore.QSize(self.width(), self.height())) elif event.button() == QtCore.Qt.RightButton: # save inline + # TODO: dialog directory = QtWidgets.QFileDialog.getExistingDirectory(self, QtWidgets.QApplication.translate("MainWindow", 'Choose folder'), diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 05007f9..412f487 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -444,7 +444,7 @@ class MainWindow(QtWidgets.QMainWindow): def create_gc(self): self.profile.create_group_chat() - def profile_settings(self): + def profile_settings(self, *args): self._modal_window = self._widget_factory.create_profile_settings_window() self._modal_window.show() diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index b4fc083..33f0690 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -84,9 +84,10 @@ class AddContact(CenteredWidget): class ProfileSettings(CenteredWidget): """Form with profile settings such as name, status, TOX ID""" - def __init__(self, profile, settings, toxes): + 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.initUI() @@ -166,7 +167,7 @@ class ProfileSettings(CenteredWidget): self.default = QtWidgets.QPushButton(self) self.default.setGeometry(QtCore.QRect(40, 550, 620, 30)) auto_profile = Settings.get_auto_profile() - self.auto = path + name == ProfileManager.get_path() + Settings.get_instance().name + # self.auto = path + name == ProfileManager.get_path() + Settings.get_instance().name self.default.clicked.connect(self.auto_profile) self.retranslateUi() if self._profile.status is not None: @@ -243,7 +244,7 @@ class ProfileSettings(CenteredWidget): self.copy_pk.setIconSize(QtCore.QSize(10, 10)) def new_no_spam(self): - self.tox_id.setText(Profile.get_instance().new_nospam()) + self.tox_id.setText(self._profile.new_nospam()) def reset_avatar(self): self._profile.reset_avatar() diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py index 1bba4a7..0c0bcbc 100644 --- a/toxygen/ui/widgets_factory.py +++ b/toxygen/ui/widgets_factory.py @@ -4,10 +4,11 @@ from ui.menu import * class WidgetsFactory: - def __init__(self, settings, profile, contacts_manager, file_transfer_handler, smiley_loader, plugin_loader, - toxes, version): + def __init__(self, settings, profile, profile_manager, contacts_manager, file_transfer_handler, smiley_loader, + plugin_loader, toxes, version): self._settings = settings self._profile = profile + self._profile_manager = profile_manager self._contacts_manager = contacts_manager self._file_transfer_handler = file_transfer_handler self._smiley_loader = smiley_loader @@ -25,7 +26,7 @@ class WidgetsFactory: return WelcomeScreen(self._settings) def create_profile_settings_window(self): - return ProfileSettings(self._profile, self._settings, self._toxes) + return ProfileSettings(self._profile, self._profile_manager, self._settings, self._toxes) def create_network_settings_window(self): return NetworkSettings(self._settings, self._profile.reset) diff --git a/toxygen/util/util.py b/toxygen/util/util.py index 09e6877..329285c 100644 --- a/toxygen/util/util.py +++ b/toxygen/util/util.py @@ -158,6 +158,8 @@ def get_platform(): return platform.system() +# TODO: to common + class ToxSave: def __init__(self, tox): From 729bd84d2baaaee371e948bd0a9b12148db5564d Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 10 May 2018 20:47:34 +0300 Subject: [PATCH 019/138] utils refactoring --- tests/tests.py | 2 +- toxygen/app.py | 6 ++--- toxygen/bootstrap/bootstrap.py | 2 +- toxygen/common/event.py | 2 +- toxygen/common/tox_save.py | 9 ++++++++ toxygen/contacts/basecontact.py | 21 +++++++++++------ toxygen/contacts/contact.py | 2 +- toxygen/contacts/contact_provider.py | 7 +++--- toxygen/contacts/contacts_manager.py | 23 ++++++++++++++++--- toxygen/contacts/group_chat.py | 2 +- toxygen/contacts/profile.py | 2 +- .../file_transfers/file_transfers_handler.py | 2 +- toxygen/history/database.py | 2 +- toxygen/history/history_logs_generators.py | 2 +- toxygen/main.py | 2 +- toxygen/messenger/messenger.py | 5 ++-- toxygen/middleware/callbacks.py | 4 ++-- toxygen/middleware/threads.py | 2 +- toxygen/network/tox_dns.py | 2 +- toxygen/notifications/sound.py | 4 ++-- toxygen/plugin_support/plugin_support.py | 2 +- toxygen/smileys/smileys.py | 2 +- toxygen/stickers/stickers.py | 2 +- toxygen/ui/av_widgets.py | 10 ++++---- toxygen/ui/create_profile_screen.py | 4 ++-- toxygen/ui/list_items.py | 2 +- toxygen/ui/main_screen.py | 4 ++-- toxygen/ui/main_screen_widgets.py | 4 ++-- toxygen/ui/menu.py | 4 ++-- toxygen/ui/messages_widgets.py | 4 ++-- toxygen/ui/tray.py | 4 ++-- toxygen/ui/widgets.py | 2 +- toxygen/updater/updater.py | 5 ++-- toxygen/user_data/profile_manager.py | 2 +- toxygen/user_data/settings.py | 2 +- toxygen/{util => utils}/__init__.py | 0 toxygen/{util => utils}/ui.py | 2 +- toxygen/{util => utils}/util.py | 11 --------- toxygen/wrapper/libtox.py | 2 +- 39 files changed, 97 insertions(+), 74 deletions(-) create mode 100644 toxygen/common/tox_save.py rename toxygen/{util => utils}/__init__.py (100%) rename toxygen/{util => utils}/ui.py (98%) rename toxygen/{util => utils}/util.py (95%) diff --git a/tests/tests.py b/tests/tests.py index b61553e..1047de4 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -4,7 +4,7 @@ from db.database import History from toxygen.smileys import SmileyLoader from messenger.messages import * import user_data.toxes as encr -import toxygen.util as util +import toxygen.utils as util import time diff --git a/toxygen/app.py b/toxygen/app.py index 7215a7a..d5fd513 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -2,7 +2,7 @@ from middleware import threads import middleware.callbacks as callbacks from PyQt5 import QtWidgets, QtGui, QtCore import ui.password_screen as password_screen -from util.util import * +from utils.util import * import updater.updater as updater import os from middleware.tox_factory import tox_factory @@ -14,8 +14,8 @@ 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 util.ui as util_ui -import util.util as util +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 diff --git a/toxygen/bootstrap/bootstrap.py b/toxygen/bootstrap/bootstrap.py index 2e5d3dc..6c34e0e 100644 --- a/toxygen/bootstrap/bootstrap.py +++ b/toxygen/bootstrap/bootstrap.py @@ -1,6 +1,6 @@ import random import urllib.request -from util.util import * +from utils.util import * from PyQt5 import QtNetwork, QtCore import json diff --git a/toxygen/common/event.py b/toxygen/common/event.py index 75c29a5..e3ecbf9 100644 --- a/toxygen/common/event.py +++ b/toxygen/common/event.py @@ -6,7 +6,7 @@ class Event: self._callbacks = set() def __iadd__(self, callback): - self._callbacks.add(callback) + self.add_callback(callback) def __isub__(self, callback): self.remove_callback(callback) diff --git a/toxygen/common/tox_save.py b/toxygen/common/tox_save.py new file mode 100644 index 0000000..5d4cee0 --- /dev/null +++ b/toxygen/common/tox_save.py @@ -0,0 +1,9 @@ + + +class ToxSave: + + def __init__(self, tox): + self._tox = tox + + def set_tox(self, tox): + self._tox = tox diff --git a/toxygen/contacts/basecontact.py b/toxygen/contacts/basecontact.py index fd084d7..44a18b3 100644 --- a/toxygen/contacts/basecontact.py +++ b/toxygen/contacts/basecontact.py @@ -1,7 +1,7 @@ from user_data.settings import * from PyQt5 import QtCore, QtGui from wrapper.toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE -import util.util as util +import utils.util as util import common.event as event @@ -24,10 +24,11 @@ class BaseContact: self._name, self._status_message = name, status_message self._status, self._widget = None, widget self._tox_id = tox_id - self.init_widget() 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() # ----------------------------------------------------------------------------------------------------------------- # Name - current name or alias of user @@ -116,6 +117,7 @@ class BaseContact: self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)) self._widget.avatar_label.repaint() + self._avatar_changed_event(avatar_path) def reset_avatar(self): avatar_path = self.get_avatar_path() @@ -143,13 +145,18 @@ class BaseContact: @staticmethod def get_default_avatar_name(): return 'avatar.png' + + def get_avatar_changed_event(self): + return self._avatar_changed_event + + avatar_changed_event = property(get_avatar_changed_event) + # ----------------------------------------------------------------------------------------------------------------- # Widgets # ----------------------------------------------------------------------------------------------------------------- def init_widget(self): - if self._widget is not None: - self._widget.name.setText(self._name) - self._widget.status_message.setText(self._status_message) - self._widget.connection_status.update(self._status) - self.load_avatar() + self._widget.name.setText(self._name) + self._widget.status_message.setText(self._status_message) + self._widget.connection_status.update(self._status) + self.load_avatar() diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index c9274ac..43f7728 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -1,6 +1,6 @@ from history.database import * from contacts import basecontact, common -import util.util as util +import utils.util as util from messenger.messages import * from file_transfers import file_transfers as ft import re diff --git a/toxygen/contacts/contact_provider.py b/toxygen/contacts/contact_provider.py index 82cf6e7..5042f97 100644 --- a/toxygen/contacts/contact_provider.py +++ b/toxygen/contacts/contact_provider.py @@ -1,7 +1,8 @@ -import util.util as util +import utils.util as util +import common.tox_save as tox_save -class ContactProvider(util.ToxSave): +class ContactProvider(tox_save.ToxSave): def __init__(self, tox, friend_factory): super().__init__(tox) @@ -50,7 +51,7 @@ class ContactProvider(util.ToxSave): # ----------------------------------------------------------------------------------------------------------------- def get_all(self): - return self.get_all_friends() + self.get_all_gc() + return self.get_all_friends() + self.get_all_groups() # ----------------------------------------------------------------------------------------------------------------- # Caching diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 818ae88..464d448 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -1,8 +1,7 @@ -import util.util as util -import util.ui as util_ui +import utils.util as util +import utils.ui as util_ui from contacts.friend import Friend from PyQt5 import QtCore, QtGui -from messenger.messages import * from wrapper.toxcore_enums_and_consts import * from history.history_loader import HistoryLoader @@ -440,9 +439,27 @@ class ContactsManager: 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): + width = self._screen.account_avatar.width() + pixmap = QtGui.QPixmap(avatar_path) + self._screen.account_avatar.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation)) diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index b247281..5121be7 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -1,5 +1,5 @@ from contacts import contact -import util.util as util +import utils.util as util from PyQt5 import QtGui, QtCore from wrapper import toxcore_enums_and_consts as constants diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index cfeae94..8102f56 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -5,7 +5,7 @@ from file_transfers.file_transfers import * import time from contacts import basecontact from contacts.group_chat import * -import util.ui as util_ui +import utils.ui as util_ui class Profile(basecontact.BaseContact): diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index 5d9b052..c4ab8b0 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -3,7 +3,7 @@ from messenger.messages import * from history.database import MESSAGE_AUTHOR from ui.list_items import * from PyQt5 import QtWidgets -import util.util as util +import utils.util as util class FileTransfersHandler: diff --git a/toxygen/history/database.py b/toxygen/history/database.py index 188e85b..bf593d8 100644 --- a/toxygen/history/database.py +++ b/toxygen/history/database.py @@ -1,6 +1,6 @@ from sqlite3 import connect import os.path -import util.util as util +import utils.util as util TIMEOUT = 11 diff --git a/toxygen/history/history_logs_generators.py b/toxygen/history/history_logs_generators.py index 3e46562..b8d0a56 100644 --- a/toxygen/history/history_logs_generators.py +++ b/toxygen/history/history_logs_generators.py @@ -1,5 +1,5 @@ from messenger.messages import * -import util.util as util +import utils.util as util class HistoryLogsGenerator: diff --git a/toxygen/main.py b/toxygen/main.py index 5ecbd77..232ad93 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -1,6 +1,6 @@ import app from user_data.settings import * -import util.util as util +import utils.util as util import argparse diff --git a/toxygen/messenger/messenger.py b/toxygen/messenger/messenger.py index 1021629..5a94f7f 100644 --- a/toxygen/messenger/messenger.py +++ b/toxygen/messenger/messenger.py @@ -1,11 +1,12 @@ -import util.util as util +import utils.util as util +import common.tox_save as tox_save from wrapper.toxcore_enums_and_consts import * from messenger.messages import * # TODO: sub profile name changed event? -class Messenger(util.ToxSave): +class Messenger(tox_save.ToxSave): def __init__(self, tox, plugin_loader, screen, contacts_manager, contacts_provider, items_factory, profile): super().__init__(tox) diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 912696b..7b7b759 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -4,8 +4,8 @@ from contacts.profile import Profile from wrapper.toxcore_enums_and_consts import * from wrapper.toxav_enums import * from wrapper.tox import bin_to_string -import util.ui as util_ui -import util.util as util +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 diff --git a/toxygen/middleware/threads.py b/toxygen/middleware/threads.py index e654a66..2e432b6 100644 --- a/toxygen/middleware/threads.py +++ b/toxygen/middleware/threads.py @@ -1,7 +1,7 @@ from bootstrap.bootstrap import * import threading import queue -from util import util +from utils import util import time diff --git a/toxygen/network/tox_dns.py b/toxygen/network/tox_dns.py index 1a525bc..02e97f5 100644 --- a/toxygen/network/tox_dns.py +++ b/toxygen/network/tox_dns.py @@ -1,6 +1,6 @@ import json import urllib.request -import util.util as util +import utils.util as util from PyQt5 import QtNetwork, QtCore diff --git a/toxygen/notifications/sound.py b/toxygen/notifications/sound.py index a0b93f0..361cd05 100644 --- a/toxygen/notifications/sound.py +++ b/toxygen/notifications/sound.py @@ -1,4 +1,4 @@ -import util.util +import utils.util import wave import pyaudio import os.path @@ -51,4 +51,4 @@ def sound_notification(t): def get_file_path(file_name): - return os.path.join(util.util.get_sounds_directory(), file_name) + return os.path.join(utils.util.get_sounds_directory(), file_name) diff --git a/toxygen/plugin_support/plugin_support.py b/toxygen/plugin_support/plugin_support.py index 6f2af9c..bd50cb7 100644 --- a/toxygen/plugin_support/plugin_support.py +++ b/toxygen/plugin_support/plugin_support.py @@ -1,4 +1,4 @@ -import util.util as util +import utils.util as util from contacts import profile import os import importlib diff --git a/toxygen/smileys/smileys.py b/toxygen/smileys/smileys.py index fc40a69..9027bcb 100644 --- a/toxygen/smileys/smileys.py +++ b/toxygen/smileys/smileys.py @@ -1,4 +1,4 @@ -from util import util +from utils import util import json import os from collections import OrderedDict diff --git a/toxygen/stickers/stickers.py b/toxygen/stickers/stickers.py index 5ad6aa1..a406b6c 100644 --- a/toxygen/stickers/stickers.py +++ b/toxygen/stickers/stickers.py @@ -1,5 +1,5 @@ import os -import util.util as util +import utils.util as util def load_stickers(): diff --git a/toxygen/ui/av_widgets.py b/toxygen/ui/av_widgets.py index 6fe3f13..f9eb6ed 100644 --- a/toxygen/ui/av_widgets.py +++ b/toxygen/ui/av_widgets.py @@ -1,11 +1,11 @@ from PyQt5 import QtCore, QtGui, QtWidgets from ui import widgets from contacts import profile -import util +import utils import pyaudio import wave from user_data import settings -from util.util import * +from utils.util import * class IncomingCallWidget(widgets.CenteredWidget): @@ -34,13 +34,13 @@ class IncomingCallWidget(widgets.CenteredWidget): self.accept_video.setGeometry(QtCore.QRect(170, 100, 150, 150)) self.decline = QtWidgets.QPushButton(self) self.decline.setGeometry(QtCore.QRect(320, 100, 150, 150)) - pixmap = QtGui.QPixmap(util.curr_directory() + '/images/accept_audio.png') + pixmap = QtGui.QPixmap(utils.curr_directory() + '/images/accept_audio.png') icon = QtGui.QIcon(pixmap) self.accept_audio.setIcon(icon) - pixmap = QtGui.QPixmap(util.curr_directory() + '/images/accept_video.png') + pixmap = QtGui.QPixmap(utils.curr_directory() + '/images/accept_video.png') icon = QtGui.QIcon(pixmap) self.accept_video.setIcon(icon) - pixmap = QtGui.QPixmap(util.curr_directory() + '/images/decline_call.png') + pixmap = QtGui.QPixmap(utils.curr_directory() + '/images/decline_call.png') icon = QtGui.QIcon(pixmap) self.decline.setIcon(icon) self.accept_audio.setIconSize(QtCore.QSize(150, 150)) diff --git a/toxygen/ui/create_profile_screen.py b/toxygen/ui/create_profile_screen.py index f461719..40c9091 100644 --- a/toxygen/ui/create_profile_screen.py +++ b/toxygen/ui/create_profile_screen.py @@ -1,7 +1,7 @@ from ui.widgets import * from PyQt5 import uic -import util.util as util -import util.ui as util_ui +import utils.util as util +import utils.ui as util_ui class CreateProfileScreenResult: diff --git a/toxygen/ui/list_items.py b/toxygen/ui/list_items.py index d075391..a305ffc 100644 --- a/toxygen/ui/list_items.py +++ b/toxygen/ui/list_items.py @@ -2,7 +2,7 @@ from wrapper.toxcore_enums_and_consts import * from PyQt5 import QtCore, QtGui, QtWidgets from contacts import profile from file_transfers.file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR -from util.util import * +from utils.util import * from ui.widgets import DataLabel, create_menu import html as h import smileys diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 412f487..54ca652 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -2,8 +2,8 @@ from contacts.profile import * from ui.list_items import * from ui.widgets import MultilineEdit, ComboBox from ui.main_screen_widgets import * -import util.util as util -import util.ui as util_ui +import utils.util as util +import utils.ui as util_ui class MainWindow(QtWidgets.QMainWindow): diff --git a/toxygen/ui/main_screen_widgets.py b/toxygen/ui/main_screen_widgets.py index 665e5dc..e739167 100644 --- a/toxygen/ui/main_screen_widgets.py +++ b/toxygen/ui/main_screen_widgets.py @@ -2,8 +2,8 @@ from PyQt5 import QtCore, QtGui, QtWidgets from ui.widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit from contacts.profile import Profile import urllib -import util.util as util -import util.ui as util_ui +import utils.util as util +import utils.ui as util_ui from stickers.stickers import load_stickers diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index 33f0690..b28ab3b 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -1,12 +1,12 @@ from PyQt5 import QtCore, QtGui, QtWidgets from user_data.settings import * from contacts.profile import Profile -from util.util import * +from utils.util import * from ui.widgets import CenteredWidget, DataLabel, LineEdit, RubberBandWindow import pyaudio from user_data import toxes import updater.updater as updater -import util.ui as util_ui +import utils.ui as util_ui class AddContact(CenteredWidget): diff --git a/toxygen/ui/messages_widgets.py b/toxygen/ui/messages_widgets.py index fbd3154..0e31891 100644 --- a/toxygen/ui/messages_widgets.py +++ b/toxygen/ui/messages_widgets.py @@ -1,8 +1,8 @@ from PyQt5 import QtWidgets, QtGui, QtCore from wrapper.toxcore_enums_and_consts import * import ui.widgets as widgets -import util.ui as util_ui -import util.util as util +import utils.ui as util_ui +import utils.util as util import ui.menu as menu import html as h import re diff --git a/toxygen/ui/tray.py b/toxygen/ui/tray.py index 859eade..2003053 100644 --- a/toxygen/ui/tray.py +++ b/toxygen/ui/tray.py @@ -1,6 +1,6 @@ from PyQt5 import QtWidgets, QtGui, QtCore -from util.ui import tr -from util.util import * +from utils.ui import tr +from utils.util import * import os.path diff --git a/toxygen/ui/widgets.py b/toxygen/ui/widgets.py index 7585f06..ec6c3e4 100644 --- a/toxygen/ui/widgets.py +++ b/toxygen/ui/widgets.py @@ -1,5 +1,5 @@ from PyQt5 import QtCore, QtGui, QtWidgets -import util.ui as util_ui +import utils.ui as util_ui class DataLabel(QtWidgets.QLabel): diff --git a/toxygen/updater/updater.py b/toxygen/updater/updater.py index f274161..329353c 100644 --- a/toxygen/updater/updater.py +++ b/toxygen/updater/updater.py @@ -1,7 +1,6 @@ -import util.util as util -import util.ui as util_ui +import utils.util as util +import utils.ui as util_ui import os -from user_data import settings import platform import urllib from PyQt5 import QtNetwork, QtCore diff --git a/toxygen/user_data/profile_manager.py b/toxygen/user_data/profile_manager.py index 6107d43..a95253f 100644 --- a/toxygen/user_data/profile_manager.py +++ b/toxygen/user_data/profile_manager.py @@ -1,4 +1,4 @@ -import util.util as util +import utils.util as util import os from user_data.settings import Settings diff --git a/toxygen/user_data/settings.py b/toxygen/user_data/settings.py index 8952956..f9390f3 100644 --- a/toxygen/user_data/settings.py +++ b/toxygen/user_data/settings.py @@ -1,6 +1,6 @@ import json import os -from util.util import * +from utils.util import * import pyaudio import smileys.smileys as smileys diff --git a/toxygen/util/__init__.py b/toxygen/utils/__init__.py similarity index 100% rename from toxygen/util/__init__.py rename to toxygen/utils/__init__.py diff --git a/toxygen/util/ui.py b/toxygen/utils/ui.py similarity index 98% rename from toxygen/util/ui.py rename to toxygen/utils/ui.py index b2b5712..cdb5f9a 100644 --- a/toxygen/util/ui.py +++ b/toxygen/utils/ui.py @@ -1,5 +1,5 @@ from PyQt5 import QtWidgets -import util.util as util +import utils.util as util def tr(s): diff --git a/toxygen/util/util.py b/toxygen/utils/util.py similarity index 95% rename from toxygen/util/util.py rename to toxygen/utils/util.py index 329285c..5790a5b 100644 --- a/toxygen/util/util.py +++ b/toxygen/utils/util.py @@ -156,14 +156,3 @@ def is_re_valid(regex): def get_platform(): return platform.system() - - -# TODO: to common - -class ToxSave: - - def __init__(self, tox): - self._tox = tox - - def set_tox(self, tox): - self._tox = tox diff --git a/toxygen/wrapper/libtox.py b/toxygen/wrapper/libtox.py index a04746c..402aa5f 100644 --- a/toxygen/wrapper/libtox.py +++ b/toxygen/wrapper/libtox.py @@ -1,6 +1,6 @@ from platform import system from ctypes import CDLL -import util.util as util +import utils.util as util class LibToxCore: From 25dbb85ef06280343aa41bac0403593016a21d62 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 10 May 2018 23:54:51 +0300 Subject: [PATCH 020/138] various fixes. profile settings and add account fixes --- toxygen/app.py | 87 ++++++++++--------- toxygen/contacts/basecontact.py | 20 +++-- toxygen/file_transfers/file_transfers.py | 15 ++-- .../file_transfers/file_transfers_handler.py | 9 +- toxygen/ui/main_screen.py | 7 +- toxygen/ui/menu.py | 37 ++++---- toxygen/user_data/profile_manager.py | 3 + toxygen/user_data/settings.py | 23 ++--- 8 files changed, 109 insertions(+), 92 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index d5fd513..713d250 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -2,7 +2,6 @@ from middleware import threads import middleware.callbacks as callbacks from PyQt5 import QtWidgets, QtGui, QtCore import ui.password_screen as password_screen -from utils.util import * import updater.updater as updater import os from middleware.tox_factory import tox_factory @@ -53,48 +52,20 @@ class App: self._app = QtWidgets.QApplication([]) self._load_icon() - if get_platform() == 'Linux': + if util.get_platform() == 'Linux': QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) self._load_base_style() - 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 - if result.is_new_profile(): # create new profile - self._create_new_profile(result.profile_path) - else: # load existing profile - self._load_existing_profile(result.profile_path) - self._path = result.profile_path - else: # default profile - path, name = auto_profile - self._path = os.path.join(path, name + '.tox') - self._load_existing_profile(self._path) - - if Settings.is_active_profile(self._path): # profile is in use - profile_name = 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 - - self._settings.set_active_profile() - - self._load_app_styles() - self._load_app_translations() + 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() @@ -133,7 +104,7 @@ class App: # ----------------------------------------------------------------------------------------------------------------- def _load_base_style(self): - with open(join_path(get_styles_directory(), 'dark_style.qss')) as fl: + with open(util.join_path(util.get_styles_directory(), 'dark_style.qss')) as fl: style = fl.read() self._app.setStyleSheet(style) @@ -143,7 +114,7 @@ class App: return for theme in self._settings.built_in_themes().keys(): if self._settings['theme'] == theme: - with open(curr_directory(__file__) + self._settings.built_in_themes()[theme]) as fl: + with open(util.curr_directory(__file__) + self._settings.built_in_themes()[theme]) as fl: style = fl.read() self._app.setStyleSheet(style) @@ -152,12 +123,12 @@ class App: if current_language in supported_languages: lang_path = supported_languages[current_language] translator = QtCore.QTranslator() - translator.load(get_translations_directory() + lang_path) + 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(get_images_directory(), 'icon.png') + icon_file = os.path.join(util.get_images_directory(), 'icon.png') self._app.setWindowIcon(QtGui.QIcon(icon_file)) @staticmethod @@ -171,10 +142,44 @@ class App: def _load_app_translations(self): lang = Settings.supported_languages()[self._settings['language']] translator = QtCore.QTranslator() - translator.load(os.path.join(get_translations_directory(), lang)) + 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 + self._create_new_profile(result.profile_path) + 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 # ----------------------------------------------------------------------------------------------------------------- @@ -223,7 +228,7 @@ class App: self._tox = self._create_tox(data) def _create_new_profile(self, profile_path): - name = get_profile_name_from_path(profile_path) or 'toxygen_user' + name = util.get_profile_name_from_path(profile_path) or 'toxygen_user' if os.path.isfile(profile_path): util_ui.message_box(util_ui.tr('Profile with this name already exists'), util_ui.tr('Error')) diff --git a/toxygen/contacts/basecontact.py b/toxygen/contacts/basecontact.py index 44a18b3..037289c 100644 --- a/toxygen/contacts/basecontact.py +++ b/toxygen/contacts/basecontact.py @@ -135,16 +135,16 @@ class BaseContact: return self._widget.avatar_label.pixmap() def get_avatar_path(self): - directory = util.join_path(self._profile_manager.get_dir(), 'avatars') - avatar_path = util.join_path(directory, '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])) + 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 = util.join_path(util.get_images_directory(), self.get_default_avatar_name()) + avatar_path = util.join_path(util.get_images_directory(), self._get_default_avatar_name()) return avatar_path - @staticmethod - def get_default_avatar_name(): - return 'avatar.png' + 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 get_avatar_changed_event(self): return self._avatar_changed_event @@ -160,3 +160,11 @@ class BaseContact: self._widget.status_message.setText(self._status_message) self._widget.connection_status.update(self._status) self.load_avatar() + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + @staticmethod + def _get_default_avatar_name(): + return 'avatar.png' diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py index 937ab12..596642e 100644 --- a/toxygen/file_transfers/file_transfers.py +++ b/toxygen/file_transfers/file_transfers.py @@ -3,7 +3,6 @@ from os.path import basename, getsize, exists, dirname from os import remove, rename, chdir from time import time, sleep from wrapper.tox import Tox -from user_data import settings from PyQt5 import QtCore @@ -48,7 +47,7 @@ class FileTransfer(QtCore.QObject): """ def __init__(self, path, tox, friend_number, size, file_number=None): - super().__init__(self) + QtCore.QObject.__init__(self) self._path = path self._tox = tox self._friend_number = friend_number @@ -305,20 +304,20 @@ class ReceiveAvatar(ReceiveTransfer): """ MAX_AVATAR_SIZE = 512 * 1024 - def __init__(self, tox, friend_number, size, file_number): - path = settings.ProfileManager.get_path() + 'avatars/{}.png'.format(tox.friend_get_public_key(friend_number)) - super().__init__(path + '.tmp', tox, friend_number, size, file_number) + def __init__(self, path, tox, friend_number, size, file_number): + full_path = path + '.tmp' + super().__init__(full_path, tox, friend_number, size, file_number) if size > self.MAX_AVATAR_SIZE: self.send_control(TOX_FILE_CONTROL['CANCEL']) self._file.close() - remove(path + '.tmp') + remove(full_path) elif not size: self.send_control(TOX_FILE_CONTROL['CANCEL']) self._file.close() if exists(path): remove(path) self._file.close() - remove(path + '.tmp') + remove(full_path) elif exists(path): hash = self.get_file_id() with open(path, 'rb') as fl: @@ -327,7 +326,7 @@ class ReceiveAvatar(ReceiveTransfer): if hash == existing_hash: self.send_control(TOX_FILE_CONTROL['CANCEL']) self._file.close() - remove(path + '.tmp') + remove(full_path) else: self.send_control(TOX_FILE_CONTROL['RESUME']) else: diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index c4ab8b0..59d94b2 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -309,8 +309,6 @@ class FileTransfersHandler: elif data[1] == friend_number and not data[2]: self.send_file(data[0], friend_number, True, key) del self._paused_file_transfers[key] - if friend_number == self.get_active_number() and self.is_active_a_friend(): - self.update() except Exception as ex: print('Exception in file sending: ' + str(ex)) @@ -333,14 +331,13 @@ class FileTransfersHandler: :param file_number: file number :param size: size of avatar or 0 (default avatar) """ - ra = ReceiveAvatar(self._tox, friend_number, size, file_number) + 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 != TOX_FILE_TRANSFER_STATE['CANCELLED']: self._file_transfers[(friend_number, file_number)] = ra ra.set_transfer_finished_handler(self.transfer_finished) else: - self._get_friend_by_number(friend_number).load_avatar() - if self.get_active_number() == friend_number and self.is_active_a_friend(): - self.set_active(None) + friend.load_avatar() # ----------------------------------------------------------------------------------------------------------------- # Private methods diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 54ca652..f871940 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -97,7 +97,7 @@ class MainWindow(QtWidgets.QMainWindow): self.actionAbout_program.triggered.connect(self.about_program) self.actionNetwork.triggered.connect(self.network_settings) - self.actionAdd_friend.triggered.connect(self.add_contact) + self.actionAdd_friend.triggered.connect(self.add_contact_triggered) self.actionAdd_gc.triggered.connect(self.create_gc) self.actionSettings.triggered.connect(self.profile_settings) self.actionPrivacy_settings.triggered.connect(self.privacy_settings) @@ -437,6 +437,9 @@ class MainWindow(QtWidgets.QMainWindow): 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() @@ -444,7 +447,7 @@ class MainWindow(QtWidgets.QMainWindow): def create_gc(self): self.profile.create_group_chat() - def profile_settings(self, *args): + def profile_settings(self, _): self._modal_window = self._widget_factory.create_profile_settings_window() self._modal_window.show() diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index b28ab3b..0585c4c 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -4,7 +4,6 @@ from contacts.profile import Profile from utils.util import * from ui.widgets import CenteredWidget, DataLabel, LineEdit, RubberBandWindow import pyaudio -from user_data import toxes import updater.updater as updater import utils.ui as util_ui @@ -90,6 +89,7 @@ class ProfileSettings(CenteredWidget): self._profile_manager = profile_manager self._settings = settings self._toxes = toxes + self._auto = False self.initUI() self.center() @@ -166,8 +166,7 @@ class ProfileSettings(CenteredWidget): self.warning.setStyleSheet('QLabel { color: #BC1C1C; }') self.default = QtWidgets.QPushButton(self) self.default.setGeometry(QtCore.QRect(40, 550, 620, 30)) - auto_profile = Settings.get_auto_profile() - # self.auto = path + name == ProfileManager.get_path() + Settings.get_instance().name + self._auto = Settings.get_auto_profile() == self._profile_manager.get_path() self.default.clicked.connect(self.auto_profile) self.retranslateUi() if self._profile.status is not None: @@ -197,23 +196,23 @@ class ProfileSettings(CenteredWidget): self.status.addItem(util_ui.tr("Away")) self.status.addItem(util_ui.tr("Busy")) self.copy_pk.setText(util_ui.tr("Copy public key")) - if self.auto: + + self.set_default_profile_button_text() + + def 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.default.setText(util_ui.tr("Mark as not default profile")) else: self.default.setText(util_ui.tr("Mark as default profile")) - def auto_profile(self): - if self.auto: - Settings.reset_auto_profile() - else: - Settings.set_auto_profile(ProfileManager.get_path(), Settings.get_instance().name) - self.auto = not self.auto - if self.auto: - self.default.setText(util_ui.tr("Mark as not default profile")) - else: - self.default.setText( - util_ui.tr("Mark as default profile")) - def new_password(self): if self.password.text() == self.confirm_password.text(): if not len(self.password.text()) or len(self.password.text()) >= 8: @@ -230,7 +229,7 @@ class ProfileSettings(CenteredWidget): def copy(self): clipboard = QtWidgets.QApplication.clipboard() clipboard.setText(self._profile.tox_id) - pixmap = QtGui.QPixmap(curr_directory() + '/images/accept.png') + pixmap = QtGui.QPixmap(join_path(get_images_directory(), 'accept.png')) icon = QtGui.QIcon(pixmap) self.copyId.setIcon(icon) self.copyId.setIconSize(QtCore.QSize(10, 10)) @@ -238,7 +237,7 @@ class ProfileSettings(CenteredWidget): def copy_public_key(self): clipboard = QtWidgets.QApplication.clipboard() clipboard.setText(self._profile.tox_id[:64]) - pixmap = QtGui.QPixmap(curr_directory() + '/images/accept.png') + pixmap = QtGui.QPixmap(join_path(get_images_directory(), 'accept.png')) icon = QtGui.QIcon(pixmap) self.copy_pk.setIcon(icon) self.copy_pk.setIconSize(QtCore.QSize(10, 10)) @@ -270,7 +269,7 @@ class ProfileSettings(CenteredWidget): util_ui.tr('Use new path')) self._settings.export(directory) self._profile.export_db(directory) - ProfileManager.get_instance().export_profile(directory, reply) + self._profile_manager.export_profile(directory, reply) def closeEvent(self, event): self._profile.set_name(self.nick.text()) diff --git a/toxygen/user_data/profile_manager.py b/toxygen/user_data/profile_manager.py index a95253f..3324fce 100644 --- a/toxygen/user_data/profile_manager.py +++ b/toxygen/user_data/profile_manager.py @@ -28,6 +28,9 @@ class ProfileManager: def get_dir(self): return self._directory + def get_path(self): + return self._path + def save_profile(self, data): if self._toxes.has_password(): data = self._toxes.pass_encrypt(data) diff --git a/toxygen/user_data/settings.py b/toxygen/user_data/settings.py index f9390f3..8289663 100644 --- a/toxygen/user_data/settings.py +++ b/toxygen/user_data/settings.py @@ -1,5 +1,4 @@ import json -import os from utils.util import * import pyaudio import smileys.smileys as smileys @@ -52,16 +51,21 @@ class Settings(dict): if os.path.isfile(p): with open(p) as fl: data = fl.read() - auto = json.loads(data) - if 'path' in auto and 'name' in auto: - path = str(auto['path']) - name = str(auto['name']) - if os.path.isfile(join_path(path, name + '.tox')): - return path, name + try: + auto = json.loads(data) + except Exception as ex: + log(str(ex)) + auto = {} + if 'profile_path' in auto: + path = str(auto['profile_path']) + if not os.path.isabs(path): + path = join_path(path, curr_directory(__file__)) + if os.path.isfile(path): + return path return None @staticmethod - def set_auto_profile(path, name): + def set_auto_profile(path): p = Settings.get_global_settings_path() if os.path.isfile(p): with open(p) as fl: @@ -69,8 +73,7 @@ class Settings(dict): data = json.loads(data) else: data = {} - data['path'] = str(path) - data['name'] = str(name) + data['profile_path'] = str(path) with open(p, 'w') as fl: fl.write(json.dumps(data)) From 7898363dcb38cdf665cbd59fb394453a8b22b3b1 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 11 May 2018 00:35:56 +0300 Subject: [PATCH 021/138] contact context menu fixes --- toxygen/app.py | 3 ++- toxygen/contacts/contacts_manager.py | 9 ++++++++- toxygen/ui/main_screen.py | 23 +++++++++++++---------- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index 713d250..de15277 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -310,7 +310,8 @@ class App: self._messenger = Messenger(self._tox, self._plugin_loader, self._ms, self._contacts_manager, self._contacts_provider, items_factory, profile) self._tray = tray.init_tray(profile, self._settings, self._ms) - self._ms.set_dependencies(widgets_factory, self._tray, self._contacts_manager, self._messenger, profile) + self._ms.set_dependencies(widgets_factory, self._tray, self._contacts_manager, self._messenger, profile, + self._plugin_loader) self._tray.show() self._ms.show() diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 464d448..4bdf78b 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -30,7 +30,7 @@ class ContactsManager: def __del__(self): del self._history - def get_friend(self, num): + def get_contact(self, num): if num < 0 or num >= len(self._contacts): return None return self._contacts[num] @@ -359,6 +359,13 @@ class ContactsManager: self.add_friend(tox_id) self.save_profile() + # ----------------------------------------------------------------------------------------------------------------- + # Groups support + # ----------------------------------------------------------------------------------------------------------------- + + def get_group_chats(self): + return list(filter(lambda c: type(c) is not Friend, self._contacts)) # TODO: fix after gc implementation + # ----------------------------------------------------------------------------------------------------------------- # Friend requests # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index f871940..821b123 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -15,16 +15,18 @@ class MainWindow(QtWidgets.QMainWindow): self._tray = tray self._widget_factory = None self._modal_window = None + self._plugins_loader = None self.setAcceptDrops(True) self._saved = False self._profile = None self.initUI() - def set_dependencies(self, widget_factory, tray, contacts_manager, messenger, profile): + def set_dependencies(self, widget_factory, tray, contacts_manager, messenger, profile, plugins_loader): self._widget_factory = widget_factory self._tray = tray self._contacts_manager = contacts_manager self._profile = profile + self._plugins_loader = plugins_loader self.messageEdit.set_messenger(messenger) def show(self): @@ -572,16 +574,17 @@ class MainWindow(QtWidgets.QMainWindow): # ----------------------------------------------------------------------------------------------------------------- def friend_right_click(self, pos): + # TODO: move to contact? item = self.friends_list.itemAt(pos) num = self.friends_list.indexFromItem(item).row() - friend = self._contacts_manager.get_friend(num) - if friend is None: + contact = self._contacts_manager.get_contact(num) + if contact is None: return - allowed = friend.tox_id in self._settings['auto_accept_from_friends'] + allowed = contact.tox_id in self._settings['auto_accept_from_friends'] auto = util_ui.tr('Disallow auto accept') if allowed else util_ui.tr('Allow auto accept') if item is not None: self.listMenu = QtWidgets.QMenu() - is_friend = type(friend) is Friend + is_friend = type(contact) is Friend if is_friend: set_alias_item = self.listMenu.addAction(util_ui.tr('Set alias')) set_alias_item.triggered.connect(lambda: self.set_alias(num)) @@ -603,7 +606,7 @@ class MainWindow(QtWidgets.QMainWindow): notes_item = self.listMenu.addAction(util_ui.tr('Notes')) chats = self._contacts_manager.get_group_chats() - if len(chats) and self.profile.is_active_online(): + if len(chats) and contact.status is not None: invite_menu = self.listMenu.addMenu(util_ui.tr('Invite to group chat')) for i in range(len(chats)): name, number = chats[i] @@ -619,15 +622,15 @@ class MainWindow(QtWidgets.QMainWindow): 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)) + notes_item.triggered.connect(lambda: self.show_note(contact)) else: leave_item = self.listMenu.addAction(util_ui.tr('Leave chat')) set_title_item = self.listMenu.addAction(util_ui.tr('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)) + copy_name_item.triggered.connect(lambda: self.copy_name(contact)) + copy_status_item.triggered.connect(lambda: self.copy_status(contact)) 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)) @@ -666,7 +669,7 @@ class MainWindow(QtWidgets.QMainWindow): self._contacts_manager.delete_friend(num) def block_friend(self, num): - friend = self.profile.get_friend(num) + friend = self.profile.get_contact(num) self._contacts_manager.block_user(friend.tox_id) def copy_friend_key(self, num): From c6192de9ddf433d1f237b9c60fc86371b9f8b57d Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 11 May 2018 21:27:46 +0300 Subject: [PATCH 022/138] new context menu generation - builder, generators --- toxygen/contacts/contact.py | 20 +++- toxygen/contacts/contact_menu.py | 144 +++++++++++++++++++++++ toxygen/contacts/contact_provider.py | 1 - toxygen/contacts/friend.py | 9 ++ toxygen/plugin_support/plugin_support.py | 4 +- toxygen/plugins/plugin_super_class.py | 5 +- toxygen/ui/main_screen.py | 73 ++---------- 7 files changed, 178 insertions(+), 78 deletions(-) create mode 100644 toxygen/contacts/contact_menu.py diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index 43f7728..ddbd1d4 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -2,6 +2,7 @@ from history.database import * from contacts import basecontact, common import utils.util as util from messenger.messages import * +from contacts.contact_menu import * from file_transfers import file_transfers as ft import re @@ -89,7 +90,7 @@ class Contact(basecontact.BaseContact): self._unsaved_messages += 1 def get_last_message_text(self): - messages = list(filter(lambda x: x.get_type() <= 1 and x.get_owner() != MESSAGE_OWNER['FRIEND'], self._corr)) + messages = list(filter(lambda x: x.get_type() <= 1 and x.get_owner() != MESSAGE_AUTHOR['FRIEND'], self._corr)) if messages: return messages[-1].get_data()[0] else: @@ -103,19 +104,19 @@ class Contact(basecontact.BaseContact): """ :return list of unsent messages """ - messages = filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr) + messages = filter(lambda x: x.get_owner() == MESSAGE_AUTHOR['NOT_SENT'], self._corr) return list(messages) def get_unsent_messages_for_saving(self): """ :return list of unsent messages for saving """ - messages = filter(lambda x: x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr) + messages = filter(lambda x: x.get_type() <= 1 and x.get_owner() == MESSAGE_AUTHOR['NOT_SENT'], self._corr) return list(map(lambda x: x.get_data(), messages)) def mark_as_sent(self): try: - message = list(filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr))[0] + message = list(filter(lambda x: x.get_owner() == MESSAGE_AUTHOR['NOT_SENT'], self._corr))[0] message.mark_as_sent() except Exception as ex: util.log('Mark as sent ex: ' + str(ex)) @@ -140,7 +141,7 @@ class Contact(basecontact.BaseContact): def save_message(x): if x.get_type() == 2 and (x.get_status() >= 2 or x.get_status() is None): return True - return x.get_owner() == MESSAGE_OWNER['NOT_SENT'] + return x.get_owner() == MESSAGE_AUTHOR['NOT_SENT'] old = filter(save_message, self._corr[:-SAVE_MESSAGES]) self._corr = list(old) + self._corr[-SAVE_MESSAGES:] @@ -162,7 +163,7 @@ class Contact(basecontact.BaseContact): self._unsaved_messages = 0 else: self._corr = list(filter(lambda x: (x.get_type() == 2 and x.get_status() in ft.ACTIVE_FILE_TRANSFERS) - or (x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT']), + or (x.get_type() <= 1 and x.get_owner() == MESSAGE_AUTHOR['NOT_SENT']), self._corr)) self._unsaved_messages = len(self.get_unsent_messages()) @@ -294,3 +295,10 @@ class Contact(basecontact.BaseContact): 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) diff --git a/toxygen/contacts/contact_menu.py b/toxygen/contacts/contact_menu.py new file mode 100644 index 0000000..d49c975 --- /dev/null +++ b/toxygen/contacts/contact_menu.py @@ -0,0 +1,144 @@ +from PyQt5 import QtWidgets +import utils.ui as util_ui + + +# ----------------------------------------------------------------------------------------------------------------- +# Builder +# ----------------------------------------------------------------------------------------------------------------- + +def _create_menu(menu_name): + return QtWidgets.QMenu(menu_name or '') + + +class ContactMenuBuilder: + + def __init__(self): + self._actions = [] + self._submenus = [] + self._name = None + + def with_name(self, name): + self._name = name + + return self + + def with_action(self, text, handler): + self._actions.append((text, handler)) + + return self + + def with_actions(self, actions): + self._actions.extend(actions) + + return self + + def with_submenu(self, submenu): + self._add_submenu(submenu) + + return self + + def with_optional_submenu(self, submenu): + if submenu is not None: + self._add_submenu(submenu) + + return self + + def build(self): # TODO: actions order + menu = _create_menu(self._name) + + for text, handler in self._actions: + action = menu.addAction(text) + action.triggered.connect(handler) + + for submenu in self._submenus: + menu.addMenu(submenu) + + return menu + + def _add_submenu(self, submenu): + self._submenus.append(submenu) + +# ----------------------------------------------------------------------------------------------------------------- +# Generators +# ----------------------------------------------------------------------------------------------------------------- + + +class BaseContactMenuGenerator: + + def __init__(self, contact): + self._contact = contact + + def generate(self, plugin_loader, contacts_manager, main_screen, settings, number): + return ContactMenuBuilder().build() + + +class FriendMenuGenerator(BaseContactMenuGenerator): + + def generate(self, plugin_loader, contacts_manager, main_screen, settings, number): + history_menu = self._generate_history_menu(main_screen, number) + copy_menu = self._generate_copy_menu(main_screen) + plugins_menu = self._generate_plugins_menu(plugin_loader, number) + + 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_action(util_ui.tr('Chat history'), lambda: main_screen.clear_history(number)) + .with_submenu(history_menu) + .with_submenu(copy_menu) + .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) + ).build() + + return menu + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + @staticmethod + def _generate_history_menu(main_screen, number): + history_menu_builder = ContactMenuBuilder() + history_menu = (history_menu_builder + .with_name(util_ui.tr('Chat history')) + .with_action(util_ui.tr('Clear history'), lambda: main_screen.clear_history(number)) + .with_action(util_ui.tr('Export as text'), lambda: main_screen.export_history(number)) + .with_action(util_ui.tr('Export as HTML'), lambda: main_screen.export_history(number, False)) + ).build() + return history_menu + + def _generate_copy_menu(self, main_screen): + copy_menu_builder = ContactMenuBuilder() + copy_menu = (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.name)) + .with_action(util_ui.tr('Public key'), lambda: main_screen.copy_text(self._contact.tox_id)) + ).build() + + return copy_menu + + @staticmethod + def _generate_plugins_menu(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 = (plugins_menu_builder + .with_name(util_ui.tr('Plugins')) + .with_actions(plugins_actions) + ) + + return plugins_menu + + def _generate_groups_menu(self, contacts_manager): + chats = contacts_manager.get_group_chats() + if not len(chats) or self._contact.status is None: + return None diff --git a/toxygen/contacts/contact_provider.py b/toxygen/contacts/contact_provider.py index 5042f97..1a08589 100644 --- a/toxygen/contacts/contact_provider.py +++ b/toxygen/contacts/contact_provider.py @@ -1,4 +1,3 @@ -import utils.util as util import common.tox_save as tox_save diff --git a/toxygen/contacts/friend.py b/toxygen/contacts/friend.py index 2e65e53..9602eb1 100644 --- a/toxygen/contacts/friend.py +++ b/toxygen/contacts/friend.py @@ -1,6 +1,7 @@ from contacts import contact, common from messenger.messages import * import os +from contacts.contact_menu import * class Friend(contact.Contact): @@ -46,6 +47,7 @@ class Friend(contact.Contact): 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): @@ -81,3 +83,10 @@ class Friend(contact.Contact): def get_typing_notification_handler(self): return self._typing_notification_handler + + # ----------------------------------------------------------------------------------------------------------------- + # Context menu support + # ----------------------------------------------------------------------------------------------------------------- + + def get_context_menu_generator(self): + return FriendMenuGenerator(self) diff --git a/toxygen/plugin_support/plugin_support.py b/toxygen/plugin_support/plugin_support.py index bd50cb7..5871afa 100644 --- a/toxygen/plugin_support/plugin_support.py +++ b/toxygen/plugin_support/plugin_support.py @@ -138,7 +138,7 @@ class PluginLoader(): if name in self._plugins and self._plugins[name][1]: self._plugins[name][0].command(text[len(name) + 1:]) - def get_menu(self, menu, num): + def get_menu(self, num): """ Return list of items for menu """ @@ -146,7 +146,7 @@ class PluginLoader(): for elem in self._plugins.values(): if elem[1]: try: - result.extend(elem[0].get_menu(menu, num)) + result.extend(elem[0].get_menu(num)) except: continue return result diff --git a/toxygen/plugins/plugin_super_class.py b/toxygen/plugins/plugin_super_class.py index c857c56..abb4da6 100644 --- a/toxygen/plugins/plugin_super_class.py +++ b/toxygen/plugins/plugin_super_class.py @@ -76,12 +76,11 @@ class PluginSuperClass: """ 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 - :param menu: menu instance :param row_number: number of selected row in list of contacts - :return list of QAction's + :return list of tuples (text, handler) """ return [] diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 821b123..5c96e97 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -576,63 +576,14 @@ class MainWindow(QtWidgets.QMainWindow): def friend_right_click(self, pos): # TODO: move to contact? item = self.friends_list.itemAt(pos) - num = self.friends_list.indexFromItem(item).row() - contact = self._contacts_manager.get_contact(num) + number = self.friends_list.indexFromItem(item).row() + contact = self._contacts_manager.get_contact(number) if contact is None: return - allowed = contact.tox_id in self._settings['auto_accept_from_friends'] - auto = util_ui.tr('Disallow auto accept') if allowed else util_ui.tr('Allow auto accept') if item is not None: - self.listMenu = QtWidgets.QMenu() - is_friend = type(contact) is Friend - if is_friend: - set_alias_item = self.listMenu.addAction(util_ui.tr('Set alias')) - set_alias_item.triggered.connect(lambda: self.set_alias(num)) - - history_menu = self.listMenu.addMenu(util_ui.tr('Chat history')) - clear_history_item = history_menu.addAction(util_ui.tr('Clear history')) - export_to_text_item = history_menu.addAction(util_ui.tr('Export as text')) - export_to_html_item = history_menu.addAction(util_ui.tr('Export as HTML')) - - copy_menu = self.listMenu.addMenu(util_ui.tr('Copy')) - copy_name_item = copy_menu.addAction(util_ui.tr('Name')) - copy_status_item = copy_menu.addAction(util_ui.tr('Status message')) - if is_friend: - copy_key_item = copy_menu.addAction(util_ui.tr('Public key')) - - auto_accept_item = self.listMenu.addAction(auto) - remove_item = self.listMenu.addAction(util_ui.tr('Remove friend')) - block_item = self.listMenu.addAction(util_ui.tr('Block friend')) - notes_item = self.listMenu.addAction(util_ui.tr('Notes')) - - chats = self._contacts_manager.get_group_chats() - if len(chats) and contact.status is not None: - invite_menu = self.listMenu.addMenu(util_ui.tr('Invite to group chat')) - for i in range(len(chats)): - name, number = chats[i] - item = invite_menu.addAction(name) - item.triggered.connect(lambda: self.invite_friend_to_gc(num, number)) - - if self._plugins_loader is not None: - submenu = self._plugins_loader.get_menu(self.listMenu, num) - if len(submenu): - plug = self.listMenu.addMenu(util_ui.tr('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(contact)) - else: - leave_item = self.listMenu.addAction(util_ui.tr('Leave chat')) - set_title_item = self.listMenu.addAction(util_ui.tr('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(contact)) - copy_status_item.triggered.connect(lambda: self.copy_status(contact)) - export_to_text_item.triggered.connect(lambda: self.export_history(num)) - export_to_html_item.triggered.connect(lambda: self.export_history(num, False)) + generator = contact.get_context_menu_generator() + self.listMenu = generator.generate(self._plugins_loader, self._contacts_manager, self, + self._settings, number) parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0)) self.listMenu.move(parent_position + pos) self.listMenu.show() @@ -672,20 +623,10 @@ class MainWindow(QtWidgets.QMainWindow): friend = self.profile.get_contact(num) self._contacts_manager.block_user(friend.tox_id) - def copy_friend_key(self, num): - tox_id = self._contacts_manager.friend_public_key(num) - clipboard = QtWidgets.QApplication.clipboard() - clipboard.setText(tox_id) - @staticmethod - def copy_name(friend): + def copy_text(text): clipboard = QtWidgets.QApplication.clipboard() - clipboard.setText(friend.name) - - @staticmethod - def copy_status(friend): - clipboard = QtWidgets.QApplication.clipboard() - clipboard.setText(friend.status_message) + clipboard.setText(text) def clear_history(self, num): self._contacts_manager.clear_history(num) From e21a9355e7b9f5814eb30362e2aec959ccc74c7e Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 11 May 2018 22:02:03 +0300 Subject: [PATCH 023/138] minor fixes - context menu --- toxygen/contacts/contact_menu.py | 5 +++-- toxygen/ui/main_screen.py | 15 ++++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/toxygen/contacts/contact_menu.py b/toxygen/contacts/contact_menu.py index d49c975..d924702 100644 --- a/toxygen/contacts/contact_menu.py +++ b/toxygen/contacts/contact_menu.py @@ -110,6 +110,7 @@ class FriendMenuGenerator(BaseContactMenuGenerator): .with_action(util_ui.tr('Export as text'), lambda: main_screen.export_history(number)) .with_action(util_ui.tr('Export as HTML'), lambda: main_screen.export_history(number, False)) ).build() + return history_menu def _generate_copy_menu(self, main_screen): @@ -134,11 +135,11 @@ class FriendMenuGenerator(BaseContactMenuGenerator): plugins_menu = (plugins_menu_builder .with_name(util_ui.tr('Plugins')) .with_actions(plugins_actions) - ) + ).build() return plugins_menu - def _generate_groups_menu(self, contacts_manager): + def _generate_groups_menu(self, contacts_manager): # TODO: fix chats = contacts_manager.get_group_chats() if not len(chats) or self._contact.status is None: return None diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 5c96e97..b318ccc 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -574,19 +574,16 @@ class MainWindow(QtWidgets.QMainWindow): # ----------------------------------------------------------------------------------------------------------------- def friend_right_click(self, pos): - # TODO: move to contact? item = self.friends_list.itemAt(pos) number = self.friends_list.indexFromItem(item).row() contact = self._contacts_manager.get_contact(number) - if contact is None: + if contact is None or item is None: return - if item is not None: - generator = contact.get_context_menu_generator() - self.listMenu = generator.generate(self._plugins_loader, self._contacts_manager, self, - self._settings, number) - parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0)) - self.listMenu.move(parent_position + pos) - self.listMenu.show() + generator = contact.get_context_menu_generator() + self.listMenu = generator.generate(self._plugins_loader, self._contacts_manager, self, self._settings, number) + 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 '' From 98dbe6a4932bae36f789c4df9975afad78fe77b0 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 15 May 2018 13:40:59 +0300 Subject: [PATCH 024/138] widgets fixes --- toxygen/app.py | 4 ++-- toxygen/contacts/contacts_manager.py | 14 +++++++------- .../file_transfers/file_transfers_handler.py | 12 +++++------- toxygen/ui/main_screen.py | 2 +- toxygen/ui/main_screen_widgets.py | 17 ++++++++++------- toxygen/ui/widgets_factory.py | 6 +++--- 6 files changed, 28 insertions(+), 27 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index de15277..20fed55 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -303,12 +303,12 @@ class App: self._contacts_provider = ContactProvider(self._tox, self._friend_factory) self._contacts_manager = ContactsManager(self._tox, self._settings, self._ms, self._profile_manager, self._contacts_provider, db, self._tox_dns) + self._messenger = Messenger(self._tox, self._plugin_loader, self._ms, self._contacts_manager, + self._contacts_provider, items_factory, profile) self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider) widgets_factory = WidgetsFactory(self._settings, profile, self._profile_manager, self._contacts_manager, self._file_transfer_handler, self._smiley_loader, self._plugin_loader, self._toxes, self._version) - self._messenger = Messenger(self._tox, self._plugin_loader, self._ms, self._contacts_manager, - self._contacts_provider, items_factory, profile) self._tray = tray.init_tray(profile, self._settings, self._ms) self._ms.set_dependencies(widgets_factory, self._tray, self._contacts_manager, self._messenger, profile, self._plugin_loader) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 4bdf78b..efe479c 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -43,7 +43,7 @@ class ContactsManager: self._profile_manager.save_profile(data) def is_friend_active(self, friend_number): - if not self._is_active_a_friend(): + if not self.is_active_a_friend(): return False return self.get_curr_contact().number == friend_number @@ -148,6 +148,8 @@ class ContactsManager: util.log('Error in set active: ' + str(ex)) raise + active_friend = property(get_active, set_active) + def set_active_by_number_and_type(self, number, is_friend): for i in range(len(self._contacts)): c = self._contacts[i] @@ -155,12 +157,13 @@ class ContactsManager: self._active_contact = i break - active_friend = property(get_active, set_active) - 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 + # ----------------------------------------------------------------------------------------------------------------- # Filtration # ----------------------------------------------------------------------------------------------------------------- @@ -174,7 +177,7 @@ class ContactsManager: # TODO: simplify? filter_str = filter_str.lower() number = self.get_active_number() - is_friend = self._is_active_a_friend() + is_friend = self.is_active_a_friend() if sorting > 1: if sorting & 2: self._contacts = sorted(self._contacts, key=lambda x: int(x.status is not None), reverse=True) @@ -424,9 +427,6 @@ class ContactsManager: # Private methods # ----------------------------------------------------------------------------------------------------------------- - def _is_active_a_friend(self): - return type(self.get_curr_contact()) is Friend - def _load_contacts(self): self._load_friends() self._load_groups() diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index 59d94b2..073b8b6 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -179,25 +179,23 @@ class FileTransfersHandler: self._get_friend_by_number(friend_number).update_transfer_data(file_number, TOX_FILE_TRANSFER_STATE['RUNNING']) - def send_screenshot(self, data): + def send_screenshot(self, data, friend_number): """ Send screenshot to current active friend :param data: raw data - png """ - self.send_inline(data, 'toxygen_inline.png') + self.send_inline(data, 'toxygen_inline.png', friend_number) - def send_sticker(self, path): + def send_sticker(self, path, friend_number): with open(path, 'rb') as fl: data = fl.read() - self.send_inline(data, 'sticker.png') + self.send_inline(data, 'sticker.png', friend_number) - def send_inline(self, data, file_name, friend_number=None, is_resend=False): - friend_number = friend_number or self.get_active_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: m = UnsentFile(file_name, data, time.time()) friend.append_message(m) - self.update() return elif friend.status is None and is_resend: raise RuntimeError() diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index b318ccc..f0a1ef5 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -542,7 +542,7 @@ class MainWindow(QtWidgets.QMainWindow): def send_sticker(self): self.menu.hide() if self._contacts_manager.is_active_a_friend(): - self.sticker = self._widget_factory.create_sticker_window(self) + 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(), diff --git a/toxygen/ui/main_screen_widgets.py b/toxygen/ui/main_screen_widgets.py index e739167..ecf329a 100644 --- a/toxygen/ui/main_screen_widgets.py +++ b/toxygen/ui/main_screen_widgets.py @@ -81,7 +81,8 @@ class MessageArea(QtWidgets.QPlainTextEdit): else: self.insertPlainText(text) - def parse_file_name(self, file_name): + @staticmethod + def parse_file_name(file_name): if file_name.endswith('\r\n'): file_name = file_name[:-2] file_name = urllib.parse.unquote(file_name) @@ -90,9 +91,10 @@ class MessageArea(QtWidgets.QPlainTextEdit): class ScreenShotWindow(RubberBandWindow): - def __init__(self, file_transfer_handler, *args): + 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): if self.parent.isHidden(): @@ -113,7 +115,8 @@ class ScreenShotWindow(RubberBandWindow): buffer = QtCore.QBuffer(byte_array) buffer.open(QtCore.QIODevice.WriteOnly) p.save(buffer, 'PNG') - self._file_transfer_handler.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() @@ -267,9 +270,10 @@ class StickerItem(QtWidgets.QWidget): class StickerWindow(QtWidgets.QWidget): """Sticker selection window""" - def __init__(self, parent, file_transfer_handler): + def __init__(self, file_transfer_handler, contacts_manager): super().__init__() self._file_transfer_handler = file_transfer_handler + self._contacts_manager = contacts_manager self.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.setMaximumSize(250, 200) self.setMinimumSize(250, 200) @@ -285,11 +289,11 @@ class StickerWindow(QtWidgets.QWidget): self.list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) self.list.setSpacing(3) self.list.clicked.connect(self.click) - self.parent = parent def click(self, index): num = index.row() - self._file_transfer_handler.send_sticker(self._stickers[num]) + friend = self._contacts_manager.get_curr_contact() + self._file_transfer_handler.send_sticker(self._stickers[num], friend.number) self.close() def leaveEvent(self, event): @@ -465,7 +469,6 @@ class SearchScreen(QtWidgets.QWidget): self.not_found(text) def closeEvent(self, *args): - Profile.get_instance().update() self._messages.setGeometry(0, 0, self._messages.width(), self._messages.height() + 40) super().closeEvent(*args) diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py index 0c0bcbc..b42e342 100644 --- a/toxygen/ui/widgets_factory.py +++ b/toxygen/ui/widgets_factory.py @@ -17,7 +17,7 @@ class WidgetsFactory: self._version = version def create_screenshot_window(self, *args): - return ScreenShotWindow(self._file_transfer_handler, *args) + return ScreenShotWindow(self._file_transfer_handler, self._contacts_manager, *args) def create_smiley_window(self, parent): return SmileyWindow(parent, self._smiley_loader) @@ -61,5 +61,5 @@ class WidgetsFactory: def create_smiley_window(self, parent): return SmileyWindow(parent, self._smiley_loader) - def create_sticker_window(self, parent): - return StickerWindow(parent, self._file_transfer_handler) + def create_sticker_window(self): + return StickerWindow(self._file_transfer_handler, self._contacts_manager) From f1c63bb4e8dab2415708025323fcf57fd28e60ef Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 15 May 2018 17:00:12 +0300 Subject: [PATCH 025/138] history loading after friend switching. refactoring --- toxygen/app.py | 15 ++- toxygen/contacts/contact.py | 4 + toxygen/contacts/contacts_manager.py | 109 ++++++------------ toxygen/contacts/friend_factory.py | 2 +- toxygen/contacts/profile.py | 22 ++-- toxygen/history/database.py | 2 +- .../history/{history_loader.py => history.py} | 5 +- toxygen/messenger/messages.py | 42 +++---- toxygen/messenger/messenger.py | 6 +- toxygen/middleware/callbacks.py | 2 - .../{items_factory.py => items_factories.py} | 42 ++++--- toxygen/ui/list_items.py | 3 - toxygen/ui/main_screen.py | 12 +- toxygen/ui/menu.py | 2 - toxygen/ui/messages_widgets.py | 11 +- 15 files changed, 135 insertions(+), 144 deletions(-) rename toxygen/history/{history_loader.py => history.py} (98%) rename toxygen/ui/{items_factory.py => items_factories.py} (73%) diff --git a/toxygen/app.py b/toxygen/app.py index 20fed55..9f41b47 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -24,9 +24,10 @@ 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_factory import ItemsFactory +from ui.items_factories import MessagesItemsFactory, FriendItemsFactory from messenger.messenger import Messenger from network.tox_dns import ToxDns +from history.history import History class App: @@ -298,13 +299,17 @@ class App: profile = Profile(self._profile_manager, self._tox, self._ms) self._plugin_loader = PluginLoader(self._tox, self._toxes, profile, self._settings) - items_factory = ItemsFactory(self._settings, self._plugin_loader, self._smiley_loader, self._ms) - self._friend_factory = FriendFactory(self._profile_manager, self._settings, self._tox, db, items_factory) + friend_items_factory = FriendItemsFactory(self._settings, self._ms) + self._friend_factory = FriendFactory(self._profile_manager, self._settings, self._tox, db, friend_items_factory) self._contacts_provider = ContactProvider(self._tox, self._friend_factory) + history = History(self._contacts_provider, db, self._settings) + messages_items_factory = MessagesItemsFactory(self._settings, self._plugin_loader, self._smiley_loader, + self._ms, history) self._contacts_manager = ContactsManager(self._tox, self._settings, self._ms, self._profile_manager, - self._contacts_provider, db, self._tox_dns) + self._contacts_provider, history, self._tox_dns, + messages_items_factory) self._messenger = Messenger(self._tox, self._plugin_loader, self._ms, self._contacts_manager, - self._contacts_provider, items_factory, profile) + self._contacts_provider, messages_items_factory, profile) self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider) widgets_factory = WidgetsFactory(self._settings, profile, self._profile_manager, self._contacts_manager, self._file_transfer_handler, self._smiley_loader, self._plugin_loader, diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index ddbd1d4..4c19e33 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -96,6 +96,10 @@ class Contact(basecontact.BaseContact): else: return '' + def remove_messages_widgets(self): + for message in self._corr: + message.remove_widget() + # ----------------------------------------------------------------------------------------------------------------- # Unsent messages # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index efe479c..c3db871 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -3,7 +3,7 @@ import utils.ui as util_ui from contacts.friend import Friend from PyQt5 import QtCore, QtGui from wrapper.toxcore_enums_and_consts import * -from history.history_loader import HistoryLoader +from messenger.messages import * class ContactsManager: @@ -11,25 +11,24 @@ class ContactsManager: Represents contacts list. """ - def __init__(self, tox, settings, screen, profile_manager, contact_provider, db, tox_dns): + def __init__(self, tox, settings, screen, profile_manager, contact_provider, history, tox_dns, + messages_items_factory): self._tox = 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._sorting = settings['sorting'] self._filter_string = '' self._friend_item_height = 40 if settings['compact_mode'] else 70 screen.online_contacts.setCurrentIndex(int(self._sorting)) - self._history = HistoryLoader(contact_provider, db, settings) + self._history = history self._load_contacts() - def __del__(self): - del self._history - def get_contact(self, num): if num < 0 or num >= len(self._contacts): return None @@ -55,10 +54,10 @@ class ContactsManager: def get_active(self): return self._active_contact - def set_active(self, value=None): + def set_active(self, value): """ Change current active friend or update info - :param value: number of new active friend in friend's list or None to update active user's data + :param value: number of new active friend in friend's list """ if value is None and self._active_contact == -1: # nothing to update return @@ -76,67 +75,34 @@ class ContactsManager: self._screen.typing.setVisible(False) current_contact = self.get_curr_contact() if current_contact is not None: + current_contact.remove_messages_widgets() # TODO: if required self._unsubscribe_from_events(current_contact) - if value is not None: - if self._active_contact + 1 and self._active_contact != value: - try: - current_contact.curr_text = self._screen.messageEdit.toPlainText() - except: - pass - friend = self._contacts[value] - self._subscribe_to_events(friend) - friend.remove_invalid_unsent_files() - if self._active_contact != value: - self._screen.messageEdit.setPlainText(friend.curr_text) - self._active_contact = value - friend.reset_messages() - if not self._settings['save_history']: - friend.delete_old_messages() - self._messages.clear() - # friend.load_corr() - # messages = friend.get_corr()[-PAGE_SIZE:] - # self._load_history = False - # for message in messages: - # if message.get_type() <= 1: - # data = message.get_data() - # self.create_message_item(data[0], - # data[2], - # data[1], - # data[3]) - # elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: - # if message.get_status() is None: - # self.create_unsent_file_item(message) - # continue - # item = self.create_file_transfer_item(message) - # if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer - # try: - # ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())] - # ft.set_state_changed_handler(item.update_transfer_state) - # ft.signal() - # except: - # print('Incoming not started transfer - no info found') - # elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline - # self.create_inline_item(message.get_data()) - # elif message.get_type() < 5: # info message - # data = message.get_data() - # self.create_message_item(data[0], - # data[2], - # '', - # data[3]) - # else: - # data = message.get_data() - # self.create_gc_message_item(data[0], data[2], data[1], data[4], data[3]) - # self._messages.scrollToBottom() - # self._load_history = True - # if value in self._call: - # self._screen.active_call() - # elif value in self._incoming_calls: - # self._screen.incoming_call() - # else: - # self._screen.call_finished() - else: - friend = self.get_curr_contact() - # TODO: to separate method + + if self._active_contact + 1 and self._active_contact != value: + try: + current_contact.curr_text = self._screen.messageEdit.toPlainText() + except: + pass + friend = self._contacts[value] + self._subscribe_to_events(friend) + friend.remove_invalid_unsent_files() + if self._active_contact != value: + self._screen.messageEdit.setPlainText(friend.curr_text) + self._active_contact = value + friend.reset_messages() + if not self._settings['save_history']: + friend.delete_old_messages() + self._messages.clear() + friend.load_corr() + corr = friend.get_corr()[-PAGE_SIZE:] + for message in corr: + self._messages_items_factory.create_message_item(message) + # 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._screen.account_status.setToolTip(friend.get_full_status()) avatar_path = friend.get_avatar_path() @@ -150,7 +116,7 @@ class ContactsManager: active_friend = property(get_active, set_active) - def set_active_by_number_and_type(self, number, is_friend): + def set_active_by_number_and_type(self, number, is_friend): # TODO: by id for i in range(len(self._contacts)): c = self._contacts[i] if c.number == number and (type(c) is Friend == is_friend): @@ -468,5 +434,6 @@ class ContactsManager: def _current_contact_avatar_changed(self, avatar_path): width = self._screen.account_avatar.width() pixmap = QtGui.QPixmap(avatar_path) - self._screen.account_avatar.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio, - QtCore.Qt.SmoothTransformation)) + self._screen.avatar_label.setPixmap(pixmap.scaled(width, width, + QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation)) diff --git a/toxygen/contacts/friend_factory.py b/toxygen/contacts/friend_factory.py index 6803c61..9ba859f 100644 --- a/toxygen/contacts/friend_factory.py +++ b/toxygen/contacts/friend_factory.py @@ -39,4 +39,4 @@ class FriendFactory: Method-factory :return: new widget for friend instance """ - return self._items_factory.friend_item() + return self._items_factory.create_friend_item() diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 8102f56..302dc0b 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -129,8 +129,8 @@ class Profile(basecontact.BaseContact): pixmap = self.get_curr_friend().get_pixmap() else: pixmap = self.get_pixmap() - return self._factory.message_item(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'], - message_type, append, pixmap) + return self._factory.create_message_item(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'], + message_type, append, pixmap) def create_gc_message_item(self, text, time, owner, name, message_type, append=True): pixmap = None @@ -139,24 +139,24 @@ class Profile(basecontact.BaseContact): pixmap = self.get_curr_friend().get_pixmap() else: pixmap = self.get_pixmap() - return self._factory.message_item(text, time, name, True, - message_type - 5, append, pixmap) + return self._factory.create_message_item(text, time, name, True, + message_type - 5, append, pixmap) def create_file_transfer_item(self, tm, append=True): data = list(tm.get_data()) data[3] = self.get_friend_by_number(data[4]).name if data[3] else self._name - return self._factory.file_transfer_item(data, append) + return self._factory.create_file_transfer_item(data, append) def create_unsent_file_item(self, message, append=True): data = message.get_data() - return self._factory.unsent_file_item(os.path.basename(data[0]), - os.path.getsize(data[0]) if data[1] is None else len(data[1]), - self.name, - data[2], - append) + return self._factory.create_unsent_file_item(os.path.basename(data[0]), + os.path.getsize(data[0]) if data[1] is None else len(data[1]), + self.name, + data[2], + append) def create_inline_item(self, data, append=True): - return self._factory.inline_item(data, append) + return self._factory.create_inline_item(data, append) # ----------------------------------------------------------------------------------------------------------------- # Reset diff --git a/toxygen/history/database.py b/toxygen/history/database.py index bf593d8..4be00c4 100644 --- a/toxygen/history/database.py +++ b/toxygen/history/database.py @@ -73,7 +73,7 @@ class Database: db = self._connect() try: cursor = db.cursor() - cursor.execute('INSERT INTO contacts VALUES (?);', (tox_id, )) + # cursor.execute('INSERT INTO contacts VALUES (?);', (tox_id, )) cursor.execute('CREATE TABLE id' + tox_id + '(' ' id INTEGER PRIMARY KEY,' ' message_id INTEGER,' diff --git a/toxygen/history/history_loader.py b/toxygen/history/history.py similarity index 98% rename from toxygen/history/history_loader.py rename to toxygen/history/history.py index a380ddb..6ded428 100644 --- a/toxygen/history/history_loader.py +++ b/toxygen/history/history.py @@ -3,7 +3,7 @@ from history.history_logs_generators import * # TODO: fix history loading and saving -class HistoryLoader: +class History: def __init__(self, contact_provider, db, settings): self._contact_provider = contact_provider @@ -45,6 +45,9 @@ class HistoryLoader: self._db.delete_messages(friend.tox_id) self._db.delete_friend_from_db(friend.tox_id) + def delete_message(self, message): + pass + def load_history(self): """ Tries to load next part of messages diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index 830e72e..dc70e8d 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -1,4 +1,5 @@ from history.database import MESSAGE_AUTHOR +from ui.messages_widgets import * MESSAGE_TYPE = { @@ -21,11 +22,10 @@ class MessageAuthor: class Message: - def __init__(self, message_id, message_type, author, time): + def __init__(self, message_type, author, time): self._time = time self._type = message_type self._author = author - self._message_id = message_id self._widget = None def get_type(self): @@ -43,19 +43,9 @@ class Message: time = property(get_time) - def get_message_id(self): - return self._message_id - - message_id = property(get_message_id) - - def get_type(self): - return self._type - - type = property(get_type) - - def get_widget(self): + def get_widget(self, *args): if self._widget is None: - self._widget = self._create_widget() + self._widget = self._create_widget(*args) return self._widget @@ -67,7 +57,7 @@ class Message: def mark_as_sent(self): self._author.author_type = MESSAGE_AUTHOR['ME'] - def _create_widget(self): + def _create_widget(self, *args): pass @@ -76,8 +66,8 @@ class TextMessage(Message): Plain text or action message """ - def __init__(self, id, message, owner, time, message_type): - super().__init__(id, message_type, owner, time) + def __init__(self, message, owner, time, message_type): + super().__init__(message_type, owner, time) self._message = message def get_text(self): @@ -88,8 +78,20 @@ class TextMessage(Message): def get_data(self): return self._message, self._owner, self._time, self._type - def _create_widget(self): - return + def _create_widget(self, *args): + return MessageItem(self, *args) + + +class OutgoingTextMessage(TextMessage): + + def __init__(self, message, owner, time, message_type, tox_message_id): + 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 + + tox_message_id = property(get_tox_message_id) class GroupChatMessage(TextMessage): @@ -107,7 +109,7 @@ class TransferMessage(Message): Message with info about file transfer """ - def __init__(self, id, owner, time, status, size, name, friend_number, file_number): + def __init__(self, owner, time, status, size, name, friend_number, file_number): super().__init__(MESSAGE_TYPE['FILE_TRANSFER'], owner, time) self._status = status self._size = size diff --git a/toxygen/messenger/messenger.py b/toxygen/messenger/messenger.py index 5a94f7f..968ca97 100644 --- a/toxygen/messenger/messenger.py +++ b/toxygen/messenger/messenger.py @@ -23,7 +23,7 @@ class Messenger(tox_save.ToxSave): def _create_message_item(self, text_message): # pixmap = self._contacts_manager.get_curr_contact().get_pixmap() - self._items_factory.message_item(text_message) + self._items_factory.create_message_item(text_message) # ----------------------------------------------------------------------------------------------------------------- # Messaging @@ -38,7 +38,7 @@ class Messenger(tox_save.ToxSave): """ t = util.get_unix_time() friend = self._get_friend_by_number(friend_number) - text_message = TextMessage(0, message, MessageAuthor(friend.name, MESSAGE_AUTHOR['FRIEND']), t, message_type) + text_message = TextMessage(message, MessageAuthor(friend.name, MESSAGE_AUTHOR['FRIEND']), t, message_type) if self._contacts_manager.is_friend_active(friend_number): # add message to list self._create_message_item(text_message) @@ -80,7 +80,7 @@ class Messenger(tox_save.ToxSave): else: message_id = 0 message_author = MessageAuthor(self._profile.name, MESSAGE_AUTHOR['NOT_SENT']) - message = TextMessage(message_id, text, message_author, t, message_type) + message = OutgoingTextMessage(text, message_author, t, message_type, message_id) friend.append_message(message) if self._contacts_manager.is_friend_active(friend_number): self._create_message_item(message) diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 7b7b759..c9571a2 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -95,8 +95,6 @@ def friend_status_message(contacts_manager, messenger): invoke_in_main_thread(friend.set_status_message, status_message) print('User #{} has new status'.format(friend_number)) invoke_in_main_thread(messenger.send_messages, friend_number) - if contacts_manager.is_friend_active(friend_number): - invoke_in_main_thread(contacts_manager.set_active) return wrapped diff --git a/toxygen/ui/items_factory.py b/toxygen/ui/items_factories.py similarity index 73% rename from toxygen/ui/items_factory.py rename to toxygen/ui/items_factories.py index 1c4e7a9..db2f065 100644 --- a/toxygen/ui/items_factory.py +++ b/toxygen/ui/items_factories.py @@ -1,28 +1,34 @@ from ui.list_items import * from ui.messages_widgets import * -# rename methods +class FriendItemsFactory: -class ItemsFactory: - - def __init__(self, settings, plugin_loader, smiley_loader, main_screen): - self._settings, self._plugin_loader = settings, plugin_loader - self._smiley_loader = smiley_loader - self._messages = main_screen.messages + def __init__(self, settings, main_screen): + self._settings = settings self._friends_list = main_screen.friends_list - self._message_edit = main_screen.messageEdit - def friend_item(self): + def create_friend_item(self): item = ContactItem(self._settings) elem = QtWidgets.QListWidgetItem(self._friends_list) elem.setSizeHint(QtCore.QSize(250, item.height())) self._friends_list.addItem(elem) self._friends_list.setItemWidget(elem, item) + return item - def message_item(self, message, append=True, pixmap=None): - item = MessageItem(self._settings, self._create_message_browser, message, self._messages) + +class MessagesItemsFactory: + + def __init__(self, settings, plugin_loader, smiley_loader, main_screen, history): + self._settings, self._plugin_loader = settings, plugin_loader + self._smiley_loader, self._history = smiley_loader, history + self._messages = main_screen.messages + self._message_edit = main_screen.messageEdit + + def create_message_item(self, message, append=True, pixmap=None): + item = message.get_widget(self._settings, self._create_message_browser, + self._history.delete_message, self._messages) if pixmap is not None: item.set_avatar(pixmap) elem = QtWidgets.QListWidgetItem() @@ -32,9 +38,10 @@ class ItemsFactory: else: self._messages.insertItem(0, elem) self._messages.setItemWidget(elem, item) + return item - def inline_item(self, data, append): + def create_inline_item(self, data, append): elem = QtWidgets.QListWidgetItem() item = InlineImageItem(data, self._messages.width(), elem) elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height())) @@ -43,9 +50,10 @@ class ItemsFactory: else: self._messages.insertItem(0, elem) self._messages.setItemWidget(elem, item) + return item - def unsent_file_item(self, file_name, size, name, time, append): + def create_unsent_file_item(self, file_name, size, name, time, append): item = UnsentFileItem(file_name, size, name, @@ -58,9 +66,10 @@ class ItemsFactory: else: self._messages.insertItem(0, elem) self._messages.setItemWidget(elem, item) + return item - def file_transfer_item(self, data, append): + def create_file_transfer_item(self, data, append): data.append(self._messages.width()) item = FileTransferItem(*data) elem = QtWidgets.QListWidgetItem() @@ -70,8 +79,13 @@ class ItemsFactory: 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) diff --git a/toxygen/ui/list_items.py b/toxygen/ui/list_items.py index a305ffc..c9638ec 100644 --- a/toxygen/ui/list_items.py +++ b/toxygen/ui/list_items.py @@ -4,10 +4,7 @@ from contacts import profile from file_transfers.file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR from utils.util import * from ui.widgets import DataLabel, create_menu -import html as h -import smileys from user_data import settings -import re class ContactItem(QtWidgets.QWidget): diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index f0a1ef5..a8a7c13 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -604,11 +604,13 @@ class MainWindow(QtWidgets.QMainWindow): extension = 'txt' if as_text else 'html' file_name, _ = util_ui.save_file_dialog(util_ui.tr('Choose file name'), extension) - if file_name: - if not file_name.endswith('.' + extension): - file_name += '.' + extension - with open(file_name, 'wt') as fl: - fl.write(s) + if not file_name: + return + + 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._contacts_manager.set_alias(num) diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index 0585c4c..d023cff 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -1,6 +1,5 @@ from PyQt5 import QtCore, QtGui, QtWidgets from user_data.settings import * -from contacts.profile import Profile from utils.util import * from ui.widgets import CenteredWidget, DataLabel, LineEdit, RubberBandWindow import pyaudio @@ -717,7 +716,6 @@ class InterfaceSettings(CenteredWidget): app.translator.load(curr_directory() + '/translations/' + path) app.installTranslator(app.translator) self._settings['message_font_size'] = self.messages_font_size.currentIndex() + 10 - Profile.get_instance().update() self._settings.save() if restart: util_ui.message_box(util_ui.tr('Restart app to apply settings'), util_ui.tr('Restart required')) diff --git a/toxygen/ui/messages_widgets.py b/toxygen/ui/messages_widgets.py index 0e31891..0f510ea 100644 --- a/toxygen/ui/messages_widgets.py +++ b/toxygen/ui/messages_widgets.py @@ -115,7 +115,7 @@ class MessageBrowser(QtWidgets.QTextBrowser): if arr[i].startswith('>'): arr[i] = '' + arr[i][4:] + '' text = '
'.join(arr) - text = self._smileys_loader.add_smileys_to_text(text, self) # smileys + text = self._smileys_loader.add_smileys_to_text(text, self) return text @@ -123,8 +123,10 @@ class MessageItem(QtWidgets.QWidget): """ Message in messages list """ - def __init__(self, settings, message_browser_factory_method, text_message, parent=None): + def __init__(self, text_message, settings, message_browser_factory_method, delete_action, parent=None): QtWidgets.QWidget.__init__(self, parent) + 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.setTextFormat(QtCore.Qt.PlainText) @@ -169,12 +171,11 @@ class MessageItem(QtWidgets.QWidget): self.listMenu.show() def delete(self): - pr = profile.Profile.get_instance() - pr.delete_message(self._time) + self._delete_action(self._message) def mark_as_sent(self): if self.t: - self.time.setText(convert_time(self._time)) + self.time.setText(util.convert_time(self._time)) self.t = False return True return False From eef02a1173f39424d27bafaadacb07742aa1605b Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 15 May 2018 22:51:42 +0300 Subject: [PATCH 026/138] history fixes - db cleanup --- toxygen/app.py | 5 +- toxygen/contacts/contacts_manager.py | 1 + toxygen/contacts/profile.py | 56 +-- .../file_transfers/file_transfers_handler.py | 2 +- toxygen/history/database.py | 55 +-- toxygen/history/history.py | 86 +++-- toxygen/ui/contact_items.py | 100 +++++ toxygen/ui/items_factories.py | 12 +- toxygen/ui/list_items.py | 343 ------------------ toxygen/ui/main_screen.py | 2 +- toxygen/ui/messages_widgets.py | 242 ++++++++++++ 11 files changed, 418 insertions(+), 486 deletions(-) create mode 100644 toxygen/ui/contact_items.py delete mode 100644 toxygen/ui/list_items.py diff --git a/toxygen/app.py b/toxygen/app.py index 9f41b47..ef5e351 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -302,9 +302,10 @@ class App: friend_items_factory = FriendItemsFactory(self._settings, self._ms) self._friend_factory = FriendFactory(self._profile_manager, self._settings, self._tox, db, friend_items_factory) self._contacts_provider = ContactProvider(self._tox, self._friend_factory) - history = History(self._contacts_provider, db, self._settings) + history = None messages_items_factory = MessagesItemsFactory(self._settings, self._plugin_loader, self._smiley_loader, - self._ms, history) + 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) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index c3db871..b65acfa 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -296,6 +296,7 @@ class ContactsManager: Adds friend to list """ self._tox.friend_add_norequest(tox_id) + self._history.add_friend_to_db(tox_id) friend = self._contact_provider.get_friend_by_public_key(tox_id) self._contacts.append(friend) diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 302dc0b..af5b87e 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -1,11 +1,10 @@ from contacts.friend import * -from user_data.settings import * -from history.database import * from file_transfers.file_transfers import * import time from contacts import basecontact from contacts.group_chat import * import utils.ui as util_ui +import random class Profile(basecontact.BaseContact): @@ -70,7 +69,6 @@ class Profile(basecontact.BaseContact): def new_nospam(self): """Sets new nospam part of tox id""" - import random self._tox.self_set_nospam(random.randint(0, 4294967295)) # no spam - uint32 self._tox_id = self._tox.self_get_address() @@ -106,58 +104,6 @@ class Profile(basecontact.BaseContact): while i < self._messages.count() and not self._messages.itemWidget(self._messages.item(i)).mark_as_sent(): i += 1 - def delete_message(self, message_id): - friend = self.get_curr_friend() - friend.delete_message(time) - self._history.delete_message(friend.tox_id, message_id) - self.update() - - # ----------------------------------------------------------------------------------------------------------------- - # Friend, message and file transfer items creation - # ----------------------------------------------------------------------------------------------------------------- - - def create_message_item(self, text, time, owner, message_type, append=True): - if message_type == MESSAGE_TYPE['INFO_MESSAGE']: - name = '' - elif owner == MESSAGE_OWNER['FRIEND']: - name = self.get_active_name() - else: - name = self._name - pixmap = None - if self._show_avatars: - if owner == MESSAGE_OWNER['FRIEND']: - pixmap = self.get_curr_friend().get_pixmap() - else: - pixmap = self.get_pixmap() - return self._factory.create_message_item(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'], - message_type, append, pixmap) - - def create_gc_message_item(self, text, time, owner, name, message_type, append=True): - pixmap = None - if self._show_avatars: - if owner == MESSAGE_OWNER['FRIEND']: - pixmap = self.get_curr_friend().get_pixmap() - else: - pixmap = self.get_pixmap() - return self._factory.create_message_item(text, time, name, True, - message_type - 5, append, pixmap) - - def create_file_transfer_item(self, tm, append=True): - data = list(tm.get_data()) - data[3] = self.get_friend_by_number(data[4]).name if data[3] else self._name - return self._factory.create_file_transfer_item(data, append) - - def create_unsent_file_item(self, message, append=True): - data = message.get_data() - return self._factory.create_unsent_file_item(os.path.basename(data[0]), - os.path.getsize(data[0]) if data[1] is None else len(data[1]), - self.name, - data[2], - append) - - def create_inline_item(self, data, append=True): - return self._factory.create_inline_item(data, append) - # ----------------------------------------------------------------------------------------------------------------- # Reset # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index 073b8b6..8da0811 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -1,7 +1,7 @@ from file_transfers.file_transfers import * from messenger.messages import * from history.database import MESSAGE_AUTHOR -from ui.list_items import * +from ui.contact_items import * from PyQt5 import QtWidgets import utils.util as util diff --git a/toxygen/history/database.py b/toxygen/history/database.py index 4be00c4..78c0bea 100644 --- a/toxygen/history/database.py +++ b/toxygen/history/database.py @@ -37,16 +37,6 @@ class Database: except Exception as ex: util.log('Db reading error: ' + str(ex)) os.remove(path) - db = self._connect() - cursor = db.cursor() - cursor.execute('CREATE TABLE IF NOT EXISTS contacts (' - ' tox_id TEXT PRIMARY KEY,' - ' contact_type INTEGER' - ')') - db.close() - - def _connect(self): - return connect(self._path, timeout=TIMEOUT) # ----------------------------------------------------------------------------------------------------------------- # Public methods @@ -73,10 +63,8 @@ class Database: db = self._connect() try: cursor = db.cursor() - # cursor.execute('INSERT INTO contacts VALUES (?);', (tox_id, )) - cursor.execute('CREATE TABLE id' + tox_id + '(' + cursor.execute('CREATE TABLE IF NOT EXISTS id' + tox_id + '(' ' id INTEGER PRIMARY KEY,' - ' message_id INTEGER,' ' author_name TEXT,' ' message TEXT,' ' author INTEGER,' @@ -94,7 +82,6 @@ class Database: db = self._connect() try: cursor = db.cursor() - cursor.execute('DELETE FROM contacts WHERE tox_id=?;', (tox_id, )) cursor.execute('DROP TABLE id' + tox_id + ';') db.commit() except: @@ -103,20 +90,12 @@ class Database: finally: db.close() - def friend_exists_in_db(self, tox_id): - db = self._connect() - cursor = db.cursor() - cursor.execute('SELECT 1 FROM contacts 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): db = self._connect() try: cursor = db.cursor() cursor.executemany('INSERT INTO id' + tox_id + - '(message, message_id, author_name, author, unix_time, message_type) ' + + '(message, author_name, author, unix_time, message_type) ' + 'VALUES (?, ?, ?, ?, ?, ?);', messages_iter) db.commit() except: @@ -130,7 +109,7 @@ class Database: try: cursor = db.cursor() cursor.execute('UPDATE id' + tox_id + ' SET author = 0 ' - 'WHERE message_id = ' + str(message_id) + ' AND author = 2;') + 'WHERE id = ' + str(message_id) + ' AND author = 2;') db.commit() except: print('Database is locked!') @@ -163,8 +142,7 @@ class Database: db.close() def messages_getter(self, tox_id): - if not self.friend_exists_in_db(tox_id): - self.add_friend_to_db(tox_id) + self.add_friend_to_db(tox_id) return Database.MessageGetter(self._path, tox_id) @@ -180,15 +158,6 @@ class Database: self._tox_id = tox_id self._db = self._cursor = None - def _connect(self): - self._db = connect(self._path, timeout=TIMEOUT) - self._cursor = self._db.cursor() - self._cursor.execute('SELECT id, message_id, message, author, unix_time, message_type FROM id' + - self._tox_id + ' ORDER BY unix_time DESC;') - - def _disconnect(self): - self._db.close() - def get_one(self): return self.get(1) @@ -214,3 +183,19 @@ class Database: 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 id, message, author, unix_time, message_type 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) diff --git a/toxygen/history/history.py b/toxygen/history/history.py index 6ded428..234b640 100644 --- a/toxygen/history/history.py +++ b/toxygen/history/history.py @@ -1,14 +1,15 @@ from history.history_logs_generators import * -# TODO: fix history loading and saving - class History: - def __init__(self, contact_provider, db, settings): + 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 def __del__(self): del self._db @@ -23,8 +24,7 @@ class History: """ if self._settings['save_db']: for friend in self._contact_provider.get_all_friends(): - if not self._db.friend_exists_in_db(friend.tox_id): - self._db.add_friend_to_db(friend.tox_id) + self._db.add_friend_to_db(friend.tox_id) if not self._settings['save_unsent_only']: messages = friend.get_corr_for_saving() else: @@ -41,68 +41,50 @@ class History: Clear chat history """ friend.clear_corr(save_unsent) - if self._db.friend_exists_in_db(friend.tox_id): - self._db.delete_messages(friend.tox_id) - self._db.delete_friend_from_db(friend.tox_id) + self._db.delete_friend_from_db(friend.tox_id) def delete_message(self, message): pass - def load_history(self): + def load_history(self, friend): """ Tries to load next part of messages """ - if not self._load_db: + if self._is_loading: return - self._load_db = False - friend = self.get_curr_friend() + self._is_loading = True friend.load_corr(False) - data = friend.get_corr() - if not data: + messages = friend.get_corr() + if not messages: + self._is_loading = False return - data.reverse() - data = data[self._messages.count():self._messages.count() + PAGE_SIZE] - for message in data: + messages.reverse() + messages = messages[self._messages.count():self._messages.count() + PAGE_SIZE] + for message in messages: if message.get_type() <= 1: # text message - data = message.get_data() - self.create_message_item(data[0], - data[2], - data[1], - data[3], - False) + self._create_message_item(message) elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: # file transfer if message.get_status() is None: - self.create_unsent_file_item(message) + self._create_unsent_file_item(message) continue - item = self.create_file_transfer_item(message, False) - if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer - try: - ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())] - ft.set_state_changed_handler(item.update_transfer_state) - ft.signal() - except: - print('Incoming not started transfer - no info found') + self._create_file_transfer_item(message) elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline image - self.create_inline_item(message.get_data(), False) + self._create_inline_item(message) else: # info message - data = message.get_data() - self.create_message_item(data[0], - data[2], - '', - data[3], - False) - self._load_db = True + self._create_message_item(message) + self._is_loading = False def get_message_getter(self, friend_public_key): - if not self._db.friend_exists_in_db(friend_public_key): - self._db.add_friend_to_db(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.clear_history(friend) - if self._db.friend_exists_in_db(friend.tox_id): - self._db.delete_friend_from_db(friend.tox_id) + 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 export_history(contact, as_text=True, _range=None): @@ -117,3 +99,19 @@ class History: 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) diff --git a/toxygen/ui/contact_items.py b/toxygen/ui/contact_items.py new file mode 100644 index 0000000..25b3c0a --- /dev/null +++ b/toxygen/ui/contact_items.py @@ -0,0 +1,100 @@ +from wrapper.toxcore_enums_and_consts import * +from PyQt5 import QtCore, QtGui, QtWidgets +from contacts import profile +from file_transfers.file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR +from utils.util import * +from ui.widgets import DataLabel, create_menu +from user_data import settings + + +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) diff --git a/toxygen/ui/items_factories.py b/toxygen/ui/items_factories.py index db2f065..dc9c002 100644 --- a/toxygen/ui/items_factories.py +++ b/toxygen/ui/items_factories.py @@ -1,4 +1,4 @@ -from ui.list_items import * +from ui.contact_items import * from ui.messages_widgets import * @@ -18,17 +18,19 @@ class FriendItemsFactory: return item +# TODO: accept messages everywhere instead of params + class MessagesItemsFactory: - def __init__(self, settings, plugin_loader, smiley_loader, main_screen, history): + def __init__(self, settings, plugin_loader, smiley_loader, main_screen, delete_action): self._settings, self._plugin_loader = settings, plugin_loader - self._smiley_loader, self._history = smiley_loader, history + self._smiley_loader, self._delete_action = smiley_loader, delete_action self._messages = main_screen.messages self._message_edit = main_screen.messageEdit def create_message_item(self, message, append=True, pixmap=None): item = message.get_widget(self._settings, self._create_message_browser, - self._history.delete_message, self._messages) + self._delete_action, self._messages) if pixmap is not None: item.set_avatar(pixmap) elem = QtWidgets.QListWidgetItem() @@ -69,7 +71,7 @@ class MessagesItemsFactory: return item - def create_file_transfer_item(self, data, append): + def create_file_transfer_item(self, data, append=True): data.append(self._messages.width()) item = FileTransferItem(*data) elem = QtWidgets.QListWidgetItem() diff --git a/toxygen/ui/list_items.py b/toxygen/ui/list_items.py deleted file mode 100644 index c9638ec..0000000 --- a/toxygen/ui/list_items.py +++ /dev/null @@ -1,343 +0,0 @@ -from wrapper.toxcore_enums_and_consts import * -from PyQt5 import QtCore, QtGui, QtWidgets -from contacts import profile -from file_transfers.file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR -from utils.util import * -from ui.widgets import DataLabel, create_menu -from user_data import settings - - -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) - - -class FileTransferItem(QtWidgets.QListWidget): - - def __init__(self, file_name, size, time, user, friend_number, file_number, state, width, parent=None): - - QtWidgets.QListWidget.__init__(self, parent) - self.resize(QtCore.QSize(width, 34)) - if state == TOX_FILE_TRANSFER_STATE['CANCELLED']: - self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') - elif state in PAUSED_FILE_TRANSFERS: - self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') - else: - self.setStyleSheet('QListWidget { border: 1px solid green; }') - self.state = state - - self.name = DataLabel(self) - self.name.setGeometry(QtCore.QRect(3, 7, 95, 25)) - self.name.setTextFormat(QtCore.Qt.PlainText) - font = QtGui.QFont() - # FIXME - font.setFamily(settings.Settings.get_instance()['font']) - font.setPointSize(11) - font.setBold(True) - self.name.setFont(font) - self.name.setText(user) - - self.time = QtWidgets.QLabel(self) - self.time.setGeometry(QtCore.QRect(width - 60, 7, 50, 25)) - font.setPointSize(10) - font.setBold(False) - self.time.setFont(font) - self.time.setText(convert_time(time)) - - self.cancel = QtWidgets.QPushButton(self) - self.cancel.setGeometry(QtCore.QRect(width - 125, 2, 30, 30)) - pixmap = QtGui.QPixmap(curr_directory() + '/images/decline.png') - icon = QtGui.QIcon(pixmap) - self.cancel.setIcon(icon) - self.cancel.setIconSize(QtCore.QSize(30, 30)) - self.cancel.setVisible(state in ACTIVE_FILE_TRANSFERS) - self.cancel.clicked.connect(lambda: self.cancel_transfer(friend_number, file_number)) - self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}') - - self.accept_or_pause = QtWidgets.QPushButton(self) - self.accept_or_pause.setGeometry(QtCore.QRect(width - 170, 2, 30, 30)) - if state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: - self.accept_or_pause.setVisible(True) - self.button_update('accept') - elif state in DO_NOT_SHOW_ACCEPT_BUTTON: - self.accept_or_pause.setVisible(False) - elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: # setup for continue - self.accept_or_pause.setVisible(True) - self.button_update('resume') - else: # pause - self.accept_or_pause.setVisible(True) - 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.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none}') - - self.pb = QtWidgets.QProgressBar(self) - self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20)) - self.pb.setValue(0) - self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }') - self.pb.setVisible(state in SHOW_PROGRESS_BAR) - - self.file_name = DataLabel(self) - self.file_name.setGeometry(QtCore.QRect(210, 7, width - 420, 20)) - font.setPointSize(12) - self.file_name.setFont(font) - file_size = size // 1024 - if not file_size: - file_size = '{}B'.format(size) - elif file_size >= 1024: - file_size = '{}MB'.format(file_size // 1024) - else: - file_size = '{}KB'.format(file_size) - file_data = '{} {}'.format(file_size, file_name) - self.file_name.setText(file_data) - self.file_name.setToolTip(file_name) - self.saved_name = file_name - self.time_left = QtWidgets.QLabel(self) - self.time_left.setGeometry(QtCore.QRect(width - 92, 7, 30, 20)) - font.setPointSize(10) - self.time_left.setFont(font) - self.time_left.setVisible(state == TOX_FILE_TRANSFER_STATE['RUNNING']) - self.setFocusPolicy(QtCore.Qt.NoFocus) - self.paused = False - - def cancel_transfer(self, friend_number, file_number): - pr = profile.Profile.get_instance() - pr.cancel_transfer(friend_number, file_number) - self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') - self.cancel.setVisible(False) - self.accept_or_pause.setVisible(False) - self.pb.setVisible(False) - - def accept_or_pause_transfer(self, friend_number, file_number, size): - if self.state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: - directory = QtWidgets.QFileDialog.getExistingDirectory(self, - QtWidgets.QApplication.translate("MainWindow", 'Choose folder'), - curr_directory(), - QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) - self.pb.setVisible(True) - if directory: - pr = profile.Profile.get_instance() - pr.accept_transfer(self, directory + '/' + self.saved_name, friend_number, file_number, size) - self.button_update('pause') - elif self.state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: # resume - self.paused = False - profile.Profile.get_instance().resume_transfer(friend_number, file_number) - self.button_update('pause') - self.state = TOX_FILE_TRANSFER_STATE['RUNNING'] - else: # pause - self.paused = True - self.state = TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER'] - profile.Profile.get_instance().pause_transfer(friend_number, file_number) - self.button_update('resume') - self.accept_or_pause.clearFocus() - - def button_update(self, path): - pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(path)) - icon = QtGui.QIcon(pixmap) - self.accept_or_pause.setIcon(icon) - self.accept_or_pause.setIconSize(QtCore.QSize(30, 30)) - - def update_transfer_state(self, state, progress, time): - self.pb.setValue(int(progress * 100)) - if time + 1: - m, s = divmod(time, 60) - self.time_left.setText('{0:02d}:{1:02d}'.format(m, s)) - if self.state != state and self.state in ACTIVE_FILE_TRANSFERS: - if state == TOX_FILE_TRANSFER_STATE['CANCELLED']: - self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') - self.cancel.setVisible(False) - self.accept_or_pause.setVisible(False) - self.pb.setVisible(False) - self.state = state - self.time_left.setVisible(False) - elif state == TOX_FILE_TRANSFER_STATE['FINISHED']: - self.accept_or_pause.setVisible(False) - self.pb.setVisible(False) - self.cancel.setVisible(False) - self.setStyleSheet('QListWidget { border: 1px solid green; }') - self.state = state - self.time_left.setVisible(False) - elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND']: - self.accept_or_pause.setVisible(False) - self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') - self.state = state - self.time_left.setVisible(False) - elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: - self.button_update('resume') # setup button continue - self.setStyleSheet('QListWidget { border: 1px solid green; }') - self.state = state - self.time_left.setVisible(False) - elif state == TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']: - self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') - self.accept_or_pause.setVisible(False) - self.time_left.setVisible(False) - self.pb.setVisible(False) - elif not self.paused: # active - self.pb.setVisible(True) - self.accept_or_pause.setVisible(True) # setup to pause - self.button_update('pause') - self.setStyleSheet('QListWidget { border: 1px solid green; }') - self.state = state - self.time_left.setVisible(True) - - def mark_as_sent(self): - return False - - -class UnsentFileItem(FileTransferItem): - - def __init__(self, file_name, size, user, time, width, parent=None): - super(UnsentFileItem, self).__init__(file_name, size, time, user, -1, -1, - TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'], width, parent) - self._time = time - self.pb.setVisible(False) - movie = QtGui.QMovie(join_path(get_images_directory(), 'spinner.gif')) - self.time.setMovie(movie) - movie.start() - - def cancel_transfer(self, *args): - pr = profile.Profile.get_instance() - pr.cancel_not_started_transfer(self._time) - - -class InlineImageItem(QtWidgets.QScrollArea): - - def __init__(self, data, width, elem): - - QtWidgets.QScrollArea.__init__(self) - self.setFocusPolicy(QtCore.Qt.NoFocus) - self._elem = elem - self._image_label = QtWidgets.QLabel(self) - self._image_label.raise_() - self.setWidget(self._image_label) - self._image_label.setScaledContents(False) - self._pixmap = QtGui.QPixmap() - self._pixmap.loadFromData(data, 'PNG') - self._max_size = width - 30 - self._resize_needed = not (self._pixmap.width() <= self._max_size) - self._full_size = not self._resize_needed - if not self._resize_needed: - self._image_label.setPixmap(self._pixmap) - self.resize(QtCore.QSize(self._max_size + 5, self._pixmap.height() + 5)) - self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height()) - else: - pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio) - self._image_label.setPixmap(pixmap) - self.resize(QtCore.QSize(self._max_size + 5, pixmap.height())) - self._image_label.setGeometry(5, 0, self._max_size + 5, pixmap.height()) - self._elem.setSizeHint(QtCore.QSize(self.width(), self.height())) - - def mouseReleaseEvent(self, event): - if event.button() == QtCore.Qt.LeftButton and self._resize_needed: # scale inline - if self._full_size: - pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio) - self._image_label.setPixmap(pixmap) - self.resize(QtCore.QSize(self._max_size, pixmap.height())) - self._image_label.setGeometry(5, 0, pixmap.width(), pixmap.height()) - else: - self._image_label.setPixmap(self._pixmap) - self.resize(QtCore.QSize(self._max_size, self._pixmap.height() + 17)) - self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height()) - self._full_size = not self._full_size - self._elem.setSizeHint(QtCore.QSize(self.width(), self.height())) - elif event.button() == QtCore.Qt.RightButton: # save inline - # TODO: dialog - directory = QtWidgets.QFileDialog.getExistingDirectory(self, - QtWidgets.QApplication.translate("MainWindow", - 'Choose folder'), - curr_directory(), - QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) - if directory: - fl = QtCore.QFile(directory + '/toxygen_inline_' + curr_time().replace(':', '_') + '.png') - self._pixmap.save(fl, 'PNG') - - def mark_as_sent(self): - return False diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index a8a7c13..cce78a4 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -1,5 +1,5 @@ from contacts.profile import * -from ui.list_items import * +from ui.contact_items import * from ui.widgets import MultilineEdit, ComboBox from ui.main_screen_widgets import * import utils.util as util diff --git a/toxygen/ui/messages_widgets.py b/toxygen/ui/messages_widgets.py index 0f510ea..dea7a5c 100644 --- a/toxygen/ui/messages_widgets.py +++ b/toxygen/ui/messages_widgets.py @@ -213,3 +213,245 @@ class MessageItem(QtWidgets.QWidget): i += len(sub) return text + +class FileTransferItem(QtWidgets.QListWidget): + + def __init__(self, file_name, size, time, user, friend_number, file_number, state, width, parent=None): + + QtWidgets.QListWidget.__init__(self, parent) + self.resize(QtCore.QSize(width, 34)) + if state == TOX_FILE_TRANSFER_STATE['CANCELLED']: + self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') + elif state in PAUSED_FILE_TRANSFERS: + self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') + else: + self.setStyleSheet('QListWidget { border: 1px solid green; }') + self.state = state + + self.name = DataLabel(self) + self.name.setGeometry(QtCore.QRect(3, 7, 95, 25)) + self.name.setTextFormat(QtCore.Qt.PlainText) + font = QtGui.QFont() + # FIXME + font.setFamily(settings.Settings.get_instance()['font']) + font.setPointSize(11) + font.setBold(True) + self.name.setFont(font) + self.name.setText(user) + + self.time = QtWidgets.QLabel(self) + self.time.setGeometry(QtCore.QRect(width - 60, 7, 50, 25)) + font.setPointSize(10) + font.setBold(False) + self.time.setFont(font) + self.time.setText(convert_time(time)) + + self.cancel = QtWidgets.QPushButton(self) + self.cancel.setGeometry(QtCore.QRect(width - 125, 2, 30, 30)) + pixmap = QtGui.QPixmap(curr_directory() + '/images/decline.png') + icon = QtGui.QIcon(pixmap) + self.cancel.setIcon(icon) + self.cancel.setIconSize(QtCore.QSize(30, 30)) + self.cancel.setVisible(state in ACTIVE_FILE_TRANSFERS) + self.cancel.clicked.connect(lambda: self.cancel_transfer(friend_number, file_number)) + self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}') + + self.accept_or_pause = QtWidgets.QPushButton(self) + self.accept_or_pause.setGeometry(QtCore.QRect(width - 170, 2, 30, 30)) + if state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: + self.accept_or_pause.setVisible(True) + self.button_update('accept') + elif state in DO_NOT_SHOW_ACCEPT_BUTTON: + self.accept_or_pause.setVisible(False) + elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: # setup for continue + self.accept_or_pause.setVisible(True) + self.button_update('resume') + else: # pause + self.accept_or_pause.setVisible(True) + 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.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none}') + + self.pb = QtWidgets.QProgressBar(self) + self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20)) + self.pb.setValue(0) + self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }') + self.pb.setVisible(state in SHOW_PROGRESS_BAR) + + self.file_name = DataLabel(self) + self.file_name.setGeometry(QtCore.QRect(210, 7, width - 420, 20)) + font.setPointSize(12) + self.file_name.setFont(font) + file_size = size // 1024 + if not file_size: + file_size = '{}B'.format(size) + elif file_size >= 1024: + file_size = '{}MB'.format(file_size // 1024) + else: + file_size = '{}KB'.format(file_size) + file_data = '{} {}'.format(file_size, file_name) + self.file_name.setText(file_data) + self.file_name.setToolTip(file_name) + self.saved_name = file_name + self.time_left = QtWidgets.QLabel(self) + self.time_left.setGeometry(QtCore.QRect(width - 92, 7, 30, 20)) + font.setPointSize(10) + self.time_left.setFont(font) + self.time_left.setVisible(state == TOX_FILE_TRANSFER_STATE['RUNNING']) + self.setFocusPolicy(QtCore.Qt.NoFocus) + self.paused = False + + def cancel_transfer(self, friend_number, file_number): + pr = profile.Profile.get_instance() + pr.cancel_transfer(friend_number, file_number) + self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') + self.cancel.setVisible(False) + self.accept_or_pause.setVisible(False) + self.pb.setVisible(False) + + def accept_or_pause_transfer(self, friend_number, file_number, size): + if self.state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: + directory = QtWidgets.QFileDialog.getExistingDirectory(self, + QtWidgets.QApplication.translate("MainWindow", 'Choose folder'), + curr_directory(), + QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) + self.pb.setVisible(True) + if directory: + pr = profile.Profile.get_instance() + pr.accept_transfer(self, directory + '/' + self.saved_name, friend_number, file_number, size) + self.button_update('pause') + elif self.state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: # resume + self.paused = False + profile.Profile.get_instance().resume_transfer(friend_number, file_number) + self.button_update('pause') + self.state = TOX_FILE_TRANSFER_STATE['RUNNING'] + else: # pause + self.paused = True + self.state = TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER'] + profile.Profile.get_instance().pause_transfer(friend_number, file_number) + self.button_update('resume') + self.accept_or_pause.clearFocus() + + def button_update(self, path): + pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(path)) + icon = QtGui.QIcon(pixmap) + self.accept_or_pause.setIcon(icon) + self.accept_or_pause.setIconSize(QtCore.QSize(30, 30)) + + def update_transfer_state(self, state, progress, time): + self.pb.setValue(int(progress * 100)) + if time + 1: + m, s = divmod(time, 60) + self.time_left.setText('{0:02d}:{1:02d}'.format(m, s)) + if self.state != state and self.state in ACTIVE_FILE_TRANSFERS: + if state == TOX_FILE_TRANSFER_STATE['CANCELLED']: + self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') + self.cancel.setVisible(False) + self.accept_or_pause.setVisible(False) + self.pb.setVisible(False) + self.state = state + self.time_left.setVisible(False) + elif state == TOX_FILE_TRANSFER_STATE['FINISHED']: + self.accept_or_pause.setVisible(False) + self.pb.setVisible(False) + self.cancel.setVisible(False) + self.setStyleSheet('QListWidget { border: 1px solid green; }') + self.state = state + self.time_left.setVisible(False) + elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND']: + self.accept_or_pause.setVisible(False) + self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') + self.state = state + self.time_left.setVisible(False) + elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: + self.button_update('resume') # setup button continue + self.setStyleSheet('QListWidget { border: 1px solid green; }') + self.state = state + self.time_left.setVisible(False) + elif state == TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']: + self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') + self.accept_or_pause.setVisible(False) + self.time_left.setVisible(False) + self.pb.setVisible(False) + elif not self.paused: # active + self.pb.setVisible(True) + self.accept_or_pause.setVisible(True) # setup to pause + self.button_update('pause') + self.setStyleSheet('QListWidget { border: 1px solid green; }') + self.state = state + self.time_left.setVisible(True) + + def mark_as_sent(self): + return False + + +class UnsentFileItem(FileTransferItem): + + def __init__(self, file_name, size, user, time, width, parent=None): + super(UnsentFileItem, self).__init__(file_name, size, time, user, -1, -1, + TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'], width, parent) + self._time = time + self.pb.setVisible(False) + movie = QtGui.QMovie(join_path(get_images_directory(), 'spinner.gif')) + self.time.setMovie(movie) + movie.start() + + def cancel_transfer(self, *args): + pr = profile.Profile.get_instance() + pr.cancel_not_started_transfer(self._time) + + +class InlineImageItem(QtWidgets.QScrollArea): + + def __init__(self, data, width, elem): + + QtWidgets.QScrollArea.__init__(self) + self.setFocusPolicy(QtCore.Qt.NoFocus) + self._elem = elem + self._image_label = QtWidgets.QLabel(self) + self._image_label.raise_() + self.setWidget(self._image_label) + self._image_label.setScaledContents(False) + self._pixmap = QtGui.QPixmap() + self._pixmap.loadFromData(data, 'PNG') + self._max_size = width - 30 + self._resize_needed = not (self._pixmap.width() <= self._max_size) + self._full_size = not self._resize_needed + if not self._resize_needed: + self._image_label.setPixmap(self._pixmap) + self.resize(QtCore.QSize(self._max_size + 5, self._pixmap.height() + 5)) + self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height()) + else: + pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio) + self._image_label.setPixmap(pixmap) + self.resize(QtCore.QSize(self._max_size + 5, pixmap.height())) + self._image_label.setGeometry(5, 0, self._max_size + 5, pixmap.height()) + self._elem.setSizeHint(QtCore.QSize(self.width(), self.height())) + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.LeftButton and self._resize_needed: # scale inline + if self._full_size: + pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio) + self._image_label.setPixmap(pixmap) + self.resize(QtCore.QSize(self._max_size, pixmap.height())) + self._image_label.setGeometry(5, 0, pixmap.width(), pixmap.height()) + else: + self._image_label.setPixmap(self._pixmap) + self.resize(QtCore.QSize(self._max_size, self._pixmap.height() + 17)) + self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height()) + self._full_size = not self._full_size + self._elem.setSizeHint(QtCore.QSize(self.width(), self.height())) + elif event.button() == QtCore.Qt.RightButton: # save inline + # TODO: dialog + directory = QtWidgets.QFileDialog.getExistingDirectory(self, + QtWidgets.QApplication.translate("MainWindow", + 'Choose folder'), + curr_directory(), + QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) + if directory: + fl = QtCore.QFile(directory + '/toxygen_inline_' + curr_time().replace(':', '_') + '.png') + self._pixmap.save(fl, 'PNG') + + def mark_as_sent(self): + return False From 2883ce5c4c0ff2858f65a562a8c82f65d5ae0bc0 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 16 May 2018 14:10:24 +0300 Subject: [PATCH 027/138] messaging - db and saving fixes --- toxygen/app.py | 1 + toxygen/contacts/contact.py | 22 +++++++++++++------- toxygen/history/database.py | 6 +++--- toxygen/history/history.py | 17 +++++++++++----- toxygen/messenger/messages.py | 38 ++++++++++++++++++++++++----------- 5 files changed, 57 insertions(+), 27 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index ef5e351..2d12876 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -309,6 +309,7 @@ class App: 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._messenger = Messenger(self._tox, self._plugin_loader, self._ms, self._contacts_manager, self._contacts_provider, messages_items_factory, profile) self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider) diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index 4c19e33..0718e5e 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -53,7 +53,7 @@ class Contact(basecontact.BaseContact): data.reverse() else: return - 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._history_loaded = True @@ -66,7 +66,7 @@ class Contact(basecontact.BaseContact): data = list(self._message_getter.get_all()) if data is not None and len(data): data.reverse() - data = list(map(lambda tupl: TextMessage(*tupl), data)) + data = list(map(lambda p: self._get_text_message(p), data)) self._corr = data + self._corr self._history_loaded = True @@ -76,7 +76,7 @@ class Contact(basecontact.BaseContact): :return: list of unsaved messages or [] """ messages = list(filter(lambda x: x.get_type() <= 1, self._corr)) - return list(map(lambda x: x.get_data(), messages[-self._unsaved_messages:])) if self._unsaved_messages else [] + return messages[-self._unsaved_messages:] if self._unsaved_messages else [] def get_corr(self): return self._corr[:] @@ -92,7 +92,7 @@ class Contact(basecontact.BaseContact): def get_last_message_text(self): messages = list(filter(lambda x: x.get_type() <= 1 and x.get_owner() != MESSAGE_AUTHOR['FRIEND'], self._corr)) if messages: - return messages[-1].get_data()[0] + return messages[-1].text else: return '' @@ -100,6 +100,13 @@ class Contact(basecontact.BaseContact): for message in self._corr: message.remove_widget() + @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 # ----------------------------------------------------------------------------------------------------------------- @@ -130,8 +137,9 @@ class Contact(basecontact.BaseContact): # ----------------------------------------------------------------------------------------------------------------- def delete_message(self, message_id): - elem = list(filter(lambda x: type(x) in (TextMessage, GroupChatMessage) and x.message_id == message_id, self._corr))[0] - tmp = list(filter(lambda x: x.get_type() <= 1, self._corr)) + elem = list(filter(lambda x: type(x) in (TextMessage, GroupChatMessage) and x.message_id == message_id, + self._corr))[0] + tmp = list(filter(lambda x: x.get_type() in (MESSAGE_TYPE['NORMAL'], MESSAGE_TYPE['ACTION']), self._corr)) if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages: self._unsaved_messages -= 1 self._corr.remove(elem) @@ -143,7 +151,7 @@ class Contact(basecontact.BaseContact): Delete old messages (reduces RAM usage if messages saving is not enabled) """ def save_message(x): - if x.get_type() == 2 and (x.get_status() >= 2 or x.get_status() is None): + if x.get_type() == 2 and (x.get_status() >= 2 or x.get_status() is None): # FIXME MAGIC NUMBERS return True return x.get_owner() == MESSAGE_AUTHOR['NOT_SENT'] diff --git a/toxygen/history/database.py b/toxygen/history/database.py index 78c0bea..751c74b 100644 --- a/toxygen/history/database.py +++ b/toxygen/history/database.py @@ -67,7 +67,7 @@ class Database: ' id INTEGER PRIMARY KEY,' ' author_name TEXT,' ' message TEXT,' - ' author INTEGER,' + ' author_type INTEGER,' ' unix_time REAL,' ' message_type INTEGER' ')') @@ -95,7 +95,7 @@ class Database: try: cursor = db.cursor() cursor.executemany('INSERT INTO id' + tox_id + - '(message, author_name, author, unix_time, message_type) ' + + '(message, author_name, author_type, unix_time, message_type) ' + 'VALUES (?, ?, ?, ?, ?, ?);', messages_iter) db.commit() except: @@ -187,7 +187,7 @@ class Database: def _connect(self): self._db = connect(self._path, timeout=TIMEOUT) self._cursor = self._db.cursor() - self._cursor.execute('SELECT id, message, author, unix_time, message_type FROM id' + + 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): diff --git a/toxygen/history/history.py b/toxygen/history/history.py index 234b640..21bad8c 100644 --- a/toxygen/history/history.py +++ b/toxygen/history/history.py @@ -10,10 +10,15 @@ class History: 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 # ----------------------------------------------------------------------------------------------------------------- @@ -30,10 +35,9 @@ class History: 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) - unsent_messages = friend.get_unsent_messages() - # unsent_time = unsent_messages[0].get_data()[2] if len(unsent_messages) else time.time() + 1 - # self._db.update_messages(friend.tox_id, unsent_time) + self._db.save() def clear_history(self, friend, save_unsent=False): @@ -44,7 +48,11 @@ class History: self._db.delete_friend_from_db(friend.tox_id) def delete_message(self, message): - pass + contact = self._contacts_manager.get_curr_contact() + if message.type in (MESSAGE_TYPE['NORMAL'], 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): """ @@ -80,7 +88,6 @@ class History: return self._db.messages_getter(friend_public_key) def delete_history(self, friend): - self.clear_history(friend) self._db.delete_friend_from_db(friend.tox_id) def add_friend_to_db(self, tox_id): diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index dc70e8d..80376f4 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -22,11 +22,14 @@ class MessageAuthor: 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 @@ -43,6 +46,11 @@ class Message: time = property(get_time) + def get_message_id(self): + return self._message_id + + message_id = property(get_message_id) + def get_widget(self, *args): if self._widget is None: self._widget = self._create_widget(*args) @@ -60,23 +68,35 @@ class Message: def _create_widget(self, *args): pass + @staticmethod + def _get_id(): + Message.MESSAGE_ID += 1 + + return Message.MESSAGE_ID + class TextMessage(Message): """ Plain text or action message """ - def __init__(self, message, owner, time, message_type): + 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_data(self): - return self._message, self._owner, self._time, self._type + 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) @@ -100,9 +120,6 @@ class GroupChatMessage(TextMessage): super().__init__(id, 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): """ @@ -131,18 +148,13 @@ class TransferMessage(Message): 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, id, path, data, time): super().__init__(id, 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 @@ -159,6 +171,8 @@ class InlineImage(Message): def get_data(self): return self._data + data = property(get_data) + class InfoMessage(TextMessage): From 7209dfae72e2769d0fee2e16930d2de7dff208c1 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 16 May 2018 14:47:14 +0300 Subject: [PATCH 028/138] minor refactoring and todo's for file transfers --- toxygen/contacts/contact.py | 19 ++++++----- toxygen/file_transfers/file_transfers.py | 8 +++-- .../file_transfers/file_transfers_handler.py | 34 ++++++++----------- toxygen/history/history.py | 3 +- toxygen/messenger/messages.py | 8 ++--- toxygen/ui/main_screen.py | 2 +- toxygen/ui/main_screen_widgets.py | 2 +- 7 files changed, 38 insertions(+), 38 deletions(-) diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index 0718e5e..8c1fb06 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -75,7 +75,7 @@ class Contact(basecontact.BaseContact): Get data to save in db :return: list of unsaved messages or [] """ - messages = list(filter(lambda x: x.get_type() <= 1, self._corr)) + messages = list(filter(lambda x: x.get_type() in (MESSAGE_TYPE['NORMAL'], MESSAGE_TYPE['ACTION']), self._corr)) return messages[-self._unsaved_messages:] if self._unsaved_messages else [] def get_corr(self): @@ -86,11 +86,12 @@ class Contact(basecontact.BaseContact): :param message: text or file transfer message """ self._corr.append(message) - if message.get_type() <= 1: + if message.get_type() in (MESSAGE_TYPE['NORMAL'], MESSAGE_TYPE['ACTION']): self._unsaved_messages += 1 def get_last_message_text(self): - messages = list(filter(lambda x: x.get_type() <= 1 and x.get_owner() != MESSAGE_AUTHOR['FRIEND'], self._corr)) + messages = list(filter(lambda x: x.get_type() in (MESSAGE_TYPE['NORMAL'], MESSAGE_TYPE['ACTION']) + and x.get_owner() != MESSAGE_AUTHOR['FRIEND'], self._corr)) if messages: return messages[-1].text else: @@ -122,7 +123,8 @@ class Contact(basecontact.BaseContact): """ :return list of unsent messages for saving """ - messages = filter(lambda x: x.get_type() <= 1 and x.get_owner() == MESSAGE_AUTHOR['NOT_SENT'], self._corr) + messages = filter(lambda x: x.get_type() in (MESSAGE_TYPE['NORMAL'], MESSAGE_TYPE['ACTION']) + and x.get_owner() == MESSAGE_AUTHOR['NOT_SENT'], self._corr) return list(map(lambda x: x.get_data(), messages)) def mark_as_sent(self): @@ -157,7 +159,7 @@ class Contact(basecontact.BaseContact): old = filter(save_message, self._corr[:-SAVE_MESSAGES]) self._corr = list(old) + self._corr[-SAVE_MESSAGES:] - text_messages = filter(lambda x: x.get_type() <= 1, self._corr) + text_messages = filter(lambda x: x.get_type() in (MESSAGE_TYPE['NORMAL'], MESSAGE_TYPE['ACTION']), self._corr) self._unsaved_messages = min(self._unsaved_messages, len(list(text_messages))) self._search_index = 0 @@ -175,7 +177,8 @@ class Contact(basecontact.BaseContact): self._unsaved_messages = 0 else: self._corr = list(filter(lambda x: (x.get_type() == 2 and x.get_status() in ft.ACTIVE_FILE_TRANSFERS) - or (x.get_type() <= 1 and x.get_owner() == MESSAGE_AUTHOR['NOT_SENT']), + or (x.get_type() in (MESSAGE_TYPE['NORMAL'], MESSAGE_TYPE['ACTION']) + and x.get_owner() == MESSAGE_AUTHOR['NOT_SENT']), self._corr)) self._unsaved_messages = len(self.get_unsent_messages()) @@ -193,7 +196,7 @@ class Contact(basecontact.BaseContact): for i in range(self._search_index - 1, -l - 1, -1): if self._corr[i].get_type() > 1: continue - message = self._corr[i].get_data()[0] + message = self._corr[i].text if re.search(self._search_string, message, re.IGNORECASE) is not None: self._search_index = i return i @@ -208,7 +211,7 @@ class Contact(basecontact.BaseContact): for i in range(self._search_index + 1, 0): if self._corr[i].get_type() > 1: 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: self._search_index = i return i diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py index 596642e..4bbcc18 100644 --- a/toxygen/file_transfers/file_transfers.py +++ b/toxygen/file_transfers/file_transfers.py @@ -24,12 +24,14 @@ DO_NOT_SHOW_ACCEPT_BUTTON = (2, 3, 4, 6) SHOW_PROGRESS_BAR = (0, 1, 4) -ALLOWED_FILES = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png') - def is_inline(file_name): - return file_name in ALLOWED_FILES or file_name.startswith('qTox_Screenshot_') + allowed_inlines = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png') + return file_name in allowed_inlines or file_name.startswith('qTox_Screenshot_') + + +# TODO: use events from common.event.py class StateSignal(QtCore.QObject): diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index 8da0811..42a69fa 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -214,20 +214,18 @@ class FileTransfersHandler: st.set_state_changed_handler(item.update_transfer_state) self._messages.scrollToBottom() - def send_file(self, path, number=None, is_resend=False, file_id=None): + def send_file(self, path, friend_number, is_resend=False, file_id=None): """ Send file to current active friend :param path: file path - :param number: friend_number + :param friend_number: friend_number :param is_resend: is 'offline' message :param file_id: file id of transfer """ - friend_number = self.get_active_number() if number is None else number friend = self._get_friend_by_number(friend_number) if friend.status is None and not is_resend: m = UnsentFile(path, None, time.time()) friend.append_message(m) - self.update() return elif friend.status is None and is_resend: print('Error in sending') @@ -235,18 +233,18 @@ class FileTransfersHandler: st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id) st.set_transfer_finished_handler(self.transfer_finished) self._file_transfers[(friend_number, st.get_file_number())] = st - tm = TransferMessage(MESSAGE_AUTHOR['ME'], - time.time(), - TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'], - os.path.getsize(path), - os.path.basename(path), - friend_number, - st.get_file_number()) - if friend_number == self.get_active_number(): - item = self.create_file_transfer_item(tm) - st.set_state_changed_handler(item.update_transfer_state) - self._messages.scrollToBottom() - self._contacts[friend_number].append_message(tm) + # tm = TransferMessage(MESSAGE_AUTHOR['ME'], + # time.time(), + # TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'], + # os.path.getsize(path), + # os.path.basename(path), + # friend_number, + # st.get_file_number()) + # if friend_number == self.get_active_number(): + # item = self.create_file_transfer_item(tm) + # st.set_state_changed_handler(item.update_transfer_state) + # self._messages.scrollToBottom() + # self._contacts[friend_number].append_message(tm) def incoming_chunk(self, friend_number, file_number, position, data): """ @@ -265,8 +263,6 @@ class FileTransfersHandler: t = type(transfer) if t is ReceiveAvatar: self._get_friend_by_number(friend_number).load_avatar() - if friend_number == self.get_active_number() and self.is_active_a_friend(): - self.set_active(None) elif t is ReceiveToBuffer or (t is SendFromBuffer and self._settings['allow_inline']): # inline image print('inline') inline = InlineImage(transfer.get_data()) @@ -284,7 +280,7 @@ class FileTransfersHandler: self._messages.scrollToBottom() elif t is not SendAvatar: self._get_friend_by_number(friend_number).update_transfer_data(file_number, - TOX_FILE_TRANSFER_STATE['FINISHED']) + TOX_FILE_TRANSFER_STATE['FINISHED']) del self._file_transfers[(friend_number, file_number)] del transfer diff --git a/toxygen/history/history.py b/toxygen/history/history.py index 21bad8c..e6f5e44 100644 --- a/toxygen/history/history.py +++ b/toxygen/history/history.py @@ -15,7 +15,6 @@ class History: def __del__(self): del self._db - def set_contacts_manager(self, contacts_manager): self._contacts_manager = contacts_manager @@ -69,7 +68,7 @@ class History: messages.reverse() messages = messages[self._messages.count():self._messages.count() + PAGE_SIZE] for message in messages: - if message.get_type() <= 1: # text message + if message.get_type() in (MESSAGE_TYPE['NORMAL'], MESSAGE_TYPE['ACTION']): # text message self._create_message_item(message) elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: # file transfer if message.get_status() is None: diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index 80376f4..42a0fd1 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -151,8 +151,8 @@ class TransferMessage(Message): class UnsentFile(Message): - def __init__(self, id, path, data, time): - super().__init__(id, MESSAGE_TYPE['FILE_TRANSFER'], 0, time) + def __init__(self, path, data, time): + super().__init__(MESSAGE_TYPE['FILE_TRANSFER'], 0, time) self._data, self._path = data, path def get_status(self): @@ -164,8 +164,8 @@ class InlineImage(Message): Inline image """ - def __init__(self, id, data): - super().__init__(id, MESSAGE_TYPE['INLINE'], None, None) + def __init__(self, data): + super().__init__(MESSAGE_TYPE['INLINE'], None, None) self._data = data def get_data(self): diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index cce78a4..3d9fd0f 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -519,7 +519,7 @@ class MainWindow(QtWidgets.QMainWindow): caption = util_ui.tr('Choose file') name = util_ui.file_dialog(caption) if name[0]: - self._contacts_manager.send_file(name[0]) + self._contacts_manager.send_file(name[0], self._contacts_manager.get_contact().number) def send_screenshot(self, hide=False): self.menu.hide() diff --git a/toxygen/ui/main_screen_widgets.py b/toxygen/ui/main_screen_widgets.py index ecf329a..dfd5ea7 100644 --- a/toxygen/ui/main_screen_widgets.py +++ b/toxygen/ui/main_screen_widgets.py @@ -116,7 +116,7 @@ class ScreenShotWindow(RubberBandWindow): buffer.open(QtCore.QIODevice.WriteOnly) p.save(buffer, 'PNG') friend = self._contacts_manager.get_curr_contact() - self._file_transfer_handler.send_screenshot(bytes(byte_array.data(), friend.number)) + self._file_transfer_handler.send_screenshot(bytes(byte_array.data()), friend.number) self.close() From bfd2a92ddec6b6e86136640fafc26faf4708afe0 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 16 May 2018 19:04:02 +0300 Subject: [PATCH 029/138] initial fixes for file transfers - messages, widgets --- tests/tests.py | 2 +- toxygen/app.py | 7 +- toxygen/contacts/contacts_manager.py | 35 +++--- toxygen/contacts/profile.py | 2 +- toxygen/file_transfers/file_transfers.py | 96 +++++++-------- .../file_transfers/file_transfers_handler.py | 106 ++++------------- .../file_transfers_messages_service.py | 58 +++++++++ toxygen/messenger/messages.py | 34 ++++-- toxygen/ui/contact_items.py | 2 +- toxygen/ui/items_factories.py | 23 ++-- toxygen/ui/messages_widgets.py | 110 +++++++++--------- 11 files changed, 245 insertions(+), 230 deletions(-) create mode 100644 toxygen/file_transfers/file_transfers_messages_service.py diff --git a/tests/tests.py b/tests/tests.py index 1047de4..442a677 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -126,7 +126,7 @@ class TestFriend: assert arr[0][0] == 'Not sent' tm = TransferMessage(MESSAGE_OWNER['FRIEND'], time.time(), - TOX_FILE_TRANSFER_STATE['RUNNING'], + FILE_TRANSFER_STATE['RUNNING'], 100, 'file_name', friend.number, 0) friend.append_message(tm) friend.clear_corr() diff --git a/toxygen/app.py b/toxygen/app.py index 2d12876..ce15093 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -28,6 +28,7 @@ from ui.items_factories import MessagesItemsFactory, FriendItemsFactory 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 class App: @@ -312,7 +313,11 @@ class App: history.set_contacts_manager(self._contacts_manager) self._messenger = Messenger(self._tox, self._plugin_loader, self._ms, self._contacts_manager, self._contacts_provider, messages_items_factory, profile) - self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider) + file_transfers_message_service = FileTransfersMessagesService(self._contacts_manager, messages_items_factory, + profile, self._ms) + self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider, + file_transfers_message_service) + messages_items_factory.set_file_transfers_handler(self._file_transfer_handler) widgets_factory = WidgetsFactory(self._settings, profile, self._profile_manager, self._contacts_manager, self._file_transfer_handler, self._smiley_loader, self._plugin_loader, self._toxes, self._version) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index b65acfa..7570c06 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -83,18 +83,18 @@ class ContactsManager: current_contact.curr_text = self._screen.messageEdit.toPlainText() except: pass - friend = self._contacts[value] - self._subscribe_to_events(friend) - friend.remove_invalid_unsent_files() + contact = self._contacts[value] + self._subscribe_to_events(contact) + contact.remove_invalid_unsent_files() if self._active_contact != value: - self._screen.messageEdit.setPlainText(friend.curr_text) + self._screen.messageEdit.setPlainText(contact.curr_text) self._active_contact = value - friend.reset_messages() + contact.reset_messages() if not self._settings['save_history']: - friend.delete_old_messages() + contact.delete_old_messages() self._messages.clear() - friend.load_corr() - corr = friend.get_corr()[-PAGE_SIZE:] + contact.load_corr() + corr = contact.get_corr()[-PAGE_SIZE:] for message in corr: self._messages_items_factory.create_message_item(message) # if value in self._call: @@ -103,12 +103,8 @@ class ContactsManager: # self._screen.incoming_call() # else: # self._screen.call_finished() + self._set_current_contact_data(contact) - self._screen.account_status.setToolTip(friend.get_full_status()) - avatar_path = friend.get_avatar_path() - pixmap = QtGui.QPixmap(avatar_path) - self._screen.account_avatar.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio, - QtCore.Qt.SmoothTransformation)) except Exception as ex: # no friend found. ignore util.log('Friend value: ' + str(value)) util.log('Error in set active: ' + str(ex)) @@ -433,8 +429,15 @@ class ContactsManager: 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.avatar_label.setPixmap(pixmap.scaled(width, width, - QtCore.Qt.KeepAspectRatio, - QtCore.Qt.SmoothTransformation)) + self._screen.account_avatar.setPixmap(pixmap.scaled(width, width, + QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)) diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index af5b87e..1a8fe0f 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -91,7 +91,7 @@ class Profile(basecontact.BaseContact): ft = self._file_transfers[(friend_num, file_num)] if type(ft) is SendTransfer: self._paused_file_transfers[ft.get_id()] = [ft.get_path(), friend_num, False, -1] - elif type(ft) is ReceiveTransfer and ft.state != TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: + elif type(ft) is ReceiveTransfer and ft.state != FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: self._paused_file_transfers[ft.get_id()] = [ft.get_path(), friend_num, True, ft.total_size()] self.cancel_transfer(friend_num, file_num, True) diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py index 4bbcc18..6673e17 100644 --- a/toxygen/file_transfers/file_transfers.py +++ b/toxygen/file_transfers/file_transfers.py @@ -3,10 +3,10 @@ from os.path import basename, getsize, exists, dirname from os import remove, rename, chdir from time import time, sleep from wrapper.tox import Tox -from PyQt5 import QtCore +from common.event import Event -TOX_FILE_TRANSFER_STATE = { +FILE_TRANSFER_STATE = { 'RUNNING': 0, 'PAUSED_BY_USER': 1, 'CANCELLED': 2, @@ -31,69 +31,58 @@ def is_inline(file_name): return file_name in allowed_inlines or file_name.startswith('qTox_Screenshot_') -# TODO: use events from common.event.py - -class StateSignal(QtCore.QObject): - - 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): +class FileTransfer: """ Superclass for file transfers """ def __init__(self, path, tox, friend_number, size, file_number=None): - QtCore.QObject.__init__(self) self._path = path self._tox = tox self._friend_number = friend_number - self.state = TOX_FILE_TRANSFER_STATE['RUNNING'] + self.state = FILE_TRANSFER_STATE['RUNNING'] self._file_number = file_number self._creation_time = None self._size = float(size) self._done = 0 - self._state_changed = StateSignal() - self._finished = TransferFinishedSignal() + self._state_changed_event = Event() + self._finished_event = Event() self._file_id = None def set_tox(self, tox): self._tox = tox def set_state_changed_handler(self, handler): - self._state_changed.signal.connect(handler) + self._state_changed_event += handler def set_transfer_finished_handler(self, handler): - self._finished.signal.connect(handler) - - def signal(self): - percentage = self._done / self._size if self._size else 0 - if self._creation_time is None or not percentage: - 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) + self._finished_event += handler def get_file_number(self): return self._file_number + file_number = property(get_file_number) + def get_friend_number(self): return self._friend_number - def get_id(self): + friend_number = property(get_friend_number) + + def get_file_id(self): return self._file_id + file_id = property(get_file_id) + def get_path(self): return self._path + path = property(get_path) + + def get_size(self): + return self._size + + size = property(get_size) + def cancel(self): self.send_control(TOX_FILE_CONTROL['CANCEL']) if hasattr(self, '_file'): @@ -104,14 +93,14 @@ class FileTransfer(QtCore.QObject): if hasattr(self, '_file'): sleep(0.1) self._file.close() - self.state = TOX_FILE_TRANSFER_STATE['CANCELLED'] + self.state = FILE_TRANSFER_STATE['CANCELLED'] self.signal() def pause(self, by_friend): if not by_friend: self.send_control(TOX_FILE_CONTROL['PAUSE']) else: - self.state = TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'] + self.state = FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'] self.signal() def send_control(self, control): @@ -122,6 +111,17 @@ class FileTransfer(QtCore.QObject): def get_file_id(self): 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 # ----------------------------------------------------------------------------------------------------------------- @@ -136,7 +136,7 @@ class SendTransfer(FileTransfer): else: size = 0 super().__init__(path, tox, friend_number, size) - self.state = TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'] + self.state = FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'] self._file_number = tox.file_send(friend_number, kind, size, file_id, bytes(basename(path), 'utf-8') if path else b'') self._file_id = self.get_file_id() @@ -157,8 +157,8 @@ class SendTransfer(FileTransfer): else: if hasattr(self, '_file'): self._file.close() - self.state = TOX_FILE_TRANSFER_STATE['FINISHED'] - self.finished() + self.state = FILE_TRANSFER_STATE['FINISHED'] + self._finished() self.signal() @@ -183,7 +183,7 @@ class SendFromBuffer(FileTransfer): def __init__(self, tox, friend_number, data, file_name): 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._file_number = tox.file_send(friend_number, TOX_FILE_KIND['DATA'], len(data), None, bytes(file_name, 'utf-8')) @@ -199,8 +199,8 @@ class SendFromBuffer(FileTransfer): self._tox.file_send_chunk(self._friend_number, self._file_number, position, data) self._done += size else: - self.state = TOX_FILE_TRANSFER_STATE['FINISHED'] - self.finished() + self.state = FILE_TRANSFER_STATE['FINISHED'] + self._finished() self.signal() @@ -250,8 +250,8 @@ class ReceiveTransfer(FileTransfer): self._creation_time = time() if data is None: self._file.close() - self.state = TOX_FILE_TRANSFER_STATE['FINISHED'] - self.finished() + self.state = FILE_TRANSFER_STATE['FINISHED'] + self._finished() else: data = bytearray(data) if self._file_size < position: @@ -286,8 +286,8 @@ class ReceiveToBuffer(FileTransfer): if self._creation_time is None: self._creation_time = time() if data is None: - self.state = TOX_FILE_TRANSFER_STATE['FINISHED'] - self.finished() + self.state = FILE_TRANSFER_STATE['FINISHED'] + self._finished() else: data = bytes(data) l = len(data) @@ -342,8 +342,8 @@ class ReceiveAvatar(ReceiveTransfer): chdir(dirname(avatar_path)) remove(avatar_path) rename(self._path, avatar_path) - self.finished(True) + self._finished(True) - def finished(self, emit=False): + def _finished(self, emit=False): if emit: - super().finished() + super()._finished() diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index 42a69fa..470299c 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -8,10 +8,11 @@ import utils.util as util class FileTransfersHandler: - def __init__(self, tox, settings, contact_provider): + def __init__(self, tox, settings, contact_provider, file_transfers_message_service): self._tox = 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']) @@ -46,51 +47,17 @@ class FileTransfersHandler: return self._tox.file_seek(friend_number, file_number, pos) self.accept_transfer(None, data[0], friend_number, file_number, size, False, pos) - tm = TransferMessage(MESSAGE_AUTHOR['FRIEND'], - time.time(), - TOX_FILE_TRANSFER_STATE['RUNNING'], - size, - file_name, - friend_number, - file_number) elif inline and size < 1024 * 1024: self.accept_transfer(None, '', friend_number, file_number, size, True) - tm = TransferMessage(MESSAGE_AUTHOR['FRIEND'], - time.time(), - TOX_FILE_TRANSFER_STATE['RUNNING'], - size, - file_name, - friend_number, - file_number) elif auto: path = self._settings['auto_accept_path'] or util.curr_directory() self.accept_transfer(None, path + '/' + file_name, friend_number, file_number, size) - tm = TransferMessage(MESSAGE_AUTHOR['FRIEND'], - time.time(), - TOX_FILE_TRANSFER_STATE['RUNNING'], - size, - file_name, - friend_number, - file_number) else: - tm = TransferMessage(MESSAGE_AUTHOR['FRIEND'], - time.time(), - TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED'], - size, - file_name, - friend_number, - file_number) accepted = False - if friend_number == self.get_active_number() and self.is_active_a_friend(): - item = self.create_file_transfer_item(tm) - if accepted: - self._file_transfers[(friend_number, file_number)].set_state_changed_handler(item.update_transfer_state) - self._messages.scrollToBottom() - else: - friend.actions = True - friend.append_message(tm) + 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): """ @@ -100,7 +67,7 @@ class FileTransfersHandler: :param already_cancelled: was cancelled by friend """ i = self._get_friend_by_number(friend_number).update_transfer_data(file_number, - TOX_FILE_TRANSFER_STATE['CANCELLED']) + FILE_TRANSFER_STATE['CANCELLED']) if (friend_number, file_number) in self._file_transfers: tr = self._file_transfers[(friend_number, file_number)] if not already_cancelled: @@ -117,12 +84,11 @@ class FileTransfersHandler: tmp = self._messages.count() + i if tmp >= 0: self._messages.itemWidget( - self._messages.item(tmp)).update_transfer_state(TOX_FILE_TRANSFER_STATE['CANCELLED'], + self._messages.item(tmp)).update_transfer_state(FILE_TRANSFER_STATE['CANCELLED'], 0, -1) def cancel_not_started_transfer(self, cancel_time): self.get_curr_friend().delete_one_unsent_file(cancel_time) - self.update() def pause_transfer(self, friend_number, file_number, by_friend=False): """ @@ -130,7 +96,7 @@ class FileTransfersHandler: """ tr = self._file_transfers[(friend_number, file_number)] tr.pause(by_friend) - t = TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'] if by_friend else TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER'] + t = FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'] if by_friend else FILE_TRANSFER_STATE['PAUSED_BY_USER'] self._get_friend_by_number(friend_number).update_transfer_data(file_number, t) def resume_transfer(self, friend_number, file_number, by_friend=False): @@ -141,7 +107,7 @@ class FileTransfersHandler: # TOX_FILE_TRANSFER_STATE['RUNNING']) tr = self._file_transfers[(friend_number, file_number)] if by_friend: - tr.state = TOX_FILE_TRANSFER_STATE['RUNNING'] + tr.state = FILE_TRANSFER_STATE['RUNNING'] tr.signal() else: tr.send_control(TOX_FILE_CONTROL['RESUME']) @@ -177,7 +143,7 @@ class FileTransfersHandler: if item is not None: rt.set_state_changed_handler(item.update_transfer_state) self._get_friend_by_number(friend_number).update_transfer_data(file_number, - TOX_FILE_TRANSFER_STATE['RUNNING']) + FILE_TRANSFER_STATE['RUNNING']) def send_screenshot(self, data, friend_number): """ @@ -201,18 +167,9 @@ class FileTransfersHandler: raise RuntimeError() st = SendFromBuffer(self._tox, friend.number, data, file_name) st.set_transfer_finished_handler(self.transfer_finished) - self._file_transfers[(friend.number, st.get_file_number())] = st - tm = TransferMessage(MESSAGE_AUTHOR['ME'], - time.time(), - TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'], - len(data), - file_name, - friend.number, - st.get_file_number()) - item = self.create_file_transfer_item(tm) - friend.append_message(tm) - st.set_state_changed_handler(item.update_transfer_state) - self._messages.scrollToBottom() + file_number = st.get_file_number() + self._file_transfers[(friend.number, file_number)] = st + self._file_transfers_message_service.add_outgoing_transfer_message(friend, st.size, file_name, file_number) def send_file(self, path, friend_number, is_resend=False, file_id=None): """ @@ -232,19 +189,10 @@ class FileTransfersHandler: raise RuntimeError() st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id) st.set_transfer_finished_handler(self.transfer_finished) - self._file_transfers[(friend_number, st.get_file_number())] = st - # tm = TransferMessage(MESSAGE_AUTHOR['ME'], - # time.time(), - # TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'], - # os.path.getsize(path), - # os.path.basename(path), - # friend_number, - # st.get_file_number()) - # if friend_number == self.get_active_number(): - # item = self.create_file_transfer_item(tm) - # st.set_state_changed_handler(item.update_transfer_state) - # self._messages.scrollToBottom() - # self._contacts[friend_number].append_message(tm) + file_number = st.get_file_number() + self._file_transfers[(friend_number, file_number)] = st + file_name = os.path.basename(path) + self._file_transfers_message_service.add_outgoing_transfer_message(friend, st.size, file_name, file_number) def incoming_chunk(self, friend_number, file_number, position, data): """ @@ -266,21 +214,13 @@ class FileTransfersHandler: elif t is ReceiveToBuffer or (t is SendFromBuffer and self._settings['allow_inline']): # inline image print('inline') inline = InlineImage(transfer.get_data()) - i = self._get_friend_by_number(friend_number).update_transfer_data(file_number, - TOX_FILE_TRANSFER_STATE['FINISHED'], - inline) - if friend_number == self.get_active_number() and self.is_active_a_friend(): - count = self._messages.count() - if count + i + 1 >= 0: - elem = QtWidgets.QListWidgetItem() - item = InlineImageItem(transfer.get_data(), self._messages.width(), elem) - elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height())) - self._messages.insertItem(count + i + 1, elem) - self._messages.setItemWidget(elem, item) - self._messages.scrollToBottom() + index = self._get_friend_by_number(friend_number).update_transfer_data(file_number, + FILE_TRANSFER_STATE['FINISHED'], + inline) + self._file_transfers_message_service.add_inline_message(transfer, index) elif t is not SendAvatar: self._get_friend_by_number(friend_number).update_transfer_data(file_number, - TOX_FILE_TRANSFER_STATE['FINISHED']) + FILE_TRANSFER_STATE['FINISHED']) del self._file_transfers[(friend_number, file_number)] del transfer @@ -288,7 +228,7 @@ class FileTransfersHandler: friend = self._get_friend_by_number(friend_number) friend.remove_invalid_unsent_files() files = friend.get_unsent_files() - try: + try: # TODO: fix for fl in files: data = fl.get_data() if data[1] is not None: @@ -327,7 +267,7 @@ class FileTransfersHandler: """ 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 != TOX_FILE_TRANSFER_STATE['CANCELLED']: + if ra.state != FILE_TRANSFER_STATE['CANCELLED']: self._file_transfers[(friend_number, file_number)] = ra ra.set_transfer_finished_handler(self.transfer_finished) else: diff --git a/toxygen/file_transfers/file_transfers_messages_service.py b/toxygen/file_transfers/file_transfers_messages_service.py new file mode 100644 index 0000000..963eeb6 --- /dev/null +++ b/toxygen/file_transfers/file_transfers_messages_service.py @@ -0,0 +1,58 @@ +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_active(friend.number): + self._create_file_transfer_item(tm) + self._messages.scrollToBottom() + else: + friend.actions = True + + friend.append_message(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_active(friend.number): + self._create_file_transfer_item(tm) + self._messages.scrollToBottom() + + friend.append_message(tm) + + def add_inline_message(self, transfer, index): + if self._is_active(transfer.friend_number): + count = self._messages.count() + if count + index + 1 >= 0: + self._create_inline_item(transfer.data, count + index + 1) + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _is_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) diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index 42a0fd1..0dfc797 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -126,27 +126,43 @@ class TransferMessage(Message): Message with info about file transfer """ - def __init__(self, owner, time, status, size, name, friend_number, file_number): - super().__init__(MESSAGE_TYPE['FILE_TRANSFER'], owner, time) - self._status = status + 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 = name + self._file_name = file_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) + return self._file_number == file_number and self._state not in (2, 3) def get_friend_number(self): return self._friend_number + friend_number = property(get_friend_number) + def get_file_number(self): return self._file_number - def get_status(self): - return self._status + file_number = property(get_file_number) - def set_status(self, value): - self._status = value + 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) class UnsentFile(Message): diff --git a/toxygen/ui/contact_items.py b/toxygen/ui/contact_items.py index 25b3c0a..2ad3ad2 100644 --- a/toxygen/ui/contact_items.py +++ b/toxygen/ui/contact_items.py @@ -1,7 +1,7 @@ from wrapper.toxcore_enums_and_consts import * from PyQt5 import QtCore, QtGui, QtWidgets from contacts import profile -from file_transfers.file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR +from file_transfers.file_transfers import FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR from utils.util import * from ui.widgets import DataLabel, create_menu from user_data import settings diff --git a/toxygen/ui/items_factories.py b/toxygen/ui/items_factories.py index dc9c002..3257279 100644 --- a/toxygen/ui/items_factories.py +++ b/toxygen/ui/items_factories.py @@ -18,16 +18,18 @@ class FriendItemsFactory: return item -# TODO: accept messages everywhere instead of params - 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) @@ -43,24 +45,20 @@ class MessagesItemsFactory: return item - def create_inline_item(self, data, append): + def create_inline_item(self, data, append, position=0): 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.insertItem(position, elem) self._messages.setItemWidget(elem, item) return item - def create_unsent_file_item(self, file_name, size, name, time, append): - item = UnsentFileItem(file_name, - size, - name, - time, - self._messages.width()) + def create_unsent_file_item(self, tm, append): + item = UnsentFileItem(self._file_transfers_handler, self._settings, tm, self._messages.width()) elem = QtWidgets.QListWidgetItem() elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34)) if append: @@ -71,9 +69,8 @@ class MessagesItemsFactory: return item - def create_file_transfer_item(self, data, append=True): - data.append(self._messages.width()) - item = FileTransferItem(*data) + def create_file_transfer_item(self, tm, append=True): + item = FileTransferItem(self._file_transfers_handler, self._settings, tm, self._messages.width()) elem = QtWidgets.QListWidgetItem() elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34)) if append: diff --git a/toxygen/ui/messages_widgets.py b/toxygen/ui/messages_widgets.py index dea7a5c..89e4ac3 100644 --- a/toxygen/ui/messages_widgets.py +++ b/toxygen/ui/messages_widgets.py @@ -6,7 +6,9 @@ import utils.util as util import ui.menu as menu import html as h import re +from ui.widgets import * from messenger.messages import MESSAGE_AUTHOR +from file_transfers.file_transfers import * class MessageBrowser(QtWidgets.QTextBrowser): @@ -216,60 +218,63 @@ class MessageItem(QtWidgets.QWidget): class FileTransferItem(QtWidgets.QListWidget): - def __init__(self, file_name, size, time, user, friend_number, file_number, state, width, parent=None): + def __init__(self, file_transfers_handler, settings, transfer_message, width, parent=None): QtWidgets.QListWidget.__init__(self, parent) + self._file_transfers_handler = file_transfers_handler 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; }') - elif state in PAUSED_FILE_TRANSFERS: + elif transfer_message.state in PAUSED_FILE_TRANSFERS: self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') else: self.setStyleSheet('QListWidget { border: 1px solid green; }') - self.state = state + self.state = transfer_message.state self.name = DataLabel(self) self.name.setGeometry(QtCore.QRect(3, 7, 95, 25)) self.name.setTextFormat(QtCore.Qt.PlainText) font = QtGui.QFont() - # FIXME - font.setFamily(settings.Settings.get_instance()['font']) + font.setFamily(settings['font']) font.setPointSize(11) font.setBold(True) self.name.setFont(font) - self.name.setText(user) + self.name.setText(transfer_message.author.name) self.time = QtWidgets.QLabel(self) self.time.setGeometry(QtCore.QRect(width - 60, 7, 50, 25)) font.setPointSize(10) font.setBold(False) self.time.setFont(font) - self.time.setText(convert_time(time)) + self.time.setText(util.convert_time(time)) self.cancel = QtWidgets.QPushButton(self) 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) self.cancel.setIcon(icon) self.cancel.setIconSize(QtCore.QSize(30, 30)) - self.cancel.setVisible(state in ACTIVE_FILE_TRANSFERS) - self.cancel.clicked.connect(lambda: self.cancel_transfer(friend_number, file_number)) + self.cancel.setVisible(transfer_message.state in ACTIVE_FILE_TRANSFERS) + 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.accept_or_pause = QtWidgets.QPushButton(self) 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.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) - 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.button_update('resume') else: # pause self.accept_or_pause.setVisible(True) 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}') @@ -277,64 +282,60 @@ class FileTransferItem(QtWidgets.QListWidget): self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20)) self.pb.setValue(0) 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.setGeometry(QtCore.QRect(210, 7, width - 420, 20)) font.setPointSize(12) self.file_name.setFont(font) - file_size = size // 1024 + file_size = transfer_message.size // 1024 if not file_size: - file_size = '{}B'.format(size) + file_size = '{}B'.format(transfer_message.size) elif file_size >= 1024: file_size = '{}MB'.format(file_size // 1024) else: 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.setToolTip(file_name) - self.saved_name = file_name + self.file_name.setToolTip(transfer_message.file_name) + self.saved_name = transfer_message.file_name self.time_left = QtWidgets.QLabel(self) self.time_left.setGeometry(QtCore.QRect(width - 92, 7, 30, 20)) font.setPointSize(10) 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.paused = False def cancel_transfer(self, friend_number, file_number): - pr = profile.Profile.get_instance() - pr.cancel_transfer(friend_number, file_number) + self._file_transfers_handler.cancel_transfer(friend_number, file_number) self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') self.cancel.setVisible(False) self.accept_or_pause.setVisible(False) self.pb.setVisible(False) def accept_or_pause_transfer(self, friend_number, file_number, size): - if self.state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: - directory = QtWidgets.QFileDialog.getExistingDirectory(self, - QtWidgets.QApplication.translate("MainWindow", 'Choose folder'), - curr_directory(), - QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) + if self.state == FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: + directory = util_ui.directory_dialog(util_ui.tr('Choose folder')) self.pb.setVisible(True) if directory: - pr = profile.Profile.get_instance() - pr.accept_transfer(self, directory + '/' + self.saved_name, friend_number, file_number, size) + self._file_transfer_handler.accept_transfer(self, directory + '/' + self.saved_name, + friend_number, file_number, size) 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 - 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.state = TOX_FILE_TRANSFER_STATE['RUNNING'] + self.state = FILE_TRANSFER_STATE['RUNNING'] else: # pause self.paused = True - self.state = TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER'] - profile.Profile.get_instance().pause_transfer(friend_number, file_number) + self.state = FILE_TRANSFER_STATE['PAUSED_BY_USER'] + self._file_transfer_handler.pause_transfer(friend_number, file_number) self.button_update('resume') self.accept_or_pause.clearFocus() 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) self.accept_or_pause.setIcon(icon) self.accept_or_pause.setIconSize(QtCore.QSize(30, 30)) @@ -345,31 +346,31 @@ class FileTransferItem(QtWidgets.QListWidget): m, s = divmod(time, 60) self.time_left.setText('{0:02d}:{1:02d}'.format(m, s)) 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.cancel.setVisible(False) self.accept_or_pause.setVisible(False) self.pb.setVisible(False) self.state = state 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.pb.setVisible(False) self.cancel.setVisible(False) self.setStyleSheet('QListWidget { border: 1px solid green; }') self.state = state 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.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') self.state = state 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.setStyleSheet('QListWidget { border: 1px solid green; }') self.state = state 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.accept_or_pause.setVisible(False) self.time_left.setVisible(False) @@ -382,24 +383,23 @@ class FileTransferItem(QtWidgets.QListWidget): self.state = state self.time_left.setVisible(True) - def mark_as_sent(self): + @staticmethod + def mark_as_sent(): return False class UnsentFileItem(FileTransferItem): - def __init__(self, file_name, size, user, time, width, parent=None): - super(UnsentFileItem, self).__init__(file_name, size, time, user, -1, -1, - TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'], width, parent) + def __init__(self, file_transfers_handler, settings, transfer_message, width, parent=None): + super().__init__(file_transfers_handler, settings, transfer_message, width, parent) self._time = time self.pb.setVisible(False) - movie = QtGui.QMovie(join_path(get_images_directory(), 'spinner.gif')) + movie = QtGui.QMovie(util.join_path(util.get_images_directory(), 'spinner.gif')) self.time.setMovie(movie) movie.start() def cancel_transfer(self, *args): - pr = profile.Profile.get_instance() - pr.cancel_not_started_transfer(self._time) + self._file_transfers_handler.cancel_not_started_transfer(self._time) class InlineImageItem(QtWidgets.QScrollArea): @@ -443,15 +443,11 @@ class InlineImageItem(QtWidgets.QScrollArea): self._full_size = not self._full_size self._elem.setSizeHint(QtCore.QSize(self.width(), self.height())) elif event.button() == QtCore.Qt.RightButton: # save inline - # TODO: dialog - directory = QtWidgets.QFileDialog.getExistingDirectory(self, - QtWidgets.QApplication.translate("MainWindow", - 'Choose folder'), - curr_directory(), - QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) + directory = util_ui.directory_dialog(util_ui.tr('Choose folder')) 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') - def mark_as_sent(self): + @staticmethod + def mark_as_sent(): return False From f3aa0aeda354969da9ce4bffde406a3559f22934 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 16 May 2018 19:31:08 +0300 Subject: [PATCH 030/138] file transfers fixes - part 2 --- toxygen/contacts/contact.py | 14 +++++++------- toxygen/messenger/messages.py | 6 ++++++ toxygen/ui/items_factories.py | 2 +- toxygen/ui/messages_widgets.py | 2 +- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index 8c1fb06..c5c0dac 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -75,7 +75,7 @@ class Contact(basecontact.BaseContact): Get data to save in db :return: list of unsaved messages or [] """ - messages = list(filter(lambda x: x.get_type() in (MESSAGE_TYPE['NORMAL'], MESSAGE_TYPE['ACTION']), self._corr)) + messages = list(filter(lambda x: x.get_type() in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']), self._corr)) return messages[-self._unsaved_messages:] if self._unsaved_messages else [] def get_corr(self): @@ -86,11 +86,11 @@ class Contact(basecontact.BaseContact): :param message: text or file transfer message """ self._corr.append(message) - if message.get_type() in (MESSAGE_TYPE['NORMAL'], MESSAGE_TYPE['ACTION']): + if message.get_type() in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']): self._unsaved_messages += 1 def get_last_message_text(self): - messages = list(filter(lambda x: x.get_type() in (MESSAGE_TYPE['NORMAL'], MESSAGE_TYPE['ACTION']) + messages = list(filter(lambda x: x.get_type() in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']) and x.get_owner() != MESSAGE_AUTHOR['FRIEND'], self._corr)) if messages: return messages[-1].text @@ -123,7 +123,7 @@ class Contact(basecontact.BaseContact): """ :return list of unsent messages for saving """ - messages = filter(lambda x: x.get_type() in (MESSAGE_TYPE['NORMAL'], MESSAGE_TYPE['ACTION']) + messages = filter(lambda x: x.get_type() in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']) and x.get_owner() == MESSAGE_AUTHOR['NOT_SENT'], self._corr) return list(map(lambda x: x.get_data(), messages)) @@ -141,7 +141,7 @@ class Contact(basecontact.BaseContact): def delete_message(self, message_id): elem = list(filter(lambda x: type(x) in (TextMessage, GroupChatMessage) and x.message_id == message_id, self._corr))[0] - tmp = list(filter(lambda x: x.get_type() in (MESSAGE_TYPE['NORMAL'], MESSAGE_TYPE['ACTION']), self._corr)) + tmp = list(filter(lambda x: x.get_type() in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']), self._corr)) if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages: self._unsaved_messages -= 1 self._corr.remove(elem) @@ -159,7 +159,7 @@ class Contact(basecontact.BaseContact): old = filter(save_message, self._corr[:-SAVE_MESSAGES]) self._corr = list(old) + self._corr[-SAVE_MESSAGES:] - text_messages = filter(lambda x: x.get_type() in (MESSAGE_TYPE['NORMAL'], MESSAGE_TYPE['ACTION']), self._corr) + text_messages = filter(lambda x: x.get_type() in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']), self._corr) self._unsaved_messages = min(self._unsaved_messages, len(list(text_messages))) self._search_index = 0 @@ -177,7 +177,7 @@ class Contact(basecontact.BaseContact): self._unsaved_messages = 0 else: self._corr = list(filter(lambda x: (x.get_type() == 2 and x.get_status() in ft.ACTIVE_FILE_TRANSFERS) - or (x.get_type() in (MESSAGE_TYPE['NORMAL'], MESSAGE_TYPE['ACTION']) + or (x.get_type() in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']) and x.get_owner() == MESSAGE_AUTHOR['NOT_SENT']), self._corr)) self._unsaved_messages = len(self.get_unsent_messages()) diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index 0dfc797..34aff38 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -164,6 +164,9 @@ class TransferMessage(Message): file_name = property(get_file_name) + def _create_widget(self, *args): + return FileTransferItem(self, *args) + class UnsentFile(Message): @@ -189,6 +192,9 @@ class InlineImage(Message): data = property(get_data) + def _create_widget(self, *args): + return InlineImageItem(self, *args) + class InfoMessage(TextMessage): diff --git a/toxygen/ui/items_factories.py b/toxygen/ui/items_factories.py index 3257279..674291c 100644 --- a/toxygen/ui/items_factories.py +++ b/toxygen/ui/items_factories.py @@ -70,7 +70,7 @@ class MessagesItemsFactory: return item def create_file_transfer_item(self, tm, append=True): - item = FileTransferItem(self._file_transfers_handler, self._settings, tm, self._messages.width()) + item = tm.get_widget(self._file_transfers_handler, self._settings, tm, self._messages.width()) elem = QtWidgets.QListWidgetItem() elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34)) if append: diff --git a/toxygen/ui/messages_widgets.py b/toxygen/ui/messages_widgets.py index 89e4ac3..c5b2120 100644 --- a/toxygen/ui/messages_widgets.py +++ b/toxygen/ui/messages_widgets.py @@ -246,7 +246,7 @@ class FileTransferItem(QtWidgets.QListWidget): font.setPointSize(10) font.setBold(False) self.time.setFont(font) - self.time.setText(util.convert_time(time)) + self.time.setText(util.convert_time(transfer_message.time)) self.cancel = QtWidgets.QPushButton(self) self.cancel.setGeometry(QtCore.QRect(width - 125, 2, 30, 30)) From c0a143c81719879db69b990cf249e33cb109f472 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 16 May 2018 20:25:21 +0300 Subject: [PATCH 031/138] identicons basic support --- setup.py | 6 +++++- toxygen/contacts/basecontact.py | 16 ++++++++++------ toxygen/contacts/common.py | 15 +++++++++++++++ toxygen/contacts/contacts_manager.py | 1 + toxygen/contacts/profile.py | 4 ++-- toxygen/file_transfers/file_transfers.py | 3 --- toxygen/file_transfers/file_transfers_handler.py | 2 +- toxygen/ui/menu.py | 2 +- toxygen/user_data/settings.py | 1 + 9 files changed, 36 insertions(+), 14 deletions(-) diff --git a/setup.py b/setup.py index f586ec3..8457ef5 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ version = main.__version__ + '.0' if system() == 'Windows': - MODULES = ['PyQt5', 'PyAudio', 'numpy', 'opencv-python'] + MODULES = ['PyQt5', 'PyAudio', 'numpy', 'opencv-python', 'pydenticon'] else: MODULES = [] try: @@ -29,6 +29,10 @@ else: import cv2 except ImportError: MODULES.append('opencv-python') + try: + import pydenticon + except ImportError: + MODULES.append('pydenticon') class InstallScript(install): diff --git a/toxygen/contacts/basecontact.py b/toxygen/contacts/basecontact.py index 037289c..988b0bc 100644 --- a/toxygen/contacts/basecontact.py +++ b/toxygen/contacts/basecontact.py @@ -3,6 +3,7 @@ from PyQt5 import QtCore, QtGui 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: @@ -119,14 +120,17 @@ class BaseContact: self._widget.avatar_label.repaint() self._avatar_changed_event(avatar_path) - def reset_avatar(self): + def reset_avatar(self, generate_new): 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) + if generate_new: + self.set_avatar(common.generate_avatar(self.tox_id)) + else: self.load_avatar() def set_avatar(self, avatar): - avatar_path = self.get_avatar_path() + avatar_path = self.get_contact_avatar_path() with open(avatar_path, 'wb') as f: f.write(avatar) self.load_avatar() @@ -137,7 +141,7 @@ class BaseContact: 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 = util.join_path(util.get_images_directory(), self._get_default_avatar_name()) + avatar_path = self._get_default_avatar_path() return avatar_path @@ -166,5 +170,5 @@ class BaseContact: # ----------------------------------------------------------------------------------------------------------------- @staticmethod - def _get_default_avatar_name(): - return 'avatar.png' + def _get_default_avatar_path(): + return util.join_path(util.get_images_directory(), 'avatar.png') diff --git a/toxygen/contacts/common.py b/toxygen/contacts/common.py index 584afc3..b37c6a2 100644 --- a/toxygen/contacts/common.py +++ b/toxygen/contacts/common.py @@ -1,3 +1,4 @@ +from pydenticon import Generator class BaseTypingNotificationHandler: @@ -22,3 +23,17 @@ class FriendTypingNotificationHandler(BaseTypingNotificationHandler): BaseTypingNotificationHandler.DEFAULT_HANDLER = BaseTypingNotificationHandler() + + +def generate_avatar(tox_id): + foreground = ["rgb(45,79,255)", + "rgb(254,180,44)", + "rgb(226,121,234)", + "rgb(30,179,253)", + "rgb(232,77,65)", + "rgb(49,203,115)", + "rgb(141,69,170)"] + generator = Generator(5, 5, foreground=foreground, background="rgba(42,42,42,0)") + identicon = generator.generate(tox_id, 220, 220, padding=(10, 10, 10, 10)) + + return identicon diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 7570c06..b4c1803 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -295,6 +295,7 @@ class ContactsManager: self._history.add_friend_to_db(tox_id) friend = self._contact_provider.get_friend_by_public_key(tox_id) self._contacts.append(friend) + friend.reset_avatar() def block_user(self, tox_id): """ diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 1a8fe0f..b4dbaf2 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -136,8 +136,8 @@ class Profile(basecontact.BaseContact): self._call.stop() del self._call - def reset_avatar(self): - super().reset_avatar() + def reset_avatar(self, generate_new): + super().reset_avatar(generate_new) for friend in filter(lambda x: x.status is not None, self._contacts): self.send_avatar(friend.number) diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py index 6673e17..91aeb86 100644 --- a/toxygen/file_transfers/file_transfers.py +++ b/toxygen/file_transfers/file_transfers.py @@ -316,9 +316,6 @@ class ReceiveAvatar(ReceiveTransfer): elif not size: self.send_control(TOX_FILE_CONTROL['CANCEL']) self._file.close() - if exists(path): - remove(path) - self._file.close() remove(full_path) elif exists(path): hash = self.get_file_id() diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index 470299c..6269295 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -271,7 +271,7 @@ class FileTransfersHandler: self._file_transfers[(friend_number, file_number)] = ra ra.set_transfer_finished_handler(self.transfer_finished) else: - friend.load_avatar() + friend.reset_avatar(self._settings['identicons']) # ----------------------------------------------------------------------------------------------------------------- # Private methods diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index d023cff..1a33bde 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -245,7 +245,7 @@ class ProfileSettings(CenteredWidget): self.tox_id.setText(self._profile.new_nospam()) def reset_avatar(self): - self._profile.reset_avatar() + self._profile.reset_avatar(self._settings['identicons']) def set_avatar(self): choose = util_ui.tr("Choose avatar") diff --git a/toxygen/user_data/settings.py b/toxygen/user_data/settings.py index 8289663..1d7329e 100644 --- a/toxygen/user_data/settings.py +++ b/toxygen/user_data/settings.py @@ -140,6 +140,7 @@ class Settings(dict): 'unread_color': 'red', 'save_unsent_only': False, 'compact_mode': False, + 'identicons': True, 'show_welcome_screen': True, 'close_to_tray': False, 'font': 'Times New Roman', From a96f6d292878167c888b19ae51f5e1ce29e744dd Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 17 May 2018 00:02:22 +0300 Subject: [PATCH 032/138] file transfers fixes - part 3 --- toxygen/app.py | 2 +- toxygen/common/event.py | 4 + toxygen/contacts/contact.py | 3 + toxygen/contacts/contacts_manager.py | 13 +-- toxygen/contacts/friend.py | 3 +- toxygen/file_transfers/file_transfers.py | 9 +- .../file_transfers/file_transfers_handler.py | 83 ++++++++----------- .../file_transfers_messages_service.py | 4 + toxygen/messenger/messages.py | 5 ++ toxygen/ui/items_factories.py | 6 +- toxygen/ui/main_screen.py | 7 +- toxygen/ui/messages_widgets.py | 14 ++-- 12 files changed, 79 insertions(+), 74 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index ce15093..40fb7c6 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -323,7 +323,7 @@ class App: self._toxes, self._version) self._tray = tray.init_tray(profile, self._settings, self._ms) self._ms.set_dependencies(widgets_factory, self._tray, self._contacts_manager, self._messenger, profile, - self._plugin_loader) + self._plugin_loader, self._file_transfer_handler) self._tray.show() self._ms.show() diff --git a/toxygen/common/event.py b/toxygen/common/event.py index e3ecbf9..687a34d 100644 --- a/toxygen/common/event.py +++ b/toxygen/common/event.py @@ -8,9 +8,13 @@ class Event: 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) diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index c5c0dac..71248db 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -101,6 +101,9 @@ class Contact(basecontact.BaseContact): 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 diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index b4c1803..b826a37 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -1,8 +1,4 @@ -import utils.util as util -import utils.ui as util_ui from contacts.friend import Friend -from PyQt5 import QtCore, QtGui -from wrapper.toxcore_enums_and_consts import * from messenger.messages import * @@ -71,10 +67,10 @@ class ContactsManager: self._screen.messageEdit.clear() return try: - # self.send_typing(False) # TODO: fix self._screen.typing.setVisible(False) current_contact = self.get_curr_contact() if current_contact is not None: + current_contact.typing_notification_handler.send(self._tox, False) current_contact.remove_messages_widgets() # TODO: if required self._unsubscribe_from_events(current_contact) @@ -96,7 +92,12 @@ class ContactsManager: contact.load_corr() corr = contact.get_corr()[-PAGE_SIZE:] for message in corr: - self._messages_items_factory.create_message_item(message) + 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.data) + else: + self._messages_items_factory.create_message_item(message) # if value in self._call: # self._screen.active_call() # elif value in self._incoming_calls: diff --git a/toxygen/contacts/friend.py b/toxygen/contacts/friend.py index 9602eb1..4b189a9 100644 --- a/toxygen/contacts/friend.py +++ b/toxygen/contacts/friend.py @@ -18,14 +18,13 @@ class Friend(contact.Contact): # File transfers support # ----------------------------------------------------------------------------------------------------------------- - def update_transfer_data(self, file_number, status, inline=None): + def update_transfer_data(self, file_number, inline): # TODO: rewrite """ 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) diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py index 91aeb86..92d92a6 100644 --- a/toxygen/file_transfers/file_transfers.py +++ b/toxygen/file_transfers/file_transfers.py @@ -1,7 +1,7 @@ from wrapper.toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL from os.path import basename, getsize, exists, dirname from os import remove, rename, chdir -from time import time, sleep +from time import time from wrapper.tox import Tox from common.event import Event @@ -47,7 +47,7 @@ class FileTransfer: self._done = 0 self._state_changed_event = Event() self._finished_event = Event() - self._file_id = None + self._file_id = self._file = None def set_tox(self, tox): self._tox = tox @@ -85,13 +85,12 @@ class FileTransfer: def cancel(self): self.send_control(TOX_FILE_CONTROL['CANCEL']) - if hasattr(self, '_file'): + if self._file is not None: self._file.close() self.signal() def cancelled(self): - if hasattr(self, '_file'): - sleep(0.1) + if self._file is not None: self._file.close() self.state = FILE_TRANSFER_STATE['CANCELLED'] self.signal() diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index 6269295..3d51767 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -1,8 +1,5 @@ -from file_transfers.file_transfers import * from messenger.messages import * -from history.database import MESSAGE_AUTHOR from ui.contact_items import * -from PyQt5 import QtWidgets import utils.util as util @@ -46,18 +43,18 @@ class FileTransfersHandler: self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL']) return self._tox.file_seek(friend_number, file_number, pos) - self.accept_transfer(None, data[0], friend_number, file_number, size, False, pos) + self.accept_transfer(data[0], friend_number, file_number, size, False, pos) elif inline and size < 1024 * 1024: - self.accept_transfer(None, '', friend_number, file_number, size, True) + self.accept_transfer('', friend_number, file_number, size, True) elif auto: path = self._settings['auto_accept_path'] or util.curr_directory() - self.accept_transfer(None, path + '/' + file_name, friend_number, file_number, size) + 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) + friend, accepted, size, file_name, file_number) def cancel_transfer(self, friend_number, file_number, already_cancelled=False): """ @@ -66,8 +63,6 @@ class FileTransfersHandler: :param file_number: file number :param already_cancelled: was cancelled by friend """ - i = self._get_friend_by_number(friend_number).update_transfer_data(file_number, - FILE_TRANSFER_STATE['CANCELLED']) if (friend_number, file_number) in self._file_transfers: tr = self._file_transfers[(friend_number, file_number)] if not already_cancelled: @@ -77,15 +72,8 @@ class FileTransfersHandler: if (friend_number, file_number) in self._file_transfers: del tr del self._file_transfers[(friend_number, file_number)] - else: - if not already_cancelled: - self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL']) - if friend_number == self.get_active_number() and self.is_active_a_friend(): - tmp = self._messages.count() + i - if tmp >= 0: - self._messages.itemWidget( - self._messages.item(tmp)).update_transfer_state(FILE_TRANSFER_STATE['CANCELLED'], - 0, -1) + elif not already_cancelled: + self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL']) def cancel_not_started_transfer(self, cancel_time): self.get_curr_friend().delete_one_unsent_file(cancel_time) @@ -96,15 +84,11 @@ class FileTransfersHandler: """ tr = self._file_transfers[(friend_number, file_number)] tr.pause(by_friend) - t = FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'] if by_friend else FILE_TRANSFER_STATE['PAUSED_BY_USER'] - self._get_friend_by_number(friend_number).update_transfer_data(file_number, t) def resume_transfer(self, friend_number, file_number, by_friend=False): """ Resume transfer with specified data """ - # self.get_friend_by_number(friend_number).update_transfer_data(file_number, - # TOX_FILE_TRANSFER_STATE['RUNNING']) tr = self._file_transfers[(friend_number, file_number)] if by_friend: tr.state = FILE_TRANSFER_STATE['RUNNING'] @@ -112,9 +96,8 @@ class FileTransfersHandler: else: tr.send_control(TOX_FILE_CONTROL['RESUME']) - def accept_transfer(self, item, path, friend_number, file_number, size, inline=False, from_position=0): + def accept_transfer(self, path, friend_number, file_number, size, inline=False, from_position=0): """ - :param item: transfer item. :param path: path for saving :param friend_number: friend number :param file_number: file number @@ -122,32 +105,23 @@ class FileTransfersHandler: :param inline: is inline image :param from_position: position for start """ - path, file_name = os.path.split(path) - new_file_name, i = file_name, 1 - if not from_position: - while os.path.isfile(path + '/' + new_file_name): # file with same name already exists - if '.' in file_name: # has extension - d = file_name.rindex('.') - else: # no extension - d = len(file_name) - new_file_name = file_name[:d] + ' ({})'.format(i) + file_name[d:] - i += 1 - path = os.path.join(path, new_file_name) + 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 == FILE_TRANSFER_STATE['INCOMING_NOT_STARTED'] + and m.file_number == file_number) + rt.set_state_changed_handler(message.transfer_updated) self._file_transfers[(friend_number, file_number)] = rt self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['RESUME']) - if item is not None: - rt.set_state_changed_handler(item.update_transfer_state) - self._get_friend_by_number(friend_number).update_transfer_data(file_number, - FILE_TRANSFER_STATE['RUNNING']) def send_screenshot(self, data, friend_number): """ - Send screenshot to current active friend + Send screenshot :param data: raw data - png """ self.send_inline(data, 'toxygen_inline.png', friend_number) @@ -169,7 +143,8 @@ class FileTransfersHandler: st.set_transfer_finished_handler(self.transfer_finished) file_number = st.get_file_number() self._file_transfers[(friend.number, file_number)] = st - self._file_transfers_message_service.add_outgoing_transfer_message(friend, st.size, file_name, file_number) + 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) def send_file(self, path, friend_number, is_resend=False, file_id=None): """ @@ -192,7 +167,8 @@ class FileTransfersHandler: file_number = st.get_file_number() self._file_transfers[(friend_number, file_number)] = st file_name = os.path.basename(path) - self._file_transfers_message_service.add_outgoing_transfer_message(friend, st.size, file_name, file_number) + 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) def incoming_chunk(self, friend_number, file_number, position, data): """ @@ -214,13 +190,8 @@ class FileTransfersHandler: elif t is ReceiveToBuffer or (t is SendFromBuffer and self._settings['allow_inline']): # inline image print('inline') inline = InlineImage(transfer.get_data()) - index = self._get_friend_by_number(friend_number).update_transfer_data(file_number, - FILE_TRANSFER_STATE['FINISHED'], - inline) + index = self._get_friend_by_number(friend_number).update_transfer_data(file_number, inline) self._file_transfers_message_service.add_inline_message(transfer, index) - elif t is not SendAvatar: - self._get_friend_by_number(friend_number).update_transfer_data(file_number, - FILE_TRANSFER_STATE['FINISHED']) del self._file_transfers[(friend_number, file_number)] del transfer @@ -279,3 +250,19 @@ class FileTransfersHandler: def _get_friend_by_number(self, friend_number): return self._contact_provider.get_friend_by_number(friend_number) + + @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(path + '/' + new_file_name): # file with same name already exists + if '.' in file_name: # has extension + d = file_name.rindex('.') + else: # no extension + d = len(file_name) + new_file_name = file_name[:d] + ' ({})'.format(i) + file_name[d:] + i += 1 + path = os.path.join(path, new_file_name) + + return path diff --git a/toxygen/file_transfers/file_transfers_messages_service.py b/toxygen/file_transfers/file_transfers_messages_service.py index 963eeb6..06b1e14 100644 --- a/toxygen/file_transfers/file_transfers_messages_service.py +++ b/toxygen/file_transfers/file_transfers_messages_service.py @@ -24,6 +24,8 @@ class FileTransfersMessagesService: 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'] @@ -35,6 +37,8 @@ class FileTransfersMessagesService: friend.append_message(tm) + return tm + def add_inline_message(self, transfer, index): if self._is_active(transfer.friend_number): count = self._messages.count() diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index 34aff38..b14dfd5 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -164,6 +164,11 @@ class TransferMessage(Message): 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) diff --git a/toxygen/ui/items_factories.py b/toxygen/ui/items_factories.py index 674291c..a9b1c1a 100644 --- a/toxygen/ui/items_factories.py +++ b/toxygen/ui/items_factories.py @@ -45,7 +45,7 @@ class MessagesItemsFactory: return item - def create_inline_item(self, data, append, position=0): + def create_inline_item(self, data, append=True, position=0): elem = QtWidgets.QListWidgetItem() item = InlineImageItem(data, self._messages.width(), elem) elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height())) @@ -57,7 +57,7 @@ class MessagesItemsFactory: return item - def create_unsent_file_item(self, tm, append): + def create_unsent_file_item(self, tm, append=True): item = UnsentFileItem(self._file_transfers_handler, self._settings, tm, self._messages.width()) elem = QtWidgets.QListWidgetItem() elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34)) @@ -70,7 +70,7 @@ class MessagesItemsFactory: return item def create_file_transfer_item(self, tm, append=True): - item = tm.get_widget(self._file_transfers_handler, self._settings, tm, self._messages.width()) + item = tm.get_widget(self._file_transfers_handler, self._settings, self._messages.width()) elem = QtWidgets.QListWidgetItem() elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34)) if append: diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 3d9fd0f..c267a12 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -19,14 +19,17 @@ class MainWindow(QtWidgets.QMainWindow): self.setAcceptDrops(True) self._saved = False self._profile = None + self._file_transfer_handler = None self.initUI() - def set_dependencies(self, widget_factory, tray, contacts_manager, messenger, profile, plugins_loader): + def set_dependencies(self, widget_factory, tray, contacts_manager, messenger, profile, plugins_loader, + file_transfer_handler): 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.messageEdit.set_messenger(messenger) def show(self): @@ -519,7 +522,7 @@ class MainWindow(QtWidgets.QMainWindow): caption = util_ui.tr('Choose file') name = util_ui.file_dialog(caption) if name[0]: - self._contacts_manager.send_file(name[0], self._contacts_manager.get_contact().number) + self._file_transfer_handler.send_file(name[0], self._contacts_manager.get_active_number()) def send_screenshot(self, hide=False): self.menu.hide() diff --git a/toxygen/ui/messages_widgets.py b/toxygen/ui/messages_widgets.py index c5b2120..5d86b64 100644 --- a/toxygen/ui/messages_widgets.py +++ b/toxygen/ui/messages_widgets.py @@ -218,10 +218,10 @@ class MessageItem(QtWidgets.QWidget): class FileTransferItem(QtWidgets.QListWidget): - def __init__(self, file_transfers_handler, settings, transfer_message, width, parent=None): + def __init__(self, transfer_message, file_transfer_handler, settings, width, parent=None): QtWidgets.QListWidget.__init__(self, parent) - self._file_transfers_handler = file_transfers_handler + self._file_transfer_handler = file_transfer_handler self.resize(QtCore.QSize(width, 34)) if transfer_message.state == FILE_TRANSFER_STATE['CANCELLED']: self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') @@ -308,7 +308,7 @@ class FileTransferItem(QtWidgets.QListWidget): self.paused = False def cancel_transfer(self, friend_number, file_number): - self._file_transfers_handler.cancel_transfer(friend_number, file_number) + self._file_transfer_handler.cancel_transfer(friend_number, file_number) self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') self.cancel.setVisible(False) self.accept_or_pause.setVisible(False) @@ -319,7 +319,7 @@ class FileTransferItem(QtWidgets.QListWidget): directory = util_ui.directory_dialog(util_ui.tr('Choose folder')) self.pb.setVisible(True) if directory: - self._file_transfer_handler.accept_transfer(self, directory + '/' + self.saved_name, + self._file_transfer_handler.accept_transfer(directory + '/' + self.saved_name, friend_number, file_number, size) self.button_update('pause') elif self.state == FILE_TRANSFER_STATE['PAUSED_BY_USER']: # resume @@ -390,8 +390,8 @@ class FileTransferItem(QtWidgets.QListWidget): class UnsentFileItem(FileTransferItem): - def __init__(self, file_transfers_handler, settings, transfer_message, width, parent=None): - super().__init__(file_transfers_handler, settings, transfer_message, width, parent) + def __init__(self, file_transfer_handler, settings, transfer_message, width, parent=None): + super().__init__(file_transfer_handler, settings, transfer_message, width, parent) self._time = time self.pb.setVisible(False) movie = QtGui.QMovie(util.join_path(util.get_images_directory(), 'spinner.gif')) @@ -399,7 +399,7 @@ class UnsentFileItem(FileTransferItem): movie.start() def cancel_transfer(self, *args): - self._file_transfers_handler.cancel_not_started_transfer(self._time) + self._file_transfer_handler.cancel_not_started_transfer(self._time) class InlineImageItem(QtWidgets.QScrollArea): From 9294c3e77913fd78192a20a438da6c210c0f3aad Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 17 May 2018 15:20:47 +0300 Subject: [PATCH 033/138] file transfers fixes - part 4 --- toxygen/app.py | 4 +- toxygen/contacts/contact.py | 7 ++- toxygen/contacts/profile.py | 17 ++----- toxygen/file_transfers/file_transfers.py | 34 +++++++------- .../file_transfers/file_transfers_handler.py | 46 ++++++++++++------- toxygen/messenger/messages.py | 2 +- toxygen/middleware/callbacks.py | 7 ++- 7 files changed, 63 insertions(+), 54 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index 40fb7c6..03509a6 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -245,7 +245,7 @@ class App: self._save_profile() except Exception as ex: print(ex) - log('Profile creation exception: ' + str(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 @@ -316,7 +316,7 @@ class App: file_transfers_message_service = FileTransfersMessagesService(self._contacts_manager, messages_items_factory, profile, self._ms) self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider, - file_transfers_message_service) + file_transfers_message_service, profile) messages_items_factory.set_file_transfers_handler(self._file_transfer_handler) widgets_factory = WidgetsFactory(self._settings, profile, self._profile_manager, self._contacts_manager, self._file_transfer_handler, self._smiley_loader, self._plugin_loader, diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index 71248db..7faf36d 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -1,6 +1,5 @@ from history.database import * from contacts import basecontact, common -import utils.util as util from messenger.messages import * from contacts.contact_menu import * from file_transfers import file_transfers as ft @@ -119,7 +118,7 @@ class Contact(basecontact.BaseContact): """ :return list of unsent messages """ - messages = filter(lambda x: x.get_owner() == MESSAGE_AUTHOR['NOT_SENT'], self._corr) + messages = filter(lambda x: x.author.type == MESSAGE_AUTHOR['NOT_SENT'], self._corr) return list(messages) def get_unsent_messages_for_saving(self): @@ -127,12 +126,12 @@ class Contact(basecontact.BaseContact): :return list of unsent messages for saving """ messages = filter(lambda x: x.get_type() in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']) - and x.get_owner() == MESSAGE_AUTHOR['NOT_SENT'], self._corr) + and x.author.type == MESSAGE_AUTHOR['NOT_SENT'], self._corr) return list(map(lambda x: x.get_data(), messages)) def mark_as_sent(self): try: - message = list(filter(lambda x: x.get_owner() == MESSAGE_AUTHOR['NOT_SENT'], self._corr))[0] + message = list(filter(lambda x: x.author.type == MESSAGE_AUTHOR['NOT_SENT'], self._corr))[0] message.mark_as_sent() except Exception as ex: util.log('Mark as sent ex: ' + str(ex)) diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index b4dbaf2..63775ff 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -2,9 +2,9 @@ from contacts.friend import * from file_transfers.file_transfers import * import time from contacts import basecontact -from contacts.group_chat import * import utils.ui as util_ui import random +import threading class Profile(basecontact.BaseContact): @@ -29,6 +29,7 @@ class Profile(basecontact.BaseContact): self._load_history = True self._waiting_for_reconnection = False self._contacts_manager = None + self._timer = threading.Timer(50, self.reconnect) # ----------------------------------------------------------------------------------------------------------------- # Edit current user's data @@ -47,7 +48,7 @@ class Profile(basecontact.BaseContact): self._tox.self_set_status(status) elif not self._waiting_for_reconnection: self._waiting_for_reconnection = True - QtCore.QTimer.singleShot(50000, self.reconnect) + self._timer.start() def set_name(self, value): if self.name == value: @@ -125,7 +126,7 @@ class Profile(basecontact.BaseContact): if self.status is None or all(list(map(lambda x: x.status is None, self._contacts))) and len(self._contacts): self._waiting_for_reconnection = True self.reset(self._screen.reset) - QtCore.QTimer.singleShot(50000, self.reconnect) + self._timer.start() def close(self): for friend in filter(lambda x: type(x) is Friend, self._contacts): @@ -135,13 +136,3 @@ class Profile(basecontact.BaseContact): if hasattr(self, '_call'): self._call.stop() del self._call - - def reset_avatar(self, generate_new): - super().reset_avatar(generate_new) - for friend in filter(lambda x: x.status is not None, self._contacts): - self.send_avatar(friend.number) - - def set_avatar(self, data): - super().set_avatar(data) - for friend in filter(lambda x: x.status is not None, self._contacts): - self.send_avatar(friend.number) diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py index 92d92a6..aa1be58 100644 --- a/toxygen/file_transfers/file_transfers.py +++ b/toxygen/file_transfers/file_transfers.py @@ -40,7 +40,7 @@ class FileTransfer: self._path = path self._tox = tox self._friend_number = friend_number - self.state = FILE_TRANSFER_STATE['RUNNING'] + self._state = FILE_TRANSFER_STATE['RUNNING'] self._file_number = file_number self._creation_time = None self._size = float(size) @@ -63,6 +63,15 @@ class FileTransfer: 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): return self._friend_number @@ -87,30 +96,27 @@ class FileTransfer: self.send_control(TOX_FILE_CONTROL['CANCEL']) if self._file is not None: self._file.close() - self.signal() + self._signal() def cancelled(self): if self._file is not None: self._file.close() self.state = FILE_TRANSFER_STATE['CANCELLED'] - self.signal() def pause(self, by_friend): if not by_friend: self.send_control(TOX_FILE_CONTROL['PAUSE']) else: self.state = FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'] - self.signal() def send_control(self, control): if self._tox.file_control(self._friend_number, self._file_number, control): self.state = control - self.signal() def get_file_id(self): return self._tox.file_get_file_id(self._friend_number, self._file_number) - def signal(self): + def _signal(self): percentage = self._done / self._size if self._size else 0 if self._creation_time is None or not percentage: t = -1 @@ -154,11 +160,11 @@ class SendTransfer(FileTransfer): self._tox.file_send_chunk(self._friend_number, self._file_number, position, data) self._done += size else: - if hasattr(self, '_file'): + if self._file is not None: self._file.close() self.state = FILE_TRANSFER_STATE['FINISHED'] self._finished() - self.signal() + self._signal() class SendAvatar(SendTransfer): @@ -200,7 +206,7 @@ class SendFromBuffer(FileTransfer): else: self.state = FILE_TRANSFER_STATE['FINISHED'] self._finished() - self.signal() + self._signal() class SendFromFileBuffer(SendTransfer): @@ -265,7 +271,7 @@ class ReceiveTransfer(FileTransfer): if position + l > self._file_size: self._file_size = position + l self._done += l - self.signal() + self._signal() class ReceiveToBuffer(FileTransfer): @@ -296,7 +302,7 @@ class ReceiveToBuffer(FileTransfer): if position + l > self._data_size: self._data_size = position + l self._done += l - self.signal() + self._signal() class ReceiveAvatar(ReceiveTransfer): @@ -338,8 +344,4 @@ class ReceiveAvatar(ReceiveTransfer): chdir(dirname(avatar_path)) remove(avatar_path) rename(self._path, avatar_path) - self._finished(True) - - def _finished(self, emit=False): - if emit: - super()._finished() + self._finished() diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index 3d51767..bb4041f 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -5,7 +5,7 @@ import utils.util as util class FileTransfersHandler: - def __init__(self, tox, settings, contact_provider, file_transfers_message_service): + def __init__(self, tox, settings, contact_provider, file_transfers_message_service, profile): self._tox = tox self._settings = settings self._contact_provider = contact_provider @@ -14,6 +14,8 @@ class FileTransfersHandler: # 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] + + profile.avatar_changed_event.add_callback(self._send_avatar_to_contacts) def __del__(self): self._settings['paused_file_transfers'] = self._paused_file_transfers if self._settings['resend_files'] else {} @@ -43,18 +45,22 @@ class FileTransfersHandler: 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(data[0], 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) + 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): """ @@ -92,7 +98,6 @@ class FileTransfersHandler: tr = self._file_transfers[(friend_number, file_number)] if by_friend: tr.state = FILE_TRANSFER_STATE['RUNNING'] - tr.signal() else: tr.send_control(TOX_FILE_CONTROL['RESUME']) @@ -117,7 +122,7 @@ class FileTransfersHandler: and m.file_number == file_number) rt.set_state_changed_handler(message.transfer_updated) self._file_transfers[(friend_number, file_number)] = rt - self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['RESUME']) + rt.send_control(TOX_FILE_CONTROL['RESUME']) def send_screenshot(self, data, friend_number): """ @@ -140,11 +145,7 @@ class FileTransfersHandler: elif friend.status is None and is_resend: raise RuntimeError() st = SendFromBuffer(self._tox, friend.number, data, file_name) - st.set_transfer_finished_handler(self.transfer_finished) - 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) + self._send_file_add_handlers(st, friend, file_name) def send_file(self, path, friend_number, is_resend=False, file_id=None): """ @@ -163,12 +164,8 @@ class FileTransfersHandler: print('Error in sending') raise RuntimeError() st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id) - st.set_transfer_finished_handler(self.transfer_finished) - file_number = st.get_file_number() - self._file_transfers[(friend_number, file_number)] = st file_name = os.path.basename(path) - 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) + self._send_file_add_handlers(st, friend, file_name) def incoming_chunk(self, friend_number, file_number, position, data): """ @@ -244,6 +241,11 @@ class FileTransfersHandler: else: friend.reset_avatar(self._settings['identicons']) + def _send_avatar_to_contacts(self): + friends = self._get_all_friends() + for friend in friends: + self.send_avatar(friend.number) + # ----------------------------------------------------------------------------------------------------------------- # Private methods # ----------------------------------------------------------------------------------------------------------------- @@ -251,6 +253,16 @@ class FileTransfersHandler: 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_handlers(self, st, friend, file_name): + 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) + @staticmethod def _generate_valid_path(path, from_position): path, file_name = os.path.split(path) diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index b14dfd5..9a56aba 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -72,7 +72,7 @@ class Message: def _get_id(): Message.MESSAGE_ID += 1 - return Message.MESSAGE_ID + return int(Message.MESSAGE_ID) class TextMessage(Message): diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index c9571a2..d4fc7c4 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -11,6 +11,7 @@ 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: gc callbacks and refactoring. Use contact provider instead of manager @@ -49,7 +50,11 @@ def friend_status(contacts_manager, file_transfer_handler, profile, settings): 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) - invoke_in_main_thread(QtCore.QTimer.singleShot, 5000, lambda: file_transfer_handler.send_files(friend_number)) + + 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 From bcefe9bc79d2a3c2477ee000f7c1f2020de1f856 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 17 May 2018 16:59:46 +0300 Subject: [PATCH 034/138] friend menu fixes - correct ordering, submenus fixes --- toxygen/contacts/contact_menu.py | 109 +++++++++++++++++-------------- 1 file changed, 60 insertions(+), 49 deletions(-) diff --git a/toxygen/contacts/contact_menu.py b/toxygen/contacts/contact_menu.py index d924702..8047392 100644 --- a/toxygen/contacts/contact_menu.py +++ b/toxygen/contacts/contact_menu.py @@ -6,16 +6,19 @@ import utils.ui as util_ui # Builder # ----------------------------------------------------------------------------------------------------------------- -def _create_menu(menu_name): - return QtWidgets.QMenu(menu_name or '') +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._actions = {} + self._submenus = {} self._name = None + self._index = 0 def with_name(self, name): self._name = name @@ -23,40 +26,49 @@ class ContactMenuBuilder: return self def with_action(self, text, handler): - self._actions.append((text, handler)) + self._add_action(text, handler) return self def with_actions(self, actions): - self._actions.extend(actions) + for action in actions: + self._add_action(*action) return self - def with_submenu(self, submenu): - self._add_submenu(submenu) + def with_submenu(self, submenu_builder): + self._add_submenu(submenu_builder) return self - def with_optional_submenu(self, submenu): - if submenu is not None: - self._add_submenu(submenu) + def with_optional_submenu(self, submenu_builder): + if submenu_builder is not None: + self._add_submenu(submenu_builder) return self - def build(self): # TODO: actions order - menu = _create_menu(self._name) + def build(self, parent=None): + menu = _create_menu(self._name, parent) - for text, handler in self._actions: - action = menu.addAction(text) - action.triggered.connect(handler) - - for submenu in self._submenus: - menu.addMenu(submenu) + 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.append(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 @@ -75,9 +87,9 @@ class BaseContactMenuGenerator: class FriendMenuGenerator(BaseContactMenuGenerator): def generate(self, plugin_loader, contacts_manager, main_screen, settings, number): - history_menu = self._generate_history_menu(main_screen, number) - copy_menu = self._generate_copy_menu(main_screen) - plugins_menu = self._generate_plugins_menu(plugin_loader, number) + history_menu_builder = self._generate_history_menu_builder(main_screen, number) + copy_menu_builder = self._generate_copy_menu_builder(main_screen) + plugins_menu_builder = self._generate_plugins_menu_builder(plugin_loader, number) 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') @@ -85,14 +97,13 @@ class FriendMenuGenerator(BaseContactMenuGenerator): builder = ContactMenuBuilder() menu = (builder .with_action(util_ui.tr('Set alias'), lambda: main_screen.set_alias(number)) - .with_action(util_ui.tr('Chat history'), lambda: main_screen.clear_history(number)) - .with_submenu(history_menu) - .with_submenu(copy_menu) + .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) + .with_optional_submenu(plugins_menu_builder) ).build() return menu @@ -102,42 +113,42 @@ class FriendMenuGenerator(BaseContactMenuGenerator): # ----------------------------------------------------------------------------------------------------------------- @staticmethod - def _generate_history_menu(main_screen, number): + def _generate_history_menu_builder(main_screen, number): history_menu_builder = ContactMenuBuilder() - history_menu = (history_menu_builder - .with_name(util_ui.tr('Chat history')) - .with_action(util_ui.tr('Clear history'), lambda: main_screen.clear_history(number)) - .with_action(util_ui.tr('Export as text'), lambda: main_screen.export_history(number)) - .with_action(util_ui.tr('Export as HTML'), lambda: main_screen.export_history(number, False)) - ).build() + (history_menu_builder + .with_name(util_ui.tr('Chat history')) + .with_action(util_ui.tr('Clear history'), lambda: main_screen.clear_history(number)) + .with_action(util_ui.tr('Export as text'), lambda: main_screen.export_history(number)) + .with_action(util_ui.tr('Export as HTML'), lambda: main_screen.export_history(number, False)) + ) - return history_menu + return history_menu_builder - def _generate_copy_menu(self, main_screen): + def _generate_copy_menu_builder(self, main_screen): copy_menu_builder = ContactMenuBuilder() - copy_menu = (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.name)) - .with_action(util_ui.tr('Public key'), lambda: main_screen.copy_text(self._contact.tox_id)) - ).build() + (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 + return copy_menu_builder @staticmethod - def _generate_plugins_menu(plugin_loader, number): + 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 = (plugins_menu_builder - .with_name(util_ui.tr('Plugins')) - .with_actions(plugins_actions) - ).build() + (plugins_menu_builder + .with_name(util_ui.tr('Plugins')) + .with_actions(plugins_actions) + ) - return plugins_menu + return plugins_menu_builder def _generate_groups_menu(self, contacts_manager): # TODO: fix chats = contacts_manager.get_group_chats() From 0b1e899931f97ee559a16b8e809fedcf7f889439 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 17 May 2018 19:03:58 +0300 Subject: [PATCH 035/138] various fixes - file transfers, friend exit callback --- toxygen/av/calls_manager.py | 18 +++++++++------- toxygen/contacts/contact_menu.py | 3 ++- toxygen/contacts/profile.py | 21 ------------------- .../file_transfers/file_transfers_handler.py | 13 +++++++++++- toxygen/middleware/callbacks.py | 10 ++++++--- 5 files changed, 32 insertions(+), 33 deletions(-) diff --git a/toxygen/av/calls_manager.py b/toxygen/av/calls_manager.py index 03d5c53..6e290e2 100644 --- a/toxygen/av/calls_manager.py +++ b/toxygen/av/calls_manager.py @@ -1,7 +1,7 @@ import threading import cv2 import av.calls -from PyQt5 import QtWidgets +import utils.ui as util_ui from messenger.messages import * import time from ui import av_widgets @@ -35,9 +35,9 @@ class CallsManager: self._call(num, audio, video) self._screen.active_call() if video: - text = QtWidgets.QApplication.translate("incoming_call", "Outgoing video call") + text = util_ui.tr("Outgoing video call") else: - text = QtWidgets.QApplication.translate("incoming_call", "Outgoing audio call") + text = util_ui.tr("Outgoing audio call") self.get_curr_friend().append_message(InfoMessage(text, time.time())) self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) self._messages.scrollToBottom() @@ -52,9 +52,9 @@ class CallsManager: return friend = self.get_friend_by_number(friend_number) if video: - text = QtWidgets.QApplication.translate("incoming_call", "Incoming video call") + text = util_ui.tr("Incoming video call") else: - text = QtWidgets.QApplication.translate("incoming_call", "Incoming audio call") + text = util_ui.tr("Incoming audio call") friend.append_message(InfoMessage(text, time.time())) self._incoming_calls.add(friend_number) if friend_number == self.get_active_number(): @@ -83,9 +83,9 @@ class CallsManager: """ if friend_number in self._incoming_calls: self._incoming_calls.remove(friend_number) - text = QtWidgets.QApplication.translate("incoming_call", "Call declined") + text = util_ui.tr("Call declined") else: - text = QtWidgets.QApplication.translate("incoming_call", "Call finished") + text = util_ui.tr("Call finished") 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 @@ -103,3 +103,7 @@ class CallsManager: if friend_number == self.get_active_number(): self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) self._messages.scrollToBottom() + + def friend_exit(self, friend_number): + if friend_number in self._call: + self._call.finish_call(friend_number, True) diff --git a/toxygen/contacts/contact_menu.py b/toxygen/contacts/contact_menu.py index 8047392..c580a3f 100644 --- a/toxygen/contacts/contact_menu.py +++ b/toxygen/contacts/contact_menu.py @@ -32,7 +32,8 @@ class ContactMenuBuilder: def with_actions(self, actions): for action in actions: - self._add_action(*action) + (text, handler) = action + self._add_action(text, handler) return self diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 63775ff..4edae71 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -75,27 +75,6 @@ class Profile(basecontact.BaseContact): return self._tox_id - # ----------------------------------------------------------------------------------------------------------------- - # Friend connection status callbacks - # ----------------------------------------------------------------------------------------------------------------- - - def friend_exit(self, friend_number): - """ - Friend with specified number quit - """ - self.get_friend_by_number(friend_number).status = None - self.friend_typing(friend_number, False) - if friend_number in self._call: - self._call.finish_call(friend_number, True) - for friend_num, file_num in list(self._file_transfers.keys()): - if friend_num == friend_number: - ft = self._file_transfers[(friend_num, file_num)] - if type(ft) is SendTransfer: - self._paused_file_transfers[ft.get_id()] = [ft.get_path(), friend_num, False, -1] - elif type(ft) is ReceiveTransfer and ft.state != FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: - self._paused_file_transfers[ft.get_id()] = [ft.get_path(), friend_num, True, ft.total_size()] - self.cancel_transfer(friend_num, file_num, True) - # ----------------------------------------------------------------------------------------------------------------- # Private messages # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index bb4041f..d060ae7 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -118,7 +118,8 @@ class FileTransfersHandler: 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 == FILE_TRANSFER_STATE['INCOMING_NOT_STARTED'] + 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 @@ -214,6 +215,16 @@ class FileTransfersHandler: except Exception as ex: print('Exception in file sending: ' + str(ex)) + def friend_exit(self, friend_number): + for friend_num, file_num in list(self._file_transfers.keys()): + if friend_num == friend_number: + ft = self._file_transfers[(friend_num, file_num)] + if type(ft) is SendTransfer: + self._paused_file_transfers[ft.get_id()] = [ft.get_path(), friend_num, False, -1] + elif type(ft) is ReceiveTransfer and ft.state != FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: + self._paused_file_transfers[ft.get_id()] = [ft.get_path(), friend_num, True, ft.total_size()] + self.cancel_transfer(friend_num, file_num, True) + # ----------------------------------------------------------------------------------------------------------------- # Avatars support # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index d4fc7c4..860070f 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -60,7 +60,8 @@ def friend_status(contacts_manager, file_transfer_handler, profile, settings): return wrapped -def friend_connection_status(contacts_manager, profile, settings, plugin_loader, file_transfer_handler): +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) @@ -68,8 +69,11 @@ def friend_connection_status(contacts_manager, profile, settings, plugin_loader, 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(profile.friend_exit, friend_number) + 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: @@ -394,7 +398,7 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, tox.callback_friend_status(friend_status(contacts_manager, file_transfer_handler, profile, settings), 0) tox.callback_friend_message(friend_message(messenger, contacts_manager, profile, settings, main_window, tray), 0) tox.callback_friend_connection_status(friend_connection_status(contacts_manager, profile, settings, plugin_loader, - file_transfer_handler), 0) + file_transfer_handler, messenger, calls_manager), 0) tox.callback_friend_name(friend_name(contacts_manager), 0) tox.callback_friend_status_message(friend_status_message(contacts_manager, messenger), 0) tox.callback_friend_request(friend_request(contacts_manager), 0) From bfa91df927236ad81b2b31dfd7076c91db496678 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 17 May 2018 19:28:44 +0300 Subject: [PATCH 036/138] fixed deps in main_screen.py --- toxygen/app.py | 2 +- toxygen/ui/main_screen.py | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index 03509a6..a618cc2 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -323,7 +323,7 @@ class App: self._toxes, self._version) self._tray = tray.init_tray(profile, self._settings, self._ms) self._ms.set_dependencies(widgets_factory, self._tray, self._contacts_manager, self._messenger, profile, - self._plugin_loader, self._file_transfer_handler) + self._plugin_loader, self._file_transfer_handler, history, self._calls_manager) self._tray.show() self._ms.show() diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index c267a12..67b09a7 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -19,17 +19,19 @@ class MainWindow(QtWidgets.QMainWindow): self.setAcceptDrops(True) self._saved = False self._profile = None - self._file_transfer_handler = None + self._file_transfer_handler = self._history_loader = self._calls_manager = None self.initUI() def set_dependencies(self, widget_factory, tray, contacts_manager, messenger, profile, plugins_loader, - file_transfer_handler): + file_transfer_handler, history_loader, calls_manager): 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.messageEdit.set_messenger(messenger) def show(self): @@ -267,11 +269,11 @@ class MainWindow(QtWidgets.QMainWindow): 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.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.profile.call_click(True, True)) + self.videocallButton.clicked.connect(lambda: self._calls_manager.call_click(True, True)) self.update_call_state('call') self.typing = QtWidgets.QLabel(Form) self.typing.setGeometry(QtCore.QRect(500, 25, 50, 30)) @@ -306,7 +308,7 @@ class MainWindow(QtWidgets.QMainWindow): def load(pos): if not pos: - self.profile.load_history() + self._history_loader.load_history() self.messages.verticalScrollBar().setValue(1) self.messages.verticalScrollBar().valueChanged.connect(load) self.messages.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) @@ -413,7 +415,7 @@ class MainWindow(QtWidgets.QMainWindow): 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.profile.export_history(self.profile.active_friend, True, indexes) + s = self._history_loader.export_history(self._contacts_manager.get_curr_friend(), True, indexes) clipboard = QtWidgets.QApplication.clipboard() clipboard.setText(s) elif key == QtCore.Qt.Key_Z and modifiers & QtCore.Qt.ControlModifier and self.messages.selectedIndexes(): @@ -622,7 +624,7 @@ class MainWindow(QtWidgets.QMainWindow): self._contacts_manager.delete_friend(num) def block_friend(self, num): - friend = self.profile.get_contact(num) + friend = self._contacts_managere.get_contact(num) self._contacts_manager.block_user(friend.tox_id) @staticmethod @@ -662,7 +664,7 @@ class MainWindow(QtWidgets.QMainWindow): 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() + self._profile.change_status() else: super().mouseReleaseEvent(event) From 9365ca291363311031a54f7b6ee615fa332fffa8 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 17 May 2018 21:45:35 +0300 Subject: [PATCH 037/138] file transfers fixes - part 5 --- toxygen/contacts/friend.py | 4 ++-- toxygen/file_transfers/file_transfers_handler.py | 15 ++++++++------- toxygen/ui/messages_widgets.py | 4 +++- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/toxygen/contacts/friend.py b/toxygen/contacts/friend.py index 4b189a9..9c4e719 100644 --- a/toxygen/contacts/friend.py +++ b/toxygen/contacts/friend.py @@ -49,8 +49,8 @@ class Friend(contact.Contact): 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)) + def delete_one_unsent_file(self, message_id): + self._corr = list(filter(lambda m: not (type(m) is UnsentFile and m.message_id == message_id), self._corr)) # ----------------------------------------------------------------------------------------------------------------- # History support diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index d060ae7..4ee1282 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -81,8 +81,8 @@ class FileTransfersHandler: elif not already_cancelled: self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL']) - def cancel_not_started_transfer(self, cancel_time): - self.get_curr_friend().delete_one_unsent_file(cancel_time) + 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): """ @@ -128,7 +128,8 @@ class FileTransfersHandler: def send_screenshot(self, data, friend_number): """ Send screenshot - :param data: raw data - png + :param data: raw data - png format + :param friend_number: friend number """ self.send_inline(data, 'toxygen_inline.png', friend_number) @@ -158,7 +159,7 @@ class FileTransfersHandler: """ friend = self._get_friend_by_number(friend_number) if friend.status is None and not is_resend: - m = UnsentFile(path, None, time.time()) + m = UnsentFile(path, None, util.get_unix_time()) friend.append_message(m) return elif friend.status is None and is_resend: @@ -220,9 +221,9 @@ class FileTransfersHandler: if friend_num == friend_number: ft = self._file_transfers[(friend_num, file_num)] if type(ft) is SendTransfer: - self._paused_file_transfers[ft.get_id()] = [ft.get_path(), friend_num, False, -1] + 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.get_id()] = [ft.get_path(), friend_num, True, ft.total_size()] + self._paused_file_transfers[ft.file_id] = [ft.path, friend_num, True, ft.total_size()] self.cancel_transfer(friend_num, file_num, True) # ----------------------------------------------------------------------------------------------------------------- @@ -235,7 +236,7 @@ class FileTransfersHandler: :param avatar_path: path to avatar or None if reset """ sa = SendAvatar(avatar_path, self._tox, friend_number) - self._file_transfers[(friend_number, sa.get_file_number())] = sa + self._file_transfers[(friend_number, sa.file_number)] = sa def incoming_avatar(self, friend_number, file_number, size): """ diff --git a/toxygen/ui/messages_widgets.py b/toxygen/ui/messages_widgets.py index 5d86b64..818f766 100644 --- a/toxygen/ui/messages_widgets.py +++ b/toxygen/ui/messages_widgets.py @@ -397,9 +397,11 @@ class UnsentFileItem(FileTransferItem): movie = QtGui.QMovie(util.join_path(util.get_images_directory(), 'spinner.gif')) self.time.setMovie(movie) movie.start() + self._message_id = transfer_message.message_id + self._friend_number = transfer_message.friend_number def cancel_transfer(self, *args): - self._file_transfer_handler.cancel_not_started_transfer(self._time) + self._file_transfer_handler.cancel_not_started_transfer(self._friend_number, self._message_id) class InlineImageItem(QtWidgets.QScrollArea): From a3103f6fb94619512b963190d16dff3b18c4b610 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 17 May 2018 23:31:48 +0300 Subject: [PATCH 038/138] file transfers fixes - part 6 --- toxygen/file_transfers/file_transfers.py | 4 +++- toxygen/middleware/callbacks.py | 7 ++----- toxygen/ui/items_factories.py | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py index aa1be58..e5c2bb4 100644 --- a/toxygen/file_transfers/file_transfers.py +++ b/toxygen/file_transfers/file_transfers.py @@ -136,11 +136,13 @@ class SendTransfer(FileTransfer): def __init__(self, path, tox, friend_number, kind=TOX_FILE_KIND['DATA'], file_id=None): if path is not None: - self._file = open(path, 'rb') + fl = open(path, 'rb') size = getsize(path) else: + fl = None size = 0 super().__init__(path, tox, friend_number, size) + self._file = fl self.state = FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'] self._file_number = tox.file_send(friend_number, kind, size, file_id, bytes(basename(path), 'utf-8') if path else b'') diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 860070f..9f4cdd8 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -26,11 +26,8 @@ def self_connection_status(tox, profile): """ def wrapped(tox_link, connection, user_data): print('Connection status: ', str(connection)) - if profile.status is None: - status = tox.self_get_status() - invoke_in_main_thread(profile.set_status, status) - elif connection == TOX_CONNECTION['NONE']: - invoke_in_main_thread(profile.set_status, None) + status = None if connection == TOX_CONNECTION['NONE'] else tox.self_get_status() + invoke_in_main_thread(profile.set_status, None) return wrapped diff --git a/toxygen/ui/items_factories.py b/toxygen/ui/items_factories.py index a9b1c1a..46e9df8 100644 --- a/toxygen/ui/items_factories.py +++ b/toxygen/ui/items_factories.py @@ -70,7 +70,7 @@ class MessagesItemsFactory: return item def create_file_transfer_item(self, tm, append=True): - item = tm.get_widget(self._file_transfers_handler, self._settings, self._messages.width()) + item = tm.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: From 1b8241eee9ea628f45b25b89f4ec0af8c780cd64 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 18 May 2018 00:06:14 +0300 Subject: [PATCH 039/138] profile minor fixes --- toxygen/app.py | 4 ++-- toxygen/contacts/profile.py | 29 +++++++++--------------- toxygen/file_transfers/file_transfers.py | 2 +- toxygen/middleware/callbacks.py | 6 ++--- toxygen/ui/menu.py | 2 +- 5 files changed, 18 insertions(+), 25 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index a618cc2..3a3476e 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -298,11 +298,11 @@ class App: self._calls_manager = CallsManager(self._tox.AV, self._settings) db = Database(self._path.replace('.tox', '.db'), self._toxes) - profile = Profile(self._profile_manager, self._tox, self._ms) - self._plugin_loader = PluginLoader(self._tox, self._toxes, profile, self._settings) friend_items_factory = FriendItemsFactory(self._settings, self._ms) self._friend_factory = FriendFactory(self._profile_manager, self._settings, self._tox, db, friend_items_factory) self._contacts_provider = ContactProvider(self._tox, self._friend_factory) + profile = Profile(self._profile_manager, self._tox, self._ms, self._contacts_provider, self._reset) + self._plugin_loader = PluginLoader(self._tox, self._toxes, profile, self._settings) history = None messages_items_factory = MessagesItemsFactory(self._settings, self._plugin_loader, self._smiley_loader, self._ms, lambda m: history.delete_message(m)) diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 4edae71..6393f7e 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -11,7 +11,7 @@ class Profile(basecontact.BaseContact): """ Profile of current toxygen user. Contains friends list, tox instance """ - def __init__(self, profile_manager, tox, screen): + def __init__(self, profile_manager, tox, screen, contacts_provider, reset_action): """ :param tox: tox instance :param screen: ref to main screen @@ -25,11 +25,10 @@ class Profile(basecontact.BaseContact): self._screen = screen self._messages = screen.messages self._tox = tox - self._file_transfers = {} # dict of file transfers. key - tuple (friend_number, file_number) - self._load_history = True + self._contacts_provider = contacts_provider + self._reset_action = reset_action self._waiting_for_reconnection = False - self._contacts_manager = None - self._timer = threading.Timer(50, self.reconnect) + self._timer = threading.Timer(50, self._reconnect) # ----------------------------------------------------------------------------------------------------------------- # Edit current user's data @@ -68,7 +67,7 @@ class Profile(basecontact.BaseContact): super().set_status_message(value) self._tox.self_set_status_message(self._status_message.encode('utf-8')) - def new_nospam(self): + 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() @@ -88,23 +87,20 @@ class Profile(basecontact.BaseContact): # Reset # ----------------------------------------------------------------------------------------------------------------- - def reset(self, restart): + def _restart(self): """ Recreate tox instance - :param restart: method which calls restart and returns new tox instance """ - for friend in self._contacts: - self.friend_exit(friend.number) del self._tox - self._tox = restart() + self._tox = self._reset_action() self.status = None - self._contacts_manager.update_filtration() - def reconnect(self): + def _reconnect(self): self._waiting_for_reconnection = False - if self.status is None or all(list(map(lambda x: x.status is None, self._contacts))) and len(self._contacts): + contacts = self._contacts_provider.get_all() + if self.status is None or all(list(map(lambda x: x.status is None, contacts))) and len(contacts): self._waiting_for_reconnection = True - self.reset(self._screen.reset) + self._restart() self._timer.start() def close(self): @@ -112,6 +108,3 @@ class Profile(basecontact.BaseContact): self.friend_exit(friend.number) for i in range(len(self._contacts)): del self._contacts[0] - if hasattr(self, '_call'): - self._call.stop() - del self._call diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py index e5c2bb4..710320c 100644 --- a/toxygen/file_transfers/file_transfers.py +++ b/toxygen/file_transfers/file_transfers.py @@ -161,12 +161,12 @@ class SendTransfer(FileTransfer): data = self._file.read(size) self._tox.file_send_chunk(self._friend_number, self._file_number, position, data) self._done += size + self._signal() else: if self._file is not None: self._file.close() self.state = FILE_TRANSFER_STATE['FINISHED'] self._finished() - self._signal() class SendAvatar(SendTransfer): diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 9f4cdd8..83dcc64 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -1,4 +1,4 @@ -from PyQt5 import QtGui, QtCore +from PyQt5 import QtGui from user_data.settings import Settings from contacts.profile import Profile from wrapper.toxcore_enums_and_consts import * @@ -26,8 +26,8 @@ def self_connection_status(tox, profile): """ def wrapped(tox_link, connection, user_data): print('Connection status: ', str(connection)) - status = None if connection == TOX_CONNECTION['NONE'] else tox.self_get_status() - invoke_in_main_thread(profile.set_status, None) + status = tox.self_get_status() if connection != TOX_CONNECTION['NONE'] else None + invoke_in_main_thread(profile.set_status, status) return wrapped diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index 1a33bde..d912619 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -242,7 +242,7 @@ class ProfileSettings(CenteredWidget): self.copy_pk.setIconSize(QtCore.QSize(10, 10)) def new_no_spam(self): - self.tox_id.setText(self._profile.new_nospam()) + self.tox_id.setText(self._profile.set_new_nospam()) def reset_avatar(self): self._profile.reset_avatar(self._settings['identicons']) From bde69bd417172ab761e1a184f14fb82e9d7b9b5d Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 18 May 2018 12:26:02 +0300 Subject: [PATCH 040/138] file transfers fixes - part 7 --- toxygen/app.py | 1 + toxygen/contacts/friend.py | 5 ++--- toxygen/file_transfers/file_transfers.py | 4 ++++ .../file_transfers/file_transfers_handler.py | 19 +++++++++++++------ toxygen/middleware/callbacks.py | 12 +++++++++--- toxygen/ui/items_factories.py | 2 +- toxygen/ui/messages_widgets.py | 4 ++-- 7 files changed, 32 insertions(+), 15 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index 3a3476e..d20eea7 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -29,6 +29,7 @@ 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 +import styles.style # TODO: dynamic loading class App: diff --git a/toxygen/contacts/friend.py b/toxygen/contacts/friend.py index 9c4e719..81830da 100644 --- a/toxygen/contacts/friend.py +++ b/toxygen/contacts/friend.py @@ -18,13 +18,12 @@ class Friend(contact.Contact): # File transfers support # ----------------------------------------------------------------------------------------------------------------- - def update_transfer_data(self, file_number, inline): # TODO: rewrite + def insert_inline(self, before_message_id, inline): """ 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 = list(filter(lambda x: x.message_id == before_message_id, self._corr))[0] i = self._corr.index(tr) if inline: # inline was loaded self._corr.insert(i, inline) diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py index 710320c..166cc7f 100644 --- a/toxygen/file_transfers/file_transfers.py +++ b/toxygen/file_transfers/file_transfers.py @@ -198,6 +198,8 @@ class SendFromBuffer(FileTransfer): def get_data(self): return self._data + data = property(get_data) + def send_chunk(self, position, size): if self._creation_time is None: self._creation_time = time() @@ -289,6 +291,8 @@ class ReceiveToBuffer(FileTransfer): def get_data(self): return self._data + data = property(get_data) + def write_chunk(self, position, data): if self._creation_time is None: self._creation_time = time() diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index 4ee1282..f64c201 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -14,6 +14,8 @@ class FileTransfersHandler: # 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) @@ -124,6 +126,8 @@ class FileTransfersHandler: 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): """ @@ -147,7 +151,7 @@ class FileTransfersHandler: elif friend.status is None and is_resend: raise RuntimeError() st = SendFromBuffer(self._tox, friend.number, data, file_name) - self._send_file_add_handlers(st, friend, 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): """ @@ -167,7 +171,7 @@ class FileTransfersHandler: raise RuntimeError() st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id) file_name = os.path.basename(path) - self._send_file_add_handlers(st, friend, file_name) + self._send_file_add_set_handlers(st, friend, file_name) def incoming_chunk(self, friend_number, file_number, position, data): """ @@ -188,11 +192,12 @@ class FileTransfersHandler: 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 = InlineImage(transfer.get_data()) - index = self._get_friend_by_number(friend_number).update_transfer_data(file_number, inline) + inline = InlineImage(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)] - del transfer def send_files(self, friend_number): friend = self._get_friend_by_number(friend_number) @@ -268,12 +273,14 @@ class FileTransfersHandler: def _get_all_friends(self): return self._contact_provider.get_all_friends() - def _send_file_add_handlers(self, st, friend, file_name): + 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): diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 83dcc64..bd122d6 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -195,8 +195,11 @@ def file_recv_chunk(file_transfer_handler): Incoming chunk """ def wrapped(tox, friend_number, file_number, position, chunk, length, user_data): - execute(file_transfer_handler.incoming_chunk, friend_number, file_number, position, - chunk[:length] if length else None) + chunk = chunk[:length] if length else None + if length: + execute(file_transfer_handler.incoming_chunk, friend_number, file_number, position, chunk) + else: + invoke_in_main_thread(file_transfer_handler.incoming_chunk, friend_number, file_number, position, chunk) return wrapped @@ -206,7 +209,10 @@ 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) + if size: + execute(file_transfer_handler.outgoing_chunk, friend_number, file_number, position, size) + else: + invoke_in_main_thread(file_transfer_handler.outgoing_chunk, friend_number, file_number, position, size) return wrapped diff --git a/toxygen/ui/items_factories.py b/toxygen/ui/items_factories.py index 46e9df8..84bf9c3 100644 --- a/toxygen/ui/items_factories.py +++ b/toxygen/ui/items_factories.py @@ -47,7 +47,7 @@ class MessagesItemsFactory: def create_inline_item(self, data, append=True, position=0): elem = QtWidgets.QListWidgetItem() - item = InlineImageItem(data, self._messages.width(), elem) + item = InlineImageItem(data, self._messages.width(), elem, self._messages) elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height())) if append: self._messages.addItem(elem) diff --git a/toxygen/ui/messages_widgets.py b/toxygen/ui/messages_widgets.py index 818f766..ad337dd 100644 --- a/toxygen/ui/messages_widgets.py +++ b/toxygen/ui/messages_widgets.py @@ -406,9 +406,9 @@ class UnsentFileItem(FileTransferItem): 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._elem = elem self._image_label = QtWidgets.QLabel(self) From e8a0a3f5bebb171f05bdb6cd0e60e60d8f4696d3 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 18 May 2018 12:54:00 +0300 Subject: [PATCH 041/138] file transfers fixes - part 8 (unsent files minor fixes) --- toxygen/contacts/friend.py | 13 +++++----- .../file_transfers/file_transfers_handler.py | 26 +++++++++---------- .../file_transfers_messages_service.py | 13 ++++++++++ toxygen/messenger/messages.py | 17 ++++++++++-- toxygen/ui/items_factories.py | 8 +++--- toxygen/ui/main_screen.py | 3 ++- toxygen/ui/messages_widgets.py | 4 +-- 7 files changed, 55 insertions(+), 29 deletions(-) diff --git a/toxygen/contacts/friend.py b/toxygen/contacts/friend.py index 81830da..63edd6a 100644 --- a/toxygen/contacts/friend.py +++ b/toxygen/contacts/friend.py @@ -32,15 +32,15 @@ class Friend(contact.Contact): pass def get_unsent_files(self): - messages = filter(lambda x: type(x) is UnsentFile, self._corr) - return messages + messages = filter(lambda x: type(x) is UnsentFileMessage, self._corr) + return list(messages) def clear_unsent_files(self): - self._corr = list(filter(lambda x: type(x) is not UnsentFile, self._corr)) + self._corr = list(filter(lambda x: type(x) is not UnsentFileMessage, self._corr)) - def remove_invalid_unsent_files(self): + def remove_invalid_unsent_files(self): # TODO: fix def is_valid(message): - if type(message) is not UnsentFile: + if type(message) is not UnsentFileMessage: return True if message.get_data()[1] is not None: return True @@ -49,7 +49,8 @@ class Friend(contact.Contact): 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 UnsentFile and m.message_id == message_id), self._corr)) + self._corr = list(filter(lambda m: not (type(m) is UnsentFileMessage and m.message_id == message_id), + self._corr)) # ----------------------------------------------------------------------------------------------------------------- # History support diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index f64c201..c46ee58 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -145,8 +145,7 @@ class FileTransfersHandler: 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: - m = UnsentFile(file_name, data, time.time()) - friend.append_message(m) + self._file_transfers_message_service.add_unsent_file_message(friend, file_name, data) return elif friend.status is None and is_resend: raise RuntimeError() @@ -163,8 +162,7 @@ class FileTransfersHandler: """ friend = self._get_friend_by_number(friend_number) if friend.status is None and not is_resend: - m = UnsentFile(path, None, util.get_unix_time()) - friend.append_message(m) + 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') @@ -192,7 +190,7 @@ class FileTransfersHandler: 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 = InlineImage(transfer.data) + 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) @@ -203,20 +201,20 @@ class FileTransfersHandler: friend = self._get_friend_by_number(friend_number) friend.remove_invalid_unsent_files() files = friend.get_unsent_files() - try: # TODO: fix + try: for fl in files: - data = fl.get_data() - if data[1] is not None: - self.send_inline(data[1], data[0], friend_number, True) + data, path = fl.data, fl.path + if data is not None: + self.send_inline(data, path, friend_number, True) else: - self.send_file(data[0], friend_number, True) + self.send_file(path, friend_number, True) friend.clear_unsent_files() for key in list(self._paused_file_transfers.keys()): - data = self._paused_file_transfers[key] - if not os.path.exists(data[0]): + (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 data[1] == friend_number and not data[2]: - self.send_file(data[0], friend_number, True, 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)) diff --git a/toxygen/file_transfers/file_transfers_messages_service.py b/toxygen/file_transfers/file_transfers_messages_service.py index 06b1e14..7f8b5f6 100644 --- a/toxygen/file_transfers/file_transfers_messages_service.py +++ b/toxygen/file_transfers/file_transfers_messages_service.py @@ -45,6 +45,16 @@ class FileTransfersMessagesService: if count + index + 1 >= 0: self._create_inline_item(transfer.data, count + index + 1) + def add_unsent_file_message(self, friend, file_path, data): + tm = UnsentFileMessage(file_path, data, util.get_unix_time()) + friend.append_message(tm) + + if self._is_active(friend.number): + self._create_unsent_file_item(tm) + self._messages.scrollToBottom() + + return tm + # ----------------------------------------------------------------------------------------------------------------- # Private methods # ----------------------------------------------------------------------------------------------------------------- @@ -60,3 +70,6 @@ class FileTransfersMessagesService: 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) diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index 9a56aba..69b5b39 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -173,17 +173,30 @@ class TransferMessage(Message): return FileTransferItem(self, *args) -class UnsentFile(Message): +class UnsentFileMessage(Message): def __init__(self, path, data, time): super().__init__(MESSAGE_TYPE['FILE_TRANSFER'], 0, time) 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 get_status(self): return None + def _create_widget(self, *args): + return UnsentFileItem(self, *args) -class InlineImage(Message): + +class InlineImageMessage(Message): """ Inline image """ diff --git a/toxygen/ui/items_factories.py b/toxygen/ui/items_factories.py index 84bf9c3..6197066 100644 --- a/toxygen/ui/items_factories.py +++ b/toxygen/ui/items_factories.py @@ -57,8 +57,8 @@ class MessagesItemsFactory: return item - def create_unsent_file_item(self, tm, append=True): - item = UnsentFileItem(self._file_transfers_handler, self._settings, tm, self._messages.width()) + 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: @@ -69,8 +69,8 @@ class MessagesItemsFactory: return item - def create_file_transfer_item(self, tm, append=True): - item = tm.get_widget(self._file_transfers_handler, self._settings, self._messages.width(), self._messages) + 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: diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 67b09a7..c87a675 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -308,7 +308,8 @@ class MainWindow(QtWidgets.QMainWindow): def load(pos): if not pos: - self._history_loader.load_history() + friend = self._contacts_manager.get_curr_friend() + self._history_loader.load_history(friend) self.messages.verticalScrollBar().setValue(1) self.messages.verticalScrollBar().valueChanged.connect(load) self.messages.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) diff --git a/toxygen/ui/messages_widgets.py b/toxygen/ui/messages_widgets.py index ad337dd..9b5f1cb 100644 --- a/toxygen/ui/messages_widgets.py +++ b/toxygen/ui/messages_widgets.py @@ -390,8 +390,8 @@ class FileTransferItem(QtWidgets.QListWidget): class UnsentFileItem(FileTransferItem): - def __init__(self, file_transfer_handler, settings, transfer_message, width, parent=None): - super().__init__(file_transfer_handler, settings, transfer_message, width, parent) + def __init__(self, transfer_message, file_transfer_handler, settings, width, parent=None): + super().__init__(transfer_message, file_transfer_handler, settings, width, parent) self._time = time self.pb.setVisible(False) movie = QtGui.QMovie(util.join_path(util.get_images_directory(), 'spinner.gif')) From ec5bcbddec63ace9f5a735cf24b9c3002c71828b Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 18 May 2018 13:23:48 +0300 Subject: [PATCH 042/138] calls manager fixes --- toxygen/app.py | 4 +-- toxygen/av/calls_manager.py | 57 +++++++++++++++++++--------------- toxygen/contacts/profile.py | 31 ++---------------- toxygen/messenger/messages.py | 4 +-- toxygen/messenger/messenger.py | 45 ++++++++++++++++++++++++--- 5 files changed, 79 insertions(+), 62 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index d20eea7..a4c466d 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -296,7 +296,6 @@ class App: self._smiley_loader = SmileyLoader(self._settings) self._tox_dns = ToxDns(self._settings) self._ms = MainWindow(self._settings, self._tray) - self._calls_manager = CallsManager(self._tox.AV, self._settings) db = Database(self._path.replace('.tox', '.db'), self._toxes) friend_items_factory = FriendItemsFactory(self._settings, self._ms) @@ -312,8 +311,9 @@ class App: 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, profile) + self._contacts_provider, messages_items_factory, profile, self._calls_manager) file_transfers_message_service = FileTransfersMessagesService(self._contacts_manager, messages_items_factory, profile, self._ms) self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider, diff --git a/toxygen/av/calls_manager.py b/toxygen/av/calls_manager.py index 6e290e2..26a06f4 100644 --- a/toxygen/av/calls_manager.py +++ b/toxygen/av/calls_manager.py @@ -1,19 +1,37 @@ import threading import cv2 import av.calls -import utils.ui as util_ui from messenger.messages import * import time from ui import av_widgets +import common.event as event class CallsManager: - def __init__(self, toxAV, settings): + 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 + + # ----------------------------------------------------------------------------------------------------------------- + # 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 @@ -26,21 +44,17 @@ class CallsManager: def call_click(self, audio=True, video=False): """User clicked audio button in main window""" - num = self.get_active_number() - if not self.is_active_a_friend(): + 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.is_active_online(): # start call + 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() - if video: - text = util_ui.tr("Outgoing video call") - else: - text = util_ui.tr("Outgoing audio call") - self.get_curr_friend().append_message(InfoMessage(text, time.time())) - self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) - self._messages.scrollToBottom() + self._call_started_event(num, audio, video, True) + self._contacts_manager.get_curr_friend().append_message(InfoMessage(text, time.time())) + self._screen.messages.scrollToBottom() elif num in self._call: # finish or cancel call if you call with active friend self.stop_call(num, False) @@ -50,17 +64,12 @@ class CallsManager: """ if not self._settings.audio['enabled']: return - friend = self.get_friend_by_number(friend_number) - if video: - text = util_ui.tr("Incoming video call") - else: - text = util_ui.tr("Incoming audio call") + friend = self._contacts_manager.get_friend_by_number(friend_number) + self._call_started_event(friend_number, audio, video, False) friend.append_message(InfoMessage(text, time.time())) self._incoming_calls.add(friend_number) - if friend_number == self.get_active_number(): + if friend_number == self._contacts_manager.get_active_number(): self._screen.incoming_call() - self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) - self._messages.scrollToBottom() else: friend.actions = True self._call_widgets[friend_number] = av_widgets.IncomingCallWidget(friend_number, text, friend.name) @@ -83,8 +92,10 @@ class CallsManager: """ if friend_number in self._incoming_calls: self._incoming_calls.remove(friend_number) + is_declined = True text = util_ui.tr("Call declined") else: + is_declined = False text = util_ui.tr("Call finished") self._screen.call_finished() is_video = self._call.is_video_call(friend_number) @@ -98,11 +109,7 @@ class CallsManager: cv2.destroyWindow(str(friend_number)) threading.Timer(2.0, destroy_window).start() - friend = self.get_friend_by_number(friend_number) - friend.append_message(InfoMessage(text, time.time())) - if friend_number == self.get_active_number(): - self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) - self._messages.scrollToBottom() + self._call_finished_event(friend_number, is_declined) def friend_exit(self, friend_number): if friend_number in self._call: diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 6393f7e..c775268 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -1,8 +1,4 @@ -from contacts.friend import * -from file_transfers.file_transfers import * -import time from contacts import basecontact -import utils.ui as util_ui import random import threading @@ -28,7 +24,7 @@ class Profile(basecontact.BaseContact): self._contacts_provider = contacts_provider self._reset_action = reset_action self._waiting_for_reconnection = False - self._timer = threading.Timer(50, self._reconnect) + self._timer = None # ----------------------------------------------------------------------------------------------------------------- # Edit current user's data @@ -47,21 +43,14 @@ class Profile(basecontact.BaseContact): 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 - tmp = self.name super().set_name(value.encode('utf-8')) self._tox.self_set_name(self._name.encode('utf-8')) - message = util_ui.tr('User {} is now known as {}') - message = message.format(tmp, value) - for friend in self._contacts: - friend.append_message(InfoMessage(message, time.time())) - if self._active_friend + 1: - self.create_message_item(message, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) - self._messages.scrollToBottom() def set_status_message(self, value): super().set_status_message(value) @@ -74,15 +63,6 @@ class Profile(basecontact.BaseContact): return self._tox_id - # ----------------------------------------------------------------------------------------------------------------- - # Private messages - # ----------------------------------------------------------------------------------------------------------------- - - def receipt(self): - i = 0 - while i < self._messages.count() and not self._messages.itemWidget(self._messages.item(i)).mark_as_sent(): - i += 1 - # ----------------------------------------------------------------------------------------------------------------- # Reset # ----------------------------------------------------------------------------------------------------------------- @@ -101,10 +81,5 @@ class Profile(basecontact.BaseContact): if self.status is None or all(list(map(lambda x: x.status is None, contacts))) and len(contacts): self._waiting_for_reconnection = True self._restart() + self._timer = threading.Timer(50, self._reconnect) self._timer.start() - - def close(self): - for friend in filter(lambda x: type(x) is Friend, self._contacts): - self.friend_exit(friend.number) - for i in range(len(self._contacts)): - del self._contacts[0] diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index 69b5b39..62f8804 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -216,5 +216,5 @@ class InlineImageMessage(Message): class InfoMessage(TextMessage): - def __init__(self, id, message, time): - super().__init__(id, message, None, time, MESSAGE_TYPE['INFO_MESSAGE']) + def __init__(self, message, time): + super().__init__(message, None, time, MESSAGE_TYPE['INFO_MESSAGE']) diff --git a/toxygen/messenger/messenger.py b/toxygen/messenger/messenger.py index 968ca97..8f3feb2 100644 --- a/toxygen/messenger/messenger.py +++ b/toxygen/messenger/messenger.py @@ -1,14 +1,11 @@ -import utils.util as util import common.tox_save as tox_save -from wrapper.toxcore_enums_and_consts import * from messenger.messages import * -# TODO: sub profile name changed event? - class Messenger(tox_save.ToxSave): - def __init__(self, tox, plugin_loader, screen, contacts_manager, contacts_provider, items_factory, profile): + 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 @@ -16,6 +13,11 @@ class Messenger(tox_save.ToxSave): 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) # ----------------------------------------------------------------------------------------------------------------- # Private methods @@ -160,3 +162,36 @@ class Messenger(tox_save.ToxSave): def _get_friend_by_number(self, friend_number): return self._contacts_provider.get_friend_by_number(friend_number) + + 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(): + friend.append_message(InfoMessage(message, util.get_unix_time())) + if self._contacts_manager.is_active_a_friend(): + self._items_factory.create_message_item(message) + self._screen.messages.scrollToBottom() + 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") + 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._items_factory.create_message_item(message) + self._screen.messages.scrollToBottom() + + def _on_call_finished(self, friend_number, is_declined): + text = util_ui.tr("Call declined") if is_declined else util_ui.tr("Call finished") + 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._items_factory.create_message_item(message) + self._screen.messages.scrollToBottom() From 42049d6a44928b926e3b24951adddab569b767ea Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 18 May 2018 18:40:41 +0300 Subject: [PATCH 043/138] messaing fixes - receipts, faux offline messages --- toxygen/contacts/contact.py | 7 ++++--- toxygen/contacts/friend.py | 17 ----------------- toxygen/messenger/messages.py | 7 ++++++- toxygen/messenger/messenger.py | 29 ++++++++++++++--------------- toxygen/styles/dark_style.qss | 8 ++++---- toxygen/ui/messages_widgets.py | 2 -- 6 files changed, 28 insertions(+), 42 deletions(-) diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index 7faf36d..fb0267c 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -118,7 +118,7 @@ class Contact(basecontact.BaseContact): """ :return list of unsent messages """ - messages = filter(lambda x: x.author.type == MESSAGE_AUTHOR['NOT_SENT'], self._corr) + messages = filter(lambda m: m.author.type == MESSAGE_AUTHOR['NOT_SENT'], self._corr) return list(messages) def get_unsent_messages_for_saving(self): @@ -129,9 +129,10 @@ class Contact(basecontact.BaseContact): and x.author.type == MESSAGE_AUTHOR['NOT_SENT'], self._corr) return list(map(lambda x: x.get_data(), messages)) - def mark_as_sent(self): + def mark_as_sent(self, tox_message_id): try: - message = list(filter(lambda x: x.author.type == MESSAGE_AUTHOR['NOT_SENT'], self._corr))[0] + message = list(filter(lambda m: m.author.type == MESSAGE_AUTHOR['NOT_SENT'] + and m.tox_message_id == tox_message_id, self._corr))[0] message.mark_as_sent() except Exception as ex: util.log('Mark as sent ex: ' + str(ex)) diff --git a/toxygen/contacts/friend.py b/toxygen/contacts/friend.py index 63edd6a..0bc7b99 100644 --- a/toxygen/contacts/friend.py +++ b/toxygen/contacts/friend.py @@ -52,23 +52,6 @@ class Friend(contact.Contact): self._corr = list(filter(lambda m: not (type(m) is UnsentFileMessage and m.message_id == message_id), 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 # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index 62f8804..85b16f7 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -64,6 +64,8 @@ class Message: def mark_as_sent(self): self._author.author_type = MESSAGE_AUTHOR['ME'] + if self._widget is not None: + self._widget.mark_as_sent() def _create_widget(self, *args): pass @@ -111,7 +113,10 @@ class OutgoingTextMessage(TextMessage): def get_tox_message_id(self): return self._tox_message_id - tox_message_id = property(get_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): diff --git a/toxygen/messenger/messenger.py b/toxygen/messenger/messenger.py index 8f3feb2..6042db5 100644 --- a/toxygen/messenger/messenger.py +++ b/toxygen/messenger/messenger.py @@ -98,10 +98,8 @@ class Messenger(tox_save.ToxSave): messages = friend.get_unsent_messages() try: for message in messages: - tox_messages = self._split_message(message.text) - for tox_message in tox_messages: - self._tox.friend_send_message(friend_number, message.message_type, tox_message) - friend.inc_receipts() + 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)) @@ -110,7 +108,8 @@ class Messenger(tox_save.ToxSave): # ----------------------------------------------------------------------------------------------------------------- def receipt(self, friend_number, message_id): - pass # TODO: process + friend = self._get_friend_by_number(friend_number) + friend.mark_as_sent(message_id) # ----------------------------------------------------------------------------------------------------------------- # Typing notifications @@ -171,8 +170,7 @@ class Messenger(tox_save.ToxSave): for friend in self._contacts_provider.get_all_friends(): friend.append_message(InfoMessage(message, util.get_unix_time())) if self._contacts_manager.is_active_a_friend(): - self._items_factory.create_message_item(message) - self._screen.messages.scrollToBottom() + self._create_info_message_item(message) self._profile_name = new_name def _on_call_started(self, friend_number, audio, video, is_outgoing): @@ -180,18 +178,19 @@ class Messenger(tox_save.ToxSave): 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") - 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._items_factory.create_message_item(message) - self._screen.messages.scrollToBottom() + 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._items_factory.create_message_item(message) - self._screen.messages.scrollToBottom() + self._create_info_message_item(message) + + def _create_info_message_item(self, message): + self._items_factory.create_message_item(message) + self._screen.messages.scrollToBottom() diff --git a/toxygen/styles/dark_style.qss b/toxygen/styles/dark_style.qss index 0216f23..714f946 100644 --- a/toxygen/styles/dark_style.qss +++ b/toxygen/styles/dark_style.qss @@ -1207,12 +1207,12 @@ MessageItem border: none; } -MessageEdit +MessageBrowser { border: none; } -MessageEdit::focus +MessageBrowser::focus { border: none; } @@ -1222,7 +1222,7 @@ MessageItem::focus border: none; } -MessageEdit:hover +MessageBrowser:hover { border: none; } @@ -1243,7 +1243,7 @@ QPushButton:hover background-color: #1E90FF; } -MessageEdit +MessageBrowser { background-color: transparent; } diff --git a/toxygen/ui/messages_widgets.py b/toxygen/ui/messages_widgets.py index 9b5f1cb..f69cbfb 100644 --- a/toxygen/ui/messages_widgets.py +++ b/toxygen/ui/messages_widgets.py @@ -1,7 +1,5 @@ -from PyQt5 import QtWidgets, QtGui, QtCore from wrapper.toxcore_enums_and_consts import * import ui.widgets as widgets -import utils.ui as util_ui import utils.util as util import ui.menu as menu import html as h From a5753121678cdab1ba26c118420ca3b936fed770 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 18 May 2018 19:40:34 +0300 Subject: [PATCH 044/138] messages refactoring and fixes, calls fixes --- toxygen/av/calls_manager.py | 27 ++++++------- toxygen/contacts/contact.py | 39 +++++++++---------- toxygen/file_transfers/file_transfers.py | 3 +- .../file_transfers/file_transfers_handler.py | 6 +-- toxygen/messenger/messages.py | 8 ++-- toxygen/ui/av_widgets.py | 30 +++++++------- toxygen/ui/messages_widgets.py | 7 ++-- 7 files changed, 57 insertions(+), 63 deletions(-) diff --git a/toxygen/av/calls_manager.py b/toxygen/av/calls_manager.py index 26a06f4..5e84573 100644 --- a/toxygen/av/calls_manager.py +++ b/toxygen/av/calls_manager.py @@ -2,7 +2,6 @@ import threading import cv2 import av.calls from messenger.messages import * -import time from ui import av_widgets import common.event as event @@ -37,11 +36,6 @@ class CallsManager: # AV support # ----------------------------------------------------------------------------------------------------------------- - def get_call(self): - return self._call - - call = property(get_call) - def call_click(self, audio=True, video=False): """User clicked audio button in main window""" num = self._contacts_manager.get_active_number() @@ -53,8 +47,6 @@ class CallsManager: self._call(num, audio, video) self._screen.active_call() self._call_started_event(num, audio, video, True) - self._contacts_manager.get_curr_friend().append_message(InfoMessage(text, time.time())) - self._screen.messages.scrollToBottom() elif num in self._call: # finish or cancel call if you call with active friend self.stop_call(num, False) @@ -66,13 +58,13 @@ class CallsManager: return friend = self._contacts_manager.get_friend_by_number(friend_number) self._call_started_event(friend_number, audio, video, False) - friend.append_message(InfoMessage(text, time.time())) self._incoming_calls.add(friend_number) if friend_number == self._contacts_manager.get_active_number(): self._screen.incoming_call() else: friend.actions = True - self._call_widgets[friend_number] = av_widgets.IncomingCallWidget(friend_number, text, friend.name) + 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() @@ -93,16 +85,14 @@ class CallsManager: if friend_number in self._incoming_calls: self._incoming_calls.remove(friend_number) is_declined = True - text = util_ui.tr("Call declined") else: is_declined = False - text = util_ui.tr("Call finished") 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 hasattr(self, '_call_widget'): - self._call_widget[friend_number].close() - del self._call_widget[friend_number] + 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: @@ -114,3 +104,10 @@ class CallsManager: 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) diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index fb0267c..a142c61 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -74,7 +74,7 @@ class Contact(basecontact.BaseContact): Get data to save in db :return: list of unsaved messages or [] """ - messages = list(filter(lambda x: x.get_type() in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']), self._corr)) + messages = list(filter(lambda x: x.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']), self._corr)) return messages[-self._unsaved_messages:] if self._unsaved_messages else [] def get_corr(self): @@ -85,12 +85,12 @@ class Contact(basecontact.BaseContact): :param message: text or file transfer message """ self._corr.append(message) - if message.get_type() in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']): + if message.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']): self._unsaved_messages += 1 def get_last_message_text(self): - messages = list(filter(lambda x: x.get_type() in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']) - and x.get_owner() != MESSAGE_AUTHOR['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: return messages[-1].text else: @@ -125,9 +125,9 @@ class Contact(basecontact.BaseContact): """ :return list of unsent messages for saving """ - messages = filter(lambda x: x.get_type() in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']) + messages = filter(lambda x: x.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']) and x.author.type == MESSAGE_AUTHOR['NOT_SENT'], self._corr) - return list(map(lambda x: x.get_data(), messages)) + return list(messages) def mark_as_sent(self, tox_message_id): try: @@ -144,7 +144,7 @@ class Contact(basecontact.BaseContact): def delete_message(self, message_id): elem = list(filter(lambda x: type(x) in (TextMessage, GroupChatMessage) and x.message_id == message_id, self._corr))[0] - tmp = list(filter(lambda x: x.get_type() in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']), self._corr)) + tmp = list(filter(lambda x: x.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']), self._corr)) if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages: self._unsaved_messages -= 1 self._corr.remove(elem) @@ -156,13 +156,13 @@ class Contact(basecontact.BaseContact): Delete old messages (reduces RAM usage if messages saving is not enabled) """ def save_message(x): - if x.get_type() == 2 and (x.get_status() >= 2 or x.get_status() is None): # FIXME MAGIC NUMBERS + if x.type == MESSAGE_TYPE['FILE_TRANSFER'] and (x.state not in ACTIVE_FILE_TRANSFERS): return True - return x.get_owner() == MESSAGE_AUTHOR['NOT_SENT'] + return x.author.type == MESSAGE_AUTHOR['NOT_SENT'] old = filter(save_message, self._corr[:-SAVE_MESSAGES]) self._corr = list(old) + self._corr[-SAVE_MESSAGES:] - text_messages = filter(lambda x: x.get_type() in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']), 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._search_index = 0 @@ -175,13 +175,14 @@ class Contact(basecontact.BaseContact): self._search_index = 0 # don't delete data about active file transfer if not save_unsent: - self._corr = list(filter(lambda x: x.get_type() == 2 and - x.get_status() in ft.ACTIVE_FILE_TRANSFERS, self._corr)) + self._corr = list(filter(lambda m: m.type == MESSAGE_TYPE['FILE_TRANSFER'] and + m.state in ft.ACTIVE_FILE_TRANSFERS, self._corr)) self._unsaved_messages = 0 else: - self._corr = list(filter(lambda x: (x.get_type() == 2 and x.get_status() in ft.ACTIVE_FILE_TRANSFERS) - or (x.get_type() in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']) - and x.get_owner() == MESSAGE_AUTHOR['NOT_SENT']), + self._corr = list(filter(lambda m: (m.type == MESSAGE_TYPE['FILE_TRANSFER'] + 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._unsaved_messages = len(self.get_unsent_messages()) @@ -197,7 +198,7 @@ class Contact(basecontact.BaseContact): while True: l = len(self._corr) 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 message = self._corr[i].text if re.search(self._search_string, message, re.IGNORECASE) is not None: @@ -212,7 +213,7 @@ class Contact(basecontact.BaseContact): if not self._search_index: return None for i in range(self._search_index + 1, 0): - if self._corr[i].get_type() > 1: + if self._corr[i].get_type() > (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']): continue message = self._corr[i].text if re.search(self._search_string, message, re.IGNORECASE) is not None: @@ -259,10 +260,6 @@ class Contact(basecontact.BaseContact): visibility = property(get_visibility, set_visibility) - def set_widget(self, widget): - self._widget = widget - self.init_widget() - # ----------------------------------------------------------------------------------------------------------------- # Unread messages and other actions from friend # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py index 166cc7f..61a379f 100644 --- a/toxygen/file_transfers/file_transfers.py +++ b/toxygen/file_transfers/file_transfers.py @@ -13,7 +13,8 @@ FILE_TRANSFER_STATE = { 'FINISHED': 3, 'PAUSED_BY_FRIEND': 4, 'INCOMING_NOT_STARTED': 5, - 'OUTGOING_NOT_STARTED': 6 + 'OUTGOING_NOT_STARTED': 6, + 'UNSENT': 7 } ACTIVE_FILE_TRANSFERS = (0, 1, 4, 5, 6) diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index c46ee58..1f5c9d0 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -41,15 +41,15 @@ class FileTransfersHandler: file_id = self._tox.file_get_file_id(friend_number, file_number) accepted = True if file_id in self._paused_file_transfers: - data = self._paused_file_transfers[file_id] - pos = data[-1] if os.path.exists(data[0]) else 0 + (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(data[0], friend_number, file_number, size, False, pos) + 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) diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index 85b16f7..c356485 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -189,14 +189,16 @@ class UnsentFileMessage(Message): data = property(get_data) + def get_state(self): + return FILE_TRANSFER_STATE['UNSENT'] + + state = property(get_state) + def get_path(self): return self._path path = property(get_path) - def get_status(self): - return None - def _create_widget(self, *args): return UnsentFileItem(self, *args) diff --git a/toxygen/ui/av_widgets.py b/toxygen/ui/av_widgets.py index f9eb6ed..e5773a8 100644 --- a/toxygen/ui/av_widgets.py +++ b/toxygen/ui/av_widgets.py @@ -1,17 +1,16 @@ from PyQt5 import QtCore, QtGui, QtWidgets from ui import widgets -from contacts import profile -import utils +import utils.util as util import pyaudio import wave -from user_data import settings -from utils.util import * class IncomingCallWidget(widgets.CenteredWidget): - def __init__(self, friend_number, text, name): + def __init__(self, settings, calls_manager, friend_number, text, name): super().__init__() + self._settings = settings + self._calls_manager = calls_manager self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowStaysOnTopHint) self.resize(QtCore.QSize(500, 270)) self.avatar_label = QtWidgets.QLabel(self) @@ -21,7 +20,7 @@ class IncomingCallWidget(widgets.CenteredWidget): self.name.setGeometry(QtCore.QRect(90, 20, 300, 25)) self._friend_number = friend_number font = QtGui.QFont() - font.setFamily(settings.Settings.get_instance()['font']) + font.setFamily(settings['font']) font.setPointSize(16) font.setBold(True) self.name.setFont(font) @@ -34,13 +33,13 @@ class IncomingCallWidget(widgets.CenteredWidget): self.accept_video.setGeometry(QtCore.QRect(170, 100, 150, 150)) self.decline = QtWidgets.QPushButton(self) self.decline.setGeometry(QtCore.QRect(320, 100, 150, 150)) - pixmap = QtGui.QPixmap(utils.curr_directory() + '/images/accept_audio.png') + pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'accept_audio.png')) icon = QtGui.QIcon(pixmap) self.accept_audio.setIcon(icon) - pixmap = QtGui.QPixmap(utils.curr_directory() + '/images/accept_video.png') + pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'accept_video.png')) icon = QtGui.QIcon(pixmap) self.accept_video.setIcon(icon) - pixmap = QtGui.QPixmap(utils.curr_directory() + '/images/decline_call.png') + pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'decline_call.png')) icon = QtGui.QIcon(pixmap) self.decline.setIcon(icon) self.accept_audio.setIconSize(QtCore.QSize(150, 150)) @@ -90,11 +89,11 @@ class IncomingCallWidget(widgets.CenteredWidget): self.stream.close() 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.close() - if settings.Settings.get_instance()['calls_sound']: + if self._settings['calls_sound']: self.thread = SoundPlay() self.thread.start() else: @@ -110,24 +109,21 @@ class IncomingCallWidget(widgets.CenteredWidget): if self._processing: return self._processing = True - pr = profile.Profile.get_instance() - pr.accept_call(self._friend_number, True, False) + self._calls_manager.accept_call(self._friend_number, True, False) self.stop() def accept_call_with_video(self): if self._processing: return self._processing = True - pr = profile.Profile.get_instance() - pr.accept_call(self._friend_number, True, True) + self._calls_manager.accept_call(self._friend_number, True, True) self.stop() def decline_call(self): if self._processing: return self._processing = True - pr = profile.Profile.get_instance() - pr.stop_call(self._friend_number, False) + self._calls_manager.stop_call(self._friend_number, False) self.stop() def set_pixmap(self, pixmap): diff --git a/toxygen/ui/messages_widgets.py b/toxygen/ui/messages_widgets.py index f69cbfb..c676783 100644 --- a/toxygen/ui/messages_widgets.py +++ b/toxygen/ui/messages_widgets.py @@ -134,8 +134,9 @@ class MessageItem(QtWidgets.QWidget): font.setFamily(settings['font']) font.setPointSize(11) font.setBold(True) - self.name.setFont(font) - self.name.setText(text_message.author.name) + if text_message.author is not None: + self.name.setFont(font) + self.name.setText(text_message.author.name) self.time = QtWidgets.QLabel(self) self.time.setGeometry(QtCore.QRect(parent.width() - 60, 0, 50, 25)) @@ -143,7 +144,7 @@ class MessageItem(QtWidgets.QWidget): font.setBold(False) self.time.setFont(font) self._time = text_message.time - if text_message.author.type == MESSAGE_AUTHOR['NOT_SENT']: + if text_message.author and text_message.author.type == MESSAGE_AUTHOR['NOT_SENT']: movie = QtGui.QMovie(util.join_path(util.get_images_directory(), 'spinner.gif')) self.time.setMovie(movie) movie.start() From 88786b03987fb6c320b365b89692889b00c8b73a Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 18 May 2018 21:07:59 +0300 Subject: [PATCH 045/138] setup.py fixes --- setup.py | 17 ++++++++++++++--- toxygen/contacts/friend.py | 6 +++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 8457ef5..fb80363 100644 --- a/setup.py +++ b/setup.py @@ -4,6 +4,8 @@ from platform import system from subprocess import call import main import sys +import os +from utils.util import curr_directory, join_path version = main.__version__ + '.0' @@ -35,6 +37,15 @@ else: MODULES.append('pydenticon') +def get_packages(): + directory = join_path(curr_directory(__file__), 'toxygen') + for root, dirs, files in os.walk(directory): + packages = map(lambda d: 'toxygen.' + d, dirs) + packages = ['toxygen'] + list(packages) + + return packages + + class InstallScript(install): """This class configures Toxygen after installation""" @@ -66,7 +77,7 @@ setup(name='Toxygen', author='Ingvar', maintainer='Ingvar', license='GPL3', - packages=['toxygen', 'toxygen.plugins', 'toxygen.styles'], + packages=get_packages(), install_requires=MODULES, include_package_data=True, classifiers=[ @@ -75,8 +86,8 @@ setup(name='Toxygen', 'Programming Language :: Python :: 3.6', ], entry_points={ - 'console_scripts': ['toxygen=toxygen.main:main'], + 'console_scripts': ['toxygen=toxygen.main:main'] }, cmdclass={ - 'install': InstallScript, + 'install': InstallScript }) diff --git a/toxygen/contacts/friend.py b/toxygen/contacts/friend.py index 0bc7b99..9103463 100644 --- a/toxygen/contacts/friend.py +++ b/toxygen/contacts/friend.py @@ -38,13 +38,13 @@ class Friend(contact.Contact): def clear_unsent_files(self): self._corr = list(filter(lambda x: type(x) is not UnsentFileMessage, self._corr)) - def remove_invalid_unsent_files(self): # TODO: fix + def remove_invalid_unsent_files(self): def is_valid(message): if type(message) is not UnsentFileMessage: return True - if message.get_data()[1] is not None: + if message.data is not None: return True - return os.path.exists(message.get_data()[0]) + return os.path.exists(message.path) self._corr = list(filter(is_valid, self._corr)) From acf75a68188a29a81f67a3d7239bd032cf16bcb0 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 19 May 2018 00:07:49 +0300 Subject: [PATCH 046/138] groups initial commit --- toxygen/app.py | 5 ++++- toxygen/contacts/contact_provider.py | 30 ++++++++++++++++++-------- toxygen/contacts/contacts_manager.py | 5 +++++ toxygen/contacts/group_chat.py | 4 ++-- toxygen/contacts/group_factory.py | 6 ++++++ toxygen/groups/__init__.py | 0 toxygen/groups/groups_service.py | 32 ++++++++++++++++++++++++++++ toxygen/ui/main_screen.py | 16 +++++++++----- 8 files changed, 81 insertions(+), 17 deletions(-) create mode 100644 toxygen/contacts/group_factory.py create mode 100644 toxygen/groups/__init__.py create mode 100644 toxygen/groups/groups_service.py diff --git a/toxygen/app.py b/toxygen/app.py index a4c466d..311ebe9 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -19,6 +19,7 @@ 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 @@ -40,6 +41,7 @@ class App: 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 = self._tox_dns = None + self._group_factory = None if uri is not None and uri.startswith('tox:'): self._uri = uri[4:] self._path = path_to_profile @@ -300,7 +302,8 @@ class App: friend_items_factory = FriendItemsFactory(self._settings, self._ms) self._friend_factory = FriendFactory(self._profile_manager, self._settings, self._tox, db, friend_items_factory) - self._contacts_provider = ContactProvider(self._tox, self._friend_factory) + self._group_factory = GroupFactory() + self._contacts_provider = ContactProvider(self._tox, self._friend_factory, self._group_factory) profile = Profile(self._profile_manager, self._tox, self._ms, self._contacts_provider, self._reset) self._plugin_loader = PluginLoader(self._tox, self._toxes, profile, self._settings) history = None diff --git a/toxygen/contacts/contact_provider.py b/toxygen/contacts/contact_provider.py index 1a08589..6d4e6b8 100644 --- a/toxygen/contacts/contact_provider.py +++ b/toxygen/contacts/contact_provider.py @@ -3,9 +3,10 @@ import common.tox_save as tox_save class ContactProvider(tox_save.ToxSave): - def __init__(self, tox, friend_factory): + def __init__(self, tox, friend_factory, group_factory): super().__init__(tox) self._friend_factory = friend_factory + self._group_factory = group_factory self._cache = {} # key - contact's public key, value - contact instance # ----------------------------------------------------------------------------------------------------------------- @@ -37,13 +38,24 @@ class ContactProvider(tox_save.ToxSave): # ----------------------------------------------------------------------------------------------------------------- def get_all_groups(self): - return [] + group_numbers = range(self._tox.group_get_number_groups) + groups = map(lambda n: self.get_group_by_number(n), group_numbers) - def get_group_by_number(self): - pass + return list(groups) - def get_group_by_public_key(self): - pass + 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 # ----------------------------------------------------------------------------------------------------------------- # All contacts @@ -59,9 +71,9 @@ class ContactProvider(tox_save.ToxSave): def clear_cache(self): self._cache.clear() - def remove_friend_from_cache(self, friend_public_key): - if friend_public_key in self._cache: - del self._cache[friend_public_key] + def remove_contact_from_cache(self, contact_public_key): + if contact_public_key in self._cache: + del self._cache[contact_public_key] # ----------------------------------------------------------------------------------------------------------------- # Private methods diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index b826a37..efca671 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -298,6 +298,11 @@ class ContactsManager: self._contacts.append(friend) friend.reset_avatar() + def add_group(self, group_number): + group = self._contact_provider.get_group_by_numner(group_number) + self._contacts.append(group) + group.reset_avatar() + def block_user(self, tox_id): """ Block user with specified tox id (or public key) - delete from friends list and ignore friend requests diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index 5121be7..c069ca4 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -24,8 +24,8 @@ class GroupChat(contact.Contact): super().set_name(title) @staticmethod - def get_default_avatar_name(): - return 'group.png' + def _get_default_avatar_path(): + return util.join_path(util.get_images_directory(), 'group.png') def remove_invalid_unsent_files(self): pass diff --git a/toxygen/contacts/group_factory.py b/toxygen/contacts/group_factory.py new file mode 100644 index 0000000..953c483 --- /dev/null +++ b/toxygen/contacts/group_factory.py @@ -0,0 +1,6 @@ + + +class GroupFactory: + + def create_group_by_public_key(self, public_key): + pass diff --git a/toxygen/groups/__init__.py b/toxygen/groups/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py new file mode 100644 index 0000000..febdb9e --- /dev/null +++ b/toxygen/groups/groups_service.py @@ -0,0 +1,32 @@ +import common.tox_save as tox_save + + +class GroupsService(tox_save.ToxSave): + + def __init__(self, tox, contacts_manager, contacts_provider): + super().__init__(tox) + self._contacts_manager = contacts_manager + self._contacts_provider = contacts_provider + + # ----------------------------------------------------------------------------------------------------------------- + # Groups creation + # ----------------------------------------------------------------------------------------------------------------- + + def create_new_gc(self, name, privacy_state): + group_number = self._tox.group_new(privacy_state, name) + self._add_new_group_by_number(group_number) + + def join_gc_by_id(self, chat_id, password): + group_number = self._tox.group_join(chat_id, password) + self._add_new_group_by_number(group_number) + + def join_gc_via_invite(self, invite_data, friend_number, password): + group_number = self._tox.group_invite_accept(invite_data, friend_number, password) + self._add_new_group_by_number(group_number) + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _add_new_group_by_number(self, group_number): + self._contacts_manager.add_group(group_number) diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index c87a675..85c2d41 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -1,4 +1,3 @@ -from contacts.profile import * from ui.contact_items import * from ui.widgets import MultilineEdit, ComboBox from ui.main_screen_widgets import * @@ -50,6 +49,7 @@ class MainWindow(QtWidgets.QMainWindow): 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) @@ -58,7 +58,6 @@ class MainWindow(QtWidgets.QMainWindow): 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") @@ -81,10 +80,14 @@ class MainWindow(QtWidgets.QMainWindow): 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.menuProfile.addAction(self.actionAdd_friend) - self.menuProfile.addAction(self.actionAdd_gc) self.menuProfile.addAction(self.actionSettings) self.menuProfile.addAction(self.lockApp) + self.menuGC.addAction(self.createGC) + self.menuGC.addAction(self.joinGC) self.menuSettings.addAction(self.actionPrivacy_settings) self.menuSettings.addAction(self.actionInterface_settings) self.menuSettings.addAction(self.actionNotifications) @@ -98,6 +101,7 @@ class MainWindow(QtWidgets.QMainWindow): 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()) @@ -105,7 +109,7 @@ class MainWindow(QtWidgets.QMainWindow): 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.actionAdd_gc.triggered.connect(self.create_gc) + self.createGC.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) @@ -130,12 +134,14 @@ class MainWindow(QtWidgets.QMainWindow): 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.actionAdd_gc.setText(util_ui.tr("Create group chat")) + self.createGC.setText(util_ui.tr("Create group chat")) + self.joinGC.setText(util_ui.tr("Join group chat")) self.actionprofilesettings.setText(util_ui.tr("Profile")) self.actionPrivacy_settings.setText(util_ui.tr("Privacy")) self.actionInterface_settings.setText(util_ui.tr("Interface")) From dfe7601dc102a7bec7b7f730238765f1181b06ef Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 19 May 2018 16:00:28 +0300 Subject: [PATCH 047/138] groups - service, chat, callbacks --- toxygen/app.py | 8 ++- toxygen/contacts/contact_provider.py | 2 +- toxygen/contacts/contacts_manager.py | 8 ++- toxygen/contacts/group_chat.py | 36 ++++++----- toxygen/contacts/group_peer_contact.py | 13 ++++ toxygen/groups/group_peer.py | 10 +++ toxygen/messenger/messages.py | 2 +- toxygen/messenger/messenger.py | 41 ++++++++++++- toxygen/middleware/callbacks.py | 66 +++++++++++++++----- toxygen/ui/create_profile_screen.py | 6 +- toxygen/ui/groups_widgets.py | 72 ++++++++++++++++++++++ toxygen/ui/main_screen.py | 8 ++- toxygen/ui/views/create_group_screen.ui | 81 +++++++++++++++++++++++++ toxygen/ui/views/join_group_screen.ui | 81 +++++++++++++++++++++++++ toxygen/ui/widgets_factory.py | 10 ++- 15 files changed, 400 insertions(+), 44 deletions(-) create mode 100644 toxygen/contacts/group_peer_contact.py create mode 100644 toxygen/groups/group_peer.py create mode 100644 toxygen/ui/groups_widgets.py create mode 100644 toxygen/ui/views/create_group_screen.ui create mode 100644 toxygen/ui/views/join_group_screen.ui diff --git a/toxygen/app.py b/toxygen/app.py index 311ebe9..b76a047 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -30,6 +30,7 @@ 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 import styles.style # TODO: dynamic loading @@ -41,7 +42,7 @@ class App: 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 = self._tox_dns = None - self._group_factory = None + self._group_factory = self._groups_service = None if uri is not None and uri.startswith('tox:'): self._uri = uri[4:] self._path = path_to_profile @@ -322,9 +323,10 @@ class App: self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider, file_transfers_message_service, profile) messages_items_factory.set_file_transfers_handler(self._file_transfer_handler) + self._groups_service = GroupsService(self._tox, self._contacts_manager, self._contacts_provider) widgets_factory = WidgetsFactory(self._settings, profile, self._profile_manager, self._contacts_manager, self._file_transfer_handler, self._smiley_loader, self._plugin_loader, - self._toxes, self._version) + self._toxes, self._version, self._groups_service) self._tray = tray.init_tray(profile, self._settings, self._ms) self._ms.set_dependencies(widgets_factory, self._tray, self._contacts_manager, self._messenger, profile, self._plugin_loader, self._file_transfer_handler, history, self._calls_manager) @@ -335,7 +337,7 @@ class App: # callbacks initialization callbacks.init_callbacks(self._tox, profile, self._settings, self._plugin_loader, self._contacts_manager, self._calls_manager, self._file_transfer_handler, self._ms, self._tray, - self._messenger) + self._messenger, self._groups_service, self._contacts_provider) def _try_to_update(self): updating = updater.start_update_if_needed(self._version, self._settings) diff --git a/toxygen/contacts/contact_provider.py b/toxygen/contacts/contact_provider.py index 6d4e6b8..6f13596 100644 --- a/toxygen/contacts/contact_provider.py +++ b/toxygen/contacts/contact_provider.py @@ -38,7 +38,7 @@ class ContactProvider(tox_save.ToxSave): # ----------------------------------------------------------------------------------------------------------------- def get_all_groups(self): - group_numbers = range(self._tox.group_get_number_groups) + group_numbers = range(self._tox.group_get_number_groups()) groups = map(lambda n: self.get_group_by_number(n), group_numbers) return list(groups) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index efca671..7dad02f 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -43,6 +43,12 @@ class ContactsManager: 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 == friend_number + # ----------------------------------------------------------------------------------------------------------------- # Work with active friend # ----------------------------------------------------------------------------------------------------------------- @@ -299,7 +305,7 @@ class ContactsManager: friend.reset_avatar() def add_group(self, group_number): - group = self._contact_provider.get_group_by_numner(group_number) + group = self._contact_provider.get_group_by_number(group_number) self._contacts.append(group) group.reset_avatar() diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index c069ca4..d37bbe7 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -1,31 +1,21 @@ from contacts import contact import utils.util as util -from PyQt5 import QtGui, QtCore +from wrapper.toxcore_enums_and_consts import * from wrapper import toxcore_enums_and_consts as constants -# TODO: ngc - class GroupChat(contact.Contact): def __init__(self, profile_manager, name, status_message, widget, tox, group_number): super().__init__(None, group_number, profile_manager, name, status_message, widget, None) self._tox = tox self.set_status(constants.TOX_USER_STATUS['NONE']) + self._peers = [] + self._add_self_to_gc() - 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) - - @staticmethod - def _get_default_avatar_path(): - return util.join_path(util.get_images_directory(), 'group.png') + def set_topic(self, topic): + self._tox.group_set_topic(self._number, topic.encode('utf-8')) + super().set_status_message(topic) def remove_invalid_unsent_files(self): pass @@ -45,3 +35,17 @@ class GroupChat(contact.Contact): def get_peer_name(self, peer_number): return self._tox.group_peername(self._number, peer_number) + + def get_self_name(self): + return self._peers[0].name + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + @staticmethod + def _get_default_avatar_path(): + return util.join_path(util.get_images_directory(), 'group.png') + + def _add_self_to_gc(self): + pass diff --git a/toxygen/contacts/group_peer_contact.py b/toxygen/contacts/group_peer_contact.py new file mode 100644 index 0000000..1c51f67 --- /dev/null +++ b/toxygen/contacts/group_peer_contact.py @@ -0,0 +1,13 @@ +import contacts.contact + + +class GroupPeerContact(contacts.contact.Contact): + + def __init__(self, profile_manager, message_getter, peer_number, name, status_messsage, widget, tox_id, group_pk): + super().__init__(profile_manager, message_getter, peer_number, name, status_messsage, widget, tox_id) + self._group_pk = group_pk + + def get_group_pk(self): + return self._group_pk + + group_pk = property(get_group_pk) diff --git a/toxygen/groups/group_peer.py b/toxygen/groups/group_peer.py new file mode 100644 index 0000000..f91decc --- /dev/null +++ b/toxygen/groups/group_peer.py @@ -0,0 +1,10 @@ + + +class GroupChatPeer: + + def __init__(self, peer_number, name, status, role, public_key): + self.peer_number = peer_number + self.name = name + self.status = status + self.role = role + self.public_key = public_key diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index c356485..91a1a04 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -106,7 +106,7 @@ class TextMessage(Message): class OutgoingTextMessage(TextMessage): - def __init__(self, message, owner, time, message_type, tox_message_id): + 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 diff --git a/toxygen/messenger/messenger.py b/toxygen/messenger/messenger.py index 6042db5..0a321f8 100644 --- a/toxygen/messenger/messenger.py +++ b/toxygen/messenger/messenger.py @@ -28,7 +28,7 @@ class Messenger(tox_save.ToxSave): self._items_factory.create_message_item(text_message) # ----------------------------------------------------------------------------------------------------------------- - # Messaging + # Messaging - friends # ----------------------------------------------------------------------------------------------------------------- def new_message(self, friend_number, message_type, message): @@ -53,7 +53,11 @@ class Messenger(tox_save.ToxSave): self._contacts_manager.update_filtration() def send_message(self): - self.send_message_to_friend(self._screen.messageEdit.toPlainText()) + text = self._screen.messageEdit.toPlainText() + if self._contacts_manager.is_active_a_friend(): + self.send_message_to_friend(text) + else: + self.send_message_to_group(text) def send_message_to_friend(self, text, friend_number=None): """ @@ -78,7 +82,6 @@ class Messenger(tox_save.ToxSave): for message in messages: if friend.status is not None: message_id = self._tox.friend_send_message(friend_number, message_type, message) - friend.inc_receipts() else: message_id = 0 message_author = MessageAuthor(self._profile.name, MESSAGE_AUTHOR['NOT_SENT']) @@ -103,6 +106,35 @@ class Messenger(tox_save.ToxSave): except Exception as ex: util.log('Sending pending messages failed with ' + str(ex)) + # ----------------------------------------------------------------------------------------------------------------- + # Messaging - groups + # ----------------------------------------------------------------------------------------------------------------- + + def send_message_to_group(self, text, group_number=None): + if group_number is None: + group_number = self._contacts_manager.get_active_number() + if text.startswith('/plugin '): + self._plugin_loader.command(text[8:]) + self._screen.messageEdit.clear() + elif text and group_number >= 0: + if text.startswith('/me '): + message_type = TOX_MESSAGE_TYPE['ACTION'] + text = text[4:] + else: + message_type = TOX_MESSAGE_TYPE['NORMAL'] + 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 self._contacts_manager.is_group_active(group_number): + self._create_message_item(message) + self._screen.messageEdit.clear() + self._screen.messages.scrollToBottom() + # ----------------------------------------------------------------------------------------------------------------- # Message receipts # ----------------------------------------------------------------------------------------------------------------- @@ -162,6 +194,9 @@ class Messenger(tox_save.ToxSave): 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 _on_profile_name_changed(self, new_name): if self._profile_name == new_name: return diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index bd122d6..40b3284 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -13,7 +13,7 @@ from notifications.tray import tray_notification from notifications.sound import * import threading -# TODO: gc callbacks and refactoring. Use contact provider instead of manager +# TODO: refactoring. Use contact provider instead of manager # ----------------------------------------------------------------------------------------------------------------- # Callbacks - current user @@ -360,18 +360,49 @@ def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, u # ----------------------------------------------------------------------------------------------------------------- -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']) - icon = os.path.join(util.get_images_directory(), 'icon_new_messages.png') - invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon)) +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 not window.isActiveWindow(): + bl = settings['notify_all_gc'] or profile.name in message + name = tox.group_peer_get_name(group_number, peer_id) + if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and (not settings.locked) and bl: + invoke_in_main_thread(tray_notification, name, message, tray, window) + if settings['sound_notifications'] and bl and profile.status != TOX_USER_STATUS['BUSY']: + sound_notification(SOUND_NOTIFICATION['MESSAGE']) + icon = os.path.join(util.get_images_directory(), 'icon_new_messages.png') + invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon)) + return wrapped + + +def group_invite(groups_service): + def wrapped(tox, friend_number, invite_data, length, user_data): + invoke_in_main_thread(groups_service.process_group_invite, + friend_number, + bytes(invite_data[:length])) + + return wrapped + + +def group_self_join(contacts_provider): + 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']) + + return wrapped + + +def group_peer_join(contacts_provider): + def wrapped(tox, group_number, peer_id, user_data): + gc = contacts_provider.get_group_by_number(group_number) + gc.add_peer(peer_id) + + return wrapped + # ----------------------------------------------------------------------------------------------------------------- # Callbacks - initialization @@ -379,7 +410,8 @@ def show_gc_notification(window, tray, message, group_number, peer_number): def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, - calls_manager, file_transfer_handler, main_window, tray, messenger): + calls_manager, file_transfer_handler, main_window, tray, messenger, groups_service, + contacts_provider): """ Initialization of all callbacks. :param tox: Tox instance @@ -425,3 +457,9 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, # custom packets tox.callback_friend_lossless_packet(lossless_packet(plugin_loader), 0) tox.callback_friend_lossy_packet(lossy_packet(plugin_loader), 0) + + # gc callbacks + tox.callback_group_message(group_message(main_window, tray, tox, messenger, settings, profile), 0) + tox.callback_group_invite(group_invite(groups_service), 0) + tox.callback_group_self_join(group_self_join(contacts_provider), 0) + tox.callback_group_peer_join(group_peer_join(contacts_provider), 0) diff --git a/toxygen/ui/create_profile_screen.py b/toxygen/ui/create_profile_screen.py index 40c9091..08b85cf 100644 --- a/toxygen/ui/create_profile_screen.py +++ b/toxygen/ui/create_profile_screen.py @@ -26,9 +26,9 @@ class CreateProfileScreen(CenteredWidget, DialogWithResult): def __init__(self): CenteredWidget.__init__(self) DialogWithResult.__init__(self) - uic.loadUi(util.get_views_path('create_profile_screen')) + uic.loadUi(util.get_views_path('create_profile_screen'), self) self.center() - self.createProfile.clicked.connect(self.create_profile) + self.createProfile.clicked.connect(self._create_profile) def retranslateUi(self): self.defaultFolder.setPlaceholderText(util_ui.tr('Save in default folder')) @@ -36,7 +36,7 @@ class CreateProfileScreen(CenteredWidget, DialogWithResult): self.createProfile.setText(util_ui.tr('Create profile')) self.passwordLabel.setText(util_ui.tr('Password:')) - def create_profile(self): + def _create_profile(self): if self.password.text() != self.confirmPassword.text(): return # TODO : error result = CreateProfileScreenResult(self.defaultFolder.isChecked(), self.password.text()) diff --git a/toxygen/ui/groups_widgets.py b/toxygen/ui/groups_widgets.py new file mode 100644 index 0000000..4dc39d5 --- /dev/null +++ b/toxygen/ui/groups_widgets.py @@ -0,0 +1,72 @@ +from PyQt5 import uic +import utils.util as util +from ui.widgets import * +from wrapper.toxcore_enums_and_consts import * + + +class CreateGroupScreen(CenteredWidget): + + def __init__(self, groups_service): + super().__init__() + self._groups_service = groups_service + uic.loadUi(util.get_views_path('create_group_screen'), self) + self.center() + self.retranslateUi() + self.addGroupButton.clicked.connect(self._create_group) + self.groupNameLineEdit.textChanged.connect(self._group_name_changed) + + def retranslateUi(self): + 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')) + + def _create_group(self): + name = self.groupNameLineEdit.text() + privacy_state = self.groupTypeComboBox.currentIndex() + self._groups_service.create_new_gc(name, privacy_state) + self.close() + + def _group_name_changed(self): + name = self.groupNameLineEdit.text() + self.addGroupButton.setEnabled(bool(name.strip())) + + +class JoinGroupScreen(CenteredWidget): + + def __init__(self, groups_service): + super().__init__() + self._groups_service = groups_service + uic.loadUi(util.get_views_path('join_group_screen'), self) + self.center() + self.retranslateUi() + self.chatIdLineEdit.textChanged.connect(self._chat_id_changed) + self.joinGroupButton.clicked.connect(self._join_group) + + def retranslateUi(self): + 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): + chat_id = self._get_chat_id() + self.joinGroupButton.setEnabled(len(chat_id) == TOX_GROUP_CHAT_ID_SIZE * 2) + + def _join_group(self): + chat_id = self._get_chat_id() + password = self.passwordLineEdit.text() + self._groups_service.join_gc_by_id(chat_id, password) + 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 diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 85c2d41..fd404b2 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -110,6 +110,7 @@ class MainWindow(QtWidgets.QMainWindow): 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) @@ -459,7 +460,12 @@ class MainWindow(QtWidgets.QMainWindow): self._modal_window.show() def create_gc(self): - self.profile.create_group_chat() + 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() diff --git a/toxygen/ui/views/create_group_screen.ui b/toxygen/ui/views/create_group_screen.ui new file mode 100644 index 0000000..08a27ff --- /dev/null +++ b/toxygen/ui/views/create_group_screen.ui @@ -0,0 +1,81 @@ + + + Form + + + + 0 + 0 + 639 + 199 + + + + Form + + + + false + + + + 180 + 150 + 271 + 41 + + + + + + + + + + 140 + 40 + 471 + 31 + + + + + + + 140 + 100 + 471 + 41 + + + + + + + 10 + 40 + 121 + 31 + + + + TextLabel + + + + + + 10 + 100 + 121 + 31 + + + + TextLabel + + + + + + diff --git a/toxygen/ui/views/join_group_screen.ui b/toxygen/ui/views/join_group_screen.ui new file mode 100644 index 0000000..66b0420 --- /dev/null +++ b/toxygen/ui/views/join_group_screen.ui @@ -0,0 +1,81 @@ + + + Form + + + + 0 + 0 + 739 + 212 + + + + Form + + + + + 30 + 40 + 67 + 17 + + + + TextLabel + + + + + + 30 + 100 + 67 + 17 + + + + TextLabel + + + + + false + + + + 258 + 150 + 241 + 51 + + + + + + + + + + 190 + 20 + 431 + 41 + + + + + + + 190 + 90 + 431 + 41 + + + + + + + diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py index b42e342..66aca2c 100644 --- a/toxygen/ui/widgets_factory.py +++ b/toxygen/ui/widgets_factory.py @@ -1,11 +1,12 @@ from ui.main_screen_widgets import * from ui.menu import * +from ui.groups_widgets import * class WidgetsFactory: def __init__(self, settings, profile, profile_manager, contacts_manager, file_transfer_handler, smiley_loader, - plugin_loader, toxes, version): + plugin_loader, toxes, version, groups_service): self._settings = settings self._profile = profile self._profile_manager = profile_manager @@ -15,6 +16,7 @@ class WidgetsFactory: self._plugin_loader = plugin_loader self._toxes = toxes self._version = version + self._groups_service = groups_service def create_screenshot_window(self, *args): return ScreenShotWindow(self._file_transfer_handler, self._contacts_manager, *args) @@ -63,3 +65,9 @@ class WidgetsFactory: def create_sticker_window(self): return StickerWindow(self._file_transfer_handler, self._contacts_manager) + + def create_group_screen_window(self): + return CreateGroupScreen(self._groups_service) + + def create_join_group_screen_window(self): + return JoinGroupScreen(self._groups_service) From eed31bf61be6f6d630e33aaaaf82319c3ff36c13 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 19 May 2018 16:07:16 +0300 Subject: [PATCH 048/138] wrapper update - ngc --- toxygen/wrapper/tox.py | 144 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 134 insertions(+), 10 deletions(-) diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index c6f5912..18c2439 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -1,4 +1,6 @@ -from ctypes import * +# -*- coding: utf-8 -*- +from ctypes import c_char_p, Structure, c_bool, byref, c_int, c_size_t, POINTER, c_uint16, c_void_p, c_uint64 +from ctypes import create_string_buffer, ArgumentError, CFUNCTYPE, c_uint32, sizeof, c_uint8 from wrapper.toxcore_enums_and_consts import * from wrapper.toxav import ToxAV from wrapper.libtox import LibToxCore @@ -20,6 +22,14 @@ class ToxOptions(Structure): ] +class GroupChatSelfPeerInfo(Structure): + _fields_ = [ + ('nick', c_char_p), + ('nick_length', c_uint16), + ('user_status', c_int) + ] + + def string_to_bin(tox_id): return c_char_p(bytes.fromhex(tox_id)) if tox_id is not None else None @@ -30,9 +40,8 @@ def bin_to_string(raw_id, length): class Tox: - libtoxcore = LibToxCore() - + def __init__(self, tox_options=None, tox_pointer=None): """ Creates and initialises a new Tox instance with the options passed. @@ -90,11 +99,22 @@ class Tox: self.file_recv_chunk_cb = None self.friend_lossy_packet_cb = None self.friend_lossless_packet_cb = None - self.group_namelist_change_cb = None - self.group_title_cb = None - self.group_action_cb = None - self.group_message_cb = None + self.group_moderation_cb = None + self.group_join_fail_cb = None + self.group_self_join_cb = None self.group_invite_cb = None + self.group_custom_packet_cb = None + self.group_private_message_cb = None + self.group_private_message_cb = None + self.group_message_cb = None + self.group_password_cb = None + self.group_peer_limit_cb = None + self.group_privacy_state_cb = None + self.group_topic_cb = None + self.group_peer_status_cb = None + self.group_peer_name_cb = None + self.group_peer_exit_cb = None + self.group_peer_join_cb = None self.AV = ToxAV(self._tox_pointer) @@ -1514,51 +1534,63 @@ class Tox: elif tox_err_get_port == TOX_ERR_GET_PORT['NOT_BOUND']: raise RuntimeError('The instance was not bound to any port.') -# ----------------------------------------------------------------------------------------------------------------- + # ----------------------------------------------------------------------------------------------------------------- # Group chat instance management # ----------------------------------------------------------------------------------------------------------------- def group_new(self, privacy_state, group_name): """ Creates a new group chat. + This function creates a new group chat object and adds it to the chats array. + The client should initiate its peer list with self info after calling this function, as the peer_join callback will not be triggered. + :param privacy_state: The privacy state of the group. If this is set to TOX_GROUP_PRIVACY_STATE_PUBLIC, the group will attempt to announce itself to the DHT and anyone with the Chat ID may join. Otherwise a friend invite will be required to join the group. :param group_name: The name of the group. The name must be non-NULL. + :return group number on success, UINT32_MAX on failure. """ error = c_int() + peer_info = self.group_chat_self_peer_info_new() result = Tox.libtoxcore.tox_group_new(self._tox_pointer, privacy_state, group_name, - len(group_name), byref(error)) + len(group_name), peer_info, byref(error)) return result def group_join(self, chat_id, password): """ Joins a group chat with specified Chat ID. + This function creates a new group chat object, adds it to the chats array, and sends a DHT announcement to find peers in the group associated with chat_id. Once a peer has been found a join attempt will be initiated. + :param chat_id: The Chat ID of the group you wish to join. This must be TOX_GROUP_CHAT_ID_SIZE bytes. :param password: The password required to join the group. Set to NULL if no password is required. + :return groupnumber on success, UINT32_MAX on failure. """ error = c_int() + peer_info = self.group_chat_self_peer_info_new() result = Tox.libtoxcore.tox_group_join(self._tox_pointer, string_to_bin(chat_id), password, len(password) if password is not None else 0, + peer_info, byref(error)) return result def group_reconnect(self, groupnumber): """ Reconnects to a group. + This function disconnects from all peers in the group, then attempts to reconnect with the group. The caller's state is not changed (i.e. name, status, role, chat public key etc.) + :param groupnumber: The group number of the group we wish to reconnect to. :return True on success. """ @@ -1570,12 +1602,15 @@ class Tox: def group_leave(self, groupnumber, message): """ Leaves a group. + This function sends a parting packet containing a custom (non-obligatory) message to all peers in a group, and deletes the group from the chat array. All group state information is permanently lost, including keys and role credentials. + :param groupnumber: The group number of the group we wish to leave. :param message: The parting message to be sent to all the peers. Set to NULL if we do not wish to send a parting message. + :return True if the group chat instance was successfully deleted. """ @@ -1593,9 +1628,12 @@ class Tox: def group_self_set_name(self, groupnumber, name): """ Set the client's nickname for the group instance designated by the given group number. + Nickname length cannot exceed TOX_MAX_NAME_LENGTH. If length is equal to zero or name is a NULL pointer, the function call will fail. + :param name: A byte array containing the new nickname. + :return True on success. """ @@ -1607,6 +1645,7 @@ class Tox: """ Return the length of the client's current nickname for the group instance designated by groupnumber as passed to tox_group_self_set_name. + If no nickname was set before calling this function, the name is empty, and this function returns 0. """ @@ -1618,8 +1657,10 @@ class Tox: def group_self_get_name(self, groupnumber): """ Write the nickname set by tox_group_self_set_name to a byte array. + If no nickname was set before calling this function, the name is empty, and this function has no effect. + Call tox_group_self_get_name_size to find out how much memory to allocate for the result. :return nickname """ @@ -1674,10 +1715,13 @@ class Tox: def group_self_get_public_key(self, groupnumber): """ Write the client's group public key designated by the given group number to a byte array. + This key will be permanently tied to the client's identity for this particular group until the client explicitly leaves the group or gets kicked/banned. This key is the only way for other peers to reliably identify the client across client restarts. + `public_key` should have room for at least TOX_GROUP_PEER_PUBLIC_KEY_SIZE bytes. + :return public key """ @@ -1695,6 +1739,7 @@ class Tox: """ Return the length of the peer's name. If the group number or ID is invalid, the return value is unspecified. + The return value is equal to the `length` argument received by the last `group_peer_name` callback. """ @@ -1707,11 +1752,15 @@ class Tox: """ Write the name of the peer designated by the given ID to a byte array. + Call tox_group_peer_get_name_size to determine the allocation size for the `name` parameter. + The data written to `name` is equal to the data received by the last `group_peer_name` callback. + :param groupnumber: The group number of the group we wish to query. :param peer_id: The ID of the peer whose name we want to retrieve. + :return name. """ error = c_int() @@ -1724,6 +1773,7 @@ class Tox: """ Return the peer's user status (away/busy/...). If the ID or group number is invalid, the return value is unspecified. + The status returned is equal to the last status received through the `group_peer_status` callback. """ @@ -1736,6 +1786,7 @@ class Tox: """ Return the peer's role (user/moderator/founder...). If the ID or group number is invalid, the return value is unspecified. + The role returned is equal to the last role received through the `group_moderation` callback. """ @@ -1747,9 +1798,12 @@ class Tox: def group_peer_get_public_key(self, groupnumber, peer_id): """ Write the group public key with the designated peer_id for the designated group number to public_key. + This key will be parmanently tied to a particular peer until they explicitly leave the group or get kicked/banned, and is the only way to reliably identify the same peer across client restarts. + `public_key` should have room for at least TOX_GROUP_PEER_PUBLIC_KEY_SIZE bytes. + :return public key """ @@ -1786,8 +1840,10 @@ class Tox: def group_set_topic(self, groupnumber, topic): """ Set the group topic and broadcast it to the rest of the group. + topic length cannot be longer than TOX_GROUP_MAX_TOPIC_LENGTH. If length is equal to zero or topic is set to NULL, the topic will be unset. + :return True on success. """ @@ -1799,6 +1855,7 @@ class Tox: """ Return the length of the group topic. If the group number is invalid, the return value is unspecified. + The return value is equal to the `length` argument received by the last `group_topic` callback. """ @@ -1813,6 +1870,7 @@ class Tox: Call tox_group_get_topic_size to determine the allocation size for the `topic` parameter. The data written to `topic` is equal to the data received by the last `group_topic` callback. + :return topic """ @@ -1870,8 +1928,10 @@ class Tox: """ Return the privacy state of the group designated by the given group number. If group number is invalid, the return value is unspecified. + The value returned is equal to the data received by the last `group_privacy_state` callback. + see the `Group chat founder controls` section for the respective set function. """ @@ -1883,8 +1943,10 @@ class Tox: """ Return the maximum number of peers allowed for the group designated by the given group number. If the group number is invalid, the return value is unspecified. + The value returned is equal to the data received by the last `group_peer_limit` callback. + see the `Group chat founder controls` section for the respective set function. """ @@ -1905,10 +1967,14 @@ class Tox: def group_get_password(self, groupnumber): """ Write the password for the group designated by the given group number to a byte array. + Call tox_group_get_password_size to determine the allocation size for the `password` parameter. + The data received is equal to the data received by the last `group_password` callback. + see the `Group chat founder controls` section for the respective set function. + :return password """ @@ -1966,13 +2032,17 @@ class Tox: def group_send_custom_packet(self, groupnumber, lossless, data): """ Send a custom packet to the group. + If lossless is true the packet will be lossless. Lossless packet behaviour is comparable to TCP (reliability, arrive in order) but with packets instead of a stream. + If lossless is false, the packet will be lossy. Lossy packets behave like UDP packets, meaning they might never reach the other side or might arrive more than once (if someone is messing with the connection) or might arrive in the wrong order. + Unless latency is an issue or message reliability is not important, it is recommended that you use lossless custom packets. + :param groupnumber: The group number of the group the message is intended for. :param lossless: True if the packet should be lossless. :param data A byte array containing the packet data. @@ -1987,14 +2057,18 @@ class Tox: def group_send_private_message(self, groupnumber, peer_id, message): """ Send a text chat message to the specified peer in the specified group. + This function creates a group private message packet and pushes it into the send queue. + The message length may not exceed TOX_MAX_MESSAGE_LENGTH. Larger messages must be split by the client and sent as separate messages. Other clients can then reassemble the fragments. Messages may not be empty. + :param groupnumber: The group number of the group the message is intended for. :param peer_id: The ID of the peer the message is intended for. :param message: A non-NULL pointer to the first element of a byte array containing the message text. + :return True on success. """ @@ -2006,14 +2080,18 @@ class Tox: def group_send_message(self, groupnumber, type, message): """ Send a text chat message to the group. + This function creates a group message packet and pushes it into the send queue. + The message length may not exceed TOX_MAX_MESSAGE_LENGTH. Larger messages must be split by the client and sent as separate messages. Other clients can then reassemble the fragments. Messages may not be empty. + :param groupnumber: The group number of the group the message is intended for. :param type: Message type (normal, action, ...). :param message: A non-NULL pointer to the first element of a byte array containing the message text. + :return True on success. """ @@ -2030,6 +2108,7 @@ class Tox: """ Set the callback for the `group_message` event. Pass NULL to unset. This event is triggered when the client receives a group message. + Callback: python function with params: tox Tox* instance groupnumber The group number of the group the message is intended for. @@ -2057,6 +2136,7 @@ class Tox: def callback_group_custom_packet(self, callback, user_data): """ Set the callback for the `group_custom_packet` event. Pass NULL to unset. + This event is triggered when the client receives a custom packet. """ @@ -2071,9 +2151,12 @@ class Tox: def group_invite_friend(self, groupnumber, friend_number): """ Invite a friend to a group. + This function creates an invite request packet and pushes it to the send queue. + :param groupnumber: The group number of the group the message is intended for. :param friend_number: The friend number of the friend the invite is intended for. + :return True on success. """ @@ -2081,10 +2164,19 @@ class Tox: result = Tox.libtoxcore.tox_group_invite_friend(self._tox_pointer, groupnumber, friend_number, byref(error)) return result + def group_chat_self_peer_info_new(self): + error = c_int() + f = Tox.libtoxcore.group_chat_self_peer_info_new + f.restype = POINTER(GroupChatSelfPeerInfo) + result = f(self._tox_pointer, byref(error)) + + return result + def group_invite_accept(self, invite_data, friend_number, password=None): """ Accept an invite to a group chat that the client previously received from a friend. The invite is only valid while the inviter is present in the group. + :param invite_data: The invite data received from the `group_invite` event. :param password: The password required to join the group. Set to NULL if no password is required. :return the groupnumber on success, UINT32_MAX on failure. @@ -2093,16 +2185,19 @@ class Tox: error = c_int() f = Tox.libtoxcore.tox_group_invite_accept f.restype = c_uint32 + peer_info = self.group_chat_self_peer_info_new() result = f(self._tox_pointer, friend_number, invite_data, len(invite_data), password, - len(password) if password is not None else 0, byref(error)) + len(password) if password is not None else 0, peer_info, byref(error)) print('Invite accept. Result:', result, 'Error:', error.value) return result def callback_group_invite(self, callback, user_data): """ Set the callback for the `group_invite` event. Pass NULL to unset. + This event is triggered when the client receives a group invite from a friend. The client must store invite_data which is used to join the group via tox_group_invite_accept. + Callback: python function with params: tox - Tox* friend_number The friend number of the contact who sent the invite. @@ -2118,6 +2213,7 @@ class Tox: def callback_group_peer_join(self, callback, user_data): """ Set the callback for the `group_peer_join` event. Pass NULL to unset. + This event is triggered when a peer other than self joins the group. Callback: python function with params: tox - Tox* @@ -2133,6 +2229,7 @@ class Tox: def callback_group_peer_exit(self, callback, user_data): """ Set the callback for the `group_peer_exit` event. Pass NULL to unset. + This event is triggered when a peer other than self exits the group. """ @@ -2143,6 +2240,7 @@ class Tox: def callback_group_self_join(self, callback, user_data): """ Set the callback for the `group_self_join` event. Pass NULL to unset. + This event is triggered when the client has successfully joined a group. Use this to initialize any group information the client may need. Callback: python fucntion with params: @@ -2158,6 +2256,7 @@ class Tox: def callback_group_join_fail(self, callback, user_data): """ Set the callback for the `group_join_fail` event. Pass NULL to unset. + This event is triggered when the client fails to join a group. """ @@ -2172,10 +2271,13 @@ class Tox: def group_founder_set_password(self, groupnumber, password): """ Set or unset the group password. + This function sets the groups password, creates a new group shared state including the change, and distributes it to the rest of the group. + :param groupnumber: The group number of the group for which we wish to set the password. :param password: The password we want to set. Set password to NULL to unset the password. + :return True on success. """ @@ -2187,12 +2289,16 @@ class Tox: def group_founder_set_privacy_state(self, groupnumber, privacy_state): """ Set the group privacy state. + This function sets the group's privacy state, creates a new group shared state including the change, and distributes it to the rest of the group. + If an attempt is made to set the privacy state to the same state that the group is already in, the function call will be successful and no action will be taken. + :param groupnumber: The group number of the group for which we wish to change the privacy state. :param privacy_state: The privacy state we wish to set the group to. + :return true on success. """ @@ -2204,10 +2310,13 @@ class Tox: def group_founder_set_peer_limit(self, groupnumber, max_peers): """ Set the group peer limit. + This function sets a limit for the number of peers who may be in the group, creates a new group shared state including the change, and distributes it to the rest of the group. + :param groupnumber: The group number of the group for which we wish to set the peer limit. :param max_peers: The maximum number of peers to allow in the group. + :return True on success. """ @@ -2223,9 +2332,11 @@ class Tox: def group_toggle_ignore(self, groupnumber, peer_id, ignore): """ Ignore or unignore a peer. + :param groupnumber: The group number of the group the in which you wish to ignore a peer. :param peer_id: The ID of the peer who shall be ignored or unignored. :param ignore: True to ignore the peer, false to unignore the peer. + :return True on success. """ @@ -2236,12 +2347,15 @@ class Tox: def group_mod_set_role(self, groupnumber, peer_id, role): """ Set a peer's role. + This function will first remove the peer's previous role and then assign them a new role. It will also send a packet to the rest of the group, requesting that they perform the role reassignment. Note: peers cannot be set to the founder role. + :param groupnumber: The group number of the group the in which you wish set the peer's role. :param peer_id: The ID of the peer whose role you wish to set. :param role: The role you wish to set the peer to. + :return True on success. """ @@ -2252,12 +2366,15 @@ class Tox: def group_mod_remove_peer(self, groupnumber, peer_id, set_ban): """ Kick/ban a peer. + This function will remove a peer from the caller's peer list and optionally add their IP address to the ban list. It will also send a packet to all group members requesting them to do the same. + :param groupnumber: The group number of the group the ban is intended for. :param peer_id: The ID of the peer who will be kicked and/or added to the ban list. :param set_ban: Set to true if a ban shall be set on the peer's IP address. + :return True on success. """ @@ -2269,10 +2386,13 @@ class Tox: def group_mod_remove_ban(self, groupnumber, ban_id): """ Removes a ban. + This function removes a ban entry from the ban list, and sends a packet to the rest of the group requesting that they do the same. + :param groupnumber: The group number of the group in which the ban is to be removed. :param ban_id: The ID of the ban entry that shall be removed. + :return True on success """ @@ -2283,6 +2403,7 @@ class Tox: def callback_group_moderation(self, callback, user_data): """ Set the callback for the `group_moderation` event. Pass NULL to unset. + This event is triggered when a moderator or founder executes a moderation event. """ @@ -2307,6 +2428,7 @@ class Tox: def group_ban_get_list(self, groupnumber): """ Copy a list of valid ban list ID's into an array. + Call tox_group_ban_get_list_size to determine the number of elements to allocate. return true on success. """ @@ -2331,7 +2453,9 @@ class Tox: """ Write the name of the ban entry designated by ban_id in the group designated by the given group number to a byte array. + Call tox_group_ban_get_name_size to find out how much memory to allocate for the result. + :return name """ From ef4a1b18fdbbe385d939e98c3561ca82c8df781c Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 19 May 2018 18:08:25 +0300 Subject: [PATCH 049/138] ngc - invites, gc menu, callbacks etc --- toxygen/app.py | 12 +++-- toxygen/contacts/contact_menu.py | 53 ++++++++++++++------ toxygen/contacts/contacts_manager.py | 73 ++++++++++++++++++---------- toxygen/contacts/friend_factory.py | 2 +- toxygen/contacts/group_chat.py | 42 +++++++++------- toxygen/contacts/group_factory.py | 45 ++++++++++++++++- toxygen/groups/group_peer.py | 46 +++++++++++++++--- toxygen/groups/groups_service.py | 30 ++++++++++++ toxygen/middleware/callbacks.py | 24 ++++++++- toxygen/ui/items_factories.py | 4 +- toxygen/ui/main_screen.py | 9 ++-- toxygen/wrapper/tox.py | 4 +- 12 files changed, 262 insertions(+), 82 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index b76a047..a4fa37b 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -25,7 +25,7 @@ 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, FriendItemsFactory +from ui.items_factories import MessagesItemsFactory, ContactItemsFactory from messenger.messenger import Messenger from network.tox_dns import ToxDns from history.history import History @@ -301,9 +301,10 @@ class App: self._ms = MainWindow(self._settings, self._tray) db = Database(self._path.replace('.tox', '.db'), self._toxes) - friend_items_factory = FriendItemsFactory(self._settings, self._ms) - self._friend_factory = FriendFactory(self._profile_manager, self._settings, self._tox, db, friend_items_factory) - self._group_factory = GroupFactory() + 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._contacts_provider = ContactProvider(self._tox, self._friend_factory, self._group_factory) profile = Profile(self._profile_manager, self._tox, self._ms, self._contacts_provider, self._reset) self._plugin_loader = PluginLoader(self._tox, self._toxes, profile, self._settings) @@ -329,7 +330,8 @@ class App: self._toxes, self._version, self._groups_service) self._tray = tray.init_tray(profile, self._settings, self._ms) self._ms.set_dependencies(widgets_factory, self._tray, self._contacts_manager, self._messenger, profile, - self._plugin_loader, self._file_transfer_handler, history, self._calls_manager) + self._plugin_loader, self._file_transfer_handler, history, self._calls_manager, + self._groups_service) self._tray.show() self._ms.show() diff --git a/toxygen/contacts/contact_menu.py b/toxygen/contacts/contact_menu.py index c580a3f..4a0dcaa 100644 --- a/toxygen/contacts/contact_menu.py +++ b/toxygen/contacts/contact_menu.py @@ -81,16 +81,28 @@ class BaseContactMenuGenerator: def __init__(self, contact): self._contact = contact - def generate(self, plugin_loader, contacts_manager, main_screen, settings, number): + def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service): return ContactMenuBuilder().build() + 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 + class FriendMenuGenerator(BaseContactMenuGenerator): - def generate(self, plugin_loader, contacts_manager, main_screen, settings, number): + def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service): history_menu_builder = self._generate_history_menu_builder(main_screen, number) 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') @@ -105,6 +117,7 @@ class FriendMenuGenerator(BaseContactMenuGenerator): .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 @@ -125,17 +138,6 @@ class FriendMenuGenerator(BaseContactMenuGenerator): return history_menu_builder - 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 - @staticmethod def _generate_plugins_menu_builder(plugin_loader, number): if plugin_loader is None: @@ -151,7 +153,30 @@ class FriendMenuGenerator(BaseContactMenuGenerator): return plugins_menu_builder - def _generate_groups_menu(self, contacts_manager): # TODO: fix + 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): + copy_menu_builder = self._generate_copy_menu_builder(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_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 diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 7dad02f..6c19b26 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -1,4 +1,5 @@ from contacts.friend import Friend +from contacts.group_chat import GroupChat from messenger.messages import * @@ -47,7 +48,7 @@ class ContactsManager: if self.is_active_a_friend(): return False - return self.get_curr_contact().number == friend_number + return self.get_curr_contact().number == group_number # ----------------------------------------------------------------------------------------------------------------- # Work with active friend @@ -194,8 +195,11 @@ class ContactsManager: # Friend getters # ----------------------------------------------------------------------------------------------------------------- - def get_friend_by_number(self, num): - return list(filter(lambda x: x.number == num and type(x) is Friend, self._contacts))[0] + def get_friend_by_number(self, number): + return list(filter(lambda x: x.number == number and type(x) is Friend, self._contacts))[0] + + def get_group_by_number(self, number): + return list(filter(lambda x: x.number == number and type(x) is GroupChat, self._contacts))[0] def get_last_message(self): if self._active_contact + 1: @@ -261,8 +265,6 @@ class ContactsManager: except: pass self._settings.save() - # if num == self.get_active_number() and self.is_active_a_friend(): - # self.update() def friend_public_key(self, num): return self._contacts[num].tox_id @@ -277,22 +279,9 @@ class ContactsManager: :param num: number of friend in list """ friend = self._contacts[num] - try: - index = list(map(lambda x: x[0], self._settings['friends_aliases'])).index(friend.tox_id) - del self._settings['friends_aliases'][index] - except: - pass - if friend.tox_id in self._settings['notes']: - del self._settings['notes'][friend.tox_id] - self._settings.save() - self._history.delete_history(friend) + self._cleanup_contact_data(friend) self._tox.friend_delete(friend.number) - del self._contacts[num] - self._screen.friends_list.takeItem(num) - if num == self._active_contact: # active friend was deleted - self.set_active(0 if len(self._contacts) else -1) - data = self._tox.get_savedata() - self._profile_manager.save_profile(data) + self._delete_contact(num) def add_friend(self, tox_id): """ @@ -302,12 +291,8 @@ class ContactsManager: self._history.add_friend_to_db(tox_id) friend = self._contact_provider.get_friend_by_public_key(tox_id) self._contacts.append(friend) - friend.reset_avatar() - - def add_group(self, group_number): - group = self._contact_provider.get_group_by_number(group_number) - self._contacts.append(group) - group.reset_avatar() + friend.reset_avatar(self._settings['identicons']) + self._save_profile() def block_user(self, tox_id): """ @@ -343,7 +328,19 @@ class ContactsManager: # ----------------------------------------------------------------------------------------------------------------- def get_group_chats(self): - return list(filter(lambda c: type(c) is not Friend, self._contacts)) # TODO: fix after gc implementation + 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) + self._contacts.append(group) + group.reset_avatar(self._settings['identicons']) + self._save_profile() + + 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) # ----------------------------------------------------------------------------------------------------------------- # Friend requests @@ -454,3 +451,25 @@ class ContactsManager: pixmap = QtGui.QPixmap(avatar_path) self._screen.account_avatar.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)) + + 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) + + def _delete_contact(self, num): + del self._contacts[num] + self._screen.friends_list.takeItem(num) + if num == self._active_contact: # active friend was deleted + self.set_active(0 if len(self._contacts) else -1) + self._save_profile() diff --git a/toxygen/contacts/friend_factory.py b/toxygen/contacts/friend_factory.py index 9ba859f..a9b0477 100644 --- a/toxygen/contacts/friend_factory.py +++ b/toxygen/contacts/friend_factory.py @@ -39,4 +39,4 @@ class FriendFactory: Method-factory :return: new widget for friend instance """ - return self._items_factory.create_friend_item() + return self._items_factory.create_contact_item() diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index d37bbe7..79c2cfa 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -1,13 +1,14 @@ from contacts import contact +from contacts.contact_menu import GroupMenuGenerator import utils.util as util -from wrapper.toxcore_enums_and_consts import * +from groups.group_peer import GroupChatPeer from wrapper import toxcore_enums_and_consts as constants class GroupChat(contact.Contact): - def __init__(self, profile_manager, name, status_message, widget, tox, group_number): - super().__init__(None, group_number, profile_manager, name, status_message, widget, None) + def __init__(self, tox, 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._tox = tox self.set_status(constants.TOX_USER_STATUS['NONE']) self._peers = [] @@ -20,25 +21,29 @@ class GroupChat(contact.Contact): 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_context_menu_generator(self): + return GroupMenuGenerator(self) - 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) + # ----------------------------------------------------------------------------------------------------------------- + # Peers methods + # ----------------------------------------------------------------------------------------------------------------- def get_self_name(self): return self._peers[0].name + def add_peer(self, peer_id): + 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)) + self._peers.append(peer) + + def get_peer(self, peer_id): + peers = list(filter(lambda p: p.id == peer_id, self._peers)) + + return peers[0] + # ----------------------------------------------------------------------------------------------------------------- # Private methods # ----------------------------------------------------------------------------------------------------------------- @@ -48,4 +53,5 @@ class GroupChat(contact.Contact): return util.join_path(util.get_images_directory(), 'group.png') def _add_self_to_gc(self): - pass + peer_id = self._tox.group_self_get_peer_id(self._number) + self.add_peer(peer_id) diff --git a/toxygen/contacts/group_factory.py b/toxygen/contacts/group_factory.py index 953c483..089fc09 100644 --- a/toxygen/contacts/group_factory.py +++ b/toxygen/contacts/group_factory.py @@ -1,6 +1,49 @@ +from contacts.group_chat import GroupChat class GroupFactory: + def __init__(self, profile_manager, settings, tox, db, items_factory): + self._profile_manager = profile_manager + self._settings, self._tox = settings, tox + self._db = db + self._items_factory = items_factory + def create_group_by_public_key(self, public_key): - pass + 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) + group = GroupChat(self._tox, self._profile_manager, message_getter, group_number, name, status_message, + item, tox_id) + 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 diff --git a/toxygen/groups/group_peer.py b/toxygen/groups/group_peer.py index f91decc..6736185 100644 --- a/toxygen/groups/group_peer.py +++ b/toxygen/groups/group_peer.py @@ -2,9 +2,43 @@ class GroupChatPeer: - def __init__(self, peer_number, name, status, role, public_key): - self.peer_number = peer_number - self.name = name - self.status = status - self.role = role - self.public_key = public_key + def __init__(self, peer_id, name, status, role, public_key): + self._peer_id = peer_id + self._name = name + self._status = status + self._role = role + self._public_key = public_key + + def get_id(self): + return self._peer_id + + id = property(get_id) + + 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_public_key(self): + return self._public_key + + public_key = property(get_public_key) diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index febdb9e..beb0853 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -1,4 +1,5 @@ import common.tox_save as tox_save +import utils.ui as util_ui class GroupsService(tox_save.ToxSave): @@ -24,9 +25,38 @@ class GroupsService(tox_save.ToxSave): group_number = self._tox.group_invite_accept(invite_data, friend_number, password) self._add_new_group_by_number(group_number) + # ----------------------------------------------------------------------------------------------------------------- + # Groups reconnect and leaving + # ----------------------------------------------------------------------------------------------------------------- + + def leave_group(self, group_number): + group = self._get_group(group_number) + self._tox.group_leave(group_number) + self._contacts_manager.delete_group(group_number) + self._contacts_provider.remove_contact_from_cache(group.tox_id) + + # ----------------------------------------------------------------------------------------------------------------- + # 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, invite_data): + friend = self._get_friend(friend_number) + text = util_ui.tr('Friend {} invites you to group. Accept?') + if util_ui.question(text.format(friend.name), util_ui.tr('Group invite')): + self.join_gc_via_invite(invite_data, friend_number, None) + # ----------------------------------------------------------------------------------------------------------------- # Private methods # ----------------------------------------------------------------------------------------------------------------- def _add_new_group_by_number(self, group_number): self._contacts_manager.add_group(group_number) + + def _get_group(self, group_number): + return self._contacts_provider.get_group_by_number(group_number) + + def _get_friend(self, friend_number): + return self._contacts_provider.get_friend_by_number(friend_number) diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 40b3284..7b0872d 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -398,8 +398,26 @@ def group_self_join(contacts_provider): def group_peer_join(contacts_provider): def wrapped(tox, group_number, peer_id, user_data): - gc = contacts_provider.get_group_by_number(group_number) - gc.add_peer(peer_id) + group = contacts_provider.get_group_by_number(group_number) + group.add_peer(peer_id) + + return wrapped + + +def group_peer_name(contacts_provider): + def wrapped(tox, group_number, peer_id, name, length, user_data): + group = contacts_provider.get_group_by_number(group_number) + peer = group.get_peer(peer_id) + peer.name = str(name[:length]) + + return wrapped + + +def group_peer_status(contacts_provider): + def wrapped(tox, group_number, peer_id, peer_status, user_data): + group = contacts_provider.get_group_by_number(group_number) + peer = group.get_peer(peer_id) + peer.status = peer_status return wrapped @@ -463,3 +481,5 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, tox.callback_group_invite(group_invite(groups_service), 0) tox.callback_group_self_join(group_self_join(contacts_provider), 0) tox.callback_group_peer_join(group_peer_join(contacts_provider), 0) + tox.callback_group_peer_name(group_peer_name(contacts_provider), 0) + tox.callback_group_peer_status(group_peer_status(contacts_provider), 0) diff --git a/toxygen/ui/items_factories.py b/toxygen/ui/items_factories.py index 6197066..2ea5660 100644 --- a/toxygen/ui/items_factories.py +++ b/toxygen/ui/items_factories.py @@ -2,13 +2,13 @@ from ui.contact_items import * from ui.messages_widgets import * -class FriendItemsFactory: +class ContactItemsFactory: def __init__(self, settings, main_screen): self._settings = settings self._friends_list = main_screen.friends_list - def create_friend_item(self): + def create_contact_item(self): item = ContactItem(self._settings) elem = QtWidgets.QListWidgetItem(self._friends_list) elem.setSizeHint(QtCore.QSize(250, item.height())) diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index fd404b2..9a4df47 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -18,11 +18,11 @@ class MainWindow(QtWidgets.QMainWindow): self.setAcceptDrops(True) self._saved = False self._profile = None - self._file_transfer_handler = self._history_loader = self._calls_manager = None + self._file_transfer_handler = self._history_loader = self._groups_service = self._calls_manager = None self.initUI() def set_dependencies(self, widget_factory, tray, contacts_manager, messenger, profile, plugins_loader, - file_transfer_handler, history_loader, calls_manager): + file_transfer_handler, history_loader, calls_manager, groups_service): self._widget_factory = widget_factory self._tray = tray self._contacts_manager = contacts_manager @@ -31,6 +31,7 @@ class MainWindow(QtWidgets.QMainWindow): self._file_transfer_handler = file_transfer_handler self._history_loader = history_loader self._calls_manager = calls_manager + self._groups_service = groups_service self.messageEdit.set_messenger(messenger) def show(self): @@ -414,7 +415,6 @@ class MainWindow(QtWidgets.QMainWindow): 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): key, modifiers = event.key(), event.modifiers() @@ -598,7 +598,8 @@ class MainWindow(QtWidgets.QMainWindow): 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.listMenu = generator.generate(self._plugins_loader, self._contacts_manager, self, self._settings, number, + self._groups_service) parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0)) self.listMenu.move(parent_position + pos) self.listMenu.show() diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index 18c2439..f455e30 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -1599,7 +1599,7 @@ class Tox: result = Tox.libtoxcore.tox_group_reconnect(self._tox_pointer, groupnumber, byref(error)) return result - def group_leave(self, groupnumber, message): + def group_leave(self, groupnumber, message=''): """ Leaves a group. @@ -1887,7 +1887,7 @@ class Tox: """ error = c_int() result = Tox.libtoxcore.tox_group_get_name_size(self._tox_pointer, groupnumber, byref(error)) - return result + return int(result) def group_get_name(self, groupnumber): """ From a935d602f8c0a52194980ef36703209113098733 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 19 May 2018 19:27:27 +0300 Subject: [PATCH 050/138] minimal working ngc version - sending messages, invites, char creation --- toxygen/bootstrap/nodes.json | 2 +- toxygen/contacts/contacts_manager.py | 3 +++ toxygen/contacts/group_chat.py | 2 +- toxygen/groups/groups_service.py | 10 +++++++- toxygen/messenger/messenger.py | 34 ++++++++++++++++++++-------- toxygen/middleware/callbacks.py | 9 ++++---- 6 files changed, 43 insertions(+), 17 deletions(-) diff --git a/toxygen/bootstrap/nodes.json b/toxygen/bootstrap/nodes.json index 003bbc0..619fe67 100644 --- a/toxygen/bootstrap/nodes.json +++ b/toxygen/bootstrap/nodes.json @@ -1 +1 @@ -{"last_scan":1516822981,"last_refresh":1516822982,"nodes":[{"ipv4":"node.tox.biribiri.org","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67","maintainer":"nurupo","location":"US","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Welcome, stranger #7985. I'm up for 5d 14h 34m 34s, running since Jan 19 05:08:27 UTC. If I get outdated, please ping my maintainer at nurupo.contributions@gmail.com","last_ping":1516822981},{"ipv4":"nodes.tox.chat","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"6FC41E2BD381D37E9748FC0E0328CE086AF9598BECC8FEB7DDF2E440475F300E","maintainer":"Impyy","location":"NL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Straps boots like no other","last_ping":1516822981},{"ipv4":"130.133.110.14","ipv6":"2001:6f8:1c3c:babe::14:1","port":33445,"tcp_ports":[33445],"public_key":"461FA3776EF0FA655F1A05477DF1B3B614F7D6B124F7DB1DD4FE3C08B03B640F","maintainer":"Manolis","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Spline tox bootstrap node","last_ping":1516822981},{"ipv4":"205.185.116.116","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"A179B09749AC826FF01F37A9613F6B57118AE014D4196A0E1105A98F93A54702","maintainer":"Busindre","location":"US","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"198.98.51.198","ipv6":"2605:6400:1:fed5:22:45af:ec10:f329","port":33445,"tcp_ports":[33445,3389],"public_key":"1D5A5F2F5D6233058BF0259B09622FB40B482E4FA0931EB8FD3AB8E7BF7DAF6F","maintainer":"Busindre","location":"US","status_udp":true,"status_tcp":true,"version":"2014101200","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"85.172.30.117","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"8E7D0B859922EF569298B4D261A8CCB5FEA14FB91ED412A7603A585A25698832","maintainer":"ray65536","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Ray's Tox Node","last_ping":1516822981},{"ipv4":"194.249.212.109","ipv6":"2001:1470:fbfe::109","port":33445,"tcp_ports":[33445,3389],"public_key":"3CEE1F054081E7A011234883BC4FC39F661A55B73637A5AC293DDF1251D9432B","maintainer":"fluke571","location":"SI","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"185.25.116.107","ipv6":"2a00:7a60:0:746b::3","port":33445,"tcp_ports":[33445,3389],"public_key":"DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43","maintainer":"MAH69K","location":"UA","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Saluton! Mia Tox ID: B229B7BD68FC66C2716EAB8671A461906321C764782D7B3EDBB650A315F6C458EF744CE89F07. Scribu! ;)","last_ping":1516822981},{"ipv4":"5.189.176.217","ipv6":"2a02:c200:1:10:3:1:605:1337","port":5190,"tcp_ports":[3389,33445,5190],"public_key":"2B2137E094F743AC8BD44652C55F41DFACC502F125E99E4FE24D40537489E32F","maintainer":"tastytea","location":"DE","status_udp":true,"status_tcp":true,"version":"","motd":"","last_ping":1516822981},{"ipv4":"217.182.143.254","ipv6":"2001:41d0:302:1000::e111","port":2306,"tcp_ports":[33445,2306,443],"public_key":"7AED21F94D82B05774F697B209628CD5A9AD17E0C073D9329076A4C28ED28147","maintainer":"pucetox","location":"FR","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"by pucetox,\nipv4/ipv6 UDP:2306 TCP:21/80/443/2306/33445\nsync your nodes here tox.0x10k.com/bootstrapd-conf , \n for communication: 1D1C0B992DEB6D7F18561176F7F5E572BCC7F2BA5CFA7E9E437B9134122CE96D906A6119F9D2","last_ping":1516822981},{"ipv4":"104.223.122.15","ipv6":"2607:ff48:aa81:800::35eb:1","port":33445,"tcp_ports":[3389,33445],"public_key":"0FB96EEBFB1650DDB52E70CF773DDFCABE25A95CC3BB50FC251082E4B63EF82A","maintainer":"ru_maniac","location":"US","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"built on: Tue Feb 21st 2017, 10:52:30 UTC+3\nplease note: running on TokTox Toxcore!\nmore info on the matter: goo.gl/Gz5KhK \u0026 goo.gl/i2TZJr\n\ntox id for queries and general info: EBD2A7B649ABB10ED9F47E5113F04000F39D46F087CEB62FCCE1069471FD6915256D197F2A97","last_ping":1516822981},{"ipv4":"tox.verdict.gg","ipv6":"-","port":33445,"tcp_ports":[33445,3389],"public_key":"1C5293AEF2114717547B39DA8EA6F1E331E5E358B35F9B6B5F19317911C5F976","maintainer":"Deliran","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Praise The Sun!","last_ping":1516822981},{"ipv4":"d4rk4.ru","ipv6":"-","port":1813,"tcp_ports":[1813],"public_key":"53737F6D47FA6BD2808F378E339AF45BF86F39B64E79D6D491C53A1D522E7039","maintainer":"D4rk4","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"TOX ID: 35EDC07AEB18B163E07EE33F6CDDA63969F394FF6A617CEAB22A7EBBEAAAF854C0EDFBD46898","last_ping":1516822981},{"ipv4":"51.254.84.212","ipv6":"2001:41d0:a:1a3b::18","port":33445,"tcp_ports":[3389,33445],"public_key":"AEC204B9A4501412D5F0BB67D9C81B5DB3EE6ADA64122D32A3E9B093D544327D","maintainer":"a68366","location":"FR","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Since 26.12.2015","last_ping":1516822981},{"ipv4":"88.99.133.52","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"2D320F971EF2CA18004416C2AAE7BA52BF7949DB34EA8E2E21AF67BD367BE211","maintainer":"Skey","location":"FR","status_udp":true,"status_tcp":true,"version":"2014101200","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"92.54.84.70","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"5625A62618CB4FCA70E147A71B29695F38CC65FF0CBD68AD46254585BE564802","maintainer":"t3mp","location":"RU","status_udp":true,"status_tcp":false,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"tox.uplinklabs.net","ipv6":"tox.uplinklabs.net","port":33445,"tcp_ports":[3389,33445],"public_key":"1A56EA3EDF5DF4C0AEABBF3C2E4E603890F87E983CAC8A0D532A335F2C6E3E1F","maintainer":"AbacusAvenger","location":"US","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"i don't know what this is for","last_ping":1516822981},{"ipv4":"toxnode.nek0.net","ipv6":"toxnode.nek0.net","port":33445,"tcp_ports":[3389,33445],"public_key":"20965721D32CE50C3E837DD75B33908B33037E6225110BFF209277AEAF3F9639","maintainer":"Phsm","location":"UA","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"95.215.44.78","ipv6":"2a02:7aa0:1619::c6fe:d0cb","port":33445,"tcp_ports":[33445,3389],"public_key":"672DBE27B4ADB9D5FB105A6BB648B2F8FDB89B3323486A7A21968316E012023C","maintainer":"HooinKyoma","location":"SE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Thanx to Hooin Kyoma","last_ping":1516822981},{"ipv4":"163.172.136.118","ipv6":"2001:bc8:4400:2100::1c:50f","port":33445,"tcp_ports":[33445,3389],"public_key":"2C289F9F37C20D09DA83565588BF496FAB3764853FA38141817A72E3F18ACA0B","maintainer":"LittleVulpix","location":"FR","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"LittleTox - your friendly neighbourhood tox node!","last_ping":1516822981},{"ipv4":"sorunome.de","ipv6":"sorunome.de","port":33445,"tcp_ports":[3389,33445],"public_key":"02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46","maintainer":"Sorunome","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Keep calm and pony on","last_ping":1516822981},{"ipv4":"37.97.185.116","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"E59A0E71ADA20D35BD1B0957059D7EF7E7792B3D680AE25C6F4DBBA09114D165","maintainer":"Yani","location":"NL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Yani's node of pleasure and leisure","last_ping":1516822981},{"ipv4":"80.87.193.193","ipv6":"2a01:230:2:6::46a8","port":33445,"tcp_ports":[3389,33445],"public_key":"B38255EE4B054924F6D79A5E6E5889EC94B6ADF6FE9906F97A3D01E3D083223A","maintainer":"linxon","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Tox DHT node by Linxon. Author ToxID: EC774ED05A7E71EEE2EBA939A27CD4FF403D7D79E1E685CFD0394B1770498217C6107E4D3C26","last_ping":1516822981},{"ipv4":"initramfs.io","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"3F0A45A268367C1BEA652F258C85F4A66DA76BCAA667A49E770BCC4917AB6A25","maintainer":"initramfs","location":"TW","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"initramfs' Tox DHT Node","last_ping":1516822981},{"ipv4":"hibiki.eve.moe","ipv6":"hibiki.eve.moe","port":33445,"tcp_ports":[33445],"public_key":"D3EB45181B343C2C222A5BCF72B760638E15ED87904625AAD351C594EEFAE03E","maintainer":"EveNeko","location":"FR","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-bootstrapd@hibiki.eve.moe","last_ping":1516822981},{"ipv4":"tox.deadteam.org","ipv6":"tox.deadteam.org","port":33445,"tcp_ports":[33445],"public_key":"C7D284129E83877D63591F14B3F658D77FF9BA9BA7293AEB2BDFBFE1A803AF47","maintainer":"DeadTeam","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Vive le TOX","last_ping":1516822981},{"ipv4":"46.229.52.198","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"813C8F4187833EF0655B10F7752141A352248462A567529A38B6BBF73E979307","maintainer":"Stranger","location":"UA","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Freedom to parrots!","last_ping":1516822981},{"ipv4":"node.tox.ngc.network","ipv6":"node.tox.ngc.network","port":33445,"tcp_ports":[3389,33445],"public_key":"A856243058D1DE633379508ADCAFCF944E40E1672FF402750EF712E30C42012A","maintainer":"Nolz","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Unlike Others","last_ping":1516822981},{"ipv4":"149.56.140.5","ipv6":"2607:5300:0201:3100:0000:0000:0000:3ec2","port":33445,"tcp_ports":[3389,33445],"public_key":"7E5668E0EE09E19F320AD47902419331FFEE147BB3606769CFBE921A2A2FD34C","maintainer":"velusip","location":"CA","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Jera","last_ping":1516822981},{"ipv4":"185.14.30.213","ipv6":"2a00:1ca8:a7::e8b","port":443,"tcp_ports":[33445,3389,443],"public_key":"2555763C8C460495B14157D234DD56B86300A2395554BCAE4621AC345B8C1B1B","maintainer":"dvor","location":"NL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Just another tox node.","last_ping":1516822981},{"ipv4":"tox.natalenko.name","ipv6":"tox.natalenko.name","port":33445,"tcp_ports":[33445],"public_key":"1CB6EBFD9D85448FA70D3CAE1220B76BF6FCE911B46ACDCF88054C190589650B","maintainer":"post-factum","location":"DE","status_udp":true,"status_tcp":true,"version":"","motd":"","last_ping":1516822981},{"ipv4":"136.243.141.187","ipv6":"2a01:4f8:212:2459::a:1337","port":443,"tcp_ports":[33445,3389,443],"public_key":"6EE1FADE9F55CC7938234CC07C864081FC606D8FE7B751EDA217F268F1078A39","maintainer":"CeBe","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"uTox is the future! - maintained by CeBe - contact: tox@cebe.cc - tox: 7F50119368DC8FD3B1ECAF5D18E3F8854F0484CEC5BBF625D420B8E38638733C02486E387AF8","last_ping":1516822981},{"ipv4":"tox.abilinski.com","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"0E9D7FEE2AA4B42A4C18FE81C038E32FFD8D907AAA7896F05AA76C8D31A20065","maintainer":"flobe","location":"CA","status_udp":true,"status_tcp":true,"version":"","motd":"","last_ping":1516822981},{"ipv4":"m.loskiq.it","ipv6":"-","port":33445,"tcp_ports":[33445,3389],"public_key":"88124F3C18C6CFA8778B7679B7329A333616BD27A4DFB562D476681315CF143D","maintainer":"loskiq","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"https://t.me/loskiq","last_ping":1516822981},{"ipv4":"192.99.232.158","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"7B6CB208C811DEA8782711CE0CAD456AAC0C7B165A0498A1AA7010D2F2EC996C","maintainer":"basiljose","location":"CA","status_udp":true,"status_tcp":false,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"tmux.ru","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"7467AFA626D3246343170B309BA5BDC975DF3924FC9D7A5917FBFA9F5CD5CD38","maintainer":"nrn","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"https://t.me/nyoroon","last_ping":1516822981},{"ipv4":"37.48.122.22","ipv6":"2001:1af8:4700:a115:6::b","port":33445,"tcp_ports":[33445,3389],"public_key":"1B5A8AB25FFFB66620A531C4646B47F0F32B74C547B30AF8BD8266CA50A3AB59","maintainer":"Pokemon","location":"NL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Those who would give up essential Liberty, to purchase a little temporary Safety, deserve neither Liberty nor Safety","last_ping":1516822981},{"ipv4":"tox.novg.net","ipv6":"-","port":33445,"tcp_ports":[33445,3389],"public_key":"D527E5847F8330D628DAB1814F0A422F6DC9D0A300E6C357634EE2DA88C35463","maintainer":"blind_oracle","location":"NL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"t0x-node1.weba.ru","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"5A59705F86B9FC0671FDF72ED9BB5E55015FF20B349985543DDD4B0656CA1C63","maintainer":"Amin","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"T0X-Node #1","last_ping":1516822981},{"ipv4":"109.195.99.39","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"EF937F61B4979B60BBF306752D8F32029A2A05CD2615B2E9FBFFEADD8E7D5032","maintainer":"NaCl","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"NaCl node respond","last_ping":1516822981},{"ipv4":"79.140.30.52","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"FFAC871E85B1E1487F87AE7C76726AE0E60318A85F6A1669E04C47EB8DC7C72D","maintainer":"warlomak","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-easy-bootstrap","last_ping":1516822981},{"ipv4":"94.41.167.70","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"E519B2C1098999B60190012C7B53E8C43A73C535721036CD9DEC7CCA06741A7D","maintainer":"warlomak","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-easy-bootstrap","last_ping":1516822981},{"ipv4":"104.223.122.204","ipv6":"-","port":33445,"tcp_ports":[3389],"public_key":"3925752E43BF2F8EB4E12B0E9414311064FF2D76707DC7D5D2CCB43F75081F6B","maintainer":"ru_maniac","location":"US","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"rmnc_third_node","last_ping":1516822981},{"ipv4":"77.55.211.53","ipv6":"-","port":53,"tcp_ports":[443,33445,3389],"public_key":"B9D109CC820C69A5D97A4A1A15708107C6BA85C13BC6188CC809D374AFF18E63","maintainer":"GDR!","location":"PL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"GDR!'s tox-bootstrapd https://gdr.name/","last_ping":1516822922},{"ipv4":"boseburo.ddns.net","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"AF3FC9FC3D121E82E362B4FA84A53E63F58C11C2BA61D988855289B8CABC9B18","maintainer":"LowEel","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"This is the Bose Buro bootstrap daemon","last_ping":1516822981},{"ipv4":"46.101.197.175","ipv6":"2a03:b0c0:3:d0::ac:5001","port":443,"tcp_ports":[443,33445,3389],"public_key":"CD133B521159541FB1D326DE9850F5E56A6C724B5B8E5EB5CD8D950408E95707","maintainer":"clearmartin","location":"DE","status_udp":false,"status_tcp":true,"version":"2014101200","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"104.233.104.126","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"EDEE8F2E839A57820DE3DA4156D88350E53D4161447068A3457EE8F59F362414","maintainer":"wildermesser","location":"CA","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":0},{"ipv4":"195.93.190.6","ipv6":"2a01:d0:ffff:a8a::2","port":33445,"tcp_ports":[],"public_key":"FB4CE0DDEFEED45F26917053E5D24BDDA0FA0A3D83A672A9DA2375928B37023D","maintainer":"strngr","location":"UA","status_udp":false,"status_tcp":false,"version":"2016010100","motd":"tox node at strngr.name","last_ping":1516816803},{"ipv4":"193.124.186.205","ipv6":"2a02:f680:1:1100::542a","port":5228,"tcp_ports":[],"public_key":"9906D65F2A4751068A59D30505C5FC8AE1A95E0843AE9372EAFA3BAB6AC16C2C","maintainer":"Cactus","location":"RU","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":0},{"ipv4":"85.21.144.224","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"8F738BBC8FA9394670BCAB146C67A507B9907C8E564E28C2B59BEBB2FF68711B","maintainer":"himura","location":"RU","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":0},{"ipv4":"37.187.122.30","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"BEB71F97ED9C99C04B8489BB75579EB4DC6AB6F441B603D63533122F1858B51D","maintainer":"dolohow","location":"FR","status_udp":false,"status_tcp":false,"version":"2016010100","motd":"#stay frosty 8218DB335926393789859EDF2D79AC4CC805ADF73472D08165FEA51555502A58AE84FCE7C3D4","last_ping":1515853621},{"ipv4":"95.215.46.114","ipv6":"2a02:7aa0:1619::bdbd:17b8","port":33445,"tcp_ports":[],"public_key":"5823FB947FF24CF83DDFAC3F3BAA18F96EA2018B16CC08429CB97FA502F40C23","maintainer":"isotoxin","location":"SE","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":0},{"ipv4":"tox.dumalogiya.ru","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"2DAE6EB8C16131761A675D7C723F618FBA9D29DD8B4E0A39E7E3E8D7055EF113","maintainer":"mikhailnov","location":"RU","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":0}]} \ No newline at end of file +{"nodes":[{"ipv4":"127.0.0.1","ipv6":"-","port":33445,"public_key":"617DA0076546F9A801D06AAA2E20234DA6A1DDA90583FB02B59E3501CA84D061","status_udp":true,"status_tcp":true}]} \ No newline at end of file diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 6c19b26..f56f13a 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -50,6 +50,9 @@ class ContactsManager: 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 + # ----------------------------------------------------------------------------------------------------------------- # Work with active friend # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index 79c2cfa..cf4e815 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -39,7 +39,7 @@ class GroupChat(contact.Contact): self._tox.group_peer_get_public_key(self._number, peer_id)) self._peers.append(peer) - def get_peer(self, peer_id): + def get_peer_by_id(self, peer_id): peers = list(filter(lambda p: p.id == peer_id, self._peers)) return peers[0] diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index beb0853..c5be5cf 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -14,7 +14,7 @@ class GroupsService(tox_save.ToxSave): # ----------------------------------------------------------------------------------------------------------------- def create_new_gc(self, name, privacy_state): - group_number = self._tox.group_new(privacy_state, name) + group_number = self._tox.group_new(privacy_state, name.encode('utf-8')) self._add_new_group_by_number(group_number) def join_gc_by_id(self, chat_id, password): @@ -48,6 +48,14 @@ class GroupsService(tox_save.ToxSave): if util_ui.question(text.format(friend.name), util_ui.tr('Group invite')): self.join_gc_via_invite(invite_data, friend_number, None) + # ----------------------------------------------------------------------------------------------------------------- + # Group info methods + # ----------------------------------------------------------------------------------------------------------------- + + def update_group_info(self, group): + group.name = self._tox.group_get_name(group.number).encode('utf-8') + group.status_message = self._tox.group_get_topic(group.number).encode('utf-8') + # ----------------------------------------------------------------------------------------------------------------- # Private methods # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/messenger/messenger.py b/toxygen/messenger/messenger.py index 0a321f8..7dff4e2 100644 --- a/toxygen/messenger/messenger.py +++ b/toxygen/messenger/messenger.py @@ -41,16 +41,7 @@ class Messenger(tox_save.ToxSave): 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) - - if self._contacts_manager.is_friend_active(friend_number): # 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: - friend.inc_messages() - friend.append_message(text_message) - if not friend.visibility: - self._contacts_manager.update_filtration() + self._add_message(text_message, friend) def send_message(self): text = self._screen.messageEdit.toPlainText() @@ -135,6 +126,18 @@ class Messenger(tox_save.ToxSave): 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) + # ----------------------------------------------------------------------------------------------------------------- # Message receipts # ----------------------------------------------------------------------------------------------------------------- @@ -229,3 +232,14 @@ class Messenger(tox_save.ToxSave): 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() diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 7b0872d..1182466 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -388,10 +388,11 @@ def group_invite(groups_service): return wrapped -def group_self_join(contacts_provider): +def group_self_join(contacts_provider, 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) return wrapped @@ -407,7 +408,7 @@ def group_peer_join(contacts_provider): def group_peer_name(contacts_provider): def wrapped(tox, group_number, peer_id, name, length, user_data): group = contacts_provider.get_group_by_number(group_number) - peer = group.get_peer(peer_id) + peer = group.get_peer_by_id(peer_id) peer.name = str(name[:length]) return wrapped @@ -416,7 +417,7 @@ def group_peer_name(contacts_provider): def group_peer_status(contacts_provider): def wrapped(tox, group_number, peer_id, peer_status, user_data): group = contacts_provider.get_group_by_number(group_number) - peer = group.get_peer(peer_id) + peer = group.get_peer_by_id(peer_id) peer.status = peer_status return wrapped @@ -479,7 +480,7 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, # gc callbacks tox.callback_group_message(group_message(main_window, tray, tox, messenger, settings, profile), 0) tox.callback_group_invite(group_invite(groups_service), 0) - tox.callback_group_self_join(group_self_join(contacts_provider), 0) + tox.callback_group_self_join(group_self_join(contacts_provider, groups_service), 0) tox.callback_group_peer_join(group_peer_join(contacts_provider), 0) tox.callback_group_peer_name(group_peer_name(contacts_provider), 0) tox.callback_group_peer_status(group_peer_status(contacts_provider), 0) From b591ac13ba09e7fb409a21f668a3f25c2f43fb04 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 19 May 2018 19:38:54 +0300 Subject: [PATCH 051/138] utf-8 decoding moved from contacts --- toxygen/contacts/basecontact.py | 2 -- toxygen/contacts/contacts_manager.py | 1 - toxygen/contacts/profile.py | 2 +- toxygen/groups/groups_service.py | 4 ++-- toxygen/middleware/callbacks.py | 4 ++-- toxygen/ui/menu.py | 2 +- 6 files changed, 6 insertions(+), 9 deletions(-) diff --git a/toxygen/contacts/basecontact.py b/toxygen/contacts/basecontact.py index 988b0bc..0357809 100644 --- a/toxygen/contacts/basecontact.py +++ b/toxygen/contacts/basecontact.py @@ -39,7 +39,6 @@ class BaseContact: return self._name def set_name(self, value): - value = str(value, 'utf-8') if self._name != value: self._name = value self._widget.name.setText(self._name) @@ -61,7 +60,6 @@ class BaseContact: return self._status_message def set_status_message(self, value): - value = str(value, 'utf-8') if self._status_message != value: self._status_message = value self._widget.status_message.setText(self._status_message) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index f56f13a..c140b59 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -224,7 +224,6 @@ class ContactsManager: friend = self.get_friend_by_number(number) tmp = friend.name friend.set_name(name) - name = str(name, 'utf-8') if friend.name == name and tmp != name: # TODO: move to friend? message = util_ui.tr('User {} is now known as {}') diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index c775268..4e29147 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -49,7 +49,7 @@ class Profile(basecontact.BaseContact): def set_name(self, value): if self.name == value: return - super().set_name(value.encode('utf-8')) + super().set_name(value) self._tox.self_set_name(self._name.encode('utf-8')) def set_status_message(self, value): diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index c5be5cf..5b0dd67 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -53,8 +53,8 @@ class GroupsService(tox_save.ToxSave): # ----------------------------------------------------------------------------------------------------------------- def update_group_info(self, group): - group.name = self._tox.group_get_name(group.number).encode('utf-8') - group.status_message = self._tox.group_get_topic(group.number).encode('utf-8') + group.name = self._tox.group_get_name(group.number) + group.status_message = self._tox.group_get_topic(group.number) # ----------------------------------------------------------------------------------------------------------------- # Private methods diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 1182466..99f9db8 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -86,7 +86,7 @@ def friend_name(contacts_manager): Friend changed his name """ print('New name friend #' + str(friend_number)) - invoke_in_main_thread(contacts_manager.new_name, friend_number, name) + invoke_in_main_thread(contacts_manager.new_name, friend_number, str(name, 'utf-8')) return wrapped @@ -98,7 +98,7 @@ def friend_status_message(contacts_manager, messenger): and calls window repaint """ friend = contacts_manager.get_friend_by_number(friend_number) - invoke_in_main_thread(friend.set_status_message, status_message) + invoke_in_main_thread(friend.set_status_message, str(status_message, 'utf-8')) print('User #{} has new status'.format(friend_number)) invoke_in_main_thread(messenger.send_messages, friend_number) diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index d912619..e98405d 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -272,7 +272,7 @@ class ProfileSettings(CenteredWidget): def closeEvent(self, event): self._profile.set_name(self.nick.text()) - self._profile.set_status_message(self.status_message.text().encode('utf-8')) + self._profile.set_status_message(self.status_message.text()) self._profile.set_status(self.status.currentIndex()) From 6495aa99207ad3f955dde9e8a972c5301c65ce3c Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 19 May 2018 20:07:42 +0300 Subject: [PATCH 052/138] name changing fixes --- toxygen/contacts/contact.py | 5 ++++- toxygen/contacts/contacts_manager.py | 20 ++------------------ toxygen/messenger/messenger.py | 28 +++++++++++++++++----------- toxygen/middleware/callbacks.py | 19 ++++++++++++++++--- 4 files changed, 39 insertions(+), 33 deletions(-) diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index a142c61..d3861bc 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -118,7 +118,7 @@ class Contact(basecontact.BaseContact): """ :return list of unsent messages """ - messages = filter(lambda m: m.author.type == MESSAGE_AUTHOR['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) def get_unsent_messages_for_saving(self): @@ -248,6 +248,9 @@ class Contact(basecontact.BaseContact): def set_alias(self, alias): self._alias = bool(alias) + def has_alias(self): + return self._alias + # ----------------------------------------------------------------------------------------------------------------- # Visibility in friends' list # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index c140b59..94affd9 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -219,22 +219,6 @@ class ContactsManager: def is_active_online(self): return self._active_contact + 1 and self.get_curr_contact().status is not None - def new_name(self, number, name): - # TODO: move to somewhere else? - friend = self.get_friend_by_number(number) - tmp = friend.name - friend.set_name(name) - if friend.name == name and tmp != name: - # TODO: move to friend? - message = util_ui.tr('User {} is now known as {}') - # message = message.format(tmp, name) - # friend.append_message(InfoMessage(0, message, util.get_unix_time())) - # friend.actions = True - # if number == self.get_active_number(): - # self.create_message_item(message, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) - # self._messages.scrollToBottom() - # self.set_active(None) - # ----------------------------------------------------------------------------------------------------------------- # Work with friends (remove, block, set alias, get public key) # ----------------------------------------------------------------------------------------------------------------- @@ -251,7 +235,7 @@ class ContactsManager: if ok: aliases = self._settings['friends_aliases'] if text: - friend.name = bytes(text, 'utf-8') + friend.name = text try: index = list(map(lambda x: x[0], aliases)).index(friend.tox_id) aliases[index] = (friend.tox_id, text) @@ -259,7 +243,7 @@ class ContactsManager: aliases.append((friend.tox_id, text)) friend.set_alias(text) else: # use default name - friend.name = bytes(self._tox.friend_get_name(friend.number), 'utf-8') + 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) diff --git a/toxygen/messenger/messenger.py b/toxygen/messenger/messenger.py index 7dff4e2..af66f0b 100644 --- a/toxygen/messenger/messenger.py +++ b/toxygen/messenger/messenger.py @@ -19,14 +19,6 @@ class Messenger(tox_save.ToxSave): calls_manager.call_started_event.add_callback(self._on_call_started) calls_manager.call_finished_event.add_callback(self._on_call_finished) - # ----------------------------------------------------------------------------------------------------------------- - # Private methods - # ----------------------------------------------------------------------------------------------------------------- - - def _create_message_item(self, text_message): - # pixmap = self._contacts_manager.get_curr_contact().get_pixmap() - self._items_factory.create_message_item(text_message) - # ----------------------------------------------------------------------------------------------------------------- # Messaging - friends # ----------------------------------------------------------------------------------------------------------------- @@ -168,6 +160,18 @@ class Messenger(tox_save.ToxSave): 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) + friend.actions = True + self._add_info_message(friend.number, message) + # ----------------------------------------------------------------------------------------------------------------- # Private methods # ----------------------------------------------------------------------------------------------------------------- @@ -206,9 +210,7 @@ class Messenger(tox_save.ToxSave): 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(): - friend.append_message(InfoMessage(message, util.get_unix_time())) - if self._contacts_manager.is_active_a_friend(): - self._create_info_message_item(message) + self._add_info_message(friend.number, message) self._profile_name = new_name def _on_call_started(self, friend_number, audio, video, is_outgoing): @@ -243,3 +245,7 @@ class Messenger(tox_save.ToxSave): 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) diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 99f9db8..f5ad9da 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -80,13 +80,17 @@ def friend_connection_status(contacts_manager, profile, settings, plugin_loader, return wrapped -def friend_name(contacts_manager): +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)) - invoke_in_main_thread(contacts_manager.new_name, friend_number, str(name, 'utf-8')) + 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 @@ -423,6 +427,14 @@ def group_peer_status(contacts_provider): 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 + # ----------------------------------------------------------------------------------------------------------------- # Callbacks - initialization # ----------------------------------------------------------------------------------------------------------------- @@ -453,7 +465,7 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, tox.callback_friend_message(friend_message(messenger, contacts_manager, profile, settings, main_window, tray), 0) tox.callback_friend_connection_status(friend_connection_status(contacts_manager, profile, settings, plugin_loader, file_transfer_handler, messenger, calls_manager), 0) - tox.callback_friend_name(friend_name(contacts_manager), 0) + tox.callback_friend_name(friend_name(contacts_provider, messenger), 0) tox.callback_friend_status_message(friend_status_message(contacts_manager, messenger), 0) tox.callback_friend_request(friend_request(contacts_manager), 0) tox.callback_friend_typing(friend_typing(messenger), 0) @@ -484,3 +496,4 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, tox.callback_group_peer_join(group_peer_join(contacts_provider), 0) tox.callback_group_peer_name(group_peer_name(contacts_provider), 0) tox.callback_group_peer_status(group_peer_status(contacts_provider), 0) + tox.callback_group_topic(group_topic(contacts_provider), 0) From 206c5c4905d24776ce264ea715e0fbf32686caec Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 19 May 2018 21:04:40 +0300 Subject: [PATCH 053/138] unsent files fixes - part 1 --- .../file_transfers_messages_service.py | 14 ++++---- toxygen/messenger/messages.py | 32 ++++++++++++------- toxygen/ui/main_screen.py | 4 +-- toxygen/ui/messages_widgets.py | 15 +++------ 4 files changed, 36 insertions(+), 29 deletions(-) diff --git a/toxygen/file_transfers/file_transfers_messages_service.py b/toxygen/file_transfers/file_transfers_messages_service.py index 7f8b5f6..7cb90f5 100644 --- a/toxygen/file_transfers/file_transfers_messages_service.py +++ b/toxygen/file_transfers/file_transfers_messages_service.py @@ -16,7 +16,7 @@ class FileTransfersMessagesService: 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_active(friend.number): + if self._is_friend_active(friend.number): self._create_file_transfer_item(tm) self._messages.scrollToBottom() else: @@ -31,7 +31,7 @@ class FileTransfersMessagesService: 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_active(friend.number): + if self._is_friend_active(friend.number): self._create_file_transfer_item(tm) self._messages.scrollToBottom() @@ -40,16 +40,18 @@ class FileTransfersMessagesService: return tm def add_inline_message(self, transfer, index): - if self._is_active(transfer.friend_number): + if self._is_friend_active(transfer.friend_number): 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): - tm = UnsentFileMessage(file_path, data, util.get_unix_time()) + 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_active(friend.number): + if self._is_friend_active(friend.number): self._create_unsent_file_item(tm) self._messages.scrollToBottom() @@ -59,7 +61,7 @@ class FileTransfersMessagesService: # Private methods # ----------------------------------------------------------------------------------------------------------------- - def _is_active(self, friend_number): + def _is_friend_active(self, friend_number): if not self._contacts_manager.is_active_a_friend(): return False diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index 91a1a04..57ed9bb 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -1,4 +1,5 @@ from history.database import MESSAGE_AUTHOR +import os.path from ui.messages_widgets import * @@ -16,8 +17,18 @@ PAGE_SIZE = 42 class MessageAuthor: def __init__(self, author_name, author_type): - self.name = author_name - self.type = 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 + + type = property(get_type) class Message: @@ -139,7 +150,10 @@ class TransferMessage(Message): self._friend_number, self._file_number = friend_number, file_number def is_active(self, file_number): - return self._file_number == file_number and self._state not in (2, 3) + 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 @@ -178,10 +192,11 @@ class TransferMessage(Message): return FileTransferItem(self, *args) -class UnsentFileMessage(Message): +class UnsentFileMessage(TransferMessage): - def __init__(self, path, data, time): - super().__init__(MESSAGE_TYPE['FILE_TRANSFER'], 0, time) + 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): @@ -189,11 +204,6 @@ class UnsentFileMessage(Message): data = property(get_data) - def get_state(self): - return FILE_TRANSFER_STATE['UNSENT'] - - state = property(get_state) - def get_path(self): return self._path diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 9a4df47..8e19c5a 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -316,8 +316,8 @@ class MainWindow(QtWidgets.QMainWindow): def load(pos): if not pos: - friend = self._contacts_manager.get_curr_friend() - self._history_loader.load_history(friend) + 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) diff --git a/toxygen/ui/messages_widgets.py b/toxygen/ui/messages_widgets.py index c676783..8a46fd0 100644 --- a/toxygen/ui/messages_widgets.py +++ b/toxygen/ui/messages_widgets.py @@ -253,7 +253,8 @@ class FileTransferItem(QtWidgets.QListWidget): icon = QtGui.QIcon(pixmap) self.cancel.setIcon(icon) self.cancel.setIconSize(QtCore.QSize(30, 30)) - self.cancel.setVisible(transfer_message.state in ACTIVE_FILE_TRANSFERS) + self.cancel.setVisible(transfer_message.state in ACTIVE_FILE_TRANSFERS or + 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;}') @@ -268,6 +269,9 @@ class FileTransferItem(QtWidgets.QListWidget): elif transfer_message.state == FILE_TRANSFER_STATE['PAUSED_BY_USER']: # setup for continue self.accept_or_pause.setVisible(True) 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 self.accept_or_pause.setVisible(True) self.button_update('pause') @@ -382,17 +386,12 @@ class FileTransferItem(QtWidgets.QListWidget): self.state = state self.time_left.setVisible(True) - @staticmethod - def mark_as_sent(): - return False - class UnsentFileItem(FileTransferItem): def __init__(self, transfer_message, file_transfer_handler, settings, width, parent=None): super().__init__(transfer_message, file_transfer_handler, settings, width, parent) self._time = time - self.pb.setVisible(False) movie = QtGui.QMovie(util.join_path(util.get_images_directory(), 'spinner.gif')) self.time.setMovie(movie) movie.start() @@ -448,7 +447,3 @@ class InlineImageItem(QtWidgets.QScrollArea): if directory: fl = QtCore.QFile(directory + '/toxygen_inline_' + util.curr_time().replace(':', '_') + '.png') self._pixmap.save(fl, 'PNG') - - @staticmethod - def mark_as_sent(): - return False From 77bdabb993fccdbbc337f2b5015a68e94c53f9d9 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 19 May 2018 21:25:57 +0300 Subject: [PATCH 054/138] minor fixes - history --- toxygen/contacts/contact.py | 20 ++++++++++---------- toxygen/contacts/friend.py | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index d3861bc..d7fe35e 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -74,7 +74,7 @@ class Contact(basecontact.BaseContact): Get data to save in db :return: list of unsaved messages or [] """ - messages = list(filter(lambda x: x.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']), self._corr)) + messages = list(filter(lambda m: m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']), self._corr)) return messages[-self._unsaved_messages:] if self._unsaved_messages else [] def get_corr(self): @@ -125,8 +125,8 @@ class Contact(basecontact.BaseContact): """ :return list of unsent messages for saving """ - messages = filter(lambda x: x.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']) - and x.author.type == MESSAGE_AUTHOR['NOT_SENT'], self._corr) + messages = filter(lambda m: m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']) + and m.author.type == MESSAGE_AUTHOR['NOT_SENT'], self._corr) return list(messages) def mark_as_sent(self, tox_message_id): @@ -142,9 +142,9 @@ class Contact(basecontact.BaseContact): # ----------------------------------------------------------------------------------------------------------------- def delete_message(self, message_id): - elem = list(filter(lambda x: type(x) in (TextMessage, GroupChatMessage) and x.message_id == message_id, + elem = list(filter(lambda m: type(m) in (TextMessage, GroupChatMessage) and m.message_id == message_id, self._corr))[0] - tmp = list(filter(lambda x: x.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']), 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: self._unsaved_messages -= 1 self._corr.remove(elem) @@ -155,10 +155,10 @@ class Contact(basecontact.BaseContact): """ Delete old messages (reduces RAM usage if messages saving is not enabled) """ - def save_message(x): - if x.type == MESSAGE_TYPE['FILE_TRANSFER'] and (x.state not in ACTIVE_FILE_TRANSFERS): + def save_message(m): + if m.type == MESSAGE_TYPE['FILE_TRANSFER'] and (m.state not in ACTIVE_FILE_TRANSFERS): return True - return x.author.type == MESSAGE_AUTHOR['NOT_SENT'] + return m.author is not None and m.author.type == MESSAGE_AUTHOR['NOT_SENT'] old = filter(save_message, self._corr[:-SAVE_MESSAGES]) self._corr = list(old) + self._corr[-SAVE_MESSAGES:] @@ -213,7 +213,7 @@ class Contact(basecontact.BaseContact): if not self._search_index: return None for i in range(self._search_index + 1, 0): - if self._corr[i].get_type() > (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']): + if self._corr[i].type not in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']): continue message = self._corr[i].text if re.search(self._search_string, message, re.IGNORECASE) is not None: @@ -294,7 +294,7 @@ class Contact(basecontact.BaseContact): 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): diff --git a/toxygen/contacts/friend.py b/toxygen/contacts/friend.py index 9103463..5c8eabb 100644 --- a/toxygen/contacts/friend.py +++ b/toxygen/contacts/friend.py @@ -23,7 +23,7 @@ class Friend(contact.Contact): Update status of active transfer and load inline if needed """ try: - tr = list(filter(lambda x: x.message_id == before_message_id, self._corr))[0] + 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) @@ -32,11 +32,11 @@ class Friend(contact.Contact): pass def get_unsent_files(self): - messages = filter(lambda x: type(x) is UnsentFileMessage, self._corr) + messages = filter(lambda m: type(m) is UnsentFileMessage, self._corr) return list(messages) def clear_unsent_files(self): - self._corr = list(filter(lambda x: type(x) is not UnsentFileMessage, self._corr)) + self._corr = list(filter(lambda m: type(m) is not UnsentFileMessage, self._corr)) def remove_invalid_unsent_files(self): def is_valid(message): From f67de1ba91c003accf473bacca9ff9176813695a Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 19 May 2018 23:20:37 +0300 Subject: [PATCH 055/138] minor tray fixes --- toxygen/app.py | 2 +- toxygen/ui/tray.py | 29 +++++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index a4fa37b..568797a 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -328,7 +328,7 @@ class App: widgets_factory = WidgetsFactory(self._settings, profile, self._profile_manager, self._contacts_manager, self._file_transfer_handler, self._smiley_loader, self._plugin_loader, self._toxes, self._version, self._groups_service) - self._tray = tray.init_tray(profile, self._settings, self._ms) + self._tray = tray.init_tray(profile, self._settings, self._ms, self._toxes) self._ms.set_dependencies(widgets_factory, self._tray, self._contacts_manager, self._messenger, profile, self._plugin_loader, self._file_transfer_handler, history, self._calls_manager, self._groups_service) diff --git a/toxygen/ui/tray.py b/toxygen/ui/tray.py index 2003053..3bfc7f3 100644 --- a/toxygen/ui/tray.py +++ b/toxygen/ui/tray.py @@ -1,6 +1,7 @@ from PyQt5 import QtWidgets, QtGui, QtCore from utils.ui import tr from utils.util import * +from ui.password_screen import UnlockAppScreen import os.path @@ -24,16 +25,16 @@ class Menu(QtWidgets.QMenu): self._settings = settings self._profile = profile - def newStatus(self, status): + def new_status(self, status): if not self._settings.locked: - self._profile.Profile.get_instance().set_status(status) - self.aboutToShowHandler() + self._profile.set_status(status) + self.about_to_show_handler() self.hide() - def aboutToShowHandler(self): + def about_to_show_handler(self): status = self._profile.status act = self.act - if status is None or self._ettings.get_instance().locked: + if status is None or self._settings.locked: self.actions()[1].setVisible(False) else: self.actions()[1].setVisible(True) @@ -52,10 +53,9 @@ class Menu(QtWidgets.QMenu): self.act.actions()[2].setText(tr('Busy')) -def init_tray(profile, settings, main_screen): +def init_tray(profile, settings, main_screen, toxes): icon = os.path.join(get_images_directory(), 'icon.png') tray = SystemTrayIcon(QtGui.QIcon(icon)) - tray.setObjectName('tray') menu = Menu(settings, profile) show = menu.addAction(tr('Open Toxygen')) @@ -72,7 +72,8 @@ def init_tray(profile, settings, main_screen): def show_window(): def show(): if not main_screen.isActiveWindow(): - main_screen.setWindowState(main_screen.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) + main_screen.setWindowState( + main_screen.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) main_screen.activateWindow() main_screen.show() if not settings.locked: @@ -84,8 +85,8 @@ def init_tray(profile, settings, main_screen): settings.unlockScreen = False if not settings.unlockScreen: settings.unlockScreen = True - self.p = UnlockAppScreen(toxes.ToxES.get_instance(), correct_pass) - self.p.show() + show_window.screen = UnlockAppScreen(toxes, correct_pass) + show_window.screen.show() def tray_activated(reason): if reason == QtWidgets.QSystemTrayIcon.DoubleClick: @@ -98,10 +99,10 @@ def init_tray(profile, settings, main_screen): show.triggered.connect(show_window) exit.triggered.connect(close_app) - menu.aboutToShow.connect(lambda: menu.aboutToShowHandler()) - online.triggered.connect(lambda: menu.newStatus(0)) - away.triggered.connect(lambda: menu.newStatus(1)) - busy.triggered.connect(lambda: menu.newStatus(2)) + 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() From dcc3a3dcfafc8ae883a4dad09d556deabc41e98a Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 19 May 2018 23:59:39 +0300 Subject: [PATCH 056/138] group peers list - base commit --- toxygen/ui/main_screen.py | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 8e19c5a..44c343d 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -18,7 +18,8 @@ class MainWindow(QtWidgets.QMainWindow): self.setAcceptDrops(True) self._saved = False self._profile = None - self._file_transfer_handler = self._history_loader = self._groups_service = self._calls_manager = 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, @@ -36,6 +37,7 @@ class MainWindow(QtWidgets.QMainWindow): def show(self): super().show() + self._contacts_manager.update() if self._settings['show_welcome_screen']: self._modal_window = self._widget_factory.create_welcome_window() @@ -174,7 +176,6 @@ class MainWindow(QtWidgets.QMainWindow): 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(self._settings['font']) @@ -182,7 +183,6 @@ class MainWindow(QtWidgets.QMainWindow): 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))) @@ -212,7 +212,6 @@ class MainWindow(QtWidgets.QMainWindow): 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) @@ -238,20 +237,17 @@ class MainWindow(QtWidgets.QMainWindow): 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) @@ -266,7 +262,6 @@ class MainWindow(QtWidgets.QMainWindow): 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) @@ -282,6 +277,13 @@ class MainWindow(QtWidgets.QMainWindow): 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._show_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.update_call_state('call') self.typing = QtWidgets.QLabel(Form) self.typing.setGeometry(QtCore.QRect(500, 25, 50, 30)) @@ -401,7 +403,10 @@ class MainWindow(QtWidgets.QMainWindow): self.close() def resizeEvent(self, *args, **kwargs): - self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155) + if not self._should_show_group_peers_list: + self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155) + else: + self.messages.setGeometry(0, 0, self.width() - 450, self.height() - 155) self.friends_list.setGeometry(0, 0, 270, self.height() - 125) self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 10, 50, 50)) @@ -424,8 +429,7 @@ class MainWindow(QtWidgets.QMainWindow): 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) - clipboard = QtWidgets.QApplication.clipboard() - clipboard.setText(s) + 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: @@ -673,6 +677,8 @@ class MainWindow(QtWidgets.QMainWindow): def friend_click(self, index): num = index.row() self._contacts_manager.set_active(num) + self.groupMenuButton.setVisible(not self._contacts_manager.is_active_a_friend()) + self.resizeEvent() def mouseReleaseEvent(self, event): pos = self.connection_status.pos() @@ -682,10 +688,6 @@ class MainWindow(QtWidgets.QMainWindow): else: super().mouseReleaseEvent(event) - def show(self): - super().show() - self._contacts_manager.update() - def filtering(self): ind = self.online_contacts.currentIndex() d = {0: 0, 1: 1, 2: 2, 3: 4, 4: 1 | 4, 5: 2 | 4} @@ -701,3 +703,7 @@ class MainWindow(QtWidgets.QMainWindow): 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() + + def _show_gc_peers_list(self): + self._should_show_group_peers_list = not self._should_show_group_peers_list + self.resizeEvent() From 02af0f767159b7fd5be3a28732bd71c4d6121936 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 20 May 2018 13:33:56 +0300 Subject: [PATCH 057/138] broken peers list --- toxygen/app.py | 2 +- toxygen/contacts/group_chat.py | 12 ++- toxygen/groups/group_peer.py | 8 +- toxygen/groups/groups_service.py | 15 +++- toxygen/groups/peers_list.py | 99 +++++++++++++++++++++ toxygen/ui/group_peers_list.py | 33 +++++++ toxygen/ui/main_screen.py | 23 ++++- toxygen/wrapper/toxcore_enums_and_consts.py | 22 ++--- 8 files changed, 193 insertions(+), 21 deletions(-) create mode 100644 toxygen/groups/peers_list.py create mode 100644 toxygen/ui/group_peers_list.py diff --git a/toxygen/app.py b/toxygen/app.py index 568797a..ff86acb 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -324,7 +324,7 @@ class App: self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider, file_transfers_message_service, profile) messages_items_factory.set_file_transfers_handler(self._file_transfer_handler) - self._groups_service = GroupsService(self._tox, self._contacts_manager, self._contacts_provider) + self._groups_service = GroupsService(self._tox, self._contacts_manager, self._contacts_provider, self._ms) widgets_factory = WidgetsFactory(self._settings, profile, self._profile_manager, self._contacts_manager, self._file_transfer_handler, self._smiley_loader, self._plugin_loader, self._toxes, self._version, self._groups_service) diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index cf4e815..4d561cf 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -31,12 +31,13 @@ class GroupChat(contact.Contact): def get_self_name(self): return self._peers[0].name - def add_peer(self, peer_id): + 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)) + self._tox.group_peer_get_public_key(self._number, peer_id), + is_current_user) self._peers.append(peer) def get_peer_by_id(self, peer_id): @@ -44,6 +45,11 @@ class GroupChat(contact.Contact): return peers[0] + def get_peers(self): + return self._peers[:] + + peers = property(get_peers) + # ----------------------------------------------------------------------------------------------------------------- # Private methods # ----------------------------------------------------------------------------------------------------------------- @@ -54,4 +60,4 @@ class GroupChat(contact.Contact): def _add_self_to_gc(self): peer_id = self._tox.group_self_get_peer_id(self._number) - self.add_peer(peer_id) + self.add_peer(peer_id, True) diff --git a/toxygen/groups/group_peer.py b/toxygen/groups/group_peer.py index 6736185..d2d5aeb 100644 --- a/toxygen/groups/group_peer.py +++ b/toxygen/groups/group_peer.py @@ -2,12 +2,13 @@ class GroupChatPeer: - def __init__(self, peer_id, name, status, role, public_key): + def __init__(self, peer_id, name, status, role, public_key, is_current_user): 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 def get_id(self): return self._peer_id @@ -42,3 +43,8 @@ class GroupChatPeer: 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) diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index 5b0dd67..01c1f44 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -1,13 +1,15 @@ import common.tox_save as tox_save import utils.ui as util_ui +from groups.peers_list import PeersListGenerator class GroupsService(tox_save.ToxSave): - def __init__(self, tox, contacts_manager, contacts_provider): + def __init__(self, tox, contacts_manager, contacts_provider, main_screen): super().__init__(tox) self._contacts_manager = contacts_manager self._contacts_provider = contacts_provider + self._peers_list_widget = main_screen.peers_list # ----------------------------------------------------------------------------------------------------------------- # Groups creation @@ -56,6 +58,17 @@ class GroupsService(tox_save.ToxSave): group.name = self._tox.group_get_name(group.number) group.status_message = self._tox.group_get_topic(group.number) + # ----------------------------------------------------------------------------------------------------------------- + # Peers list + # ----------------------------------------------------------------------------------------------------------------- + + def generate_peers_list(self): + 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): + pass + # ----------------------------------------------------------------------------------------------------------------- # Private methods # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/groups/peers_list.py b/toxygen/groups/peers_list.py new file mode 100644 index 0000000..e1db064 --- /dev/null +++ b/toxygen/groups/peers_list.py @@ -0,0 +1,99 @@ +from PyQt5 import QtWidgets, QtCore +from ui.group_peers_list import PeerItem, PeerTypeItem +import utils.ui as util_ui +from wrapper.toxcore_enums_and_consts 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, parent): + parent.clear() + + for i in range(self._index): + if i in self._peers: + peer = self._peers[i] + self._add_peer_item(peer, parent) + else: + title = self._titles[i] + self._add_peer_type_item(title, parent) + + return parent + + def _add_peer_item(self, peer, parent): + item = PeerItem(peer, self._handler, parent.width()) + self._add_item(parent, item) + + def _add_peer_type_item(self, text, parent): + item = PeerTypeItem(text, parent.width()) + self._add_item(parent, item) + + @staticmethod + def _add_item(parent, item): + elem = QtWidgets.QListWidgetItem() + 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, parent, 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)) + .with_title(admin_title) + .with_peers(admins) + .with_title(moderators_title) + .with_peers(moderators) + .with_title(users_title) + .with_peers(users) + .with_title(observers_title) + .with_peers(observers) + ) + + return builder.build(parent) diff --git a/toxygen/ui/group_peers_list.py b/toxygen/ui/group_peers_list.py new file mode 100644 index 0000000..a648c04 --- /dev/null +++ b/toxygen/ui/group_peers_list.py @@ -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(0, 0, width, 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(0, 0, width, 34) + self.nameLabel.setText(text) diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 44c343d..81c4f15 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -279,7 +279,7 @@ class MainWindow(QtWidgets.QMainWindow): 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._show_gc_peers_list) + 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) @@ -325,6 +325,13 @@ class MainWindow(QtWidgets.QMainWindow): 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) + def initUI(self): self.setMinimumSize(920, 500) s = self._settings @@ -403,14 +410,18 @@ class MainWindow(QtWidgets.QMainWindow): self.close() def resizeEvent(self, *args, **kwargs): + width = self.width() - 270 if not self._should_show_group_peers_list: - self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155) + self.messages.setGeometry(0, 0, width, self.height() - 155) + self.peers_list.setGeometry(0, 0, 0, 0) else: - self.messages.setGeometry(0, 0, self.width() - 450, self.height() - 155) + 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) 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.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)) @@ -678,6 +689,8 @@ class MainWindow(QtWidgets.QMainWindow): num = index.row() self._contacts_manager.set_active(num) self.groupMenuButton.setVisible(not self._contacts_manager.is_active_a_friend()) + if self._should_show_group_peers_list: + self._toggle_gc_peers_list() self.resizeEvent() def mouseReleaseEvent(self, event): @@ -704,6 +717,8 @@ class MainWindow(QtWidgets.QMainWindow): self.messages.setGeometry(x, self.messages.y(), self.messages.width(), self.messages.height() - 40) self.search_field.show() - def _show_gc_peers_list(self): + def _toggle_gc_peers_list(self): self._should_show_group_peers_list = not self._should_show_group_peers_list + if self._should_show_group_peers_list: + self._groups_service.generate_peers_list() self.resizeEvent() diff --git a/toxygen/wrapper/toxcore_enums_and_consts.py b/toxygen/wrapper/toxcore_enums_and_consts.py index 6942d3a..4d09338 100644 --- a/toxygen/wrapper/toxcore_enums_and_consts.py +++ b/toxygen/wrapper/toxcore_enums_and_consts.py @@ -198,7 +198,7 @@ TOX_GROUP_PRIVACY_STATE = { # and peer ID's (but not Tox ID's) is visible to anyone with access to a node # storing a DHT entry for the given group. # - 'TOX_GROUP_PRIVACY_STATE_PUBLIC': 0, + 'PUBLIC': 0, # # The group is considered to be private. The only way to join the group is by having @@ -208,7 +208,7 @@ TOX_GROUP_PRIVACY_STATE = { # the DHT is not used for any purpose at all. If a public group is set to private, # all DHT information related to the group will expire shortly. # - 'TOX_GROUP_PRIVACY_STATE_PRIVATE': 1 + 'PRIVATE': 1 } TOX_GROUP_ROLE = { @@ -217,23 +217,23 @@ TOX_GROUP_ROLE = { # May kick and ban all other peers as well as set their role to anything (except founder). # Founders may also set the group password, toggle the privacy state, and set the peer limit. # - 'TOX_GROUP_ROLE_FOUNDER': 0, + 'FOUNDER': 0, # # May kick, ban and set the user and observer roles for peers below this role. # May also set the group topic. # - 'TOX_GROUP_ROLE_MODERATOR': 1, + 'MODERATOR': 1, # # May communicate with other peers normally. # - 'TOX_GROUP_ROLE_USER': 2, + 'USER': 2, # # May observe the group and ignore peers; may not communicate with other peers or with the group. # - 'TOX_GROUP_ROLE_OBSERVER': 3 + 'OBSERVER': 3 } TOX_ERR_GROUP_NEW = { @@ -870,27 +870,27 @@ TOX_GROUP_MOD_EVENT = { # # A peer has been kicked from the group. # - 'TOX_GROUP_MOD_EVENT_KICK': 0, + 'KICK': 0, # # A peer has been banned from the group. # - 'TOX_GROUP_MOD_EVENT_BAN': 1, + 'BAN': 1, # # A peer as been given the observer role. # - 'TOX_GROUP_MOD_EVENT_OBSERVER': 2, + 'OBSERVER': 2, # # A peer has been given the user role. # - 'TOX_GROUP_MOD_EVENT_USER': 3, + 'USER': 3, # # A peer has been given the moderator role. # - 'TOX_GROUP_MOD_EVENT_MODERATOR': 4, + 'MODERATOR': 4, } TOX_ERR_GROUP_BAN_QUERY = { From b8fa8df41a4091d6ecab0bf29ff6646a53c249a5 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 20 May 2018 15:57:08 +0300 Subject: [PATCH 058/138] various fixes - peers list, resize event, tox instance recreation --- toxygen/app.py | 8 ++++-- toxygen/av/calls.py | 3 +++ toxygen/av/calls_manager.py | 3 +++ toxygen/contacts/contacts_manager.py | 15 ++++++++--- toxygen/contacts/friend_factory.py | 6 +++-- toxygen/contacts/group_chat.py | 5 ++-- toxygen/contacts/group_factory.py | 6 +++-- toxygen/contacts/profile.py | 4 +-- .../file_transfers/file_transfers_handler.py | 5 ++-- toxygen/groups/groups_service.py | 8 ++++++ toxygen/groups/peers_list.py | 8 +++--- toxygen/plugin_support/plugin_support.py | 10 +++---- toxygen/ui/group_peers_list.py | 4 +-- toxygen/ui/main_screen.py | 21 ++++++++++----- toxygen/ui/main_screen_widgets.py | 27 ++++++++++--------- toxygen/ui/menu.py | 4 +-- toxygen/ui/widgets_factory.py | 8 ++++-- toxygen/user_data/settings.py | 3 ++- 18 files changed, 97 insertions(+), 51 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index ff86acb..2f18045 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -291,7 +291,11 @@ class App: self._tox = self._create_tox(data) self._start_threads() - # TODO: foreach in list of tox savers set_tox + 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] + for tox_saver in tox_savers: + tox_saver.set_tox(self._tox) + self._calls_manager.set_toxav(self._tox.AV) return self._tox @@ -327,7 +331,7 @@ class App: self._groups_service = GroupsService(self._tox, self._contacts_manager, self._contacts_provider, self._ms) widgets_factory = WidgetsFactory(self._settings, profile, self._profile_manager, self._contacts_manager, self._file_transfer_handler, self._smiley_loader, self._plugin_loader, - self._toxes, self._version, self._groups_service) + self._toxes, self._version, self._groups_service, history) self._tray = tray.init_tray(profile, self._settings, self._ms, self._toxes) self._ms.set_dependencies(widgets_factory, self._tray, self._contacts_manager, self._messenger, profile, self._plugin_loader, self._file_transfer_handler, history, self._calls_manager, diff --git a/toxygen/av/calls.py b/toxygen/av/calls.py index 6f3eccf..2673f1a 100644 --- a/toxygen/av/calls.py +++ b/toxygen/av/calls.py @@ -36,6 +36,9 @@ class AV: self._video_width = 640 self._video_height = 480 + def set_toxav(self, toxav): + self._toxav = toxav + def stop(self): self._running = False self.stop_audio_thread() diff --git a/toxygen/av/calls_manager.py b/toxygen/av/calls_manager.py index 5e84573..eefd03a 100644 --- a/toxygen/av/calls_manager.py +++ b/toxygen/av/calls_manager.py @@ -18,6 +18,9 @@ class CallsManager: 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 # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 94affd9..d0f0980 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -1,16 +1,17 @@ from contacts.friend import Friend from contacts.group_chat import GroupChat from messenger.messages import * +from common.tox_save import ToxSave -class ContactsManager: +class ContactsManager(ToxSave): """ Represents contacts list. """ def __init__(self, tox, settings, screen, profile_manager, contact_provider, history, tox_dns, messages_items_factory): - self._tox = tox + super().__init__(tox) self._settings = settings self._screen = screen self._profile_manager = profile_manager @@ -19,6 +20,7 @@ class ContactsManager: 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 = '' self._friend_item_height = 40 if settings['compact_mode'] else 70 @@ -115,13 +117,18 @@ class ContactsManager: # 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_friend = property(get_active, set_active) + 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 set_active_by_number_and_type(self, number, is_friend): # TODO: by id for i in range(len(self._contacts)): diff --git a/toxygen/contacts/friend_factory.py b/toxygen/contacts/friend_factory.py index a9b0477..8ebafd6 100644 --- a/toxygen/contacts/friend_factory.py +++ b/toxygen/contacts/friend_factory.py @@ -1,11 +1,13 @@ from contacts.friend import Friend +from common.tox_save import ToxSave -class FriendFactory: +class FriendFactory(ToxSave): def __init__(self, profile_manager, settings, tox, db, items_factory): + super().__init__(tox) self._profile_manager = profile_manager - self._settings, self._tox = settings, tox + self._settings = settings self._db = db self._items_factory = items_factory diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index 4d561cf..1f964c6 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -3,13 +3,14 @@ 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 -class GroupChat(contact.Contact): +class GroupChat(contact.Contact, ToxSave): def __init__(self, tox, 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._tox = tox + ToxSave.__init__(self, tox) self.set_status(constants.TOX_USER_STATUS['NONE']) self._peers = [] self._add_self_to_gc() diff --git a/toxygen/contacts/group_factory.py b/toxygen/contacts/group_factory.py index 089fc09..8db3e9a 100644 --- a/toxygen/contacts/group_factory.py +++ b/toxygen/contacts/group_factory.py @@ -1,11 +1,13 @@ from contacts.group_chat import GroupChat +from common.tox_save import ToxSave -class GroupFactory: +class GroupFactory(ToxSave): def __init__(self, profile_manager, settings, tox, db, items_factory): + super().__init__(tox) self._profile_manager = profile_manager - self._settings, self._tox = settings, tox + self._settings = settings self._db = db self._items_factory = items_factory diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 4e29147..469b8fe 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -67,7 +67,7 @@ class Profile(basecontact.BaseContact): # Reset # ----------------------------------------------------------------------------------------------------------------- - def _restart(self): + def restart(self): """ Recreate tox instance """ @@ -80,6 +80,6 @@ class Profile(basecontact.BaseContact): contacts = self._contacts_provider.get_all() if self.status is None or all(list(map(lambda x: x.status is None, contacts))) and len(contacts): self._waiting_for_reconnection = True - self._restart() + self.restart() self._timer = threading.Timer(50, self._reconnect) self._timer.start() diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index 1f5c9d0..64a2bfe 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -1,12 +1,13 @@ from messenger.messages import * from ui.contact_items import * import utils.util as util +from common.tox_save import ToxSave -class FileTransfersHandler: +class FileTransfersHandler(ToxSave): def __init__(self, tox, settings, contact_provider, file_transfers_message_service, profile): - self._tox = tox + super().__init__(tox) self._settings = settings self._contact_provider = contact_provider self._file_transfers_message_service = file_transfers_message_service diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index 01c1f44..c9960fc 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -11,6 +11,11 @@ class GroupsService(tox_save.ToxSave): self._contacts_provider = contacts_provider self._peers_list_widget = main_screen.peers_list + def set_tox(self, tox): + super().set_tox(tox) + for group in self._get_all_groups(): + group.set_tox(tox) + # ----------------------------------------------------------------------------------------------------------------- # Groups creation # ----------------------------------------------------------------------------------------------------------------- @@ -81,3 +86,6 @@ class GroupsService(tox_save.ToxSave): def _get_friend(self, friend_number): return self._contacts_provider.get_friend_by_number(friend_number) + + def _get_all_groups(self): + return self._contacts_provider.get_all_groups() diff --git a/toxygen/groups/peers_list.py b/toxygen/groups/peers_list.py index e1db064..21995b4 100644 --- a/toxygen/groups/peers_list.py +++ b/toxygen/groups/peers_list.py @@ -2,6 +2,8 @@ from PyQt5 import QtWidgets, QtCore from ui.group_peers_list import PeerItem, PeerTypeItem import utils.ui as util_ui from wrapper.toxcore_enums_and_consts import * +from ui.widgets import * + # ----------------------------------------------------------------------------------------------------------------- # Builder @@ -47,16 +49,16 @@ class PeerListBuilder: return parent def _add_peer_item(self, peer, parent): - item = PeerItem(peer, self._handler, parent.width()) + 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()) + item = PeerTypeItem(text, parent.width(), parent) self._add_item(parent, item) @staticmethod def _add_item(parent, item): - elem = QtWidgets.QListWidgetItem() + elem = QtWidgets.QListWidgetItem(parent) elem.setSizeHint(QtCore.QSize(parent.width(), item.height())) parent.addItem(elem) parent.setItemWidget(elem, item) diff --git a/toxygen/plugin_support/plugin_support.py b/toxygen/plugin_support/plugin_support.py index 5871afa..e8c9a56 100644 --- a/toxygen/plugin_support/plugin_support.py +++ b/toxygen/plugin_support/plugin_support.py @@ -1,28 +1,26 @@ import utils.util as util -from contacts import profile import os import importlib import inspect import plugins.plugin_super_class as pl -from user_data import toxes import sys +from common.tox_save import ToxSave -class PluginLoader(): +class PluginLoader(ToxSave): def __init__(self, tox, toxes, profile, settings): - super().__init__() + super().__init__(tox) self._profile = profile self._settings = settings self._plugins = {} # dict. key - plugin unique short name, value - tuple (plugin instance, is active) - self._tox = tox self._toxes = toxes def set_tox(self, tox): """ New tox instance """ - self._tox = tox + super().set_tox(tox) for value in self._plugins.values(): value[0].set_tox(tox) diff --git a/toxygen/ui/group_peers_list.py b/toxygen/ui/group_peers_list.py index a648c04..9d2632d 100644 --- a/toxygen/ui/group_peers_list.py +++ b/toxygen/ui/group_peers_list.py @@ -8,7 +8,7 @@ class PeerItem(QtWidgets.QWidget): super().__init__(parent) self.resize(QtCore.QSize(width, 34)) self.nameLabel = DataLabel(self) - self.nameLabel.setGeometry(0, 0, width, 34) + self.nameLabel.setGeometry(5, 0, width - 5, 34) name = peer.name if peer.is_current_user: name += util_ui.tr(' (You)') @@ -29,5 +29,5 @@ class PeerTypeItem(QtWidgets.QWidget): super().__init__(parent) self.resize(QtCore.QSize(width, 34)) self.nameLabel = DataLabel(self) - self.nameLabel.setGeometry(0, 0, width, 34) + self.nameLabel.setGeometry(5, 0, width - 5, 34) self.nameLabel.setText(text) diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 81c4f15..c32d0fa 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -33,6 +33,7 @@ class MainWindow(QtWidgets.QMainWindow): self._history_loader = history_loader self._calls_manager = calls_manager self._groups_service = groups_service + self._contacts_manager.active_contact_changed.add_callback(self._new_contact_selected) self.messageEdit.set_messenger(messenger) def show(self): @@ -284,6 +285,7 @@ class MainWindow(QtWidgets.QMainWindow): 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)) @@ -331,6 +333,8 @@ class MainWindow(QtWidgets.QMainWindow): 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) @@ -431,6 +435,7 @@ class MainWindow(QtWidgets.QMainWindow): 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._contacts_manager.update() def keyPressEvent(self, event): key, modifiers = event.key(), event.modifiers() @@ -564,7 +569,7 @@ class MainWindow(QtWidgets.QMainWindow): def send_smiley(self): self.menu.hide() - if self._contacts_manager.active_friend + 1: + if self._contacts_manager.get_curr_contact() is not None: self.smiley = self._widget_factory.create_smiley_window(self) self.smiley.setGeometry(QtCore.QRect(self.x() if self._settings['mirror_mode'] else 270 + self.x(), self.y() + self.height() - 200, @@ -687,11 +692,8 @@ class MainWindow(QtWidgets.QMainWindow): def friend_click(self, index): num = index.row() - self._contacts_manager.set_active(num) + self._contacts_manager.active_contact = num self.groupMenuButton.setVisible(not self._contacts_manager.is_active_a_friend()) - if self._should_show_group_peers_list: - self._toggle_gc_peers_list() - self.resizeEvent() def mouseReleaseEvent(self, event): pos = self.connection_status.pos() @@ -711,14 +713,21 @@ class MainWindow(QtWidgets.QMainWindow): return if self._contacts_manager.get_curr_friend() is None: return - self.search_field = SearchScreen(self.messages, self.messages.width(), self.messages.parent()) + 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, contact): + if self._should_show_group_peers_list: + self._toggle_gc_peers_list() self.resizeEvent() diff --git a/toxygen/ui/main_screen_widgets.py b/toxygen/ui/main_screen_widgets.py index dfd5ea7..479575e 100644 --- a/toxygen/ui/main_screen_widgets.py +++ b/toxygen/ui/main_screen_widgets.py @@ -374,8 +374,10 @@ class ClickableLabel(QtWidgets.QLabel): class SearchScreen(QtWidgets.QWidget): - def __init__(self, messages, width, *args): + def __init__(self, contacts_manager, history_loader, messages, width, *args): super().__init__(*args) + self._contacts_manager = contacts_manager + self._history_loader = history_loader self.setMaximumSize(width, 40) self.setMinimumSize(width, 40) self._messages = messages @@ -426,24 +428,24 @@ class SearchScreen(QtWidgets.QWidget): self.search_text.setFocus() def search(self): - Profile.get_instance().update() + self._contacts_manager.update() text = self.search_text.text() - friend = Profile.get_instance().get_curr_friend() - if text and friend and util.is_re_valid(text): - index = friend.search_string(text) + contact = self._contacts_manager.get_curr_contact() + if text and contact and util.is_re_valid(text): + index = contact.search_string(text) self.load_messages(index) def prev(self): - friend = Profile.get_instance().get_curr_friend() - if friend is not None: - index = friend.search_prev() + contact = self._contacts_manager.get_curr_contact() + if contact is not None: + index = contact.search_prev() self.load_messages(index) def next(self): - friend = Profile.get_instance().get_curr_friend() + contact = self._contacts_manager.get_curr_contact() text = self.search_text.text() - if friend is not None: - index = friend.search_next() + if contact is not None: + index = contact.search_next() if index is not None: count = self._messages.count() index += count @@ -456,10 +458,9 @@ class SearchScreen(QtWidgets.QWidget): def load_messages(self, index): text = self.search_text.text() if index is not None: - profile = Profile.get_instance() count = self._messages.count() while count + index < 0: - profile.load_history() + self._history_loader.load_history() count = self._messages.count() index += count item = self._messages.item(index) diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index e98405d..2f55bb5 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -281,7 +281,7 @@ class NetworkSettings(CenteredWidget): def __init__(self, settings, reset): super().__init__() self._settings = settings - self.reset = reset + self._reset = reset self.initUI() self.center() @@ -360,7 +360,7 @@ class NetworkSettings(CenteredWidget): self._settings['download_nodes_list'] = self.nodes.isChecked() self._settings.save() # recreate tox instance - self._profile.reset() + self._reset() self.close() except Exception as ex: log('Exception in restart: ' + str(ex)) diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py index 66aca2c..4c4f1c6 100644 --- a/toxygen/ui/widgets_factory.py +++ b/toxygen/ui/widgets_factory.py @@ -6,7 +6,7 @@ from ui.groups_widgets import * class WidgetsFactory: def __init__(self, settings, profile, profile_manager, contacts_manager, file_transfer_handler, smiley_loader, - plugin_loader, toxes, version, groups_service): + plugin_loader, toxes, version, groups_service, history): self._settings = settings self._profile = profile self._profile_manager = profile_manager @@ -17,6 +17,7 @@ class WidgetsFactory: self._toxes = toxes self._version = version self._groups_service = groups_service + self._history = history def create_screenshot_window(self, *args): return ScreenShotWindow(self._file_transfer_handler, self._contacts_manager, *args) @@ -31,7 +32,7 @@ class WidgetsFactory: return ProfileSettings(self._profile, self._profile_manager, self._settings, self._toxes) def create_network_settings_window(self): - return NetworkSettings(self._settings, self._profile.reset) + return NetworkSettings(self._settings, self._profile.restart) def create_audio_settings_window(self): return AudioSettings(self._settings) @@ -71,3 +72,6 @@ class WidgetsFactory: def create_join_group_screen_window(self): return JoinGroupScreen(self._groups_service) + + def create_search_screen(self, messages): + return SearchScreen(self._contacts_manager, self._history, messages, messages.parent()) diff --git a/toxygen/user_data/settings.py b/toxygen/user_data/settings.py index 1d7329e..76a37c0 100644 --- a/toxygen/user_data/settings.py +++ b/toxygen/user_data/settings.py @@ -146,7 +146,8 @@ class Settings(dict): 'font': 'Times New Roman', 'update': 1, 'group_notifications': True, - 'download_nodes_list': False + 'download_nodes_list': False, + 'notify_all_gc': False } @staticmethod From c6b67452ed3606ffb665c260e07db73b4b10607a Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 20 May 2018 17:22:44 +0300 Subject: [PATCH 059/138] peers - more callback and peers list refactoring --- toxygen/contacts/contacts_manager.py | 3 ++ toxygen/contacts/group_chat.py | 4 +++ toxygen/groups/groups_service.py | 2 ++ toxygen/groups/peers_list.py | 43 +++++++++++++++------------- toxygen/middleware/callbacks.py | 27 +++++++++++++---- toxygen/ui/main_screen.py | 1 - 6 files changed, 53 insertions(+), 27 deletions(-) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index d0f0980..02028d7 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -144,6 +144,9 @@ class ContactsManager(ToxSave): 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 + # ----------------------------------------------------------------------------------------------------------------- # Filtration # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index 1f964c6..b7429d4 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -41,6 +41,10 @@ class GroupChat(contact.Contact, ToxSave): is_current_user) self._peers.append(peer) + def remove_peer(self, peer_id): + 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)) diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index c9960fc..9b4287d 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -68,6 +68,8 @@ class GroupsService(tox_save.ToxSave): # ----------------------------------------------------------------------------------------------------------------- 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) diff --git a/toxygen/groups/peers_list.py b/toxygen/groups/peers_list.py index 21995b4..17495f5 100644 --- a/toxygen/groups/peers_list.py +++ b/toxygen/groups/peers_list.py @@ -1,6 +1,4 @@ -from PyQt5 import QtWidgets, QtCore from ui.group_peers_list import PeerItem, PeerTypeItem -import utils.ui as util_ui from wrapper.toxcore_enums_and_consts import * from ui.widgets import * @@ -35,18 +33,16 @@ class PeerListBuilder: return self - def build(self, parent): - parent.clear() + 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, parent) + self._add_peer_item(peer, list_widget) else: title = self._titles[i] - self._add_peer_type_item(title, parent) - - return parent + self._add_peer_type_item(title, list_widget) def _add_peer_item(self, peer, parent): item = PeerItem(peer, self._handler, parent.width(), parent) @@ -75,7 +71,7 @@ class PeerListBuilder: class PeersListGenerator: @staticmethod - def generate(peers_list, groups_service, parent, chat_id): + 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') @@ -87,15 +83,22 @@ class PeersListGenerator: 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)) - .with_title(admin_title) - .with_peers(admins) - .with_title(moderators_title) - .with_peers(moderators) - .with_title(users_title) - .with_peers(users) - .with_title(observers_title) - .with_peers(observers) - ) + .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)) - return builder.build(parent) + builder.build(list_widget) diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index f5ad9da..f7a9531 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -401,28 +401,40 @@ def group_self_join(contacts_provider, groups_service): return wrapped -def group_peer_join(contacts_provider): +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) return wrapped -def group_peer_name(contacts_provider): +def group_peer_exit(contacts_provider, groups_service): + 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]) + invoke_in_main_thread(groups_service.generate_peers_list) return wrapped -def group_peer_status(contacts_provider): +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 @@ -456,6 +468,8 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, :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), 0) @@ -493,7 +507,8 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, tox.callback_group_message(group_message(main_window, tray, tox, messenger, settings, profile), 0) tox.callback_group_invite(group_invite(groups_service), 0) tox.callback_group_self_join(group_self_join(contacts_provider, groups_service), 0) - tox.callback_group_peer_join(group_peer_join(contacts_provider), 0) - tox.callback_group_peer_name(group_peer_name(contacts_provider), 0) - tox.callback_group_peer_status(group_peer_status(contacts_provider), 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), 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) diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index c32d0fa..d39cc91 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -435,7 +435,6 @@ class MainWindow(QtWidgets.QMainWindow): 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._contacts_manager.update() def keyPressEvent(self, event): key, modifiers = event.key(), event.modifiers() From 0a9939f33b8aca2f4fbc7b420a32e8450fa3675f Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 23 May 2018 21:23:51 +0300 Subject: [PATCH 060/138] Tests cleanup --- .travis.yml | 3 +- tests/tests.py | 165 +------------------------------------------------ 2 files changed, 4 insertions(+), 164 deletions(-) diff --git a/.travis.yml b/.travis.yml index cfabadd..8de5702 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,7 @@ install: - pip install pyqt5 - pip install pyaudio - pip install opencv-python + - pip install pydenticon before_script: # Opus - wget http://downloads.xiph.org/releases/opus/opus-1.0.3.tar.gz @@ -37,7 +38,7 @@ before_script: - sudo ldconfig - cd .. # Toxcore - - git clone https://github.com/irungentoo/toxcore.git + - git clone https://github.com/ingvar1995/toxcore.git --branch=new_gc - cd toxcore - autoreconf -if - ./configure diff --git a/tests/tests.py b/tests/tests.py index 442a677..130f56d 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,14 +1,7 @@ -from contacts.profile import * -from network.tox_dns import tox_dns -from db.database import History -from toxygen.smileys import SmileyLoader -from messenger.messages import * -import user_data.toxes as encr -import toxygen.utils as util -import time +from middleware.tox_factory import * -# TODO: fic +# TODO: add new tests class TestTox: @@ -23,157 +16,3 @@ class TestTox: tox = tox_factory(data) assert tox.self_get_name() == str(name, 'utf-8') assert tox.self_get_status_message() == str(status_message, 'utf-8') - - -class TestProfileManager: - - def test_creation(self): - file_name, path = 'test.tox', os.path.dirname(os.path.realpath(__file__)) + '/' - data = b'test' - with open(path + file_name, 'wb') as fl: - fl.write(data) - ph = ProfileManager(path, file_name[:4]) - assert ProfileManager.get_path() == path - assert ph.open_profile() == data - assert os.path.exists(path + 'avatars/') - - -class TestDNS: - - def test_dns(self): - Settings._instance = Settings.get_default_settings() - bot_id = '56A1ADE4B65B86BCD51CC73E2CD4E542179F47959FE3E0E21B4B0ACDADE51855D34D34D37CB5' - tox_id = tox_dns('groupbot@toxme.io') - assert tox_id == bot_id - - def test_dns2(self): - Settings._instance = Settings.get_default_settings() - bot_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6' - tox_id = tox_dns('echobot@toxme.io') - assert tox_id == bot_id - - -class TestEncryption: - - def test_encr_decr(self): - tox = tox_factory() - data = tox.get_savedata() - lib = encr.ToxES() - for password in ('easypassword', 'njvnFjfn7vaGGV6', 'toxygen'): - lib.set_password(password) - copy_data = data[:] - new_data = lib.pass_encrypt(data) - assert lib.is_data_encrypted(new_data) - new_data = lib.pass_decrypt(new_data) - assert copy_data == new_data - - -class TestSmileys: - - def test_loading(self): - settings = {'smiley_pack': 'default', 'smileys': True} - sm = SmileyLoader(settings) - assert sm.get_smileys_path() is not None - l = sm.get_packs_list() - assert len(l) == 4 - - -def create_singletons(): - folder = util.curr_directory() + '/abc' - Settings._instance = Settings.get_default_settings() - if not os.path.exists(folder): - os.makedirs(folder) - ProfileManager(folder, 'test') - - -def create_friend(name, status_message, number, tox_id): - friend = Friend(None, number, name, status_message, None, tox_id) - return friend - - -def create_random_friend(): - name, status_message, number = 'Friend', 'I am friend!', 0 - tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6' - friend = create_friend(name, status_message, number, tox_id) - return friend - - -class TestFriend: - - def test_friend_creation(self): - create_singletons() - name, status_message, number = 'Friend', 'I am friend!', 0 - tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6' - friend = create_friend(name, status_message, number, tox_id) - assert friend.name == name - assert friend.tox_id == tox_id - assert friend.status_message == status_message - assert friend.number == number - - def test_friend_corr(self): - create_singletons() - friend = create_random_friend() - t = time.time() - friend.append_message(InfoMessage('Info message', t)) - friend.append_message(TextMessage('Hello! It is test!', MESSAGE_OWNER['ME'], t + 0.001, 0)) - friend.append_message(TextMessage('Hello!', MESSAGE_OWNER['FRIEND'], t + 0.002, 0)) - assert friend.get_last_message_text() == 'Hello! It is test!' - assert len(friend.get_corr()) == 3 - assert len(friend.get_corr_for_saving()) == 2 - friend.append_message(TextMessage('Not sent', MESSAGE_OWNER['NOT_SENT'], t + 0.002, 0)) - arr = friend.get_unsent_messages_for_saving() - assert len(arr) == 1 - assert arr[0][0] == 'Not sent' - tm = TransferMessage(MESSAGE_OWNER['FRIEND'], - time.time(), - FILE_TRANSFER_STATE['RUNNING'], - 100, 'file_name', friend.number, 0) - friend.append_message(tm) - friend.clear_corr() - assert len(friend.get_corr()) == 1 - assert len(friend.get_corr_for_saving()) == 0 - friend.append_message(TextMessage('Hello! It is test!', MESSAGE_OWNER['ME'], t, 0)) - assert len(friend.get_corr()) == 2 - assert len(friend.get_corr_for_saving()) == 1 - - def test_history_search(self): - create_singletons() - friend = create_random_friend() - message = 'Hello! It is test!' - friend.append_message(TextMessage(message, MESSAGE_OWNER['ME'], time.time(), 0)) - last_message = friend.get_last_message_text() - assert last_message == message - result = friend.search_string('e[m|s]') - assert result is not None - result = friend.search_string('tox') - assert result is None - - -class TestHistory: - - def test_history(self): - create_singletons() - db_name = 'my_name' - name, status_message, number = 'Friend', 'I am friend!', 0 - tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6' - friend = create_friend(name, status_message, number, tox_id) - history = History(db_name) - history.add_friend_to_db(friend.tox_id) - assert history.friend_exists_in_db(friend.tox_id) - text_message = 'Test!' - t = time.time() - friend.append_message(TextMessage(text_message, MESSAGE_OWNER['ME'], t, 0)) - messages = friend.get_corr_for_saving() - history.save_messages_to_db(friend.tox_id, messages) - getter = history.messages_getter(friend.tox_id) - messages = getter.get_all() - assert len(messages) == 1 - assert messages[0][0] == text_message - assert messages[0][1] == MESSAGE_OWNER['ME'] - assert messages[0][-1] == 0 - history.delete_message(friend.tox_id, t) - getter = history.messages_getter(friend.tox_id) - messages = getter.get_all() - assert len(messages) == 0 - history.delete_friend_from_db(friend.tox_id) - assert not history.friend_exists_in_db(friend.tox_id) From 43302b0130963d4fc36eed25e1c9ec9cd033c4bb Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 23 May 2018 21:32:14 +0300 Subject: [PATCH 061/138] test import fixed --- tests/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.py b/tests/tests.py index 130f56d..54e374e 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,4 +1,4 @@ -from middleware.tox_factory import * +from toxygen.middleware.tox_factory import * # TODO: add new tests From eb9ab56c6ec2037a24dfba970059a61ed072ff68 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 24 May 2018 15:01:17 +0300 Subject: [PATCH 062/138] fix for deleting last contact in list --- toxygen/contacts/contacts_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 02028d7..c99d439 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -464,8 +464,8 @@ class ContactsManager(ToxSave): self._history.delete_history(contact) def _delete_contact(self, num): + if num == self._active_contact: # active friend was deleted + self.set_active(0 if len(self._contacts) - 1 else -1) del self._contacts[num] self._screen.friends_list.takeItem(num) - if num == self._active_contact: # active friend was deleted - self.set_active(0 if len(self._contacts) else -1) self._save_profile() From c97fb6b467c5822d8cd50e34d8f47827b5eb641f Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 24 May 2018 15:20:21 +0300 Subject: [PATCH 063/138] minor stickers and smileys window fixes --- toxygen/smileys/smileys.py | 2 +- toxygen/stickers/stickers.py | 4 ++-- toxygen/ui/main_screen.py | 4 ++-- toxygen/ui/widgets_factory.py | 6 ------ 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/toxygen/smileys/smileys.py b/toxygen/smileys/smileys.py index 9027bcb..0391856 100644 --- a/toxygen/smileys/smileys.py +++ b/toxygen/smileys/smileys.py @@ -34,7 +34,7 @@ class SmileyLoader: print('Smiley pack {} loaded'.format(pack_name)) keys, values, self._list = [], [], [] 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: keys.append(key) values.append(value) diff --git a/toxygen/stickers/stickers.py b/toxygen/stickers/stickers.py index a406b6c..14142c7 100644 --- a/toxygen/stickers/stickers.py +++ b/toxygen/stickers/stickers.py @@ -10,9 +10,9 @@ def load_stickers(): d = util.get_stickers_directory() keys = [x[1] for x in os.walk(d)][0] for key in keys: - path = d + key + '/' + path = util.join_path(d, key) files = filter(lambda f: f.endswith('.png'), os.listdir(path)) - files = map(lambda f: str(path + f), files) + files = map(lambda f: util.join_path(path, f), files) result.extend(files) return result diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index d39cc91..415d8a5 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -570,8 +570,8 @@ class MainWindow(QtWidgets.QMainWindow): self.menu.hide() if self._contacts_manager.get_curr_contact() is not None: self.smiley = self._widget_factory.create_smiley_window(self) - self.smiley.setGeometry(QtCore.QRect(self.x() if self._settings['mirror_mode'] else 270 + self.x(), - self.y() + self.height() - 200, + self.smiley.setGeometry(QtCore.QRect(self.x() if self._settings['mirror_mode'] else 200 + self.x(), + self.y() + self.height() - 400, self.smiley.width(), self.smiley.height())) self.smiley.show() diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py index 4c4f1c6..68583d6 100644 --- a/toxygen/ui/widgets_factory.py +++ b/toxygen/ui/widgets_factory.py @@ -22,9 +22,6 @@ class WidgetsFactory: def create_screenshot_window(self, *args): return ScreenShotWindow(self._file_transfer_handler, self._contacts_manager, *args) - def create_smiley_window(self, parent): - return SmileyWindow(parent, self._smiley_loader) - def create_welcome_window(self): return WelcomeScreen(self._settings) @@ -49,9 +46,6 @@ class WidgetsFactory: def create_add_contact_window(self, tox_id): return AddContact(self._settings, self._contacts_manager, tox_id) - def create_welcome_window(self): - return WelcomeScreen(self._settings) - def create_privacy_settings_window(self): return PrivacySettings(self._contacts_manager, self._settings) From 486c13a3d36f7eaefadacb4f3910cb21005db769 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 24 May 2018 21:22:12 +0300 Subject: [PATCH 064/138] Login screen converted, create profile screen fixed --- toxygen/app.py | 33 ++++-- toxygen/ui/create_profile_screen.py | 21 ++-- toxygen/ui/login_screen.py | 109 ++++++------------- toxygen/ui/password_screen.py | 37 ++++--- toxygen/ui/views/create_profile_screen.ui | 38 +++++-- toxygen/ui/views/login_screen.ui | 124 ++++++++++++++++++++++ toxygen/ui/widgets.py | 15 ++- 7 files changed, 261 insertions(+), 116 deletions(-) create mode 100644 toxygen/ui/views/login_screen.ui diff --git a/toxygen/app.py b/toxygen/app.py index 2f18045..05a38d7 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -31,6 +31,7 @@ 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 import styles.style # TODO: dynamic loading @@ -165,7 +166,8 @@ class App: if result is None: return False if result.is_new_profile(): # create new profile - self._create_new_profile(result.profile_path) + 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 @@ -233,16 +235,25 @@ class App: data = self._enter_pass(data) self._tox = self._create_tox(data) - def _create_new_profile(self, profile_path): - name = util.get_profile_name_from_path(profile_path) or 'toxygen_user' + 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 + return False + name = profile_name or 'toxygen_user' self._tox = tox_factory() self._tox.self_set_name(bytes(name, 'utf-8') if name else b'Toxygen User') self._tox.self_set_status_message(b'Toxing on Toxygen') - # TODO: set profile password + 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._settings, self._toxes, profile_path) try: @@ -252,12 +263,22 @@ class App: 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 + + 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) diff --git a/toxygen/ui/create_profile_screen.py b/toxygen/ui/create_profile_screen.py index 08b85cf..9d7b503 100644 --- a/toxygen/ui/create_profile_screen.py +++ b/toxygen/ui/create_profile_screen.py @@ -29,15 +29,24 @@ class CreateProfileScreen(CenteredWidget, DialogWithResult): uic.loadUi(util.get_views_path('create_profile_screen'), self) self.center() self.createProfile.clicked.connect(self._create_profile) + self.retranslateUi() def retranslateUi(self): - self.defaultFolder.setPlaceholderText(util_ui.tr('Save in default folder')) - self.programFolder.setPlaceholderText(util_ui.tr('Save in program folder')) + 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:')) + self.passwordLabel.setText(util_ui.tr('Password (at least 8 symbols):')) def _create_profile(self): - if self.password.text() != self.confirmPassword.text(): - return # TODO : error - result = CreateProfileScreenResult(self.defaultFolder.isChecked(), self.password.text()) + 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) diff --git a/toxygen/ui/login_screen.py b/toxygen/ui/login_screen.py index bbafef9..946dc7a 100644 --- a/toxygen/ui/login_screen.py +++ b/toxygen/ui/login_screen.py @@ -1,20 +1,10 @@ from ui.widgets import * +from PyQt5 import uic +import utils.util as util +import utils.ui as util_ui import os.path -class NickEdit(LineEdit): - - def __init__(self, parent): - super().__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 LoginScreenResult: def __init__(self, profile_path, load_as_default, password=None): @@ -46,75 +36,42 @@ class LoginScreen(CenteredWidget, DialogWithResult): def __init__(self): CenteredWidget.__init__(self) DialogWithResult.__init__(self) - self.initUI() + uic.loadUi(util.get_views_path('login_screen'), self) self.center() self._profiles = [] - - 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_existing_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.toxygen.setGeometry(QtCore.QRect(160, 8, 90, 25)) - 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_() - font = QtGui.QFont() - font.setFamily("Impact") - font.setPointSize(16) - self.toxygen.setFont(font) - self.toxygen.setObjectName("toxygen") - self.retranslateUi() - QtCore.QMetaObject.connectSlotsByName(self) + self._update_ui() 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_existing_profile(self): - index = self.comboBox.currentIndex() - load_as_default = self.default.isChecked() - path = os.path.join(self._profiles[index][0], self._profiles[index][1] + '.tox') - result = LoginScreenResult(path, load_as_default) - self.close_with_result(result) + 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')) def update_select(self, profiles): profiles = sorted(profiles, key=lambda p: p[1]) self._profiles = list(profiles) - self.comboBox.addItems(list(map(lambda p: p[1], profiles))) - self.load_profile.setEnabled(len(profiles) > 0) + 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, 170, 30)) + self.retranslateUi() + 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) diff --git a/toxygen/ui/password_screen.py b/toxygen/ui/password_screen.py index bd6c146..bbae7ff 100644 --- a/toxygen/ui/password_screen.py +++ b/toxygen/ui/password_screen.py @@ -1,19 +1,20 @@ from ui.widgets import CenteredWidget, LineEdit, DialogWithResult from PyQt5 import QtCore, QtWidgets +import utils.ui as util_ui class PasswordArea(LineEdit): def __init__(self, parent): - super(PasswordArea, self).__init__(parent) - self.parent = parent + super().__init__(parent) + self._parent = parent self.setEchoMode(QtWidgets.QLineEdit.Password) def keyPressEvent(self, event): if event.key() == QtCore.Qt.Key_Return: - self.parent.button_click() + self._parent.button_click() else: - super(PasswordArea, self).keyPressEvent(event) + super().keyPressEvent(event) class PasswordScreenBase(CenteredWidget, DialogWithResult): @@ -37,7 +38,7 @@ class PasswordScreenBase(CenteredWidget, DialogWithResult): self.button = QtWidgets.QPushButton(self) 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.warning = QtWidgets.QLabel(self) @@ -59,15 +60,15 @@ class PasswordScreenBase(CenteredWidget, DialogWithResult): super(PasswordScreenBase, self).keyPressEvent(event) def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate("pass", "Enter password")) - self.enter_pass.setText(QtWidgets.QApplication.translate("pass", "Password:")) - self.warning.setText(QtWidgets.QApplication.translate("pass", "Incorrect password")) + self.setWindowTitle(util_ui.tr('Enter password')) + self.enter_pass.setText(util_ui.tr('Password:')) + self.warning.setText(util_ui.tr('Incorrect password')) class PasswordScreen(PasswordScreenBase): def __init__(self, encrypt, data): - super(PasswordScreen, self).__init__(encrypt) + super().__init__(encrypt) self._data = data def button_click(self): @@ -129,16 +130,15 @@ class SetProfilePasswordScreen(CenteredWidget): self.warning.setStyleSheet('QLabel { color: #BC1C1C; }') def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate("PasswordScreen", "Profile password")) + self.setWindowTitle(util_ui.tr('Profile password')) self.password.setPlaceholderText( - QtWidgets.QApplication.translate("PasswordScreen", "Password (at least 8 symbols)")) + util_ui.tr('Password (at least 8 symbols)')) self.confirm_password.setPlaceholderText( - QtWidgets.QApplication.translate("PasswordScreen", "Confirm password")) + util_ui.tr('Confirm password')) self.set_password.setText( - QtWidgets.QApplication.translate("PasswordScreen", "Set password")) - self.not_match.setText(QtWidgets.QApplication.translate("PasswordScreen", "Passwords do not match")) - self.warning.setText( - QtWidgets.QApplication.translate("PasswordScreen", "There is no way to recover lost passwords")) + util_ui.tr('Set password')) + self.not_match.setText(util_ui.tr('Passwords do not match')) + self.warning.setText(util_ui.tr('There is no way to recover lost passwords')) def new_password(self): if self.password.text() == self.confirm_password.text(): @@ -146,9 +146,8 @@ class SetProfilePasswordScreen(CenteredWidget): self._encrypt.set_password(self.password.text()) self.close() else: - self.not_match.setText( - QtWidgets.QApplication.translate("PasswordScreen", "Password must be at least 8 symbols")) + self.not_match.setText(util_ui.tr('Password must be at least 8 symbols')) self.not_match.setVisible(True) 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) diff --git a/toxygen/ui/views/create_profile_screen.ui b/toxygen/ui/views/create_profile_screen.ui index f9d3241..5407dca 100644 --- a/toxygen/ui/views/create_profile_screen.ui +++ b/toxygen/ui/views/create_profile_screen.ui @@ -7,19 +7,19 @@ 0 0 400 - 300 + 380 400 - 300 + 380 400 - 300 + 380 @@ -29,7 +29,7 @@ 30 - 240 + 290 341 51 @@ -47,6 +47,9 @@ 41 + + QLineEdit::Password + @@ -57,14 +60,17 @@ 41 + + QLineEdit::Password + 30 100 - 67 - 17 + 330 + 20 @@ -76,7 +82,7 @@ 30 10 - 112 + 330 23 @@ -92,7 +98,7 @@ 30 50 - 112 + 330 23 @@ -100,6 +106,22 @@ RadioButton + + + + 30 + 250 + 341 + 30 + + + + + + + Qt::AlignCenter + + diff --git a/toxygen/ui/views/login_screen.ui b/toxygen/ui/views/login_screen.ui new file mode 100644 index 0000000..a4c205c --- /dev/null +++ b/toxygen/ui/views/login_screen.ui @@ -0,0 +1,124 @@ + + + loginScreen + + + + 0 + 0 + 400 + 200 + + + + Form + + + + + 0 + 5 + 401 + 30 + + + + + Garuda + 16 + 75 + true + + + + Toxygen + + + Qt::AlignCenter + + + + + + 10 + 40 + 180 + 150 + + + + GroupBox + + + Qt::AlignCenter + + + + + 20 + 110 + 150 + 27 + + + + PushButton + + + + + + + 210 + 40 + 180 + 150 + + + + GroupBox + + + Qt::AlignCenter + + + + + 10 + 40 + 160 + 27 + + + + + + + 10 + 75 + 160 + 27 + + + + CheckBox + + + + + + 20 + 110 + 150 + 27 + + + + PushButton + + + + + + + diff --git a/toxygen/ui/widgets.py b/toxygen/ui/widgets.py index ec6c3e4..c25607b 100644 --- a/toxygen/ui/widgets.py +++ b/toxygen/ui/widgets.py @@ -52,7 +52,7 @@ class DialogWithResult(QtWidgets.QWidget): class LineEdit(QtWidgets.QLineEdit): def __init__(self, parent=None): - super(LineEdit, self).__init__(parent) + super().__init__(parent) def contextMenuEvent(self, event): menu = create_menu(self.createStandardContextMenu()) @@ -181,3 +181,16 @@ class MultilineEdit(CenteredWidget): def button_click(self): self.save(self.edit.toPlainText()) self.close() + + +class LineEditWithEnterSupport(LineEdit): + + def __init__(self, enter_action, parent=None): + super().__init__(parent) + self._action = enter_action + + def keyPressEvent(self, event): + if event.key() == QtCore.Qt.Key_Return: + self._action() + else: + super().keyPressEvent(event) From 439ce30e6ec0c83fdc79984b249293dadc8bbd3e Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 24 May 2018 21:43:34 +0300 Subject: [PATCH 065/138] reconnection fixes --- toxygen/app.py | 51 ++++++++++++++++------------ toxygen/contacts/contacts_manager.py | 9 +++++ toxygen/contacts/profile.py | 12 +++---- toxygen/ui/main_screen.py | 1 + 4 files changed, 45 insertions(+), 28 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index 05a38d7..29619fd 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -43,7 +43,7 @@ class App: 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 = self._tox_dns = None - self._group_factory = self._groups_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 @@ -192,7 +192,7 @@ class App: # Threads # ----------------------------------------------------------------------------------------------------------------- - def _start_threads(self): + def _start_threads(self, initial_start=True): # init thread self._init = threads.InitThread(self._tox, self._plugin_loader, self._settings) self._init.start() @@ -203,15 +203,17 @@ class App: self._av_loop = threads.ToxAVIterateThread(self._tox.AV) self._av_loop.start() - threads.start_file_transfer_thread() + if initial_start: + threads.start_file_transfer_thread() - def _stop_threads(self): + def _stop_threads(self, is_app_closing=True): self._init.stop_thread() self._av_loop.stop_thread() self._main_loop.stop_thread() - threads.stop_file_transfer_thread() + if is_app_closing: + threads.stop_file_transfer_thread() # ----------------------------------------------------------------------------------------------------------------- # Profiles @@ -304,21 +306,24 @@ class App: Create new tox instance (new network settings) :return: tox instance """ - self._stop_threads() + self._stop_threads(False) data = self._tox.get_savedata() self._save_profile(data) del self._tox # create new tox instance self._tox = self._create_tox(data) - self._start_threads() + 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._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) - return self._tox + self._calls_manager.set_toxav(self._tox.AV) + self._contacts_manager.update_friends_numbers() + + self._init_callbacks() def _create_dependencies(self): self._smiley_loader = SmileyLoader(self._settings) @@ -331,8 +336,8 @@ class App: self._tox, db, contact_items_factory) self._group_factory = GroupFactory(self._profile_manager, self._settings, self._tox, db, contact_items_factory) self._contacts_provider = ContactProvider(self._tox, self._friend_factory, self._group_factory) - profile = Profile(self._profile_manager, self._tox, self._ms, self._contacts_provider, self._reset) - self._plugin_loader = PluginLoader(self._tox, self._toxes, profile, self._settings) + self._profile = Profile(self._profile_manager, self._tox, self._ms, self._contacts_provider, self._reset) + self._plugin_loader = PluginLoader(self._tox, self._toxes, self._profile, self._settings) history = None messages_items_factory = MessagesItemsFactory(self._settings, self._plugin_loader, self._smiley_loader, self._ms, lambda m: history.delete_message(m)) @@ -343,28 +348,25 @@ class App: 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, profile, self._calls_manager) + self._contacts_provider, messages_items_factory, self._profile, self._calls_manager) file_transfers_message_service = FileTransfersMessagesService(self._contacts_manager, messages_items_factory, - profile, self._ms) + self._profile, self._ms) self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider, - file_transfers_message_service, profile) + file_transfers_message_service, self._profile) messages_items_factory.set_file_transfers_handler(self._file_transfer_handler) self._groups_service = GroupsService(self._tox, self._contacts_manager, self._contacts_provider, self._ms) - widgets_factory = WidgetsFactory(self._settings, profile, self._profile_manager, self._contacts_manager, + 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._tray = tray.init_tray(profile, self._settings, self._ms, self._toxes) - self._ms.set_dependencies(widgets_factory, self._tray, self._contacts_manager, self._messenger, profile, + 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._tray.show() self._ms.show() - # callbacks initialization - callbacks.init_callbacks(self._tox, 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) + self._init_callbacks() def _try_to_update(self): updating = updater.start_update_if_needed(self._version, self._settings) @@ -376,3 +378,8 @@ class App: def _create_tox(self, data): return tox_factory(data, self._settings) + + 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) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index c99d439..546f04d 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -392,6 +392,15 @@ class ContactsManager(ToxSave): def can_send_typing_notification(self): return self._settings['typing_notifications'] and self._active_contact + 1 + # ----------------------------------------------------------------------------------------------------------------- + # 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() + # ----------------------------------------------------------------------------------------------------------------- # Private methods # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 469b8fe..3529be4 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -1,9 +1,10 @@ from contacts import basecontact import random import threading +import common.tox_save as tox_save -class Profile(basecontact.BaseContact): +class Profile(basecontact.BaseContact, tox_save.ToxSave): """ Profile of current toxygen user. Contains friends list, tox instance """ @@ -18,9 +19,9 @@ class Profile(basecontact.BaseContact): tox.self_get_status_message(), screen.user_info, tox.self_get_address()) + tox_save.ToxSave.__init__(self, tox) self._screen = screen self._messages = screen.messages - self._tox = tox self._contacts_provider = contacts_provider self._reset_action = reset_action self._waiting_for_reconnection = False @@ -71,14 +72,13 @@ class Profile(basecontact.BaseContact): """ Recreate tox instance """ - del self._tox - self._tox = self._reset_action() self.status = None + self._reset_action() def _reconnect(self): self._waiting_for_reconnection = False - contacts = self._contacts_provider.get_all() - if self.status is None or all(list(map(lambda x: x.status is None, contacts))) and len(contacts): + contacts = self._contacts_provider.get_all_friends() + if self.status is None or (all(list(map(lambda x: x.status is None, contacts))) and len(contacts)): self._waiting_for_reconnection = True self.restart() self._timer = threading.Timer(50, self._reconnect) diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 415d8a5..652bbe3 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -212,6 +212,7 @@ class MainWindow(QtWidgets.QMainWindow): self.search_label.setPixmap(pixmap) self.contact_name = LineEdit(Form) + self.contact_name.setObjectName('contact_name') self.contact_name.setGeometry(QtCore.QRect(0, 0, 150, 25)) self.contact_name.textChanged.connect(self.filtering) From 370716015b30b8f6908ba47ee829a96474f78626 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 24 May 2018 21:55:44 +0300 Subject: [PATCH 066/138] groups numbers update --- toxygen/contacts/contacts_manager.py | 20 ++++++++++++++++---- toxygen/groups/groups_service.py | 3 +-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 546f04d..74452e1 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -205,14 +205,17 @@ class ContactsManager(ToxSave): self.filtration_and_sorting(self._sorting, self._filter_string) # ----------------------------------------------------------------------------------------------------------------- - # Friend getters + # Contact getters # ----------------------------------------------------------------------------------------------------------------- def get_friend_by_number(self, number): - return list(filter(lambda x: x.number == number and type(x) is Friend, self._contacts))[0] + 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 x: x.number == number and type(x) is GroupChat, self._contacts))[0] + return list(filter(lambda c: c.number == number and type(c) is GroupChat, self._contacts))[0] + + def get_contact_by_tox_id(self, tox_id): + return list(filter(lambda c: c.tox_id == tox_id, self._contacts))[0] def get_last_message(self): if self._active_contact + 1: @@ -401,6 +404,14 @@ class ContactsManager(ToxSave): 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() + # ----------------------------------------------------------------------------------------------------------------- # Private methods # ----------------------------------------------------------------------------------------------------------------- @@ -410,7 +421,7 @@ class ContactsManager(ToxSave): self._load_groups() if len(self._contacts): self.set_active(0) - self.filtration_and_sorting(self._sorting) + self.update_filtration() def _load_friends(self): self._contacts.extend(self._contact_provider.get_all_friends()) @@ -475,6 +486,7 @@ class ContactsManager(ToxSave): def _delete_contact(self, num): if num == self._active_contact: # active friend was deleted self.set_active(0 if len(self._contacts) - 1 else -1) + 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() diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index 9b4287d..0e9bef7 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -37,10 +37,9 @@ class GroupsService(tox_save.ToxSave): # ----------------------------------------------------------------------------------------------------------------- def leave_group(self, group_number): - group = self._get_group(group_number) self._tox.group_leave(group_number) self._contacts_manager.delete_group(group_number) - self._contacts_provider.remove_contact_from_cache(group.tox_id) + self._contacts_manager.update_groups_numbers() # ----------------------------------------------------------------------------------------------------------------- # Group invites From 13b2d17786d1e1f5a1ade312573fba9ac9ff60be Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 24 May 2018 23:58:39 +0300 Subject: [PATCH 067/138] notifications and audio settings views converted --- toxygen/ui/create_profile_screen.py | 4 +- toxygen/ui/groups_widgets.py | 13 +- toxygen/ui/login_screen.py | 20 +-- toxygen/ui/menu.py | 118 ++++++------------ toxygen/ui/views/audio_settings_screen.ui | 87 +++++++++++++ toxygen/ui/views/create_profile_screen.ui | 18 +-- .../ui/views/notifications_settings_screen.ui | 71 +++++++++++ 7 files changed, 229 insertions(+), 102 deletions(-) create mode 100644 toxygen/ui/views/audio_settings_screen.ui create mode 100644 toxygen/ui/views/notifications_settings_screen.ui diff --git a/toxygen/ui/create_profile_screen.py b/toxygen/ui/create_profile_screen.py index 9d7b503..512c141 100644 --- a/toxygen/ui/create_profile_screen.py +++ b/toxygen/ui/create_profile_screen.py @@ -29,9 +29,9 @@ class CreateProfileScreen(CenteredWidget, DialogWithResult): uic.loadUi(util.get_views_path('create_profile_screen'), self) self.center() self.createProfile.clicked.connect(self._create_profile) - self.retranslateUi() + self._retranslate_ui() - def retranslateUi(self): + 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')) diff --git a/toxygen/ui/groups_widgets.py b/toxygen/ui/groups_widgets.py index 4dc39d5..c6d0248 100644 --- a/toxygen/ui/groups_widgets.py +++ b/toxygen/ui/groups_widgets.py @@ -11,11 +11,14 @@ class CreateGroupScreen(CenteredWidget): self._groups_service = groups_service uic.loadUi(util.get_views_path('create_group_screen'), self) self.center() - self.retranslateUi() + self._update_ui() + + def _update_ui(self): + self._retranslate_ui() self.addGroupButton.clicked.connect(self._create_group) self.groupNameLineEdit.textChanged.connect(self._group_name_changed) - def retranslateUi(self): + def _retranslate_ui(self): 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:')) @@ -42,11 +45,13 @@ class JoinGroupScreen(CenteredWidget): self._groups_service = groups_service uic.loadUi(util.get_views_path('join_group_screen'), self) self.center() - self.retranslateUi() + + def _update_ui(self): + self._retranslate_ui() self.chatIdLineEdit.textChanged.connect(self._chat_id_changed) self.joinGroupButton.clicked.connect(self._join_group) - def retranslateUi(self): + def _retranslate_ui(self): self.setWindowTitle(util_ui.tr('Join public group chat')) self.chatIdLabel.setText(util_ui.tr('Group ID:')) self.passwordLabel.setText(util_ui.tr('Password:')) diff --git a/toxygen/ui/login_screen.py b/toxygen/ui/login_screen.py index 946dc7a..7198c36 100644 --- a/toxygen/ui/login_screen.py +++ b/toxygen/ui/login_screen.py @@ -41,15 +41,6 @@ class LoginScreen(CenteredWidget, DialogWithResult): self._profiles = [] self._update_ui() - def retranslateUi(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')) - def update_select(self, profiles): profiles = sorted(profiles, key=lambda p: p[1]) self._profiles = list(profiles) @@ -59,7 +50,7 @@ class LoginScreen(CenteredWidget, DialogWithResult): def _update_ui(self): self.profileNameLineEdit = LineEditWithEnterSupport(self._create_profile, self) self.profileNameLineEdit.setGeometry(QtCore.QRect(20, 100, 170, 30)) - self.retranslateUi() + self._retranslate_ui() self.createProfilePushButton.clicked.connect(self._create_profile) self.loadProfilePushButton.clicked.connect(self._load_existing_profile) @@ -75,3 +66,12 @@ class LoginScreen(CenteredWidget, DialogWithResult): 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')) diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index 2f55bb5..36930dd 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -1,4 +1,4 @@ -from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5 import QtCore, QtGui, QtWidgets, uic from user_data.settings import * from utils.util import * from ui.widgets import CenteredWidget, DataLabel, LineEdit, RubberBandWindow @@ -493,50 +493,31 @@ class NotificationsSettings(CenteredWidget): def __init__(self, setttings): super().__init__() self._settings = setttings - self.initUI() + uic.loadUi(get_views_path('notifications_settings_screen'), self) + self._update_ui() self.center() - def initUI(self): - self.setObjectName("notificationsForm") - self.resize(350, 210) - self.setMinimumSize(QtCore.QSize(350, 210)) - self.setMaximumSize(QtCore.QSize(350, 210)) - self.enableNotifications = QtWidgets.QCheckBox(self) - self.enableNotifications.setGeometry(QtCore.QRect(10, 20, 340, 18)) - self.callsSound = QtWidgets.QCheckBox(self) - self.callsSound.setGeometry(QtCore.QRect(10, 170, 340, 18)) - self.soundNotifications = QtWidgets.QCheckBox(self) - self.soundNotifications.setGeometry(QtCore.QRect(10, 70, 340, 18)) - self.groupNotifications = QtWidgets.QCheckBox(self) - self.groupNotifications.setGeometry(QtCore.QRect(10, 120, 340, 18)) - font = QtGui.QFont() - font.setFamily(self._settings['font']) - font.setPointSize(12) - self.callsSound.setFont(font) - self.soundNotifications.setFont(font) - self.enableNotifications.setFont(font) - self.groupNotifications.setFont(font) - self.enableNotifications.setChecked(self._settings['notifications']) - self.soundNotifications.setChecked(self._settings['sound_notifications']) - self.groupNotifications.setChecked(self._settings['group_notifications']) - self.callsSound.setChecked(self._settings['calls_sound']) - self.retranslateUi() - QtCore.QMetaObject.connectSlotsByName(self) - - def retranslateUi(self): - self.setWindowTitle(util_ui.tr("Notification settings")) - self.enableNotifications.setText(util_ui.tr("Enable notifications")) - self.groupNotifications.setText(util_ui.tr("Notify about all messages in groups")) - self.callsSound.setText(util_ui.tr("Enable call\'s sound")) - self.soundNotifications.setText(util_ui.tr("Enable sound notifications")) - def closeEvent(self, *args, **kwargs): - self._settings['notifications'] = self.enableNotifications.isChecked() - self._settings['sound_notifications'] = self.soundNotifications.isChecked() - self._settings['group_notifications'] = self.groupNotifications.isChecked() - self._settings['calls_sound'] = self.callsSound.isChecked() + 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""" @@ -729,52 +710,35 @@ class AudioSettings(CenteredWidget): def __init__(self, settings): super().__init__() self._settings = settings - self.initUI() - self.retranslateUi() + self._in_indexes = self._out_indexes = None + uic.loadUi(get_views_path('audio_settings_screen'), self) + self._update_ui() self.center() - def initUI(self): - self.setObjectName("audioSettingsForm") - self.resize(400, 150) - self.setMinimumSize(QtCore.QSize(400, 150)) - self.setMaximumSize(QtCore.QSize(400, 150)) - self.in_label = QtWidgets.QLabel(self) - self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20)) - self.out_label = QtWidgets.QLabel(self) - self.out_label.setGeometry(QtCore.QRect(25, 65, 350, 20)) - font = QtGui.QFont() - font.setPointSize(16) - font.setBold(True) - font.setFamily(self._settings['font']) - self.in_label.setFont(font) - self.out_label.setFont(font) - self.input = QtWidgets.QComboBox(self) - self.input.setGeometry(QtCore.QRect(25, 30, 350, 30)) - self.output = QtWidgets.QComboBox(self) - self.output.setGeometry(QtCore.QRect(25, 90, 350, 30)) + 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 = [], [] + 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.input.addItem(str(device["name"])) - self.in_indexes.append(i) + self.inputDeviceComboBox.addItem(str(device["name"])) + self._in_indexes.append(i) if device["maxOutputChannels"]: - self.output.addItem(str(device["name"])) - self.out_indexes.append(i) - self.input.setCurrentIndex(self.in_indexes.index(self._settings.audio['input'])) - self.output.setCurrentIndex(self.out_indexes.index(self._settings.audio['output'])) - QtCore.QMetaObject.connectSlotsByName(self) + 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 retranslateUi(self): + def _retranslate_ui(self): self.setWindowTitle(util_ui.tr("Audio settings")) - self.in_label.setText(util_ui.tr("Input device:")) - self.out_label.setText(util_ui.tr("Output device:")) - - def closeEvent(self, event): - self._settings.audio['input'] = self.in_indexes[self.input.currentIndex()] - self._settings.audio['output'] = self.out_indexes[self.output.currentIndex()] - self._settings.save() + self.inputDeviceLabel.setText(util_ui.tr("Input device:")) + self.outputDeviceLabel.setText(util_ui.tr("Output device:")) class DesktopAreaSelectionWindow(RubberBandWindow): diff --git a/toxygen/ui/views/audio_settings_screen.ui b/toxygen/ui/views/audio_settings_screen.ui new file mode 100644 index 0000000..a404592 --- /dev/null +++ b/toxygen/ui/views/audio_settings_screen.ui @@ -0,0 +1,87 @@ + + + Form + + + + 0 + 0 + 315 + 218 + + + + + 315 + 218 + + + + + 315 + 218 + + + + Form + + + + + 30 + 10 + 261 + 30 + + + + + 16 + + + + TextLabel + + + + + + 30 + 100 + 261 + 30 + + + + + 16 + + + + TextLabel + + + + + + 30 + 50 + 255 + 41 + + + + + + + 30 + 140 + 255 + 41 + + + + + + + diff --git a/toxygen/ui/views/create_profile_screen.ui b/toxygen/ui/views/create_profile_screen.ui index 5407dca..bfffee5 100644 --- a/toxygen/ui/views/create_profile_screen.ui +++ b/toxygen/ui/views/create_profile_screen.ui @@ -7,19 +7,19 @@ 0 0 400 - 380 + 340 400 - 380 + 340 400 - 380 + 340 @@ -29,7 +29,7 @@ 30 - 290 + 270 341 51 @@ -42,7 +42,7 @@ 30 - 190 + 170 341 41 @@ -55,7 +55,7 @@ 30 - 140 + 120 341 41 @@ -68,7 +68,7 @@ 30 - 100 + 80 330 20 @@ -97,7 +97,7 @@ 30 - 50 + 40 330 23 @@ -110,7 +110,7 @@ 30 - 250 + 220 341 30 diff --git a/toxygen/ui/views/notifications_settings_screen.ui b/toxygen/ui/views/notifications_settings_screen.ui new file mode 100644 index 0000000..67e2dc6 --- /dev/null +++ b/toxygen/ui/views/notifications_settings_screen.ui @@ -0,0 +1,71 @@ + + + Form + + + + 0 + 0 + 320 + 201 + + + + Form + + + + + 20 + 20 + 271 + 41 + + + + CheckBox + + + + + + 20 + 60 + 271 + 41 + + + + CheckBox + + + + + + 20 + 100 + 271 + 41 + + + + CheckBox + + + + + + 20 + 140 + 271 + 41 + + + + CheckBox + + + + + + From 238f7e367a45da2197e558c80685d762d0d9a587 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 25 May 2018 00:16:21 +0300 Subject: [PATCH 068/138] update settings screen converted --- toxygen/app.py | 2 +- toxygen/ui/main_screen.py | 5 +- toxygen/ui/menu.py | 61 ++++++++------------ toxygen/ui/views/update_settings_screen.ui | 67 ++++++++++++++++++++++ toxygen/ui/widgets_factory.py | 2 +- 5 files changed, 97 insertions(+), 40 deletions(-) create mode 100644 toxygen/ui/views/update_settings_screen.ui diff --git a/toxygen/app.py b/toxygen/app.py index 29619fd..60bb8bf 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -361,7 +361,7 @@ class App: 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._groups_service, self._toxes) self._tray.show() self._ms.show() diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 652bbe3..e88437a 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -17,13 +17,13 @@ class MainWindow(QtWidgets.QMainWindow): self._plugins_loader = None self.setAcceptDrops(True) self._saved = False - self._profile = None + self._profile = self._toxes = 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): + file_transfer_handler, history_loader, calls_manager, groups_service, toxes): self._widget_factory = widget_factory self._tray = tray self._contacts_manager = contacts_manager @@ -33,6 +33,7 @@ class MainWindow(QtWidgets.QMainWindow): self._history_loader = history_loader self._calls_manager = calls_manager self._groups_service = groups_service + self._toxes = toxes self._contacts_manager.active_contact_changed.add_callback(self._new_contact_selected) self.messageEdit.set_messenger(messenger) diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index 36930dd..fff95a9 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -946,55 +946,44 @@ class UpdateSettings(CenteredWidget): Updates settings form """ - def __init__(self, settings): + def __init__(self, settings, version): super().__init__() self._settings = settings - self.initUI() + self._version = version + uic.loadUi(get_views_path('update_settings_screen'), self) + self._update_ui() self.center() - def initUI(self): - self.setObjectName("updateSettingsForm") - self.resize(400, 150) - self.setMinimumSize(QtCore.QSize(400, 120)) - self.setMaximumSize(QtCore.QSize(400, 120)) - self.in_label = QtWidgets.QLabel(self) - self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20)) - font = QtGui.QFont() - font.setPointSize(16) - font.setBold(True) - font.setFamily(self._settings['font']) - self.in_label.setFont(font) - self.autoupdate = QtWidgets.QComboBox(self) - self.autoupdate.setGeometry(QtCore.QRect(25, 30, 350, 30)) - self.button = QtWidgets.QPushButton(self) - self.button.setGeometry(QtCore.QRect(25, 70, 350, 30)) - self.button.setEnabled(self._settings['update']) - self.button.clicked.connect(self.update_client) - - self.retranslateUi() - self.autoupdate.setCurrentIndex(self._settings['update']) - QtCore.QMetaObject.connectSlotsByName(self) - - def retranslateUi(self): - self.setWindowTitle(util_ui.tr("Update settings")) - self.in_label.setText(util_ui.tr("Select update mode:")) - self.button.setText(util_ui.tr("Update Toxygen")) - self.autoupdate.addItem(util_ui.tr("Disabled")) - self.autoupdate.addItem(util_ui.tr("Manual")) - self.autoupdate.addItem(util_ui.tr("Auto")) - def closeEvent(self, event): - self._settings['update'] = self.autoupdate.currentIndex() + self._settings['update'] = self.updateModeComboBox.currentIndex() self._settings.save() - def update_client(self): + 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() + version = updater.check_for_updates(self._version, self._settings) if version is not None: updater.download(version) util_ui.close_all_windows() diff --git a/toxygen/ui/views/update_settings_screen.ui b/toxygen/ui/views/update_settings_screen.ui new file mode 100644 index 0000000..76e7c57 --- /dev/null +++ b/toxygen/ui/views/update_settings_screen.ui @@ -0,0 +1,67 @@ + + + Form + + + + 0 + 0 + 400 + 120 + + + + + 400 + 120 + + + + + 400 + 120 + + + + Form + + + + + 25 + 5 + 350 + 20 + + + + TextLabel + + + + + + 25 + 30 + 350 + 30 + + + + + + + 25 + 70 + 350 + 30 + + + + PushButton + + + + + + diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py index 68583d6..ead09ce 100644 --- a/toxygen/ui/widgets_factory.py +++ b/toxygen/ui/widgets_factory.py @@ -38,7 +38,7 @@ class WidgetsFactory: return VideoSettings(self._settings) def create_update_settings_window(self): - return UpdateSettings(self._settings) + return UpdateSettings(self._settings, self._version) def create_plugins_settings_window(self): return PluginsSettings(self._plugin_loader) From 423bda93c691ebd959b99ecb363f971848778c68 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 25 May 2018 11:26:22 +0300 Subject: [PATCH 069/138] video settings screen converted --- toxygen/ui/menu.py | 116 +++++++++------------- toxygen/ui/views/video_settings_screen.ui | 77 ++++++++++++++ 2 files changed, 126 insertions(+), 67 deletions(-) create mode 100644 toxygen/ui/views/video_settings_screen.ui diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index fff95a9..c473c8b 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -5,6 +5,7 @@ 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): @@ -761,70 +762,18 @@ class VideoSettings(CenteredWidget): def __init__(self, settings): super().__init__() self._settings = settings - self.initUI() - self.retranslateUi() + 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 initUI(self): - self.setObjectName("videoSettingsForm") - self.resize(400, 120) - self.setMinimumSize(QtCore.QSize(400, 120)) - self.setMaximumSize(QtCore.QSize(400, 120)) - self.in_label = QtWidgets.QLabel(self) - self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20)) - font = QtGui.QFont() - font.setPointSize(16) - font.setBold(True) - font.setFamily(self._settings['font']) - self.in_label.setFont(font) - self.video_size = QtWidgets.QComboBox(self) - self.video_size.setGeometry(QtCore.QRect(25, 70, 350, 30)) - self.input = QtWidgets.QComboBox(self) - self.input.setGeometry(QtCore.QRect(25, 30, 350, 30)) - self.input.currentIndexChanged.connect(self.selectionChanged) - self.button = QtWidgets.QPushButton(self) - self.button.clicked.connect(self.button_clicked) - self.button.setGeometry(QtCore.QRect(25, 70, 350, 30)) - import cv2 - self.devices = [-1] - screen = QtWidgets.QApplication.primaryScreen() - size = screen.size() - self.frame_max_sizes = [(size.width(), size.height())] - desktop = util_ui.tr("Desktop") - self.input.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.input.addItem('Device #' + str(i)) - try: - index = self.devices.index(self._settings.video['device']) - self.input.setCurrentIndex(index) - except: - print('Video devices error!') - - def retranslateUi(self): - self.setWindowTitle(util_ui.tr("Video settings")) - self.in_label.setText(util_ui.tr("Device:")) - self.button.setText(util_ui.tr("Select region")) - - def button_clicked(self): - self.desktopAreaSelection = DesktopAreaSelectionWindow(self) - def closeEvent(self, event): - if self.input.currentIndex() == 0: + if self.deviceComboBox.currentIndex() == 0: return try: self._settings.video['device'] = self.devices[self.input.currentIndex()] - text = self.video_size.currentText() + text = self.resolutionComboBox.currentText() self._settings.video['width'] = int(text.split(' ')[0]) self._settings.video['height'] = int(text.split(' ')[-1]) self._settings.save() @@ -840,15 +789,48 @@ class VideoSettings(CenteredWidget): self._settings.video['y'] = y self._settings.save() - def selectionChanged(self): - if self.input.currentIndex() == 0: - self.button.setVisible(True) - self.video_size.setVisible(False) - else: - self.button.setVisible(False) - self.video_size.setVisible(True) - width, height = self.frame_max_sizes[self.input.currentIndex()] - self.video_size.clear() + 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), @@ -860,7 +842,7 @@ class VideoSettings(CenteredWidget): ] for w, h in dims: if w <= width and h <= height: - self.video_size.addItem(str(w) + ' * ' + str(h)) + self.resolutionComboBox.addItem(str(w) + ' * ' + str(h)) class PluginsSettings(CenteredWidget): diff --git a/toxygen/ui/views/video_settings_screen.ui b/toxygen/ui/views/video_settings_screen.ui new file mode 100644 index 0000000..cfa36fb --- /dev/null +++ b/toxygen/ui/views/video_settings_screen.ui @@ -0,0 +1,77 @@ + + + Form + + + + 0 + 0 + 400 + 120 + + + + + 400 + 120 + + + + + 400 + 120 + + + + Form + + + + + 25 + 5 + 350 + 20 + + + + TextLabel + + + + + + 25 + 30 + 350 + 30 + + + + + + + 25 + 70 + 350 + 30 + + + + PushButton + + + + + + 25 + 70 + 350 + 30 + + + + + + + From 03e2fa4cb862c2748d99078f7f5c64419a1b9017 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 25 May 2018 11:48:47 +0300 Subject: [PATCH 070/138] add friend screen coverted --- toxygen/ui/menu.py | 69 ++++++------------ toxygen/ui/views/add_contact_screen.ui | 99 ++++++++++++++++++++++++++ toxygen/ui/widgets.py | 1 + 3 files changed, 120 insertions(+), 49 deletions(-) create mode 100644 toxygen/ui/views/add_contact_screen.ui diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index c473c8b..6c9218c 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -15,70 +15,41 @@ class AddContact(CenteredWidget): super().__init__() self._settings = settings self._contacts_manager = contacts_manager - self.initUI(tox_id) + uic.loadUi(get_views_path('add_contact_screen'), self) + self._update_ui(tox_id) self._adding = False - self.setAttribute(QtCore.Qt.WA_DeleteOnClose) - def initUI(self, tox_id): - self.setObjectName('AddContact') - self.resize(568, 306) - self.sendRequestButton = QtWidgets.QPushButton(self) - self.sendRequestButton.setGeometry(QtCore.QRect(50, 270, 471, 31)) - self.sendRequestButton.setMinimumSize(QtCore.QSize(0, 0)) - self.sendRequestButton.setBaseSize(QtCore.QSize(0, 0)) - self.sendRequestButton.setObjectName("sendRequestButton") - self.sendRequestButton.clicked.connect(self.add_friend) - self.tox_id = LineEdit(self) - self.tox_id.setGeometry(QtCore.QRect(50, 40, 471, 27)) - self.tox_id.setObjectName("lineEdit") - self.tox_id.setText(tox_id) - self.label = QtWidgets.QLabel(self) - self.label.setGeometry(QtCore.QRect(50, 10, 80, 20)) - self.error_label = DataLabel(self) - self.error_label.setGeometry(QtCore.QRect(120, 10, 420, 20)) - font = QtGui.QFont() - font.setFamily(self._settings['font']) - font.setPointSize(10) - font.setWeight(30) - self.error_label.setFont(font) - self.error_label.setStyleSheet("QLabel { color: #BC1C1C; }") - self.label.setObjectName("label") - self.message_edit = QtWidgets.QTextEdit(self) - self.message_edit.setGeometry(QtCore.QRect(50, 110, 471, 151)) - self.message_edit.setObjectName("textEdit") - self.message = QtWidgets.QLabel(self) - self.message.setGeometry(QtCore.QRect(50, 70, 101, 31)) - self.message.setFont(font) - self.message.setObjectName("label_2") - self.retranslateUi() - self.message_edit.setText('Hello! Add me to your contact list please') - font.setPointSize(12) - font.setBold(True) - self.label.setFont(font) - self.message.setFont(font) - QtCore.QMetaObject.connectSlotsByName(self) + def _update_ui(self, tox_id): + self.toxIdLineEdit = LineEdit(self) + self.toxIdLineEdit.setGeometry(QtCore.QRect(50, 40, 460, 30)) + self.toxIdLineEdit.setText(tox_id) - def add_friend(self): + 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.tox_id.text().strip() + tox_id = self.toxIdLineEdit.text().strip() if tox_id.startswith('tox:'): tox_id = tox_id[4:] - send = self._contacts_manager.send_friend_request(tox_id, self.message_edit.toPlainText()) + 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.error_label.setText(send) + self.errorLabel.setText(send) - def retranslateUi(self): + def _retranslate_ui(self): self.setWindowTitle(util_ui.tr('Add contact')) - self.sendRequestButton.setText(util_ui.tr('Send request')) - self.label.setText(util_ui.tr('TOX ID:')) - self.message.setText(util_ui.tr('Message:')) - self.tox_id.setPlaceholderText(util_ui.tr('TOX ID or public key of 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 ProfileSettings(CenteredWidget): diff --git a/toxygen/ui/views/add_contact_screen.ui b/toxygen/ui/views/add_contact_screen.ui new file mode 100644 index 0000000..0f26a25 --- /dev/null +++ b/toxygen/ui/views/add_contact_screen.ui @@ -0,0 +1,99 @@ + + + Form + + + + 0 + 0 + 560 + 320 + + + + + 560 + 320 + + + + + 560 + 320 + + + + Form + + + + + 50 + 10 + 150 + 20 + + + + TextLabel + + + + + + 50 + 70 + 150 + 30 + + + + TextLabel + + + + + + 50 + 110 + 460 + 150 + + + + + + + 50 + 270 + 460 + 30 + + + + PushButton + + + + + true + + + + 220 + 10 + 321 + 31 + + + + Qt::NoContextMenu + + + + + + + + + diff --git a/toxygen/ui/widgets.py b/toxygen/ui/widgets.py index c25607b..e7fe623 100644 --- a/toxygen/ui/widgets.py +++ b/toxygen/ui/widgets.py @@ -24,6 +24,7 @@ class CenteredWidget(QtWidgets.QWidget): def __init__(self): super().__init__() + self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.center() def center(self): From 74a5f95a5656861949b60596837f0110919f73b1 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 24 May 2018 19:13:19 +0300 Subject: [PATCH 071/138] rebased ngc - initial commit --- toxygen/bootstrap/nodes.json | 2 +- toxygen/middleware/callbacks.py | 36 ++++++------- toxygen/middleware/tox_factory.py | 1 + toxygen/user_data/settings.py | 3 +- toxygen/wrapper/libtox.py | 16 +++--- toxygen/wrapper/tox.py | 87 ++++++++++++++----------------- 6 files changed, 69 insertions(+), 76 deletions(-) diff --git a/toxygen/bootstrap/nodes.json b/toxygen/bootstrap/nodes.json index 619fe67..f9574dc 100644 --- a/toxygen/bootstrap/nodes.json +++ b/toxygen/bootstrap/nodes.json @@ -1 +1 @@ -{"nodes":[{"ipv4":"127.0.0.1","ipv6":"-","port":33445,"public_key":"617DA0076546F9A801D06AAA2E20234DA6A1DDA90583FB02B59E3501CA84D061","status_udp":true,"status_tcp":true}]} \ No newline at end of file +{"nodes":[{"ipv4":"127.0.0.1","ipv6":"-","port":33445,"public_key":"82B9E28CF62A2D78D83BAC452CD18778F1F36B7BDF2989A3B8927D86D09C2E3D","status_udp":true,"status_tcp":true}]} \ No newline at end of file diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index f7a9531..6855adc 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -1,6 +1,4 @@ from PyQt5 import QtGui -from user_data.settings import Settings -from contacts.profile import Profile from wrapper.toxcore_enums_and_consts import * from wrapper.toxav_enums import * from wrapper.tox import bin_to_string @@ -22,7 +20,7 @@ import threading def self_connection_status(tox, profile): """ - Current user changed connection status (offline, UDP, TCP) + Current user changed connection status (offline, TCP, UDP) """ def wrapped(tox_link, connection, user_data): print('Connection status: ', str(connection)) @@ -103,7 +101,7 @@ def friend_status_message(contacts_manager, messenger): """ 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'.format(friend_number)) + print('User #{} has new status message'.format(friend_number)) invoke_in_main_thread(messenger.send_messages, friend_number) return wrapped @@ -472,25 +470,25 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, :param contacts_provider: ContactsProvider instance """ # self callbacks - tox.callback_self_connection_status(self_connection_status(tox, profile), 0) + 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), 0) - tox.callback_friend_message(friend_message(messenger, contacts_manager, profile, settings, main_window, tray), 0) + 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), 0) - tox.callback_friend_name(friend_name(contacts_provider, messenger), 0) - tox.callback_friend_status_message(friend_status_message(contacts_manager, messenger), 0) - tox.callback_friend_request(friend_request(contacts_manager), 0) - tox.callback_friend_typing(friend_typing(messenger), 0) - tox.callback_friend_read_receipt(friend_read_receipt(messenger), 0) + 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), 0) - tox.callback_file_recv_chunk(file_recv_chunk(file_transfer_handler), 0) - tox.callback_file_chunk_request(file_chunk_request(file_transfer_handler), 0) - tox.callback_file_recv_control(file_recv_control(file_transfer_handler), 0) + 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 @@ -500,8 +498,8 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, toxav.callback_video_receive_frame(video_receive_frame, 0) # custom packets - tox.callback_friend_lossless_packet(lossless_packet(plugin_loader), 0) - tox.callback_friend_lossy_packet(lossy_packet(plugin_loader), 0) + 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) diff --git a/toxygen/middleware/tox_factory.py b/toxygen/middleware/tox_factory.py index 588bd30..9ee5c01 100644 --- a/toxygen/middleware/tox_factory.py +++ b/toxygen/middleware/tox_factory.py @@ -21,6 +21,7 @@ def tox_factory(data=None, settings=None): 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) diff --git a/toxygen/user_data/settings.py b/toxygen/user_data/settings.py index 76a37c0..4171978 100644 --- a/toxygen/user_data/settings.py +++ b/toxygen/user_data/settings.py @@ -147,7 +147,8 @@ class Settings(dict): 'update': 1, 'group_notifications': True, 'download_nodes_list': False, - 'notify_all_gc': False + 'notify_all_gc': False, + 'lan_discovery': True } @staticmethod diff --git a/toxygen/wrapper/libtox.py b/toxygen/wrapper/libtox.py index 402aa5f..d5f599a 100644 --- a/toxygen/wrapper/libtox.py +++ b/toxygen/wrapper/libtox.py @@ -28,13 +28,13 @@ class LibToxAV: # 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') + self._libtoxav = CDLL('libtoxcore.dylib') else: - # /usr/lib/libtoxav.so must exists + # /usr/lib/libtoxcore.so must exists try: - self._libtoxav = CDLL('libtoxav.so') + self._libtoxav = CDLL('libtoxcore.so') except: - self._libtoxav = CDLL(util.curr_directory() + '/libs/libtoxav.so') + self._libtoxav = CDLL(util.curr_directory() + '/libs/libtoxcore.so') def __getattr__(self, item): return self._libtoxav.__getattr__(item) @@ -47,13 +47,13 @@ class LibToxEncryptSave: # 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') + self._lib_tox_encrypt_save = CDLL('libtoxcore.dylib') else: - # /usr/lib/libtoxencryptsave.so must exists + # /usr/lib/libtoxcore.so must exists try: - self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.so') + self._lib_tox_encrypt_save = CDLL('libtoxcore.so') except: - self._lib_tox_encrypt_save = CDLL(util.curr_directory() + '/libs/libtoxencryptsave.so') + self._lib_tox_encrypt_save = CDLL(util.curr_directory() + '/libs/libtoxcore.so') def __getattr__(self, item): return self._lib_tox_encrypt_save.__getattr__(item) diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index f455e30..9d955a4 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -10,15 +10,19 @@ class ToxOptions(Structure): _fields_ = [ ('ipv6_enabled', c_bool), ('udp_enabled', c_bool), + ('local_discovery_enabled', c_bool), ('proxy_type', c_int), ('proxy_host', c_char_p), ('proxy_port', c_uint16), ('start_port', c_uint16), ('end_port', c_uint16), ('tcp_port', c_uint16), + ('hole_punching_enabled', c_bool), ('savedata_type', c_int), ('savedata_data', c_char_p), - ('savedata_length', c_size_t) + ('savedata_length', c_size_t), + ('log_callback', c_void_p), + ('log_user_data', c_void_p) ] @@ -265,7 +269,7 @@ class Tox: """ return Tox.libtoxcore.tox_self_get_connection_status(self._tox_pointer) - def callback_self_connection_status(self, callback, user_data): + def callback_self_connection_status(self, callback): """ Set the callback for the `self_connection_status` event. Pass None to unset. @@ -276,12 +280,11 @@ class Tox: :param callback: Python function. Should take pointer (c_void_p) to Tox object, TOX_CONNECTION (c_int), pointer (c_void_p) to user_data - :param user_data: pointer (c_void_p) to user data """ c_callback = CFUNCTYPE(None, c_void_p, c_int, c_void_p) self.self_connection_status_cb = c_callback(callback) Tox.libtoxcore.tox_callback_self_connection_status(self._tox_pointer, - self.self_connection_status_cb, user_data) + self.self_connection_status_cb) def iteration_interval(self): """ @@ -290,11 +293,13 @@ class Tox: """ return Tox.libtoxcore.tox_iteration_interval(self._tox_pointer) - def iterate(self): + def iterate(self, user_data=None): """ The main loop that needs to be run in intervals of tox_iteration_interval() milliseconds. """ - Tox.libtoxcore.tox_iterate(self._tox_pointer) + if user_data is not None: + user_data = c_char_p(user_data) + Tox.libtoxcore.tox_iterate(self._tox_pointer, user_data) # ----------------------------------------------------------------------------------------------------------------- # Internal client information (Tox address/id) @@ -719,7 +724,7 @@ class Tox: elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']: raise ArgumentError('The friend_number did not designate a valid friend.') - def callback_friend_name(self, callback, user_data): + def callback_friend_name(self, callback): """ Set the callback for the `friend_name` event. Pass None to unset. @@ -730,11 +735,10 @@ class Tox: A byte array (c_char_p) containing the same data as tox_friend_get_name would write to its `name` parameter, A value (c_size_t) equal to the return value of tox_friend_get_name_size, pointer (c_void_p) to user_data - :param user_data: pointer (c_void_p) to user data """ c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_char_p, c_size_t, c_void_p) self.friend_name_cb = c_callback(callback) - Tox.libtoxcore.tox_callback_friend_name(self._tox_pointer, self.friend_name_cb, user_data) + Tox.libtoxcore.tox_callback_friend_name(self._tox_pointer, self.friend_name_cb) def friend_get_status_message_size(self, friend_number): """ @@ -783,7 +787,7 @@ class Tox: elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']: raise ArgumentError('The friend_number did not designate a valid friend.') - def callback_friend_status_message(self, callback, user_data): + def callback_friend_status_message(self, callback): """ Set the callback for the `friend_status_message` event. Pass NULL to unset. @@ -795,12 +799,11 @@ class Tox: `status_message` parameter, A value (c_size_t) equal to the return value of tox_friend_get_status_message_size, pointer (c_void_p) to user_data - :param user_data: pointer (c_void_p) to user data """ c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_char_p, c_size_t, c_void_p) self.friend_status_message_cb = c_callback(callback) Tox.libtoxcore.tox_callback_friend_status_message(self._tox_pointer, - self.friend_status_message_cb, c_void_p(user_data)) + self.friend_status_message_cb) def friend_get_status(self, friend_number): """ @@ -824,7 +827,7 @@ class Tox: elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']: raise ArgumentError('The friend_number did not designate a valid friend.') - def callback_friend_status(self, callback, user_data): + def callback_friend_status(self, callback): """ Set the callback for the `friend_status` event. Pass None to unset. @@ -838,7 +841,7 @@ class Tox: """ c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_int, c_void_p) self.friend_status_cb = c_callback(callback) - Tox.libtoxcore.tox_callback_friend_status(self._tox_pointer, self.friend_status_cb, c_void_p(user_data)) + Tox.libtoxcore.tox_callback_friend_status(self._tox_pointer, self.friend_status_cb) def friend_get_connection_status(self, friend_number): """ @@ -863,7 +866,7 @@ class Tox: elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']: raise ArgumentError('The friend_number did not designate a valid friend.') - def callback_friend_connection_status(self, callback, user_data): + def callback_friend_connection_status(self, callback): """ Set the callback for the `friend_connection_status` event. Pass NULL to unset. @@ -876,12 +879,11 @@ class Tox: The friend number (c_uint32) of the friend whose connection status changed, The result of calling tox_friend_get_connection_status (TOX_CONNECTION) on the passed friend_number, pointer (c_void_p) to user_data - :param user_data: pointer (c_void_p) to user data """ c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_int, c_void_p) self.friend_connection_status_cb = c_callback(callback) Tox.libtoxcore.tox_callback_friend_connection_status(self._tox_pointer, - self.friend_connection_status_cb, c_void_p(user_data)) + self.friend_connection_status_cb) def friend_get_typing(self, friend_number): """ @@ -903,7 +905,7 @@ class Tox: elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']: raise ArgumentError('The friend_number did not designate a valid friend.') - def callback_friend_typing(self, callback, user_data): + def callback_friend_typing(self, callback): """ Set the callback for the `friend_typing` event. Pass NULL to unset. @@ -913,11 +915,10 @@ class Tox: The friend number (c_uint32) of the friend who started or stopped typing, The result of calling tox_friend_get_typing (c_bool) on the passed friend_number, pointer (c_void_p) to user_data - :param user_data: pointer (c_void_p) to user data """ c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_bool, c_void_p) self.friend_typing_cb = c_callback(callback) - Tox.libtoxcore.tox_callback_friend_typing(self._tox_pointer, self.friend_typing_cb, c_void_p(user_data)) + Tox.libtoxcore.tox_callback_friend_typing(self._tox_pointer, self.friend_typing_cb) # ----------------------------------------------------------------------------------------------------------------- # Sending private messages @@ -982,7 +983,7 @@ class Tox: elif tox_err_friend_send_message == TOX_ERR_FRIEND_SEND_MESSAGE['EMPTY']: raise ArgumentError('Attempted to send a zero-length message.') - def callback_friend_read_receipt(self, callback, user_data): + def callback_friend_read_receipt(self, callback): """ Set the callback for the `friend_read_receipt` event. Pass None to unset. @@ -998,13 +999,13 @@ class Tox: c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_void_p) self.friend_read_receipt_cb = c_callback(callback) Tox.libtoxcore.tox_callback_friend_read_receipt(self._tox_pointer, - self.friend_read_receipt_cb, c_void_p(user_data)) + self.friend_read_receipt_cb) # ----------------------------------------------------------------------------------------------------------------- # Receiving private messages and friend requests # ----------------------------------------------------------------------------------------------------------------- - def callback_friend_request(self, callback, user_data): + def callback_friend_request(self, callback): """ Set the callback for the `friend_request` event. Pass None to unset. @@ -1019,9 +1020,9 @@ class Tox: """ c_callback = CFUNCTYPE(None, c_void_p, POINTER(c_uint8), c_char_p, c_size_t, c_void_p) self.friend_request_cb = c_callback(callback) - Tox.libtoxcore.tox_callback_friend_request(self._tox_pointer, self.friend_request_cb, c_void_p(user_data)) + Tox.libtoxcore.tox_callback_friend_request(self._tox_pointer, self.friend_request_cb) - def callback_friend_message(self, callback, user_data): + def callback_friend_message(self, callback): """ Set the callback for the `friend_message` event. Pass None to unset. @@ -1033,11 +1034,10 @@ class Tox: The message data (c_char_p) they sent, The size (c_size_t) of the message byte array. pointer (c_void_p) to user_data - :param user_data: pointer (c_void_p) to user data """ c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_int, c_char_p, c_size_t, c_void_p) self.friend_message_cb = c_callback(callback) - Tox.libtoxcore.tox_callback_friend_message(self._tox_pointer, self.friend_message_cb, c_void_p(user_data)) + Tox.libtoxcore.tox_callback_friend_message(self._tox_pointer, self.friend_message_cb) # ----------------------------------------------------------------------------------------------------------------- # File transmission: common between sending and receiving @@ -1095,7 +1095,7 @@ class Tox: elif tox_err_file_control == TOX_ERR_FILE_CONTROL['SENDQ']: raise RuntimeError('Packet queue is full.') - def callback_file_recv_control(self, callback, user_data): + def callback_file_recv_control(self, callback): """ Set the callback for the `file_recv_control` event. Pass NULL to unset. @@ -1110,12 +1110,11 @@ class Tox: The friend-specific file number (c_uint32) the data received is associated with. The file control (TOX_FILE_CONTROL) command received. pointer (c_void_p) to user_data - :param user_data: pointer (c_void_p) to user data """ c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_int, c_void_p) self.file_recv_control_cb = c_callback(callback) Tox.libtoxcore.tox_callback_file_recv_control(self._tox_pointer, - self.file_recv_control_cb, user_data) + self.file_recv_control_cb) def file_seek(self, friend_number, file_number, position): """ @@ -1285,7 +1284,7 @@ class Tox: elif tox_err_file_send_chunk == TOX_ERR_FILE_SEND_CHUNK['WRONG_POSITION']: raise ArgumentError('Position parameter was wrong.') - def callback_file_chunk_request(self, callback, user_data): + def callback_file_chunk_request(self, callback): """ Set the callback for the `file_chunk_request` event. Pass None to unset. @@ -1311,17 +1310,16 @@ class Tox: The file or stream position (c_uint64) from which to continue reading. The number of bytes (c_size_t) requested for the current chunk. pointer (c_void_p) to user_data - :param user_data: pointer (c_void_p) to user data """ c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint64, c_size_t, c_void_p) self.file_chunk_request_cb = c_callback(callback) - self.libtoxcore.tox_callback_file_chunk_request(self._tox_pointer, self.file_chunk_request_cb, user_data) + self.libtoxcore.tox_callback_file_chunk_request(self._tox_pointer, self.file_chunk_request_cb) # ----------------------------------------------------------------------------------------------------------------- # File transmission: receiving # ----------------------------------------------------------------------------------------------------------------- - def callback_file_recv(self, callback, user_data): + def callback_file_recv(self, callback): """ Set the callback for the `file_recv` event. Pass None to unset. @@ -1341,13 +1339,12 @@ class Tox: send request. Size in bytes (c_size_t) of the filename. pointer (c_void_p) to user_data - :param user_data: pointer (c_void_p) to user data """ - c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint32, c_uint64, c_char_p, c_size_t, c_void_p) + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint32, c_uint64, c_char_p, c_size_t) self.file_recv_cb = c_callback(callback) - self.libtoxcore.tox_callback_file_recv(self._tox_pointer, self.file_recv_cb, user_data) + self.libtoxcore.tox_callback_file_recv(self._tox_pointer, self.file_recv_cb) - def callback_file_recv_chunk(self, callback, user_data): + def callback_file_recv_chunk(self, callback): """ Set the callback for the `file_recv_chunk` event. Pass NULL to unset. @@ -1368,11 +1365,10 @@ class Tox: A byte array (c_char_p) containing the received chunk. The length (c_size_t) of the received chunk. pointer (c_void_p) to user_data - :param user_data: pointer (c_void_p) to user data """ c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint64, POINTER(c_uint8), c_size_t, c_void_p) self.file_recv_chunk_cb = c_callback(callback) - self.libtoxcore.tox_callback_file_recv_chunk(self._tox_pointer, self.file_recv_chunk_cb, user_data) + self.libtoxcore.tox_callback_file_recv_chunk(self._tox_pointer, self.file_recv_chunk_cb) # ----------------------------------------------------------------------------------------------------------------- # Low-level custom packet sending and receiving @@ -1453,7 +1449,7 @@ class Tox: elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['SENDQ']: raise RuntimeError('Packet queue is full.') - def callback_friend_lossy_packet(self, callback, user_data): + def callback_friend_lossy_packet(self, callback): """ Set the callback for the `friend_lossy_packet` event. Pass NULL to unset. @@ -1463,13 +1459,12 @@ class Tox: A byte array (c_uint8 array) containing the received packet data, length (c_size_t) - The length of the packet data byte array, pointer (c_void_p) to user_data - :param user_data: pointer (c_void_p) to user data """ c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t, c_void_p) self.friend_lossy_packet_cb = c_callback(callback) - self.libtoxcore.tox_callback_friend_lossy_packet(self._tox_pointer, self.friend_lossy_packet_cb, user_data) + self.libtoxcore.tox_callback_friend_lossy_packet(self._tox_pointer, self.friend_lossy_packet_cb) - def callback_friend_lossless_packet(self, callback, user_data): + def callback_friend_lossless_packet(self, callback): """ Set the callback for the `friend_lossless_packet` event. Pass NULL to unset. @@ -1479,12 +1474,10 @@ class Tox: A byte array (c_uint8 array) containing the received packet data, length (c_size_t) - The length of the packet data byte array, pointer (c_void_p) to user_data - :param user_data: pointer (c_void_p) to user data """ c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t, c_void_p) self.friend_lossless_packet_cb = c_callback(callback) - self.libtoxcore.tox_callback_friend_lossless_packet(self._tox_pointer, self.friend_lossless_packet_cb, - user_data) + self.libtoxcore.tox_callback_friend_lossless_packet(self._tox_pointer, self.friend_lossless_packet_cb) # ----------------------------------------------------------------------------------------------------------------- # Low-level network information From fa3529f5f27db7720335a1da28245e404e172d9b Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 25 May 2018 16:55:36 +0300 Subject: [PATCH 072/138] fixed broken ft callback --- toxygen/wrapper/tox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index 9d955a4..b4c401c 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -1340,7 +1340,7 @@ class Tox: Size in bytes (c_size_t) of the filename. pointer (c_void_p) to user_data """ - c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint32, c_uint64, c_char_p, c_size_t) + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint32, c_uint64, c_char_p, c_size_t, c_void_p) self.file_recv_cb = c_callback(callback) self.libtoxcore.tox_callback_file_recv(self._tox_pointer, self.file_recv_cb) From 1c80b4fd7dfdcfe6d3341f20506f04df8e479668 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 26 May 2018 16:27:30 +0300 Subject: [PATCH 073/138] process group creation fail --- toxygen/groups/groups_service.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index 0e9bef7..454efda 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -22,7 +22,8 @@ class GroupsService(tox_save.ToxSave): def create_new_gc(self, name, privacy_state): group_number = self._tox.group_new(privacy_state, name.encode('utf-8')) - self._add_new_group_by_number(group_number) + if group_number != -1: + self._add_new_group_by_number(group_number) def join_gc_by_id(self, chat_id, password): group_number = self._tox.group_join(chat_id, password) From 56731be79dd4becf1f35dd462ec54ee23d7e7d02 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 2 Jun 2018 20:20:57 +0300 Subject: [PATCH 074/138] minor ui issues fixed --- toxygen/bootstrap/nodes.json | 2 +- toxygen/ui/groups_widgets.py | 1 + toxygen/ui/views/login_screen.ui | 12 ++++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/toxygen/bootstrap/nodes.json b/toxygen/bootstrap/nodes.json index f9574dc..c71642b 100644 --- a/toxygen/bootstrap/nodes.json +++ b/toxygen/bootstrap/nodes.json @@ -1 +1 @@ -{"nodes":[{"ipv4":"127.0.0.1","ipv6":"-","port":33445,"public_key":"82B9E28CF62A2D78D83BAC452CD18778F1F36B7BDF2989A3B8927D86D09C2E3D","status_udp":true,"status_tcp":true}]} \ No newline at end of file +{"nodes":[{"ipv4":"127.0.0.1","ipv6":"-","port":33445,"public_key":"AB38C55C594B9D6BF23A896345DB832722146FCAA120EFA0EE721B7BFB4DB83F","status_udp":true,"status_tcp":true}]} \ No newline at end of file diff --git a/toxygen/ui/groups_widgets.py b/toxygen/ui/groups_widgets.py index c6d0248..d666614 100644 --- a/toxygen/ui/groups_widgets.py +++ b/toxygen/ui/groups_widgets.py @@ -45,6 +45,7 @@ class JoinGroupScreen(CenteredWidget): self._groups_service = groups_service uic.loadUi(util.get_views_path('join_group_screen'), self) self.center() + self._update_ui() def _update_ui(self): self._retranslate_ui() diff --git a/toxygen/ui/views/login_screen.ui b/toxygen/ui/views/login_screen.ui index a4c205c..524b7b7 100644 --- a/toxygen/ui/views/login_screen.ui +++ b/toxygen/ui/views/login_screen.ui @@ -10,6 +10,18 @@ 200 + + + 400 + 200 + + + + + 400 + 200 + + Form From 41de31549678fc5f28f6f3e26a83ac5df36cdcc4 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 3 Jun 2018 21:18:22 +0300 Subject: [PATCH 075/138] Filtration fixed --- toxygen/contacts/contact.py | 8 ++++++++ toxygen/contacts/contacts_manager.py | 9 +++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index d7fe35e..fb124dd 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -320,3 +320,11 @@ class Contact(basecontact.BaseContact): def get_context_menu_generator(self): return BaseContactMenuGenerator(self) + + # ----------------------------------------------------------------------------------------------------------------- + # Filtration support + # ----------------------------------------------------------------------------------------------------------------- + + def set_widget(self, widget): + self._widget = widget + self.init_widget() diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 74452e1..08ee678 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -154,7 +154,7 @@ class ContactsManager(ToxSave): def filtration_and_sorting(self, sorting=0, filter_str=''): """ Filtration of friends list - :param sorting: 0 - no sort, 1 - online only, 2 - online first, 4 - by name + :param sorting: 0 - no sorting, 1 - online only, 2 - online first, 4 - by name :param filter_str: show contacts which name contains this substring """ # TODO: simplify? @@ -183,9 +183,10 @@ class ContactsManager(ToxSave): part1 = sorted(part1, key=lambda x: x.number) part2 = sorted(part2, key=lambda x: x.number) self._contacts = part1 + part2 - # self._screen.friends_list.clear() - # for contact in self._contacts: - # contact.set_widget(self.create_friend_item()) + 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): friend.visibility = (friend.status is not None or not (sorting & 1)) and (filter_str in friend.name.lower()) friend.visibility = friend.visibility or friend.messages or friend.actions From 8809ef1f6e8b145b97033d0fd2aa346f29eee5eb Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 5 Jun 2018 23:58:14 +0300 Subject: [PATCH 076/138] minor ui fixes --- toxygen/ui/groups_widgets.py | 1 + toxygen/ui/login_screen.py | 2 +- toxygen/ui/views/login_screen.ui | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/toxygen/ui/groups_widgets.py b/toxygen/ui/groups_widgets.py index d666614..aa91935 100644 --- a/toxygen/ui/groups_widgets.py +++ b/toxygen/ui/groups_widgets.py @@ -26,6 +26,7 @@ class CreateGroupScreen(CenteredWidget): 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): name = self.groupNameLineEdit.text() diff --git a/toxygen/ui/login_screen.py b/toxygen/ui/login_screen.py index 7198c36..35e33b5 100644 --- a/toxygen/ui/login_screen.py +++ b/toxygen/ui/login_screen.py @@ -49,7 +49,7 @@ class LoginScreen(CenteredWidget, DialogWithResult): def _update_ui(self): self.profileNameLineEdit = LineEditWithEnterSupport(self._create_profile, self) - self.profileNameLineEdit.setGeometry(QtCore.QRect(20, 100, 170, 30)) + 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) diff --git a/toxygen/ui/views/login_screen.ui b/toxygen/ui/views/login_screen.ui index 524b7b7..50ca1e0 100644 --- a/toxygen/ui/views/login_screen.ui +++ b/toxygen/ui/views/login_screen.ui @@ -67,9 +67,9 @@ - 20 + 10 110 - 150 + 160 27 @@ -119,9 +119,9 @@ - 20 + 10 110 - 150 + 160 27 From b2ecf5314e45777c00cbe3ff25f8d64762cdc60c Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 23 Jun 2018 00:20:13 +0300 Subject: [PATCH 077/138] gc invite - support of gc name added --- toxygen/groups/groups_service.py | 6 +++--- toxygen/middleware/callbacks.py | 5 +++-- toxygen/wrapper/tox.py | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index 454efda..83f6a4e 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -49,10 +49,10 @@ class GroupsService(tox_save.ToxSave): def invite_friend(self, friend_number, group_number): self._tox.group_invite_friend(group_number, friend_number) - def process_group_invite(self, friend_number, invite_data): + def process_group_invite(self, friend_number, group_name, invite_data): friend = self._get_friend(friend_number) - text = util_ui.tr('Friend {} invites you to group. Accept?') - if util_ui.question(text.format(friend.name), util_ui.tr('Group invite')): + text = util_ui.tr('Friend {} invites you to group "{}". Accept?') + if util_ui.question(text.format(friend.name, group_name), util_ui.tr('Group invite')): self.join_gc_via_invite(invite_data, friend_number, None) # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 6855adc..41a5717 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -382,9 +382,10 @@ def group_message(window, tray, tox, messenger, settings, profile): def group_invite(groups_service): - def wrapped(tox, friend_number, invite_data, length, user_data): + def wrapped(tox, friend_number, invite_data, length, group_name, group_name_length, user_data): + group_name = bytes(group_name[:group_name_length]) invoke_in_main_thread(groups_service.process_group_invite, - friend_number, + friend_number, str(group_name, 'utf-8'), bytes(invite_data[:length])) return wrapped diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index b4c401c..96e7fd5 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -2199,7 +2199,8 @@ class Tox: user_data - user data """ - c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t, c_void_p) + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t, + POINTER(c_uint8), c_size_t, c_void_p) self.group_invite_cb = c_callback(callback) Tox.libtoxcore.tox_callback_group_invite(self._tox_pointer, self.group_invite_cb, user_data) From 47c115e6993e20a501cf711408e98a843fb1d25e Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 30 Jun 2018 14:56:41 +0300 Subject: [PATCH 078/138] avatars support fixed --- .gitignore | 2 ++ toxygen/app.py | 6 +++--- toxygen/contacts/basecontact.py | 5 ++--- toxygen/contacts/contact.py | 4 ++-- toxygen/contacts/friend.py | 4 ++-- toxygen/contacts/friend_factory.py | 5 ++--- toxygen/contacts/group_chat.py | 4 ++-- toxygen/contacts/group_factory.py | 5 ++--- toxygen/contacts/group_peer_contact.py | 4 ++-- toxygen/contacts/profile.py | 3 +-- toxygen/file_transfers/file_transfers.py | 8 ++------ 11 files changed, 22 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index f31ca4c..0a8182a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ html Toxygen.egg-info *.tox .cache +*.db + diff --git a/toxygen/app.py b/toxygen/app.py index 60bb8bf..f3fd429 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -332,11 +332,11 @@ class App: 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._friend_factory = FriendFactory(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_factory = GroupFactory(self._settings, self._tox, db, contact_items_factory) self._contacts_provider = ContactProvider(self._tox, self._friend_factory, self._group_factory) - self._profile = Profile(self._profile_manager, self._tox, self._ms, self._contacts_provider, self._reset) + self._profile = Profile(self._tox, self._ms, self._contacts_provider, self._reset) self._plugin_loader = PluginLoader(self._tox, self._toxes, self._profile, self._settings) history = None messages_items_factory = MessagesItemsFactory(self._settings, self._plugin_loader, self._smiley_loader, diff --git a/toxygen/contacts/basecontact.py b/toxygen/contacts/basecontact.py index 0357809..0a7a456 100644 --- a/toxygen/contacts/basecontact.py +++ b/toxygen/contacts/basecontact.py @@ -14,14 +14,13 @@ class BaseContact: Base class for all contacts. """ - def __init__(self, profile_manager, name, status_message, widget, tox_id): + def __init__(self, name, status_message, widget, tox_id): """ :param name: name, example: 'Toxygen user' :param status_message: status message, example: 'Toxing on Toxygen' :param widget: ContactItem instance :param tox_id: tox id of contact """ - self._profile_manager = profile_manager self._name, self._status_message = name, status_message self._status, self._widget = None, widget self._tox_id = tox_id @@ -144,7 +143,7 @@ class BaseContact: return avatar_path def get_contact_avatar_path(self): - directory = util.join_path(self._profile_manager.get_dir(), 'avatars') + directory = util.join_path(Settings.get_default_path(), 'avatars') return util.join_path(directory, '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])) diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index fb124dd..4969b73 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -12,12 +12,12 @@ class Contact(basecontact.BaseContact): Properties: number, message getter, history etc. Base class for friend and gc classes """ - def __init__(self, profile_manager, message_getter, number, name, status_message, widget, tox_id): + def __init__(self, message_getter, number, name, status_message, widget, tox_id): """ :param message_getter: gets messages from db :param number: number of friend. """ - super().__init__(profile_manager, name, status_message, widget, tox_id) + super().__init__(name, status_message, widget, tox_id) self._number = number self._new_messages = False self._visible = True diff --git a/toxygen/contacts/friend.py b/toxygen/contacts/friend.py index 5c8eabb..142185a 100644 --- a/toxygen/contacts/friend.py +++ b/toxygen/contacts/friend.py @@ -9,8 +9,8 @@ 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) + 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 self._typing_notification_handler = common.FriendTypingNotificationHandler(number) diff --git a/toxygen/contacts/friend_factory.py b/toxygen/contacts/friend_factory.py index 8ebafd6..a85f4c9 100644 --- a/toxygen/contacts/friend_factory.py +++ b/toxygen/contacts/friend_factory.py @@ -4,9 +4,8 @@ from common.tox_save import ToxSave class FriendFactory(ToxSave): - def __init__(self, profile_manager, settings, tox, db, items_factory): + def __init__(self, settings, tox, db, items_factory): super().__init__(tox) - self._profile_manager = profile_manager self._settings = settings self._db = db self._items_factory = items_factory @@ -27,7 +26,7 @@ class FriendFactory(ToxSave): 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 = Friend(message_getter, friend_number, name, status_message, item, tox_id) friend.set_alias(alias) return friend diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index b7429d4..0feed0f 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -8,8 +8,8 @@ from common.tox_save import ToxSave class GroupChat(contact.Contact, ToxSave): - def __init__(self, tox, profile_manager, message_getter, number, name, status_message, widget, tox_id): - super().__init__(profile_manager, message_getter, number, name, status_message, widget, tox_id) + def __init__(self, tox, message_getter, number, name, status_message, widget, tox_id): + super().__init__(message_getter, number, name, status_message, widget, tox_id) ToxSave.__init__(self, tox) self.set_status(constants.TOX_USER_STATUS['NONE']) self._peers = [] diff --git a/toxygen/contacts/group_factory.py b/toxygen/contacts/group_factory.py index 8db3e9a..0bd3285 100644 --- a/toxygen/contacts/group_factory.py +++ b/toxygen/contacts/group_factory.py @@ -4,9 +4,8 @@ from common.tox_save import ToxSave class GroupFactory(ToxSave): - def __init__(self, profile_manager, settings, tox, db, items_factory): + def __init__(self, settings, tox, db, items_factory): super().__init__(tox) - self._profile_manager = profile_manager self._settings = settings self._db = db self._items_factory = items_factory @@ -27,7 +26,7 @@ class GroupFactory(ToxSave): 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) - group = GroupChat(self._tox, self._profile_manager, message_getter, group_number, name, status_message, + group = GroupChat(self._tox, message_getter, group_number, name, status_message, item, tox_id) group.set_alias(alias) diff --git a/toxygen/contacts/group_peer_contact.py b/toxygen/contacts/group_peer_contact.py index 1c51f67..6480985 100644 --- a/toxygen/contacts/group_peer_contact.py +++ b/toxygen/contacts/group_peer_contact.py @@ -3,8 +3,8 @@ import contacts.contact class GroupPeerContact(contacts.contact.Contact): - def __init__(self, profile_manager, message_getter, peer_number, name, status_messsage, widget, tox_id, group_pk): - super().__init__(profile_manager, message_getter, peer_number, name, status_messsage, widget, tox_id) + def __init__(self, message_getter, peer_number, name, status_messsage, widget, tox_id, group_pk): + super().__init__(message_getter, peer_number, name, status_messsage, widget, tox_id) self._group_pk = group_pk def get_group_pk(self): diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 3529be4..847039b 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -8,13 +8,12 @@ class Profile(basecontact.BaseContact, tox_save.ToxSave): """ Profile of current toxygen user. Contains friends list, tox instance """ - def __init__(self, profile_manager, tox, screen, contacts_provider, reset_action): + def __init__(self, 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.user_info, diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py index 61a379f..46a777f 100644 --- a/toxygen/file_transfers/file_transfers.py +++ b/toxygen/file_transfers/file_transfers.py @@ -50,9 +50,6 @@ class FileTransfer: self._finished_event = Event() self._file_id = self._file = None - def set_tox(self, tox): - self._tox = tox - def set_state_changed_handler(self, handler): self._state_changed_event += handler @@ -344,11 +341,10 @@ class ReceiveAvatar(ReceiveTransfer): self.send_control(TOX_FILE_CONTROL['RESUME']) def write_chunk(self, position, data): - super().write_chunk(position, data) - if self.state: + if data is None: avatar_path = self._path[:-4] if exists(avatar_path): chdir(dirname(avatar_path)) remove(avatar_path) rename(self._path, avatar_path) - self._finished() + super().write_chunk(position, data) From 8411f08348008f5c8f3bef7e1d023aefe3801588 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 30 Jun 2018 15:23:04 +0300 Subject: [PATCH 079/138] bug with avatars fixed. bug with contacts statuses during reconnection was fixed --- toxygen/app.py | 1 + toxygen/contacts/contacts_manager.py | 8 ++++++++ toxygen/file_transfers/file_transfers_handler.py | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/toxygen/app.py b/toxygen/app.py index f3fd429..db3ddc8 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -306,6 +306,7 @@ class App: 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) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 08ee678..46e98a2 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -55,6 +55,14 @@ class ContactsManager(ToxSave): 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 # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index 64a2bfe..d462c89 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -254,7 +254,7 @@ class FileTransfersHandler(ToxSave): if ra.state != FILE_TRANSFER_STATE['CANCELLED']: self._file_transfers[(friend_number, file_number)] = ra ra.set_transfer_finished_handler(self.transfer_finished) - else: + elif not size: friend.reset_avatar(self._settings['identicons']) def _send_avatar_to_contacts(self): From 04f0aef3df92a7c26851cbc42d081634fc5943e0 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 30 Jun 2018 18:49:25 +0300 Subject: [PATCH 080/138] Threads fixed --- toxygen/middleware/callbacks.py | 10 ++-------- toxygen/middleware/threads.py | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 41a5717..513cdd2 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -198,10 +198,7 @@ def file_recv_chunk(file_transfer_handler): """ def wrapped(tox, friend_number, file_number, position, chunk, length, user_data): chunk = chunk[:length] if length else None - if length: - execute(file_transfer_handler.incoming_chunk, friend_number, file_number, position, chunk) - else: - invoke_in_main_thread(file_transfer_handler.incoming_chunk, friend_number, file_number, position, chunk) + execute(file_transfer_handler.incoming_chunk, friend_number, file_number, position, chunk) return wrapped @@ -211,10 +208,7 @@ def file_chunk_request(file_transfer_handler): Outgoing chunk """ def wrapped(tox, friend_number, file_number, position, size, user_data): - if size: - execute(file_transfer_handler.outgoing_chunk, friend_number, file_number, position, size) - else: - invoke_in_main_thread(file_transfer_handler.outgoing_chunk, friend_number, file_number, position, size) + invoke_in_main_thread(file_transfer_handler.outgoing_chunk, friend_number, file_number, position, size) return wrapped diff --git a/toxygen/middleware/threads.py b/toxygen/middleware/threads.py index 2e432b6..192f723 100644 --- a/toxygen/middleware/threads.py +++ b/toxygen/middleware/threads.py @@ -3,8 +3,13 @@ import threading import queue from utils import util import time +from PyQt5 import QtCore +# ----------------------------------------------------------------------------------------------------------------- +# Base threads +# ----------------------------------------------------------------------------------------------------------------- + class BaseThread(threading.Thread): def __init__(self): @@ -16,6 +21,21 @@ class BaseThread(threading.Thread): 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): @@ -53,7 +73,7 @@ class InitThread(BaseThread): time.sleep(5) -class ToxIterateThread(BaseThread): +class ToxIterateThread(BaseQThread): def __init__(self, tox): super().__init__() @@ -65,7 +85,7 @@ class ToxIterateThread(BaseThread): time.sleep(self._tox.iteration_interval() / 1000) -class ToxAVIterateThread(BaseThread): +class ToxAVIterateThread(BaseQThread): def __init__(self, toxav): super().__init__() @@ -77,6 +97,10 @@ class ToxAVIterateThread(BaseThread): time.sleep(self._toxav.iteration_interval() / 1000) +# ----------------------------------------------------------------------------------------------------------------- +# File transfers thread +# ----------------------------------------------------------------------------------------------------------------- + class FileTransfersThread(BaseThread): def __init__(self): @@ -115,6 +139,10 @@ 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()) From a0cae147276b40c928570b3b6d2af16a1eddb137 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 30 Jun 2018 18:57:41 +0300 Subject: [PATCH 081/138] travis.yml updated --- .travis.yml | 7 +++---- MANIFEST.in | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8de5702..d4a7fae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,15 +38,14 @@ before_script: - sudo ldconfig - cd .. # Toxcore - - git clone https://github.com/ingvar1995/toxcore.git --branch=new_gc + - git clone https://github.com/ingvar1995/toxcore.git --branch=ngc_rebase - cd toxcore - - autoreconf -if - - ./configure + - mkdir _build && cd _build + - cmake .. - make -j$(nproc) - sudo make install - echo '/usr/local/lib/' | sudo tee -a /etc/ld.so.conf.d/locallib.conf - sudo ldconfig - - cd .. script: - py.test tests/travis.py - py.test tests/tests.py diff --git a/MANIFEST.in b/MANIFEST.in index 6629fb6..89e57c6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -16,4 +16,4 @@ include toxygen/styles/*.qss include toxygen/translations/*.qm include toxygen/libs/libtox.dll include toxygen/libs/libsodium.a -include toxygen/nodes.json +include toxygen/bootstrap/nodes.json From 595c35a6b874025a264514d2047cf01c130a4793 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 30 Jun 2018 19:54:08 +0300 Subject: [PATCH 082/138] network settings screen converted --- .travis.yml | 1 + toxygen/main.py | 10 +- toxygen/styles/dark_style.qss | 5 + toxygen/styles/style.qss | 5 + toxygen/ui/menu.py | 119 ++++++------- toxygen/ui/views/network_settings_screen.ui | 180 ++++++++++++++++++++ 6 files changed, 245 insertions(+), 75 deletions(-) create mode 100644 toxygen/ui/views/network_settings_screen.ui diff --git a/.travis.yml b/.travis.yml index d4a7fae..378e5e5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,6 +46,7 @@ before_script: - sudo make install - echo '/usr/local/lib/' | sudo tee -a /etc/ld.so.conf.d/locallib.conf - sudo ldconfig + - cd .. script: - py.test tests/travis.py - py.test tests/tests.py diff --git a/toxygen/main.py b/toxygen/main.py index 232ad93..eca3ac3 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -9,7 +9,7 @@ __version__ = '0.5.0' def clean(): - """Removes all windows libs from libs folder""" + """Removes libs folder""" directory = util.get_libs_directory() util.remove(directory) @@ -25,10 +25,10 @@ def print_toxygen_version(): def main(): parser = argparse.ArgumentParser() parser.add_argument('--version', action='store_true', help='Prints Toxygen version') - parser.add_argument('--clean', action='store_true', help='Deletes toxcore libs from libs folder') - parser.add_argument('--reset', action='store_true', help='Resets default profile') - parser.add_argument('--uri', help='Adds specified TOX ID to friends') - parser.add_argument('profile', nargs='?', default=None, help='Path to TOX profile') + parser.add_argument('--clean', action='store_true', help='Delete toxcore libs from libs folder') + parser.add_argument('--reset', action='store_true', help='Reset default profile') + parser.add_argument('--uri', help='Add specified Tox ID to friends') + parser.add_argument('profile', nargs='?', default=None, help='Path to Tox profile') args = parser.parse_args() if args.version: diff --git a/toxygen/styles/dark_style.qss b/toxygen/styles/dark_style.qss index 714f946..3b2837b 100644 --- a/toxygen/styles/dark_style.qss +++ b/toxygen/styles/dark_style.qss @@ -1322,3 +1322,8 @@ ClickableLabel:hover { background-color: #4A4949; } + +#warningLabel +{ + color: #BC1C1C; +} diff --git a/toxygen/styles/style.qss b/toxygen/styles/style.qss index 26fbaf2..956ee63 100644 --- a/toxygen/styles/style.qss +++ b/toxygen/styles/style.qss @@ -27,3 +27,8 @@ MessageEdit { background-color: transparent; } + +#warningLabel +{ + color: #BC1C1C; +} diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index 6c9218c..07fbb85 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -254,82 +254,61 @@ class NetworkSettings(CenteredWidget): super().__init__() self._settings = settings self._reset = reset - self.initUI() - self.center() + uic.loadUi(get_views_path('network_settings_screen'), self) + self._update_ui() - def initUI(self): - self.setObjectName("NetworkSettings") - self.resize(300, 400) - self.setMinimumSize(QtCore.QSize(300, 400)) - self.setMaximumSize(QtCore.QSize(300, 400)) - self.setBaseSize(QtCore.QSize(300, 400)) - self.ipv = QtWidgets.QCheckBox(self) - self.ipv.setGeometry(QtCore.QRect(20, 10, 97, 22)) - self.ipv.setObjectName("ipv") - self.udp = QtWidgets.QCheckBox(self) - self.udp.setGeometry(QtCore.QRect(150, 10, 97, 22)) - self.udp.setObjectName("udp") - self.proxy = QtWidgets.QCheckBox(self) - self.proxy.setGeometry(QtCore.QRect(20, 40, 97, 22)) - self.http = QtWidgets.QCheckBox(self) - self.http.setGeometry(QtCore.QRect(20, 70, 97, 22)) - self.proxy.setObjectName("proxy") - self.proxyip = LineEdit(self) - self.proxyip.setGeometry(QtCore.QRect(40, 130, 231, 27)) - self.proxyip.setObjectName("proxyip") - self.proxyport = LineEdit(self) - self.proxyport.setGeometry(QtCore.QRect(40, 190, 231, 27)) - self.proxyport.setObjectName("proxyport") - self.label = QtWidgets.QLabel(self) - self.label.setGeometry(QtCore.QRect(40, 100, 66, 17)) - self.label_2 = QtWidgets.QLabel(self) - self.label_2.setGeometry(QtCore.QRect(40, 165, 66, 17)) - self.reconnect = QtWidgets.QPushButton(self) - self.reconnect.setGeometry(QtCore.QRect(40, 230, 231, 30)) - self.reconnect.clicked.connect(self.restart_core) - self.ipv.setChecked(self._settings['ipv6_enabled']) - self.udp.setChecked(self._settings['udp_enabled']) - self.proxy.setChecked(self._settings['proxy_type']) - self.proxyip.setText(self._settings['proxy_host']) - self.proxyport.setText(str(self._settings['proxy_port'])) - self.http.setChecked(self._settings['proxy_type'] == 1) - self.warning = QtWidgets.QLabel(self) - self.warning.setGeometry(QtCore.QRect(5, 270, 290, 60)) - self.warning.setStyleSheet('QLabel { color: #BC1C1C; }') - self.nodes = QtWidgets.QCheckBox(self) - self.nodes.setGeometry(QtCore.QRect(20, 350, 270, 22)) - self.nodes.setChecked(self._settings['download_nodes_list']) - self.retranslateUi() - self.proxy.stateChanged.connect(lambda x: self.activate()) - self.activate() - QtCore.QMetaObject.connectSlotsByName(self) + 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 retranslateUi(self): + def _retranslate_ui(self): self.setWindowTitle(util_ui.tr("Network settings")) - self.ipv.setText(util_ui.tr("IPv6")) - self.udp.setText(util_ui.tr("UDP")) - self.proxy.setText(util_ui.tr("Proxy")) - self.label.setText(util_ui.tr("IP:")) - self.label_2.setText(util_ui.tr("Port:")) - self.reconnect.setText(util_ui.tr("Restart TOX core")) - self.http.setText(util_ui.tr("HTTP")) - self.nodes.setText(util_ui.tr("Download nodes list from tox.chat")) - self.warning.setText(util_ui.tr("WARNING:\nusing proxy with enabled UDP\ncan produce IP leak")) + 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(self): - bl = self.proxy.isChecked() - self.proxyip.setEnabled(bl) - self.http.setEnabled(bl) - self.proxyport.setEnabled(bl) + 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): + def _restart_core(self): try: - self._settings['ipv6_enabled'] = self.ipv.isChecked() - self._settings['udp_enabled'] = self.udp.isChecked() - self._settings['proxy_type'] = 2 - int(self.http.isChecked()) if self.proxy.isChecked() else 0 - self._settings['proxy_host'] = str(self.proxyip.text()) - self._settings['proxy_port'] = int(self.proxyport.text()) - self._settings['download_nodes_list'] = self.nodes.isChecked() + 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() diff --git a/toxygen/ui/views/network_settings_screen.ui b/toxygen/ui/views/network_settings_screen.ui new file mode 100644 index 0000000..aacf1e0 --- /dev/null +++ b/toxygen/ui/views/network_settings_screen.ui @@ -0,0 +1,180 @@ + + + Form + + + + 0 + 0 + 400 + 500 + + + + + 400 + 500 + + + + + 400 + 500 + + + + Form + + + + + 30 + 20 + 150 + 30 + + + + CheckBox + + + + + + 210 + 20 + 150 + 30 + + + + CheckBox + + + + + + 30 + 140 + 150 + 30 + + + + CheckBox + + + + + + 30 + 190 + 150 + 25 + + + + RadioButton + + + + + + 30 + 230 + 150 + 25 + + + + RadioButton + + + + + + 30 + 100 + 150 + 30 + + + + CheckBox + + + + + + 30 + 280 + 60 + 20 + + + + TextLabel + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 30 + 330 + 60 + 20 + + + + TextLabel + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 30 + 370 + 340 + 40 + + + + PushButton + + + + + + 30 + 60 + 340 + 30 + + + + CheckBox + + + + + + 30 + 420 + 340 + 65 + + + + TextLabel + + + + + + From 0adb9c1e5219513051ee480fbf3b267468798a43 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 30 Jun 2018 20:02:44 +0300 Subject: [PATCH 083/138] fixed wrong avatars directory path --- .travis.yml | 1 + toxygen/user_data/profile_manager.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 378e5e5..a4011e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,6 +47,7 @@ before_script: - echo '/usr/local/lib/' | sudo tee -a /etc/ld.so.conf.d/locallib.conf - sudo ldconfig - cd .. + - cd .. script: - py.test tests/travis.py - py.test tests/tests.py diff --git a/toxygen/user_data/profile_manager.py b/toxygen/user_data/profile_manager.py index 3324fce..6643d68 100644 --- a/toxygen/user_data/profile_manager.py +++ b/toxygen/user_data/profile_manager.py @@ -13,7 +13,7 @@ class ProfileManager: self._path = path self._directory = os.path.dirname(path) # create /avatars if not exists: - avatars_directory = util.join_path(self._directory, 'avatars') + avatars_directory = util.join_path(Settings.get_default_path(), 'avatars') if not os.path.exists(avatars_directory): os.makedirs(avatars_directory) From bc485372090f40b529086581a2095f03d83ae130 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 2 Jul 2018 22:50:46 +0300 Subject: [PATCH 084/138] Revert "avatars support fixed" This reverts commit 47c115e6993e20a501cf711408e98a843fb1d25e. --- .gitignore | 2 -- toxygen/app.py | 6 +++--- toxygen/contacts/basecontact.py | 5 +++-- toxygen/contacts/contact.py | 4 ++-- toxygen/contacts/friend.py | 4 ++-- toxygen/contacts/friend_factory.py | 5 +++-- toxygen/contacts/group_chat.py | 4 ++-- toxygen/contacts/group_factory.py | 5 +++-- toxygen/contacts/group_peer_contact.py | 4 ++-- toxygen/contacts/profile.py | 3 ++- toxygen/file_transfers/file_transfers.py | 8 ++++++-- 11 files changed, 28 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 0a8182a..f31ca4c 100644 --- a/.gitignore +++ b/.gitignore @@ -24,5 +24,3 @@ html Toxygen.egg-info *.tox .cache -*.db - diff --git a/toxygen/app.py b/toxygen/app.py index db3ddc8..3d20389 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -333,11 +333,11 @@ class App: db = Database(self._path.replace('.tox', '.db'), self._toxes) contact_items_factory = ContactItemsFactory(self._settings, self._ms) - self._friend_factory = FriendFactory(self._settings, + self._friend_factory = FriendFactory(self._profile_manager, self._settings, self._tox, db, contact_items_factory) - self._group_factory = GroupFactory(self._settings, self._tox, db, contact_items_factory) + self._group_factory = GroupFactory(self._profile_manager, self._settings, self._tox, db, contact_items_factory) self._contacts_provider = ContactProvider(self._tox, self._friend_factory, self._group_factory) - self._profile = Profile(self._tox, self._ms, self._contacts_provider, self._reset) + self._profile = Profile(self._profile_manager, self._tox, self._ms, self._contacts_provider, self._reset) self._plugin_loader = PluginLoader(self._tox, self._toxes, self._profile, self._settings) history = None messages_items_factory = MessagesItemsFactory(self._settings, self._plugin_loader, self._smiley_loader, diff --git a/toxygen/contacts/basecontact.py b/toxygen/contacts/basecontact.py index 0a7a456..0357809 100644 --- a/toxygen/contacts/basecontact.py +++ b/toxygen/contacts/basecontact.py @@ -14,13 +14,14 @@ class BaseContact: 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 status_message: status message, example: 'Toxing on Toxygen' :param widget: ContactItem instance :param tox_id: tox id of contact """ + self._profile_manager = profile_manager self._name, self._status_message = name, status_message self._status, self._widget = None, widget self._tox_id = tox_id @@ -143,7 +144,7 @@ class BaseContact: return avatar_path def get_contact_avatar_path(self): - directory = util.join_path(Settings.get_default_path(), 'avatars') + 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])) diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index 4969b73..fb124dd 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -12,12 +12,12 @@ class Contact(basecontact.BaseContact): 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 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._new_messages = False self._visible = True diff --git a/toxygen/contacts/friend.py b/toxygen/contacts/friend.py index 142185a..5c8eabb 100644 --- a/toxygen/contacts/friend.py +++ b/toxygen/contacts/friend.py @@ -9,8 +9,8 @@ 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) + 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) diff --git a/toxygen/contacts/friend_factory.py b/toxygen/contacts/friend_factory.py index a85f4c9..8ebafd6 100644 --- a/toxygen/contacts/friend_factory.py +++ b/toxygen/contacts/friend_factory.py @@ -4,8 +4,9 @@ from common.tox_save import ToxSave class FriendFactory(ToxSave): - def __init__(self, settings, tox, db, items_factory): + 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 @@ -26,7 +27,7 @@ class FriendFactory(ToxSave): 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(message_getter, friend_number, name, status_message, item, tox_id) + friend = Friend(self._profile_manager, message_getter, friend_number, name, status_message, item, tox_id) friend.set_alias(alias) return friend diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index 0feed0f..b7429d4 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -8,8 +8,8 @@ from common.tox_save import ToxSave class GroupChat(contact.Contact, ToxSave): - def __init__(self, tox, message_getter, number, name, status_message, widget, tox_id): - super().__init__(message_getter, number, name, status_message, widget, tox_id) + def __init__(self, tox, profile_manager, message_getter, number, name, status_message, widget, tox_id): + super().__init__(profile_manager, message_getter, number, name, status_message, widget, tox_id) ToxSave.__init__(self, tox) self.set_status(constants.TOX_USER_STATUS['NONE']) self._peers = [] diff --git a/toxygen/contacts/group_factory.py b/toxygen/contacts/group_factory.py index 0bd3285..8db3e9a 100644 --- a/toxygen/contacts/group_factory.py +++ b/toxygen/contacts/group_factory.py @@ -4,8 +4,9 @@ from common.tox_save import ToxSave class GroupFactory(ToxSave): - def __init__(self, settings, tox, db, items_factory): + 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 @@ -26,7 +27,7 @@ class GroupFactory(ToxSave): 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) - group = GroupChat(self._tox, message_getter, group_number, name, status_message, + group = GroupChat(self._tox, self._profile_manager, message_getter, group_number, name, status_message, item, tox_id) group.set_alias(alias) diff --git a/toxygen/contacts/group_peer_contact.py b/toxygen/contacts/group_peer_contact.py index 6480985..1c51f67 100644 --- a/toxygen/contacts/group_peer_contact.py +++ b/toxygen/contacts/group_peer_contact.py @@ -3,8 +3,8 @@ import contacts.contact class GroupPeerContact(contacts.contact.Contact): - def __init__(self, message_getter, peer_number, name, status_messsage, widget, tox_id, group_pk): - super().__init__(message_getter, peer_number, name, status_messsage, widget, tox_id) + def __init__(self, profile_manager, message_getter, peer_number, name, status_messsage, widget, tox_id, group_pk): + super().__init__(profile_manager, message_getter, peer_number, name, status_messsage, widget, tox_id) self._group_pk = group_pk def get_group_pk(self): diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 847039b..3529be4 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -8,12 +8,13 @@ class Profile(basecontact.BaseContact, tox_save.ToxSave): """ Profile of current toxygen user. Contains friends list, tox instance """ - def __init__(self, tox, screen, contacts_provider, reset_action): + 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.user_info, diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py index 46a777f..61a379f 100644 --- a/toxygen/file_transfers/file_transfers.py +++ b/toxygen/file_transfers/file_transfers.py @@ -50,6 +50,9 @@ class FileTransfer: self._finished_event = Event() self._file_id = self._file = None + def set_tox(self, tox): + self._tox = tox + def set_state_changed_handler(self, handler): self._state_changed_event += handler @@ -341,10 +344,11 @@ class ReceiveAvatar(ReceiveTransfer): self.send_control(TOX_FILE_CONTROL['RESUME']) def write_chunk(self, position, data): - if data is None: + super().write_chunk(position, data) + if self.state: avatar_path = self._path[:-4] if exists(avatar_path): chdir(dirname(avatar_path)) remove(avatar_path) rename(self._path, avatar_path) - super().write_chunk(position, data) + self._finished() From e8193afedfc95995b40f80fe261a9a4212e01d9e Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 2 Jul 2018 22:53:07 +0300 Subject: [PATCH 085/138] fixes after revert --- .gitignore | 2 ++ toxygen/contacts/common.py | 20 ++++++++++---------- toxygen/file_transfers/file_transfers.py | 7 ++----- toxygen/user_data/profile_manager.py | 2 +- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index f31ca4c..0a8182a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ html Toxygen.egg-info *.tox .cache +*.db + diff --git a/toxygen/contacts/common.py b/toxygen/contacts/common.py index b37c6a2..4dc25e8 100644 --- a/toxygen/contacts/common.py +++ b/toxygen/contacts/common.py @@ -25,15 +25,15 @@ class FriendTypingNotificationHandler(BaseTypingNotificationHandler): BaseTypingNotificationHandler.DEFAULT_HANDLER = BaseTypingNotificationHandler() -def generate_avatar(tox_id): - foreground = ["rgb(45,79,255)", - "rgb(254,180,44)", - "rgb(226,121,234)", - "rgb(30,179,253)", - "rgb(232,77,65)", - "rgb(49,203,115)", - "rgb(141,69,170)"] - generator = Generator(5, 5, foreground=foreground, background="rgba(42,42,42,0)") - identicon = generator.generate(tox_id, 220, 220, padding=(10, 10, 10, 10)) +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)') + identicon = generator.generate(public_key, 220, 220, padding=(10, 10, 10, 10)) return identicon diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py index 61a379f..c2d1168 100644 --- a/toxygen/file_transfers/file_transfers.py +++ b/toxygen/file_transfers/file_transfers.py @@ -50,9 +50,6 @@ class FileTransfer: self._finished_event = Event() self._file_id = self._file = None - def set_tox(self, tox): - self._tox = tox - def set_state_changed_handler(self, handler): self._state_changed_event += handler @@ -344,11 +341,11 @@ class ReceiveAvatar(ReceiveTransfer): self.send_control(TOX_FILE_CONTROL['RESUME']) def write_chunk(self, position, data): - super().write_chunk(position, data) - if self.state: + if data is None: avatar_path = self._path[:-4] if exists(avatar_path): chdir(dirname(avatar_path)) remove(avatar_path) rename(self._path, avatar_path) self._finished() + super().write_chunk(position, data) diff --git a/toxygen/user_data/profile_manager.py b/toxygen/user_data/profile_manager.py index 6643d68..3324fce 100644 --- a/toxygen/user_data/profile_manager.py +++ b/toxygen/user_data/profile_manager.py @@ -13,7 +13,7 @@ class ProfileManager: self._path = path self._directory = os.path.dirname(path) # create /avatars if not exists: - avatars_directory = util.join_path(Settings.get_default_path(), 'avatars') + avatars_directory = util.join_path(self._directory, 'avatars') if not os.path.exists(avatars_directory): os.makedirs(avatars_directory) From d09609a5e5a2254f82ab4077cbc9e9feccc9ea0d Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 5 Jul 2018 00:26:05 +0300 Subject: [PATCH 086/138] fixes after revert. identicons update --- toxygen/app.py | 5 ++++- toxygen/contacts/common.py | 4 +++- toxygen/file_transfers/file_transfers.py | 1 - 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index 3d20389..3f66e2c 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -299,7 +299,7 @@ class App: self._app.exec_() if p.result is not None: return p.result - raise SystemExit() + self._force_exit() def _reset(self): """ @@ -380,6 +380,9 @@ class App: 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, diff --git a/toxygen/contacts/common.py b/toxygen/contacts/common.py index 4dc25e8..ba5dac6 100644 --- a/toxygen/contacts/common.py +++ b/toxygen/contacts/common.py @@ -1,4 +1,5 @@ from pydenticon import Generator +import hashlib class BaseTypingNotificationHandler: @@ -34,6 +35,7 @@ def generate_avatar(public_key): 'rgb(49,203,115)', 'rgb(141,69,170)'] generator = Generator(5, 5, foreground=foreground, background='rgba(42,42,42,0)') - identicon = generator.generate(public_key, 220, 220, padding=(10, 10, 10, 10)) + digest = hashlib.sha256(public_key.encode('utf-8')).hexdigest() + identicon = generator.generate(digest, 220, 220, padding=(10, 10, 10, 10)) return identicon diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py index c2d1168..46a777f 100644 --- a/toxygen/file_transfers/file_transfers.py +++ b/toxygen/file_transfers/file_transfers.py @@ -347,5 +347,4 @@ class ReceiveAvatar(ReceiveTransfer): chdir(dirname(avatar_path)) remove(avatar_path) rename(self._path, avatar_path) - self._finished() super().write_chunk(position, data) From 7aac248bf9b81546e147bdcfb3d3513f0d2c3b84 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 10 Jul 2018 00:41:08 +0300 Subject: [PATCH 087/138] identicons fixes. sending messages button fixed --- toxygen/contacts/basecontact.py | 5 +++++ toxygen/contacts/contacts_manager.py | 19 ++++++++++++------- toxygen/ui/main_screen.py | 3 ++- toxygen/utils/util.py | 4 ++++ 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/toxygen/contacts/basecontact.py b/toxygen/contacts/basecontact.py index 0357809..fa709db 100644 --- a/toxygen/contacts/basecontact.py +++ b/toxygen/contacts/basecontact.py @@ -148,6 +148,11 @@ class BaseContact: 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 diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 46e98a2..6812391 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -296,11 +296,7 @@ class ContactsManager(ToxSave): Adds friend to list """ self._tox.friend_add_norequest(tox_id) - self._history.add_friend_to_db(tox_id) - friend = self._contact_provider.get_friend_by_public_key(tox_id) - self._contacts.append(friend) - friend.reset_avatar(self._settings['identicons']) - self._save_profile() + self._add_friend(tox_id) def block_user(self, tox_id): """ @@ -375,8 +371,7 @@ class ContactsManager(ToxSave): else: self._tox.friend_add(tox_id, message.encode('utf-8')) tox_id = tox_id[:TOX_PUBLIC_KEY_SIZE * 2] - friend = self._contact_provider.get_friend_by_public_key(tox_id) - self._contacts.append(friend) + self._add_friend(tox_id) self.save_profile() return True except Exception as ex: # wrong data @@ -430,6 +425,8 @@ class ContactsManager(ToxSave): 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): @@ -477,6 +474,14 @@ class ContactsManager(ToxSave): 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) + self._contacts.append(friend) + if not friend.has_avatar(): + friend.reset_avatar(self._settings['identicons']) + self._save_profile() + def _save_profile(self): data = self._tox.get_savedata() self._profile_manager.save_profile(data) diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index e88437a..5bcd15a 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -17,7 +17,7 @@ class MainWindow(QtWidgets.QMainWindow): self._plugins_loader = None self.setAcceptDrops(True) self._saved = False - self._profile = self._toxes = 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() @@ -34,6 +34,7 @@ class MainWindow(QtWidgets.QMainWindow): 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_messenger(messenger) diff --git a/toxygen/utils/util.py b/toxygen/utils/util.py index 5790a5b..0f718ef 100644 --- a/toxygen/utils/util.py +++ b/toxygen/utils/util.py @@ -102,6 +102,10 @@ def join_path(a, b): return os.path.join(a, b) +def file_exists(file_path): + return os.path.exists(file_path) + + def copy(src, dest): if not os.path.exists(dest): os.makedirs(dest) From 2a97beb5afa29cb14b511dca1c292294e0d1470d Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 15 Jul 2018 17:04:51 +0300 Subject: [PATCH 088/138] minor bug fixes --- toxygen/contacts/contacts_manager.py | 6 ------ toxygen/messenger/messenger.py | 7 +++++++ toxygen/ui/main_screen.py | 2 +- toxygen/ui/main_screen_widgets.py | 1 - 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 6812391..5d07bd2 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -226,12 +226,6 @@ class ContactsManager(ToxSave): def get_contact_by_tox_id(self, tox_id): return list(filter(lambda c: c.tox_id == tox_id, self._contacts))[0] - def get_last_message(self): - if self._active_contact + 1: - return self.get_curr_contact().get_last_message_text() - else: - return '' - def get_active_number(self): return self.get_curr_contact().number if self._active_contact + 1 else -1 diff --git a/toxygen/messenger/messenger.py b/toxygen/messenger/messenger.py index af66f0b..dbd2d1e 100644 --- a/toxygen/messenger/messenger.py +++ b/toxygen/messenger/messenger.py @@ -19,6 +19,13 @@ class Messenger(tox_save.ToxSave): 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 # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 5bcd15a..1bc3612 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -170,7 +170,7 @@ class MainWindow(QtWidgets.QMainWindow): self.online_contacts.addItem(util_ui.tr("Online and by name")) self.online_contacts.addItem(util_ui.tr("Online first and by name")) ind = self._settings['sorting'] - d = {0: 0, 1: 1, 2: 2, 3: 4, 1 | 4: 4, 2 | 4: 5} + d = {0: 0, 1: 1, 2: 2, 3: 4, 4: 3, 1 | 4: 4, 2 | 4: 5} self.online_contacts.setCurrentIndex(d[ind]) self.importPlugin.setText(util_ui.tr("Import plugin")) self.reloadPlugins.setText(util_ui.tr("Reload plugins")) diff --git a/toxygen/ui/main_screen_widgets.py b/toxygen/ui/main_screen_widgets.py index 479575e..0945b0c 100644 --- a/toxygen/ui/main_screen_widgets.py +++ b/toxygen/ui/main_screen_widgets.py @@ -1,6 +1,5 @@ from PyQt5 import QtCore, QtGui, QtWidgets from ui.widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit -from contacts.profile import Profile import urllib import utils.util as util import utils.ui as util_ui From 9c742d10de8db9b87413e1533cfae397c7584641 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 16 Jul 2018 21:29:15 +0300 Subject: [PATCH 089/138] plugins refactoring --- toxygen/app.py | 2 +- toxygen/contacts/profile.py | 2 +- toxygen/plugin_support/plugin_support.py | 134 +++++++++++++---------- toxygen/plugins/plugin_super_class.py | 34 ++---- 4 files changed, 91 insertions(+), 81 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index 3f66e2c..1dd74b4 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -338,7 +338,7 @@ class App: self._group_factory = GroupFactory(self._profile_manager, self._settings, self._tox, db, contact_items_factory) self._contacts_provider = ContactProvider(self._tox, self._friend_factory, self._group_factory) self._profile = Profile(self._profile_manager, self._tox, self._ms, self._contacts_provider, self._reset) - self._plugin_loader = PluginLoader(self._tox, self._toxes, self._profile, self._settings) + 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)) diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 3529be4..ab8cb2a 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -6,7 +6,7 @@ import common.tox_save as tox_save class Profile(basecontact.BaseContact, tox_save.ToxSave): """ - Profile of current toxygen user. Contains friends list, tox instance + Profile of current toxygen user. """ def __init__(self, profile_manager, tox, screen, contacts_provider, reset_action): """ diff --git a/toxygen/plugin_support/plugin_support.py b/toxygen/plugin_support/plugin_support.py index e8c9a56..ed45910 100644 --- a/toxygen/plugin_support/plugin_support.py +++ b/toxygen/plugin_support/plugin_support.py @@ -4,31 +4,47 @@ import importlib import inspect import plugins.plugin_super_class as pl import sys -from common.tox_save import ToxSave -class PluginLoader(ToxSave): +class Plugin: - def __init__(self, tox, toxes, profile, settings): - super().__init__(tox) - self._profile = profile + def __init__(self, plugin, is_active): + self._instance = plugin + 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._plugins = {} # dict. key - plugin unique short name, value - tuple (plugin instance, is active) - self._toxes = toxes + self._app = app + self._plugins = {} # dict. key - plugin unique short name, value - Plugin instance def set_tox(self, tox): """ New tox instance """ - super().set_tox(tox) - for value in self._plugins.values(): - value[0].set_tox(tox) + for plugin in self._plugins.values(): + plugin.instance.set_tox(tox) def load(self): """ Load all plugins in plugins folder """ - path = util.curr_directory() + '/plugins/' + path = util.get_plugins_directory() if not os.path.exists(path): util.log('Plugin dir not found') return @@ -50,18 +66,19 @@ class PluginLoader(ToxSave): for elem in dir(module): obj = getattr(module, elem) # looking for plugin class in module - if inspect.isclass(obj) and hasattr(obj, 'is_plugin') and obj.is_plugin: - print('Plugin', elem) - try: # create instance of plugin class - inst = obj(self._tox, self._profile, self._settings, self._toxes) - autostart = inst.get_short_name() in self._settings['plugins'] - if autostart: - inst.start() - except Exception as ex: - util.log('Exception in module ' + name + ' Exception: ' + str(ex)) - continue - self._plugins[inst.get_short_name()] = [inst, autostart] # (inst, is active) - break + if not inspect.isclass(obj) or not hasattr(obj, 'is_plugin') or not obj.is_plugin: + continue + print('Plugin', elem) + try: # create instance of plugin class + instance = obj(self._app) + is_active = instance.get_short_name() in self._settings['plugins'] + if is_active: + instance.start() + except Exception as ex: + util.log('Exception in module ' + name + ' Exception: ' + str(ex)) + continue + self._plugins[instance.get_short_name()] = Plugin(instance, is_active) + break def callback_lossless(self, friend_number, data): """ @@ -69,8 +86,8 @@ class PluginLoader(ToxSave): """ l = data[0] - pl.LOSSLESS_FIRST_BYTE name = ''.join(chr(x) for x in data[1:l + 1]) - if name in self._plugins and self._plugins[name][1]: - self._plugins[name][0].lossless_packet(''.join(chr(x) for x in data[l + 1:]), friend_number) + if name in self._plugins and self._plugins[name].is_active: + self._plugins[name].instance.lossless_packet(''.join(chr(x) for x in data[l + 1:]), friend_number) def callback_lossy(self, friend_number, data): """ @@ -78,37 +95,38 @@ class PluginLoader(ToxSave): """ l = data[0] - pl.LOSSY_FIRST_BYTE name = ''.join(chr(x) for x in data[1:l + 1]) - if name in self._plugins and self._plugins[name][1]: - self._plugins[name][0].lossy_packet(''.join(chr(x) for x in data[l + 1:]), friend_number) + if name in self._plugins and self._plugins[name].is_active: + self._plugins[name].instance.lossy_packet(''.join(chr(x) for x in data[l + 1:]), friend_number) def friend_online(self, friend_number): """ Friend with specified number is online """ - for elem in self._plugins.values(): - if elem[1]: - elem[0].friend_connected(friend_number) + for plugin in self._plugins.values(): + if plugin.is_active: + plugin.instance.friend_connected(friend_number) def get_plugins_list(self): """ Returns list of all plugins """ result = [] - for data in self._plugins.values(): + for plugin in self._plugins.values(): try: - result.append([data[0].get_name(), # plugin full name - data[1], # is enabled - data[0].get_description(), # plugin description - data[0].get_short_name()]) # key - short unique name + result.append([plugin.instance.get_name(), # plugin full name + plugin.is_active, # is enabled + plugin.instance.get_description(), # plugin description + plugin.instance.get_short_name()]) # key - short unique name except: continue + return result def plugin_window(self, key): """ 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): """ @@ -116,12 +134,12 @@ class PluginLoader(ToxSave): :param key: plugin short name """ plugin = self._plugins[key] - if plugin[1]: - plugin[0].stop() + if plugin.is_active: + plugin.instance.stop() else: - plugin[0].start() - plugin[1] = not plugin[1] - if plugin[1]: + plugin.instance.start() + plugin.is_active = not plugin.is_active + if plugin.is_active: self._settings['plugins'].append(key) else: self._settings['plugins'].remove(key) @@ -133,30 +151,32 @@ class PluginLoader(ToxSave): """ text = text.strip() name = text.split()[0] - if name in self._plugins and self._plugins[name][1]: - self._plugins[name][0].command(text[len(name) + 1:]) + if name in self._plugins and self._plugins[name].is_active: + self._plugins[name].instance.command(text[len(name) + 1:]) def get_menu(self, num): """ Return list of items for menu """ result = [] - for elem in self._plugins.values(): - if elem[1]: - try: - result.extend(elem[0].get_menu(num)) - except: - continue + for plugin in self._plugins.values(): + if not plugin.is_active: + continue + try: + result.extend(plugin.instance.get_menu(num)) + except: + continue return result def get_message_menu(self, menu, selected_text): result = [] - for elem in self._plugins.values(): - if elem[1]: - try: - result.extend(elem[0].get_message_menu(menu, selected_text)) - except: - pass + for plugin in self._plugins.values(): + if not plugin.is_active: + continue + try: + result.extend(plugin.instance.get_message_menu(menu, selected_text)) + except: + pass return result def stop(self): @@ -164,8 +184,8 @@ class PluginLoader(ToxSave): App is closing, stop all plugins """ for key in list(self._plugins.keys()): - if self._plugins[key][1]: - self._plugins[key][0].close() + if self._plugins[key].is_active: + self._plugins[key].instance.close() del self._plugins[key] def reload(self): diff --git a/toxygen/plugins/plugin_super_class.py b/toxygen/plugins/plugin_super_class.py index abb4da6..0056d36 100644 --- a/toxygen/plugins/plugin_super_class.py +++ b/toxygen/plugins/plugin_super_class.py @@ -1,5 +1,7 @@ import os from PyQt5 import QtCore, QtWidgets +import utils.ui as util_ui +import common.tox_save as tox_save MAX_SHORT_NAME_LENGTH = 5 @@ -26,25 +28,22 @@ def log(name, data): 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. """ 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 short_name: plugin unique short name (length of short name should not exceed MAX_SHORT_NAME_LENGTH) - :param tox: tox instance - :param profile: profile instance - :param settings: profile settings - :param encrypt_save: ToxES instance. + :param app: App instance """ - self._settings = settings - self._profile = profile - self._tox = tox + tox = getattr(app, '_tox') + super().__init__(tox) + self._settings = getattr(app, '_settings') name = name.strip() short_name = short_name.strip() if not name or not short_name: @@ -52,7 +51,6 @@ class PluginSuperClass: self._name = name self._short_name = short_name[:MAX_SHORT_NAME_LENGTH] self._translator = None # translator for plugin's GUI - self._encrypt_save = encrypt_save # ----------------------------------------------------------------------------------------------------------------- # Get methods @@ -99,12 +97,6 @@ class PluginSuperClass: """ return None - def set_tox(self, tox): - """ - New tox instance - """ - self._tox = tox - # ----------------------------------------------------------------------------------------------------------------- # Plugin was stopped, started or new command received # ----------------------------------------------------------------------------------------------------------------- @@ -133,11 +125,9 @@ class PluginSuperClass: :param command: string with command """ if command == 'help': - msgbox = QtWidgets.QMessageBox() - title = QtWidgets.QApplication.translate("PluginWindow", "List of commands for plugin {}") - msgbox.setWindowTitle(title.format(self._name)) - msgbox.setText(QtWidgets.QApplication.translate("PluginWindow", "No commands available")) - msgbox.exec_() + text = util_ui.tr('No commands available') + title = util_ui.tr('List of commands for plugin {}').format(self._name) + util_ui.message_box(text, title) # ----------------------------------------------------------------------------------------------------------------- # Translations support From 329ab23f89c01c4b85c7589cfcbd7b5199b11280 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 17 Jul 2018 20:52:42 +0300 Subject: [PATCH 090/138] api changes - new methods and renaming --- toxygen/wrapper/tox.py | 228 +++++++++++++++++++++-------------------- 1 file changed, 119 insertions(+), 109 deletions(-) diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index 96e7fd5..fc438d8 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -1565,7 +1565,7 @@ class Tox: :param chat_id: The Chat ID of the group you wish to join. This must be TOX_GROUP_CHAT_ID_SIZE bytes. :param password: The password required to join the group. Set to NULL if no password is required. - :return groupnumber on success, UINT32_MAX on failure. + :return group_number on success, UINT32_MAX on failure. """ error = c_int() @@ -1577,22 +1577,32 @@ class Tox: byref(error)) return result - def group_reconnect(self, groupnumber): + def group_reconnect(self, group_number): """ Reconnects to a group. This function disconnects from all peers in the group, then attempts to reconnect with the group. The caller's state is not changed (i.e. name, status, role, chat public key etc.) - :param groupnumber: The group number of the group we wish to reconnect to. + :param group_number: The group number of the group we wish to reconnect to. :return True on success. """ error = c_int() - result = Tox.libtoxcore.tox_group_reconnect(self._tox_pointer, groupnumber, byref(error)) + result = Tox.libtoxcore.tox_group_reconnect(self._tox_pointer, group_number, byref(error)) return result - def group_leave(self, groupnumber, message=''): + def group_is_connected(self, group_number): + error = c_int() + result = Tox.libtoxcore.tox_group_is_connected(self._tox_pointer, group_number, byref(error)) + return result + + def group_disconnect(self, group_number): + error = c_int() + result = Tox.libtoxcore.tox_group_disconnect(self._tox_pointer, group_number, byref(error)) + return result + + def group_leave(self, group_number, message=''): """ Leaves a group. @@ -1600,7 +1610,7 @@ class Tox: peers in a group, and deletes the group from the chat array. All group state information is permanently lost, including keys and role credentials. - :param groupnumber: The group number of the group we wish to leave. + :param group_number: The group number of the group we wish to leave. :param message: The parting message to be sent to all the peers. Set to NULL if we do not wish to send a parting message. @@ -1610,7 +1620,7 @@ class Tox: error = c_int() f = Tox.libtoxcore.tox_group_leave f.restype = c_bool - result = f(self._tox_pointer, groupnumber, message, + result = f(self._tox_pointer, group_number, message, len(message) if message is not None else 0, byref(error)) return result @@ -1618,7 +1628,7 @@ class Tox: # Group user-visible client information (nickname/status/role/public key) # ----------------------------------------------------------------------------------------------------------------- - def group_self_set_name(self, groupnumber, name): + def group_self_set_name(self, group_number, name): """ Set the client's nickname for the group instance designated by the given group number. @@ -1631,23 +1641,23 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_self_set_name(self._tox_pointer, groupnumber, name, len(name), byref(error)) + result = Tox.libtoxcore.tox_group_self_set_name(self._tox_pointer, group_number, name, len(name), byref(error)) return result - def group_self_get_name_size(self, groupnumber): + def group_self_get_name_size(self, group_number): """ Return the length of the client's current nickname for the group instance designated - by groupnumber as passed to tox_group_self_set_name. + by group_number as passed to tox_group_self_set_name. If no nickname was set before calling this function, the name is empty, and this function returns 0. """ error = c_int() - result = Tox.libtoxcore.tox_group_self_get_name_size(self._tox_pointer, groupnumber, byref(error)) + result = Tox.libtoxcore.tox_group_self_get_name_size(self._tox_pointer, group_number, byref(error)) return result - def group_self_get_name(self, groupnumber): + def group_self_get_name(self, group_number): """ Write the nickname set by tox_group_self_set_name to a byte array. @@ -1659,12 +1669,12 @@ class Tox: """ error = c_int() - size = self.group_self_get_name_size(groupnumber) + size = self.group_self_get_name_size(group_number) name = create_string_buffer(size) - result = Tox.libtoxcore.tox_group_self_get_name(self._tox_pointer, groupnumber, name, byref(error)) + result = Tox.libtoxcore.tox_group_self_get_name(self._tox_pointer, group_number, name, byref(error)) return str(name[:size], 'utf-8') - def group_self_set_status(self, groupnumber, status): + def group_self_set_status(self, group_number, status): """ Set the client's status for the group instance. Status must be a TOX_USER_STATUS. @@ -1672,40 +1682,40 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_self_set_status(self._tox_pointer, groupnumber, status, byref(error)) + result = Tox.libtoxcore.tox_group_self_set_status(self._tox_pointer, group_number, status, byref(error)) return result - def group_self_get_status(self, groupnumber): + def group_self_get_status(self, group_number): """ returns the client's status for the group instance on success. return value is unspecified on failure. """ error = c_int() - result = Tox.libtoxcore.tox_group_self_get_status(self._tox_pointer, groupnumber, byref(error)) + result = Tox.libtoxcore.tox_group_self_get_status(self._tox_pointer, group_number, byref(error)) return result - def group_self_get_role(self, groupnumber): + def group_self_get_role(self, group_number): """ returns the client's role for the group instance on success. return value is unspecified on failure. """ error = c_int() - result = Tox.libtoxcore.tox_group_self_get_role(self._tox_pointer, groupnumber, byref(error)) + result = Tox.libtoxcore.tox_group_self_get_role(self._tox_pointer, group_number, byref(error)) return result - def group_self_get_peer_id(self, groupnumber): + def group_self_get_peer_id(self, group_number): """ returns the client's peer id for the group instance on success. return value is unspecified on failure. """ error = c_int() - result = Tox.libtoxcore.tox_group_self_get_peer_id(self._tox_pointer, groupnumber, byref(error)) + result = Tox.libtoxcore.tox_group_self_get_peer_id(self._tox_pointer, group_number, byref(error)) return result - def group_self_get_public_key(self, groupnumber): + def group_self_get_public_key(self, group_number): """ Write the client's group public key designated by the given group number to a byte array. @@ -1720,7 +1730,7 @@ class Tox: error = c_int() key = create_string_buffer(TOX_GROUP_PEER_PUBLIC_KEY_SIZE) - result = Tox.libtoxcore.tox_group_self_get_public_key(self._tox_pointer, groupnumber, + result = Tox.libtoxcore.tox_group_self_get_public_key(self._tox_pointer, group_number, key, byref(error)) return bin_to_string(key, TOX_GROUP_PEER_PUBLIC_KEY_SIZE) @@ -1728,7 +1738,7 @@ class Tox: # Peer-specific group state queries. # ----------------------------------------------------------------------------------------------------------------- - def group_peer_get_name_size(self, groupnumber, peer_id): + def group_peer_get_name_size(self, group_number, peer_id): """ Return the length of the peer's name. If the group number or ID is invalid, the return value is unspecified. @@ -1738,10 +1748,10 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_peer_get_name_size(self._tox_pointer, groupnumber, peer_id, byref(error)) + result = Tox.libtoxcore.tox_group_peer_get_name_size(self._tox_pointer, group_number, peer_id, byref(error)) return result - def group_peer_get_name(self, groupnumber, peer_id): + def group_peer_get_name(self, group_number, peer_id): """ Write the name of the peer designated by the given ID to a byte array. @@ -1751,18 +1761,18 @@ class Tox: The data written to `name` is equal to the data received by the last `group_peer_name` callback. - :param groupnumber: The group number of the group we wish to query. + :param group_number: The group number of the group we wish to query. :param peer_id: The ID of the peer whose name we want to retrieve. :return name. """ error = c_int() - size = self.group_peer_get_name_size(groupnumber, peer_id) + size = self.group_peer_get_name_size(group_number, peer_id) name = create_string_buffer(size) - result = Tox.libtoxcore.tox_group_peer_get_name(self._tox_pointer, groupnumber, peer_id, name, byref(error)) + result = Tox.libtoxcore.tox_group_peer_get_name(self._tox_pointer, group_number, peer_id, name, byref(error)) return str(name[:], 'utf-8') - def group_peer_get_status(self, groupnumber, peer_id): + def group_peer_get_status(self, group_number, peer_id): """ Return the peer's user status (away/busy/...). If the ID or group number is invalid, the return value is unspecified. @@ -1772,10 +1782,10 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_peer_get_status(self._tox_pointer, groupnumber, peer_id, byref(error)) + result = Tox.libtoxcore.tox_group_peer_get_status(self._tox_pointer, group_number, peer_id, byref(error)) return result - def group_peer_get_role(self, groupnumber, peer_id): + def group_peer_get_role(self, group_number, peer_id): """ Return the peer's role (user/moderator/founder...). If the ID or group number is invalid, the return value is unspecified. @@ -1785,10 +1795,10 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_peer_get_role(self._tox_pointer, groupnumber, peer_id, byref(error)) + result = Tox.libtoxcore.tox_group_peer_get_role(self._tox_pointer, group_number, peer_id, byref(error)) return result - def group_peer_get_public_key(self, groupnumber, peer_id): + def group_peer_get_public_key(self, group_number, peer_id): """ Write the group public key with the designated peer_id for the designated group number to public_key. @@ -1802,7 +1812,7 @@ class Tox: error = c_int() key = create_string_buffer(TOX_GROUP_PEER_PUBLIC_KEY_SIZE) - result = Tox.libtoxcore.tox_group_peer_get_public_key(self._tox_pointer, groupnumber, peer_id, + result = Tox.libtoxcore.tox_group_peer_get_public_key(self._tox_pointer, group_number, peer_id, key, byref(error)) return bin_to_string(key, TOX_GROUP_PEER_PUBLIC_KEY_SIZE) @@ -1830,7 +1840,7 @@ class Tox: # Group chat state queries and events. # ----------------------------------------------------------------------------------------------------------------- - def group_set_topic(self, groupnumber, topic): + def group_set_topic(self, group_number, topic): """ Set the group topic and broadcast it to the rest of the group. @@ -1841,10 +1851,10 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_set_topic(self._tox_pointer, groupnumber, topic, len(topic), byref(error)) + result = Tox.libtoxcore.tox_group_set_topic(self._tox_pointer, group_number, topic, len(topic), byref(error)) return result - def group_get_topic_size(self, groupnumber): + def group_get_topic_size(self, group_number): """ Return the length of the group topic. If the group number is invalid, the return value is unspecified. @@ -1854,10 +1864,10 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_get_topic_size(self._tox_pointer, groupnumber, byref(error)) + result = Tox.libtoxcore.tox_group_get_topic_size(self._tox_pointer, group_number, byref(error)) return result - def group_get_topic(self, groupnumber): + def group_get_topic(self, group_number): """ Write the topic designated by the given group number to a byte array. Call tox_group_get_topic_size to determine the allocation size for the `topic` parameter. @@ -1868,21 +1878,21 @@ class Tox: """ error = c_int() - size = self.group_get_topic_size(groupnumber) + size = self.group_get_topic_size(group_number) topic = create_string_buffer(size) - result = Tox.libtoxcore.tox_group_get_topic(self._tox_pointer, groupnumber, topic, byref(error)) + result = Tox.libtoxcore.tox_group_get_topic(self._tox_pointer, group_number, topic, byref(error)) return str(topic[:size], 'utf-8') - def group_get_name_size(self, groupnumber): + def group_get_name_size(self, group_number): """ Return the length of the group name. If the group number is invalid, the return value is unspecified. """ error = c_int() - result = Tox.libtoxcore.tox_group_get_name_size(self._tox_pointer, groupnumber, byref(error)) + result = Tox.libtoxcore.tox_group_get_name_size(self._tox_pointer, group_number, byref(error)) return int(result) - def group_get_name(self, groupnumber): + def group_get_name(self, group_number): """ Write the name of the group designated by the given group number to a byte array. Call tox_group_get_name_size to determine the allocation size for the `name` parameter. @@ -1890,13 +1900,13 @@ class Tox: """ error = c_int() - size = self.group_get_name_size(groupnumber) + size = self.group_get_name_size(group_number) name = create_string_buffer(size) - result = Tox.libtoxcore.tox_group_get_name(self._tox_pointer, groupnumber, + result = Tox.libtoxcore.tox_group_get_name(self._tox_pointer, group_number, name, byref(error)) return str(name[:size], 'utf-8') - def group_get_chat_id(self, groupnumber): + def group_get_chat_id(self, group_number): """ Write the Chat ID designated by the given group number to a byte array. `chat_id` should have room for at least TOX_GROUP_CHAT_ID_SIZE bytes. @@ -1905,7 +1915,7 @@ class Tox: error = c_int() buff = create_string_buffer(TOX_GROUP_CHAT_ID_SIZE) - result = Tox.libtoxcore.tox_group_get_chat_id(self._tox_pointer, groupnumber, + result = Tox.libtoxcore.tox_group_get_chat_id(self._tox_pointer, group_number, buff, byref(error)) return bin_to_string(buff, TOX_GROUP_CHAT_ID_SIZE) @@ -1917,7 +1927,7 @@ class Tox: result = Tox.libtoxcore.tox_group_get_number_groups(self._tox_pointer) return result - def group_get_privacy_state(self, groupnumber): + def group_get_privacy_state(self, group_number): """ Return the privacy state of the group designated by the given group number. If group number is invalid, the return value is unspecified. @@ -1929,10 +1939,10 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_get_privacy_state(self._tox_pointer, groupnumber, byref(error)) + result = Tox.libtoxcore.tox_group_get_privacy_state(self._tox_pointer, group_number, byref(error)) return result - def group_get_peer_limit(self, groupnumber): + def group_get_peer_limit(self, group_number): """ Return the maximum number of peers allowed for the group designated by the given group number. If the group number is invalid, the return value is unspecified. @@ -1944,20 +1954,20 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_get_peer_limit(self._tox_pointer, groupnumber, byref(error)) + result = Tox.libtoxcore.tox_group_get_peer_limit(self._tox_pointer, group_number, byref(error)) return result - def group_get_password_size(self, groupnumber): + def group_get_password_size(self, group_number): """ Return the length of the group password. If the group number is invalid, the return value is unspecified. """ error = c_int() - result = Tox.libtoxcore.tox_group_get_password_size(self._tox_pointer, groupnumber, byref(error)) + result = Tox.libtoxcore.tox_group_get_password_size(self._tox_pointer, group_number, byref(error)) return result - def group_get_password(self, groupnumber): + def group_get_password(self, group_number): """ Write the password for the group designated by the given group number to a byte array. @@ -1972,9 +1982,9 @@ class Tox: """ error = c_int() - size = self.group_get_password_size(groupnumber) + size = self.group_get_password_size(group_number) password = create_string_buffer(size) - result = Tox.libtoxcore.tox_group_get_password(self._tox_pointer, groupnumber, + result = Tox.libtoxcore.tox_group_get_password(self._tox_pointer, group_number, password, byref(error)) return str(password[:size], 'utf-8') @@ -2022,7 +2032,7 @@ class Tox: # Group message sending # ----------------------------------------------------------------------------------------------------------------- - def group_send_custom_packet(self, groupnumber, lossless, data): + def group_send_custom_packet(self, group_number, lossless, data): """ Send a custom packet to the group. @@ -2036,18 +2046,18 @@ class Tox: Unless latency is an issue or message reliability is not important, it is recommended that you use lossless custom packets. - :param groupnumber: The group number of the group the message is intended for. + :param group_number: The group number of the group the message is intended for. :param lossless: True if the packet should be lossless. :param data A byte array containing the packet data. :return True on success. """ error = c_int() - result = Tox.libtoxcore.tox_group_send_custom_packet(self._tox_pointer, groupnumber, lossless, data, + result = Tox.libtoxcore.tox_group_send_custom_packet(self._tox_pointer, group_number, lossless, data, len(data), byref(error)) return result - def group_send_private_message(self, groupnumber, peer_id, message): + def group_send_private_message(self, group_number, peer_id, message): """ Send a text chat message to the specified peer in the specified group. @@ -2058,7 +2068,7 @@ class Tox: must be split by the client and sent as separate messages. Other clients can then reassemble the fragments. Messages may not be empty. - :param groupnumber: The group number of the group the message is intended for. + :param group_number: The group number of the group the message is intended for. :param peer_id: The ID of the peer the message is intended for. :param message: A non-NULL pointer to the first element of a byte array containing the message text. @@ -2066,11 +2076,11 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_send_private_message(self._tox_pointer, groupnumber, peer_id, message, + result = Tox.libtoxcore.tox_group_send_private_message(self._tox_pointer, group_number, peer_id, message, len(message), byref(error)) return result - def group_send_message(self, groupnumber, type, message): + def group_send_message(self, group_number, type, message): """ Send a text chat message to the group. @@ -2081,7 +2091,7 @@ class Tox: must be split by the client and sent as separate messages. Other clients can then reassemble the fragments. Messages may not be empty. - :param groupnumber: The group number of the group the message is intended for. + :param group_number: The group number of the group the message is intended for. :param type: Message type (normal, action, ...). :param message: A non-NULL pointer to the first element of a byte array containing the message text. @@ -2089,7 +2099,7 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_send_message(self._tox_pointer, groupnumber, type, message, len(message), + result = Tox.libtoxcore.tox_group_send_message(self._tox_pointer, group_number, type, message, len(message), byref(error)) return result @@ -2104,7 +2114,7 @@ class Tox: Callback: python function with params: tox Tox* instance - groupnumber The group number of the group the message is intended for. + group_number The group number of the group the message is intended for. peer_id The ID of the peer who sent the message. type The type of message (normal, action, ...). message The message data. @@ -2141,20 +2151,20 @@ class Tox: # Group chat inviting and join/part events # ----------------------------------------------------------------------------------------------------------------- - def group_invite_friend(self, groupnumber, friend_number): + def group_invite_friend(self, group_number, friend_number): """ Invite a friend to a group. This function creates an invite request packet and pushes it to the send queue. - :param groupnumber: The group number of the group the message is intended for. + :param group_number: The group number of the group the message is intended for. :param friend_number: The friend number of the friend the invite is intended for. :return True on success. """ error = c_int() - result = Tox.libtoxcore.tox_group_invite_friend(self._tox_pointer, groupnumber, friend_number, byref(error)) + result = Tox.libtoxcore.tox_group_invite_friend(self._tox_pointer, group_number, friend_number, byref(error)) return result def group_chat_self_peer_info_new(self): @@ -2172,7 +2182,7 @@ class Tox: :param invite_data: The invite data received from the `group_invite` event. :param password: The password required to join the group. Set to NULL if no password is required. - :return the groupnumber on success, UINT32_MAX on failure. + :return the group_number on success, UINT32_MAX on failure. """ error = c_int() @@ -2262,25 +2272,25 @@ class Tox: # Group chat founder controls (these only work for the group founder) # ----------------------------------------------------------------------------------------------------------------- - def group_founder_set_password(self, groupnumber, password): + def group_founder_set_password(self, group_number, password): """ Set or unset the group password. This function sets the groups password, creates a new group shared state including the change, and distributes it to the rest of the group. - :param groupnumber: The group number of the group for which we wish to set the password. + :param group_number: The group number of the group for which we wish to set the password. :param password: The password we want to set. Set password to NULL to unset the password. :return True on success. """ error = c_int() - result = Tox.libtoxcore.tox_group_founder_set_password(self._tox_pointer, groupnumber, password, + result = Tox.libtoxcore.tox_group_founder_set_password(self._tox_pointer, group_number, password, len(password), byref(error)) return result - def group_founder_set_privacy_state(self, groupnumber, privacy_state): + def group_founder_set_privacy_state(self, group_number, privacy_state): """ Set the group privacy state. @@ -2290,32 +2300,32 @@ class Tox: If an attempt is made to set the privacy state to the same state that the group is already in, the function call will be successful and no action will be taken. - :param groupnumber: The group number of the group for which we wish to change the privacy state. + :param group_number: The group number of the group for which we wish to change the privacy state. :param privacy_state: The privacy state we wish to set the group to. :return true on success. """ error = c_int() - result = Tox.libtoxcore.tox_group_founder_set_privacy_state(self._tox_pointer, groupnumber, privacy_state, + result = Tox.libtoxcore.tox_group_founder_set_privacy_state(self._tox_pointer, group_number, privacy_state, byref(error)) return result - def group_founder_set_peer_limit(self, groupnumber, max_peers): + def group_founder_set_peer_limit(self, group_number, max_peers): """ Set the group peer limit. This function sets a limit for the number of peers who may be in the group, creates a new group shared state including the change, and distributes it to the rest of the group. - :param groupnumber: The group number of the group for which we wish to set the peer limit. + :param group_number: The group number of the group for which we wish to set the peer limit. :param max_peers: The maximum number of peers to allow in the group. :return True on success. """ error = c_int() - result = Tox.libtoxcore.tox_group_founder_set_peer_limit(self._tox_pointer, groupnumber, + result = Tox.libtoxcore.tox_group_founder_set_peer_limit(self._tox_pointer, group_number, max_peers, byref(error)) return result @@ -2323,11 +2333,11 @@ class Tox: # Group chat moderation # ----------------------------------------------------------------------------------------------------------------- - def group_toggle_ignore(self, groupnumber, peer_id, ignore): + def group_toggle_ignore(self, group_number, peer_id, ignore): """ Ignore or unignore a peer. - :param groupnumber: The group number of the group the in which you wish to ignore a peer. + :param group_number: The group number of the group the in which you wish to ignore a peer. :param peer_id: The ID of the peer who shall be ignored or unignored. :param ignore: True to ignore the peer, false to unignore the peer. @@ -2335,10 +2345,10 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_toggle_ignore(self._tox_pointer, groupnumber, peer_id, ignore, byref(error)) + result = Tox.libtoxcore.tox_group_toggle_ignore(self._tox_pointer, group_number, peer_id, ignore, byref(error)) return result - def group_mod_set_role(self, groupnumber, peer_id, role): + def group_mod_set_role(self, group_number, peer_id, role): """ Set a peer's role. @@ -2346,7 +2356,7 @@ class Tox: It will also send a packet to the rest of the group, requesting that they perform the role reassignment. Note: peers cannot be set to the founder role. - :param groupnumber: The group number of the group the in which you wish set the peer's role. + :param group_number: The group number of the group the in which you wish set the peer's role. :param peer_id: The ID of the peer whose role you wish to set. :param role: The role you wish to set the peer to. @@ -2354,10 +2364,10 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_mod_set_role(self._tox_pointer, groupnumber, peer_id, role, byref(error)) + result = Tox.libtoxcore.tox_group_mod_set_role(self._tox_pointer, group_number, peer_id, role, byref(error)) return result - def group_mod_remove_peer(self, groupnumber, peer_id, set_ban): + def group_mod_remove_peer(self, group_number, peer_id, set_ban): """ Kick/ban a peer. @@ -2365,7 +2375,7 @@ class Tox: to the ban list. It will also send a packet to all group members requesting them to do the same. - :param groupnumber: The group number of the group the ban is intended for. + :param group_number: The group number of the group the ban is intended for. :param peer_id: The ID of the peer who will be kicked and/or added to the ban list. :param set_ban: Set to true if a ban shall be set on the peer's IP address. @@ -2373,25 +2383,25 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_mod_remove_peer(self._tox_pointer, groupnumber, peer_id, + result = Tox.libtoxcore.tox_group_mod_remove_peer(self._tox_pointer, group_number, peer_id, set_ban, byref(error)) return result - def group_mod_remove_ban(self, groupnumber, ban_id): + def group_mod_remove_ban(self, group_number, ban_id): """ Removes a ban. This function removes a ban entry from the ban list, and sends a packet to the rest of the group requesting that they do the same. - :param groupnumber: The group number of the group in which the ban is to be removed. + :param group_number: The group number of the group in which the ban is to be removed. :param ban_id: The ID of the ban entry that shall be removed. :return True on success """ error = c_int() - result = Tox.libtoxcore.tox_group_mod_remove_ban(self._tox_pointer, groupnumber, ban_id, byref(error)) + result = Tox.libtoxcore.tox_group_mod_remove_ban(self._tox_pointer, group_number, ban_id, byref(error)) return result def callback_group_moderation(self, callback, user_data): @@ -2409,17 +2419,17 @@ class Tox: # Group chat ban list queries # ----------------------------------------------------------------------------------------------------------------- - def group_ban_get_list_size(self, groupnumber): + def group_ban_get_list_size(self, group_number): """ Return the number of entries in the ban list for the group designated by the given group number. If the group number is invalid, the return value is unspecified. """ error = c_int() - result = Tox.libtoxcore.tox_group_ban_get_list_size(self._tox_pointer, groupnumber, byref(error)) + result = Tox.libtoxcore.tox_group_ban_get_list_size(self._tox_pointer, group_number, byref(error)) return result - def group_ban_get_list(self, groupnumber): + def group_ban_get_list(self, group_number): """ Copy a list of valid ban list ID's into an array. @@ -2428,22 +2438,22 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_ban_get_list(self._tox_pointer, groupnumber, POINTER(c_uint32)( - create_string_buffer(sizeof(c_uint32) * self.group_ban_get_list_size(groupnumber)), byref(error))) + result = Tox.libtoxcore.tox_group_ban_get_list(self._tox_pointer, group_number, POINTER(c_uint32)( + create_string_buffer(sizeof(c_uint32) * self.group_ban_get_list_size(group_number)), byref(error))) return result - def group_ban_get_name_size(self, groupnumber, ban_id): + def group_ban_get_name_size(self, group_number, ban_id): """ Return the length of the name for the ban list entry designated by ban_id, in the - group designated by the given group number. If either groupnumber or ban_id is invalid, + group designated by the given group number. If either group_number or ban_id is invalid, the return value is unspecified. """ error = c_int() - result = Tox.libtoxcore.tox_group_ban_get_name_size(self._tox_pointer, groupnumber, ban_id, byref(error)) + result = Tox.libtoxcore.tox_group_ban_get_name_size(self._tox_pointer, group_number, ban_id, byref(error)) return result - def group_ban_get_name(self, groupnumber, ban_id): + def group_ban_get_name(self, group_number, ban_id): """ Write the name of the ban entry designated by ban_id in the group designated by the given group number to a byte array. @@ -2454,20 +2464,20 @@ class Tox: """ error = c_int() - size = self.group_ban_get_name_size(groupnumber, ban_id) + size = self.group_ban_get_name_size(group_number, ban_id) name = create_string_buffer() - result = Tox.libtoxcore.tox_group_ban_get_name(self._tox_pointer, groupnumber, ban_id, + result = Tox.libtoxcore.tox_group_ban_get_name(self._tox_pointer, group_number, ban_id, name, byref(error)) return str(name[:size], 'utf-8') - def group_ban_get_time_set(self, groupnumber, ban_id): + def group_ban_get_time_set(self, group_number, ban_id): """ Return a time stamp indicating the time the ban was set, for the ban list entry designated by ban_id, in the group designated by the given group number. - If either groupnumber or ban_id is invalid, the return value is unspecified. + If either group_number or ban_id is invalid, the return value is unspecified. """ error = c_int() - result = Tox.libtoxcore.tox_group_ban_get_time_set(self._tox_pointer, groupnumber, ban_id, byref(error)) + result = Tox.libtoxcore.tox_group_ban_get_time_set(self._tox_pointer, group_number, ban_id, byref(error)) return result From 6538cedcf2f62c260369561ace108d4bd498a584 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 19 Jul 2018 00:00:01 +0300 Subject: [PATCH 091/138] reconnect/disconnect functionality --- toxygen/app.py | 8 +++++++- toxygen/contacts/contact_menu.py | 11 +++++++++++ toxygen/contacts/contacts_manager.py | 5 ++++- toxygen/contacts/group_chat.py | 3 ++- toxygen/groups/groups_service.py | 11 +++++++++++ 5 files changed, 35 insertions(+), 3 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index 1dd74b4..62025d9 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -338,6 +338,7 @@ class App: self._group_factory = GroupFactory(self._profile_manager, self._settings, self._tox, db, contact_items_factory) self._contacts_provider = ContactProvider(self._tox, self._friend_factory, self._group_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, @@ -349,7 +350,8 @@ class App: 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) + 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, @@ -387,3 +389,7 @@ class App: 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']) diff --git a/toxygen/contacts/contact_menu.py b/toxygen/contacts/contact_menu.py index 4a0dcaa..ff6a9ed 100644 --- a/toxygen/contacts/contact_menu.py +++ b/toxygen/contacts/contact_menu.py @@ -30,6 +30,12 @@ class ContactMenuBuilder: 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 @@ -175,6 +181,11 @@ class GroupMenuGenerator(BaseContactMenuGenerator): menu = (builder .with_action(util_ui.tr('Set alias'), lambda: main_screen.set_alias(number)) .with_submenu(copy_menu_builder) + .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() diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 5d07bd2..2729959 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -490,10 +490,13 @@ class ContactsManager(ToxSave): 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): if num == self._active_contact: # active friend was deleted - self.set_active(0 if len(self._contacts) - 1 else -1) + self.set_active(0 if len(self._contacts) > 1 else -1) self._contact_provider.remove_contact_from_cache(self._contacts[num].tox_id) del self._contacts[num] self._screen.friends_list.takeItem(num) diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index b7429d4..57d471a 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -11,7 +11,8 @@ class GroupChat(contact.Contact, ToxSave): def __init__(self, tox, profile_manager, message_getter, number, name, status_message, widget, tox_id): super().__init__(profile_manager, message_getter, number, name, status_message, widget, tox_id) ToxSave.__init__(self, tox) - self.set_status(constants.TOX_USER_STATUS['NONE']) + status = self._tox.group_is_connected(number) + self.set_status(constants.TOX_USER_STATUS['NONE'] if status else None) self._peers = [] self._add_self_to_gc() diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index 83f6a4e..69f8ec1 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -1,6 +1,7 @@ import common.tox_save as tox_save import utils.ui as util_ui from groups.peers_list import PeersListGenerator +import wrapper.toxcore_enums_and_consts as constants class GroupsService(tox_save.ToxSave): @@ -42,6 +43,16 @@ class GroupsService(tox_save.ToxSave): self._contacts_manager.delete_group(group_number) self._contacts_manager.update_groups_numbers() + def disconnect_from_group(self, group_number): + self._tox.group_disconnect(group_number) + group = self._get_group(group_number) + group.status = None + + def reconnect_to_group(self, group_number): + self._tox.group_reconnect(group_number) + group = self._get_group(group_number) + group.status = constants.TOX_USER_STATUS['NONE'] + # ----------------------------------------------------------------------------------------------------------------- # Group invites # ----------------------------------------------------------------------------------------------------------------- From 820b5a02534e2ce75382c27a90a6eb75ab7ad35a Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 21 Jul 2018 17:16:01 +0300 Subject: [PATCH 092/138] reconnection - clear peers list --- toxygen/contacts/group_chat.py | 3 +++ toxygen/groups/groups_service.py | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index 57d471a..b18fb14 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -51,6 +51,9 @@ class GroupChat(contact.Contact, ToxSave): return peers[0] + def remove_all_peers_except_self(self): + self._peers = self._peers[:1] + def get_peers(self): return self._peers[:] diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index 69f8ec1..2c0d4c3 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -47,11 +47,13 @@ class GroupsService(tox_save.ToxSave): self._tox.group_disconnect(group_number) group = self._get_group(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(group_number) group.status = constants.TOX_USER_STATUS['NONE'] + self._clear_peers_list(group) # ----------------------------------------------------------------------------------------------------------------- # Group invites @@ -102,3 +104,7 @@ class GroupsService(tox_save.ToxSave): def _get_all_groups(self): return self._contacts_provider.get_all_groups() + + def _clear_peers_list(self, group): + group.remove_all_peers_except_self() + self.generate_peers_list() From 7e08be71e05444bd4e96ae04b5acea65e44b4a7a Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 21 Jul 2018 20:25:10 +0300 Subject: [PATCH 093/138] group topic support --- toxygen/contacts/contact_menu.py | 3 +++ toxygen/contacts/group_chat.py | 6 ++++++ toxygen/groups/groups_service.py | 11 +++++++++++ toxygen/middleware/callbacks.py | 1 + toxygen/wrapper/tox.py | 1 + 5 files changed, 22 insertions(+) diff --git a/toxygen/contacts/contact_menu.py b/toxygen/contacts/contact_menu.py index ff6a9ed..6a2eb86 100644 --- a/toxygen/contacts/contact_menu.py +++ b/toxygen/contacts/contact_menu.py @@ -181,6 +181,9 @@ class GroupMenuGenerator(BaseContactMenuGenerator): menu = (builder .with_action(util_ui.tr('Set alias'), lambda: main_screen.set_alias(number)) .with_submenu(copy_menu_builder) + .with_optional_action(util_ui.tr('Set topic'), + lambda: groups_service.set_group_topic(self._contact), + self._contact.is_moderator_or_founder()) .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'), diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index b18fb14..fac2165 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -33,6 +33,12 @@ class GroupChat(contact.Contact, ToxSave): def get_self_name(self): return self._peers[0].name + def get_self_role(self): + return self._peers[0].role + + def is_moderator_or_founder(self): + return self.get_self_role() <= constants.TOX_GROUP_ROLE['MODERATOR'] + def add_peer(self, peer_id, is_current_user=False): peer = GroupChatPeer(peer_id, self._tox.group_peer_get_name(self._number, peer_id), diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index 2c0d4c3..1a8ccc0 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -76,6 +76,17 @@ class GroupsService(tox_save.ToxSave): 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_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 + # ----------------------------------------------------------------------------------------------------------------- # Peers list # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 513cdd2..8730b54 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -399,6 +399,7 @@ def group_peer_join(contacts_provider, groups_service): 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 diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index fc438d8..836d38c 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -1851,6 +1851,7 @@ class Tox: """ error = c_int() + topic = bytes(topic, 'utf-8') result = Tox.libtoxcore.tox_group_set_topic(self._tox_pointer, group_number, topic, len(topic), byref(error)) return result From e15620c3ad763b33659d5fa659cac6d007a3dc83 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 21 Jul 2018 20:43:16 +0300 Subject: [PATCH 094/138] str to bytes convert moved to wrapper --- tests/tests.py | 8 ++++---- toxygen/app.py | 4 ++-- toxygen/contacts/profile.py | 4 ++-- toxygen/wrapper/tox.py | 2 ++ 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/tests.py b/tests/tests.py index 54e374e..e3c9b6b 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -6,13 +6,13 @@ from toxygen.middleware.tox_factory import * class TestTox: def test_creation(self): - name = b'Toxygen User' - status_message = b'Toxing on Toxygen' + name = 'Toxygen User' + status_message = 'Toxing on Toxygen' tox = tox_factory() tox.self_set_name(name) tox.self_set_status_message(status_message) data = tox.get_savedata() del tox tox = tox_factory(data) - assert tox.self_get_name() == str(name, 'utf-8') - assert tox.self_get_status_message() == str(status_message, 'utf-8') + assert tox.self_get_name() == name + assert tox.self_get_status_message() == status_message diff --git a/toxygen/app.py b/toxygen/app.py index 62025d9..ec0a1cb 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -251,8 +251,8 @@ class App: return False name = profile_name or 'toxygen_user' self._tox = tox_factory() - self._tox.self_set_name(bytes(name, 'utf-8') if name else b'Toxygen User') - self._tox.self_set_status_message(b'Toxing on Toxygen') + 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) diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index ab8cb2a..c47eca2 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -51,11 +51,11 @@ class Profile(basecontact.BaseContact, tox_save.ToxSave): if self.name == value: return super().set_name(value) - self._tox.self_set_name(self._name.encode('utf-8')) + 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.encode('utf-8')) + self._tox.self_set_status_message(self._status_message) def set_new_nospam(self): """Sets new nospam part of tox id""" diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index 836d38c..9cdac2e 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -375,6 +375,7 @@ class Tox: :return: True on success. """ tox_err_set_info = c_int() + name = bytes(name, 'utf-8') result = Tox.libtoxcore.tox_self_set_name(self._tox_pointer, c_char_p(name), c_size_t(len(name)), byref(tox_err_set_info)) tox_err_set_info = tox_err_set_info.value @@ -423,6 +424,7 @@ class Tox: :return: True on success. """ tox_err_set_info = c_int() + status_message = bytes(status_message, 'utf-8') result = Tox.libtoxcore.tox_self_set_status_message(self._tox_pointer, c_char_p(status_message), c_size_t(len(status_message)), byref(tox_err_set_info)) tox_err_set_info = tox_err_set_info.value From 5521b768bc5fe0cc46579f042f6e1ff7127a1108 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 22 Jul 2018 12:59:52 +0300 Subject: [PATCH 095/138] private messages support --- toxygen/app.py | 14 ++- toxygen/common/provider.py | 13 +++ toxygen/contacts/contact_provider.py | 27 ++++- toxygen/contacts/contacts_manager.py | 22 ++++ toxygen/contacts/group_chat.py | 7 +- toxygen/contacts/group_peer_contact.py | 10 +- toxygen/contacts/group_peer_factory.py | 23 ++++ toxygen/groups/group_peer.py | 8 +- toxygen/groups/groups_service.py | 32 ++++-- toxygen/messenger/messenger.py | 142 ++++++++++++++++++------- toxygen/middleware/callbacks.py | 21 ++++ toxygen/ui/contact_items.py | 5 +- toxygen/ui/peer_screen.py | 36 +++++++ toxygen/ui/views/peer_screen.ui | 83 +++++++++++++++ toxygen/ui/widgets_factory.py | 4 + toxygen/wrapper/tox.py | 2 +- 16 files changed, 384 insertions(+), 65 deletions(-) create mode 100644 toxygen/common/provider.py create mode 100644 toxygen/contacts/group_peer_factory.py create mode 100644 toxygen/ui/peer_screen.py create mode 100644 toxygen/ui/views/peer_screen.ui diff --git a/toxygen/app.py b/toxygen/app.py index ec0a1cb..b270f10 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -32,6 +32,8 @@ 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 import styles.style # TODO: dynamic loading @@ -42,7 +44,8 @@ class App: 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 = self._tox_dns = None + self._friend_factory = self._calls_manager = self._contacts_manager = self._smiley_loader = None + self._group_peer_factory = self._tox_dns = None self._group_factory = self._groups_service = self._profile = None if uri is not None and uri.startswith('tox:'): self._uri = uri[4:] @@ -336,7 +339,9 @@ class App: 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._contacts_provider = ContactProvider(self._tox, self._friend_factory, self._group_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) @@ -357,7 +362,10 @@ class App: 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) - self._groups_service = GroupsService(self._tox, self._contacts_manager, self._contacts_provider, self._ms) + 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) diff --git a/toxygen/common/provider.py b/toxygen/common/provider.py new file mode 100644 index 0000000..d16edb4 --- /dev/null +++ b/toxygen/common/provider.py @@ -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 diff --git a/toxygen/contacts/contact_provider.py b/toxygen/contacts/contact_provider.py index 6f13596..76e8e79 100644 --- a/toxygen/contacts/contact_provider.py +++ b/toxygen/contacts/contact_provider.py @@ -3,10 +3,11 @@ import common.tox_save as tox_save class ContactProvider(tox_save.ToxSave): - def __init__(self, tox, friend_factory, group_factory): + 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 # ----------------------------------------------------------------------------------------------------------------- @@ -34,7 +35,7 @@ class ContactProvider(tox_save.ToxSave): return list(friends) # ----------------------------------------------------------------------------------------------------------------- - # GC + # Groups # ----------------------------------------------------------------------------------------------------------------- def get_all_groups(self): @@ -57,12 +58,29 @@ class ContactProvider(tox_save.ToxSave): 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() + return self.get_all_friends() + self.get_all_groups() + self.get_all_group_peers() # ----------------------------------------------------------------------------------------------------------------- # Caching @@ -84,3 +102,6 @@ class ContactProvider(tox_save.ToxSave): 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) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 2729959..6135967 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -2,6 +2,7 @@ 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): @@ -223,6 +224,17 @@ class ContactsManager(ToxSave): 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] @@ -340,6 +352,16 @@ class ContactsManager(ToxSave): 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) + self._contacts.append(contact) + contact.reset_avatar(self._settings['identicons']) + self._save_profile() + # ----------------------------------------------------------------------------------------------------------------- # Friend requests # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index fac2165..61b3858 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -11,8 +11,6 @@ class GroupChat(contact.Contact, ToxSave): def __init__(self, tox, profile_manager, message_getter, number, name, status_message, widget, tox_id): super().__init__(profile_manager, message_getter, number, name, status_message, widget, tox_id) ToxSave.__init__(self, tox) - status = self._tox.group_is_connected(number) - self.set_status(constants.TOX_USER_STATUS['NONE'] if status else None) self._peers = [] self._add_self_to_gc() @@ -57,6 +55,11 @@ class GroupChat(contact.Contact, ToxSave): 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] diff --git a/toxygen/contacts/group_peer_contact.py b/toxygen/contacts/group_peer_contact.py index 1c51f67..47cd16b 100644 --- a/toxygen/contacts/group_peer_contact.py +++ b/toxygen/contacts/group_peer_contact.py @@ -3,11 +3,17 @@ import contacts.contact class GroupPeerContact(contacts.contact.Contact): - def __init__(self, profile_manager, message_getter, peer_number, name, status_messsage, widget, tox_id, group_pk): - super().__init__(profile_manager, message_getter, peer_number, name, status_messsage, widget, tox_id) + 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 None diff --git a/toxygen/contacts/group_peer_factory.py b/toxygen/contacts/group_peer_factory.py new file mode 100644 index 0000000..38b3a20 --- /dev/null +++ b/toxygen/contacts/group_peer_factory.py @@ -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() diff --git a/toxygen/groups/group_peer.py b/toxygen/groups/group_peer.py index d2d5aeb..57724a5 100644 --- a/toxygen/groups/group_peer.py +++ b/toxygen/groups/group_peer.py @@ -2,13 +2,14 @@ class GroupChatPeer: - def __init__(self, peer_id, name, status, role, public_key, is_current_user): + 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 def get_id(self): return self._peer_id @@ -48,3 +49,8 @@ class GroupChatPeer: return self._is_current_user is_current_user = property(get_is_current_user) + + def get_is_muted(self): + return self._is_muted + + is_muted = property(get_is_muted) diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index 1a8ccc0..49d5f25 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -6,11 +6,13 @@ import wrapper.toxcore_enums_and_consts as constants class GroupsService(tox_save.ToxSave): - def __init__(self, tox, contacts_manager, contacts_provider, main_screen): + 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._peers_list_widget = main_screen.peers_list + self._widgets_factory_provider = widgets_factory_provider + self._peer_screen = None def set_tox(self, tox): super().set_tox(tox) @@ -23,8 +25,12 @@ class GroupsService(tox_save.ToxSave): def create_new_gc(self, name, privacy_state): group_number = self._tox.group_new(privacy_state, name.encode('utf-8')) - if group_number != -1: - self._add_new_group_by_number(group_number) + 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'] def join_gc_by_id(self, chat_id, password): group_number = self._tox.group_join(chat_id, password) @@ -45,13 +51,13 @@ class GroupsService(tox_save.ToxSave): def disconnect_from_group(self, group_number): self._tox.group_disconnect(group_number) - group = self._get_group(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(group_number) + group = self._get_group_by_number(group_number) group.status = constants.TOX_USER_STATUS['NONE'] self._clear_peers_list(group) @@ -63,7 +69,7 @@ class GroupsService(tox_save.ToxSave): self._tox.group_invite_friend(group_number, friend_number) def process_group_invite(self, friend_number, group_name, invite_data): - friend = self._get_friend(friend_number) + friend = self._get_friend_by_number(friend_number) text = util_ui.tr('Friend {} invites you to group "{}". Accept?') if util_ui.question(text.format(friend.name, group_name), util_ui.tr('Group invite')): self.join_gc_via_invite(invite_data, friend_number, None) @@ -98,7 +104,10 @@ class GroupsService(tox_save.ToxSave): PeersListGenerator().generate(group.peers, self, self._peers_list_widget, group.tox_id) def peer_selected(self, chat_id, peer_id): - pass + widgets_factory = self._widgets_factory_provider.get_item() + group = self._get_group_by_public_key(chat_id) + self._peer_screen = widgets_factory.create_peer_screen_window(group, peer_id) + self._peer_screen.show() # ----------------------------------------------------------------------------------------------------------------- # Private methods @@ -107,15 +116,18 @@ class GroupsService(tox_save.ToxSave): def _add_new_group_by_number(self, group_number): self._contacts_manager.add_group(group_number) - def _get_group(self, group_number): + def _get_group_by_number(self, group_number): return self._contacts_provider.get_group_by_number(group_number) - def _get_friend(self, friend_number): - return self._contacts_provider.get_friend_by_number(friend_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 _clear_peers_list(self, group): group.remove_all_peers_except_self() self.generate_peers_list() diff --git a/toxygen/messenger/messenger.py b/toxygen/messenger/messenger.py index dbd2d1e..0768f2b 100644 --- a/toxygen/messenger/messenger.py +++ b/toxygen/messenger/messenger.py @@ -46,8 +46,10 @@ class Messenger(tox_save.ToxSave): text = self._screen.messageEdit.toPlainText() if self._contacts_manager.is_active_a_friend(): self.send_message_to_friend(text) - else: + elif self._contacts_manager.is_active_a_group(): self.send_message_to_group(text) + else: + self.send_message_to_group_peer(text) def send_message_to_friend(self, text, friend_number=None): """ @@ -57,30 +59,36 @@ class Messenger(tox_save.ToxSave): """ if friend_number is None: friend_number = self._contacts_manager.get_active_number() + if text.startswith('/plugin '): self._plugin_loader.command(text[8:]) self._screen.messageEdit.clear() - elif text and friend_number >= 0: - if text.startswith('/me '): - message_type = TOX_MESSAGE_TYPE['ACTION'] - text = text[4:] + return + + if not text or friend_number < 0: + return + + if text.startswith('/me '): + message_type = TOX_MESSAGE_TYPE['ACTION'] + text = text[4:] + else: + message_type = TOX_MESSAGE_TYPE['NORMAL'] + 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_type = TOX_MESSAGE_TYPE['NORMAL'] - 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 self._contacts_manager.is_friend_active(friend_number): - self._create_message_item(message) - self._screen.messageEdit.clear() - self._screen.messages.scrollToBottom() + 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): """ @@ -103,27 +111,33 @@ class Messenger(tox_save.ToxSave): def send_message_to_group(self, text, group_number=None): if group_number is None: group_number = self._contacts_manager.get_active_number() + if text.startswith('/plugin '): self._plugin_loader.command(text[8:]) self._screen.messageEdit.clear() - elif text and group_number >= 0: - if text.startswith('/me '): - message_type = TOX_MESSAGE_TYPE['ACTION'] - text = text[4:] - else: - message_type = TOX_MESSAGE_TYPE['NORMAL'] - 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 self._contacts_manager.is_group_active(group_number): - self._create_message_item(message) - self._screen.messageEdit.clear() - self._screen.messages.scrollToBottom() + return + + if not text or group_number < 0: + return + + if text.startswith('/me '): + message_type = TOX_MESSAGE_TYPE['ACTION'] + text = text[4:] + else: + message_type = TOX_MESSAGE_TYPE['NORMAL'] + 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): """ @@ -137,6 +151,53 @@ class Messenger(tox_save.ToxSave): 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, 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 text.startswith('/plugin '): + self._plugin_loader.command(text[8:]) + self._screen.messageEdit.clear() + return + + 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) + message_author = MessageAuthor(group.get_self_name(), MESSAGE_AUTHOR['GC_PEER']) + message = OutgoingTextMessage(text, message_author, t, MESSAGE_TYPE['TEXT']) + 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, 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['TEXT']) + 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 # ----------------------------------------------------------------------------------------------------------------- @@ -211,6 +272,9 @@ class Messenger(tox_save.ToxSave): 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 diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 8730b54..323d5d1 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -375,6 +375,26 @@ def group_message(window, tray, tox, messenger, settings, profile): 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, length, user_data): + message = str(message[:length], 'utf-8') + invoke_in_main_thread(messenger.new_group_private_message, group_number, message, peer_id) + if not window.isActiveWindow(): + bl = settings['notify_all_gc'] or profile.name in message + name = tox.group_peer_get_name(group_number, peer_id) + if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and (not settings.locked) and bl: + invoke_in_main_thread(tray_notification, name, message, tray, window) + if settings['sound_notifications'] and bl and profile.status != TOX_USER_STATUS['BUSY']: + sound_notification(SOUND_NOTIFICATION['MESSAGE']) + icon = os.path.join(util.get_images_directory(), 'icon_new_messages.png') + invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon)) + + return wrapped + + def group_invite(groups_service): def wrapped(tox, friend_number, invite_data, length, group_name, group_name_length, user_data): group_name = bytes(group_name[:group_name_length]) @@ -499,6 +519,7 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, # 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(groups_service), 0) tox.callback_group_self_join(group_self_join(contacts_provider, groups_service), 0) tox.callback_group_peer_join(group_peer_join(contacts_provider, groups_service), 0) diff --git a/toxygen/ui/contact_items.py b/toxygen/ui/contact_items.py index 2ad3ad2..7a32284 100644 --- a/toxygen/ui/contact_items.py +++ b/toxygen/ui/contact_items.py @@ -1,10 +1,7 @@ from wrapper.toxcore_enums_and_consts import * from PyQt5 import QtCore, QtGui, QtWidgets -from contacts import profile -from file_transfers.file_transfers import FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR from utils.util import * -from ui.widgets import DataLabel, create_menu -from user_data import settings +from ui.widgets import DataLabel class ContactItem(QtWidgets.QWidget): diff --git a/toxygen/ui/peer_screen.py b/toxygen/ui/peer_screen.py new file mode 100644 index 0000000..4145fe1 --- /dev/null +++ b/toxygen/ui/peer_screen.py @@ -0,0 +1,36 @@ +from ui.widgets import CenteredWidget +from PyQt5 import QtCore, QtWidgets, uic +import utils.util as util +import utils.ui as util_ui +from ui.contact_items import * + + +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) + + uic.loadUi(util.get_views_path('peer_screen'), self) + self._update_ui() + + def _update_ui(self): + self.statusCircle = StatusCircle(self) + self.statusCircle.setGeometry(50, 20, 20, 20) + self.statusCircle.update(self._peer.status) + self.peerNameLabel.setText(self._peer.name) + self.ignorePeerCheckBox.setChecked(self._peer.is_muted) + self.sendPrivateMessagePushButton.clicked.connect(self._send_private_message) + self._retranslate_ui() + + def _retranslate_ui(self): + self.setWindowTitle(util_ui.tr('Peer details')) + self.ignorePeerCheckBox.setText(util_ui.tr('Ignore peer')) + self.sendPrivateMessagePushButton.setText(util_ui.tr('Send private message')) + + def _send_private_message(self): + self._contacts_manager.add_group_peer(self._group, self._peer) + self.close() diff --git a/toxygen/ui/views/peer_screen.ui b/toxygen/ui/views/peer_screen.ui new file mode 100644 index 0000000..cf221c0 --- /dev/null +++ b/toxygen/ui/views/peer_screen.ui @@ -0,0 +1,83 @@ + + + Form + + + + 0 + 0 + 600 + 400 + + + + + 600 + 400 + + + + + 600 + 400 + + + + Form + + + + + 110 + 10 + 431 + 41 + + + + TextLabel + + + + + + 50 + 120 + 500 + 50 + + + + PushButton + + + + + + 50 + 70 + 500 + 23 + + + + CheckBox + + + + + + 50 + 200 + 521 + 161 + + + + GroupBox + + + + + + diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py index ead09ce..6a0d772 100644 --- a/toxygen/ui/widgets_factory.py +++ b/toxygen/ui/widgets_factory.py @@ -1,6 +1,7 @@ from ui.main_screen_widgets import * from ui.menu import * from ui.groups_widgets import * +from ui.peer_screen import * class WidgetsFactory: @@ -69,3 +70,6 @@ class WidgetsFactory: def create_search_screen(self, messages): return SearchScreen(self._contacts_manager, self._history, messages, messages.parent()) + + def create_peer_screen_window(self, group, peer_id): + return PeerScreen(self._contacts_manager, self._groups_service, group, peer_id) diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index 9cdac2e..7912103 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -1804,7 +1804,7 @@ class Tox: """ Write the group public key with the designated peer_id for the designated group number to public_key. - This key will be parmanently tied to a particular peer until they explicitly leave the group or + This key will be permanently tied to a particular peer until they explicitly leave the group or get kicked/banned, and is the only way to reliably identify the same peer across client restarts. `public_key` should have room for at least TOX_GROUP_PEER_PUBLIC_KEY_SIZE bytes. From eba7e0c0dcd38c7129c036b4929f5e5f273e53cd Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 22 Jul 2018 14:08:47 +0300 Subject: [PATCH 096/138] group peer context menu --- toxygen/contacts/contact_menu.py | 17 +++++++++++++++++ toxygen/contacts/contacts_manager.py | 6 ++++++ toxygen/contacts/group_peer_contact.py | 3 ++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/toxygen/contacts/contact_menu.py b/toxygen/contacts/contact_menu.py index 6a2eb86..e10f07d 100644 --- a/toxygen/contacts/contact_menu.py +++ b/toxygen/contacts/contact_menu.py @@ -194,3 +194,20 @@ class GroupMenuGenerator(BaseContactMenuGenerator): ).build() return menu + + +class GroupPeerMenuGenerator(BaseContactMenuGenerator): + + def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service): + copy_menu_builder = self._generate_copy_menu_builder(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_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 diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 6135967..5913617 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -362,6 +362,12 @@ class ContactsManager(ToxSave): contact.reset_avatar(self._settings['identicons']) self._save_profile() + 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) + # ----------------------------------------------------------------------------------------------------------------- # Friend requests # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/contacts/group_peer_contact.py b/toxygen/contacts/group_peer_contact.py index 47cd16b..8854198 100644 --- a/toxygen/contacts/group_peer_contact.py +++ b/toxygen/contacts/group_peer_contact.py @@ -1,4 +1,5 @@ import contacts.contact +from contacts.contact_menu import GroupPeerMenuGenerator class GroupPeerContact(contacts.contact.Contact): @@ -16,4 +17,4 @@ class GroupPeerContact(contacts.contact.Contact): pass def get_context_menu_generator(self): - return None + return GroupPeerMenuGenerator(self) From 5e1f060fac4eecb06d251072695b3bd46dc1ffca Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 22 Jul 2018 19:39:42 +0300 Subject: [PATCH 097/138] private messages - types --- toxygen/messenger/messenger.py | 14 ++++++--- toxygen/middleware/callbacks.py | 4 +-- toxygen/ui/peer_screen.py | 18 +++++++++++ toxygen/ui/views/peer_screen.ui | 55 ++++++++++++++++++++++++++++----- toxygen/wrapper/tox.py | 7 +++-- 5 files changed, 81 insertions(+), 17 deletions(-) diff --git a/toxygen/messenger/messenger.py b/toxygen/messenger/messenger.py index 0768f2b..99d7f2f 100644 --- a/toxygen/messenger/messenger.py +++ b/toxygen/messenger/messenger.py @@ -170,14 +170,20 @@ class Messenger(tox_save.ToxSave): if not text or group_number < 0 or peer_id < 0: return + if text.startswith('/me '): + message_type = TOX_MESSAGE_TYPE['ACTION'] + text = text[4:] + else: + message_type = TOX_MESSAGE_TYPE['NORMAL'] + 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) + 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['TEXT']) + 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 @@ -185,7 +191,7 @@ class Messenger(tox_save.ToxSave): self._screen.messageEdit.clear() self._screen.messages.scrollToBottom() - def new_group_private_message(self, group_number, message, peer_id): + def new_group_private_message(self, group_number, message_type, message, peer_id): """ Current user gets new message :param message: text of message @@ -194,7 +200,7 @@ class Messenger(tox_save.ToxSave): 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['TEXT']) + 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) diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 323d5d1..9e6059c 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -379,9 +379,9 @@ 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, length, user_data): + 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, peer_id) + invoke_in_main_thread(messenger.new_group_private_message, group_number, message_type, message, peer_id) if not window.isActiveWindow(): bl = settings['notify_all_gc'] or profile.name in message name = tox.group_peer_get_name(group_number, peer_id) diff --git a/toxygen/ui/peer_screen.py b/toxygen/ui/peer_screen.py index 4145fe1..6850125 100644 --- a/toxygen/ui/peer_screen.py +++ b/toxygen/ui/peer_screen.py @@ -24,13 +24,31 @@ class PeerScreen(CenteredWidget): self.peerNameLabel.setText(self._peer.name) self.ignorePeerCheckBox.setChecked(self._peer.is_muted) self.sendPrivateMessagePushButton.clicked.connect(self._send_private_message) + self.copyPublicKeyPushButton.clicked.connect(self._copy_public_key) + self.roleNameLabel.setText(self._get_role_name()) self._retranslate_ui() 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')) + def _get_role_name(self): + roles = { + 0: util_ui.tr('Administrator'), + 1: util_ui.tr('Moderator'), + 2: util_ui.tr('User'), + 3: util_ui.tr('Observer') + } + + return roles[self._peer.role] + def _send_private_message(self): self._contacts_manager.add_group_peer(self._group, self._peer) self.close() + + def _copy_public_key(self): + clipboard = QtWidgets.QApplication.clipboard() + clipboard.setText(self._peer.public_key) diff --git a/toxygen/ui/views/peer_screen.ui b/toxygen/ui/views/peer_screen.ui index cf221c0..f719ec6 100644 --- a/toxygen/ui/views/peer_screen.ui +++ b/toxygen/ui/views/peer_screen.ui @@ -7,19 +7,19 @@ 0 0 600 - 400 + 500 600 - 400 + 500 600 - 400 + 500 @@ -31,7 +31,7 @@ 110 10 431 - 41 + 40 @@ -42,7 +42,7 @@ 50 - 120 + 140 500 50 @@ -55,7 +55,7 @@ 50 - 70 + 100 500 23 @@ -68,8 +68,8 @@ 50 - 200 - 521 + 300 + 500 161 @@ -77,6 +77,45 @@ GroupBox + + + + 50 + 60 + 67 + 20 + + + + TextLabel + + + + + + 140 + 60 + 401 + 20 + + + + TextLabel + + + + + + 50 + 210 + 500 + 50 + + + + PushButton + + diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index 7912103..64b55ea 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -2060,7 +2060,7 @@ class Tox: len(data), byref(error)) return result - def group_send_private_message(self, group_number, peer_id, message): + def group_send_private_message(self, group_number, peer_id, message_type, message): """ Send a text chat message to the specified peer in the specified group. @@ -2079,7 +2079,8 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_send_private_message(self._tox_pointer, group_number, peer_id, message, + result = Tox.libtoxcore.tox_group_send_private_message(self._tox_pointer, group_number, peer_id, + message_type, message, len(message), byref(error)) return result @@ -2135,7 +2136,7 @@ class Tox: This event is triggered when the client receives a private message. """ - c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_char_p, c_size_t, c_void_p) + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint8, c_char_p, c_size_t, c_void_p) self.group_private_message_cb = c_callback(callback) Tox.libtoxcore.tox_callback_group_private_message(self._tox_pointer, self.group_private_message_cb, user_data) From 20f36e06ad59d5cadbf96340708b652564c7f553 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 23 Jul 2018 00:35:52 +0300 Subject: [PATCH 098/138] roles support - callbacks, peer screen --- toxygen/app.py | 5 +-- toxygen/contacts/contacts_manager.py | 51 +++++++++++++++++----------- toxygen/contacts/group_chat.py | 3 ++ toxygen/groups/groups_service.py | 13 +++++++ toxygen/middleware/callbacks.py | 37 ++++++++++++++++++-- toxygen/ui/peer_screen.py | 49 +++++++++++++++++++++----- toxygen/ui/views/peer_screen.ui | 14 ++++++-- 7 files changed, 139 insertions(+), 33 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index b270f10..23a465a 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -237,7 +237,7 @@ class App: self._profile_manager = ProfileManager(self._settings, self._toxes, profile_path) data = self._profile_manager.open_profile() if self._toxes.is_data_encrypted(data): - data = self._enter_pass(data) + data = self._enter_password(data) self._tox = self._create_tox(data) def _create_new_profile(self, profile_name): @@ -292,7 +292,7 @@ class App: # Other private methods # ----------------------------------------------------------------------------------------------------------------- - def _enter_pass(self, data): + def _enter_password(self, data): """ Show password screen """ @@ -326,6 +326,7 @@ class App: self._calls_manager.set_toxav(self._tox.AV) self._contacts_manager.update_friends_numbers() + self._contacts_manager.update_groups_lists() self._init_callbacks() diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 5913617..10f50ca 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -260,25 +260,26 @@ class ContactsManager(ToxSave): 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 ok: - 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() + 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 @@ -362,6 +363,13 @@ class ContactsManager(ToxSave): 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) @@ -438,6 +446,11 @@ class ContactsManager(ToxSave): 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 # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index 61b3858..5409a26 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -28,6 +28,9 @@ class GroupChat(contact.Contact, ToxSave): # Peers methods # ----------------------------------------------------------------------------------------------------------------- + def get_self_peer(self): + return self._peers[0] + def get_self_name(self): return self._peers[0].name diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index 49d5f25..692fb31 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -109,6 +109,19 @@ class GroupsService(tox_save.ToxSave): self._peer_screen = widgets_factory.create_peer_screen_window(group, peer_id) self._peer_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 + # ----------------------------------------------------------------------------------------------------------------- # Private methods # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 9e6059c..a480223 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -424,9 +424,10 @@ def group_peer_join(contacts_provider, groups_service): return wrapped -def group_peer_exit(contacts_provider, groups_service): +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) + contacts_manager.remove_group_peer_by_id(group, peer_id) group.remove_peer(peer_id) invoke_in_main_thread(groups_service.generate_peers_list) @@ -461,6 +462,37 @@ def group_topic(contacts_provider): 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']) + + groups_service.generate_peers_list() + + return wrapped + # ----------------------------------------------------------------------------------------------------------------- # Callbacks - initialization # ----------------------------------------------------------------------------------------------------------------- @@ -523,7 +555,8 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, tox.callback_group_invite(group_invite(groups_service), 0) tox.callback_group_self_join(group_self_join(contacts_provider, 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), 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) diff --git a/toxygen/ui/peer_screen.py b/toxygen/ui/peer_screen.py index 6850125..651d998 100644 --- a/toxygen/ui/peer_screen.py +++ b/toxygen/ui/peer_screen.py @@ -14,36 +14,69 @@ class PeerScreen(CenteredWidget): 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, 20, 20, 20) + 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 = self._can_change_role() + self.rolesComboBox.setVisible(can_change_role) + self.roleNameLabel.setVisible(not can_change_role) + 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.banGroupBox.setTitle(util_ui.tr('Moderation')) + + 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(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): - roles = { - 0: util_ui.tr('Administrator'), - 1: util_ui.tr('Moderator'), - 2: util_ui.tr('User'), - 3: util_ui.tr('Observer') - } + return self._roles[self._peer.role] - return 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) diff --git a/toxygen/ui/views/peer_screen.ui b/toxygen/ui/views/peer_screen.ui index f719ec6..f6b13b3 100644 --- a/toxygen/ui/views/peer_screen.ui +++ b/toxygen/ui/views/peer_screen.ui @@ -93,9 +93,9 @@ - 140 + 130 60 - 401 + 411 20 @@ -116,6 +116,16 @@ PushButton + + + + 130 + 55 + 291 + 30 + + + From 27d24ecaf4dcabad9ad7c76b6c78441a40dc1f32 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 23 Jul 2018 00:50:53 +0300 Subject: [PATCH 099/138] group - privacy state added --- toxygen/contacts/group_chat.py | 12 +++++++++++- toxygen/contacts/group_factory.py | 4 +++- toxygen/middleware/callbacks.py | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index 5409a26..38ab686 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -8,9 +8,10 @@ from common.tox_save import ToxSave class GroupChat(contact.Contact, ToxSave): - def __init__(self, tox, profile_manager, message_getter, number, name, status_message, widget, tox_id): + 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._peers = [] self._add_self_to_gc() @@ -24,6 +25,15 @@ class GroupChat(contact.Contact, ToxSave): def get_context_menu_generator(self): return GroupMenuGenerator(self) + # ----------------------------------------------------------------------------------------------------------------- + # Properties + # ----------------------------------------------------------------------------------------------------------------- + + def get_is_private(self): + return self._is_private + + is_private = property(get_is_private) + # ----------------------------------------------------------------------------------------------------------------- # Peers methods # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/contacts/group_factory.py b/toxygen/contacts/group_factory.py index 8db3e9a..2a925f3 100644 --- a/toxygen/contacts/group_factory.py +++ b/toxygen/contacts/group_factory.py @@ -1,5 +1,6 @@ from contacts.group_chat import GroupChat from common.tox_save import ToxSave +import wrapper.toxcore_enums_and_consts as constants class GroupFactory(ToxSave): @@ -27,8 +28,9 @@ class GroupFactory(ToxSave): 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() == constants.TOX_GROUP_PRIVACY_STATE['PRIVATE'] group = GroupChat(self._tox, self._profile_manager, message_getter, group_number, name, status_message, - item, tox_id) + item, tox_id, is_private) group.set_alias(alias) return group diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index a480223..3d40cc9 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -489,7 +489,7 @@ def group_moderation(groups_service, contacts_provider, contacts_manager, messen elif event_type == TOX_GROUP_MOD_EVENT['MODERATOR']: update_peer_role(group, mod_peer_id, peer_id, TOX_GROUP_ROLE['MODERATOR']) - groups_service.generate_peers_list() + invoke_in_main_thread(groups_service.generate_peers_list) return wrapped From 850c3b1ca3398a1b7df85826f2088cbccebc3466 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 24 Jul 2018 23:40:29 +0300 Subject: [PATCH 100/138] self peer screen added --- toxygen/contacts/group_factory.py | 2 +- toxygen/groups/groups_service.py | 13 +++++- toxygen/ui/self_peer_screen.py | 67 +++++++++++++++++++++++++++++++ toxygen/ui/widgets_factory.py | 4 ++ 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 toxygen/ui/self_peer_screen.py diff --git a/toxygen/contacts/group_factory.py b/toxygen/contacts/group_factory.py index 2a925f3..4083438 100644 --- a/toxygen/contacts/group_factory.py +++ b/toxygen/contacts/group_factory.py @@ -28,7 +28,7 @@ class GroupFactory(ToxSave): 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() == constants.TOX_GROUP_PRIVACY_STATE['PRIVATE'] + 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) diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index 692fb31..94d5653 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -106,7 +106,11 @@ class GroupsService(tox_save.ToxSave): def peer_selected(self, chat_id, peer_id): widgets_factory = self._widgets_factory_provider.get_item() group = self._get_group_by_public_key(chat_id) - self._peer_screen = widgets_factory.create_peer_screen_window(group, peer_id) + self_peer = group.get_self_peer() + if self_peer.id != peer_id: + self._peer_screen = widgets_factory.create_peer_screen_window(group, peer_id) + else: + self._peer_screen = widgets_factory.create_self_peer_screen_window(group) self._peer_screen.show() # ----------------------------------------------------------------------------------------------------------------- @@ -122,6 +126,13 @@ class GroupsService(tox_save.ToxSave): 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 + # ----------------------------------------------------------------------------------------------------------------- # Private methods # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/ui/self_peer_screen.py b/toxygen/ui/self_peer_screen.py new file mode 100644 index 0000000..ebc1426 --- /dev/null +++ b/toxygen/ui/self_peer_screen.py @@ -0,0 +1,67 @@ +from ui.widgets import CenteredWidget, LineEdit +from PyQt5 import QtCore, QtWidgets, 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): + clipboard = QtWidgets.QApplication.clipboard() + clipboard.setText(self._peer.public_key) diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py index 6a0d772..1ddc6a5 100644 --- a/toxygen/ui/widgets_factory.py +++ b/toxygen/ui/widgets_factory.py @@ -2,6 +2,7 @@ from ui.main_screen_widgets import * from ui.menu import * from ui.groups_widgets import * from ui.peer_screen import * +from ui.self_peer_screen import * class WidgetsFactory: @@ -73,3 +74,6 @@ class WidgetsFactory: def create_peer_screen_window(self, group, peer_id): return PeerScreen(self._contacts_manager, self._groups_service, group, peer_id) + + def create_self_peer_screen_window(self, group): + return SelfPeerScreen(self._contacts_manager, self._groups_service, group) From 3272617403699e3b6d1a6f1197047ec046c2bd57 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 26 Jul 2018 00:38:25 +0300 Subject: [PATCH 101/138] join group with different credentials --- toxygen/app.py | 2 +- toxygen/groups/groups_service.py | 17 +++--- toxygen/ui/groups_widgets.py | 60 ++++++++++++++++--- toxygen/ui/views/create_group_screen.ui | 80 +++++++++++++++++++------ toxygen/ui/views/join_group_screen.ui | 78 ++++++++++++++++++++---- toxygen/ui/widgets_factory.py | 4 +- toxygen/wrapper/tox.py | 32 ++++++---- 7 files changed, 217 insertions(+), 56 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index 23a465a..77864e7 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -366,7 +366,7 @@ class App: 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_provider, self._profile) 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) diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index 94d5653..f9cdfc9 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -6,12 +6,13 @@ 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): + def __init__(self, tox, contacts_manager, contacts_provider, main_screen, widgets_factory_provider, profile): super().__init__(tox) self._contacts_manager = contacts_manager self._contacts_provider = contacts_provider self._peers_list_widget = main_screen.peers_list self._widgets_factory_provider = widgets_factory_provider + self._profile = profile self._peer_screen = None def set_tox(self, tox): @@ -23,8 +24,8 @@ class GroupsService(tox_save.ToxSave): # Groups creation # ----------------------------------------------------------------------------------------------------------------- - def create_new_gc(self, name, privacy_state): - group_number = self._tox.group_new(privacy_state, name.encode('utf-8')) + 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 @@ -32,12 +33,12 @@ class GroupsService(tox_save.ToxSave): group = self._get_group_by_number(group_number) group.status = constants.TOX_USER_STATUS['NONE'] - def join_gc_by_id(self, chat_id, password): - group_number = self._tox.group_join(chat_id, password) + 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) - def join_gc_via_invite(self, invite_data, friend_number, password): - group_number = self._tox.group_invite_accept(invite_data, friend_number, password) + 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) # ----------------------------------------------------------------------------------------------------------------- @@ -72,7 +73,7 @@ class GroupsService(tox_save.ToxSave): friend = self._get_friend_by_number(friend_number) text = util_ui.tr('Friend {} invites you to group "{}". Accept?') if util_ui.question(text.format(friend.name, group_name), util_ui.tr('Group invite')): - self.join_gc_via_invite(invite_data, friend_number, None) + self.join_gc_via_invite(invite_data, friend_number, self._profile.name, self._profile.status or 0, None) # ----------------------------------------------------------------------------------------------------------------- # Group info methods diff --git a/toxygen/ui/groups_widgets.py b/toxygen/ui/groups_widgets.py index aa91935..b9f38a9 100644 --- a/toxygen/ui/groups_widgets.py +++ b/toxygen/ui/groups_widgets.py @@ -3,72 +3,116 @@ import utils.util as util from ui.widgets import * from wrapper.toxcore_enums_and_consts import * +# TODO: move common logic to separate class + class CreateGroupScreen(CenteredWidget): - def __init__(self, groups_service): + def __init__(self, groups_service, profile): super().__init__() self._groups_service = groups_service + self._profile = 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): 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.nickLabel.setText(util_ui.tr('Nickname:')) + self.statusLabel.setText(util_ui.tr('Status:')) + self.nickLineEdit.setPlaceholderText(util_ui.tr('Your nick in chat')) 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) + self.statusComboBox.addItem(util_ui.tr('Online')) + self.statusComboBox.addItem(util_ui.tr('Away')) + self.statusComboBox.addItem(util_ui.tr('Busy')) def _create_group(self): - name = self.groupNameLineEdit.text() + group_name = self.groupNameLineEdit.text() privacy_state = self.groupTypeComboBox.currentIndex() - self._groups_service.create_new_gc(name, privacy_state) + 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): - name = self.groupNameLineEdit.text() - self.addGroupButton.setEnabled(bool(name.strip())) + 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(CenteredWidget): - def __init__(self, groups_service): + def __init__(self, groups_service, profile): super().__init__() self._groups_service = groups_service + self._profile = 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): 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.nickLabel.setText(util_ui.tr('Nickname:')) + self.statusLabel.setText(util_ui.tr('Status:')) self.chatIdLineEdit.setPlaceholderText(util_ui.tr('Group\'s chat ID')) + self.nickLineEdit.setPlaceholderText(util_ui.tr('Your nick in chat')) self.joinGroupButton.setText(util_ui.tr('Join group')) self.passwordLineEdit.setPlaceholderText(util_ui.tr('Optional password')) + self.statusComboBox.addItem(util_ui.tr('Online')) + self.statusComboBox.addItem(util_ui.tr('Away')) + self.statusComboBox.addItem(util_ui.tr('Busy')) 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() - self.joinGroupButton.setEnabled(len(chat_id) == TOX_GROUP_CHAT_ID_SIZE * 2) + 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() - self._groups_service.join_gc_by_id(chat_id, password) + 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): diff --git a/toxygen/ui/views/create_group_screen.ui b/toxygen/ui/views/create_group_screen.ui index 08a27ff..3a3358a 100644 --- a/toxygen/ui/views/create_group_screen.ui +++ b/toxygen/ui/views/create_group_screen.ui @@ -6,8 +6,8 @@ 0 0 - 639 - 199 + 640 + 300 @@ -19,9 +19,9 @@ - 180 - 150 - 271 + 20 + 250 + 601 41 @@ -32,28 +32,28 @@ - 140 - 40 - 471 - 31 + 150 + 20 + 470 + 35 - 140 - 100 - 471 - 41 + 150 + 80 + 470 + 35 - 10 - 40 + 20 + 20 121 31 @@ -65,8 +65,8 @@ - 10 - 100 + 20 + 80 121 31 @@ -75,6 +75,52 @@ TextLabel + + + + 20 + 200 + 111 + 17 + + + + TextLabel + + + + + + 20 + 150 + 111 + 17 + + + + TextLabel + + + + + + 150 + 140 + 470 + 35 + + + + + + + 150 + 190 + 470 + 35 + + + diff --git a/toxygen/ui/views/join_group_screen.ui b/toxygen/ui/views/join_group_screen.ui index 66b0420..077a332 100644 --- a/toxygen/ui/views/join_group_screen.ui +++ b/toxygen/ui/views/join_group_screen.ui @@ -6,10 +6,22 @@ 0 0 - 739 - 212 + 740 + 320 + + + 740 + 320 + + + + + 740 + 320 + + Form @@ -17,7 +29,7 @@ 30 - 40 + 30 67 17 @@ -30,7 +42,7 @@ 30 - 100 + 90 67 17 @@ -45,9 +57,9 @@ - 258 - 150 - 241 + 30 + 260 + 680 51 @@ -60,7 +72,7 @@ 190 20 - 431 + 520 41 @@ -69,8 +81,54 @@ 190 - 90 - 431 + 80 + 520 + 41 + + + + + + + 30 + 150 + 67 + 17 + + + + TextLabel + + + + + + 30 + 210 + 67 + 17 + + + + TextLabel + + + + + + 190 + 140 + 520 + 41 + + + + + + + 190 + 200 + 520 41 diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py index 1ddc6a5..8f341df 100644 --- a/toxygen/ui/widgets_factory.py +++ b/toxygen/ui/widgets_factory.py @@ -64,10 +64,10 @@ class WidgetsFactory: return StickerWindow(self._file_transfer_handler, self._contacts_manager) def create_group_screen_window(self): - return CreateGroupScreen(self._groups_service) + return CreateGroupScreen(self._groups_service, self._profile) def create_join_group_screen_window(self): - return JoinGroupScreen(self._groups_service) + return JoinGroupScreen(self._groups_service, self._profile) def create_search_screen(self, messages): return SearchScreen(self._contacts_manager, self._history, messages, messages.parent()) diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index 64b55ea..cb2f25e 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -29,7 +29,7 @@ class ToxOptions(Structure): class GroupChatSelfPeerInfo(Structure): _fields_ = [ ('nick', c_char_p), - ('nick_length', c_uint16), + ('nick_length', c_uint8), ('user_status', c_int) ] @@ -1533,7 +1533,7 @@ class Tox: # Group chat instance management # ----------------------------------------------------------------------------------------------------------------- - def group_new(self, privacy_state, group_name): + def group_new(self, privacy_state, group_name, nick, status): """ Creates a new group chat. @@ -1551,12 +1551,16 @@ class Tox: """ error = c_int() - peer_info = self.group_chat_self_peer_info_new() - result = Tox.libtoxcore.tox_group_new(self._tox_pointer, privacy_state, group_name, + peer_info = self.group_self_peer_info_new() + nick = bytes(nick, 'utf-8') + peer_info.contents.nick = c_char_p(nick) + peer_info.contents.nick_length = len(nick) + peer_info.contents.user_status = status + result = Tox.libtoxcore.tox_group_new(self._tox_pointer, privacy_state, group_name.encode('utf-8'), len(group_name), peer_info, byref(error)) return result - def group_join(self, chat_id, password): + def group_join(self, chat_id, password, nick, status): """ Joins a group chat with specified Chat ID. @@ -1571,7 +1575,11 @@ class Tox: """ error = c_int() - peer_info = self.group_chat_self_peer_info_new() + peer_info = self.group_self_peer_info_new() + nick = bytes(nick, 'utf-8') + peer_info.contents.nick = c_char_p(nick) + peer_info.contents.nick_length = len(nick) + peer_info.contents.user_status = status result = Tox.libtoxcore.tox_group_join(self._tox_pointer, string_to_bin(chat_id), password, len(password) if password is not None else 0, @@ -2171,15 +2179,15 @@ class Tox: result = Tox.libtoxcore.tox_group_invite_friend(self._tox_pointer, group_number, friend_number, byref(error)) return result - def group_chat_self_peer_info_new(self): + def group_self_peer_info_new(self): error = c_int() - f = Tox.libtoxcore.group_chat_self_peer_info_new + f = Tox.libtoxcore.tox_group_self_peer_info_new f.restype = POINTER(GroupChatSelfPeerInfo) result = f(self._tox_pointer, byref(error)) return result - def group_invite_accept(self, invite_data, friend_number, password=None): + def group_invite_accept(self, invite_data, friend_number, nick, status, password=None): """ Accept an invite to a group chat that the client previously received from a friend. The invite is only valid while the inviter is present in the group. @@ -2192,7 +2200,11 @@ class Tox: error = c_int() f = Tox.libtoxcore.tox_group_invite_accept f.restype = c_uint32 - peer_info = self.group_chat_self_peer_info_new() + peer_info = self.group_self_peer_info_new() + nick = bytes(nick, 'utf-8') + peer_info.contents.nick = c_char_p(nick) + peer_info.contents.nick_length = len(nick) + peer_info.contents.user_status = status result = f(self._tox_pointer, friend_number, invite_data, len(invite_data), password, len(password) if password is not None else 0, peer_info, byref(error)) print('Invite accept. Result:', result, 'Error:', error.value) From 1728a45cf35a25c5921443469c56609280aaa6ff Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 26 Jul 2018 21:27:20 +0300 Subject: [PATCH 102/138] peers screen refactoring --- toxygen/ui/groups_widgets.py | 38 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/toxygen/ui/groups_widgets.py b/toxygen/ui/groups_widgets.py index b9f38a9..ad4b703 100644 --- a/toxygen/ui/groups_widgets.py +++ b/toxygen/ui/groups_widgets.py @@ -3,15 +3,27 @@ import utils.util as util from ui.widgets import * from wrapper.toxcore_enums_and_consts import * -# TODO: move common logic to separate class - -class CreateGroupScreen(CenteredWidget): +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() @@ -27,20 +39,15 @@ class CreateGroupScreen(CenteredWidget): 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.nickLabel.setText(util_ui.tr('Nickname:')) - self.statusLabel.setText(util_ui.tr('Status:')) - self.nickLineEdit.setPlaceholderText(util_ui.tr('Your nick in chat')) 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) - self.statusComboBox.addItem(util_ui.tr('Online')) - self.statusComboBox.addItem(util_ui.tr('Away')) - self.statusComboBox.addItem(util_ui.tr('Busy')) def _create_group(self): group_name = self.groupNameLineEdit.text() @@ -62,12 +69,10 @@ class CreateGroupScreen(CenteredWidget): self.addGroupButton.setEnabled(is_nick_set and is_group_name_set) -class JoinGroupScreen(CenteredWidget): +class JoinGroupScreen(BaseGroupScreen): def __init__(self, groups_service, profile): - super().__init__() - self._groups_service = groups_service - self._profile = profile + super().__init__(groups_service, profile) uic.loadUi(util.get_views_path('join_group_screen'), self) self.center() self._update_ui() @@ -83,18 +88,13 @@ class JoinGroupScreen(CenteredWidget): 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.nickLabel.setText(util_ui.tr('Nickname:')) - self.statusLabel.setText(util_ui.tr('Status:')) self.chatIdLineEdit.setPlaceholderText(util_ui.tr('Group\'s chat ID')) - self.nickLineEdit.setPlaceholderText(util_ui.tr('Your nick in chat')) self.joinGroupButton.setText(util_ui.tr('Join group')) self.passwordLineEdit.setPlaceholderText(util_ui.tr('Optional password')) - self.statusComboBox.addItem(util_ui.tr('Online')) - self.statusComboBox.addItem(util_ui.tr('Away')) - self.statusComboBox.addItem(util_ui.tr('Busy')) def _chat_id_changed(self): self._update_button_state() From 184ba55aedc802b5e9aaa9ee4cb0e12ea325cb85 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 28 Jul 2018 13:14:16 +0300 Subject: [PATCH 103/138] group invites page --- toxygen/app.py | 5 +- toxygen/groups/group_invite.py | 23 +++++ toxygen/groups/groups_service.py | 40 ++++++-- toxygen/middleware/callbacks.py | 2 +- toxygen/ui/group_invites_widgets.py | 120 +++++++++++++++++++++++ toxygen/ui/main_screen.py | 8 ++ toxygen/ui/views/gc_invite_item.ui | 71 ++++++++++++++ toxygen/ui/views/group_invites_screen.ui | 113 +++++++++++++++++++++ toxygen/ui/views/self_peer_screen.ui | 119 ++++++++++++++++++++++ toxygen/ui/widgets_factory.py | 7 +- toxygen/wrapper/tox.py | 1 + 11 files changed, 496 insertions(+), 13 deletions(-) create mode 100644 toxygen/groups/group_invite.py create mode 100644 toxygen/ui/group_invites_widgets.py create mode 100644 toxygen/ui/views/gc_invite_item.ui create mode 100644 toxygen/ui/views/group_invites_screen.ui create mode 100644 toxygen/ui/views/self_peer_screen.ui diff --git a/toxygen/app.py b/toxygen/app.py index 77864e7..a7fbaff 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -366,10 +366,11 @@ class App: 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, self._profile) + 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._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, diff --git a/toxygen/groups/group_invite.py b/toxygen/groups/group_invite.py new file mode 100644 index 0000000..a2eed47 --- /dev/null +++ b/toxygen/groups/group_invite.py @@ -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) diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index f9cdfc9..76ecdaf 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -1,18 +1,19 @@ 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, profile): + 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._peers_list_widget = main_screen.peers_list self._widgets_factory_provider = widgets_factory_provider - self._profile = profile + self._group_invites = [] self._peer_screen = None def set_tox(self, tox): @@ -37,10 +38,6 @@ class GroupsService(tox_save.ToxSave): group_number = self._tox.group_join(chat_id, password, nick, status) self._add_new_group_by_number(group_number) - 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) - # ----------------------------------------------------------------------------------------------------------------- # Groups reconnect and leaving # ----------------------------------------------------------------------------------------------------------------- @@ -71,9 +68,23 @@ class GroupsService(tox_save.ToxSave): def process_group_invite(self, friend_number, group_name, invite_data): friend = self._get_friend_by_number(friend_number) - text = util_ui.tr('Friend {} invites you to group "{}". Accept?') - if util_ui.question(text.format(friend.name, group_name), util_ui.tr('Group invite')): - self.join_gc_via_invite(invite_data, friend_number, self._profile.name, self._profile.status or 0, None) + invite = GroupInvite(friend.tox_id, group_name, invite_data) + self._group_invites.append(invite) + # TODO: notification on main screen + + 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) + + def decline_group_invite(self, invite): + self._delete_group_invite(invite) + + def get_group_invites(self): + return self._group_invites[:] + + group_invites = property(get_group_invites) # ----------------------------------------------------------------------------------------------------------------- # Group info methods @@ -153,6 +164,17 @@ class GroupsService(tox_save.ToxSave): 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) diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 3d40cc9..3f25e0c 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -438,7 +438,7 @@ 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]) + peer.name = str(name[:length], 'utf-8') invoke_in_main_thread(groups_service.generate_peers_list) return wrapped diff --git a/toxygen/ui/group_invites_widgets.py b/toxygen/ui/group_invites_widgets.py new file mode 100644 index 0000000..77403d9 --- /dev/null +++ b/toxygen/ui/group_invites_widgets.py @@ -0,0 +1,120 @@ +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._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() + + 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() + + 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) diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 1bc3612..d02c689 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -88,12 +88,14 @@ class MainWindow(QtWidgets.QMainWindow): 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) @@ -128,6 +130,7 @@ class MainWindow(QtWidgets.QMainWindow): 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() @@ -149,6 +152,7 @@ class MainWindow(QtWidgets.QMainWindow): 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")) @@ -733,3 +737,7 @@ class MainWindow(QtWidgets.QMainWindow): if self._should_show_group_peers_list: self._toggle_gc_peers_list() self.resizeEvent() + + def _open_gc_invites_list(self): + self._modal_window = self._widget_factory.create_group_invites_window() + self._modal_window.show() diff --git a/toxygen/ui/views/gc_invite_item.ui b/toxygen/ui/views/gc_invite_item.ui new file mode 100644 index 0000000..1048a55 --- /dev/null +++ b/toxygen/ui/views/gc_invite_item.ui @@ -0,0 +1,71 @@ + + + Form + + + + 0 + 0 + 600 + 150 + + + + Form + + + + + 250 + 30 + 300 + 21 + + + + TextLabel + + + + + + 250 + 70 + 300 + 21 + + + + TextLabel + + + + + + 140 + 30 + 60 + 60 + + + + TextLabel + + + + + + 40 + 50 + 16 + 23 + + + + + + + + + + diff --git a/toxygen/ui/views/group_invites_screen.ui b/toxygen/ui/views/group_invites_screen.ui new file mode 100644 index 0000000..183f801 --- /dev/null +++ b/toxygen/ui/views/group_invites_screen.ui @@ -0,0 +1,113 @@ + + + Form + + + + 0 + 0 + 600 + 500 + + + + + 600 + 500 + + + + + 600 + 500 + + + + Form + + + + + 0 + 150 + 600 + 25 + + + + TextLabel + + + Qt::AlignCenter + + + + + + 0 + 0 + 600 + 341 + + + + + + + 10 + 360 + 350 + 35 + + + + + + + 10 + 410 + 350 + 35 + + + + + + + 390 + 390 + 200 + 35 + + + + + + + 40 + 460 + 201 + 31 + + + + PushButton + + + + + + 360 + 460 + 201 + 31 + + + + PushButton + + + + + + diff --git a/toxygen/ui/views/self_peer_screen.ui b/toxygen/ui/views/self_peer_screen.ui new file mode 100644 index 0000000..38e1f88 --- /dev/null +++ b/toxygen/ui/views/self_peer_screen.ui @@ -0,0 +1,119 @@ + + + Form + + + + 0 + 0 + 600 + 500 + + + + + 600 + 500 + + + + + 600 + 500 + + + + Form + + + + + 50 + 120 + 67 + 20 + + + + TextLabel + + + + + + 50 + 250 + 500 + 50 + + + + PushButton + + + + + + 140 + 110 + 400 + 40 + + + + + + + 50 + 40 + 67 + 20 + + + + TextLabel + + + + + + 50 + 190 + 67 + 20 + + + + TextLabel + + + + + + 140 + 190 + 411 + 20 + + + + TextLabel + + + + + + 50 + 330 + 500 + 50 + + + + PushButton + + + + + + diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py index 8f341df..75b83b4 100644 --- a/toxygen/ui/widgets_factory.py +++ b/toxygen/ui/widgets_factory.py @@ -3,12 +3,13 @@ from ui.menu import * from ui.groups_widgets import * from ui.peer_screen import * from ui.self_peer_screen import * +from ui.group_invites_widgets import * class WidgetsFactory: def __init__(self, settings, profile, profile_manager, contacts_manager, file_transfer_handler, smiley_loader, - plugin_loader, toxes, version, groups_service, history): + plugin_loader, toxes, version, groups_service, history, contacts_provider): self._settings = settings self._profile = profile self._profile_manager = profile_manager @@ -20,6 +21,7 @@ class WidgetsFactory: self._version = version self._groups_service = groups_service self._history = history + self._contacts_provider = contacts_provider def create_screenshot_window(self, *args): return ScreenShotWindow(self._file_transfer_handler, self._contacts_manager, *args) @@ -77,3 +79,6 @@ class WidgetsFactory: def create_self_peer_screen_window(self, group): return SelfPeerScreen(self._contacts_manager, self._groups_service, group) + + def create_group_invites_window(self): + return GroupInvitesScreen(self._groups_service, self._profile, self._contacts_provider) diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index cb2f25e..d705293 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -1651,6 +1651,7 @@ class Tox: """ error = c_int() + name = bytes(name, 'utf-8') result = Tox.libtoxcore.tox_group_self_set_name(self._tox_pointer, group_number, name, len(name), byref(error)) return result From 603dfd40b55689d33d1393faaa4f1f7551ec6ff2 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 28 Jul 2018 18:16:26 +0300 Subject: [PATCH 104/138] tray notification on gc invite --- toxygen/middleware/callbacks.py | 56 ++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 3f25e0c..1716b49 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -363,15 +363,17 @@ def group_message(window, tray, tox, messenger, settings, profile): 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 not window.isActiveWindow(): - bl = settings['notify_all_gc'] or profile.name in message - name = tox.group_peer_get_name(group_number, peer_id) - if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and (not settings.locked) and bl: - invoke_in_main_thread(tray_notification, name, message, tray, window) - if settings['sound_notifications'] and bl and profile.status != TOX_USER_STATUS['BUSY']: - sound_notification(SOUND_NOTIFICATION['MESSAGE']) - icon = os.path.join(util.get_images_directory(), 'icon_new_messages.png') - invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon)) + 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 = os.path.join(util.get_images_directory(), 'icon_new_messages.png') + invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon)) + return wrapped @@ -382,25 +384,35 @@ def group_private_message(window, tray, tox, messenger, settings, profile): 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 not window.isActiveWindow(): - bl = settings['notify_all_gc'] or profile.name in message - name = tox.group_peer_get_name(group_number, peer_id) - if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and (not settings.locked) and bl: - invoke_in_main_thread(tray_notification, name, message, tray, window) - if settings['sound_notifications'] and bl and profile.status != TOX_USER_STATUS['BUSY']: - sound_notification(SOUND_NOTIFICATION['MESSAGE']) - icon = os.path.join(util.get_images_directory(), 'icon_new_messages.png') - invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon)) + 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 = os.path.join(util.get_images_directory(), 'icon_new_messages.png') + invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon)) return wrapped -def group_invite(groups_service): +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 = bytes(group_name[:group_name_length]) + group_name = str(bytes(group_name[:group_name_length]), 'utf-8') invoke_in_main_thread(groups_service.process_group_invite, - friend_number, str(group_name, 'utf-8'), + 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 = os.path.join(util.get_images_directory(), 'icon_new_messages.png') + invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon)) return wrapped @@ -552,7 +564,7 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, # 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(groups_service), 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, 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) From 10a77960dc3a50ecf789916cf20e6ea41d68d999 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 29 Jul 2018 00:06:33 +0300 Subject: [PATCH 105/138] friends column converted to .ui. added gc invites button --- toxygen/contacts/contacts_manager.py | 2 +- toxygen/contacts/profile.py | 2 +- toxygen/groups/groups_service.py | 10 +- toxygen/styles/dark_style.qss | 10 +- toxygen/styles/style.qss | 8 +- toxygen/ui/main_screen.py | 146 ++++++++++++--------------- toxygen/ui/views/gc_invite_item.ui | 2 +- 7 files changed, 94 insertions(+), 86 deletions(-) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 10f50ca..ca9e00d 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -25,7 +25,7 @@ class ContactsManager(ToxSave): self._sorting = settings['sorting'] self._filter_string = '' self._friend_item_height = 40 if settings['compact_mode'] else 70 - screen.online_contacts.setCurrentIndex(int(self._sorting)) + #screen.online_contacts.setCurrentIndex(int(self._sorting)) self._history = history self._load_contacts() diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index c47eca2..ed7cd83 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -17,7 +17,7 @@ class Profile(basecontact.BaseContact, tox_save.ToxSave): profile_manager, tox.self_get_name(), tox.self_get_status_message(), - screen.user_info, + screen, tox.self_get_address()) tox_save.ToxSave.__init__(self, tox) self._screen = screen diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index 76ecdaf..5aefe5a 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -11,6 +11,7 @@ class GroupsService(tox_save.ToxSave): 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 = [] @@ -70,22 +71,29 @@ class GroupsService(tox_save.ToxSave): friend = self._get_friend_by_number(friend_number) invite = GroupInvite(friend.tox_id, group_name, invite_data) self._group_invites.append(invite) - # TODO: notification on main screen + self._main_screen.update_gc_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._main_screen.update_gc_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 # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/styles/dark_style.qss b/toxygen/styles/dark_style.qss index 3b2837b..ece5ec3 100644 --- a/toxygen/styles/dark_style.qss +++ b/toxygen/styles/dark_style.qss @@ -1253,7 +1253,7 @@ MessageBrowser background-color: #1E90FF; } -#friends_list:item:selected +#friendsListWidget:item:selected { background-color: #333333; } @@ -1277,7 +1277,7 @@ QListWidget > QLabel color: #A9A9A9; } -#contact_name +#searchLineEdit { padding-left: 22px; } @@ -1327,3 +1327,9 @@ ClickableLabel:hover { color: #BC1C1C; } + +#groupInvitesPushButton +{ + background-color: #009c00; +} + diff --git a/toxygen/styles/style.qss b/toxygen/styles/style.qss index 956ee63..ff9f614 100644 --- a/toxygen/styles/style.qss +++ b/toxygen/styles/style.qss @@ -1,4 +1,4 @@ -#contact_name +#searchLineEdit { padding-left: 22px; } @@ -32,3 +32,9 @@ MessageEdit { color: #BC1C1C; } + +#groupInvitesPushButton +{ + background-color: #009c00; +} + diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index d02c689..43a6b19 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -3,6 +3,7 @@ from ui.widgets import MultilineEdit, ComboBox 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): @@ -38,6 +39,8 @@ class MainWindow(QtWidgets.QMainWindow): self._contacts_manager.active_contact_changed.add_callback(self._new_contact_selected) self.messageEdit.set_messenger(messenger) + self.update_gc_invites_button_state() + def show(self): super().show() self._contacts_manager.update() @@ -163,19 +166,21 @@ class MainWindow(QtWidgets.QMainWindow): self.audioSettings.setText(util_ui.tr("Audio")) self.videoSettings.setText(util_ui.tr("Video")) self.updateSettings.setText(util_ui.tr("Updates")) - self.contact_name.setPlaceholderText(util_ui.tr("Search")) + + 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.online_contacts.clear() - self.online_contacts.addItem(util_ui.tr("All")) - self.online_contacts.addItem(util_ui.tr("Online")) - self.online_contacts.addItem(util_ui.tr("Online first")) - self.online_contacts.addItem(util_ui.tr("Name")) - self.online_contacts.addItem(util_ui.tr("Online and by name")) - self.online_contacts.addItem(util_ui.tr("Online first and by name")) + 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")) + ind = self._settings['sorting'] d = {0: 0, 1: 1, 2: 2, 3: 4, 4: 3, 1 | 4: 4, 2 | 4: 5} - self.online_contacts.setCurrentIndex(d[ind]) + self.contactsFilterComboBox.setCurrentIndex(d[ind]) self.importPlugin.setText(util_ui.tr("Import plugin")) self.reloadPlugins.setText(util_ui.tr("Reload plugins")) @@ -208,54 +213,48 @@ class MainWindow(QtWidgets.QMainWindow): 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)) + 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')) - self.search_label.setScaledContents(False) - self.search_label.setPixmap(pixmap) + left_column.searchLabel.setPixmap(pixmap) - self.contact_name = LineEdit(Form) - self.contact_name.setObjectName('contact_name') - self.contact_name.setGeometry(QtCore.QRect(0, 0, 150, 25)) - 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)) + 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) - Form.name.setFont(font) - 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) - self.connection_status = Form.connection_status = StatusCircle(Form) - Form.connection_status.setGeometry(QtCore.QRect(230, 10, 32, 32)) + 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.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.connection_status.raise_() + + self.friends_list = left_column.friendsListWidget + 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_top(self, Form): Form.resize(650, 75) @@ -303,18 +302,6 @@ class MainWindow(QtWidgets.QMainWindow): 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) @@ -351,35 +338,27 @@ class MainWindow(QtWidgets.QMainWindow): menu = QtWidgets.QWidget() main = QtWidgets.QWidget() grid = QtWidgets.QGridLayout() - search = QtWidgets.QWidget() - name = QtWidgets.QWidget() info = QtWidgets.QWidget() - main_list = QtWidgets.QWidget() + left_column = 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_left_column(left_column) self.setup_menu(menu) if not s['mirror_mode']: - grid.addWidget(search, 2, 0) - grid.addWidget(name, 1, 0) + 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.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(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.addWidget(main_list, 3, 1, 2, 1) grid.setColumnMinimumWidth(0, 500) grid.setColumnMinimumWidth(1, 270) @@ -396,7 +375,6 @@ class MainWindow(QtWidgets.QMainWindow): main.setLayout(grid) self.setCentralWidget(main) self.messageEdit.setFocus() - self.user_info = name self.friend_info = info self.retranslateUi() @@ -428,7 +406,10 @@ class MainWindow(QtWidgets.QMainWindow): 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) - self.friends_list.setGeometry(0, 0, 270, self.height() - 125) + + 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)) @@ -617,7 +598,7 @@ class MainWindow(QtWidgets.QMainWindow): # Functions which called when user open context menu in friends list # ----------------------------------------------------------------------------------------------------------------- - def friend_right_click(self, pos): + 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) @@ -696,23 +677,23 @@ class MainWindow(QtWidgets.QMainWindow): # Functions which called when user click somewhere else # ----------------------------------------------------------------------------------------------------------------- - def friend_click(self, index): + def _friend_click(self, index): num = index.row() self._contacts_manager.active_contact = num self.groupMenuButton.setVisible(not self._contacts_manager.is_active_a_friend()) 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() + 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): - ind = self.online_contacts.currentIndex() + def _filtering(self): + ind = self.contactsFilterComboBox.currentIndex() d = {0: 0, 1: 1, 2: 2, 3: 4, 4: 1 | 4, 5: 2 | 4} - self._contacts_manager.filtration_and_sorting(d[ind], self.contact_name.text()) + self._contacts_manager.filtration_and_sorting(d[ind], self.searchLineEdit.text()) def show_search_field(self): if hasattr(self, 'search_field') and self.search_field.isVisible(): @@ -741,3 +722,10 @@ class MainWindow(QtWidgets.QMainWindow): 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() diff --git a/toxygen/ui/views/gc_invite_item.ui b/toxygen/ui/views/gc_invite_item.ui index 1048a55..dc077a7 100644 --- a/toxygen/ui/views/gc_invite_item.ui +++ b/toxygen/ui/views/gc_invite_item.ui @@ -57,7 +57,7 @@ 40 50 - 16 + 23 23 From f38df2494798a414507b0d7f23bec77186036c92 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 29 Jul 2018 11:16:03 +0300 Subject: [PATCH 106/138] filtering fixed --- toxygen/contacts/contacts_manager.py | 71 +++++++++++---------- toxygen/ui/main_screen.py | 16 ++--- toxygen/ui/views/gc_invite_item.ui | 2 +- toxygen/ui/views/ms_left_column.ui | 94 ++++++++++++++++++++++++++++ 4 files changed, 139 insertions(+), 44 deletions(-) create mode 100644 toxygen/ui/views/ms_left_column.ui diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index ca9e00d..3499f42 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -25,7 +25,7 @@ class ContactsManager(ToxSave): self._sorting = settings['sorting'] self._filter_string = '' self._friend_item_height = 40 if settings['compact_mode'] else 70 - #screen.online_contacts.setCurrentIndex(int(self._sorting)) + screen.contacts_filter.setCurrentIndex(int(self._sorting)) self._history = history self._load_contacts() @@ -163,50 +163,55 @@ class ContactsManager(ToxSave): def filtration_and_sorting(self, sorting=0, filter_str=''): """ Filtration of friends list - :param sorting: 0 - no sorting, 1 - online only, 2 - online first, 4 - by name + :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 """ - # TODO: simplify? filter_str = filter_str.lower() - number = self.get_active_number() - is_friend = self.is_active_a_friend() - if sorting > 1: - if sorting & 2: - self._contacts = sorted(self._contacts, key=lambda x: int(x.status is not None), reverse=True) - if sorting & 4: - if not sorting & 2: - self._contacts = sorted(self._contacts, key=lambda x: x.name.lower()) - else: # save results of prev sorting - online_friends = filter(lambda x: x.status is not None, self._contacts) - count = len(list(online_friends)) - part1 = self._contacts[:count] - part2 = self._contacts[count:] - part1 = sorted(part1, key=lambda x: x.name.lower()) - part2 = sorted(part2, key=lambda x: x.name.lower()) - self._contacts = part1 + part2 - else: # sort by number - online_friends = filter(lambda x: x.status is not None, self._contacts) - count = len(list(online_friends)) - part1 = self._contacts[:count] - part2 = self._contacts[count:] - part1 = sorted(part1, key=lambda x: x.number) - part2 = sorted(part2, key=lambda x: x.number) - self._contacts = part1 + part2 - 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) + 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: + self._contacts = sorted(self._contacts, key=lambda x: x.number) + 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): - friend.visibility = (friend.status is not None or not (sorting & 1)) and (filter_str in friend.name.lower()) + 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 if friend.visibility: self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, self._friend_item_height)) else: self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, 0)) + # save soring results self._sorting, self._filter_string = sorting, filter_str self._settings['sorting'] = self._sorting self._settings.save() - self.set_active_by_number_and_type(number, is_friend) + # update active contact + index = self._contacts.index(contact) + self.set_active(index) def update_filtration(self): """ diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 43a6b19..d57667b 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -166,6 +166,8 @@ class MainWindow(QtWidgets.QMainWindow): 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")) @@ -178,12 +180,6 @@ class MainWindow(QtWidgets.QMainWindow): self.contactsFilterComboBox.addItem(util_ui.tr("Online and by name")) self.contactsFilterComboBox.addItem(util_ui.tr("Online first and by name")) - ind = self._settings['sorting'] - d = {0: 0, 1: 1, 2: 2, 3: 4, 4: 3, 1 | 4: 4, 2 | 4: 5} - self.contactsFilterComboBox.setCurrentIndex(d[ind]) - self.importPlugin.setText(util_ui.tr("Import plugin")) - self.reloadPlugins.setText(util_ui.tr("Reload plugins")) - def setup_right_bottom(self, Form): Form.resize(650, 60) self.messageEdit = MessageArea(Form, self) @@ -238,7 +234,7 @@ class MainWindow(QtWidgets.QMainWindow): self.avatar_label = left_column.avatarLabel self.searchLineEdit = left_column.searchLineEdit - self.contactsFilterComboBox = left_column.contactsFilterComboBox + self.contacts_filter = self.contactsFilterComboBox = left_column.contactsFilterComboBox self.groupInvitesPushButton = left_column.groupInvitesPushButton @@ -691,9 +687,9 @@ class MainWindow(QtWidgets.QMainWindow): super().mouseReleaseEvent(event) def _filtering(self): - ind = self.contactsFilterComboBox.currentIndex() - d = {0: 0, 1: 1, 2: 2, 3: 4, 4: 1 | 4, 5: 2 | 4} - self._contacts_manager.filtration_and_sorting(d[ind], self.searchLineEdit.text()) + 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(): diff --git a/toxygen/ui/views/gc_invite_item.ui b/toxygen/ui/views/gc_invite_item.ui index dc077a7..6eddbeb 100644 --- a/toxygen/ui/views/gc_invite_item.ui +++ b/toxygen/ui/views/gc_invite_item.ui @@ -57,7 +57,7 @@ 40 50 - 23 + 20 23 diff --git a/toxygen/ui/views/ms_left_column.ui b/toxygen/ui/views/ms_left_column.ui new file mode 100644 index 0000000..ffbff71 --- /dev/null +++ b/toxygen/ui/views/ms_left_column.ui @@ -0,0 +1,94 @@ + + + Form + + + + 0 + 0 + 270 + 500 + + + + PointingHandCursor + + + Form + + + + + 5 + 5 + 64 + 64 + + + + PointingHandCursor + + + TextLabel + + + + + + 0 + 75 + 150 + 25 + + + + + + + 150 + 75 + 120 + 25 + + + + + + + 0 + 77 + 20 + 20 + + + + TextLabel + + + + + + 0 + 100 + 270 + 400 + + + + + + + 0 + 100 + 270 + 30 + + + + PushButton + + + + + + From 250551e7527b16aee29ad63457f2337852606270 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 29 Jul 2018 13:36:16 +0300 Subject: [PATCH 107/138] fixed group numbers restoring. contact selection fixed --- toxygen/app.py | 1 + toxygen/contacts/contact.py | 28 ++++++++++++++++------------ toxygen/contacts/contacts_manager.py | 16 +++++++--------- toxygen/groups/groups_service.py | 1 - toxygen/middleware/callbacks.py | 5 +++-- toxygen/ui/main_screen.py | 9 ++++++--- 6 files changed, 33 insertions(+), 27 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index a7fbaff..80c26ea 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -327,6 +327,7 @@ class App: 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() diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index fb124dd..418ac15 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -43,18 +43,22 @@ class Contact(basecontact.BaseContact): """ :param first_time: friend became active, load first part of messages """ - if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')): - return - if self._message_getter is None: - return - data = list(self._message_getter.get(PAGE_SIZE)) - if data is not None and len(data): - data.reverse() - else: - return - data = list(map(lambda p: self._get_text_message(p), data)) - self._corr = data + self._corr - self._history_loaded = True + try: + if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')): + return + if self._message_getter is None: + return + data = list(self._message_getter.get(PAGE_SIZE)) + if data is not None and len(data): + data.reverse() + else: + return + data = list(map(lambda p: self._get_text_message(p), data)) + self._corr = data + self._corr + except: + pass + finally: + self._history_loaded = True def load_all_corr(self): """ diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 3499f42..0800655 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -139,13 +139,6 @@ class ContactsManager(ToxSave): active_contact_changed = property(get_active_contact_changed) - def set_active_by_number_and_type(self, number, is_friend): # TODO: by id - for i in range(len(self._contacts)): - c = self._contacts[i] - if c.number == number and (type(c) is Friend == is_friend): - self._active_contact = i - break - def update(self): if self._active_contact + 1: self.set_active(self._active_contact) @@ -309,6 +302,7 @@ class ContactsManager(ToxSave): """ self._tox.friend_add_norequest(tox_id) self._add_friend(tox_id) + self.update_filtration() def block_user(self, tox_id): """ @@ -351,6 +345,7 @@ class ContactsManager(ToxSave): self._contacts.append(group) group.reset_avatar(self._settings['identicons']) self._save_profile() + self.update_filtration() def delete_group(self, group_number): group = self.get_group_by_number(group_number) @@ -464,7 +459,7 @@ class ContactsManager(ToxSave): self._load_friends() self._load_groups() if len(self._contacts): - self.set_active(0) + self._screen.select_contact_row(0) for contact in filter(lambda c: not c.has_avatar(), self._contacts): contact.reset_avatar(self._settings['identicons']) self.update_filtration() @@ -542,7 +537,10 @@ class ContactsManager(ToxSave): def _delete_contact(self, num): if num == self._active_contact: # active friend was deleted - self.set_active(0 if len(self._contacts) > 1 else -1) + if len(self._contacts) == 0: + self.set_active(-1) + else: + self._screen.select_contact_row(0) self._contact_provider.remove_contact_from_cache(self._contacts[num].tox_id) del self._contacts[num] self._screen.friends_list.takeItem(num) diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index 5aefe5a..c062685 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -46,7 +46,6 @@ class GroupsService(tox_save.ToxSave): def leave_group(self, group_number): self._tox.group_leave(group_number) self._contacts_manager.delete_group(group_number) - self._contacts_manager.update_groups_numbers() def disconnect_from_group(self, group_number): self._tox.group_disconnect(group_number) diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 1716b49..f8300ff 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -417,11 +417,12 @@ def group_invite(window, settings, tray, profile, groups_service, contacts_provi return wrapped -def group_self_join(contacts_provider, groups_service): +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 @@ -565,7 +566,7 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, 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, groups_service), 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) diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index d57667b..54b6804 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -244,7 +244,7 @@ class MainWindow(QtWidgets.QMainWindow): self.name.mouseReleaseEvent = self.profile_settings self.friends_list = left_column.friendsListWidget - self.friends_list.clicked.connect(self._friend_click) + 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) @@ -669,12 +669,15 @@ class MainWindow(QtWidgets.QMainWindow): 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 _friend_click(self, index): - num = index.row() + def _selected_contact_changed(self): + num = self.friends_list.currentRow() self._contacts_manager.active_contact = num self.groupMenuButton.setVisible(not self._contacts_manager.is_active_a_friend()) From c66dcb0ca2c348bd9e20bbdd056bdcef5795a9cd Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 29 Jul 2018 16:11:34 +0300 Subject: [PATCH 108/138] contact selection fixes --- toxygen/contacts/contacts_manager.py | 19 +++++++++++++------ toxygen/ui/main_screen.py | 5 ++++- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 0800655..9b4ad93 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -342,9 +342,11 @@ class ContactsManager(ToxSave): 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): @@ -402,6 +404,7 @@ class ContactsManager(ToxSave): 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 @@ -459,7 +462,7 @@ class ContactsManager(ToxSave): self._load_friends() self._load_groups() if len(self._contacts): - self._screen.select_contact_row(0) + 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() @@ -512,10 +515,12 @@ class ContactsManager(ToxSave): 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() @@ -536,12 +541,14 @@ class ContactsManager(ToxSave): remove(avatar_path) def _delete_contact(self, num): - if num == self._active_contact: # active friend was deleted - if len(self._contacts) == 0: - self.set_active(-1) - else: - self._screen.select_contact_row(0) + if len(self._contacts) == 1: + self.set_active(-1) + else: + self.set_active(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() diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 54b6804..edf7ac6 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -713,9 +713,12 @@ class MainWindow(QtWidgets.QMainWindow): if self._should_show_group_peers_list: self._groups_service.generate_peers_list() - def _new_contact_selected(self, contact): + 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): From dd323e3cbb41861d83abada047c106c6564676e6 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 29 Jul 2018 21:26:53 +0300 Subject: [PATCH 109/138] minor fixes for group invites screen --- toxygen/ui/group_invites_widgets.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/toxygen/ui/group_invites_widgets.py b/toxygen/ui/group_invites_widgets.py index 77403d9..d35aca1 100644 --- a/toxygen/ui/group_invites_widgets.py +++ b/toxygen/ui/group_invites_widgets.py @@ -45,6 +45,7 @@ class GroupInvitesScreen(CenteredWidget): self.declinePushButton.clicked.connect(self._decline_invites) self.invitesListWidget.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) + self.invitesListWidget.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) self._update_buttons_state() @@ -72,6 +73,7 @@ class GroupInvitesScreen(CenteredWidget): 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() @@ -79,6 +81,7 @@ class GroupInvitesScreen(CenteredWidget): 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() @@ -118,3 +121,7 @@ class GroupInvitesScreen(CenteredWidget): 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() From c7a83055b15c9c2e963be6d57d07f0098abd90fc Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 1 Aug 2018 00:47:57 +0300 Subject: [PATCH 110/138] various fixes --- toxygen/contacts/contacts_manager.py | 5 +++-- toxygen/middleware/callbacks.py | 2 +- toxygen/ui/main_screen.py | 5 +++-- toxygen/wrapper/tox.py | 8 +++++--- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 9b4ad93..c1d0b10 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -203,8 +203,9 @@ class ContactsManager(ToxSave): self._settings['sorting'] = self._sorting self._settings.save() # update active contact - index = self._contacts.index(contact) - self.set_active(index) + if contact is not None: + index = self._contacts.index(contact) + self.set_active(index) def update_filtration(self): """ diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index f8300ff..36bb960 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -409,7 +409,7 @@ def group_invite(window, settings, tray, profile, groups_service, contacts_provi 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) + text = util_ui.tr('{} invites you to group "{}"').format(friend.name, group_name) invoke_in_main_thread(tray_notification, title, text, tray, window) icon = os.path.join(util.get_images_directory(), 'icon_new_messages.png') invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon)) diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index edf7ac6..4cccdd5 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -678,8 +678,9 @@ class MainWindow(QtWidgets.QMainWindow): def _selected_contact_changed(self): num = self.friends_list.currentRow() - self._contacts_manager.active_contact = num - self.groupMenuButton.setVisible(not self._contacts_manager.is_active_a_friend()) + 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() diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index d705293..782020c 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -1553,10 +1553,11 @@ class Tox: error = c_int() peer_info = self.group_self_peer_info_new() nick = bytes(nick, 'utf-8') + group_name = group_name.encode('utf-8') peer_info.contents.nick = c_char_p(nick) peer_info.contents.nick_length = len(nick) peer_info.contents.user_status = status - result = Tox.libtoxcore.tox_group_new(self._tox_pointer, privacy_state, group_name.encode('utf-8'), + result = Tox.libtoxcore.tox_group_new(self._tox_pointer, privacy_state, group_name, len(group_name), peer_info, byref(error)) return result @@ -2180,11 +2181,12 @@ class Tox: result = Tox.libtoxcore.tox_group_invite_friend(self._tox_pointer, group_number, friend_number, byref(error)) return result - def group_self_peer_info_new(self): + @staticmethod + def group_self_peer_info_new(): error = c_int() f = Tox.libtoxcore.tox_group_self_peer_info_new f.restype = POINTER(GroupChatSelfPeerInfo) - result = f(self._tox_pointer, byref(error)) + result = f(byref(error)) return result From 25de4fa2ef99668d33911493c9c77f95448b10a9 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 3 Aug 2018 18:04:28 +0300 Subject: [PATCH 111/138] wrapper update and minor fixes --- toxygen/contacts/contacts_manager.py | 12 +++++++----- toxygen/groups/groups_service.py | 3 ++- toxygen/wrapper/tox.py | 23 +++++++++++++++-------- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index c1d0b10..4a5454b 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -179,7 +179,11 @@ class ContactsManager(ToxSave): part2 = sorted(part2, key=key_lambda) self._contacts = part1 + part2 elif sorting == 0: - self._contacts = sorted(self._contacts, key=lambda x: x.number) + 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()) @@ -194,6 +198,7 @@ class ContactsManager(ToxSave): 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 + # TODO: calculate height if friend.visibility: self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, self._friend_item_height)) else: @@ -542,10 +547,7 @@ class ContactsManager(ToxSave): remove(avatar_path) def _delete_contact(self, num): - if len(self._contacts) == 1: - self.set_active(-1) - else: - self.set_active(0) + 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] diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index c062685..1016bd7 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -104,7 +104,7 @@ class GroupsService(tox_save.ToxSave): def set_group_topic(self, group): if not group.is_moderator_or_founder(): return - text = util_ui.tr('New topic for group {}:'.format(group.name)) + 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: @@ -151,6 +151,7 @@ class GroupsService(tox_save.ToxSave): self_peer = group.get_self_peer() self_peer.name = name self_peer.status = status + self.generate_peers_list() # ----------------------------------------------------------------------------------------------------------------- # Private methods diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index 782020c..ebb6aee 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -1940,6 +1940,13 @@ class Tox: result = Tox.libtoxcore.tox_group_get_number_groups(self._tox_pointer) return result + def groups_get_list(self): + groups_list_size = self.group_get_number_groups() + groups_list = create_string_buffer(sizeof(c_uint32) * groups_list_size) + groups_list = POINTER(c_uint32)(groups_list) + Tox.libtoxcore.tox_groups_get_list(self._tox_pointer, groups_list) + return groups_list[0:groups_list_size] + def group_get_privacy_state(self, group_number): """ Return the privacy state of the group designated by the given group number. If group number @@ -2461,7 +2468,7 @@ class Tox: create_string_buffer(sizeof(c_uint32) * self.group_ban_get_list_size(group_number)), byref(error))) return result - def group_ban_get_name_size(self, group_number, ban_id): + def group_ban_get_target_size(self, group_number, ban_id): """ Return the length of the name for the ban list entry designated by ban_id, in the group designated by the given group number. If either group_number or ban_id is invalid, @@ -2469,10 +2476,10 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_ban_get_name_size(self._tox_pointer, group_number, ban_id, byref(error)) + result = Tox.libtoxcore.tox_group_ban_get_target_size(self._tox_pointer, group_number, ban_id, byref(error)) return result - def group_ban_get_name(self, group_number, ban_id): + def group_ban_get_target(self, group_number, ban_id): """ Write the name of the ban entry designated by ban_id in the group designated by the given group number to a byte array. @@ -2483,12 +2490,12 @@ class Tox: """ error = c_int() - size = self.group_ban_get_name_size(group_number, ban_id) - name = create_string_buffer() + size = self.group_ban_get_target_size(group_number, ban_id) + target = create_string_buffer(size) - result = Tox.libtoxcore.tox_group_ban_get_name(self._tox_pointer, group_number, ban_id, - name, byref(error)) - return str(name[:size], 'utf-8') + result = Tox.libtoxcore.tox_group_ban_get_target(self._tox_pointer, group_number, ban_id, + target, byref(error)) + return str(target[:size], 'utf-8') def group_ban_get_time_set(self, group_number, ban_id): """ From 5f56d630ce19dcf2ab4ca68f6c7befbdbd905eb6 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 3 Aug 2018 21:07:18 +0300 Subject: [PATCH 112/138] messenger refactoring --- toxygen/contacts/contacts_manager.py | 14 +++--- toxygen/messenger/messenger.py | 73 ++++++++++------------------ toxygen/ui/menu.py | 34 +++++++------ 3 files changed, 52 insertions(+), 69 deletions(-) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 4a5454b..757bfb0 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -24,7 +24,6 @@ class ContactsManager(ToxSave): self._active_contact_changed = Event() self._sorting = settings['sorting'] self._filter_string = '' - self._friend_item_height = 40 if settings['compact_mode'] else 70 screen.contacts_filter.setCurrentIndex(int(self._sorting)) self._history = history self._load_contacts() @@ -149,6 +148,9 @@ class ContactsManager(ToxSave): 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 # ----------------------------------------------------------------------------------------------------------------- @@ -198,11 +200,9 @@ class ContactsManager(ToxSave): 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 - # TODO: calculate height - if friend.visibility: - self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, self._friend_item_height)) - else: - self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, 0)) + 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 @@ -436,7 +436,7 @@ class ContactsManager(ToxSave): util.log('Accept friend request failed! ' + str(ex)) def can_send_typing_notification(self): - return self._settings['typing_notifications'] and self._active_contact + 1 + return self._settings['typing_notifications'] and not self.is_active_a_group_chat_peer() # ----------------------------------------------------------------------------------------------------------------- # Contacts numbers update diff --git a/toxygen/messenger/messenger.py b/toxygen/messenger/messenger.py index 99d7f2f..cce619d 100644 --- a/toxygen/messenger/messenger.py +++ b/toxygen/messenger/messenger.py @@ -44,14 +44,28 @@ class Messenger(tox_save.ToxSave): def send_message(self): text = self._screen.messageEdit.toPlainText() - if self._contacts_manager.is_active_a_friend(): - self.send_message_to_friend(text) - elif self._contacts_manager.is_active_a_group(): - self.send_message_to_group(text) - else: - self.send_message_to_group_peer(text) - def send_message_to_friend(self, text, friend_number=None): + 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 @@ -60,19 +74,9 @@ class Messenger(tox_save.ToxSave): if friend_number is None: friend_number = self._contacts_manager.get_active_number() - if text.startswith('/plugin '): - self._plugin_loader.command(text[8:]) - self._screen.messageEdit.clear() - return - if not text or friend_number < 0: return - if text.startswith('/me '): - message_type = TOX_MESSAGE_TYPE['ACTION'] - text = text[4:] - else: - message_type = TOX_MESSAGE_TYPE['NORMAL'] friend = self._get_friend_by_number(friend_number) messages = self._split_message(text.encode('utf-8')) t = util.get_unix_time() @@ -108,23 +112,13 @@ class Messenger(tox_save.ToxSave): # Messaging - groups # ----------------------------------------------------------------------------------------------------------------- - def send_message_to_group(self, text, group_number=None): + 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 text.startswith('/plugin '): - self._plugin_loader.command(text[8:]) - self._screen.messageEdit.clear() - return - if not text or group_number < 0: return - if text.startswith('/me '): - message_type = TOX_MESSAGE_TYPE['ACTION'] - text = text[4:] - else: - message_type = TOX_MESSAGE_TYPE['NORMAL'] group = self._get_group_by_number(group_number) messages = self._split_message(text.encode('utf-8')) t = util.get_unix_time() @@ -155,27 +149,16 @@ class Messenger(tox_save.ToxSave): # Messaging - group peers # ----------------------------------------------------------------------------------------------------------------- - def send_message_to_group_peer(self, text, group_number=None, peer_id=None): + 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 text.startswith('/plugin '): - self._plugin_loader.command(text[8:]) - self._screen.messageEdit.clear() - return - if not text or group_number < 0 or peer_id < 0: return - if text.startswith('/me '): - message_type = TOX_MESSAGE_TYPE['ACTION'] - text = text[4:] - else: - message_type = TOX_MESSAGE_TYPE['NORMAL'] - 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')) @@ -220,12 +203,10 @@ class Messenger(tox_save.ToxSave): """ Send typing notification to a friend """ - if self._contacts_manager.can_send_typing_notification(): - try: - contact = self._contacts_manager.get_curr_contact() - contact.typing_notification_handler.send(self._tox, typing) - except: - pass + 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): """ diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index 07fbb85..ea49e82 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -589,22 +589,24 @@ class InterfaceSettings(CenteredWidget): def import_sm(self): directory = util_ui.directory_dialog(util_ui.tr('Choose folder with smiley pack')) - if directory: - src = directory + '/' - dest = curr_directory() + '/smileys/' + os.path.basename(directory) + '/' - copy(src, dest) + if not directory: + return + src = directory + '/' + dest = get_smileys_directory() + os.path.basename(directory) + '/' + copy(src, dest) def new_font(self): font, ok = QtWidgets.QFontDialog.getFont(QtGui.QFont(self._settings['font'], 10), self) - if ok: - self._settings['font'] = font.family() - self._settings.save() - util_ui.question() - msgBox = QtWidgets.QMessageBox() - text = util_ui.tr('Restart app to apply settings') - msgBox.setWindowTitle(util_ui.tr('Restart required')) - msgBox.setText(text) - msgBox.exec_() + if not ok: + return + self._settings['font'] = font.family() + self._settings.save() + util_ui.question() + msgBox = QtWidgets.QMessageBox() + text = util_ui.tr('Restart app to apply settings') + msgBox.setWindowTitle(util_ui.tr('Restart required')) + msgBox.setText(text) + msgBox.exec_() def select_color(self): col = QtWidgets.QColorDialog.getColor(QtGui.QColor(self._settings['unread_color'])) @@ -616,10 +618,10 @@ class InterfaceSettings(CenteredWidget): def closeEvent(self, event): self._settings['theme'] = str(self.themeSelect.currentText()) + app = QtWidgets.QApplication.instance() try: theme = self._settings['theme'] - app = QtWidgets.QApplication.instance() - with open(curr_directory() + self._settings.built_in_themes()[theme]) as fl: + with open(get_styles_directory() + self._settings.built_in_themes()[theme]) as fl: style = fl.read() app.setStyleSheet(style) except IsADirectoryError: @@ -637,7 +639,7 @@ class InterfaceSettings(CenteredWidget): restart = True self._settings['smiley_pack'] = self.smiley_pack.currentText() self._settings['close_to_tray'] = self.close_to_tray.isChecked() - smileys.SmileyLoader.get_instance().load_pack() + self._smiley_loader.load_pack() language = self.lang_choose.currentText() if self._settings['language'] != language: self._settings['language'] = language From bc9dfd1bc43aaa4887b8f563e787d2c01e30fcb6 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 4 Aug 2018 17:46:02 +0300 Subject: [PATCH 113/138] interface settings screen converted. --- toxygen/bootstrap/nodes.json | 2 +- toxygen/ui/main_screen.py | 8 +- toxygen/ui/menu.py | 232 ++++++++++++++-------------------- toxygen/user_data/settings.py | 6 +- 4 files changed, 102 insertions(+), 146 deletions(-) diff --git a/toxygen/bootstrap/nodes.json b/toxygen/bootstrap/nodes.json index c71642b..382e000 100644 --- a/toxygen/bootstrap/nodes.json +++ b/toxygen/bootstrap/nodes.json @@ -1 +1 @@ -{"nodes":[{"ipv4":"127.0.0.1","ipv6":"-","port":33445,"public_key":"AB38C55C594B9D6BF23A896345DB832722146FCAA120EFA0EE721B7BFB4DB83F","status_udp":true,"status_tcp":true}]} \ No newline at end of file +{"nodes":[{"ipv4":"80.211.19.83","ipv6":"-","port":33445,"public_key":"C78997BEA65096B09EAD41F850116D798A0D4A14D2D8652FCD2495ADDE006523","status_udp":true,"status_tcp":true}]} \ No newline at end of file diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 4cccdd5..9b80576 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -375,7 +375,8 @@ class MainWindow(QtWidgets.QMainWindow): self.retranslateUi() def closeEvent(self, event): - if not self._settings['close_to_tray'] or self._settings.closing: + close_setting = self._settings['close_app'] + if close_setting == 0 or self._settings.closing: if self._saved: return self._saved = True @@ -386,9 +387,12 @@ class MainWindow(QtWidgets.QMainWindow): self._settings.save() util_ui.close_all_windows() event.accept() - elif QtWidgets.QSystemTrayIcon.isSystemTrayAvailable(): + 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 diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index ea49e82..96067fb 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -222,16 +222,17 @@ class ProfileSettings(CenteredWidget): def set_avatar(self): choose = util_ui.tr("Choose avatar") name = util_ui.file_dialog(choose, 'Images (*.png)') - if name[0]: - bitmap = QtGui.QPixmap(name[0]) - bitmap.scaled(QtCore.QSize(128, 128), aspectRatioMode=QtCore.Qt.KeepAspectRatio, - transformMode=QtCore.Qt.SmoothTransformation) + if not name[0]: + return + bitmap = QtGui.QPixmap(name[0]) + bitmap.scaled(QtCore.QSize(128, 128), aspectRatioMode=QtCore.Qt.KeepAspectRatio, + transformMode=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())) + 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() + '/' @@ -260,8 +261,10 @@ class NetworkSettings(CenteredWidget): 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']) @@ -472,185 +475,134 @@ class NotificationsSettings(CenteredWidget): class InterfaceSettings(CenteredWidget): """Interface settings form""" + def __init__(self, settings, smiley_loader): super().__init__() self._settings = settings self._smiley_loader = smiley_loader - self.initUI() + + uic.loadUi(get_views_path('interface_settings_screen'), self) + self._update_ui() self.center() - def initUI(self): - self.setObjectName("interfaceForm") - self.setMinimumSize(QtCore.QSize(400, 650)) - self.setMaximumSize(QtCore.QSize(400, 650)) - self.label = QtWidgets.QLabel(self) - self.label.setGeometry(QtCore.QRect(30, 10, 370, 20)) - font = QtGui.QFont() - font.setPointSize(14) - font.setBold(True) - font.setFamily(self._settings['font']) - self.label.setFont(font) - self.themeSelect = QtWidgets.QComboBox(self) - self.themeSelect.setGeometry(QtCore.QRect(30, 40, 120, 30)) - self.themeSelect.addItems(list(self._settings.built_in_themes().keys())) + 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 = list(self._settings.built_in_themes().keys()).index(theme) + index = themes.index(theme) else: index = 0 - self.themeSelect.setCurrentIndex(index) - self.lang_choose = QtWidgets.QComboBox(self) - self.lang_choose.setGeometry(QtCore.QRect(30, 110, 120, 30)) - supported = sorted(Settings.supported_languages().keys(), reverse=True) - for key in supported: - self.lang_choose.insertItem(0, key) + 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.lang_choose.setCurrentIndex(0) - self.lang = QtWidgets.QLabel(self) - self.lang.setGeometry(QtCore.QRect(30, 80, 370, 20)) - self.lang.setFont(font) - self.mirror_mode = QtWidgets.QCheckBox(self) - self.mirror_mode.setGeometry(QtCore.QRect(30, 160, 370, 20)) - self.mirror_mode.setChecked(self._settings['mirror_mode']) - self.smileys = QtWidgets.QCheckBox(self) - self.smileys.setGeometry(QtCore.QRect(30, 190, 120, 20)) - self.smileys.setChecked(self._settings['smileys']) - self.smiley_pack_label = QtWidgets.QLabel(self) - self.smiley_pack_label.setGeometry(QtCore.QRect(30, 230, 370, 20)) - self.smiley_pack_label.setFont(font) - self.smiley_pack = QtWidgets.QComboBox(self) - self.smiley_pack.setGeometry(QtCore.QRect(30, 260, 160, 30)) - self.smiley_pack.addItems(self._smiley_loader.get_packs_list()) + self.languageComboBox.setCurrentIndex(0) + + smiley_packs = self._smiley_loader.get_packs_list() + self.smileysPackComboBox.addItems(smiley_packs) try: - ind = self._smiley_loader.get_packs_list().index(self._settings['smiley_pack']) + index = smiley_packs.index(self._settings['smiley_pack']) except: - ind = self._smiley_loader.get_packs_list().index('default') - self.smiley_pack.setCurrentIndex(ind) - self.messages_font_size_label = QtWidgets.QLabel(self) - self.messages_font_size_label.setGeometry(QtCore.QRect(30, 300, 370, 20)) - self.messages_font_size_label.setFont(font) - self.messages_font_size = QtWidgets.QComboBox(self) - self.messages_font_size.setGeometry(QtCore.QRect(30, 330, 160, 30)) - self.messages_font_size.addItems([str(x) for x in range(10, 25)]) - self.messages_font_size.setCurrentIndex(self._settings['message_font_size'] - 10) + index = smiley_packs.index('default') + self.smileysPackComboBox.setCurrentIndex(index) - self.unread = QtWidgets.QPushButton(self) - self.unread.setGeometry(QtCore.QRect(30, 470, 340, 30)) - self.unread.clicked.connect(self.select_color) + 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.compact_mode = QtWidgets.QCheckBox(self) - self.compact_mode.setGeometry(QtCore.QRect(30, 380, 370, 20)) - self.compact_mode.setChecked(self._settings['compact_mode']) + self.compactModeCheckBox.setChecked(self._settings['compact_mode']) + self.showAvatarsCheckBox.setChecked(self._settings['show_avatars']) + self.smileysCheckBox.setChecked(self._settings['smileys']) - self.close_to_tray = QtWidgets.QCheckBox(self) - self.close_to_tray.setGeometry(QtCore.QRect(30, 410, 370, 20)) - self.close_to_tray.setChecked(self._settings['close_to_tray']) + self.importSmileysPushButton.clicked.connect(self._import_smileys) + self.importStickersPushButton.clicked.connect(self._import_stickers) - self.show_avatars = QtWidgets.QCheckBox(self) - self.show_avatars.setGeometry(QtCore.QRect(30, 440, 370, 20)) - self.show_avatars.setChecked(self._settings['show_avatars']) + self._retranslate_ui() - self.choose_font = QtWidgets.QPushButton(self) - self.choose_font.setGeometry(QtCore.QRect(30, 510, 340, 30)) - self.choose_font.clicked.connect(self.new_font) - - self.import_smileys = QtWidgets.QPushButton(self) - self.import_smileys.setGeometry(QtCore.QRect(30, 550, 340, 30)) - self.import_smileys.clicked.connect(self.import_sm) - - self.import_stickers = QtWidgets.QPushButton(self) - self.import_stickers.setGeometry(QtCore.QRect(30, 590, 340, 30)) - self.import_stickers.clicked.connect(self.import_st) - - self.retranslateUi() - QtCore.QMetaObject.connectSlotsByName(self) - - def retranslateUi(self): - self.show_avatars.setText(util_ui.tr("Show avatars in chat")) + def _retranslate_ui(self): self.setWindowTitle(util_ui.tr("Interface settings")) - self.label.setText(util_ui.tr("Theme:")) - self.lang.setText(util_ui.tr("Language:")) - self.smileys.setText(util_ui.tr("Smileys")) - self.smiley_pack_label.setText(util_ui.tr("Smiley pack:")) - self.mirror_mode.setText(util_ui.tr("Mirror mode")) - self.messages_font_size_label.setText(util_ui.tr("Messages font size:")) - self.unread.setText(util_ui.tr("Select unread messages notification color")) - self.compact_mode.setText(util_ui.tr("Compact contact list")) - self.import_smileys.setText(util_ui.tr("Import smiley pack")) - self.import_stickers.setText(util_ui.tr("Import sticker pack")) - self.close_to_tray.setText(util_ui.tr("Close to tray")) - self.choose_font.setText(util_ui.tr("Select font")) + 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")) - def import_st(self): + @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) - def import_sm(self): + @staticmethod + def _import_smileys(): directory = util_ui.directory_dialog(util_ui.tr('Choose folder with smiley pack')) if not directory: return src = directory + '/' - dest = get_smileys_directory() + os.path.basename(directory) + '/' + dest = join_path(get_smileys_directory(), os.path.basename(directory)) copy(src, dest) - def new_font(self): - font, ok = QtWidgets.QFontDialog.getFont(QtGui.QFont(self._settings['font'], 10), self) - if not ok: - return - self._settings['font'] = font.family() - self._settings.save() - util_ui.question() - msgBox = QtWidgets.QMessageBox() - text = util_ui.tr('Restart app to apply settings') - msgBox.setWindowTitle(util_ui.tr('Restart required')) - msgBox.setText(text) - msgBox.exec_() - - def select_color(self): - col = QtWidgets.QColorDialog.getColor(QtGui.QColor(self._settings['unread_color'])) - - if col.isValid(): - name = col.name() - self._settings['unread_color'] = name - self._settings.save() - def closeEvent(self, event): - self._settings['theme'] = str(self.themeSelect.currentText()) app = QtWidgets.QApplication.instance() + + self._settings['theme'] = str(self.themeComboBox.currentText()) try: theme = self._settings['theme'] - with open(get_styles_directory() + self._settings.built_in_themes()[theme]) as fl: + 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: - app.setStyleSheet('') # for default style - self._settings['smileys'] = self.smileys.isChecked() + pass + + self._settings['smileys'] = self.smileysCheckBox.isChecked() + restart = False - if self._settings['mirror_mode'] != self.mirror_mode.isChecked(): - self._settings['mirror_mode'] = self.mirror_mode.isChecked() + if self._settings['mirror_mode'] != self.mirrorModeCheckBox.isChecked(): + self._settings['mirror_mode'] = self.mirrorModeCheckBox.isChecked() restart = True - if self._settings['compact_mode'] != self.compact_mode.isChecked(): - self._settings['compact_mode'] = self.compact_mode.isChecked() + + if self._settings['compact_mode'] != self.compactModeCheckBox.isChecked(): + self._settings['compact_mode'] = self.compactModeCheckBox.isChecked() restart = True - if self._settings['show_avatars'] != self.show_avatars.isChecked(): - self._settings['show_avatars'] = self.show_avatars.isChecked() + + if self._settings['show_avatars'] != self.showAvatarsCheckBox.isChecked(): + self._settings['show_avatars'] = self.showAvatarsCheckBox.isChecked() restart = True - self._settings['smiley_pack'] = self.smiley_pack.currentText() - self._settings['close_to_tray'] = self.close_to_tray.isChecked() + + self._settings['smiley_pack'] = self.smileysPackComboBox.currentText() self._smiley_loader.load_pack() - language = self.lang_choose.currentText() + + language = self.languageComboBox.currentText() if self._settings['language'] != language: self._settings['language'] = language - text = self.lang_choose.currentText() - path = Settings.supported_languages()[text] - app = QtWidgets.QApplication.instance() + path = Settings.supported_languages()[language] app.removeTranslator(app.translator) - app.translator.load(curr_directory() + '/translations/' + path) + app.translator.load(join_path(get_translations_directory(), path)) app.installTranslator(app.translator) - self._settings['message_font_size'] = self.messages_font_size.currentIndex() + 10 self._settings.save() + + 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 + if restart: util_ui.message_box(util_ui.tr('Restart app to apply settings'), util_ui.tr('Restart required')) diff --git a/toxygen/user_data/settings.py b/toxygen/user_data/settings.py index 4171978..d375f92 100644 --- a/toxygen/user_data/settings.py +++ b/toxygen/user_data/settings.py @@ -142,7 +142,7 @@ class Settings(dict): 'compact_mode': False, 'identicons': True, 'show_welcome_screen': True, - 'close_to_tray': False, + 'close_app': 0, 'font': 'Times New Roman', 'update': 1, 'group_notifications': True, @@ -163,8 +163,8 @@ class Settings(dict): @staticmethod def built_in_themes(): return { - 'dark': '/styles/dark_style.qss', - 'default': '/styles/style.qss' + 'dark': 'dark_style.qss', + 'default': 'style.qss' } def upgrade(self): From 9f702339dddb76d5be7ed3d9751bffe4855927ac Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 5 Aug 2018 11:19:07 +0300 Subject: [PATCH 114/138] dockerfile for building - initial version --- build/Dockerfile | 11 +++++++++++ build/build.sh | 19 +++++++++++++++++++ toxygen/ui/items_factories.py | 2 +- 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 build/Dockerfile create mode 100644 build/build.sh diff --git a/build/Dockerfile b/build/Dockerfile new file mode 100644 index 0000000..04c59e3 --- /dev/null +++ b/build/Dockerfile @@ -0,0 +1,11 @@ +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 --upgrade pip && \ +pip3 install numpy pydenticon opencv-python pyinstaller diff --git a/build/build.sh b/build/build.sh new file mode 100644 index 0000000..97f8e60 --- /dev/null +++ b/build/build.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +git clone https://github.com/toxygen-project/toxygen.git --branch=next_gen +cd toxygen/toxygen +ln -sf /usr/lib/x86_64-linux-gnu/qt5/plugins/platforms/ /usr/bin/ +pyinstaller --windowed --icon=images/icon.ico --hidden-import=PyQt5.uic.plugins main.py +cp -r styles dist/main/ +cp -r plugins dist/main/ +cp -r sounds dist/main/ +cp -r smileys dist/main/ +cp -r stickers dist/main/ +cp -r bootstrap dist/main/ +cp -r images dist/main/ +cd dist +mv main toxygen +cd toxygen +mv main toxygen +cd .. +tar -zcvf toxygen_linux_64.tar.gz toxygen +rm -rf toxygen diff --git a/toxygen/ui/items_factories.py b/toxygen/ui/items_factories.py index 2ea5660..df6c95d 100644 --- a/toxygen/ui/items_factories.py +++ b/toxygen/ui/items_factories.py @@ -11,7 +11,7 @@ class ContactItemsFactory: def create_contact_item(self): item = ContactItem(self._settings) elem = QtWidgets.QListWidgetItem(self._friends_list) - elem.setSizeHint(QtCore.QSize(250, item.height())) + elem.setSizeHint(QtCore.QSize(250, 40 if self._settings['compact_mode'] else 70)) self._friends_list.addItem(elem) self._friends_list.setItemWidget(elem, item) From 8f9b57325316b0b9d47a55dfe055cb14d850a9fd Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 5 Aug 2018 11:35:24 +0300 Subject: [PATCH 115/138] settings.py refactoring --- toxygen/user_data/profile_manager.py | 3 +- toxygen/user_data/settings.py | 124 +++++++++++++++------------ 2 files changed, 68 insertions(+), 59 deletions(-) diff --git a/toxygen/user_data/profile_manager.py b/toxygen/user_data/profile_manager.py index 3324fce..12e998f 100644 --- a/toxygen/user_data/profile_manager.py +++ b/toxygen/user_data/profile_manager.py @@ -49,7 +49,7 @@ class ProfileManager: if use_new_path: self._path = new_path + os.path.basename(self._path) self._directory = new_path - self._settings.update_path() + self._settings.update_path(new_path) @staticmethod def find_profiles(): @@ -72,4 +72,3 @@ class ProfileManager: name = fl[:-4] result.append((path + '/', name)) return result - diff --git a/toxygen/user_data/settings.py b/toxygen/user_data/settings.py index d375f92..768a07e 100644 --- a/toxygen/user_data/settings.py +++ b/toxygen/user_data/settings.py @@ -1,7 +1,6 @@ import json from utils.util import * import pyaudio -import smileys.smileys as smileys class Settings(dict): @@ -24,11 +23,10 @@ class Settings(dict): info = Settings.get_default_settings() log('Parsing settings error: ' + str(ex)) super().__init__(info) - self.upgrade() + self._upgrade() else: super().__init__(Settings.get_default_settings()) - self.save() - smileys.SmileyLoader(self) + self.save() self.locked = False self.closing = False self.unlockScreen = False @@ -45,24 +43,64 @@ class Settings(dict): 'enabled': input_devices and output_devices} self.video = {'device': -1, 'width': 640, 'height': 480, 'x': 0, 'y': 0} + # ----------------------------------------------------------------------------------------------------------------- + # Public methods + # ----------------------------------------------------------------------------------------------------------------- + + def save(self): + text = json.dumps(self) + if self._toxes.has_password(): + text = bytes(self._toxes.pass_encrypt(bytes(text, 'utf-8'))) + else: + text = bytes(text, 'utf-8') + with open(self._path, 'wb') as fl: + fl.write(text) + + def close(self): + path = self._profile_path + '.lock' + if os.path.isfile(path): + os.remove(path) + + def set_active_profile(self): + """ + Mark current profile as active + """ + path = self._profile_path + '.lock' + with open(path, 'w') as fl: + fl.write('active') + + def export(self, path): + text = json.dumps(self) + name = os.path.basename(self._path) + with open(join_path(path, str(name)), 'w') as fl: + fl.write(text) + + def update_path(self, new_path): + self._path = new_path + self.save() + + # ----------------------------------------------------------------------------------------------------------------- + # Static methods + # ----------------------------------------------------------------------------------------------------------------- + @staticmethod def get_auto_profile(): p = Settings.get_global_settings_path() - if os.path.isfile(p): - with open(p) as fl: - data = fl.read() - try: - auto = json.loads(data) - except Exception as ex: - log(str(ex)) - auto = {} - if 'profile_path' in auto: - path = str(auto['profile_path']) - if not os.path.isabs(path): - path = join_path(path, curr_directory(__file__)) - if os.path.isfile(path): - return path - return None + if not os.path.isfile(p): + return None + with open(p) as fl: + data = fl.read() + try: + auto = json.loads(data) + except Exception as ex: + log(str(ex)) + auto = {} + if 'profile_path' in auto: + path = str(auto['profile_path']) + if not os.path.isabs(path): + path = join_path(path, curr_directory(__file__)) + if os.path.isfile(path): + return path @staticmethod def set_auto_profile(path): @@ -167,44 +205,6 @@ class Settings(dict): 'default': 'style.qss' } - def upgrade(self): - default = Settings.get_default_settings() - for key in default: - if key not in self: - print(key) - self[key] = default[key] - self.save() - - def save(self): - text = json.dumps(self) - if self._toxes.has_password(): - text = bytes(self._toxes.pass_encrypt(bytes(text, 'utf-8'))) - else: - text = bytes(text, 'utf-8') - with open(self._path, 'wb') as fl: - fl.write(text) - - def close(self): - path = self._profile_path + '.lock' - if os.path.isfile(path): - os.remove(path) - - def set_active_profile(self): - """ - Mark current profile as active - """ - path = self._profile_path + '.lock' - with open(path, 'w') as fl: - fl.write('active') - - def export(self, path): - text = json.dumps(self) - with open(path + str(self.name) + '.json', 'w') as fl: - fl.write(text) - - def update_path(self): - self.path = ProfileManager.get_path() + self.name + '.json' - @staticmethod def get_global_settings_path(): return os.path.join(get_base_directory(), 'toxygen.json') @@ -219,3 +219,13 @@ class Settings(dict): else: return os.getenv('HOME') + '/.config/tox/' + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _upgrade(self): + default = Settings.get_default_settings() + for key in default: + if key not in self: + print(key) + self[key] = default[key] From 33052f8a98a4f4e80a222e58cf7e446497161104 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 5 Aug 2018 12:34:11 +0300 Subject: [PATCH 116/138] group moderation screen and all callbacks --- toxygen/contacts/contact_menu.py | 5 +- toxygen/contacts/group_chat.py | 28 +- toxygen/groups/groups_service.py | 40 ++- toxygen/middleware/callbacks.py | 31 +++ toxygen/ui/group_management_screen.py | 46 ++++ toxygen/ui/views/group_management_screen.ui | 110 ++++++++ toxygen/ui/views/interface_settings_screen.ui | 253 ++++++++++++++++++ toxygen/ui/widgets_factory.py | 4 + 8 files changed, 509 insertions(+), 8 deletions(-) create mode 100644 toxygen/ui/group_management_screen.py create mode 100644 toxygen/ui/views/group_management_screen.ui create mode 100644 toxygen/ui/views/interface_settings_screen.ui diff --git a/toxygen/contacts/contact_menu.py b/toxygen/contacts/contact_menu.py index e10f07d..63d0d36 100644 --- a/toxygen/contacts/contact_menu.py +++ b/toxygen/contacts/contact_menu.py @@ -181,9 +181,12 @@ class GroupMenuGenerator(BaseContactMenuGenerator): menu = (builder .with_action(util_ui.tr('Set alias'), lambda: main_screen.set_alias(number)) .with_submenu(copy_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('Set topic'), lambda: groups_service.set_group_topic(self._contact), - self._contact.is_moderator_or_founder()) + self._contact.is_self_moderator_or_founder()) .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'), diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index 38ab686..b38e538 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -12,6 +12,8 @@ class GroupChat(contact.Contact, ToxSave): super().__init__(profile_manager, message_getter, number, name, status_message, widget, tox_id) ToxSave.__init__(self, tox) self._is_private = is_private + self._password = None + self._peers_limit = 512 self._peers = [] self._add_self_to_gc() @@ -32,7 +34,26 @@ class GroupChat(contact.Contact, ToxSave): def get_is_private(self): return self._is_private - is_private = property(get_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 @@ -47,9 +68,12 @@ class GroupChat(contact.Contact, ToxSave): def get_self_role(self): return self._peers[0].role - def is_moderator_or_founder(self): + 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), diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index 1016bd7..4cf1368 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -15,7 +15,7 @@ class GroupsService(tox_save.ToxSave): self._peers_list_widget = main_screen.peers_list self._widgets_factory_provider = widgets_factory_provider self._group_invites = [] - self._peer_screen = None + self._peer_screen = self._management_screen = None def set_tox(self, tox): super().set_tox(tox) @@ -70,14 +70,14 @@ class GroupsService(tox_save.ToxSave): friend = self._get_friend_by_number(friend_number) invite = GroupInvite(friend.tox_id, group_name, invite_data) self._group_invites.append(invite) - self._main_screen.update_gc_invites_button_state() + 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._main_screen.update_gc_invites_button_state() + self._update_invites_button_state() def decline_group_invite(self, invite): self._delete_group_invite(invite) @@ -102,7 +102,7 @@ class GroupsService(tox_save.ToxSave): group.status_message = self._tox.group_get_topic(group.number) def set_group_topic(self, group): - if not group.is_moderator_or_founder(): + 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') @@ -112,6 +112,30 @@ class GroupsService(tox_save.ToxSave): 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._management_screen = widgets_factory.create_group_management_screen(group) + self._management_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 # ----------------------------------------------------------------------------------------------------------------- @@ -123,7 +147,7 @@ class GroupsService(tox_save.ToxSave): PeersListGenerator().generate(group.peers, self, self._peers_list_widget, group.tox_id) def peer_selected(self, chat_id, peer_id): - widgets_factory = self._widgets_factory_provider.get_item() + 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: @@ -186,3 +210,9 @@ class GroupsService(tox_save.ToxSave): 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() diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 36bb960..77a029d 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -506,6 +506,34 @@ def group_moderation(groups_service, contacts_provider, contacts_manager, messen 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 # ----------------------------------------------------------------------------------------------------------------- @@ -573,3 +601,6 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, 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) diff --git a/toxygen/ui/group_management_screen.py b/toxygen/ui/group_management_screen.py new file mode 100644 index 0000000..bbc2629 --- /dev/null +++ b/toxygen/ui/group_management_screen.py @@ -0,0 +1,46 @@ +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() diff --git a/toxygen/ui/views/group_management_screen.ui b/toxygen/ui/views/group_management_screen.ui new file mode 100644 index 0000000..859754b --- /dev/null +++ b/toxygen/ui/views/group_management_screen.ui @@ -0,0 +1,110 @@ + + + Form + + + + 0 + 0 + 658 + 238 + + + + Form + + + + + 180 + 20 + 450 + 41 + + + + + + + 20 + 30 + 145 + 20 + + + + TextLabel + + + + + + 20 + 80 + 145 + 20 + + + + TextLabel + + + + + + 180 + 70 + 450 + 40 + + + + 2 + + + 9999 + + + 512 + + + + + + 20 + 130 + 145 + 20 + + + + TextLabel + + + + + + 180 + 120 + 450 + 40 + + + + + + + 20 + 180 + 611 + 41 + + + + PushButton + + + + + + diff --git a/toxygen/ui/views/interface_settings_screen.ui b/toxygen/ui/views/interface_settings_screen.ui new file mode 100644 index 0000000..fb0bcf1 --- /dev/null +++ b/toxygen/ui/views/interface_settings_screen.ui @@ -0,0 +1,253 @@ + + + Form + + + + 0 + 0 + 552 + 847 + + + + Form + + + + + + Qt::ScrollBarAsNeeded + + + true + + + + + 0 + 0 + 532 + 827 + + + + + + 30 + 140 + 67 + 17 + + + + TextLabel + + + + + + 20 + 180 + 471 + 31 + + + + + + + 20 + 60 + 471 + 31 + + + + + + + 30 + 20 + 67 + 17 + + + + TextLabel + + + + + + 30 + 220 + 461 + 23 + + + + CheckBox + + + + + + 30 + 280 + 461 + 221 + + + + GroupBox + + + + + 30 + 40 + 92 + 23 + + + + CheckBox + + + + + + 30 + 80 + 411 + 17 + + + + TextLabel + + + + + + 30 + 120 + 411 + 31 + + + + + + + + 30 + 250 + 461 + 23 + + + + CheckBox + + + + + + 30 + 750 + 471 + 40 + + + + PushButton + + + + + + 30 + 690 + 471 + 40 + + + + PushButton + + + + + + 30 + 520 + 461 + 23 + + + + CheckBox + + + + + + 30 + 550 + 471 + 131 + + + + GroupBox + + + + + 30 + 30 + 421 + 23 + + + + RadioButton + + + + + + 30 + 60 + 431 + 23 + + + + RadioButton + + + + + + 30 + 90 + 421 + 23 + + + + RadioButton + + + + + + + + + + + diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py index 75b83b4..e394684 100644 --- a/toxygen/ui/widgets_factory.py +++ b/toxygen/ui/widgets_factory.py @@ -4,6 +4,7 @@ from ui.groups_widgets import * from ui.peer_screen import * from ui.self_peer_screen import * from ui.group_invites_widgets import * +from ui.group_management_screen import * class WidgetsFactory: @@ -82,3 +83,6 @@ class WidgetsFactory: def create_group_invites_window(self): return GroupInvitesScreen(self._groups_service, self._profile, self._contacts_provider) + + def create_group_management_screen(self, group): + return GroupManagementScreen(self._groups_service, group) From 37541db07ddf8342cd1087a1b67c8fbb3dc6e702 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 5 Aug 2018 16:33:51 +0300 Subject: [PATCH 117/138] bans - wrapper --- toxygen/contacts/group_chat.py | 7 ++----- toxygen/wrapper/tox.py | 16 ++++++++++------ toxygen/wrapper/toxcore_enums_and_consts.py | 11 +++++++++++ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index b38e538..e89b561 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -11,16 +11,13 @@ 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 = None + self._password = str() self._peers_limit = 512 self._peers = [] self._add_self_to_gc() - def set_topic(self, topic): - self._tox.group_set_topic(self._number, topic.encode('utf-8')) - super().set_status_message(topic) - def remove_invalid_unsent_files(self): pass diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index ebb6aee..0e3310d 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from ctypes import c_char_p, Structure, c_bool, byref, c_int, c_size_t, POINTER, c_uint16, c_void_p, c_uint64 -from ctypes import create_string_buffer, ArgumentError, CFUNCTYPE, c_uint32, sizeof, c_uint8 +from ctypes import * from wrapper.toxcore_enums_and_consts import * from wrapper.toxav import ToxAV from wrapper.libtox import LibToxCore @@ -109,7 +108,6 @@ class Tox: self.group_invite_cb = None self.group_custom_packet_cb = None self.group_private_message_cb = None - self.group_private_message_cb = None self.group_message_cb = None self.group_password_cb = None self.group_peer_limit_cb = None @@ -2393,7 +2391,7 @@ class Tox: result = Tox.libtoxcore.tox_group_mod_set_role(self._tox_pointer, group_number, peer_id, role, byref(error)) return result - def group_mod_remove_peer(self, group_number, peer_id, set_ban): + def group_mod_remove_peer(self, group_number, peer_id): """ Kick/ban a peer. @@ -2403,14 +2401,20 @@ class Tox: :param group_number: The group number of the group the ban is intended for. :param peer_id: The ID of the peer who will be kicked and/or added to the ban list. - :param set_ban: Set to true if a ban shall be set on the peer's IP address. :return True on success. """ error = c_int() result = Tox.libtoxcore.tox_group_mod_remove_peer(self._tox_pointer, group_number, peer_id, - set_ban, byref(error)) + byref(error)) + return result + + def group_mod_ban_peer(self, group_number, peer_id, ban_type): + + error = c_int() + result = Tox.libtoxcore.tox_group_mod_ban_peer(self._tox_pointer, group_number, peer_id, + ban_type, byref(error)) return result def group_mod_remove_ban(self, group_number, ban_id): diff --git a/toxygen/wrapper/toxcore_enums_and_consts.py b/toxygen/wrapper/toxcore_enums_and_consts.py index 4d09338..7c6478c 100644 --- a/toxygen/wrapper/toxcore_enums_and_consts.py +++ b/toxygen/wrapper/toxcore_enums_and_consts.py @@ -911,6 +911,17 @@ TOX_ERR_GROUP_BAN_QUERY = { 'TOX_ERR_GROUP_BAN_QUERY_BAD_ID': 2, } + +TOX_GROUP_BAN_TYPE = { + + 'TOX_GROUP_BAN_TYPE_IP_PORT': 0, + + 'TOX_GROUP_BAN_TYPE_PUBLIC_KEY': 1, + + 'TOX_GROUP_BAN_TYPE_NICK': 2 + +} + TOX_PUBLIC_KEY_SIZE = 32 TOX_ADDRESS_SIZE = TOX_PUBLIC_KEY_SIZE + 6 From 741adcdf182317a98a0815ad07a8c4fc82f1e808 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 5 Aug 2018 21:05:18 +0300 Subject: [PATCH 118/138] bans - untested --- toxygen/contacts/contact_menu.py | 3 + toxygen/contacts/group_chat.py | 14 +++++ toxygen/groups/group_ban.py | 23 +++++++ toxygen/groups/groups_service.py | 32 ++++++++-- toxygen/ui/group_bans_widgets.py | 62 +++++++++++++++++++ toxygen/ui/peer_screen.py | 35 +++++++++-- toxygen/ui/views/bans_list_screen.ui | 29 +++++++++ toxygen/ui/views/gc_ban_item.ui | 58 ++++++++++++++++++ toxygen/ui/views/peer_screen.ui | 68 +++++++++++++++++++++ toxygen/ui/widgets_factory.py | 4 ++ toxygen/utils/util.py | 13 +++- toxygen/wrapper/tox.py | 8 ++- toxygen/wrapper/toxcore_enums_and_consts.py | 6 +- 13 files changed, 334 insertions(+), 21 deletions(-) create mode 100644 toxygen/groups/group_ban.py create mode 100644 toxygen/ui/group_bans_widgets.py create mode 100644 toxygen/ui/views/bans_list_screen.ui create mode 100644 toxygen/ui/views/gc_ban_item.ui diff --git a/toxygen/contacts/contact_menu.py b/toxygen/contacts/contact_menu.py index 63d0d36..7330b35 100644 --- a/toxygen/contacts/contact_menu.py +++ b/toxygen/contacts/contact_menu.py @@ -187,6 +187,9 @@ class GroupMenuGenerator(BaseContactMenuGenerator): .with_optional_action(util_ui.tr('Set topic'), lambda: groups_service.set_group_topic(self._contact), self._contact.is_self_moderator_or_founder()) + .with_optional_action(util_ui.tr('Bans list'), + lambda: groups_service.show_bans_list(self._contact), + self._contact.is_self_moderator_or_founder()) .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'), diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index e89b561..f173695 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -4,6 +4,7 @@ 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): @@ -102,6 +103,19 @@ class GroupChat(contact.Contact, ToxSave): 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 # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/groups/group_ban.py b/toxygen/groups/group_ban.py new file mode 100644 index 0000000..89ecc7e --- /dev/null +++ b/toxygen/groups/group_ban.py @@ -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) diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index 4cf1368..19b53cf 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -15,7 +15,7 @@ class GroupsService(tox_save.ToxSave): self._peers_list_widget = main_screen.peers_list self._widgets_factory_provider = widgets_factory_provider self._group_invites = [] - self._peer_screen = self._management_screen = None + self._screen = None def set_tox(self, tox): super().set_tox(tox) @@ -114,8 +114,8 @@ class GroupsService(tox_save.ToxSave): def show_group_management_screen(self, group): widgets_factory = self._get_widgets_factory() - self._management_screen = widgets_factory.create_group_management_screen(group) - self._management_screen.show() + self._screen = widgets_factory.create_group_management_screen(group) + self._screen.show() def set_group_password(self, group, password): if group.password == password: @@ -151,10 +151,10 @@ class GroupsService(tox_save.ToxSave): group = self._get_group_by_public_key(chat_id) self_peer = group.get_self_peer() if self_peer.id != peer_id: - self._peer_screen = widgets_factory.create_peer_screen_window(group, peer_id) + self._screen = widgets_factory.create_peer_screen_window(group, peer_id) else: - self._peer_screen = widgets_factory.create_self_peer_screen_window(group) - self._peer_screen.show() + self._screen = widgets_factory.create_self_peer_screen_window(group) + self._screen.show() # ----------------------------------------------------------------------------------------------------------------- # Peers actions @@ -177,6 +177,26 @@ class GroupsService(tox_save.ToxSave): 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) + group.remove_peer(peer_id) + + def kick_peer(self, group, peer_id): + self._tox.group_mod_remove_peer(group.number, peer_id) + group.remove_peer(peer_id) + + def cancel_ban(self, group_number, ban_id): + self._tox.group_mod_remove_ban(group_number, ban_id) + # ----------------------------------------------------------------------------------------------------------------- # Private methods # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/ui/group_bans_widgets.py b/toxygen/ui/group_bans_widgets.py new file mode 100644 index 0000000..45a5f2b --- /dev/null +++ b/toxygen/ui/group_bans_widgets.py @@ -0,0 +1,62 @@ +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, parent=None): + super().__init__(parent) + self._ban = ban + self._cancel_ban = cancel_ban + + def _update_ui(self): + self._retranslate_ui() + + self.banTargetLabel.setText(self._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) + + 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() + for ban in self._group.bans: + self._create_ban_item(ban) + + def _create_ban_item(self, ban): + item = GroupBanItem(ban, self._on_ban_cancelled, 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() diff --git a/toxygen/ui/peer_screen.py b/toxygen/ui/peer_screen.py index 651d998..687c818 100644 --- a/toxygen/ui/peer_screen.py +++ b/toxygen/ui/peer_screen.py @@ -1,8 +1,9 @@ from ui.widgets import CenteredWidget -from PyQt5 import QtCore, QtWidgets, uic +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): @@ -35,9 +36,12 @@ class PeerScreen(CenteredWidget): 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 = self._can_change_role() - self.rolesComboBox.setVisible(can_change_role) - self.roleNameLabel.setVisible(not can_change_role) + 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() @@ -49,7 +53,12 @@ class PeerScreen(CenteredWidget): 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.banGroupBox.setTitle(util_ui.tr('Moderation')) + 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 @@ -58,7 +67,7 @@ class PeerScreen(CenteredWidget): self.rolesComboBox.addItem(role) self.rolesComboBox.setCurrentIndex(self._peer.role - index - 1) - def _can_change_role(self): + def _can_change_role_or_ban(self): self_peer = self._group.get_self_peer() if self_peer.role > TOX_GROUP_ROLE['MODERATOR']: return False @@ -85,3 +94,17 @@ class PeerScreen(CenteredWidget): def _copy_public_key(self): clipboard = QtWidgets.QApplication.clipboard() clipboard.setText(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) + + def _kick_peer(self): + self._groups_service.kick_peer(self._group, self._peer.id) + + def _get_ban_type(self): + if self.ipBanRadioButton.isChecked(): + return consts.TOX_GROUP_BAN_TYPE['IP_PORT'] + elif self.nickRadioButton.isCHecked(): + return consts.TOX_GROUP_BAN_TYPE['NICK'] + return consts.TOX_GROUP_BAN_TYPE['PUBLIC_KEY'] diff --git a/toxygen/ui/views/bans_list_screen.ui b/toxygen/ui/views/bans_list_screen.ui new file mode 100644 index 0000000..16339d8 --- /dev/null +++ b/toxygen/ui/views/bans_list_screen.ui @@ -0,0 +1,29 @@ + + + Form + + + + 0 + 0 + 500 + 375 + + + + Form + + + + + 0 + 0 + 500 + 375 + + + + + + + diff --git a/toxygen/ui/views/gc_ban_item.ui b/toxygen/ui/views/gc_ban_item.ui new file mode 100644 index 0000000..20e1e16 --- /dev/null +++ b/toxygen/ui/views/gc_ban_item.ui @@ -0,0 +1,58 @@ + + + Form + + + + 0 + 0 + 500 + 100 + + + + Form + + + + + 320 + 30 + 161 + 41 + + + + PushButton + + + + + + 20 + 20 + 200 + 20 + + + + TextLabel + + + + + + 20 + 50 + 200 + 20 + + + + TextLabel + + + + + + diff --git a/toxygen/ui/views/peer_screen.ui b/toxygen/ui/views/peer_screen.ui index f6b13b3..a66ec11 100644 --- a/toxygen/ui/views/peer_screen.ui +++ b/toxygen/ui/views/peer_screen.ui @@ -76,6 +76,74 @@ GroupBox + + + + 380 + 50 + 101 + 41 + + + + PushButton + + + + + + 40 + 40 + 251 + 23 + + + + RadioButton + + + true + + + + + + 40 + 80 + 251 + 23 + + + + RadioButton + + + + + + 40 + 120 + 251 + 23 + + + + RadioButton + + + + + + 380 + 100 + 101 + 41 + + + + PushButton + + diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py index e394684..b146088 100644 --- a/toxygen/ui/widgets_factory.py +++ b/toxygen/ui/widgets_factory.py @@ -5,6 +5,7 @@ from ui.peer_screen import * from ui.self_peer_screen import * from ui.group_invites_widgets import * from ui.group_management_screen import * +from ui.group_bans_widgets import * class WidgetsFactory: @@ -86,3 +87,6 @@ class WidgetsFactory: def create_group_management_screen(self, group): return GroupManagementScreen(self._groups_service, group) + + def create_groups_bans_screen(self, group): + return GroupBansScreen(self._groups_service, group) diff --git a/toxygen/utils/util.py b/toxygen/utils/util.py index 0f718ef..3c6910e 100644 --- a/toxygen/utils/util.py +++ b/toxygen/utils/util.py @@ -4,6 +4,7 @@ import shutil import sys import re import platform +import datetime def cached(func): @@ -21,10 +22,10 @@ def cached(func): def log(data): try: - with open(curr_directory() + '/logs.log', 'a') as fl: + with open(join_path(curr_directory(), 'logs.log'), 'a') as fl: fl.write(str(data) + '\n') - except: - pass + except Exception as ex: + print(ex) def curr_directory(current_file=None): @@ -144,6 +145,12 @@ def time_offset(): return result +def unix_time_to_long_str(unix_time): + date_time = datetime.datetime.utcfromtimestamp(unix_time) + + return date_time.strftime('%Y-%m-%d %H:%M:%S') + + @cached def is_64_bit(): return sys.maxsize > 2 ** 32 diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index 0e3310d..a2abf6a 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -2468,9 +2468,11 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_ban_get_list(self._tox_pointer, group_number, POINTER(c_uint32)( - create_string_buffer(sizeof(c_uint32) * self.group_ban_get_list_size(group_number)), byref(error))) - return result + bans_list_size = self.group_ban_get_list_size(group_number) + bans_list = create_string_buffer(sizeof(c_uint32) * bans_list_size) + bans_list = POINTER(c_uint32)(bans_list) + result = Tox.libtoxcore.tox_group_ban_get_list(self._tox_pointer, group_number, bans_list, byref(error)) + return bans_list[:bans_list_size] def group_ban_get_target_size(self, group_number, ban_id): """ diff --git a/toxygen/wrapper/toxcore_enums_and_consts.py b/toxygen/wrapper/toxcore_enums_and_consts.py index 7c6478c..1e134f9 100644 --- a/toxygen/wrapper/toxcore_enums_and_consts.py +++ b/toxygen/wrapper/toxcore_enums_and_consts.py @@ -914,11 +914,11 @@ TOX_ERR_GROUP_BAN_QUERY = { TOX_GROUP_BAN_TYPE = { - 'TOX_GROUP_BAN_TYPE_IP_PORT': 0, + 'IP_PORT': 0, - 'TOX_GROUP_BAN_TYPE_PUBLIC_KEY': 1, + 'PUBLIC_KEY': 1, - 'TOX_GROUP_BAN_TYPE_NICK': 2 + 'NICK': 2 } From 1a0bd9deee3f37c9228035c81e1e5232636f4ad4 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 6 Aug 2018 00:52:01 +0300 Subject: [PATCH 119/138] dockerfile for linux builds --- build/Dockerfile | 4 +++- build/build.sh | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index 04c59e3..0b45358 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -7,5 +7,7 @@ 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 --upgrade pip && \ pip3 install numpy pydenticon opencv-python pyinstaller + +RUN useradd -ms /bin/bash toxygen +USER toxygen diff --git a/build/build.sh b/build/build.sh index 97f8e60..b415054 100644 --- a/build/build.sh +++ b/build/build.sh @@ -1,19 +1,29 @@ #!/usr/bin/env bash + git clone https://github.com/toxygen-project/toxygen.git --branch=next_gen cd toxygen/toxygen -ln -sf /usr/lib/x86_64-linux-gnu/qt5/plugins/platforms/ /usr/bin/ -pyinstaller --windowed --icon=images/icon.ico --hidden-import=PyQt5.uic.plugins main.py + +pyinstaller --windowed --icon=images/icon.ico main.py + cp -r styles dist/main/ 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/ cp -r images dist/main/ +cp -r translations dist/main/ + 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 + +tar -zcvf toxygen_linux_64.tar.gz toxygen > /dev/null rm -rf toxygen From 318c9c942df5ced0a351a612a020b70af74a5b93 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 7 Aug 2018 00:41:27 +0300 Subject: [PATCH 120/138] ngc bug fixes --- build/build.sh | 1 + toxygen/app.py | 12 ++++++++---- toxygen/history/history.py | 2 +- toxygen/ui/peer_screen.py | 2 +- toxygen/ui/views/peer_screen.ui | 2 +- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/build/build.sh b/build/build.sh index b415054..522d6b2 100644 --- a/build/build.sh +++ b/build/build.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash +cd ~ git clone https://github.com/toxygen-project/toxygen.git --branch=next_gen cd toxygen/toxygen diff --git a/toxygen/app.py b/toxygen/app.py index 80c26ea..cfdac4f 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -123,10 +123,14 @@ class App: if self._settings['theme'] == 'dark': return for theme in self._settings.built_in_themes().keys(): - if self._settings['theme'] == theme: - with open(util.curr_directory(__file__) + self._settings.built_in_themes()[theme]) as fl: - style = fl.read() - self._app.setStyleSheet(style) + 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() diff --git a/toxygen/history/history.py b/toxygen/history/history.py index e6f5e44..c430117 100644 --- a/toxygen/history/history.py +++ b/toxygen/history/history.py @@ -68,7 +68,7 @@ class History: messages.reverse() messages = messages[self._messages.count():self._messages.count() + PAGE_SIZE] for message in messages: - if message.get_type() in (MESSAGE_TYPE['NORMAL'], MESSAGE_TYPE['ACTION']): # text message + if message.get_type() in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']): # text message self._create_message_item(message) elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: # file transfer if message.get_status() is None: diff --git a/toxygen/ui/peer_screen.py b/toxygen/ui/peer_screen.py index 687c818..acc93ee 100644 --- a/toxygen/ui/peer_screen.py +++ b/toxygen/ui/peer_screen.py @@ -105,6 +105,6 @@ class PeerScreen(CenteredWidget): def _get_ban_type(self): if self.ipBanRadioButton.isChecked(): return consts.TOX_GROUP_BAN_TYPE['IP_PORT'] - elif self.nickRadioButton.isCHecked(): + elif self.nickBanRadioButton.isChecked(): return consts.TOX_GROUP_BAN_TYPE['NICK'] return consts.TOX_GROUP_BAN_TYPE['PUBLIC_KEY'] diff --git a/toxygen/ui/views/peer_screen.ui b/toxygen/ui/views/peer_screen.ui index a66ec11..e8e9e31 100644 --- a/toxygen/ui/views/peer_screen.ui +++ b/toxygen/ui/views/peer_screen.ui @@ -105,7 +105,7 @@ true - + 40 From 4ecf666b2f5f66bf7563d6234b9e1f03308b9eed Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 9 Aug 2018 23:30:05 +0300 Subject: [PATCH 121/138] various fixes --- toxygen/contacts/contact.py | 2 +- toxygen/history/history.py | 2 +- toxygen/messenger/messenger.py | 3 ++- toxygen/ui/main_screen.py | 8 +------- toxygen/utils/util.py | 1 + toxygen/wrapper/libtox.py | 28 +++++++++++++++------------- 6 files changed, 21 insertions(+), 23 deletions(-) diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index 418ac15..ecf944d 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -135,7 +135,7 @@ class Contact(basecontact.BaseContact): def mark_as_sent(self, tox_message_id): try: - message = list(filter(lambda m: m.author.type == MESSAGE_AUTHOR['NOT_SENT'] + 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() except Exception as ex: diff --git a/toxygen/history/history.py b/toxygen/history/history.py index c430117..4850f7d 100644 --- a/toxygen/history/history.py +++ b/toxygen/history/history.py @@ -48,7 +48,7 @@ class History: def delete_message(self, message): contact = self._contacts_manager.get_curr_contact() - if message.type in (MESSAGE_TYPE['NORMAL'], MESSAGE_TYPE['ACTION']): + 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) diff --git a/toxygen/messenger/messenger.py b/toxygen/messenger/messenger.py index cce619d..9ee86dd 100644 --- a/toxygen/messenger/messenger.py +++ b/toxygen/messenger/messenger.py @@ -224,7 +224,8 @@ class Messenger(tox_save.ToxSave): return message = util_ui.tr('User {} is now known as {}') message = message.format(old_name, new_name) - friend.actions = True + if self._contacts_manager.is_friend_active(friend.number): + friend.actions = True self._add_info_message(friend.number, message) # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 9b80576..7b2b17d 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -645,7 +645,7 @@ class MainWindow(QtWidgets.QMainWindow): self._contacts_manager.delete_friend(num) def block_friend(self, num): - friend = self._contacts_managere.get_contact(num) + friend = self._contacts_manager.get_contact(num) self._contacts_manager.block_user(friend.tox_id) @staticmethod @@ -656,12 +656,6 @@ class MainWindow(QtWidgets.QMainWindow): def clear_history(self, num): self._contacts_manager.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): tox_id = self._contacts_manager.friend_public_key(num) if value: diff --git a/toxygen/utils/util.py b/toxygen/utils/util.py index 3c6910e..5bd5c3a 100644 --- a/toxygen/utils/util.py +++ b/toxygen/utils/util.py @@ -165,5 +165,6 @@ def is_re_valid(regex): return True +@cached def get_platform(): return platform.system() diff --git a/toxygen/wrapper/libtox.py b/toxygen/wrapper/libtox.py index d5f599a..01d41f1 100644 --- a/toxygen/wrapper/libtox.py +++ b/toxygen/wrapper/libtox.py @@ -1,4 +1,3 @@ -from platform import system from ctypes import CDLL import utils.util as util @@ -6,16 +5,17 @@ import utils.util as util class LibToxCore: def __init__(self): - if system() == 'Windows': - self._libtoxcore = CDLL(util.curr_directory() + '/libs/libtox.dll') - elif system() == 'Darwin': + platform = util.get_platform() + if platform == 'Windows': + self._libtoxcore = CDLL(util.join_path(util.get_libs_directory(), 'libtox.dll')) + elif platform == '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') + self._libtoxcore = CDLL(util.join_path(util.get_libs_directory(), 'libtoxcore.so')) def __getattr__(self, item): return self._libtoxcore.__getattr__(item) @@ -24,17 +24,18 @@ class LibToxCore: class LibToxAV: def __init__(self): - if system() == 'Windows': + platform = util.get_platform() + if platform == 'Windows': # on Windows av api is in libtox.dll - self._libtoxav = CDLL(util.curr_directory() + '/libs/libtox.dll') - elif system() == 'Darwin': + self._libtoxav = CDLL(util.join_path(util.get_libs_directory(), 'libtox.dll')) + elif platform == 'Darwin': self._libtoxav = CDLL('libtoxcore.dylib') else: # /usr/lib/libtoxcore.so must exists try: self._libtoxav = CDLL('libtoxcore.so') except: - self._libtoxav = CDLL(util.curr_directory() + '/libs/libtoxcore.so') + self._libtoxav = CDLL(util.join_path(util.get_libs_directory(), 'libtoxcore.so')) def __getattr__(self, item): return self._libtoxav.__getattr__(item) @@ -43,17 +44,18 @@ class LibToxAV: class LibToxEncryptSave: def __init__(self): - if system() == 'Windows': + platform = util.get_platform() + if platform == '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(util.join_path(util.get_libs_directory(), 'libtox.dll')) + elif platform == 'Darwin': self._lib_tox_encrypt_save = CDLL('libtoxcore.dylib') else: # /usr/lib/libtoxcore.so must exists try: self._lib_tox_encrypt_save = CDLL('libtoxcore.so') except: - self._lib_tox_encrypt_save = CDLL(util.curr_directory() + '/libs/libtoxcore.so') + self._lib_tox_encrypt_save = CDLL(util.join_path(util.get_libs_directory(), 'libtoxcore.so')) def __getattr__(self, item): return self._lib_tox_encrypt_save.__getattr__(item) From 85ea9ab6e815a44017fc577a8537b74cc4e800e0 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 11 Aug 2018 00:30:33 +0300 Subject: [PATCH 122/138] fixes for messaging, contacts filtering etc --- toxygen/app.py | 19 ++++++++++--------- toxygen/contacts/contacts_manager.py | 10 ++++++---- toxygen/groups/groups_service.py | 1 + toxygen/messenger/messages.py | 7 +++++-- toxygen/ui/main_screen.py | 2 +- toxygen/ui/menu.py | 2 +- toxygen/user_data/profile_manager.py | 7 +++---- 7 files changed, 27 insertions(+), 21 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index cfdac4f..a370a91 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -134,12 +134,13 @@ class App: def _load_login_screen_translations(self): current_language, supported_languages = self._get_languages() - if current_language in supported_languages: - 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 + 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') @@ -237,11 +238,11 @@ class App: return ls.result def _load_existing_profile(self, profile_path): - self._settings = Settings(self._toxes, profile_path.replace('.tox', '.json')) - self._profile_manager = ProfileManager(self._settings, self._toxes, 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): @@ -264,7 +265,7 @@ class App: if result.password: self._toxes.set_password(result.password) self._settings = Settings(self._toxes, self._path.replace('.tox', '.json')) - self._profile_manager = ProfileManager(self._settings, self._toxes, profile_path) + self._profile_manager = ProfileManager(self._toxes, profile_path) try: self._save_profile() except Exception as ex: diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 757bfb0..48038ca 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -163,7 +163,7 @@ class ContactsManager(ToxSave): :param filter_str: show contacts which name contains this substring """ filter_str = filter_str.lower() - contact = self.get_curr_contact() + current_contact = self.get_curr_contact() if sorting > 5 or sorting < 0: sorting = 0 @@ -189,7 +189,7 @@ class ContactsManager(ToxSave): else: self._contacts = sorted(self._contacts, key=lambda x: x.name.lower()) - # change item widgets + # 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) @@ -203,13 +203,15 @@ class ContactsManager(ToxSave): 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 contact is not None: - index = self._contacts.index(contact) + if current_contact is not None: + index = self._contacts.index(current_contact) self.set_active(index) def update_filtration(self): diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index 19b53cf..941ee1b 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -34,6 +34,7 @@ class GroupsService(tox_save.ToxSave): 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) diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index 57ed9bb..d5882b8 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -28,7 +28,10 @@ class MessageAuthor: def get_type(self): return self._type - type = property(get_type) + def set_type(self, value): + self._type = value + + type = property(get_type, set_type) class Message: @@ -74,7 +77,7 @@ class Message: self._widget = None def mark_as_sent(self): - self._author.author_type = MESSAGE_AUTHOR['ME'] + self._author.type = MESSAGE_AUTHOR['ME'] if self._widget is not None: self._widget.mark_as_sent() diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 7b2b17d..3df3162 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -654,7 +654,7 @@ class MainWindow(QtWidgets.QMainWindow): clipboard.setText(text) def clear_history(self, num): - self._contacts_manager.clear_history(num) + self._history_loader.clear_history(num) def auto_accept(self, num, value): tox_id = self._contacts_manager.friend_public_key(num) diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index 96067fb..75db280 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -241,7 +241,7 @@ class ProfileSettings(CenteredWidget): util_ui.tr('Use new path')) self._settings.export(directory) self._profile.export_db(directory) - self._profile_manager.export_profile(directory, reply) + self._profile_manager.export_profile(self._settings, directory, reply) def closeEvent(self, event): self._profile.set_name(self.nick.text()) diff --git a/toxygen/user_data/profile_manager.py b/toxygen/user_data/profile_manager.py index 12e998f..e49f5f4 100644 --- a/toxygen/user_data/profile_manager.py +++ b/toxygen/user_data/profile_manager.py @@ -7,8 +7,7 @@ class ProfileManager: """ Class with methods for search, load and save profiles """ - def __init__(self, settings, toxes, path): - self._settings = settings + def __init__(self, toxes, path): self._toxes = toxes self._path = path self._directory = os.path.dirname(path) @@ -38,7 +37,7 @@ class ProfileManager: fl.write(data) print('Profile saved successfully') - def export_profile(self, new_path, use_new_path): + def export_profile(self, settings, new_path, use_new_path): path = new_path + os.path.basename(self._path) with open(self._path, 'rb') as fin: data = fin.read() @@ -49,7 +48,7 @@ class ProfileManager: if use_new_path: self._path = new_path + os.path.basename(self._path) self._directory = new_path - self._settings.update_path(new_path) + settings.update_path(new_path) @staticmethod def find_profiles(): From 0ee8a0ec21ae09a5cd3e6c25dcdc009449a26fa9 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 22 Aug 2018 12:08:00 +0300 Subject: [PATCH 123/138] gc settings screen added --- toxygen/bootstrap/nodes.json | 2 +- toxygen/contacts/contact_menu.py | 3 + toxygen/groups/groups_service.py | 5 ++ ...nt_screen.py => group_settings_widgets.py} | 35 +++++++- toxygen/ui/main_screen.py | 3 +- toxygen/ui/peer_screen.py | 3 +- toxygen/ui/self_peer_screen.py | 5 +- toxygen/ui/views/gc_settings_screen.ui | 83 +++++++++++++++++++ toxygen/ui/widgets_factory.py | 6 +- toxygen/utils/ui.py | 5 ++ 10 files changed, 139 insertions(+), 11 deletions(-) rename toxygen/ui/{group_management_screen.py => group_settings_widgets.py} (55%) create mode 100644 toxygen/ui/views/gc_settings_screen.ui diff --git a/toxygen/bootstrap/nodes.json b/toxygen/bootstrap/nodes.json index 382e000..4f4f011 100644 --- a/toxygen/bootstrap/nodes.json +++ b/toxygen/bootstrap/nodes.json @@ -1 +1 @@ -{"nodes":[{"ipv4":"80.211.19.83","ipv6":"-","port":33445,"public_key":"C78997BEA65096B09EAD41F850116D798A0D4A14D2D8652FCD2495ADDE006523","status_udp":true,"status_tcp":true}]} \ No newline at end of file +{"nodes":[{"ipv4":"80.211.19.83","ipv6":"-","port":33445,"public_key":"A05AFD9C7B785ADBB93DCD55FC8992177A2BA50413AAD7AD8D3442EE5E7ADA21","status_udp":true,"status_tcp":true}]} \ No newline at end of file diff --git a/toxygen/contacts/contact_menu.py b/toxygen/contacts/contact_menu.py index 7330b35..da1cddb 100644 --- a/toxygen/contacts/contact_menu.py +++ b/toxygen/contacts/contact_menu.py @@ -184,6 +184,9 @@ class GroupMenuGenerator(BaseContactMenuGenerator): .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()) diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index 941ee1b..058e7ea 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -118,6 +118,11 @@ class GroupsService(tox_save.ToxSave): 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 diff --git a/toxygen/ui/group_management_screen.py b/toxygen/ui/group_settings_widgets.py similarity index 55% rename from toxygen/ui/group_management_screen.py rename to toxygen/ui/group_settings_widgets.py index bbc2629..c32168b 100644 --- a/toxygen/ui/group_management_screen.py +++ b/toxygen/ui/group_settings_widgets.py @@ -25,9 +25,9 @@ class GroupManagementScreen(CenteredWidget): def _retranslate_ui(self): self.setWindowTitle(util_ui.tr('Group "{}"').format(self._group.name)) - self.passwordLabel.setText(util_ui.tr('Password')) + self.passwordLabel.setText(util_ui.tr('Password:')) self.peerLimitLabel.setText(util_ui.tr('Peer limit:')) - self.privacyStateLabel.setText(util_ui.tr('Privacy state')) + self.privacyStateLabel.setText(util_ui.tr('Privacy state:')) self.savePushButton.setText(util_ui.tr('Save')) self.privacyStateComboBox.clear() @@ -44,3 +44,34 @@ class GroupManagementScreen(CenteredWidget): 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) diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 3df3162..afb3ef9 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -650,8 +650,7 @@ class MainWindow(QtWidgets.QMainWindow): @staticmethod def copy_text(text): - clipboard = QtWidgets.QApplication.clipboard() - clipboard.setText(text) + util_ui.copy_to_clipboard(text) def clear_history(self, num): self._history_loader.clear_history(num) diff --git a/toxygen/ui/peer_screen.py b/toxygen/ui/peer_screen.py index acc93ee..f366aad 100644 --- a/toxygen/ui/peer_screen.py +++ b/toxygen/ui/peer_screen.py @@ -92,8 +92,7 @@ class PeerScreen(CenteredWidget): self.close() def _copy_public_key(self): - clipboard = QtWidgets.QApplication.clipboard() - clipboard.setText(self._peer.public_key) + util_ui.copy_to_clipboard(self._peer.public_key) def _ban_peer(self): ban_type = self._get_ban_type() diff --git a/toxygen/ui/self_peer_screen.py b/toxygen/ui/self_peer_screen.py index ebc1426..cf252d3 100644 --- a/toxygen/ui/self_peer_screen.py +++ b/toxygen/ui/self_peer_screen.py @@ -1,5 +1,5 @@ from ui.widgets import CenteredWidget, LineEdit -from PyQt5 import QtCore, QtWidgets, uic +from PyQt5 import uic import utils.util as util import utils.ui as util_ui from ui.contact_items import * @@ -63,5 +63,4 @@ class SelfPeerScreen(CenteredWidget): self.close() def _copy_public_key(self): - clipboard = QtWidgets.QApplication.clipboard() - clipboard.setText(self._peer.public_key) + util_ui.copy_to_clipboard(self._peer.public_key) diff --git a/toxygen/ui/views/gc_settings_screen.ui b/toxygen/ui/views/gc_settings_screen.ui new file mode 100644 index 0000000..526c156 --- /dev/null +++ b/toxygen/ui/views/gc_settings_screen.ui @@ -0,0 +1,83 @@ + + + Form + + + + 0 + 0 + 400 + 220 + + + + + 400 + 220 + + + + + 400 + 220 + + + + Form + + + + + 10 + 20 + 380 + 20 + + + + TextLabel + + + + + + 10 + 60 + 380 + 40 + + + + PushButton + + + + + + 10 + 120 + 380 + 20 + + + + TextLabel + + + + + + 10 + 160 + 380 + 20 + + + + TextLabel + + + + + + diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py index b146088..aa70861 100644 --- a/toxygen/ui/widgets_factory.py +++ b/toxygen/ui/widgets_factory.py @@ -4,7 +4,7 @@ from ui.groups_widgets import * from ui.peer_screen import * from ui.self_peer_screen import * from ui.group_invites_widgets import * -from ui.group_management_screen import * +from ui.group_settings_widgets import * from ui.group_bans_widgets import * @@ -88,5 +88,9 @@ class WidgetsFactory: def create_group_management_screen(self, group): return GroupManagementScreen(self._groups_service, group) + @staticmethod + def create_group_settings_screen(group): + return GroupSettingsScreen(group) + def create_groups_bans_screen(self, group): return GroupBansScreen(self._groups_service, group) diff --git a/toxygen/utils/ui.py b/toxygen/utils/ui.py index cdb5f9a..d2d7122 100644 --- a/toxygen/utils/ui.py +++ b/toxygen/utils/ui.py @@ -46,4 +46,9 @@ def close_all_windows(): QtWidgets.QApplication.closeAllWindows() +def copy_to_clipboard(text): + clipboard = QtWidgets.QApplication.clipboard() + clipboard.setText(text) + + # TODO: all dialogs From c0a34d3e140994bd78591f8f044c25a4b81d0a21 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 22 Aug 2018 13:36:22 +0300 Subject: [PATCH 124/138] groups - nicks auto complete --- toxygen/contacts/contacts_manager.py | 16 ++++++++++++++++ toxygen/contacts/group_chat.py | 5 +++++ toxygen/ui/main_screen.py | 4 ++-- toxygen/ui/main_screen_widgets.py | 19 ++++++++++++++----- 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 48038ca..23dc61e 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -386,6 +386,22 @@ class ContactsManager(ToxSave): 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 # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index f173695..1ef6742 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -98,6 +98,11 @@ class GroupChat(contact.Contact, ToxSave): 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[:] diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index afb3ef9..6217b47 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -1,5 +1,5 @@ from ui.contact_items import * -from ui.widgets import MultilineEdit, ComboBox +from ui.widgets import MultilineEdit from ui.main_screen_widgets import * import utils.util as util import utils.ui as util_ui @@ -37,7 +37,7 @@ class MainWindow(QtWidgets.QMainWindow): self._toxes = toxes self._messenger = messenger self._contacts_manager.active_contact_changed.add_callback(self._new_contact_selected) - self.messageEdit.set_messenger(messenger) + self.messageEdit.set_dependencies(messenger, contacts_manager) self.update_gc_invites_button_state() diff --git a/toxygen/ui/main_screen_widgets.py b/toxygen/ui/main_screen_widgets.py index 0945b0c..8b7bf52 100644 --- a/toxygen/ui/main_screen_widgets.py +++ b/toxygen/ui/main_screen_widgets.py @@ -1,6 +1,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets from ui.widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit import urllib +import re import utils.util as util import utils.ui as util_ui from stickers.stickers import load_stickers @@ -11,14 +12,15 @@ class MessageArea(QtWidgets.QPlainTextEdit): def __init__(self, parent, form): super().__init__(parent) - self._messenger = None + self._messenger = self._contacts_manager = None self.parent = form self.setAcceptDrops(True) self._timer = QtCore.QTimer(self) self._timer.timeout.connect(lambda: self._messenger.send_typing(False)) - def set_messenger(self, messenger): + def set_dependencies(self, messenger, contacts_manager): self._messenger = messenger + self._contacts_manager = contacts_manager def keyPressEvent(self, event): if event.matches(QtGui.QKeySequence.Paste): @@ -39,10 +41,17 @@ class MessageArea(QtWidgets.QPlainTextEdit): self._messenger.send_message() elif event.key() == QtCore.Qt.Key_Up and not self.toPlainText(): self.appendPlainText(self._messenger.get_last_message()) - elif event.key() == QtCore.Qt.Key_Tab and not self._messenger.is_active_a_friend(): + elif event.key() == QtCore.Qt.Key_Tab and self._contacts_manager.is_active_a_group(): text = self.toPlainText() - pos = self.textCursor().position() - self.insertPlainText(self._messenger.get_gc_peer_name(text[:pos])) + text_cursor = self.textCursor() + 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: self._messenger.send_typing(True) if self._timer.isActive(): From ce19efe34065710b045b4d64b5fed1d15b05480e Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 23 Aug 2018 16:02:29 +0300 Subject: [PATCH 125/138] ban fixes --- toxygen/bootstrap/nodes.json | 2 +- toxygen/contacts/contact_menu.py | 5 ++--- toxygen/contacts/group_chat.py | 7 +++++-- toxygen/groups/groups_service.py | 2 -- toxygen/ui/group_bans_widgets.py | 16 +++++++++++----- toxygen/ui/peer_screen.py | 2 ++ toxygen/ui/views/gc_ban_item.ui | 10 +++++----- toxygen/wrapper/tox.py | 14 ++++++++++++++ toxygen/wrapper/toxcore_enums_and_consts.py | 1 - 9 files changed, 40 insertions(+), 19 deletions(-) diff --git a/toxygen/bootstrap/nodes.json b/toxygen/bootstrap/nodes.json index 4f4f011..5314998 100644 --- a/toxygen/bootstrap/nodes.json +++ b/toxygen/bootstrap/nodes.json @@ -1 +1 @@ -{"nodes":[{"ipv4":"80.211.19.83","ipv6":"-","port":33445,"public_key":"A05AFD9C7B785ADBB93DCD55FC8992177A2BA50413AAD7AD8D3442EE5E7ADA21","status_udp":true,"status_tcp":true}]} \ No newline at end of file +{"nodes":[{"ipv4":"80.211.19.83","ipv6":"-","port":33445,"public_key":"A2D7BF17C10A12C339B9F4E8DD77DEEE8457D580535A6F0D0F9AF04B8B4C4420","status_udp":true,"status_tcp":true}]} \ No newline at end of file diff --git a/toxygen/contacts/contact_menu.py b/toxygen/contacts/contact_menu.py index da1cddb..b1c23f5 100644 --- a/toxygen/contacts/contact_menu.py +++ b/toxygen/contacts/contact_menu.py @@ -190,9 +190,8 @@ class GroupMenuGenerator(BaseContactMenuGenerator): .with_optional_action(util_ui.tr('Set topic'), lambda: groups_service.set_group_topic(self._contact), self._contact.is_self_moderator_or_founder()) - .with_optional_action(util_ui.tr('Bans list'), - lambda: groups_service.show_bans_list(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'), diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py index 1ef6742..19ebc8e 100644 --- a/toxygen/contacts/group_chat.py +++ b/toxygen/contacts/group_chat.py @@ -82,8 +82,11 @@ class GroupChat(contact.Contact, ToxSave): self._peers.append(peer) def remove_peer(self, peer_id): - peer = self.get_peer_by_id(peer_id) - self._peers.remove(peer) + 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)) diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index 058e7ea..b8fc7cc 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -194,11 +194,9 @@ class GroupsService(tox_save.ToxSave): def ban_peer(self, group, peer_id, ban_type): self._tox.group_mod_ban_peer(group.number, peer_id, ban_type) - group.remove_peer(peer_id) def kick_peer(self, group, peer_id): self._tox.group_mod_remove_peer(group.number, peer_id) - group.remove_peer(peer_id) def cancel_ban(self, group_number, ban_id): self._tox.group_mod_remove_ban(group_number, ban_id) diff --git a/toxygen/ui/group_bans_widgets.py b/toxygen/ui/group_bans_widgets.py index 45a5f2b..b2758c7 100644 --- a/toxygen/ui/group_bans_widgets.py +++ b/toxygen/ui/group_bans_widgets.py @@ -6,19 +6,24 @@ import utils.ui as util_ui class GroupBanItem(QtWidgets.QWidget): - def __init__(self, ban, cancel_ban, parent=None): + 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.target) + 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')) @@ -47,11 +52,12 @@ class GroupBansScreen(CenteredWidget): 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) + self._create_ban_item(ban, can_cancel_ban) - def _create_ban_item(self, ban): - item = GroupBanItem(ban, self._on_ban_cancelled, self.bansListWidget) + 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) diff --git a/toxygen/ui/peer_screen.py b/toxygen/ui/peer_screen.py index f366aad..8f2d5ba 100644 --- a/toxygen/ui/peer_screen.py +++ b/toxygen/ui/peer_screen.py @@ -97,9 +97,11 @@ class PeerScreen(CenteredWidget): 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(): diff --git a/toxygen/ui/views/gc_ban_item.ui b/toxygen/ui/views/gc_ban_item.ui index 20e1e16..a57d0e1 100644 --- a/toxygen/ui/views/gc_ban_item.ui +++ b/toxygen/ui/views/gc_ban_item.ui @@ -16,7 +16,7 @@ - 320 + 330 30 161 41 @@ -29,9 +29,9 @@ - 20 + 15 20 - 200 + 305 20 @@ -42,9 +42,9 @@ - 20 + 15 50 - 200 + 305 20 diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index a2abf6a..fcdf266 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -2474,6 +2474,17 @@ class Tox: result = Tox.libtoxcore.tox_group_ban_get_list(self._tox_pointer, group_number, bans_list, byref(error)) return bans_list[:bans_list_size] + def group_ban_get_type(self, group_number, ban_id): + """ + Return the type for the ban list entry designated by ban_id, in the + group designated by the given group number. If either group_number or ban_id is invalid, + the return value is unspecified. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_ban_get_type(self._tox_pointer, group_number, ban_id, byref(error)) + return result + def group_ban_get_target_size(self, group_number, ban_id): """ Return the length of the name for the ban list entry designated by ban_id, in the @@ -2498,9 +2509,12 @@ class Tox: error = c_int() size = self.group_ban_get_target_size(group_number, ban_id) target = create_string_buffer(size) + target_type = self.group_ban_get_type(group_number, ban_id) result = Tox.libtoxcore.tox_group_ban_get_target(self._tox_pointer, group_number, ban_id, target, byref(error)) + if target_type == TOX_GROUP_BAN_TYPE['PUBLIC_KEY']: + return bin_to_string(target, size) return str(target[:size], 'utf-8') def group_ban_get_time_set(self, group_number, ban_id): diff --git a/toxygen/wrapper/toxcore_enums_and_consts.py b/toxygen/wrapper/toxcore_enums_and_consts.py index 1e134f9..b34e272 100644 --- a/toxygen/wrapper/toxcore_enums_and_consts.py +++ b/toxygen/wrapper/toxcore_enums_and_consts.py @@ -919,7 +919,6 @@ TOX_GROUP_BAN_TYPE = { 'PUBLIC_KEY': 1, 'NICK': 2 - } TOX_PUBLIC_KEY_SIZE = 32 From 0f9aa4f51561dbca3f1ffd84788eb4a03e81f009 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 23 Aug 2018 23:51:05 +0300 Subject: [PATCH 126/138] refactoring and is_muted fix --- toxygen/bootstrap/bootstrap.py | 30 +++++++++++++-------------- toxygen/contacts/basecontact.py | 31 +++++++++++++++------------- toxygen/contacts/common.py | 9 +++++++++ toxygen/groups/group_peer.py | 36 +++++++++++++++++++++++---------- toxygen/wrapper/tox.py | 2 ++ 5 files changed, 68 insertions(+), 40 deletions(-) diff --git a/toxygen/bootstrap/bootstrap.py b/toxygen/bootstrap/bootstrap.py index 6c34e0e..fad68c4 100644 --- a/toxygen/bootstrap/bootstrap.py +++ b/toxygen/bootstrap/bootstrap.py @@ -20,11 +20,7 @@ class Node: priority = property(get_priority) def get_data(self): - return bytes(self._ip, 'utf-8'), self._port, self._tox_key - - -def _get_nodes_path(): - return join_path(curr_directory(__file__), 'nodes.json') + return self._ip, self._port, self._tox_key def generate_nodes(nodes_count=DEFAULT_NODES_COUNT): @@ -39,14 +35,6 @@ def generate_nodes(nodes_count=DEFAULT_NODES_COUNT): yield node.get_data() -def save_nodes(nodes): - if not nodes: - return - print('Saving nodes...') - with open(_get_nodes_path(), 'wb') as fl: - fl.write(nodes) - - def download_nodes_list(settings): url = 'https://nodes.tox.chat/json' if not settings['download_nodes_list']: @@ -58,7 +46,7 @@ def download_nodes_list(settings): req.add_header('Content-Type', 'application/json') response = urllib.request.urlopen(req) result = response.read() - save_nodes(result) + _save_nodes(result) except Exception as ex: log('TOX nodes loading error: ' + str(ex)) else: # proxy @@ -78,6 +66,18 @@ def download_nodes_list(settings): QtCore.QThread.msleep(1) QtCore.QCoreApplication.processEvents() data = bytes(reply.readAll().data()) - save_nodes(data) + _save_nodes(data) except Exception as 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) diff --git a/toxygen/contacts/basecontact.py b/toxygen/contacts/basecontact.py index fa709db..c64ffdb 100644 --- a/toxygen/contacts/basecontact.py +++ b/toxygen/contacts/basecontact.py @@ -39,11 +39,12 @@ class BaseContact: return self._name def set_name(self, value): - if self._name != value: - self._name = value - self._widget.name.setText(self._name) - self._widget.name.repaint() - self._name_changed_event(self._name) + if self._name == value: + return + self._name = value + self._widget.name.setText(self._name) + self._widget.name.repaint() + self._name_changed_event(self._name) name = property(get_name, set_name) @@ -60,11 +61,12 @@ class BaseContact: return self._status_message def set_status_message(self, value): - if self._status_message != value: - self._status_message = value - self._widget.status_message.setText(self._status_message) - self._widget.status_message.repaint() - self._status_message_changed_event(self._status_message) + if self._status_message == value: + return + self._status_message = value + self._widget.status_message.setText(self._status_message) + self._widget.status_message.repaint() + self._status_message_changed_event(self._status_message) status_message = property(get_status_message, set_status_message) @@ -81,10 +83,11 @@ class BaseContact: return self._status def set_status(self, value): - if self._status != value: - self._status = value - self._widget.connection_status.update(value) - self._status_changed_event(self._status) + if self._status == value: + return + self._status = value + self._widget.connection_status.update(value) + self._status_changed_event(self._status) status = property(get_status, set_status) diff --git a/toxygen/contacts/common.py b/toxygen/contacts/common.py index ba5dac6..27750a2 100644 --- a/toxygen/contacts/common.py +++ b/toxygen/contacts/common.py @@ -2,6 +2,10 @@ from pydenticon import Generator import hashlib +# ----------------------------------------------------------------------------------------------------------------- +# Typing notifications +# ----------------------------------------------------------------------------------------------------------------- + class BaseTypingNotificationHandler: DEFAULT_HANDLER = None @@ -26,6 +30,11 @@ class FriendTypingNotificationHandler(BaseTypingNotificationHandler): 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)', diff --git a/toxygen/groups/group_peer.py b/toxygen/groups/group_peer.py index 57724a5..4eaf255 100644 --- a/toxygen/groups/group_peer.py +++ b/toxygen/groups/group_peer.py @@ -1,6 +1,9 @@ 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 @@ -11,11 +14,29 @@ class GroupChatPeer: 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 @@ -40,17 +61,10 @@ class GroupChatPeer: role = property(get_role, set_role) - 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) - def get_is_muted(self): return self._is_muted - is_muted = property(get_is_muted) + def set_is_muted(self, is_muted): + self._is_muted = is_muted + + is_muted = property(get_is_muted, set_is_muted) diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index fcdf266..1ba0bbc 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -218,6 +218,7 @@ class Tox: :param public_key: The long term public key of the bootstrap node (TOX_PUBLIC_KEY_SIZE bytes). :return: True on success. """ + address = bytes(address, 'utf-8') tox_err_bootstrap = c_int() result = Tox.libtoxcore.tox_bootstrap(self._tox_pointer, c_char_p(address), c_uint16(port), string_to_bin(public_key), byref(tox_err_bootstrap)) @@ -244,6 +245,7 @@ class Tox: :param public_key: The long term public key of the TCP relay (TOX_PUBLIC_KEY_SIZE bytes). :return: True on success. """ + address = bytes(address, 'utf-8') tox_err_bootstrap = c_int() result = Tox.libtoxcore.tox_add_tcp_relay(self._tox_pointer, c_char_p(address), c_uint16(port), string_to_bin(public_key), byref(tox_err_bootstrap)) From 531fa81bba940354be317c9023c7904a194bd3d0 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 25 Aug 2018 13:31:43 +0300 Subject: [PATCH 127/138] fixed bugs with plugin reloading and toxav_kill --- toxygen/app.py | 3 ++- toxygen/av/calls.py | 8 +++----- toxygen/av/calls_manager.py | 4 ++-- toxygen/common/tox_save.py | 9 +++++++++ toxygen/middleware/threads.py | 15 ++++++++++----- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index a370a91..497a460 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -107,6 +107,7 @@ class App: self._tray.hide() self._save_profile() self._settings.close() + self._calls_manager.set_toxav(None) del self._tox # ----------------------------------------------------------------------------------------------------------------- @@ -202,7 +203,7 @@ class App: def _start_threads(self, initial_start=True): # init thread - self._init = threads.InitThread(self._tox, self._plugin_loader, self._settings) + self._init = threads.InitThread(self._tox, self._plugin_loader, self._settings, initial_start) self._init.start() # starting threads for tox iterate and toxav iterate diff --git a/toxygen/av/calls.py b/toxygen/av/calls.py index 2673f1a..d5f2fe7 100644 --- a/toxygen/av/calls.py +++ b/toxygen/av/calls.py @@ -7,12 +7,13 @@ import itertools import numpy as np from av import screen_sharing from av.call import Call +import common.tox_save -class AV: +class AV(common.tox_save.ToxAvSave): def __init__(self, toxav, settings): - self._toxav = toxav + super().__init__(toxav) self._settings = settings self._running = True @@ -36,9 +37,6 @@ class AV: self._video_width = 640 self._video_height = 480 - def set_toxav(self, toxav): - self._toxav = toxav - def stop(self): self._running = False self.stop_audio_thread() diff --git a/toxygen/av/calls_manager.py b/toxygen/av/calls_manager.py index eefd03a..5a48672 100644 --- a/toxygen/av/calls_manager.py +++ b/toxygen/av/calls_manager.py @@ -8,8 +8,8 @@ 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 + 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 diff --git a/toxygen/common/tox_save.py b/toxygen/common/tox_save.py index 5d4cee0..09c159b 100644 --- a/toxygen/common/tox_save.py +++ b/toxygen/common/tox_save.py @@ -7,3 +7,12 @@ class ToxSave: 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 diff --git a/toxygen/middleware/threads.py b/toxygen/middleware/threads.py index 192f723..15e9665 100644 --- a/toxygen/middleware/threads.py +++ b/toxygen/middleware/threads.py @@ -38,15 +38,18 @@ class BaseQThread(QtCore.QThread): class InitThread(BaseThread): - def __init__(self, tox, plugin_loader, settings): + 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): - # download list of nodes if needed - download_nodes_list(self._settings) - # start plugins - self._plugin_loader.load() + 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(): @@ -56,10 +59,12 @@ class InitThread(BaseThread): 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): From 6e07d3e3d4f33faf9d7d641e9a57bc23a610bd24 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 25 Aug 2018 14:23:59 +0300 Subject: [PATCH 128/138] contacts menu history fixes --- toxygen/app.py | 1 + toxygen/contacts/contact_menu.py | 42 +++++++++++++++++----------- toxygen/contacts/contacts_manager.py | 4 --- toxygen/history/history.py | 16 ++++++++++- toxygen/ui/main_screen.py | 18 +----------- 5 files changed, 42 insertions(+), 39 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index 497a460..e28ccea 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -319,6 +319,7 @@ class App: self._stop_threads(False) data = self._tox.get_savedata() self._save_profile(data) + self._calls_manager.set_toxav(None) del self._tox # create new tox instance self._tox = self._create_tox(data) diff --git a/toxygen/contacts/contact_menu.py b/toxygen/contacts/contact_menu.py index b1c23f5..865e7d5 100644 --- a/toxygen/contacts/contact_menu.py +++ b/toxygen/contacts/contact_menu.py @@ -87,9 +87,13 @@ class BaseContactMenuGenerator: def __init__(self, contact): self._contact = contact - def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service): + 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 @@ -101,11 +105,23 @@ class BaseContactMenuGenerator: 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) + and 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_menu_builder = self._generate_history_menu_builder(main_screen, number) + 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) @@ -132,18 +148,6 @@ class FriendMenuGenerator(BaseContactMenuGenerator): # Private methods # ----------------------------------------------------------------------------------------------------------------- - @staticmethod - def _generate_history_menu_builder(main_screen, number): - history_menu_builder = ContactMenuBuilder() - (history_menu_builder - .with_name(util_ui.tr('Chat history')) - .with_action(util_ui.tr('Clear history'), lambda: main_screen.clear_history(number)) - .with_action(util_ui.tr('Export as text'), lambda: main_screen.export_history(number)) - .with_action(util_ui.tr('Export as HTML'), lambda: main_screen.export_history(number, False)) - ) - - return history_menu_builder - @staticmethod def _generate_plugins_menu_builder(plugin_loader, number): if plugin_loader is None: @@ -174,13 +178,15 @@ class FriendMenuGenerator(BaseContactMenuGenerator): class GroupMenuGenerator(BaseContactMenuGenerator): - def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service): + 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()) @@ -206,13 +212,15 @@ class GroupMenuGenerator(BaseContactMenuGenerator): class GroupPeerMenuGenerator(BaseContactMenuGenerator): - def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service): + 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)) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 23dc61e..50dbc87 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -290,10 +290,6 @@ class ContactsManager(ToxSave): def friend_public_key(self, num): return self._contacts[num].tox_id - def export_history(self, num, as_text): - contact = self._contacts[num] - return self._history.export_history(contact, as_text) - def delete_friend(self, num): """ Removes friend from contact list diff --git a/toxygen/history/history.py b/toxygen/history/history.py index 4850f7d..f551df5 100644 --- a/toxygen/history/history.py +++ b/toxygen/history/history.py @@ -46,6 +46,20 @@ class 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']): @@ -93,7 +107,7 @@ class History: self._db.add_friend_to_db(tox_id) @staticmethod - def export_history(contact, as_text=True, _range=None): + def generate_history(contact, as_text=True, _range=None): if _range is None: contact.load_all_corr() corr = contact.get_corr() diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 6217b47..c89f811 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -606,7 +606,7 @@ class MainWindow(QtWidgets.QMainWindow): 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._groups_service, self._history_loader) parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0)) self.listMenu.move(parent_position + pos) self.listMenu.show() @@ -625,19 +625,6 @@ class MainWindow(QtWidgets.QMainWindow): self.note = MultilineEdit(user, note, save_note) self.note.show() - def export_history(self, num, as_text=True): - s = self._contacts_manager.export_history(num, as_text) - 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 - with open(file_name, 'wt') as fl: - fl.write(s) - def set_alias(self, num): self._contacts_manager.set_alias(num) @@ -652,9 +639,6 @@ class MainWindow(QtWidgets.QMainWindow): def copy_text(text): util_ui.copy_to_clipboard(text) - def clear_history(self, num): - self._history_loader.clear_history(num) - def auto_accept(self, num, value): tox_id = self._contacts_manager.friend_public_key(num) if value: From ee994973db8ca4077ff85b8a1dc2aee48230c26e Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 25 Aug 2018 14:45:58 +0300 Subject: [PATCH 129/138] toxav kill fixed --- toxygen/app.py | 8 ++++++-- toxygen/wrapper/tox.py | 1 - toxygen/wrapper/toxav.py | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index e28ccea..7810751 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -107,7 +107,7 @@ class App: self._tray.hide() self._save_profile() self._settings.close() - self._calls_manager.set_toxav(None) + self._kill_toxav() del self._tox # ----------------------------------------------------------------------------------------------------------------- @@ -319,7 +319,7 @@ class App: self._stop_threads(False) data = self._tox.get_savedata() self._save_profile(data) - self._calls_manager.set_toxav(None) + self._kill_toxav() del self._tox # create new tox instance self._tox = self._create_tox(data) @@ -411,3 +411,7 @@ class App: 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() diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index 1ba0bbc..6ca7741 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -121,7 +121,6 @@ class Tox: self.AV = ToxAV(self._tox_pointer) def __del__(self): - del self.AV Tox.libtoxcore.tox_kill(self._tox_pointer) # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/wrapper/toxav.py b/toxygen/wrapper/toxav.py index 78ab11f..be61309 100644 --- a/toxygen/wrapper/toxav.py +++ b/toxygen/wrapper/toxav.py @@ -40,7 +40,7 @@ class ToxAV: self.video_receive_frame_cb = None self.call_cb = None - def __del__(self): + def kill(self): """ Releases all resources associated with the A/V session. From a4ceeccfd8cff20c26b5bf1514a58dc2d7096a56 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 27 Aug 2018 00:51:40 +0300 Subject: [PATCH 130/138] various bug fixes --- toxygen/app.py | 5 +++ toxygen/contacts/contact.py | 3 +- toxygen/contacts/contact_menu.py | 2 +- toxygen/contacts/contacts_manager.py | 4 +++ toxygen/middleware/callbacks.py | 47 ++++++++++++++++++++++++++++ toxygen/ui/menu.py | 2 +- toxygen/wrapper/tox.py | 1 + 7 files changed, 60 insertions(+), 4 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index 7810751..4bca27c 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -107,6 +107,7 @@ class App: self._tray.hide() self._save_profile() self._settings.close() + self._unset_callbacks() self._kill_toxav() del self._tox @@ -319,6 +320,7 @@ class App: self._stop_threads(False) data = self._tox.get_savedata() self._save_profile(data) + self._unset_callbacks() self._kill_toxav() del self._tox # create new tox instance @@ -408,6 +410,9 @@ class App: self._calls_manager, self._file_transfer_handler, self._ms, self._tray, self._messenger, self._groups_service, self._contacts_provider) + def _unset_callbacks(self): + callbacks.unset_callbacks(self._tox) + def _init_profile(self): if not self._profile.has_avatar(): self._profile.reset_avatar(self._settings['identicons']) diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index ecf944d..e88acf2 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -146,8 +146,7 @@ class Contact(basecontact.BaseContact): # ----------------------------------------------------------------------------------------------------------------- def delete_message(self, message_id): - elem = list(filter(lambda m: type(m) in (TextMessage, GroupChatMessage) and m.message_id == message_id, - self._corr))[0] + elem = list(filter(lambda m: m.message_id == message_id, self._corr))[0] 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: self._unsaved_messages -= 1 diff --git a/toxygen/contacts/contact_menu.py b/toxygen/contacts/contact_menu.py index 865e7d5..8178d31 100644 --- a/toxygen/contacts/contact_menu.py +++ b/toxygen/contacts/contact_menu.py @@ -110,7 +110,7 @@ class BaseContactMenuGenerator: (history_menu_builder .with_name(util_ui.tr('Chat history')) .with_action(util_ui.tr('Clear history'), lambda: history_loader.clear_history(self._contact) - and main_screen.messages.clear()) + 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)) ) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 50dbc87..156c409 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -90,6 +90,7 @@ class ContactsManager(ToxSave): 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) @@ -118,6 +119,7 @@ class ContactsManager(ToxSave): self._messages_items_factory.create_inline_item(message.data) 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: @@ -365,6 +367,8 @@ class ContactsManager(ToxSave): 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() diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 77a029d..5fcce03 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -604,3 +604,50 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, 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) + + +def unset_callbacks(tox): + # self callbacks + tox.callback_self_connection_status(0) + + # friend callbacks + tox.callback_friend_status(0) + tox.callback_friend_message(0) + tox.callback_friend_connection_status(0) + tox.callback_friend_name(0) + tox.callback_friend_status_message(0) + tox.callback_friend_request(0) + tox.callback_friend_typing(0) + tox.callback_friend_read_receipt(0) + + # file transfer + tox.callback_file_recv(0) + tox.callback_file_recv_chunk(0) + tox.callback_file_chunk_request(0) + tox.callback_file_recv_control(0) + + # av + toxav = tox.AV + toxav.callback_call_state(0, 0) + toxav.callback_call(0, 0) + toxav.callback_audio_receive_frame(0, 0) + toxav.callback_video_receive_frame(0, 0) + + # custom packets + tox.callback_friend_lossless_packet(0) + tox.callback_friend_lossy_packet(0) + + # gc callbacks + tox.callback_group_message(0, 0) + tox.callback_group_private_message(0, 0) + tox.callback_group_invite(0, 0) + tox.callback_group_self_join(0, 0) + tox.callback_group_peer_join(0, 0) + tox.callback_group_peer_exit(0, 0) + tox.callback_group_peer_name(0, 0) + tox.callback_group_peer_status(0, 0) + tox.callback_group_topic(0, 0) + tox.callback_group_moderation(0, 0) + tox.callback_group_password(0, 0) + tox.callback_group_peer_limit(0, 0) + tox.callback_group_privacy_state(0, 0) diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index 75db280..45e8765 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -594,7 +594,6 @@ class InterfaceSettings(CenteredWidget): app.removeTranslator(app.translator) app.translator.load(join_path(get_translations_directory(), path)) app.installTranslator(app.translator) - self._settings.save() app_closing_setting = 0 if self.hideRadioButton.isChecked(): @@ -602,6 +601,7 @@ class InterfaceSettings(CenteredWidget): 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')) diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index 6ca7741..1ba0bbc 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -121,6 +121,7 @@ class Tox: self.AV = ToxAV(self._tox_pointer) def __del__(self): + del self.AV Tox.libtoxcore.tox_kill(self._tox_pointer) # ----------------------------------------------------------------------------------------------------------------- From 5e788a543d2d3219549db403156ee5ffb54e92cc Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 30 Aug 2018 00:36:07 +0300 Subject: [PATCH 131/138] fixed messages caching and drag n drop --- toxygen/messenger/messages.py | 3 +-- toxygen/ui/main_screen.py | 2 +- toxygen/ui/main_screen_widgets.py | 15 ++++++++++----- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index d5882b8..e777c4b 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -66,8 +66,7 @@ class Message: message_id = property(get_message_id) def get_widget(self, *args): - if self._widget is None: - self._widget = self._create_widget(*args) + self._widget = self._create_widget(*args) return self._widget diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index c89f811..96b2b9e 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -37,7 +37,7 @@ class MainWindow(QtWidgets.QMainWindow): 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) + self.messageEdit.set_dependencies(messenger, contacts_manager, file_transfer_handler) self.update_gc_invites_button_state() diff --git a/toxygen/ui/main_screen_widgets.py b/toxygen/ui/main_screen_widgets.py index 8b7bf52..a2d7526 100644 --- a/toxygen/ui/main_screen_widgets.py +++ b/toxygen/ui/main_screen_widgets.py @@ -12,15 +12,16 @@ class MessageArea(QtWidgets.QPlainTextEdit): def __init__(self, parent, form): super().__init__(parent) - self._messenger = self._contacts_manager = None + self._messenger = self._contacts_manager = self._file_transfer_handler = None self.parent = form self.setAcceptDrops(True) self._timer = QtCore.QTimer(self) self._timer.timeout.connect(lambda: self._messenger.send_typing(False)) - def set_dependencies(self, messenger, contacts_manager): + 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): if event.matches(QtGui.QKeySequence.Paste): @@ -84,16 +85,20 @@ class MessageArea(QtWidgets.QPlainTextEdit): def pasteEvent(self, text=None): text = text or QtWidgets.QApplication.clipboard().text() if text.startswith('file://'): - file_name = self.parse_file_name(text) - self.parent.profile.send_file(file_name) + if not self._contacts_manager.is_active_a_friend(): + return + friend_number = self._contacts_manager.get_active_number() + file_path = self._parse_file_path(text) + self._file_transfer_handler.send_file(file_path, friend_number) else: self.insertPlainText(text) @staticmethod - def parse_file_name(file_name): + def _parse_file_path(file_name): if file_name.endswith('\r\n'): file_name = file_name[:-2] file_name = urllib.parse.unquote(file_name) + return file_name[8 if util.get_platform() == 'Windows' else 7:] From 9a580824960aa255ea8986ddae943bae4544ee7f Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 13 Sep 2018 23:04:22 +0300 Subject: [PATCH 132/138] bug fixes --- toxygen/app.py | 10 ++-- toxygen/file_transfers/file_transfers.py | 2 +- .../file_transfers/file_transfers_handler.py | 6 +-- toxygen/messenger/messenger.py | 16 +++---- toxygen/middleware/callbacks.py | 47 ------------------- toxygen/wrapper/tox.py | 7 +-- toxygen/wrapper/toxav.py | 5 +- 7 files changed, 24 insertions(+), 69 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index 4bca27c..18e5236 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -107,8 +107,8 @@ class App: self._tray.hide() self._save_profile() self._settings.close() - self._unset_callbacks() self._kill_toxav() + self._kill_tox() del self._tox # ----------------------------------------------------------------------------------------------------------------- @@ -320,8 +320,8 @@ class App: self._stop_threads(False) data = self._tox.get_savedata() self._save_profile(data) - self._unset_callbacks() self._kill_toxav() + self._kill_tox() del self._tox # create new tox instance self._tox = self._create_tox(data) @@ -410,9 +410,6 @@ class App: self._calls_manager, self._file_transfer_handler, self._ms, self._tray, self._messenger, self._groups_service, self._contacts_provider) - def _unset_callbacks(self): - callbacks.unset_callbacks(self._tox) - def _init_profile(self): if not self._profile.has_avatar(): self._profile.reset_avatar(self._settings['identicons']) @@ -420,3 +417,6 @@ class App: def _kill_toxav(self): self._calls_manager.set_toxav(None) self._tox.AV.kill() + + def _kill_tox(self): + self._tox.kill() diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py index 46a777f..445dac9 100644 --- a/toxygen/file_transfers/file_transfers.py +++ b/toxygen/file_transfers/file_transfers.py @@ -29,7 +29,7 @@ SHOW_PROGRESS_BAR = (0, 1, 4) def is_inline(file_name): allowed_inlines = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png') - return file_name in allowed_inlines or file_name.startswith('qTox_Screenshot_') + return file_name in allowed_inlines or file_name.startswith('qTox_Image_') class FileTransfer: diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index d462c89..cde1795 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -257,7 +257,7 @@ class FileTransfersHandler(ToxSave): elif not size: friend.reset_avatar(self._settings['identicons']) - def _send_avatar_to_contacts(self): + def _send_avatar_to_contacts(self, _): friends = self._get_all_friends() for friend in friends: self.send_avatar(friend.number) @@ -286,13 +286,13 @@ class FileTransfersHandler(ToxSave): path, file_name = os.path.split(path) new_file_name, i = file_name, 1 if not from_position: - while os.path.isfile(path + '/' + new_file_name): # file with same name already exists + 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 = os.path.join(path, new_file_name) + path = join_path(path, new_file_name) return path diff --git a/toxygen/messenger/messenger.py b/toxygen/messenger/messenger.py index 9ee86dd..e859135 100644 --- a/toxygen/messenger/messenger.py +++ b/toxygen/messenger/messenger.py @@ -224,7 +224,7 @@ class Messenger(tox_save.ToxSave): return message = util_ui.tr('User {} is now known as {}') message = message.format(old_name, new_name) - if self._contacts_manager.is_friend_active(friend.number): + if not self._contacts_manager.is_friend_active(friend.number): friend.actions = True self._add_info_message(friend.number, message) @@ -236,14 +236,14 @@ class Messenger(tox_save.ToxSave): def _split_message(message): messages = [] while len(message) > TOX_MAX_MESSAGE_LENGTH: - size = TOX_MAX_MESSAGE_LENGTH * 4 / 5 + size = TOX_MAX_MESSAGE_LENGTH * 4 // 5 last_part = message[size:TOX_MAX_MESSAGE_LENGTH] - if ' ' in last_part: - index = last_part.index(' ') - elif ',' in last_part: - index = last_part.index(',') - elif '.' in last_part: - index = last_part.index('.') + 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 diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 5fcce03..77a029d 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -604,50 +604,3 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, 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) - - -def unset_callbacks(tox): - # self callbacks - tox.callback_self_connection_status(0) - - # friend callbacks - tox.callback_friend_status(0) - tox.callback_friend_message(0) - tox.callback_friend_connection_status(0) - tox.callback_friend_name(0) - tox.callback_friend_status_message(0) - tox.callback_friend_request(0) - tox.callback_friend_typing(0) - tox.callback_friend_read_receipt(0) - - # file transfer - tox.callback_file_recv(0) - tox.callback_file_recv_chunk(0) - tox.callback_file_chunk_request(0) - tox.callback_file_recv_control(0) - - # av - toxav = tox.AV - toxav.callback_call_state(0, 0) - toxav.callback_call(0, 0) - toxav.callback_audio_receive_frame(0, 0) - toxav.callback_video_receive_frame(0, 0) - - # custom packets - tox.callback_friend_lossless_packet(0) - tox.callback_friend_lossy_packet(0) - - # gc callbacks - tox.callback_group_message(0, 0) - tox.callback_group_private_message(0, 0) - tox.callback_group_invite(0, 0) - tox.callback_group_self_join(0, 0) - tox.callback_group_peer_join(0, 0) - tox.callback_group_peer_exit(0, 0) - tox.callback_group_peer_name(0, 0) - tox.callback_group_peer_status(0, 0) - tox.callback_group_topic(0, 0) - tox.callback_group_moderation(0, 0) - tox.callback_group_password(0, 0) - tox.callback_group_peer_limit(0, 0) - tox.callback_group_privacy_state(0, 0) diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index 1ba0bbc..21b0ebc 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -59,8 +59,9 @@ class Tox: self._tox_pointer = tox_pointer else: tox_err_new = c_int() - Tox.libtoxcore.tox_new.restype = POINTER(c_void_p) - self._tox_pointer = Tox.libtoxcore.tox_new(tox_options, byref(tox_err_new)) + f = Tox.libtoxcore.tox_new + f.restype = POINTER(c_void_p) + self._tox_pointer = f(tox_options, byref(tox_err_new)) tox_err_new = tox_err_new.value if tox_err_new == TOX_ERR_NEW['NULL']: raise ArgumentError('One of the arguments to the function was NULL when it was not expected.') @@ -120,7 +121,7 @@ class Tox: self.AV = ToxAV(self._tox_pointer) - def __del__(self): + def kill(self): del self.AV Tox.libtoxcore.tox_kill(self._tox_pointer) diff --git a/toxygen/wrapper/toxav.py b/toxygen/wrapper/toxav.py index be61309..98e1c73 100644 --- a/toxygen/wrapper/toxav.py +++ b/toxygen/wrapper/toxav.py @@ -24,8 +24,9 @@ class ToxAV: """ self.libtoxav = LibToxAV() toxav_err_new = c_int() - self.libtoxav.toxav_new.restype = POINTER(c_void_p) - self._toxav_pointer = self.libtoxav.toxav_new(tox_pointer, byref(toxav_err_new)) + f = self.libtoxav.toxav_new + f.restype = POINTER(c_void_p) + self._toxav_pointer = f(tox_pointer, byref(toxav_err_new)) toxav_err_new = toxav_err_new.value if toxav_err_new == TOXAV_ERR_NEW['NULL']: raise ArgumentError('One of the arguments to the function was NULL when it was not expected.') From 9f7de204d4fddf0eabf5dff51d6e14270168ebd8 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 14 Sep 2018 18:35:07 +0300 Subject: [PATCH 133/138] fixes for smileys selection and file transfers --- toxygen/file_transfers/file_transfers.py | 5 +- .../file_transfers_messages_service.py | 9 +- toxygen/middleware/threads.py | 2 +- toxygen/ui/main_screen.py | 17 ++-- toxygen/ui/main_screen_widgets.py | 83 ++++++++++--------- 5 files changed, 63 insertions(+), 53 deletions(-) diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py index 445dac9..9b29575 100644 --- a/toxygen/file_transfers/file_transfers.py +++ b/toxygen/file_transfers/file_transfers.py @@ -4,6 +4,7 @@ from os import remove, rename, chdir from time import time from wrapper.tox import Tox from common.event import Event +from middleware.threads import invoke_in_main_thread FILE_TRANSFER_STATE = { @@ -120,10 +121,10 @@ class FileTransfer: t = -1 else: t = ((time() - self._creation_time) / percentage) * (1 - percentage) - self._state_changed_event(self.state, percentage, int(t)) + invoke_in_main_thread(self._state_changed_event, self.state, percentage, int(t)) def _finished(self): - self._finished_event(self._friend_number, self._file_number) + invoke_in_main_thread(self._finished_event, self._friend_number, self._file_number) # ----------------------------------------------------------------------------------------------------------------- # Send file diff --git a/toxygen/file_transfers/file_transfers_messages_service.py b/toxygen/file_transfers/file_transfers_messages_service.py index 7cb90f5..4509183 100644 --- a/toxygen/file_transfers/file_transfers_messages_service.py +++ b/toxygen/file_transfers/file_transfers_messages_service.py @@ -40,10 +40,11 @@ class FileTransfersMessagesService: return tm def add_inline_message(self, transfer, index): - if self._is_friend_active(transfer.friend_number): - count = self._messages.count() - if count + index + 1 >= 0: - self._create_inline_item(transfer.data, count + index + 1) + 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']) diff --git a/toxygen/middleware/threads.py b/toxygen/middleware/threads.py index 15e9665..5f9404b 100644 --- a/toxygen/middleware/threads.py +++ b/toxygen/middleware/threads.py @@ -106,7 +106,7 @@ class ToxAVIterateThread(BaseQThread): # File transfers thread # ----------------------------------------------------------------------------------------------------------------- -class FileTransfersThread(BaseThread): +class FileTransfersThread(BaseQThread): def __init__(self): super().__init__() diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 96b2b9e..5a510a5 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -18,6 +18,7 @@ class MainWindow(QtWidgets.QMainWindow): 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 @@ -556,13 +557,15 @@ class MainWindow(QtWidgets.QMainWindow): def send_smiley(self): self.menu.hide() - if self._contacts_manager.get_curr_contact() is not None: - self.smiley = self._widget_factory.create_smiley_window(self) - self.smiley.setGeometry(QtCore.QRect(self.x() if self._settings['mirror_mode'] else 200 + self.x(), - self.y() + self.height() - 400, - self.smiley.width(), - self.smiley.height())) - self.smiley.show() + 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() diff --git a/toxygen/ui/main_screen_widgets.py b/toxygen/ui/main_screen_widgets.py index a2d7526..122561b 100644 --- a/toxygen/ui/main_screen_widgets.py +++ b/toxygen/ui/main_screen_widgets.py @@ -141,57 +141,62 @@ class SmileyWindow(QtWidgets.QWidget): def __init__(self, parent, smiley_loader): super().__init__(parent) self.setWindowFlags(QtCore.Qt.FramelessWindowHint) - self.data = smiley_loader.get_smileys() - count = len(self.data) + self._parent = parent + self._data = smiley_loader.get_smileys() + + count = len(self._data) if not count: self.close() - self.page_size = int(pow(count / 8, 0.5) + 1) * 8 # smileys per page - if count % self.page_size == 0: - self.page_count = count // self.page_size + + self._page_size = int(pow(count / 8, 0.5) + 1) * 8 # smileys per page + if count % self._page_size == 0: + self._page_count = count // self._page_size else: - self.page_count = round(count / self.page_size + 0.5) - self.page = -1 - self.radio = [] - self.parent = parent - for i in range(self.page_count): # buttons with smileys + self._page_count = round(count / self._page_size + 0.5) + self._page = -1 + self._radio = [] + + for i in range(self._page_count): # pages - radio buttons elem = QtWidgets.QRadioButton(self) - elem.setGeometry(QtCore.QRect(i * 20 + 5, 180, 20, 20)) - elem.clicked.connect(lambda c, t=i: self.checked(t)) - self.radio.append(elem) - width = max(self.page_count * 20 + 30, (self.page_size + 5) * 8 // 10) + elem.setGeometry(QtCore.QRect(i * 20 + 5, 160, 20, 20)) + elem.clicked.connect(lambda c, t=i: self._checked(t)) + self._radio.append(elem) + + width = max(self._page_count * 20 + 30, (self._page_size + 5) * 8 // 10) self.setMaximumSize(width, 200) self.setMinimumSize(width, 200) - self.buttons = [] - for i in range(self.page_size): # pages - radio buttons + self._buttons = [] + + for i in range(self._page_size): # buttons with smileys b = QtWidgets.QPushButton(self) b.setGeometry(QtCore.QRect((i // 8) * 20 + 5, (i % 8) * 20, 20, 20)) - b.clicked.connect(lambda c, t=i: self.clicked(t)) - self.buttons.append(b) - 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() + b.clicked.connect(lambda c, t=i: self._clicked(t)) + self._buttons.append(b) + self._checked(0) def leaveEvent(self, event): 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): From 02b2d07b6d2745fc676a7ee49e72a69dc0839abd Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 14 Sep 2018 19:10:35 +0300 Subject: [PATCH 134/138] profile backup - initial infrastructure --- toxygen/app.py | 4 ++- toxygen/user_data/backup_service.py | 40 ++++++++++++++++++++++++++++ toxygen/user_data/profile_manager.py | 17 ++++++++++++ toxygen/user_data/settings.py | 16 ++++++++++- 4 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 toxygen/user_data/backup_service.py diff --git a/toxygen/app.py b/toxygen/app.py index 18e5236..4597d0a 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -34,6 +34,7 @@ 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 @@ -45,7 +46,7 @@ class App: 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 = 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:] @@ -341,6 +342,7 @@ class App: 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) diff --git a/toxygen/user_data/backup_service.py b/toxygen/user_data/backup_service.py new file mode 100644 index 0000000..bb0cef9 --- /dev/null +++ b/toxygen/user_data/backup_service.py @@ -0,0 +1,40 @@ +import os.path +from utils.util import get_profile_name_from_path, join_path + + +class BackupService: + + def __init__(self, settings, profile_manager): + self._settings = settings + self._profile_name = get_profile_name_from_path(profile_manager.get_path()) + + settings.settings_saved_event.add_callback(self._settings_saved) + profile_manager.profile_saved_event.add_callback(self._profile_saved) + + def _settings_saved(self, data): + if not self._check_if_should_save_backup(): + return + + file_path = join_path(self._get_backup_directory(), self._profile_name + '.json') + + with open(file_path, 'wt') as fl: + fl.write(data) + + def _profile_saved(self, data): + if not self._check_if_should_save_backup(): + return + + file_path = join_path(self._get_backup_directory(), self._profile_name + '.tox') + + with open(file_path, 'wb') as fl: + fl.write(data) + + def _check_if_should_save_backup(self): + backup_directory = self._get_backup_directory() + if backup_directory is None: + return False + + return os.path.exists(backup_directory) and os.path.isdir(backup_directory) + + def _get_backup_directory(self): + return self._settings['backup_directory'] diff --git a/toxygen/user_data/profile_manager.py b/toxygen/user_data/profile_manager.py index e49f5f4..05e2f2d 100644 --- a/toxygen/user_data/profile_manager.py +++ b/toxygen/user_data/profile_manager.py @@ -1,6 +1,7 @@ import utils.util as util import os from user_data.settings import Settings +from common.event import Event class ProfileManager: @@ -11,11 +12,25 @@ class ProfileManager: self._toxes = toxes self._path = path self._directory = os.path.dirname(path) + self._profile_saved_event = Event() # create /avatars if not exists: avatars_directory = util.join_path(self._directory, 'avatars') if not os.path.exists(avatars_directory): os.makedirs(avatars_directory) + # ----------------------------------------------------------------------------------------------------------------- + # Properties + # ----------------------------------------------------------------------------------------------------------------- + + def get_profile_saved_event(self): + return self._profile_saved_event + + profile_saved_event = property(get_profile_saved_event) + + # ----------------------------------------------------------------------------------------------------------------- + # Public methods + # ----------------------------------------------------------------------------------------------------------------- + def open_profile(self): with open(self._path, 'rb') as fl: data = fl.read() @@ -37,6 +52,8 @@ class ProfileManager: fl.write(data) print('Profile saved successfully') + self._profile_saved_event(data) + def export_profile(self, settings, new_path, use_new_path): path = new_path + os.path.basename(self._path) with open(self._path, 'rb') as fin: diff --git a/toxygen/user_data/settings.py b/toxygen/user_data/settings.py index 768a07e..c986019 100644 --- a/toxygen/user_data/settings.py +++ b/toxygen/user_data/settings.py @@ -1,6 +1,7 @@ import json from utils.util import * import pyaudio +from common.event import Event class Settings(dict): @@ -12,6 +13,7 @@ class Settings(dict): self._path = path self._profile_path = path.replace('.json', '.tox') self._toxes = toxes + self._settings_saved_event = Event() if os.path.isfile(path): with open(path, 'rb') as fl: data = fl.read() @@ -43,6 +45,15 @@ class Settings(dict): 'enabled': input_devices and output_devices} self.video = {'device': -1, 'width': 640, 'height': 480, 'x': 0, 'y': 0} + # ----------------------------------------------------------------------------------------------------------------- + # Properties + # ----------------------------------------------------------------------------------------------------------------- + + def get_settings_saved_event(self): + return self._settings_saved_event + + settings_saved_event = property(get_settings_saved_event) + # ----------------------------------------------------------------------------------------------------------------- # Public methods # ----------------------------------------------------------------------------------------------------------------- @@ -56,6 +67,8 @@ class Settings(dict): with open(self._path, 'wb') as fl: fl.write(text) + self._settings_saved_event(text) + def close(self): path = self._profile_path + '.lock' if os.path.isfile(path): @@ -186,7 +199,8 @@ class Settings(dict): 'group_notifications': True, 'download_nodes_list': False, 'notify_all_gc': False, - 'lan_discovery': True + 'lan_discovery': True, + 'backup_directory': None } @staticmethod From ad3bbb5e4599de2b21a2f59842e5305b920ca7c0 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 14 Sep 2018 20:38:18 +0300 Subject: [PATCH 135/138] profile settings screen converted --- toxygen/ui/menu.py | 197 -------------- toxygen/ui/profile_settings_screen.py | 157 +++++++++++ toxygen/ui/views/profile_settings_screen.ui | 280 ++++++++++++++++++++ toxygen/ui/widgets_factory.py | 1 + toxygen/user_data/settings.py | 5 +- 5 files changed, 440 insertions(+), 200 deletions(-) create mode 100644 toxygen/ui/profile_settings_screen.py create mode 100644 toxygen/ui/views/profile_settings_screen.ui diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index 45e8765..8aec578 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -52,203 +52,6 @@ class AddContact(CenteredWidget): self.toxIdLineEdit.setPlaceholderText(util_ui.tr('TOX ID or public key of contact')) -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 - self.initUI() - self.center() - - def initUI(self): - self.setObjectName("ProfileSettingsForm") - self.setMinimumSize(QtCore.QSize(700, 600)) - self.setMaximumSize(QtCore.QSize(700, 600)) - self.nick = LineEdit(self) - self.nick.setGeometry(QtCore.QRect(30, 60, 350, 27)) - self.nick.setText(self._profile.name) - self.status = QtWidgets.QComboBox(self) - self.status.setGeometry(QtCore.QRect(400, 60, 200, 27)) - self.status_message = LineEdit(self) - self.status_message.setGeometry(QtCore.QRect(30, 130, 350, 27)) - self.status_message.setText(self._profile.status_message) - self.label = QtWidgets.QLabel(self) - self.label.setGeometry(QtCore.QRect(40, 30, 91, 25)) - font = QtGui.QFont() - font.setFamily(self._settings['font']) - font.setPointSize(18) - font.setWeight(75) - font.setBold(True) - self.label.setFont(font) - self.label_2 = QtWidgets.QLabel(self) - self.label_2.setGeometry(QtCore.QRect(40, 100, 100, 25)) - self.label_2.setFont(font) - self.label_3 = QtWidgets.QLabel(self) - self.label_3.setGeometry(QtCore.QRect(40, 180, 100, 25)) - self.label_3.setFont(font) - self.tox_id = QtWidgets.QLabel(self) - self.tox_id.setGeometry(QtCore.QRect(15, 210, 685, 21)) - font.setPointSize(10) - self.tox_id.setFont(font) - self.tox_id.setText(self._profile.tox_id) - self.copyId = QtWidgets.QPushButton(self) - self.copyId.setGeometry(QtCore.QRect(40, 250, 180, 30)) - self.copyId.clicked.connect(self.copy) - self.export = QtWidgets.QPushButton(self) - self.export.setGeometry(QtCore.QRect(230, 250, 180, 30)) - self.export.clicked.connect(self.export_profile) - self.new_nospam = QtWidgets.QPushButton(self) - self.new_nospam.setGeometry(QtCore.QRect(420, 250, 180, 30)) - self.new_nospam.clicked.connect(self.new_no_spam) - self.copy_pk = QtWidgets.QPushButton(self) - self.copy_pk.setGeometry(QtCore.QRect(40, 300, 180, 30)) - self.copy_pk.clicked.connect(self.copy_public_key) - self.new_avatar = QtWidgets.QPushButton(self) - self.new_avatar.setGeometry(QtCore.QRect(230, 300, 180, 30)) - self.delete_avatar = QtWidgets.QPushButton(self) - self.delete_avatar.setGeometry(QtCore.QRect(420, 300, 180, 30)) - self.delete_avatar.clicked.connect(self.reset_avatar) - self.new_avatar.clicked.connect(self.set_avatar) - self.profilepass = QtWidgets.QLabel(self) - self.profilepass.setGeometry(QtCore.QRect(40, 340, 300, 30)) - font.setPointSize(18) - self.profilepass.setFont(font) - self.password = LineEdit(self) - self.password.setGeometry(QtCore.QRect(40, 380, 300, 30)) - self.password.setEchoMode(QtWidgets.QLineEdit.Password) - self.leave_blank = QtWidgets.QLabel(self) - self.leave_blank.setGeometry(QtCore.QRect(350, 380, 300, 30)) - self.confirm_password = LineEdit(self) - self.confirm_password.setGeometry(QtCore.QRect(40, 420, 300, 30)) - self.confirm_password.setEchoMode(QtWidgets.QLineEdit.Password) - self.set_password = QtWidgets.QPushButton(self) - self.set_password.setGeometry(QtCore.QRect(40, 470, 300, 30)) - self.set_password.clicked.connect(self.new_password) - self.not_match = QtWidgets.QLabel(self) - self.not_match.setGeometry(QtCore.QRect(350, 420, 300, 30)) - self.not_match.setVisible(False) - self.not_match.setStyleSheet('QLabel { color: #BC1C1C; }') - self.warning = QtWidgets.QLabel(self) - self.warning.setGeometry(QtCore.QRect(40, 510, 500, 30)) - self.warning.setStyleSheet('QLabel { color: #BC1C1C; }') - self.default = QtWidgets.QPushButton(self) - self.default.setGeometry(QtCore.QRect(40, 550, 620, 30)) - self._auto = Settings.get_auto_profile() == self._profile_manager.get_path() - self.default.clicked.connect(self.auto_profile) - self.retranslateUi() - if self._profile.status is not None: - self.status.setCurrentIndex(self._profile.status) - else: - self.status.setVisible(False) - QtCore.QMetaObject.connectSlotsByName(self) - - def retranslateUi(self): - self.export.setText(util_ui.tr("Export profile")) - self.setWindowTitle(util_ui.tr("Profile settings")) - self.label.setText(util_ui.tr("Name:")) - self.label_2.setText(util_ui.tr("Status:")) - self.label_3.setText(util_ui.tr("TOX ID:")) - self.copyId.setText(util_ui.tr("Copy TOX ID")) - self.new_avatar.setText(util_ui.tr("New avatar")) - self.delete_avatar.setText(util_ui.tr("Reset avatar")) - self.new_nospam.setText(util_ui.tr("New NoSpam")) - self.profilepass.setText(util_ui.tr("Profile password")) - self.password.setPlaceholderText(util_ui.tr("Password (at least 8 symbols)")) - self.confirm_password.setPlaceholderText(util_ui.tr("Confirm password")) - self.set_password.setText(util_ui.tr("Set password")) - self.not_match.setText(util_ui.tr("Passwords do not match")) - self.leave_blank.setText(util_ui.tr("Leaving blank will reset current password")) - self.warning.setText(util_ui.tr("There is no way to recover lost passwords")) - self.status.addItem(util_ui.tr("Online")) - self.status.addItem(util_ui.tr("Away")) - self.status.addItem(util_ui.tr("Busy")) - self.copy_pk.setText(util_ui.tr("Copy public key")) - - self.set_default_profile_button_text() - - def 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.default.setText(util_ui.tr("Mark as not default profile")) - else: - self.default.setText(util_ui.tr("Mark as default profile")) - - def new_password(self): - if self.password.text() == self.confirm_password.text(): - if not len(self.password.text()) or len(self.password.text()) >= 8: - self._toxes.set_password(self.password.text()) - self.close() - else: - self.not_match.setText( - util_ui.tr("Password must be at least 8 symbols")) - self.not_match.setVisible(True) - else: - self.not_match.setText(util_ui.tr("Passwords do not match")) - self.not_match.setVisible(True) - - def copy(self): - clipboard = QtWidgets.QApplication.clipboard() - clipboard.setText(self._profile.tox_id) - pixmap = QtGui.QPixmap(join_path(get_images_directory(), 'accept.png')) - icon = QtGui.QIcon(pixmap) - self.copyId.setIcon(icon) - self.copyId.setIconSize(QtCore.QSize(10, 10)) - - def copy_public_key(self): - clipboard = QtWidgets.QApplication.clipboard() - clipboard.setText(self._profile.tox_id[:64]) - pixmap = QtGui.QPixmap(join_path(get_images_directory(), 'accept.png')) - icon = QtGui.QIcon(pixmap) - self.copy_pk.setIcon(icon) - self.copy_pk.setIconSize(QtCore.QSize(10, 10)) - - def new_no_spam(self): - self.tox_id.setText(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), aspectRatioMode=QtCore.Qt.KeepAspectRatio, - transformMode=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 directory != '/': - 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) - - def closeEvent(self, event): - self._profile.set_name(self.nick.text()) - self._profile.set_status_message(self.status_message.text()) - self._profile.set_status(self.status.currentIndex()) - - class NetworkSettings(CenteredWidget): """Network settings form: UDP, Ipv6 and proxy""" def __init__(self, settings, reset): diff --git a/toxygen/ui/profile_settings_screen.py b/toxygen/ui/profile_settings_screen.py new file mode 100644 index 0000000..2e55d3d --- /dev/null +++ b/toxygen/ui/profile_settings_screen.py @@ -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) + diff --git a/toxygen/ui/views/profile_settings_screen.ui b/toxygen/ui/views/profile_settings_screen.ui new file mode 100644 index 0000000..ece0083 --- /dev/null +++ b/toxygen/ui/views/profile_settings_screen.ui @@ -0,0 +1,280 @@ + + + Form + + + + 0 + 0 + 900 + 702 + + + + Form + + + + + 30 + 10 + 161 + 31 + + + + TextLabel + + + + + + 30 + 90 + 161 + 31 + + + + TextLabel + + + + + + 30 + 50 + 421 + 31 + + + + + + + 30 + 130 + 421 + 31 + + + + + + + 520 + 30 + 311 + 31 + + + + + + + 40 + 180 + 131 + 21 + + + + TextLabel + + + + + + 40 + 210 + 831 + 61 + + + + TextLabel + + + true + + + + + + 40 + 280 + 371 + 31 + + + + PushButton + + + + + + 440 + 280 + 371 + 31 + + + + PushButton + + + + + + 520 + 80 + 321 + 35 + + + + PushButton + + + + + + 520 + 130 + 321 + 35 + + + + PushButton + + + + + + 60 + 380 + 161 + 31 + + + + TextLabel + + + + + + 50 + 420 + 421 + 31 + + + + + + + 50 + 470 + 421 + 31 + + + + + + + 500 + 420 + 381 + 21 + + + + TextLabel + + + + + + 60 + 580 + 381 + 21 + + + + TextLabel + + + + + + 40 + 630 + 831 + 35 + + + + PushButton + + + + + + 50 + 520 + 421 + 35 + + + + PushButton + + + + + + 500 + 470 + 381 + 21 + + + + TextLabel + + + + + + 40 + 330 + 371 + 35 + + + + PushButton + + + + + + 440 + 330 + 371 + 35 + + + + PushButton + + + + + + diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py index aa70861..128e85e 100644 --- a/toxygen/ui/widgets_factory.py +++ b/toxygen/ui/widgets_factory.py @@ -6,6 +6,7 @@ from ui.self_peer_screen import * from ui.group_invites_widgets import * from ui.group_settings_widgets import * from ui.group_bans_widgets import * +from ui.profile_settings_screen import ProfileSettings class WidgetsFactory: diff --git a/toxygen/user_data/settings.py b/toxygen/user_data/settings.py index c986019..71422c2 100644 --- a/toxygen/user_data/settings.py +++ b/toxygen/user_data/settings.py @@ -137,9 +137,8 @@ class Settings(dict): data = json.loads(data) else: data = {} - if 'path' in data: - del data['path'] - del data['name'] + if 'profile_path' in data: + del data['profile_path'] with open(p, 'w') as fl: fl.write(json.dumps(data)) From ae4eae92ae6ba6db699b27e266a652444c7f16f7 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 23 Sep 2018 13:04:27 +0300 Subject: [PATCH 136/138] minor bug fixes --- toxygen/app.py | 6 +++--- toxygen/contacts/basecontact.py | 2 +- toxygen/file_transfers/file_transfers_handler.py | 7 +++++-- toxygen/middleware/callbacks.py | 8 ++++---- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/toxygen/app.py b/toxygen/app.py index 4597d0a..a23816d 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -105,12 +105,12 @@ class App: 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() - del self._tox # ----------------------------------------------------------------------------------------------------------------- # App loading @@ -323,7 +323,6 @@ class App: self._save_profile(data) self._kill_toxav() self._kill_tox() - del self._tox # create new tox instance self._tox = self._create_tox(data) self._start_threads(False) @@ -398,7 +397,8 @@ class App: if updating: self._save_profile() self._settings.close() - del self._tox + self._kill_toxav() + self._kill_tox() return updating def _create_tox(self, data): diff --git a/toxygen/contacts/basecontact.py b/toxygen/contacts/basecontact.py index c64ffdb..2058890 100644 --- a/toxygen/contacts/basecontact.py +++ b/toxygen/contacts/basecontact.py @@ -126,7 +126,7 @@ class BaseContact: if os.path.isfile(avatar_path) and not avatar_path == self._get_default_avatar_path(): os.remove(avatar_path) if generate_new: - self.set_avatar(common.generate_avatar(self.tox_id)) + self.set_avatar(common.generate_avatar(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])) else: self.load_avatar() diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index cde1795..463152f 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -20,7 +20,7 @@ class FileTransfersHandler(ToxSave): profile.avatar_changed_event.add_callback(self._send_avatar_to_contacts) - def __del__(self): + def stop(self): self._settings['paused_file_transfers'] = self._paused_file_transfers if self._settings['resend_files'] else {} self._settings.save() @@ -259,13 +259,16 @@ class FileTransfersHandler(ToxSave): def _send_avatar_to_contacts(self, _): friends = self._get_all_friends() - for friend in friends: + for friend in filter(self._is_friend_online, friends): self.send_avatar(friend.number) # ----------------------------------------------------------------------------------------------------------------- # Private methods # ----------------------------------------------------------------------------------------------------------------- + def _is_friend_online(self, friend_number): + return self._get_friend_by_number(friend_number).status is not None + def _get_friend_by_number(self, friend_number): return self._contact_provider.get_friend_by_number(friend_number) diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 77a029d..da94a7f 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -181,7 +181,7 @@ def tox_file_recv(window, tray, profile, file_transfer_handler, contacts_manager 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 = os.path.join(util.get_images_directory(), 'icon_new_messages.png') + 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') @@ -371,7 +371,7 @@ def group_message(window, tray, tox, messenger, settings, profile): 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 = os.path.join(util.get_images_directory(), 'icon_new_messages.png') + icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png') invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon)) return wrapped @@ -392,7 +392,7 @@ def group_private_message(window, tray, tox, messenger, settings, profile): 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 = os.path.join(util.get_images_directory(), 'icon_new_messages.png') + icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png') invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon)) return wrapped @@ -411,7 +411,7 @@ def group_invite(window, settings, tray, profile, groups_service, contacts_provi 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 = os.path.join(util.get_images_directory(), 'icon_new_messages.png') + icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png') invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon)) return wrapped From cf4cfa979cc0973595446789320ab38f3752c12c Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 24 Sep 2018 22:06:30 +0300 Subject: [PATCH 137/138] Fixed crash on reconnection, file transfers fixes --- toxygen/contacts/contacts_manager.py | 2 +- toxygen/contacts/profile.py | 6 +++-- toxygen/file_transfers/file_transfers.py | 14 +++++------ .../file_transfers/file_transfers_handler.py | 25 +++++++++++-------- toxygen/history/history.py | 13 +++++----- toxygen/middleware/callbacks.py | 5 ++-- toxygen/ui/items_factories.py | 4 +-- 7 files changed, 37 insertions(+), 32 deletions(-) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 156c409..87a61ff 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -116,7 +116,7 @@ class ContactsManager(ToxSave): 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.data) + self._messages_items_factory.create_inline_item(message) else: self._messages_items_factory.create_message_item(message) self._messages.scrollToBottom() diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index ed7cd83..81220af 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -2,6 +2,7 @@ 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): @@ -73,12 +74,13 @@ class Profile(basecontact.BaseContact, tox_save.ToxSave): Recreate tox instance """ self.status = None - self._reset_action() + invoke_in_main_thread(self._reset_action) def _reconnect(self): self._waiting_for_reconnection = False contacts = self._contacts_provider.get_all_friends() - if self.status is None or (all(list(map(lambda x: x.status is None, contacts))) and len(contacts)): + 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) diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py index 9b29575..0f04e5b 100644 --- a/toxygen/file_transfers/file_transfers.py +++ b/toxygen/file_transfers/file_transfers.py @@ -52,10 +52,10 @@ class FileTransfer: self._file_id = self._file = None def set_state_changed_handler(self, handler): - self._state_changed_event += handler + self._state_changed_event += lambda *args: invoke_in_main_thread(handler, *args) def set_transfer_finished_handler(self, handler): - self._finished_event += handler + self._finished_event += lambda *args: invoke_in_main_thread(handler, *args) def get_file_number(self): return self._file_number @@ -100,17 +100,17 @@ class FileTransfer: def cancelled(self): if self._file is not None: self._file.close() - self.state = FILE_TRANSFER_STATE['CANCELLED'] + self.set_state(FILE_TRANSFER_STATE['CANCELLED']) def pause(self, by_friend): if not by_friend: self.send_control(TOX_FILE_CONTROL['PAUSE']) else: - self.state = FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'] + self.set_state(FILE_TRANSFER_STATE['PAUSED_BY_FRIEND']) def send_control(self, control): if self._tox.file_control(self._friend_number, self._file_number, control): - self.state = control + self.set_state(control) def get_file_id(self): return self._tox.file_get_file_id(self._friend_number, self._file_number) @@ -121,10 +121,10 @@ class FileTransfer: t = -1 else: t = ((time() - self._creation_time) / percentage) * (1 - percentage) - invoke_in_main_thread(self._state_changed_event, self.state, percentage, int(t)) + self._state_changed_event(self.state, percentage, int(t)) def _finished(self): - invoke_in_main_thread(self._finished_event, self._friend_number, self._file_number) + self._finished_event(self._friend_number, self._file_number) # ----------------------------------------------------------------------------------------------------------------- # Send file diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index 463152f..114383b 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -167,7 +167,7 @@ class FileTransfersHandler(ToxSave): return elif friend.status is None and is_resend: print('Error in sending') - raise RuntimeError() + 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) @@ -210,7 +210,7 @@ class FileTransfersHandler(ToxSave): else: self.send_file(path, friend_number, True) friend.clear_unsent_files() - for key in list(self._paused_file_transfers.keys()): + 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] @@ -221,14 +221,15 @@ class FileTransfersHandler(ToxSave): print('Exception in file sending: ' + str(ex)) def friend_exit(self, friend_number): - for friend_num, file_num in list(self._file_transfers.keys()): - if friend_num == friend_number: - ft = self._file_transfers[(friend_num, file_num)] - if type(ft) is SendTransfer: - self._paused_file_transfers[ft.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) + 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 @@ -267,7 +268,9 @@ class FileTransfersHandler(ToxSave): # ----------------------------------------------------------------------------------------------------------------- def _is_friend_online(self, friend_number): - return self._get_friend_by_number(friend_number).status is not None + 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) diff --git a/toxygen/history/history.py b/toxygen/history/history.py index f551df5..bd7e353 100644 --- a/toxygen/history/history.py +++ b/toxygen/history/history.py @@ -82,14 +82,15 @@ class History: messages.reverse() messages = messages[self._messages.count():self._messages.count() + PAGE_SIZE] for message in messages: - if message.get_type() in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']): # text message + message_type = message.get_type() + if message_type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']): # text message self._create_message_item(message) - elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: # file transfer - if message.get_status() is None: + elif message_type == MESSAGE_TYPE['FILE_TRANSFER']: # file transfer + if message.state == FILE_TRANSFER_STATE['UNSENT']: self._create_unsent_file_item(message) - continue - self._create_file_transfer_item(message) - elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline image + 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) diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index da94a7f..b9a4099 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -183,7 +183,7 @@ def tox_file_recv(window, tray, profile, file_transfer_handler, contacts_manager 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 + else: # avatar print('Avatar') invoke_in_main_thread(file_transfer_handler.incoming_avatar, friend_number, @@ -208,7 +208,7 @@ def file_chunk_request(file_transfer_handler): Outgoing chunk """ def wrapped(tox, friend_number, file_number, position, size, user_data): - invoke_in_main_thread(file_transfer_handler.outgoing_chunk, friend_number, file_number, position, size) + execute(file_transfer_handler.outgoing_chunk, friend_number, file_number, position, size) return wrapped @@ -440,7 +440,6 @@ def group_peer_join(contacts_provider, groups_service): 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) - contacts_manager.remove_group_peer_by_id(group, peer_id) group.remove_peer(peer_id) invoke_in_main_thread(groups_service.generate_peers_list) diff --git a/toxygen/ui/items_factories.py b/toxygen/ui/items_factories.py index df6c95d..7346f8f 100644 --- a/toxygen/ui/items_factories.py +++ b/toxygen/ui/items_factories.py @@ -45,9 +45,9 @@ class MessagesItemsFactory: return item - def create_inline_item(self, data, append=True, position=0): + def create_inline_item(self, message, append=True, position=0): elem = QtWidgets.QListWidgetItem() - item = InlineImageItem(data, self._messages.width(), elem, self._messages) + 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) From 62c6dbfb34223a3ccfb7b08a7cbd1f408fb3504d Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 29 Sep 2018 16:50:17 +0300 Subject: [PATCH 138/138] build.sh and docs update --- build/build.sh | 3 +++ docs/compile.md | 18 +++++++++++++----- docs/contact.md | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/build/build.sh b/build/build.sh index 522d6b2..fb6c4b2 100644 --- a/build/build.sh +++ b/build/build.sh @@ -7,6 +7,7 @@ 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/ @@ -14,8 +15,10 @@ 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 diff --git a/docs/compile.md b/docs/compile.md index 995dc35..b4f6810 100644 --- a/docs/compile.md +++ b/docs/compile.md @@ -2,10 +2,18 @@ You can compile Toxygen using [PyInstaller](http://www.pyinstaller.org/) -Install PyInstaller: -``pip3 install pyinstaller`` +Use Dockerfile and build script from `build` directory: -Compile Toxygen: -``pyinstaller --windowed --icon images/icon.ico main.py`` +1. Build image: +``` +docker build -t toxygen . +``` -Don't forget to copy /images/, /sounds/, /translations/, /styles/, /smileys/, /stickers/, /plugins/ (and /libs/libtox.dll, /libs/libsodium.a on Windows) to /dist/main/ +2. Run container: +``` +docker run -it toxygen bash +``` + +3. Execute `build.sh` script: + +```./build.sh``` diff --git a/docs/contact.md b/docs/contact.md index c66da1c..9f80595 100644 --- a/docs/contact.md +++ b/docs/contact.md @@ -2,4 +2,4 @@ 1) Using GitHub - open issue -2) Use Toxygen Tox Group - add bot kalina@toxme.io (or 12EDB939AA529641CE53830B518D6EB30241868EE0E5023C46A372363CAEC91C2C948AEFE4EB) +2) Use Toxygen Tox Group (NGC) - ID: 59D68B2709E81A679CF91416CB0E3692851C6CFCABEFF98B7131E3805A6D75FA