diff --git a/toxygen/app.py b/toxygen/app.py index 8dd4ea8..423ae5f 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -20,6 +20,9 @@ from contacts.profile import Profile from file_transfers.file_transfers_handler import FileTransfersHandler from contacts.contact_provider import ContactProvider from contacts.friend_factory import FriendFactory +from contacts.contacts_manager import ContactsManager +from av.calls_manager import CallsManager +from history.database import Database class App: @@ -29,6 +32,7 @@ class App: self._app = self._settings = self._profile_manager = self._plugin_loader = None self._tox = self._ms = self._init = self._main_loop = self._av_loop = None self._uri = self._toxes = self._tray = self._file_transfer_handler = self._contacts_provider = None + self._friend_factory = self._calls_manager = self._contacts_manager = None if uri is not None and uri.startswith('tox:'): self._uri = uri[4:] self._path = path_to_profile @@ -97,24 +101,9 @@ class App: if self.try_to_update(): return - self._ms = MainWindow(self._settings, self._tox, self.reset, self._tray) - self._friend_factory = FriendFactory(None, self._profile_manager, self._settings, self._tox) - self._contacts_provider = ContactProvider(self._tox, self._friend_factory) - self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider) - profile = Profile(self._profile_manager, self._tox, self._ms, self._file_transfer_handler) - self._ms.profile = profile - self._ms.show() - - self._tray = tray.init_tray(profile, self._settings, self._ms) - self._tray.show() - - self._plugin_loader = PluginLoader(self._tox, self._toxes, profile, self._settings) # plugins support + self.create_dependencies() self.start_threads() - # callbacks initialization - callbacks.init_callbacks(self._tox, profile, self._settings, self._plugin_loader, None, None, None, - self._ms, self._tray) - if self._uri is not None: self._ms.add_contact(self._uri) @@ -131,6 +120,9 @@ class App: else: break + self.stop_app() + + def stop_app(self): self._plugin_loader.stop() self.stop_threads() self._tray.hide() @@ -155,6 +147,28 @@ class App: return self._tox + def create_dependencies(self): + self._ms = MainWindow(self._settings, self._tox, self.reset, self._tray) + db = Database(self._path.replace('.tox', '.db'), self._toxes) + self._friend_factory = FriendFactory(self._profile_manager, self._settings, self._tox, db) + self._contacts_provider = ContactProvider(self._tox, self._friend_factory) + self._contacts_manager = ContactsManager(self._tox, self._settings, self._ms, self._profile_manager, + self._contacts_provider, db) + self._calls_manager = CallsManager(self._tox.AV, self._settings) + self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider) + profile = Profile(self._profile_manager, self._tox, self._ms, self._file_transfer_handler) + self._ms.profile = profile + self._ms.show() + + self._tray = tray.init_tray(profile, self._settings, self._ms) + self._tray.show() + + self._plugin_loader = PluginLoader(self._tox, self._toxes, profile, self._settings) # plugins support + + # callbacks initialization + callbacks.init_callbacks(self._tox, profile, self._settings, self._plugin_loader, self._contacts_manager, + self._calls_manager, self._file_transfer_handler, self._ms, self._tray) + def load_app_styles(self): # application color scheme for theme in self._settings.built_in_themes().keys(): diff --git a/toxygen/av/calls_manager.py b/toxygen/av/calls_manager.py index 8060881..03d5c53 100644 --- a/toxygen/av/calls_manager.py +++ b/toxygen/av/calls_manager.py @@ -9,8 +9,8 @@ from ui import av_widgets class CallsManager: - def __init__(self, tox, settings): - self._call = av.calls.AV(tox.AV) # object with data about calls + def __init__(self, toxAV, settings): + self._call = av.calls.AV(toxAV, settings) # object with data about calls self._call_widgets = {} # dict of incoming call widgets self._incoming_calls = set() self._settings = settings diff --git a/toxygen/contacts/contact_provider.py b/toxygen/contacts/contact_provider.py index 10b778b..52da937 100644 --- a/toxygen/contacts/contact_provider.py +++ b/toxygen/contacts/contact_provider.py @@ -8,6 +8,20 @@ class ContactProvider(util.ToxSave): self._friend_factory = friend_factory self._cache = {} # key - contact's public key, value - contact instance + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _get_contact_from_cache(self, public_key): + return self._cache[public_key] if public_key in self._cache else None + + def _add_to_cache(self, public_key, contact): + self._cache[public_key] = contact + + # ----------------------------------------------------------------------------------------------------------------- + # Friends + # ----------------------------------------------------------------------------------------------------------------- + def get_friend_by_number(self, friend_number): public_key = self._tox.friend_get_public_key(friend_number) @@ -22,17 +36,39 @@ class ContactProvider(util.ToxSave): return friend + def get_all_friends(self): + friend_numbers = self._tox.self_get_friend_list() + friends = map(lambda n: self.get_friend_by_number(n), friend_numbers) + + return list(friends) + + # ----------------------------------------------------------------------------------------------------------------- + # GC + # ----------------------------------------------------------------------------------------------------------------- + + def get_all_gc(self): + return [] + def get_gc_by_number(self): pass def get_gc_by_public_key(self): pass + # ----------------------------------------------------------------------------------------------------------------- + # All contacts + # ----------------------------------------------------------------------------------------------------------------- + + def get_all(self): + return self.get_all_friends() + self.get_all_gc() + + # ----------------------------------------------------------------------------------------------------------------- + # Caching + # ----------------------------------------------------------------------------------------------------------------- + def clear_cache(self): self._cache.clear() - def _get_contact_from_cache(self, public_key): - return self._cache[public_key] if public_key in self._cache else None - - def _add_to_cache(self, public_key, contact): - self._cache[public_key] = contact + def remove_friend_from_cache(self, friend_public_key): + if friend_public_key in self._cache: + del self._cache[friend_public_key] diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index 122db5c..510dbb3 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -6,31 +6,40 @@ from PyQt5 import QtCore, QtGui, QtWidgets from messenger.messages import * from wrapper.toxcore_enums_and_consts import * from network.tox_dns import tox_dns +from history.history_loader import HistoryLoader class ContactsManager: - def __init__(self, tox, settings, screen, profile_manager): + def __init__(self, tox, settings, screen, profile_manager, contact_provider, db): self._tox = tox self._settings = settings self._screen = screen self._profile_manager = profile_manager + self._contact_provider = contact_provider self._messages = screen.messages - self._contacts, self._active_friend = [], -1 + self._contacts, self._active_contact = [], -1 self._sorting = settings['sorting'] self._filter_string = '' self._friend_item_height = 40 if settings['compact_mode'] else 70 screen.online_contacts.setCurrentIndex(int(self._sorting)) - self.load_contacts() + self._history = HistoryLoader(contact_provider, db, settings) + self._load_contacts() - def load_contacts(self): - self.load_friends() - self.load_groups() - if len(self._contacts): - self.set_active(0) + def __del__(self): + del self._history + + def _is_active_a_friend(self): + return type(self.get_curr_contact()) is Friend + + def _load_contacts(self): + self._load_friends() + self._load_groups() + # if len(self._contacts): + # self.set_active(0) self.filtration_and_sorting(self._sorting) - def load_friends(self): + def _load_friends(self): aliases = self._settings['friends_aliases'] friend_numbers = self._tox.self_get_friend_list() for friend_number in friend_numbers: # creates list of friends @@ -41,15 +50,14 @@ class ContactsManager: alias = '' item = self.create_friend_item() name = alias or self._tox.friend_get_name(friend_number) or tox_id - status_message = self._tox.friend_get_status_message(i) - if not self._history.friend_exists_in_db(tox_id): - self._history.add_friend_to_db(tox_id) - message_getter = self._history.messages_getter(tox_id) + status_message = self._tox.friend_get_status_message(friend_number) + self._history.get_message_getter(tox_id) + message_getter = self._history.get_message_getter(tox_id) friend = Friend(self._profile_manager, message_getter, friend_number, name, status_message, item, tox_id) friend.set_alias(alias) self._contacts.append(friend) - def load_groups(self): + def _load_groups(self): pass def get_friend(self, num): @@ -57,8 +65,8 @@ class ContactsManager: return None return self._contacts[num] - def get_curr_friend(self): - return self._contacts[self._active_friend] if self._active_friend + 1 else None + def get_curr_contact(self): + return self._contacts[self._active_contact] if self._active_contact + 1 else None def save_profile(self): data = self._tox.get_savedata() @@ -69,20 +77,20 @@ class ContactsManager: # ----------------------------------------------------------------------------------------------------------------- def get_active(self): - return self._active_friend + return self._active_contact def set_active(self, value=None): """ Change current active friend or update info :param value: number of new active friend in friend's list or None to update active user's data """ - if value is None and self._active_friend == -1: # nothing to update + if value is None and self._active_contact == -1: # nothing to update return if value == -1: # all friends were deleted self._screen.account_name.setText('') self._screen.account_status.setText('') self._screen.account_status.setToolTip('') - self._active_friend = -1 + self._active_contact = -1 self._screen.account_avatar.setHidden(True) self._messages.clear() self._screen.messageEdit.clear() @@ -91,16 +99,16 @@ class ContactsManager: self.send_typing(False) self._screen.typing.setVisible(False) if value is not None: - if self._active_friend + 1 and self._active_friend != value: + if self._active_contact + 1 and self._active_contact != value: try: self.get_curr_friend().curr_text = self._screen.messageEdit.toPlainText() except: pass friend = self._contacts[value] friend.remove_invalid_unsent_files() - if self._active_friend != value: + if self._active_contact != value: self._screen.messageEdit.setPlainText(friend.curr_text) - self._active_friend = value + self._active_contact = value friend.reset_messages() if not self._settings['save_history']: friend.delete_old_messages() @@ -166,14 +174,14 @@ class ContactsManager: for i in range(len(self._contacts)): c = self._contacts[i] if c.number == number and (type(c) is Friend == is_friend): - self._active_friend = i + self._active_contact = i break active_friend = property(get_active, set_active) def update(self): - if self._active_friend + 1: - self.set_active(self._active_friend) + if self._active_contact + 1: + self.set_active(self._active_contact) # ----------------------------------------------------------------------------------------------------------------- # Filtration @@ -187,7 +195,7 @@ class ContactsManager: """ filter_str = filter_str.lower() number = self.get_active_number() - is_friend = self.is_active_a_friend() + is_friend = self._is_active_a_friend() if sorting > 1: if sorting & 2: self._contacts = sorted(self._contacts, key=lambda x: int(x.status is not None), reverse=True) @@ -216,10 +224,10 @@ class ContactsManager: for index, friend in enumerate(self._contacts): friend.visibility = (friend.status is not None or not (sorting & 1)) and (filter_str in friend.name.lower()) friend.visibility = friend.visibility or friend.messages or friend.actions - if friend.visibility: - self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, self._friend_item_height)) - else: - self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, 0)) + # if friend.visibility: + # self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, self._friend_item_height)) + # else: + # self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, 0)) self._sorting, self._filter_string = sorting, filter_str self._settings['sorting'] = self._sorting self._settings.save() @@ -236,7 +244,7 @@ class ContactsManager: Method-factory :return: new widget for friend instance """ - return self._factory.friend_item() + return None #self._factory.friend_item() # ----------------------------------------------------------------------------------------------------------------- # Friend getters @@ -246,19 +254,19 @@ class ContactsManager: return list(filter(lambda x: x.number == num and type(x) is Friend, self._contacts))[0] def get_last_message(self): - if self._active_friend + 1: + if self._active_contact + 1: return self.get_curr_friend().get_last_message_text() else: return '' def get_active_number(self): - return self.get_curr_friend().number if self._active_friend + 1 else -1 + return self.get_curr_friend().number if self._active_contact + 1 else -1 def get_active_name(self): - return self.get_curr_friend().name if self._active_friend + 1 else '' + return self.get_curr_friend().name if self._active_contact + 1 else '' def is_active_online(self): - return self._active_friend + 1 and self.get_curr_friend().status is not None + return self._active_contact + 1 and self.get_curr_friend().status is not None def new_name(self, number, name): friend = self.get_friend_by_number(number) @@ -333,7 +341,7 @@ class ContactsManager: self._tox.friend_delete(friend.number) del self._contacts[num] self._screen.friends_list.takeItem(num) - if num == self._active_friend: # active friend was deleted + if num == self._active_contact: # active friend was deleted if not len(self._contacts): # last friend was deleted self.set_active(-1) else: @@ -449,7 +457,7 @@ class ContactsManager: """ Send typing notification to a friend """ - if self._settings['typing_notifications'] and self._active_friend + 1: + if self._settings['typing_notifications'] and self._active_contact + 1: try: friend = self.get_curr_friend() if friend.status is not None: diff --git a/toxygen/contacts/friend_factory.py b/toxygen/contacts/friend_factory.py index 556f2d4..23966f8 100644 --- a/toxygen/contacts/friend_factory.py +++ b/toxygen/contacts/friend_factory.py @@ -3,9 +3,10 @@ from contacts.friend import Friend class FriendFactory: - def __init__(self, history, profile_manager, settings, tox): - self._history, self._profile_manager = history, profile_manager + def __init__(self, profile_manager, settings, tox, db): + self._profile_manager = profile_manager self._settings, self._tox = settings, tox + self._db = db def create_friend_by_number(self, friend_number): aliases = self._settings['friends_aliases'] @@ -17,9 +18,7 @@ class FriendFactory: item = self.create_friend_item() name = alias or self._tox.friend_get_name(friend_number) or tox_id status_message = self._tox.friend_get_status_message(friend_number) - if not self._history.friend_exists_in_db(tox_id): - self._history.add_friend_to_db(tox_id) - message_getter = self._history.messages_getter(tox_id) + message_getter = self._db.messages_getter(tox_id) friend = Friend(self._profile_manager, message_getter, friend_number, name, status_message, item, tox_id) friend.set_alias(alias) diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 0bfc764..70a6f7e 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -16,6 +16,7 @@ import cv2 import threading from contacts.group_chat import * import re +import util.ui as util_ui class Profile(basecontact.BaseContact): @@ -69,7 +70,7 @@ class Profile(basecontact.BaseContact): tmp = self.name super(Profile, self).set_name(value.encode('utf-8')) self._tox.self_set_name(self._name.encode('utf-8')) - message = QtWidgets.QApplication.translate("MainWindow", 'User {} is now known as {}') + message = util_ui.tr('User {} is now known as {}') message = message.format(tmp, value) for friend in self._contacts: friend.append_message(InfoMessage(message, time.time())) @@ -290,14 +291,9 @@ class Profile(basecontact.BaseContact): """ for friend in self._contacts: self.friend_exit(friend.number) - self._call.stop() - del self._call del self._tox self._tox = restart() - self._call = calls.AV(self._tox.AV) self.status = None - for friend in self._contacts: - friend.number = self._tox.friend_by_public_key(friend.tox_id) # numbers update self._contacts_manager.update_filtration() def reconnect(self): @@ -315,9 +311,6 @@ class Profile(basecontact.BaseContact): if hasattr(self, '_call'): self._call.stop() del self._call - s = Settings.get_instance() - s['paused_file_transfers'] = dict(self._paused_file_transfers) if s['resend_files'] else {} - s.save() def reset_avatar(self): super().reset_avatar() diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index 11c47c5..826adc0 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -1,6 +1,6 @@ from file_transfers.file_transfers import * from messenger.messages import * -from history.database import MESSAGE_OWNER +from history.database import MESSAGE_AUTHOR import os import util.util as util @@ -15,6 +15,13 @@ class FileTransfersHandler: # key = (friend number, file number), value - transfer instance self._paused_file_transfers = dict(settings['paused_file_transfers']) # key - file id, value: [path, friend number, is incoming, start position] + + def __del__(self): + self._settings['paused_file_transfers'] = self._paused_file_transfers if self._settings['resend_files'] else {} + self._settings.save() + + def _get_friend_by_number(self, friend_number): + return self._contact_provider.get_friend_by_number(friend_number) # ----------------------------------------------------------------------------------------------------------------- # File transfers support @@ -41,7 +48,7 @@ class FileTransfersHandler: return self._tox.file_seek(friend_number, file_number, pos) self.accept_transfer(None, data[0], friend_number, file_number, size, False, pos) - tm = TransferMessage(MESSAGE_OWNER['FRIEND'], + tm = TransferMessage(MESSAGE_AUTHOR['FRIEND'], time.time(), TOX_FILE_TRANSFER_STATE['RUNNING'], size, @@ -50,7 +57,7 @@ class FileTransfersHandler: file_number) elif inline and size < 1024 * 1024: self.accept_transfer(None, '', friend_number, file_number, size, True) - tm = TransferMessage(MESSAGE_OWNER['FRIEND'], + tm = TransferMessage(MESSAGE_AUTHOR['FRIEND'], time.time(), TOX_FILE_TRANSFER_STATE['RUNNING'], size, @@ -61,7 +68,7 @@ class FileTransfersHandler: elif auto: path = self._settings['auto_accept_path'] or util.curr_directory() self.accept_transfer(None, path + '/' + file_name, friend_number, file_number, size) - tm = TransferMessage(MESSAGE_OWNER['FRIEND'], + tm = TransferMessage(MESSAGE_AUTHOR['FRIEND'], time.time(), TOX_FILE_TRANSFER_STATE['RUNNING'], size, @@ -69,7 +76,7 @@ class FileTransfersHandler: friend_number, file_number) else: - tm = TransferMessage(MESSAGE_OWNER['FRIEND'], + tm = TransferMessage(MESSAGE_AUTHOR['FRIEND'], time.time(), TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED'], size, @@ -180,7 +187,6 @@ class FileTransfersHandler: :param data: raw data - png """ self.send_inline(data, 'toxygen_inline.png') - self._messages.repaint() def send_sticker(self, path): with open(path, 'rb') as fl: @@ -200,7 +206,7 @@ class FileTransfersHandler: st = SendFromBuffer(self._tox, friend.number, data, file_name) st.set_transfer_finished_handler(self.transfer_finished) self._file_transfers[(friend.number, st.get_file_number())] = st - tm = TransferMessage(MESSAGE_OWNER['ME'], + tm = TransferMessage(MESSAGE_AUTHOR['ME'], time.time(), TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'], len(data), @@ -233,7 +239,7 @@ class FileTransfersHandler: st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id) st.set_transfer_finished_handler(self.transfer_finished) self._file_transfers[(friend_number, st.get_file_number())] = st - tm = TransferMessage(MESSAGE_OWNER['ME'], + tm = TransferMessage(MESSAGE_AUTHOR['ME'], time.time(), TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'], os.path.getsize(path), @@ -293,6 +299,7 @@ class FileTransfersHandler: def send_avatar(self, friend_number, avatar_path=None): """ :param friend_number: number of friend who should get new avatar + :param avatar_path: path to avatar or None if reset """ sa = SendAvatar(avatar_path, self._tox, friend_number) self._file_transfers[(friend_number, sa.get_file_number())] = sa @@ -312,6 +319,3 @@ class FileTransfersHandler: self.get_friend_by_number(friend_number).load_avatar() if self.get_active_number() == friend_number and self.is_active_a_friend(): self.set_active(None) - - def _get_friend_by_number(self, friend_number): - return self._contact_provider.get_friend_by_number(friend_number) diff --git a/toxygen/history/database.py b/toxygen/history/database.py index 9960b54..d7c10eb 100644 --- a/toxygen/history/database.py +++ b/toxygen/history/database.py @@ -1,32 +1,31 @@ from sqlite3 import connect -from user_data import settings -from os import chdir import os.path +import util.util as util -PAGE_SIZE = 42 - TIMEOUT = 11 SAVE_MESSAGES = 500 -MESSAGE_OWNER = { +MESSAGE_AUTHOR = { 'ME': 0, 'FRIEND': 1, 'NOT_SENT': 2, 'GC_PEER': 3 } -# TODO: unique message id and ngc support, profile name as db name +CONTACT_TYPE = { + 'FRIEND': 0, + 'GC_PEER': 1, + 'GC_PEER_PRIVATE': 2 +} class Database: - def __init__(self, name, toxes): - self._name = name - self._toxes = toxes - chdir(settings.ProfileManager.get_path()) - path = settings.ProfileManager.get_path() + self._name + '.hstr' + def __init__(self, path, toxes): + self._path, self._toxes = path, toxes + self._name = os.path.basename(path) if os.path.exists(path): try: with open(path, 'rb') as fin: @@ -35,28 +34,35 @@ class Database: data = toxes.pass_decrypt(data) with open(path, 'wb') as fout: fout.write(data) - except: + except Exception as ex: + util.log('Db reading error: ' + str(ex)) os.remove(path) - db = connect(name + '.hstr', timeout=TIMEOUT) + db = self._connect() cursor = db.cursor() - cursor.execute('CREATE TABLE IF NOT EXISTS friends(' - ' tox_id TEXT PRIMARY KEY' + cursor.execute('CREATE TABLE IF NOT EXISTS contacts (' + ' tox_id TEXT PRIMARY KEY,' + ' contact_type INTEGER' ')') db.close() + def _connect(self): + return connect(self._path, timeout=TIMEOUT) + + # ----------------------------------------------------------------------------------------------------------------- + # Public methods + # ----------------------------------------------------------------------------------------------------------------- + def save(self): if self._toxes.has_password(): - path = settings.ProfileManager.get_path() + self._name + '.hstr' - with open(path, 'rb') as fin: + with open(self._path, 'rb') as fin: data = fin.read() data = self._toxes.pass_encrypt(bytes(data)) - with open(path, 'wb') as fout: + with open(self._path, 'wb') as fout: fout.write(data) def export(self, directory): - path = settings.ProfileManager.get_path() + self._name + '.hstr' - new_path = directory + self._name + '.hstr' - with open(path, 'rb') as fin: + new_path = util.join_path(directory, self._name) + with open(self._path, 'rb') as fin: data = fin.read() if self._toxes.has_password(): data = self._toxes.pass_encrypt(data) @@ -64,15 +70,16 @@ class Database: fout.write(data) def add_friend_to_db(self, tox_id): - chdir(settings.ProfileManager.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) + db = self._connect() try: cursor = db.cursor() - cursor.execute('INSERT INTO friends VALUES (?);', (tox_id, )) + cursor.execute('INSERT INTO contacts VALUES (?);', (tox_id, )) cursor.execute('CREATE TABLE id' + tox_id + '(' ' id INTEGER PRIMARY KEY,' + ' message_id INTEGER,' + ' author_name TEXT,' ' message TEXT,' - ' owner INTEGER,' + ' author INTEGER,' ' unix_time REAL,' ' message_type INTEGER' ')') @@ -84,11 +91,10 @@ class Database: db.close() def delete_friend_from_db(self, tox_id): - chdir(settings.ProfileManager.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) + db = self._connect() try: cursor = db.cursor() - cursor.execute('DELETE FROM friends WHERE tox_id=?;', (tox_id, )) + cursor.execute('DELETE FROM contacts WHERE tox_id=?;', (tox_id, )) cursor.execute('DROP TABLE id' + tox_id + ';') db.commit() except: @@ -98,21 +104,20 @@ class Database: db.close() def friend_exists_in_db(self, tox_id): - chdir(settings.ProfileManager.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) + db = self._connect() cursor = db.cursor() - cursor.execute('SELECT 0 FROM friends WHERE tox_id=?', (tox_id, )) + cursor.execute('SELECT 1 FROM contacts WHERE tox_id=?', (tox_id, )) result = cursor.fetchone() db.close() return result is not None def save_messages_to_db(self, tox_id, messages_iter): - chdir(settings.ProfileManager.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) + db = self._connect() try: cursor = db.cursor() - cursor.executemany('INSERT INTO id' + tox_id + '(message, owner, unix_time, message_type) ' - 'VALUES (?, ?, ?, ?);', messages_iter) + cursor.executemany('INSERT INTO id' + tox_id + + '(message, message_id, author_name, author, unix_time, message_type) ' + + 'VALUES (?, ?, ?, ?, ?, ?);', messages_iter) db.commit() except: print('Database is locked!') @@ -120,13 +125,12 @@ class Database: finally: db.close() - def update_messages(self, tox_id, unsent_time): - chdir(settings.ProfileManager.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) + def update_messages(self, tox_id, message_id): + db = self._connect() try: cursor = db.cursor() - cursor.execute('UPDATE id' + tox_id + ' SET owner = 0 ' - 'WHERE unix_time < ' + str(unsent_time) + ' AND owner = 2;') + cursor.execute('UPDATE id' + tox_id + ' SET author = 0 ' + 'WHERE message_id = ' + str(message_id) + ' AND author = 2;') db.commit() except: print('Database is locked!') @@ -134,13 +138,11 @@ class Database: finally: db.close() - def delete_message(self, tox_id, message_id): - chdir(settings.ProfileManager.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) + def delete_message(self, tox_id, unique_id): + db = self._connect() try: cursor = db.cursor() - cursor.execute('DELETE FROM id' + tox_id + ' WHERE unix_time < ' + end + ' AND unix_time > ' + - start + ';') + cursor.execute('DELETE FROM id' + tox_id + ' WHERE id = ' + str(unique_id) + ';') db.commit() except: print('Database is locked!') @@ -149,8 +151,7 @@ class Database: db.close() def delete_messages(self, tox_id): - chdir(settings.ProfileManager.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) + db = self._connect() try: cursor = db.cursor() cursor.execute('DELETE FROM id' + tox_id + ';') @@ -162,46 +163,44 @@ class Database: db.close() def messages_getter(self, tox_id): - return Database.MessageGetter(self._name, tox_id) + return Database.MessageGetter(self._path, tox_id) + + # ----------------------------------------------------------------------------------------------------------------- + # Messages loading + # ----------------------------------------------------------------------------------------------------------------- class MessageGetter: - def __init__(self, name, tox_id): + def __init__(self, path, tox_id): self._count = 0 - self._name = name + self._path = path self._tox_id = tox_id self._db = self._cursor = None - def connect(self): - chdir(settings.ProfileManager.get_path()) - self._db = connect(self._name + '.hstr', timeout=TIMEOUT) + def _connect(self): + self._db = connect(self._path, timeout=TIMEOUT) self._cursor = self._db.cursor() - self._cursor.execute('SELECT message, owner, unix_time, message_type FROM id' + self._tox_id + - ' ORDER BY unix_time DESC;') + self._cursor.execute('SELECT id, message_id, message, author, unix_time, message_type FROM id' + + self._tox_id + ' ORDER BY unix_time DESC;') - def disconnect(self): + def _disconnect(self): self._db.close() def get_one(self): - self.connect() - self.skip() - data = self._cursor.fetchone() - self._count += 1 - self.disconnect() - return data + return self.get(1) def get_all(self): - self.connect() + self._connect() data = self._cursor.fetchall() - self.disconnect() + self._disconnect() self._count = len(data) return data def get(self, count): - self.connect() + self._connect() self.skip() data = self._cursor.fetchmany(count) - self.disconnect() + self._disconnect() self._count += len(data) return data diff --git a/toxygen/history/history_loader.py b/toxygen/history/history_loader.py index 86e61c5..801125b 100644 --- a/toxygen/history/history_loader.py +++ b/toxygen/history/history_loader.py @@ -1,11 +1,17 @@ from messenger.messages import * +# TODO: fix history loading and saving + class HistoryLoader: - def __init__(self, db, settings): + def __init__(self, contact_provider, db, settings): + self._contact_provider = contact_provider self._db = db self._settings = settings + + def __del__(self): + del self._db # ----------------------------------------------------------------------------------------------------------------- # History support @@ -15,22 +21,20 @@ class HistoryLoader: """ Save history to db """ - if hasattr(self, '_history'): - if self._settings['save_history']: - for friend in filter(lambda x: type(x) is Friend, self._contacts): - if not self._history.friend_exists_in_db(friend.tox_id): - self._history.add_friend_to_db(friend.tox_id) - if not self._settings['save_unsent_only']: - messages = friend.get_corr_for_saving() - else: - messages = friend.get_unsent_messages_for_saving() - self._history.delete_messages(friend.tox_id) - self._history.save_messages_to_db(friend.tox_id, messages) - unsent_messages = friend.get_unsent_messages() - unsent_time = unsent_messages[0].get_data()[2] if len(unsent_messages) else time.time() + 1 - self._history.update_messages(friend.tox_id, unsent_time) - self._history.save() - del self._history + if self._settings['save_db']: + for friend in self._contact_provider.get_all_friends(): + if not self._db.friend_exists_in_db(friend.tox_id): + self._db.add_friend_to_db(friend.tox_id) + if not self._settings['save_unsent_only']: + messages = friend.get_corr_for_saving() + else: + messages = friend.get_unsent_messages_for_saving() + self._db.delete_messages(friend.tox_id) + self._db.save_messages_to_db(friend.tox_id, messages) + unsent_messages = friend.get_unsent_messages() + # unsent_time = unsent_messages[0].get_data()[2] if len(unsent_messages) else time.time() + 1 + # self._db.update_messages(friend.tox_id, unsent_time) + self._db.save() def clear_history(self, friend, save_unsent=False): """ @@ -45,9 +49,9 @@ class HistoryLoader: """ Tries to load next part of messages """ - if not self._load_history: + if not self._load_db: return - self._load_history = False + self._load_db = False friend = self.get_curr_friend() friend.load_corr(False) data = friend.get_corr() @@ -84,9 +88,16 @@ class HistoryLoader: '', data[3], False) - self._load_history = True + self._load_db = True - def export_history(self, friend, as_text=True, _range=None): + def get_message_getter(self, friend_public_key): + if not self._db.friend_exists_in_db(friend_public_key): + self._db.add_friend_to_db(friend_public_key) + + return self._db.messages_getter(friend_public_key) + + @staticmethod + def export_history(friend, as_text=True, _range=None): if _range is None: friend.load_all_corr() corr = friend.get_corr() @@ -95,16 +106,28 @@ class HistoryLoader: else: corr = friend.get_corr()[_range[0]:] - generator = TextHistoryGenerator() if as_text else HtmlHistoryGenerator + generator = TextHistoryGenerator(corr) if as_text else HtmlHistoryGenerator(corr) - return generator.generate(corr) + return generator.generate() -class HtmlHistoryGenerator: +class HistoryLogsGenerator: - def generate(self, corr): + def __init__(self, history): + self._history = history + + def generate(self): + return str() + + +class HtmlHistoryGenerator(HistoryLogsGenerator): + + def __init__(self, history): + super().__init__(history) + + def generate(self): arr = [] - for message in corr: + for message in self._history: if type(message) is TextMessage: data = message.get_data() x = '[{}] {}: {}
' @@ -117,11 +140,14 @@ class HtmlHistoryGenerator: return s -class TextHistoryGenerator: +class TextHistoryGenerator(HistoryLogsGenerator): - def generate(self, corr): + def __init__(self, history): + super().__init__(history) + + def generate(self): arr = [] - for message in corr: + for message in self._history: if type(message) is TextMessage: data = message.get_data() x = '[{}] {}: {}\n' diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index e8abec7..023a2e7 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -1,4 +1,4 @@ -from history.database import MESSAGE_OWNER +from history.database import MESSAGE_AUTHOR MESSAGE_TYPE = { @@ -9,6 +9,8 @@ MESSAGE_TYPE = { 'INFO_MESSAGE': 4 } +PAGE_SIZE = 42 + class MessageAuthor: @@ -53,7 +55,7 @@ class Message: self._widget = None def mark_as_sent(self): - self._author.author_type = MESSAGE_OWNER['ME'] + self._author.author_type = MESSAGE_AUTHOR['ME'] def _create_widget(self): pass diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 926c7af..153f341 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -377,6 +377,10 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, :param tox: Tox instance :param profile: Profile instance :param settings: Settings instance + :param contacts_manager: ContactsManager instance + :param contacts_manager: ContactsManager instance + :param calls_manager: CallsManager instance + :param file_transfer_handler: FileTransferHandler instance :param plugin_loader: PluginLoader instance :param main_window: main window screen :param tray: tray (for notifications) diff --git a/toxygen/ui/av_widgets.py b/toxygen/ui/av_widgets.py index a783eca..676df78 100644 --- a/toxygen/ui/av_widgets.py +++ b/toxygen/ui/av_widgets.py @@ -11,7 +11,7 @@ from util.util import curr_directory class IncomingCallWidget(widgets.CenteredWidget): def __init__(self, friend_number, text, name): - super(IncomingCallWidget, self).__init__() + super().__init__() self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowStaysOnTopHint) self.resize(QtCore.QSize(500, 270)) self.avatar_label = QtWidgets.QLabel(self) diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 3852867..73f4d4e 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -360,7 +360,6 @@ class MainWindow(QtWidgets.QMainWindow): if self._saved: return self._saved = True - self.profile.save_history() self.profile.close() self._settings['x'] = self.geometry().x() self._settings['y'] = self.geometry().y() @@ -500,7 +499,7 @@ class MainWindow(QtWidgets.QMainWindow): def show_menu(self): if not hasattr(self, 'menu'): self.menu = DropdownMenu(self) - self.menu.setGeometry(QtCore.QRect(0 if Settings.get_instance()['mirror_mode'] else 270, + self.menu.setGeometry(QtCore.QRect(0 if self._settings['mirror_mode'] else 270, self.height() - 120, 180, 120)) diff --git a/toxygen/ui/main_screen_widgets.py b/toxygen/ui/main_screen_widgets.py index 064c745..fe66a68 100644 --- a/toxygen/ui/main_screen_widgets.py +++ b/toxygen/ui/main_screen_widgets.py @@ -2,14 +2,16 @@ from PyQt5 import QtCore, QtGui, QtWidgets from ui.widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit from contacts.profile import Profile import smileys +import urllib import util.util as util +import util.ui as util_ui class MessageArea(QtWidgets.QPlainTextEdit): """User types messages here""" def __init__(self, parent, form): - super(MessageArea, self).__init__(parent) + super().__init__(parent) self.parent = form self.setAcceptDrops(True) self.timer = QtCore.QTimer(self) @@ -76,15 +78,18 @@ class MessageArea(QtWidgets.QPlainTextEdit): self.insertPlainText(text) def parse_file_name(self, file_name): - import urllib if file_name.endswith('\r\n'): file_name = file_name[:-2] file_name = urllib.parse.unquote(file_name) - return file_name[8 if platform.system() == 'Windows' else 7:] + return file_name[8 if util.get_platform() == 'Windows' else 7:] class ScreenShotWindow(RubberBandWindow): + def __init__(self, file_transfer_handler, *args): + super().__init__(*args) + self._file_transfer_handler = file_transfer_handler + def closeEvent(self, *args): if self.parent.isHidden(): self.parent.show() @@ -104,7 +109,7 @@ class ScreenShotWindow(RubberBandWindow): buffer = QtCore.QBuffer(byte_array) buffer.open(QtCore.QIODevice.WriteOnly) p.save(buffer, 'PNG') - Profile.get_instance().send_screenshot(bytes(byte_array.data())) + self._file_transfer_handler.send_screenshot(bytes(byte_array.data())) self.close() @@ -113,11 +118,10 @@ class SmileyWindow(QtWidgets.QWidget): Smiley selection window """ - def __init__(self, parent): - super(SmileyWindow, self).__init__() + def __init__(self, parent, smiley_loader): + super().__init__(parent) self.setWindowFlags(QtCore.Qt.FramelessWindowHint) - inst = smileys.SmileyLoader.get_instance() - self.data = inst.get_smileys() + self.data = smiley_loader.get_smileys() count = len(self.data) if not count: self.close() @@ -172,18 +176,18 @@ class SmileyWindow(QtWidgets.QWidget): class MenuButton(QtWidgets.QPushButton): def __init__(self, parent, enter): - super(MenuButton, self).__init__(parent) + super().__init__(parent) self.enter = enter def enterEvent(self, event): self.enter() - super(MenuButton, self).enterEvent(event) + super().enterEvent(event) class DropdownMenu(QtWidgets.QWidget): def __init__(self, parent): - super(DropdownMenu, self).__init__(parent) + super().__init__(parent) self.installEventFilter(self) self.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.setMaximumSize(120, 120) @@ -245,7 +249,7 @@ class DropdownMenu(QtWidgets.QWidget): class StickerItem(QtWidgets.QWidget): def __init__(self, fl): - super(StickerItem, self).__init__() + super().__init__() self._image_label = QtWidgets.QLabel(self) self.path = fl self.pixmap = QtGui.QPixmap() @@ -260,7 +264,7 @@ class StickerWindow(QtWidgets.QWidget): """Sticker selection window""" def __init__(self, parent): - super(StickerWindow, self).__init__() + super().__init__() self.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.setMaximumSize(250, 200) self.setMinimumSize(250, 200) @@ -289,8 +293,9 @@ class StickerWindow(QtWidgets.QWidget): class WelcomeScreen(CenteredWidget): - def __init__(self): + def __init__(self, settings): super().__init__() + self._settings = settings self.setMaximumSize(250, 200) self.setMinimumSize(250, 200) self.center() @@ -300,51 +305,39 @@ class WelcomeScreen(CenteredWidget): self.text.setOpenExternalLinks(True) self.checkbox = QtWidgets.QCheckBox(self) self.checkbox.setGeometry(QtCore.QRect(5, 170, 240, 30)) - self.checkbox.setText(QtWidgets.QApplication.translate('WelcomeScreen', "Don't show again")) - self.setWindowTitle(QtWidgets.QApplication.translate('WelcomeScreen', 'Tip of the day')) + self.checkbox.setText(util_ui.tr( "Don't show again")) + self.setWindowTitle(util_ui.tr( 'Tip of the day')) import random num = random.randint(0, 10) if num == 0: - text = QtWidgets.QApplication.translate('WelcomeScreen', 'Press Esc if you want hide app to tray.') + text = util_ui.tr('Press Esc if you want hide app to tray.') elif num == 1: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Right click on screenshot button hides app to tray during screenshot.') + text = util_ui.tr('Right click on screenshot button hides app to tray during screenshot.') elif num == 2: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'You can use Tox over Tor. For more info read this post') + text = util_ui.tr('You can use Tox over Tor. For more info read this post') elif num == 3: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Use Settings -> Interface to customize interface.') + text = util_ui.tr('Use Settings -> Interface to customize interface.') elif num == 4: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.') + text = util_ui.tr('Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.') elif num == 5: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Since v0.1.3 Toxygen supports plugins. Read more') + text = util_ui.tr('Since v0.1.3 Toxygen supports plugins. Read more') elif num == 6: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.') + text = util_ui.tr('Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.') elif num == 7: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'New in Toxygen 0.4.1:
Downloading nodes from tox.chat
Bug fixes') + text = util_ui.tr('New in Toxygen 0.4.1:
Downloading nodes from tox.chat
Bug fixes') elif num == 8: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu') + text = util_ui.tr('Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu') elif num == 9: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Use right click on inline image to save it') + text = util_ui.tr( 'Use right click on inline image to save it') else: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.') + text = util_ui.tr('Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.') self.text.setHtml(text) self.checkbox.stateChanged.connect(self.not_show) QtCore.QTimer.singleShot(1000, self.show) def not_show(self): - from user_data import settings - s = settings.Settings.get_instance() - s['show_welcome_screen'] = False - s.save() + self._settings['show_welcome_screen'] = False + self._settings.save() class MainMenuButton(QtWidgets.QPushButton): @@ -417,7 +410,7 @@ class SearchScreen(QtWidgets.QWidget): self.retranslateUi() def retranslateUi(self): - self.search_text.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "Search")) + self.search_text.setPlaceholderText(util_ui.tr('Search')) def show(self): super().show() @@ -473,11 +466,4 @@ class SearchScreen(QtWidgets.QWidget): @staticmethod def not_found(text): - mbox = QtWidgets.QMessageBox() - mbox_text = QtWidgets.QApplication.translate("MainWindow", - 'Text "{}" was not found') - - mbox.setText(mbox_text.format(text)) - mbox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", - 'Not found')) - mbox.exec_() + util_ui.message_box(util_ui.tr('Text "{}" was not found').format(text), util_ui.tr('Not found')) diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py index 9dcf180..c509359 100644 --- a/toxygen/ui/menu.py +++ b/toxygen/ui/menu.py @@ -7,13 +7,15 @@ import pyaudio from user_data import toxes import plugin_support import updater +import util.ui as util_ui class AddContact(CenteredWidget): """Add contact form""" - def __init__(self, tox_id=''): - super(AddContact, self).__init__() + def __init__(self, contacts_manager, tox_id=''): + super().__init__() + self._contacts_manager = contacts_manager self.initUI(tox_id) self._adding = False self.setAttribute(QtCore.Qt.WA_DeleteOnClose) @@ -61,8 +63,10 @@ class AddContact(CenteredWidget): if self._adding: return self._adding = True - profile = Profile.get_instance() - send = profile.send_friend_request(self.tox_id.text().strip(), self.message_edit.toPlainText()) + tox_id = self.tox_id.text().strip() + if tox_id.startswith('tox:'): + tox_id = tox_id[4:] + send = self._contacts_manager.send_friend_request(tox_id, self.message_edit.toPlainText()) self._adding = False if send is True: # request was successful @@ -71,17 +75,18 @@ class AddContact(CenteredWidget): self.error_label.setText(send) def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate('AddContact', "Add contact")) - self.sendRequestButton.setText(QtWidgets.QApplication.translate("Form", "Send request")) - self.label.setText(QtWidgets.QApplication.translate('AddContact', "TOX ID:")) - self.message.setText(QtWidgets.QApplication.translate('AddContact', "Message:")) - self.tox_id.setPlaceholderText(QtWidgets.QApplication.translate('AddContact', "TOX ID or public key of contact")) + self.setWindowTitle(util_ui.tr('Add contact')) + self.sendRequestButton.setText(util_ui.tr('Send request')) + self.label.setText(util_ui.tr('TOX ID:')) + self.message.setText(util_ui.tr('Message:')) + self.tox_id.setPlaceholderText(util_ui.tr('TOX ID or public key of contact')) class ProfileSettings(CenteredWidget): """Form with profile settings such as name, status, TOX ID""" - def __init__(self): - super(ProfileSettings, self).__init__() + def __init__(self, profile): + super().__init__() + self._profile = profile self.initUI() self.center() @@ -91,13 +96,12 @@ class ProfileSettings(CenteredWidget): self.setMaximumSize(QtCore.QSize(700, 600)) self.nick = LineEdit(self) self.nick.setGeometry(QtCore.QRect(30, 60, 350, 27)) - profile = Profile.get_instance() - self.nick.setText(profile.name) + self.nick.setText(self._profile.name) self.status = QtWidgets.QComboBox(self) self.status.setGeometry(QtCore.QRect(400, 60, 200, 27)) self.status_message = LineEdit(self) self.status_message.setGeometry(QtCore.QRect(30, 130, 350, 27)) - self.status_message.setText(profile.status_message) + self.status_message.setText(self._profile.status_message) self.label = QtWidgets.QLabel(self) self.label.setGeometry(QtCore.QRect(40, 30, 91, 25)) font = QtGui.QFont() @@ -164,37 +168,37 @@ class ProfileSettings(CenteredWidget): self.auto = path + name == ProfileManager.get_path() + Settings.get_instance().name self.default.clicked.connect(self.auto_profile) self.retranslateUi() - if profile.status is not None: - self.status.setCurrentIndex(profile.status) + if self._profile.status is not None: + self.status.setCurrentIndex(self._profile.status) else: self.status.setVisible(False) QtCore.QMetaObject.connectSlotsByName(self) def retranslateUi(self): - self.export.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Export profile")) - self.setWindowTitle(QtWidgets.QApplication.translate("ProfileSettingsForm", "Profile settings")) - self.label.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Name:")) - self.label_2.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Status:")) - self.label_3.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "TOX ID:")) - self.copyId.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Copy TOX ID")) - self.new_avatar.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "New avatar")) - self.delete_avatar.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Reset avatar")) - self.new_nospam.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "New NoSpam")) - self.profilepass.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Profile password")) - self.password.setPlaceholderText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Password (at least 8 symbols)")) - self.confirm_password.setPlaceholderText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Confirm password")) - self.set_password.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Set password")) - self.not_match.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Passwords do not match")) - self.leave_blank.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Leaving blank will reset current password")) - self.warning.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "There is no way to recover lost passwords")) - self.status.addItem(QtWidgets.QApplication.translate("ProfileSettingsForm", "Online")) - self.status.addItem(QtWidgets.QApplication.translate("ProfileSettingsForm", "Away")) - self.status.addItem(QtWidgets.QApplication.translate("ProfileSettingsForm", "Busy")) - self.copy_pk.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Copy public key")) + self.export.setText(util_ui.tr("Export profile")) + self.setWindowTitle(util_ui.tr("Profile settings")) + self.label.setText(util_ui.tr("Name:")) + self.label_2.setText(util_ui.tr("Status:")) + self.label_3.setText(util_ui.tr("TOX ID:")) + self.copyId.setText(util_ui.tr("Copy TOX ID")) + self.new_avatar.setText(util_ui.tr("New avatar")) + self.delete_avatar.setText(util_ui.tr("Reset avatar")) + self.new_nospam.setText(util_ui.tr("New NoSpam")) + self.profilepass.setText(util_ui.tr("Profile password")) + self.password.setPlaceholderText(util_ui.tr("Password (at least 8 symbols)")) + self.confirm_password.setPlaceholderText(util_ui.tr("Confirm password")) + self.set_password.setText(util_ui.tr("Set password")) + self.not_match.setText(util_ui.tr("Passwords do not match")) + self.leave_blank.setText(util_ui.tr("Leaving blank will reset current password")) + self.warning.setText(util_ui.tr("There is no way to recover lost passwords")) + self.status.addItem(util_ui.tr("Online")) + self.status.addItem(util_ui.tr("Away")) + self.status.addItem(util_ui.tr("Busy")) + self.copy_pk.setText(util_ui.tr("Copy public key")) if self.auto: - self.default.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as not default profile")) + self.default.setText(util_ui.tr("Mark as not default profile")) else: - self.default.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as default profile")) + self.default.setText(util_ui.tr("Mark as default profile")) def auto_profile(self): if self.auto: @@ -203,10 +207,10 @@ class ProfileSettings(CenteredWidget): Settings.set_auto_profile(ProfileManager.get_path(), Settings.get_instance().name) self.auto = not self.auto if self.auto: - self.default.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as not default profile")) + self.default.setText(util_ui.tr("Mark as not default profile")) else: self.default.setText( - QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as default profile")) + util_ui.tr("Mark as default profile")) def new_password(self): if self.password.text() == self.confirm_password.text(): @@ -216,10 +220,10 @@ class ProfileSettings(CenteredWidget): self.close() else: self.not_match.setText( - QtWidgets.QApplication.translate("ProfileSettingsForm", "Password must be at least 8 symbols")) + util_ui.tr("Password must be at least 8 symbols")) self.not_match.setVisible(True) else: - self.not_match.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Passwords do not match")) + self.not_match.setText(util_ui.tr("Passwords do not match")) self.not_match.setVisible(True) def copy(self): @@ -244,10 +248,10 @@ class ProfileSettings(CenteredWidget): self.tox_id.setText(Profile.get_instance().new_nospam()) def reset_avatar(self): - Profile.get_instance().reset_avatar() + self._profile.reset_avatar() def set_avatar(self): - choose = QtWidgets.QApplication.translate("ProfileSettingsForm", "Choose avatar") + choose = util_ui.tr("Choose avatar") name = QtWidgets.QFileDialog.getOpenFileName(self, choose, None, 'Images (*.png)', options=QtWidgets.QFileDialog.DontUseNativeDialog) if name[0]: @@ -262,21 +266,15 @@ class ProfileSettings(CenteredWidget): Profile.get_instance().set_avatar(bytes(byte_array.data())) def export_profile(self): - directory = QtWidgets.QFileDialog.getExistingDirectory(self, '', curr_directory(), - QtWidgets.QFileDialog.DontUseNativeDialog) + '/' + directory = util_ui.directory_dialog() + '/' if directory != '/': - reply = QtWidgets.QMessageBox.question(None, - QtWidgets.QApplication.translate("ProfileSettingsForm", - 'Use new path'), - QtWidgets.QApplication.translate("ProfileSettingsForm", - 'Do you want to move your profile to this location?'), - QtWidgets.QMessageBox.Yes, - QtWidgets.QMessageBox.No) + reply = util_ui.question(util_ui.tr('Do you want to move your profile to this location?'), + util_ui.tr('Use new path')) settings = Settings.get_instance() settings.export(directory) profile = Profile.get_instance() profile.export_db(directory) - ProfileManager.get_instance().export_profile(directory, reply == QtWidgets.QMessageBox.Yes) + ProfileManager.get_instance().export_profile(directory, reply) def closeEvent(self, event): profile = Profile.get_instance() @@ -287,8 +285,9 @@ class ProfileSettings(CenteredWidget): class NetworkSettings(CenteredWidget): """Network settings form: UDP, Ipv6 and proxy""" - def __init__(self, reset): - super(NetworkSettings, self).__init__() + def __init__(self, settings, reset): + super().__init__() + self._settings = settings self.reset = reset self.initUI() self.center() @@ -323,35 +322,34 @@ class NetworkSettings(CenteredWidget): self.reconnect = QtWidgets.QPushButton(self) self.reconnect.setGeometry(QtCore.QRect(40, 230, 231, 30)) self.reconnect.clicked.connect(self.restart_core) - settings = Settings.get_instance() - self.ipv.setChecked(settings['ipv6_enabled']) - self.udp.setChecked(settings['udp_enabled']) - self.proxy.setChecked(settings['proxy_type']) - self.proxyip.setText(settings['proxy_host']) - self.proxyport.setText(str(settings['proxy_port'])) - self.http.setChecked(settings['proxy_type'] == 1) + self.ipv.setChecked(self._settings['ipv6_enabled']) + self.udp.setChecked(self._settings['udp_enabled']) + self.proxy.setChecked(self._settings['proxy_type']) + self.proxyip.setText(self._settings['proxy_host']) + self.proxyport.setText(str(self._settings['proxy_port'])) + self.http.setChecked(self._settings['proxy_type'] == 1) self.warning = QtWidgets.QLabel(self) self.warning.setGeometry(QtCore.QRect(5, 270, 290, 60)) self.warning.setStyleSheet('QLabel { color: #BC1C1C; }') self.nodes = QtWidgets.QCheckBox(self) self.nodes.setGeometry(QtCore.QRect(20, 350, 270, 22)) - self.nodes.setChecked(settings['download_nodes_list']) + self.nodes.setChecked(self._settings['download_nodes_list']) self.retranslateUi() self.proxy.stateChanged.connect(lambda x: self.activate()) self.activate() QtCore.QMetaObject.connectSlotsByName(self) def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate("NetworkSettings", "Network settings")) - self.ipv.setText(QtWidgets.QApplication.translate("Form", "IPv6")) - self.udp.setText(QtWidgets.QApplication.translate("Form", "UDP")) - self.proxy.setText(QtWidgets.QApplication.translate("Form", "Proxy")) - self.label.setText(QtWidgets.QApplication.translate("Form", "IP:")) - self.label_2.setText(QtWidgets.QApplication.translate("Form", "Port:")) - self.reconnect.setText(QtWidgets.QApplication.translate("NetworkSettings", "Restart TOX core")) - self.http.setText(QtWidgets.QApplication.translate("Form", "HTTP")) - self.nodes.setText(QtWidgets.QApplication.translate("Form", "Download nodes list from tox.chat")) - self.warning.setText(QtWidgets.QApplication.translate("Form", "WARNING:\nusing proxy with enabled UDP\ncan produce IP leak")) + self.setWindowTitle(util_ui.tr("Network settings")) + self.ipv.setText(util_ui.tr("IPv6")) + self.udp.setText(util_ui.tr("UDP")) + self.proxy.setText(util_ui.tr("Proxy")) + self.label.setText(util_ui.tr("IP:")) + self.label_2.setText(util_ui.tr("Port:")) + self.reconnect.setText(util_ui.tr("Restart TOX core")) + self.http.setText(util_ui.tr("HTTP")) + self.nodes.setText(util_ui.tr("Download nodes list from tox.chat")) + self.warning.setText(util_ui.tr("WARNING:\nusing proxy with enabled UDP\ncan produce IP leak")) def activate(self): bl = self.proxy.isChecked() @@ -361,14 +359,13 @@ class NetworkSettings(CenteredWidget): def restart_core(self): try: - settings = Settings.get_instance() - settings['ipv6_enabled'] = self.ipv.isChecked() - settings['udp_enabled'] = self.udp.isChecked() - settings['proxy_type'] = 2 - int(self.http.isChecked()) if self.proxy.isChecked() else 0 - settings['proxy_host'] = str(self.proxyip.text()) - settings['proxy_port'] = int(self.proxyport.text()) - settings['download_nodes_list'] = self.nodes.isChecked() - settings.save() + self._settings['ipv6_enabled'] = self.ipv.isChecked() + self._settings['udp_enabled'] = self.udp.isChecked() + self._settings['proxy_type'] = 2 - int(self.http.isChecked()) if self.proxy.isChecked() else 0 + self._settings['proxy_host'] = str(self.proxyip.text()) + self._settings['proxy_port'] = int(self.proxyport.text()) + self._settings['download_nodes_list'] = self.nodes.isChecked() + self._settings.save() # recreate tox instance Profile.get_instance().reset(self.reset) self.close() @@ -380,7 +377,7 @@ class PrivacySettings(CenteredWidget): """Privacy settings form: history, typing notifications""" def __init__(self): - super(PrivacySettings, self).__init__() + super().__init__() self.initUI() self.center() diff --git a/toxygen/ui/widgets.py b/toxygen/ui/widgets.py index aab027a..735ad39 100644 --- a/toxygen/ui/widgets.py +++ b/toxygen/ui/widgets.py @@ -79,7 +79,7 @@ class QRightClickButton(QtWidgets.QPushButton): class RubberBand(QtWidgets.QRubberBand): def __init__(self): - super(RubberBand, self).__init__(QtWidgets.QRubberBand.Rectangle, None) + super().__init__(QtWidgets.QRubberBand.Rectangle, None) self.setPalette(QtGui.QPalette(QtCore.Qt.transparent)) self.pen = QtGui.QPen(QtCore.Qt.blue, 4) self.pen.setStyle(QtCore.Qt.SolidLine) diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py new file mode 100644 index 0000000..60ae8c3 --- /dev/null +++ b/toxygen/ui/widgets_factory.py @@ -0,0 +1,27 @@ +from ui.main_screen_widgets import * +from ui.menu import * + + +class WidgetsFactory: + + def __init__(self, settings, profile, contacts_manager, file_transfer_handler, smiley_loader): + self._settings = settings + self._profile = profile + self._contacts_manager = contacts_manager + self._file_transfer_handler = file_transfer_handler + self._smiley_loader = smiley_loader + + def create_screenshot_window(self, *args): + return ScreenShotWindow(self._file_transfer_handler, *args) + + def create_smiley_window(self, parent): + return SmileyWindow(parent, self._smiley_loader) + + def create_welcome_window(self): + return WelcomeScreen(self._settings) + + def create_profile_settings_window(self): + return ProfileSettings(self._profile) + + def create_network_settings_window(self): + return NetworkSettings(self._settings, self._profile.reset) diff --git a/toxygen/util/ui.py b/toxygen/util/ui.py index 4b9e806..a117202 100644 --- a/toxygen/util/ui.py +++ b/toxygen/util/ui.py @@ -1,4 +1,5 @@ from PyQt5 import QtWidgets +import util.util as util def tr(s): @@ -25,4 +26,9 @@ def text_dialog(text, title='', default_value=''): return text, ok +def directory_dialog(caption=''): + return QtWidgets.QFileDialog.getExistingDirectory(None, caption, util.curr_directory(), + QtWidgets.QFileDialog.DontUseNativeDialog) + + # TODO: move all dialogs here