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):