From 6ebafbda447625cdc1fd82a0c3bddb3a085f7122 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 1 May 2018 16:39:09 +0300 Subject: [PATCH] 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)