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)