diff --git a/toxygen/app.py b/toxygen/app.py index 7f789eb..b4c5d55 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -1,50 +1,60 @@ +import communication.callbacks import threads - +from PyQt5 import QtWidgets, QtGui, QtCore +import ui.password_screen as passwordscreen +from util.util import curr_directory, get_platform, get_images_directory, get_styles_directory, log +import updater.updater as updater +import os +from communication.tox_factory import tox_factory +import wrapper.libtox as libtox +import user_data.toxes +from user_data.settings import Settings +from ui.login_screen import LoginScreen +from user_data.profile_manager import ProfileManager class App: - def __init__(self, path_or_uri=None): + def __init__(self, path_to_profile=None, uri=None): self.tox = self.ms = self.init = self.app = self.tray = self.mainloop = self.avloop = None - if path_or_uri is None: - self.uri = self.path = None - elif path_or_uri.startswith('tox:'): - self.path = None - self.uri = path_or_uri[4:] - else: - self.path = path_or_uri - self.uri = None + self.uri = self.path = self.toxes = None + if uri is not None and uri.startswith('tox:'): + self.uri = uri[4:] + if path_to_profile is not None: + self.path = path_to_profile def enter_pass(self, data): """ Show password screen """ - tmp = [data] - p = PasswordScreen(toxes.ToxES.get_instance(), tmp) + p = passwordscreen.PasswordScreen(self.toxes, data) p.show() self.app.lastWindowClosed.connect(self.app.quit) self.app.exec_() - if tmp[0] == data: + result = p.result + if result is None: raise SystemExit() else: - return tmp[0] + return result def main(self): """ Main function of app. loads login screen if needed and starts main screen """ - app = QtWidgets.QApplication(sys.argv) - app.setWindowIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) + app = QtWidgets.QApplication([]) + icon_file = os.path.join(get_images_directory(), 'icon.png') + app.setWindowIcon(QtGui.QIcon(icon_file)) self.app = app - if platform.system() == 'Linux': + if get_platform() == 'Linux': QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) - with open(curr_directory() + '/styles/dark_style.qss') as fl: + with open(os.path.join(get_styles_directory(), 'dark_style.qss')) as fl: style = fl.read() app.setStyleSheet(style) - encrypt_save = toxes.ToxES() + encrypt_save = libtox.LibToxEncryptSave() + toxes = user_data.toxes.ToxES(encrypt_save) if self.path is not None: path = os.path.dirname(self.path) + '/' @@ -70,15 +80,13 @@ class App: ls = LoginScreen() ls.setWindowIconText("Toxygen") profiles = ProfileManager.find_profiles() - ls.update_select(map(lambda x: x[1], profiles)) - _login = self.Login(profiles) - ls.update_on_close(_login.login_screen_close) + ls.update_select(profiles) ls.show() app.exec_() - if not _login.t: + result = ls.result + if result is None: return - elif _login.t == 1: # create new profile - _login.name = _login.name.strip() + elif result.is_new_profile(): # create new profile name = _login.name if _login.name else 'toxygen_user' pr = map(lambda x: x[1], ProfileManager.find_profiles()) if name in list(pr): @@ -130,14 +138,14 @@ class App: settings['language'] = curr_lang settings.save() else: # load existing profile - path, name = _login.get_data() - if _login.default: - Settings.set_auto_profile(path, name) - data = ProfileManager(path, name).open_profile() - if encrypt_save.is_data_encrypted(data): + path = result.profile_path + if result.load_as_default: + Settings.set_auto_profile(path) + settings = Settings(toxes, path) + data = ProfileManager(settings, toxes, path).open_profile() + if toxes.is_data_encrypted(data): data = self.enter_pass(data) - settings = Settings(name) - self.tox = profile.tox_factory(data, settings) + self.tox = communication.tox_factory.tox_factory(data, settings) else: path, name = auto_profile data = ProfileManager(path, name).open_profile() @@ -264,4 +272,4 @@ class App: plugin_helper = PluginLoader.get_instance() plugin_helper.set_tox(self.tox) - return self.tox \ No newline at end of file + return self.tox diff --git a/toxygen/communication/callbacks.py b/toxygen/communication/callbacks.py index ac93173..0566325 100644 --- a/toxygen/communication/callbacks.py +++ b/toxygen/communication/callbacks.py @@ -5,10 +5,7 @@ from contacts.profile import Profile from wrapper.toxcore_enums_and_consts import * from wrapper.toxav_enums import * from wrapper.tox import bin_to_string -from plugin_support import PluginLoader -import queue -import threading -import util +from plugin_support.plugin_support import PluginLoader import cv2 import numpy as np from threads import invoke_in_main_thread, execute @@ -31,6 +28,7 @@ def self_connection_status(tox, profile): invoke_in_main_thread(profile.set_status, status) elif connection == TOX_CONNECTION['NONE']: invoke_in_main_thread(profile.set_status, None) + return wrapped @@ -132,41 +130,45 @@ def friend_request(contacts_manager): return wrapped -def friend_typing(tox, friend_number, typing, user_data): - invoke_in_main_thread(Profile.get_instance().friend_typing, friend_number, typing) +def friend_typing(contacts_manager): + def wrapped(tox, friend_number, typing, user_data): + invoke_in_main_thread(contacts_manager.friend_typing, friend_number, typing) + + return wrapped -def friend_read_receipt(tox, friend_number, message_id, user_data): - profile = Profile.get_instance() - profile.get_friend_by_number(friend_number).dec_receipt() - if friend_number == profile.get_active_number(): - invoke_in_main_thread(profile.receipt) +def friend_read_receipt(contacts_manager): + def wrapped(tox, friend_number, message_id, user_data): + contacts_manager.get_friend_by_number(friend_number).dec_receipt() + if friend_number == contacts_manager.get_active_number(): + invoke_in_main_thread(contacts_manager.receipt) + + return wrapped + # ----------------------------------------------------------------------------------------------------------------- # Callbacks - file transfers # ----------------------------------------------------------------------------------------------------------------- -def tox_file_recv(window, tray): +def tox_file_recv(window, tray, profile, file_transfer_handler, contacts_manager, settings): """ New incoming file """ def wrapped(tox, friend_number, file_number, file_type, size, file_name, file_name_size, user_data): - profile = Profile.get_instance() - settings = Settings.get_instance() if file_type == TOX_FILE_KIND['DATA']: print('File') try: file_name = str(file_name[:file_name_size], 'utf-8') except: file_name = 'toxygen_file' - invoke_in_main_thread(profile.incoming_file_transfer, + invoke_in_main_thread(file_transfer_handler.incoming_file_transfer, friend_number, file_number, size, file_name) 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: file_from = QtWidgets.QApplication.translate("Callback", "File from") invoke_in_main_thread(tray_notification, file_from + ' ' + friend.name, file_name, tray, window) @@ -175,7 +177,7 @@ def tox_file_recv(window, tray): invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png')) else: # AVATAR print('Avatar') - invoke_in_main_thread(profile.incoming_avatar, + invoke_in_main_thread(file_transfer_handler.incoming_avatar, friend_number, file_number, size) @@ -222,55 +224,69 @@ def file_recv_control(file_transfer_handler): # ----------------------------------------------------------------------------------------------------------------- -def lossless_packet(tox, friend_number, data, length, user_data): - """ - Incoming lossless packet - """ - data = data[:length] - plugin = PluginLoader.get_instance() - invoke_in_main_thread(plugin.callback_lossless, friend_number, data) +def lossless_packet(plugin_loader): + def wrapped(tox, friend_number, data, length, user_data): + """ + Incoming lossless packet + """ + data = data[:length] + invoke_in_main_thread(plugin_loader.callback_lossless, friend_number, data) + + return wrapped -def lossy_packet(tox, friend_number, data, length, user_data): - """ - Incoming lossy packet - """ - data = data[:length] - plugin = PluginLoader.get_instance() - invoke_in_main_thread(plugin.callback_lossy, friend_number, data) +def lossy_packet(plugin_loader): + def wrapped(tox, friend_number, data, length, user_data): + """ + Incoming lossy packet + """ + data = data[:length] + plugin = PluginLoader.get_instance() + invoke_in_main_thread(plugin.callback_lossy, friend_number, data) + + return wrapped # ----------------------------------------------------------------------------------------------------------------- # Callbacks - audio # ----------------------------------------------------------------------------------------------------------------- -def call_state(toxav, friend_number, mask, user_data): - """ - New call state - """ - print(friend_number, mask) - if mask == TOXAV_FRIEND_CALL_STATE['FINISHED'] or mask == TOXAV_FRIEND_CALL_STATE['ERROR']: - invoke_in_main_thread(Profile.get_instance().stop_call, friend_number, True) - else: - Profile.get_instance().call.toxav_call_state_cb(friend_number, mask) +def call_state(calls_manager): + def wrapped(toxav, friend_number, mask, user_data): + """ + New call state + """ + print(friend_number, mask) + if mask == TOXAV_FRIEND_CALL_STATE['FINISHED'] or mask == TOXAV_FRIEND_CALL_STATE['ERROR']: + invoke_in_main_thread(calls_manager.stop_call, friend_number, True) + else: + calls_manager.toxav_call_state_cb(friend_number, mask) + + return wrapped -def call(toxav, friend_number, audio, video, user_data): - """ - Incoming call from friend - """ - print(friend_number, audio, video) - invoke_in_main_thread(Profile.get_instance().incoming_call, audio, video, friend_number) +def call(calls_manager): + def wrapped(toxav, friend_number, audio, video, user_data): + """ + Incoming call from friend + """ + print(friend_number, audio, video) + invoke_in_main_thread(calls_manager.incoming_call, audio, video, friend_number) + + return wrapped -def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, audio_channels_count, rate, user_data): - """ - New audio chunk - """ - Profile.get_instance().call.audio_chunk( - bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]), - audio_channels_count, - rate) +def callback_audio(calls_manager): + def wrapped(toxav, friend_number, samples, audio_samples_per_channel, audio_channels_count, rate, user_data): + """ + New audio chunk + """ + calls_manager.call.audio_chunk( + bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]), + audio_channels_count, + rate) + + return wrapped # ----------------------------------------------------------------------------------------------------------------- # Callbacks - video @@ -333,11 +349,6 @@ def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, u # ----------------------------------------------------------------------------------------------------------------- -def group_invite(tox, friend_number, gc_type, data, length, user_data): - invoke_in_main_thread(Profile.get_instance().group_invite, friend_number, gc_type, - bytes(data[:length])) - - def show_gc_notification(window, tray, message, group_number, peer_number): profile = Profile.get_instance() settings = Settings.get_instance() @@ -350,72 +361,40 @@ def show_gc_notification(window, tray, message, group_number, peer_number): sound_notification(SOUND_NOTIFICATION['MESSAGE']) invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png')) - -def group_message(window, tray): - def wrapped(tox, group_number, peer_number, message, length, user_data): - message = str(message[:length], 'utf-8') - invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number, - peer_number, TOX_MESSAGE_TYPE['NORMAL'], message) - show_gc_notification(window, tray, message, group_number, peer_number) - return wrapped - - -def group_action(window, tray): - def wrapped(tox, group_number, peer_number, message, length, user_data): - message = str(message[:length], 'utf-8') - invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number, - peer_number, TOX_MESSAGE_TYPE['ACTION'], message) - show_gc_notification(window, tray, message, group_number, peer_number) - return wrapped - - -def group_title(tox, group_number, peer_number, title, length, user_data): - invoke_in_main_thread(Profile.get_instance().new_gc_title, group_number, - title[:length]) - - -def group_namelist_change(tox, group_number, peer_number, change, user_data): - invoke_in_main_thread(Profile.get_instance().update_gc, group_number) - # ----------------------------------------------------------------------------------------------------------------- # Callbacks - initialization # ----------------------------------------------------------------------------------------------------------------- -def init_callbacks(tox, window, tray): +def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, + calls_manager, file_transfer_handler, window, tray): """ Initialization of all callbacks. :param tox: tox instance :param window: main window :param tray: tray (for notifications) """ - tox.callback_self_connection_status(self_connection_status(tox), 0) + tox.callback_self_connection_status(self_connection_status(tox, profile), 0) - tox.callback_friend_status(friend_status, 0) - tox.callback_friend_message(friend_message(window, tray), 0) - tox.callback_friend_connection_status(friend_connection_status, 0) - tox.callback_friend_name(friend_name, 0) - tox.callback_friend_status_message(friend_status_message, 0) - tox.callback_friend_request(friend_request, 0) - tox.callback_friend_typing(friend_typing, 0) - tox.callback_friend_read_receipt(friend_read_receipt, 0) + tox.callback_friend_status(friend_status(profile, settings), 0) + tox.callback_friend_message(friend_message(profile, settings, 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_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) - tox.callback_file_recv(tox_file_recv(window, tray), 0) - tox.callback_file_recv_chunk(file_recv_chunk, 0) - tox.callback_file_chunk_request(file_chunk_request, 0) - tox.callback_file_recv_control(file_recv_control, 0) + tox.callback_file_recv(tox_file_recv(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) - toxav = tox.AV - toxav.callback_call_state(call_state, 0) - toxav.callback_call(call, 0) - toxav.callback_audio_receive_frame(callback_audio, 0) + 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) - tox.callback_friend_lossless_packet(lossless_packet, 0) - tox.callback_friend_lossy_packet(lossy_packet, 0) + tox.callback_friend_lossless_packet(lossless_packet(plugin_loader), 0) + tox.callback_friend_lossy_packet(lossy_packet(plugin_loader), 0) - tox.callback_group_invite(group_invite) - tox.callback_group_message(group_message(window, tray)) - tox.callback_group_action(group_action(window, tray)) - tox.callback_group_title(group_title) - tox.callback_group_namelist_change(group_namelist_change) diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 9d33f19..eb69044 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -3,10 +3,9 @@ from PyQt5 import QtWidgets from contacts.friend import * from user_data.settings import * from wrapper.toxcore_enums_and_consts import * -from ctypes import * -from util import log, Singleton, curr_directory +from util.util import log, curr_directory from network.tox_dns import tox_dns -from db.database import * +from history.database import * from file_transfers.file_transfers import * import time from av import calls @@ -19,7 +18,7 @@ from contacts.group_chat import * import re -class Profile(basecontact.BaseContact, Singleton): +class Profile(basecontact.BaseContact): """ Profile of current toxygen user. Contains friends list, tox instance """ @@ -33,7 +32,6 @@ class Profile(basecontact.BaseContact, Singleton): tox.self_get_status_message(), screen.user_info, tox.self_get_address()) - Singleton.__init__(self) self._screen = screen self._messages = screen.messages self._tox = tox @@ -43,8 +41,6 @@ class Profile(basecontact.BaseContact, Singleton): self._factory = items_factory.ItemsFactory(self._screen.friends_list, self._messages) settings = Settings.get_instance() self._show_avatars = settings['show_avatars'] - self._paused_file_transfers = dict(settings['paused_file_transfers']) - # key - file id, value: [path, friend number, is incoming, start position] # ----------------------------------------------------------------------------------------------------------------- @@ -97,6 +93,7 @@ class Profile(basecontact.BaseContact, Singleton): def get_friend_by_number(self, num): 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: return self.get_curr_friend().get_last_message_text() diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index 2706d97..6431fe4 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -9,6 +9,8 @@ class FileTransfersHandler: self._tox = tox self._settings = settings self._file_transfers = {} + self._paused_file_transfers = dict(settings['paused_file_transfers']) + # key - file id, value: [path, friend number, is incoming, start position] # ----------------------------------------------------------------------------------------------------------------- # File transfers support diff --git a/toxygen/main.py b/toxygen/main.py index e8a4694..42e8b76 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -1,9 +1,12 @@ import sys import app from user_data.settings import * -from util.util import curr_directory, program_version, remove +from util.util import curr_directory, remove import argparse +__maintainer__ = 'Ingvar' +__version__ = '0.5.0' + def clean(): """Removes all windows libs from libs folder""" @@ -15,30 +18,31 @@ def reset(): Settings.reset_auto_profile() +def print_toxygen_version(): + print('Toxygen v' + __version__) + + def main(): parser = argparse.ArgumentParser() - parser.add_argument('--version') - parser.add_argument('--clean') - parser.add_argument('--reset') + parser.add_argument('--version', help='Prints Toxygen version') + parser.add_argument('--clean', help='Deletes toxcore libs from libs folder') + parser.add_argument('--reset', help='Resets default profile') + parser.add_argument('profile_path', nargs='?', default=None, help='Resets default profile') args = parser.parse_args() - if not len(args): - toxygen = app.App() - else: # started with argument(s) - arg = sys.argv[1] - if arg == '--version': - print('Toxygen v' + program_version) - return - elif arg == '--help': - print('Usage:\ntoxygen path_to_profile\ntoxygen tox_id\ntoxygen --version\ntoxygen --reset') - return - elif arg == '--clean': - clean() - return - elif arg == '--reset': - reset() - return - else: - toxygen = app.App(arg) + + if args.version: + print_toxygen_version() + return + + if args.clean: + clean() + return + + if args.reset: + reset() + return + + toxygen = app.App(path_to_profile=args.profile_path) toxygen.main() diff --git a/toxygen/network/tox_dns.py b/toxygen/network/tox_dns.py index 36702ee..d3d48a1 100644 --- a/toxygen/network/tox_dns.py +++ b/toxygen/network/tox_dns.py @@ -1,6 +1,6 @@ import json import urllib.request -from util import log +from util.util import log from user_data import settings from PyQt5 import QtNetwork, QtCore diff --git a/toxygen/plugin_support/plugin_support.py b/toxygen/plugin_support/plugin_support.py index de39e5f..6f2af9c 100644 --- a/toxygen/plugin_support/plugin_support.py +++ b/toxygen/plugin_support/plugin_support.py @@ -1,4 +1,4 @@ -import util +import util.util as util from contacts import profile import os import importlib @@ -8,15 +8,15 @@ from user_data import toxes import sys -class PluginLoader(util.Singleton): +class PluginLoader(): - def __init__(self, tox, settings): + def __init__(self, tox, toxes, profile, settings): super().__init__() - self._profile = profile.Profile.get_instance() + self._profile = profile self._settings = settings self._plugins = {} # dict. key - plugin unique short name, value - tuple (plugin instance, is active) self._tox = tox - self._encr = toxes.ToxES.get_instance() + self._toxes = toxes def set_tox(self, tox): """ @@ -55,7 +55,7 @@ class PluginLoader(util.Singleton): if inspect.isclass(obj) and hasattr(obj, 'is_plugin') and obj.is_plugin: print('Plugin', elem) try: # create instance of plugin class - inst = obj(self._tox, self._profile, self._settings, self._encr) + inst = obj(self._tox, self._profile, self._settings, self._toxes) autostart = inst.get_short_name() in self._settings['plugins'] if autostart: inst.start() @@ -158,7 +158,7 @@ class PluginLoader(util.Singleton): try: result.extend(elem[0].get_message_menu(menu, selected_text)) except: - continue + pass return result def stop(self): diff --git a/toxygen/smileys_and_stickers.py b/toxygen/smileys_and_stickers.py index 7294185..dd72fd9 100644 --- a/toxygen/smileys_and_stickers.py +++ b/toxygen/smileys_and_stickers.py @@ -5,7 +5,7 @@ from collections import OrderedDict from PyQt5 import QtCore -class SmileyLoader(util.Singleton): +class SmileyLoader: """ Class which loads smileys packs and insert smileys into messages """ diff --git a/toxygen/threads.py b/toxygen/threads.py index f437d5a..4a129d1 100644 --- a/toxygen/threads.py +++ b/toxygen/threads.py @@ -1,11 +1,11 @@ from PyQt5 import QtCore -from communication.callbacks import init_callbacks from bootstrap.bootstrap import * import threading import queue from util import util + class InitThread(QtCore.QThread): def __init__(self, tox, ms, tray): diff --git a/toxygen/tray.py b/toxygen/tray.py index bc22ed4..0c474b4 100644 --- a/toxygen/tray.py +++ b/toxygen/tray.py @@ -3,6 +3,19 @@ from util.ui import tr from util.util import curr_directory +class SystemTrayIcon(QtWidgets.QSystemTrayIcon): + + leftClicked = QtCore.pyqtSignal() + + def __init__(self, icon, parent=None): + super().__init__(self, icon, parent) + self.activated.connect(self.iconActivated) + + def iconActivated(self, reason): + if reason == QtGui.QSystemTrayIcon.Trigger: + self.leftClicked.emit() + + class Menu(QtWidgets.QMenu): def __init__(self, settings, profile, *args): @@ -39,7 +52,7 @@ class Menu(QtWidgets.QMenu): def init_tray(profile, settings, main_screen): - tray = QtWidgets.QSystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) + tray = SystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) tray.setObjectName('tray') m = Menu(settings, profile) diff --git a/toxygen/ui/avwidgets.py b/toxygen/ui/avwidgets.py index 67bd04a..a783eca 100644 --- a/toxygen/ui/avwidgets.py +++ b/toxygen/ui/avwidgets.py @@ -5,7 +5,7 @@ import util import pyaudio import wave from user_data import settings -from util import curr_directory +from util.util import curr_directory class IncomingCallWidget(widgets.CenteredWidget): diff --git a/toxygen/ui/list_items.py b/toxygen/ui/list_items.py index 64b4a2e..76e6185 100644 --- a/toxygen/ui/list_items.py +++ b/toxygen/ui/list_items.py @@ -2,7 +2,7 @@ from wrapper.toxcore_enums_and_consts import * from PyQt5 import QtCore, QtGui, QtWidgets from contacts import profile from file_transfers.file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR -from util import curr_directory, convert_time, curr_time +from util.util import curr_directory, convert_time, curr_time from ui.widgets import DataLabel, create_menu import html as h import smileys @@ -60,8 +60,8 @@ class MessageEdit(QtWidgets.QTextBrowser): def quote_text(self): text = self.textCursor().selection().toPlainText() if text: - from ui import mainscreen - window = mainscreen.MainWindow.get_instance() + from ui import main_screen + window = main_screen.MainWindow.get_instance() text = '>' + '\n>'.join(text.split('\n')) if window.messageEdit.toPlainText(): text = '\n' + text diff --git a/toxygen/ui/loginscreen.py b/toxygen/ui/login_screen.py similarity index 69% rename from toxygen/ui/loginscreen.py rename to toxygen/ui/login_screen.py index cc1f45c..a5c91e1 100644 --- a/toxygen/ui/loginscreen.py +++ b/toxygen/ui/login_screen.py @@ -1,4 +1,5 @@ from ui.widgets import * +import os.path class NickEdit(LineEdit): @@ -14,12 +15,40 @@ class NickEdit(LineEdit): super(NickEdit, self).keyPressEvent(event) -class LoginScreen(CenteredWidget): +class LoginScreenResult: + + def __init__(self, profile_path, load_as_default, password=None): + self._profile_path = profile_path + self._load_as_default = load_as_default + self._password = password + + def get_profile_path(self): + return self._profile_path + + profile_path = property(get_profile_path) + + def get_load_as_default(self): + return self._load_as_default + + load_as_default = property(get_load_as_default) + + def get_password(self): + return self._password + + password = property(get_password) + + def is_new_profile(self): + return not os.path.isfile(self._profile_path) + + +class LoginScreen(CenteredWidget, DialogWithResult): def __init__(self): - super(LoginScreen, self).__init__() + CenteredWidget.__init__(self) + DialogWithResult.__init__(self) self.initUI() self.center() + self._profiles = [] def initUI(self): self.resize(400, 200) @@ -34,7 +63,7 @@ class LoginScreen(CenteredWidget): self.new_name.setGeometry(QtCore.QRect(20, 100, 171, 31)) self.load_profile = QtWidgets.QPushButton(self) self.load_profile.setGeometry(QtCore.QRect(220, 150, 161, 27)) - self.load_profile.clicked.connect(self.load_ex_profile) + self.load_profile.clicked.connect(self.load_existing_profile) self.default = QtWidgets.QCheckBox(self) self.default.setGeometry(QtCore.QRect(220, 110, 131, 22)) self.groupBox = QtWidgets.QGroupBox(self) @@ -44,6 +73,7 @@ class LoginScreen(CenteredWidget): self.groupBox_2 = QtWidgets.QGroupBox(self) self.groupBox_2.setGeometry(QtCore.QRect(10, 40, 191, 151)) self.toxygen = QtWidgets.QLabel(self) + self.toxygen.setGeometry(QtCore.QRect(160, 8, 90, 25)) self.groupBox.raise_() self.groupBox_2.raise_() self.comboBox.raise_() @@ -51,16 +81,11 @@ class LoginScreen(CenteredWidget): self.load_profile.raise_() self.new_name.raise_() self.new_profile.raise_() - self.toxygen.setGeometry(QtCore.QRect(160, 8, 90, 25)) font = QtGui.QFont() font.setFamily("Impact") font.setPointSize(16) self.toxygen.setFont(font) self.toxygen.setObjectName("toxygen") - self.type = 0 - self.number = -1 - self.load_as_default = False - self.name = None self.retranslateUi() QtCore.QMetaObject.connectSlotsByName(self) @@ -80,19 +105,18 @@ class LoginScreen(CenteredWidget): self.name = self.new_name.text() self.close() - def load_ex_profile(self): - if not self.create_only: - self.type = 2 - self.number = self.comboBox.currentIndex() - self.load_as_default = self.default.isChecked() - self.close() + def load_existing_profile(self): + index = self.comboBox.currentIndex() + load_as_default = self.default.isChecked() + path = os.path.join(self._profiles[index][0], self._profiles[index][1] + '.tox') + result = LoginScreenResult(path, load_as_default) + self.close_with_result(result) - def update_select(self, data): - list_of_profiles = [] - for elem in data: - list_of_profiles.append(elem) - self.comboBox.addItems(list_of_profiles) - self.create_only = not list_of_profiles + def update_select(self, profiles): + profiles = sorted(profiles, key=lambda p: p[1]) + self._profiles = list(profiles) + self.comboBox.addItems(list(map(lambda p: p[1], profiles))) + self.load_profile.setEnabled(len(profiles) > 0) def update_on_close(self, func): self.onclose = func diff --git a/toxygen/ui/mainscreen.py b/toxygen/ui/main_screen.py similarity index 99% rename from toxygen/ui/mainscreen.py rename to toxygen/ui/main_screen.py index ce828b0..c2912d6 100644 --- a/toxygen/ui/mainscreen.py +++ b/toxygen/ui/main_screen.py @@ -3,7 +3,7 @@ from contacts.profile import * from ui.list_items import * from ui.widgets import MultilineEdit, ComboBox import plugin_support -from ui.mainscreen_widgets import * +from ui.main_screen_widgets import * from user_data import toxes, settings diff --git a/toxygen/ui/mainscreen_widgets.py b/toxygen/ui/main_screen_widgets.py similarity index 100% rename from toxygen/ui/mainscreen_widgets.py rename to toxygen/ui/main_screen_widgets.py diff --git a/toxygen/ui/passwordscreen.py b/toxygen/ui/password_screen.py similarity index 95% rename from toxygen/ui/passwordscreen.py rename to toxygen/ui/password_screen.py index 9d3d523..bd6c146 100644 --- a/toxygen/ui/passwordscreen.py +++ b/toxygen/ui/password_screen.py @@ -1,4 +1,4 @@ -from ui.widgets import CenteredWidget, LineEdit +from ui.widgets import CenteredWidget, LineEdit, DialogWithResult from PyQt5 import QtCore, QtWidgets @@ -16,10 +16,11 @@ class PasswordArea(LineEdit): super(PasswordArea, self).keyPressEvent(event) -class PasswordScreenBase(CenteredWidget): +class PasswordScreenBase(CenteredWidget, DialogWithResult): def __init__(self, encrypt): - super(PasswordScreenBase, self).__init__() + CenteredWidget.__init__(self) + DialogWithResult.__init__(self) self._encrypt = encrypt self.initUI() @@ -73,13 +74,12 @@ class PasswordScreen(PasswordScreenBase): if self.password.text(): try: self._encrypt.set_password(self.password.text()) - new_data = self._encrypt.pass_decrypt(self._data[0]) + new_data = self._encrypt.pass_decrypt(self._data) except Exception as ex: self.warning.setVisible(True) print('Decryption error:', ex) else: - self._data[0] = new_data - self.close() + self.close_with_result(new_data) class UnlockAppScreen(PasswordScreenBase): diff --git a/toxygen/ui/widgets.py b/toxygen/ui/widgets.py index b63deb0..7b811e7 100644 --- a/toxygen/ui/widgets.py +++ b/toxygen/ui/widgets.py @@ -32,6 +32,22 @@ class CenteredWidget(QtWidgets.QWidget): self.move(qr.topLeft()) +class DialogWithResult(QtWidgets.QWidget): + + def __init__(self, parent=None): + super().__init__(parent) + self._result = None + + def get_result(self): + return self._result + + result = property(get_result) + + def close_with_result(self, result): + self._result = result + self.close() + + class LineEdit(QtWidgets.QLineEdit): def __init__(self, parent=None): diff --git a/toxygen/user_data/profile_manager.py b/toxygen/user_data/profile_manager.py index 12ce185..a810105 100644 --- a/toxygen/user_data/profile_manager.py +++ b/toxygen/user_data/profile_manager.py @@ -1,11 +1,17 @@ +import util.util as util +import os +from user_data.settings import Settings + + class ProfileManager: """ Class with methods for search, load and save profiles """ - def __init__(self, path, name): - path = append_slash(path) - self._path = path + name + '.tox' - self._directory = path + def __init__(self, settings, toxes, path): + self._settings = settings + self._toxes = toxes + self._path = path + self._directory = os.path.basename(path) # create /avatars if not exists: directory = path + 'avatars' if not os.path.exists(directory): @@ -23,9 +29,8 @@ class ProfileManager: return self._directory def save_profile(self, data): - inst = ToxES.get_instance() - if inst.has_password(): - data = inst.pass_encrypt(data) + if self._toxes.has_password(): + data = self._toxes.pass_encrypt(data) with open(self._path, 'wb') as fl: fl.write(data) print('Profile saved successfully') @@ -37,11 +42,11 @@ class ProfileManager: with open(path, 'wb') as fout: fout.write(data) print('Profile exported successfully') - copy(self._directory + 'avatars', new_path + 'avatars') + util.copy(self._directory + 'avatars', new_path + 'avatars') if use_new_path: self._path = new_path + os.path.basename(self._path) self._directory = new_path - Settings.get_instance().update_path() + self._settings.update_path() @staticmethod def find_profiles(): @@ -57,7 +62,7 @@ class ProfileManager: if fl.endswith('.tox'): name = fl[:-4] result.append((path, name)) - path = curr_directory() + path = util.get_base_directory(__file__) # check current directory for fl in os.listdir(path): if fl.endswith('.tox'): diff --git a/toxygen/user_data/settings.py b/toxygen/user_data/settings.py index 45d41bd..f879e53 100644 --- a/toxygen/user_data/settings.py +++ b/toxygen/user_data/settings.py @@ -1,28 +1,26 @@ from platform import system import json import os -from util import Singleton, curr_directory, log, copy, append_slash +from util.util import log, curr_directory, append_slash import pyaudio from user_data.toxes import ToxES -import smileys +import smileys_and_stickers as smileys -class Settings(dict, Singleton): +class Settings(dict): """ Settings of current profile + global app settings """ - def __init__(self, name): - Singleton.__init__(self) - self.path = ProfileManager.get_path() + str(name) + '.json' - self.name = name - if os.path.isfile(self.path): - with open(self.path, 'rb') as fl: + def __init__(self, toxes, path): + self._path = path + self._toxes = toxes + if os.path.isfile(path): + with open(path, 'rb') as fl: data = fl.read() - inst = ToxES.get_instance() try: - if inst.is_data_encrypted(data): - data = inst.pass_decrypt(data) + if toxes.is_data_encrypted(data): + data = toxes.pass_decrypt(data) info = json.loads(str(data, 'utf-8')) except Exception as ex: info = Settings.get_default_settings() @@ -175,12 +173,11 @@ class Settings(dict, Singleton): def save(self): text = json.dumps(self) - inst = ToxES.get_instance() - if inst.has_password(): - text = bytes(inst.pass_encrypt(bytes(text, 'utf-8'))) + if self._toxes.has_password(): + text = bytes(self._toxes.pass_encrypt(bytes(text, 'utf-8'))) else: text = bytes(text, 'utf-8') - with open(self.path, 'wb') as fl: + with open(self._path, 'wb') as fl: fl.write(text) def close(self): diff --git a/toxygen/util/util.py b/toxygen/util/util.py index 33b4758..45bc345 100644 --- a/toxygen/util/util.py +++ b/toxygen/util/util.py @@ -3,9 +3,7 @@ import time import shutil import sys import re - - -program_version = '0.5.0' +import platform def cached(func): @@ -33,6 +31,18 @@ def curr_directory(current_file=None): return os.path.dirname(os.path.realpath(current_file or __file__)) +def get_base_directory(current_file=None): + return os.path.dirname(curr_directory()) + + +def get_images_directory(): + return os.path.join(get_base_directory(), 'images') + + +def get_styles_directory(): + return os.path.join(get_base_directory(), 'styles') + + def curr_time(): return time.strftime('%H:%M') @@ -95,12 +105,5 @@ def is_re_valid(regex): return True -class Singleton: - _instance = None - - def __init__(self): - self.__class__._instance = self - - @classmethod - def get_instance(cls): - return cls._instance +def get_platform(): + return platform.system() diff --git a/toxygen/wrapper/libtox.py b/toxygen/wrapper/libtox.py index 752798f..a04746c 100644 --- a/toxygen/wrapper/libtox.py +++ b/toxygen/wrapper/libtox.py @@ -1,6 +1,6 @@ from platform import system from ctypes import CDLL -import util +import util.util as util class LibToxCore: