messenger created. callbacks fixes. contacts refactoring

This commit is contained in:
ingvar1995 2018-05-01 16:39:09 +03:00
parent ddf6cd8328
commit 6ebafbda44
17 changed files with 406 additions and 289 deletions

View file

@ -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()

View file

@ -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()

View file

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

View file

@ -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

View file

@ -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())

View file

@ -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

View file

@ -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()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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

View file

@ -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())

View file

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

View file

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

View file

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