diff --git a/toxygen/app.py b/toxygen/app.py index 9f41b47..ef5e351 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -302,9 +302,10 @@ class App: friend_items_factory = FriendItemsFactory(self._settings, self._ms) self._friend_factory = FriendFactory(self._profile_manager, self._settings, self._tox, db, friend_items_factory) self._contacts_provider = ContactProvider(self._tox, self._friend_factory) - history = History(self._contacts_provider, db, self._settings) + history = None messages_items_factory = MessagesItemsFactory(self._settings, self._plugin_loader, self._smiley_loader, - self._ms, history) + self._ms, lambda m: history.delete_message(m)) + history = History(self._contacts_provider, db, self._settings, self._ms, messages_items_factory) self._contacts_manager = ContactsManager(self._tox, self._settings, self._ms, self._profile_manager, self._contacts_provider, history, self._tox_dns, messages_items_factory) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index c3db871..b65acfa 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -296,6 +296,7 @@ class ContactsManager: Adds friend to list """ self._tox.friend_add_norequest(tox_id) + self._history.add_friend_to_db(tox_id) friend = self._contact_provider.get_friend_by_public_key(tox_id) self._contacts.append(friend) diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 302dc0b..af5b87e 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -1,11 +1,10 @@ from contacts.friend import * -from user_data.settings import * -from history.database import * from file_transfers.file_transfers import * import time from contacts import basecontact from contacts.group_chat import * import utils.ui as util_ui +import random class Profile(basecontact.BaseContact): @@ -70,7 +69,6 @@ class Profile(basecontact.BaseContact): def new_nospam(self): """Sets new nospam part of tox id""" - import random self._tox.self_set_nospam(random.randint(0, 4294967295)) # no spam - uint32 self._tox_id = self._tox.self_get_address() @@ -106,58 +104,6 @@ class Profile(basecontact.BaseContact): while i < self._messages.count() and not self._messages.itemWidget(self._messages.item(i)).mark_as_sent(): i += 1 - def delete_message(self, message_id): - friend = self.get_curr_friend() - friend.delete_message(time) - self._history.delete_message(friend.tox_id, message_id) - self.update() - - # ----------------------------------------------------------------------------------------------------------------- - # Friend, message and file transfer items creation - # ----------------------------------------------------------------------------------------------------------------- - - def create_message_item(self, text, time, owner, message_type, append=True): - if message_type == MESSAGE_TYPE['INFO_MESSAGE']: - name = '' - elif owner == MESSAGE_OWNER['FRIEND']: - name = self.get_active_name() - else: - name = self._name - pixmap = None - if self._show_avatars: - if owner == MESSAGE_OWNER['FRIEND']: - pixmap = self.get_curr_friend().get_pixmap() - else: - pixmap = self.get_pixmap() - return self._factory.create_message_item(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'], - message_type, append, pixmap) - - def create_gc_message_item(self, text, time, owner, name, message_type, append=True): - pixmap = None - if self._show_avatars: - if owner == MESSAGE_OWNER['FRIEND']: - pixmap = self.get_curr_friend().get_pixmap() - else: - pixmap = self.get_pixmap() - return self._factory.create_message_item(text, time, name, True, - message_type - 5, append, pixmap) - - def create_file_transfer_item(self, tm, append=True): - data = list(tm.get_data()) - data[3] = self.get_friend_by_number(data[4]).name if data[3] else self._name - return self._factory.create_file_transfer_item(data, append) - - def create_unsent_file_item(self, message, append=True): - data = message.get_data() - return self._factory.create_unsent_file_item(os.path.basename(data[0]), - os.path.getsize(data[0]) if data[1] is None else len(data[1]), - self.name, - data[2], - append) - - def create_inline_item(self, data, append=True): - return self._factory.create_inline_item(data, append) - # ----------------------------------------------------------------------------------------------------------------- # Reset # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py index 073b8b6..8da0811 100644 --- a/toxygen/file_transfers/file_transfers_handler.py +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -1,7 +1,7 @@ from file_transfers.file_transfers import * from messenger.messages import * from history.database import MESSAGE_AUTHOR -from ui.list_items import * +from ui.contact_items import * from PyQt5 import QtWidgets import utils.util as util diff --git a/toxygen/history/database.py b/toxygen/history/database.py index 4be00c4..78c0bea 100644 --- a/toxygen/history/database.py +++ b/toxygen/history/database.py @@ -37,16 +37,6 @@ class Database: except Exception as ex: util.log('Db reading error: ' + str(ex)) os.remove(path) - db = self._connect() - cursor = db.cursor() - 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 @@ -73,10 +63,8 @@ class Database: db = self._connect() try: cursor = db.cursor() - # cursor.execute('INSERT INTO contacts VALUES (?);', (tox_id, )) - cursor.execute('CREATE TABLE id' + tox_id + '(' + cursor.execute('CREATE TABLE IF NOT EXISTS id' + tox_id + '(' ' id INTEGER PRIMARY KEY,' - ' message_id INTEGER,' ' author_name TEXT,' ' message TEXT,' ' author INTEGER,' @@ -94,7 +82,6 @@ class Database: db = self._connect() try: cursor = db.cursor() - cursor.execute('DELETE FROM contacts WHERE tox_id=?;', (tox_id, )) cursor.execute('DROP TABLE id' + tox_id + ';') db.commit() except: @@ -103,20 +90,12 @@ class Database: finally: db.close() - def friend_exists_in_db(self, tox_id): - db = self._connect() - cursor = db.cursor() - 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): db = self._connect() try: cursor = db.cursor() cursor.executemany('INSERT INTO id' + tox_id + - '(message, message_id, author_name, author, unix_time, message_type) ' + + '(message, author_name, author, unix_time, message_type) ' + 'VALUES (?, ?, ?, ?, ?, ?);', messages_iter) db.commit() except: @@ -130,7 +109,7 @@ class Database: try: cursor = db.cursor() cursor.execute('UPDATE id' + tox_id + ' SET author = 0 ' - 'WHERE message_id = ' + str(message_id) + ' AND author = 2;') + 'WHERE id = ' + str(message_id) + ' AND author = 2;') db.commit() except: print('Database is locked!') @@ -163,8 +142,7 @@ class Database: db.close() def messages_getter(self, tox_id): - if not self.friend_exists_in_db(tox_id): - self.add_friend_to_db(tox_id) + self.add_friend_to_db(tox_id) return Database.MessageGetter(self._path, tox_id) @@ -180,15 +158,6 @@ class Database: self._tox_id = tox_id self._db = self._cursor = None - def _connect(self): - self._db = connect(self._path, timeout=TIMEOUT) - self._cursor = self._db.cursor() - 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): - self._db.close() - def get_one(self): return self.get(1) @@ -214,3 +183,19 @@ class Database: def delete_one(self): if self._count: self._count -= 1 + + def _connect(self): + self._db = connect(self._path, timeout=TIMEOUT) + self._cursor = self._db.cursor() + self._cursor.execute('SELECT id, message, author, unix_time, message_type FROM id' + + self._tox_id + ' ORDER BY unix_time DESC;') + + def _disconnect(self): + self._db.close() + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _connect(self): + return connect(self._path, timeout=TIMEOUT) diff --git a/toxygen/history/history.py b/toxygen/history/history.py index 6ded428..234b640 100644 --- a/toxygen/history/history.py +++ b/toxygen/history/history.py @@ -1,14 +1,15 @@ from history.history_logs_generators import * -# TODO: fix history loading and saving - class History: - def __init__(self, contact_provider, db, settings): + def __init__(self, contact_provider, db, settings, main_screen, messages_items_factory): self._contact_provider = contact_provider self._db = db self._settings = settings + self._messages = main_screen.messages + self._messages_items_factory = messages_items_factory + self._is_loading = False def __del__(self): del self._db @@ -23,8 +24,7 @@ class 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) + self._db.add_friend_to_db(friend.tox_id) if not self._settings['save_unsent_only']: messages = friend.get_corr_for_saving() else: @@ -41,68 +41,50 @@ class History: Clear chat history """ friend.clear_corr(save_unsent) - if self._db.friend_exists_in_db(friend.tox_id): - self._db.delete_messages(friend.tox_id) - self._db.delete_friend_from_db(friend.tox_id) + self._db.delete_friend_from_db(friend.tox_id) def delete_message(self, message): pass - def load_history(self): + def load_history(self, friend): """ Tries to load next part of messages """ - if not self._load_db: + if self._is_loading: return - self._load_db = False - friend = self.get_curr_friend() + self._is_loading = True friend.load_corr(False) - data = friend.get_corr() - if not data: + messages = friend.get_corr() + if not messages: + self._is_loading = False return - data.reverse() - data = data[self._messages.count():self._messages.count() + PAGE_SIZE] - for message in data: + messages.reverse() + messages = messages[self._messages.count():self._messages.count() + PAGE_SIZE] + for message in messages: if message.get_type() <= 1: # text message - data = message.get_data() - self.create_message_item(data[0], - data[2], - data[1], - data[3], - False) + self._create_message_item(message) elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: # file transfer if message.get_status() is None: - self.create_unsent_file_item(message) + self._create_unsent_file_item(message) continue - item = self.create_file_transfer_item(message, False) - if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer - try: - ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())] - ft.set_state_changed_handler(item.update_transfer_state) - ft.signal() - except: - print('Incoming not started transfer - no info found') + self._create_file_transfer_item(message) elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline image - self.create_inline_item(message.get_data(), False) + self._create_inline_item(message) else: # info message - data = message.get_data() - self.create_message_item(data[0], - data[2], - '', - data[3], - False) - self._load_db = True + self._create_message_item(message) + self._is_loading = False 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) + self._db.add_friend_to_db(friend_public_key) return self._db.messages_getter(friend_public_key) def delete_history(self, friend): self.clear_history(friend) - if self._db.friend_exists_in_db(friend.tox_id): - self._db.delete_friend_from_db(friend.tox_id) + self._db.delete_friend_from_db(friend.tox_id) + + def add_friend_to_db(self, tox_id): + self._db.add_friend_to_db(tox_id) @staticmethod def export_history(contact, as_text=True, _range=None): @@ -117,3 +99,19 @@ class History: generator = TextHistoryGenerator(corr, contact.name) if as_text else HtmlHistoryGenerator(corr, contact.name) return generator.generate() + + # ----------------------------------------------------------------------------------------------------------------- + # Items creation + # ----------------------------------------------------------------------------------------------------------------- + + def _create_message_item(self, message): + return self._messages_items_factory.create_message_item(message, False) + + def _create_unsent_file_item(self, message): + return self._messages_items_factory.create_unsent_file_item(message, False) + + def _create_file_transfer_item(self, message): + return self._messages_items_factory.create_file_transfer_item(message, False) + + def _create_inline_item(self, message): + return self._messages_items_factory.create_inline_item(message, False) diff --git a/toxygen/ui/contact_items.py b/toxygen/ui/contact_items.py new file mode 100644 index 0000000..25b3c0a --- /dev/null +++ b/toxygen/ui/contact_items.py @@ -0,0 +1,100 @@ +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 utils.util import * +from ui.widgets import DataLabel, create_menu +from user_data import settings + + +class ContactItem(QtWidgets.QWidget): + """ + Contact in friends list + """ + + def __init__(self, settings, parent=None): + QtWidgets.QWidget.__init__(self, parent) + mode = settings['compact_mode'] + self.setBaseSize(QtCore.QSize(250, 40 if mode else 70)) + self.avatar_label = QtWidgets.QLabel(self) + size = 32 if mode else 64 + self.avatar_label.setGeometry(QtCore.QRect(3, 4, size, size)) + self.avatar_label.setScaledContents(False) + self.avatar_label.setAlignment(QtCore.Qt.AlignCenter) + self.name = DataLabel(self) + self.name.setGeometry(QtCore.QRect(50 if mode else 75, 3 if mode else 10, 150, 15 if mode else 25)) + font = QtGui.QFont() + font.setFamily(settings['font']) + font.setPointSize(10 if mode else 12) + font.setBold(True) + self.name.setFont(font) + self.status_message = DataLabel(self) + self.status_message.setGeometry(QtCore.QRect(50 if mode else 75, 20 if mode else 30, 170, 15 if mode else 20)) + font.setPointSize(10) + font.setBold(False) + self.status_message.setFont(font) + self.connection_status = StatusCircle(self) + self.connection_status.setGeometry(QtCore.QRect(230, -2 if mode else 5, 32, 32)) + self.messages = UnreadMessagesCount(settings, self) + self.messages.setGeometry(QtCore.QRect(20 if mode else 52, 20 if mode else 50, 30, 20)) + + +class StatusCircle(QtWidgets.QWidget): + """ + Connection status + """ + def __init__(self, parent): + QtWidgets.QWidget.__init__(self, parent) + self.setGeometry(0, 0, 32, 32) + self.label = QtWidgets.QLabel(self) + self.label.setGeometry(QtCore.QRect(0, 0, 32, 32)) + self.unread = False + + def update(self, status, unread_messages=None): + if unread_messages is None: + unread_messages = self.unread + else: + self.unread = unread_messages + if status == TOX_USER_STATUS['NONE']: + name = 'online' + elif status == TOX_USER_STATUS['AWAY']: + name = 'idle' + elif status == TOX_USER_STATUS['BUSY']: + name = 'busy' + else: + name = 'offline' + if unread_messages: + name += '_notification' + self.label.setGeometry(QtCore.QRect(0, 0, 32, 32)) + else: + self.label.setGeometry(QtCore.QRect(2, 0, 32, 32)) + pixmap = QtGui.QPixmap(join_path(get_images_directory(), '{}.png'.format(name))) + self.label.setPixmap(pixmap) + + +class UnreadMessagesCount(QtWidgets.QWidget): + + def __init__(self, settings, parent=None): + super().__init__(parent) + self._settings = settings + self.resize(30, 20) + self.label = QtWidgets.QLabel(self) + self.label.setGeometry(QtCore.QRect(0, 0, 30, 20)) + self.label.setVisible(False) + font = QtGui.QFont() + font.setFamily(settings['font']) + font.setPointSize(12) + font.setBold(True) + self.label.setFont(font) + self.label.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignCenter) + color = settings['unread_color'] + self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }') + + def update(self, messages_count): + color = self._settings['unread_color'] + self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }') + if messages_count: + self.label.setVisible(True) + self.label.setText(str(messages_count)) + else: + self.label.setVisible(False) diff --git a/toxygen/ui/items_factories.py b/toxygen/ui/items_factories.py index db2f065..dc9c002 100644 --- a/toxygen/ui/items_factories.py +++ b/toxygen/ui/items_factories.py @@ -1,4 +1,4 @@ -from ui.list_items import * +from ui.contact_items import * from ui.messages_widgets import * @@ -18,17 +18,19 @@ class FriendItemsFactory: return item +# TODO: accept messages everywhere instead of params + class MessagesItemsFactory: - def __init__(self, settings, plugin_loader, smiley_loader, main_screen, history): + def __init__(self, settings, plugin_loader, smiley_loader, main_screen, delete_action): self._settings, self._plugin_loader = settings, plugin_loader - self._smiley_loader, self._history = smiley_loader, history + self._smiley_loader, self._delete_action = smiley_loader, delete_action self._messages = main_screen.messages self._message_edit = main_screen.messageEdit def create_message_item(self, message, append=True, pixmap=None): item = message.get_widget(self._settings, self._create_message_browser, - self._history.delete_message, self._messages) + self._delete_action, self._messages) if pixmap is not None: item.set_avatar(pixmap) elem = QtWidgets.QListWidgetItem() @@ -69,7 +71,7 @@ class MessagesItemsFactory: return item - def create_file_transfer_item(self, data, append): + def create_file_transfer_item(self, data, append=True): data.append(self._messages.width()) item = FileTransferItem(*data) elem = QtWidgets.QListWidgetItem() diff --git a/toxygen/ui/list_items.py b/toxygen/ui/list_items.py deleted file mode 100644 index c9638ec..0000000 --- a/toxygen/ui/list_items.py +++ /dev/null @@ -1,343 +0,0 @@ -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 utils.util import * -from ui.widgets import DataLabel, create_menu -from user_data import settings - - -class ContactItem(QtWidgets.QWidget): - """ - Contact in friends list - """ - - def __init__(self, settings, parent=None): - QtWidgets.QWidget.__init__(self, parent) - mode = settings['compact_mode'] - self.setBaseSize(QtCore.QSize(250, 40 if mode else 70)) - self.avatar_label = QtWidgets.QLabel(self) - size = 32 if mode else 64 - self.avatar_label.setGeometry(QtCore.QRect(3, 4, size, size)) - self.avatar_label.setScaledContents(False) - self.avatar_label.setAlignment(QtCore.Qt.AlignCenter) - self.name = DataLabel(self) - self.name.setGeometry(QtCore.QRect(50 if mode else 75, 3 if mode else 10, 150, 15 if mode else 25)) - font = QtGui.QFont() - font.setFamily(settings['font']) - font.setPointSize(10 if mode else 12) - font.setBold(True) - self.name.setFont(font) - self.status_message = DataLabel(self) - self.status_message.setGeometry(QtCore.QRect(50 if mode else 75, 20 if mode else 30, 170, 15 if mode else 20)) - font.setPointSize(10) - font.setBold(False) - self.status_message.setFont(font) - self.connection_status = StatusCircle(self) - self.connection_status.setGeometry(QtCore.QRect(230, -2 if mode else 5, 32, 32)) - self.messages = UnreadMessagesCount(settings, self) - self.messages.setGeometry(QtCore.QRect(20 if mode else 52, 20 if mode else 50, 30, 20)) - - -class StatusCircle(QtWidgets.QWidget): - """ - Connection status - """ - def __init__(self, parent): - QtWidgets.QWidget.__init__(self, parent) - self.setGeometry(0, 0, 32, 32) - self.label = QtWidgets.QLabel(self) - self.label.setGeometry(QtCore.QRect(0, 0, 32, 32)) - self.unread = False - - def update(self, status, unread_messages=None): - if unread_messages is None: - unread_messages = self.unread - else: - self.unread = unread_messages - if status == TOX_USER_STATUS['NONE']: - name = 'online' - elif status == TOX_USER_STATUS['AWAY']: - name = 'idle' - elif status == TOX_USER_STATUS['BUSY']: - name = 'busy' - else: - name = 'offline' - if unread_messages: - name += '_notification' - self.label.setGeometry(QtCore.QRect(0, 0, 32, 32)) - else: - self.label.setGeometry(QtCore.QRect(2, 0, 32, 32)) - pixmap = QtGui.QPixmap(join_path(get_images_directory(), '{}.png'.format(name))) - self.label.setPixmap(pixmap) - - -class UnreadMessagesCount(QtWidgets.QWidget): - - def __init__(self, settings, parent=None): - super().__init__(parent) - self._settings = settings - self.resize(30, 20) - self.label = QtWidgets.QLabel(self) - self.label.setGeometry(QtCore.QRect(0, 0, 30, 20)) - self.label.setVisible(False) - font = QtGui.QFont() - font.setFamily(settings['font']) - font.setPointSize(12) - font.setBold(True) - self.label.setFont(font) - self.label.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignCenter) - color = settings['unread_color'] - self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }') - - def update(self, messages_count): - color = self._settings['unread_color'] - self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }') - if messages_count: - self.label.setVisible(True) - self.label.setText(str(messages_count)) - else: - self.label.setVisible(False) - - -class FileTransferItem(QtWidgets.QListWidget): - - def __init__(self, file_name, size, time, user, friend_number, file_number, state, width, parent=None): - - QtWidgets.QListWidget.__init__(self, parent) - self.resize(QtCore.QSize(width, 34)) - if state == TOX_FILE_TRANSFER_STATE['CANCELLED']: - self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') - elif state in PAUSED_FILE_TRANSFERS: - self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') - else: - self.setStyleSheet('QListWidget { border: 1px solid green; }') - self.state = state - - self.name = DataLabel(self) - self.name.setGeometry(QtCore.QRect(3, 7, 95, 25)) - self.name.setTextFormat(QtCore.Qt.PlainText) - font = QtGui.QFont() - # FIXME - font.setFamily(settings.Settings.get_instance()['font']) - font.setPointSize(11) - font.setBold(True) - self.name.setFont(font) - self.name.setText(user) - - self.time = QtWidgets.QLabel(self) - self.time.setGeometry(QtCore.QRect(width - 60, 7, 50, 25)) - font.setPointSize(10) - font.setBold(False) - self.time.setFont(font) - self.time.setText(convert_time(time)) - - self.cancel = QtWidgets.QPushButton(self) - self.cancel.setGeometry(QtCore.QRect(width - 125, 2, 30, 30)) - pixmap = QtGui.QPixmap(curr_directory() + '/images/decline.png') - icon = QtGui.QIcon(pixmap) - self.cancel.setIcon(icon) - self.cancel.setIconSize(QtCore.QSize(30, 30)) - self.cancel.setVisible(state in ACTIVE_FILE_TRANSFERS) - self.cancel.clicked.connect(lambda: self.cancel_transfer(friend_number, file_number)) - self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}') - - self.accept_or_pause = QtWidgets.QPushButton(self) - self.accept_or_pause.setGeometry(QtCore.QRect(width - 170, 2, 30, 30)) - if state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: - self.accept_or_pause.setVisible(True) - self.button_update('accept') - elif state in DO_NOT_SHOW_ACCEPT_BUTTON: - self.accept_or_pause.setVisible(False) - elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: # setup for continue - self.accept_or_pause.setVisible(True) - self.button_update('resume') - else: # pause - self.accept_or_pause.setVisible(True) - self.button_update('pause') - self.accept_or_pause.clicked.connect(lambda: self.accept_or_pause_transfer(friend_number, file_number, size)) - - self.accept_or_pause.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none}') - - self.pb = QtWidgets.QProgressBar(self) - self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20)) - self.pb.setValue(0) - self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }') - self.pb.setVisible(state in SHOW_PROGRESS_BAR) - - self.file_name = DataLabel(self) - self.file_name.setGeometry(QtCore.QRect(210, 7, width - 420, 20)) - font.setPointSize(12) - self.file_name.setFont(font) - file_size = size // 1024 - if not file_size: - file_size = '{}B'.format(size) - elif file_size >= 1024: - file_size = '{}MB'.format(file_size // 1024) - else: - file_size = '{}KB'.format(file_size) - file_data = '{} {}'.format(file_size, file_name) - self.file_name.setText(file_data) - self.file_name.setToolTip(file_name) - self.saved_name = file_name - self.time_left = QtWidgets.QLabel(self) - self.time_left.setGeometry(QtCore.QRect(width - 92, 7, 30, 20)) - font.setPointSize(10) - self.time_left.setFont(font) - self.time_left.setVisible(state == TOX_FILE_TRANSFER_STATE['RUNNING']) - self.setFocusPolicy(QtCore.Qt.NoFocus) - self.paused = False - - def cancel_transfer(self, friend_number, file_number): - pr = profile.Profile.get_instance() - pr.cancel_transfer(friend_number, file_number) - self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') - self.cancel.setVisible(False) - self.accept_or_pause.setVisible(False) - self.pb.setVisible(False) - - def accept_or_pause_transfer(self, friend_number, file_number, size): - if self.state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: - directory = QtWidgets.QFileDialog.getExistingDirectory(self, - QtWidgets.QApplication.translate("MainWindow", 'Choose folder'), - curr_directory(), - QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) - self.pb.setVisible(True) - if directory: - pr = profile.Profile.get_instance() - pr.accept_transfer(self, directory + '/' + self.saved_name, friend_number, file_number, size) - self.button_update('pause') - elif self.state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: # resume - self.paused = False - profile.Profile.get_instance().resume_transfer(friend_number, file_number) - self.button_update('pause') - self.state = TOX_FILE_TRANSFER_STATE['RUNNING'] - else: # pause - self.paused = True - self.state = TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER'] - profile.Profile.get_instance().pause_transfer(friend_number, file_number) - self.button_update('resume') - self.accept_or_pause.clearFocus() - - def button_update(self, path): - pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(path)) - icon = QtGui.QIcon(pixmap) - self.accept_or_pause.setIcon(icon) - self.accept_or_pause.setIconSize(QtCore.QSize(30, 30)) - - def update_transfer_state(self, state, progress, time): - self.pb.setValue(int(progress * 100)) - if time + 1: - m, s = divmod(time, 60) - self.time_left.setText('{0:02d}:{1:02d}'.format(m, s)) - if self.state != state and self.state in ACTIVE_FILE_TRANSFERS: - if state == TOX_FILE_TRANSFER_STATE['CANCELLED']: - self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') - self.cancel.setVisible(False) - self.accept_or_pause.setVisible(False) - self.pb.setVisible(False) - self.state = state - self.time_left.setVisible(False) - elif state == TOX_FILE_TRANSFER_STATE['FINISHED']: - self.accept_or_pause.setVisible(False) - self.pb.setVisible(False) - self.cancel.setVisible(False) - self.setStyleSheet('QListWidget { border: 1px solid green; }') - self.state = state - self.time_left.setVisible(False) - elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND']: - self.accept_or_pause.setVisible(False) - self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') - self.state = state - self.time_left.setVisible(False) - elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: - self.button_update('resume') # setup button continue - self.setStyleSheet('QListWidget { border: 1px solid green; }') - self.state = state - self.time_left.setVisible(False) - elif state == TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']: - self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') - self.accept_or_pause.setVisible(False) - self.time_left.setVisible(False) - self.pb.setVisible(False) - elif not self.paused: # active - self.pb.setVisible(True) - self.accept_or_pause.setVisible(True) # setup to pause - self.button_update('pause') - self.setStyleSheet('QListWidget { border: 1px solid green; }') - self.state = state - self.time_left.setVisible(True) - - def mark_as_sent(self): - return False - - -class UnsentFileItem(FileTransferItem): - - def __init__(self, file_name, size, user, time, width, parent=None): - super(UnsentFileItem, self).__init__(file_name, size, time, user, -1, -1, - TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'], width, parent) - self._time = time - self.pb.setVisible(False) - movie = QtGui.QMovie(join_path(get_images_directory(), 'spinner.gif')) - self.time.setMovie(movie) - movie.start() - - def cancel_transfer(self, *args): - pr = profile.Profile.get_instance() - pr.cancel_not_started_transfer(self._time) - - -class InlineImageItem(QtWidgets.QScrollArea): - - def __init__(self, data, width, elem): - - QtWidgets.QScrollArea.__init__(self) - self.setFocusPolicy(QtCore.Qt.NoFocus) - self._elem = elem - self._image_label = QtWidgets.QLabel(self) - self._image_label.raise_() - self.setWidget(self._image_label) - self._image_label.setScaledContents(False) - self._pixmap = QtGui.QPixmap() - self._pixmap.loadFromData(data, 'PNG') - self._max_size = width - 30 - self._resize_needed = not (self._pixmap.width() <= self._max_size) - self._full_size = not self._resize_needed - if not self._resize_needed: - self._image_label.setPixmap(self._pixmap) - self.resize(QtCore.QSize(self._max_size + 5, self._pixmap.height() + 5)) - self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height()) - else: - pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio) - self._image_label.setPixmap(pixmap) - self.resize(QtCore.QSize(self._max_size + 5, pixmap.height())) - self._image_label.setGeometry(5, 0, self._max_size + 5, pixmap.height()) - self._elem.setSizeHint(QtCore.QSize(self.width(), self.height())) - - def mouseReleaseEvent(self, event): - if event.button() == QtCore.Qt.LeftButton and self._resize_needed: # scale inline - if self._full_size: - pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio) - self._image_label.setPixmap(pixmap) - self.resize(QtCore.QSize(self._max_size, pixmap.height())) - self._image_label.setGeometry(5, 0, pixmap.width(), pixmap.height()) - else: - self._image_label.setPixmap(self._pixmap) - self.resize(QtCore.QSize(self._max_size, self._pixmap.height() + 17)) - self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height()) - self._full_size = not self._full_size - self._elem.setSizeHint(QtCore.QSize(self.width(), self.height())) - elif event.button() == QtCore.Qt.RightButton: # save inline - # TODO: dialog - directory = QtWidgets.QFileDialog.getExistingDirectory(self, - QtWidgets.QApplication.translate("MainWindow", - 'Choose folder'), - curr_directory(), - QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) - if directory: - fl = QtCore.QFile(directory + '/toxygen_inline_' + curr_time().replace(':', '_') + '.png') - self._pixmap.save(fl, 'PNG') - - def mark_as_sent(self): - return False diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index a8a7c13..cce78a4 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -1,5 +1,5 @@ from contacts.profile import * -from ui.list_items import * +from ui.contact_items import * from ui.widgets import MultilineEdit, ComboBox from ui.main_screen_widgets import * import utils.util as util diff --git a/toxygen/ui/messages_widgets.py b/toxygen/ui/messages_widgets.py index 0f510ea..dea7a5c 100644 --- a/toxygen/ui/messages_widgets.py +++ b/toxygen/ui/messages_widgets.py @@ -213,3 +213,245 @@ class MessageItem(QtWidgets.QWidget): i += len(sub) return text + +class FileTransferItem(QtWidgets.QListWidget): + + def __init__(self, file_name, size, time, user, friend_number, file_number, state, width, parent=None): + + QtWidgets.QListWidget.__init__(self, parent) + self.resize(QtCore.QSize(width, 34)) + if state == TOX_FILE_TRANSFER_STATE['CANCELLED']: + self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') + elif state in PAUSED_FILE_TRANSFERS: + self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') + else: + self.setStyleSheet('QListWidget { border: 1px solid green; }') + self.state = state + + self.name = DataLabel(self) + self.name.setGeometry(QtCore.QRect(3, 7, 95, 25)) + self.name.setTextFormat(QtCore.Qt.PlainText) + font = QtGui.QFont() + # FIXME + font.setFamily(settings.Settings.get_instance()['font']) + font.setPointSize(11) + font.setBold(True) + self.name.setFont(font) + self.name.setText(user) + + self.time = QtWidgets.QLabel(self) + self.time.setGeometry(QtCore.QRect(width - 60, 7, 50, 25)) + font.setPointSize(10) + font.setBold(False) + self.time.setFont(font) + self.time.setText(convert_time(time)) + + self.cancel = QtWidgets.QPushButton(self) + self.cancel.setGeometry(QtCore.QRect(width - 125, 2, 30, 30)) + pixmap = QtGui.QPixmap(curr_directory() + '/images/decline.png') + icon = QtGui.QIcon(pixmap) + self.cancel.setIcon(icon) + self.cancel.setIconSize(QtCore.QSize(30, 30)) + self.cancel.setVisible(state in ACTIVE_FILE_TRANSFERS) + self.cancel.clicked.connect(lambda: self.cancel_transfer(friend_number, file_number)) + self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}') + + self.accept_or_pause = QtWidgets.QPushButton(self) + self.accept_or_pause.setGeometry(QtCore.QRect(width - 170, 2, 30, 30)) + if state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: + self.accept_or_pause.setVisible(True) + self.button_update('accept') + elif state in DO_NOT_SHOW_ACCEPT_BUTTON: + self.accept_or_pause.setVisible(False) + elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: # setup for continue + self.accept_or_pause.setVisible(True) + self.button_update('resume') + else: # pause + self.accept_or_pause.setVisible(True) + self.button_update('pause') + self.accept_or_pause.clicked.connect(lambda: self.accept_or_pause_transfer(friend_number, file_number, size)) + + self.accept_or_pause.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none}') + + self.pb = QtWidgets.QProgressBar(self) + self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20)) + self.pb.setValue(0) + self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }') + self.pb.setVisible(state in SHOW_PROGRESS_BAR) + + self.file_name = DataLabel(self) + self.file_name.setGeometry(QtCore.QRect(210, 7, width - 420, 20)) + font.setPointSize(12) + self.file_name.setFont(font) + file_size = size // 1024 + if not file_size: + file_size = '{}B'.format(size) + elif file_size >= 1024: + file_size = '{}MB'.format(file_size // 1024) + else: + file_size = '{}KB'.format(file_size) + file_data = '{} {}'.format(file_size, file_name) + self.file_name.setText(file_data) + self.file_name.setToolTip(file_name) + self.saved_name = file_name + self.time_left = QtWidgets.QLabel(self) + self.time_left.setGeometry(QtCore.QRect(width - 92, 7, 30, 20)) + font.setPointSize(10) + self.time_left.setFont(font) + self.time_left.setVisible(state == TOX_FILE_TRANSFER_STATE['RUNNING']) + self.setFocusPolicy(QtCore.Qt.NoFocus) + self.paused = False + + def cancel_transfer(self, friend_number, file_number): + pr = profile.Profile.get_instance() + pr.cancel_transfer(friend_number, file_number) + self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') + self.cancel.setVisible(False) + self.accept_or_pause.setVisible(False) + self.pb.setVisible(False) + + def accept_or_pause_transfer(self, friend_number, file_number, size): + if self.state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: + directory = QtWidgets.QFileDialog.getExistingDirectory(self, + QtWidgets.QApplication.translate("MainWindow", 'Choose folder'), + curr_directory(), + QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) + self.pb.setVisible(True) + if directory: + pr = profile.Profile.get_instance() + pr.accept_transfer(self, directory + '/' + self.saved_name, friend_number, file_number, size) + self.button_update('pause') + elif self.state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: # resume + self.paused = False + profile.Profile.get_instance().resume_transfer(friend_number, file_number) + self.button_update('pause') + self.state = TOX_FILE_TRANSFER_STATE['RUNNING'] + else: # pause + self.paused = True + self.state = TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER'] + profile.Profile.get_instance().pause_transfer(friend_number, file_number) + self.button_update('resume') + self.accept_or_pause.clearFocus() + + def button_update(self, path): + pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(path)) + icon = QtGui.QIcon(pixmap) + self.accept_or_pause.setIcon(icon) + self.accept_or_pause.setIconSize(QtCore.QSize(30, 30)) + + def update_transfer_state(self, state, progress, time): + self.pb.setValue(int(progress * 100)) + if time + 1: + m, s = divmod(time, 60) + self.time_left.setText('{0:02d}:{1:02d}'.format(m, s)) + if self.state != state and self.state in ACTIVE_FILE_TRANSFERS: + if state == TOX_FILE_TRANSFER_STATE['CANCELLED']: + self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') + self.cancel.setVisible(False) + self.accept_or_pause.setVisible(False) + self.pb.setVisible(False) + self.state = state + self.time_left.setVisible(False) + elif state == TOX_FILE_TRANSFER_STATE['FINISHED']: + self.accept_or_pause.setVisible(False) + self.pb.setVisible(False) + self.cancel.setVisible(False) + self.setStyleSheet('QListWidget { border: 1px solid green; }') + self.state = state + self.time_left.setVisible(False) + elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND']: + self.accept_or_pause.setVisible(False) + self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') + self.state = state + self.time_left.setVisible(False) + elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: + self.button_update('resume') # setup button continue + self.setStyleSheet('QListWidget { border: 1px solid green; }') + self.state = state + self.time_left.setVisible(False) + elif state == TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']: + self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') + self.accept_or_pause.setVisible(False) + self.time_left.setVisible(False) + self.pb.setVisible(False) + elif not self.paused: # active + self.pb.setVisible(True) + self.accept_or_pause.setVisible(True) # setup to pause + self.button_update('pause') + self.setStyleSheet('QListWidget { border: 1px solid green; }') + self.state = state + self.time_left.setVisible(True) + + def mark_as_sent(self): + return False + + +class UnsentFileItem(FileTransferItem): + + def __init__(self, file_name, size, user, time, width, parent=None): + super(UnsentFileItem, self).__init__(file_name, size, time, user, -1, -1, + TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'], width, parent) + self._time = time + self.pb.setVisible(False) + movie = QtGui.QMovie(join_path(get_images_directory(), 'spinner.gif')) + self.time.setMovie(movie) + movie.start() + + def cancel_transfer(self, *args): + pr = profile.Profile.get_instance() + pr.cancel_not_started_transfer(self._time) + + +class InlineImageItem(QtWidgets.QScrollArea): + + def __init__(self, data, width, elem): + + QtWidgets.QScrollArea.__init__(self) + self.setFocusPolicy(QtCore.Qt.NoFocus) + self._elem = elem + self._image_label = QtWidgets.QLabel(self) + self._image_label.raise_() + self.setWidget(self._image_label) + self._image_label.setScaledContents(False) + self._pixmap = QtGui.QPixmap() + self._pixmap.loadFromData(data, 'PNG') + self._max_size = width - 30 + self._resize_needed = not (self._pixmap.width() <= self._max_size) + self._full_size = not self._resize_needed + if not self._resize_needed: + self._image_label.setPixmap(self._pixmap) + self.resize(QtCore.QSize(self._max_size + 5, self._pixmap.height() + 5)) + self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height()) + else: + pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio) + self._image_label.setPixmap(pixmap) + self.resize(QtCore.QSize(self._max_size + 5, pixmap.height())) + self._image_label.setGeometry(5, 0, self._max_size + 5, pixmap.height()) + self._elem.setSizeHint(QtCore.QSize(self.width(), self.height())) + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.LeftButton and self._resize_needed: # scale inline + if self._full_size: + pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio) + self._image_label.setPixmap(pixmap) + self.resize(QtCore.QSize(self._max_size, pixmap.height())) + self._image_label.setGeometry(5, 0, pixmap.width(), pixmap.height()) + else: + self._image_label.setPixmap(self._pixmap) + self.resize(QtCore.QSize(self._max_size, self._pixmap.height() + 17)) + self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height()) + self._full_size = not self._full_size + self._elem.setSizeHint(QtCore.QSize(self.width(), self.height())) + elif event.button() == QtCore.Qt.RightButton: # save inline + # TODO: dialog + directory = QtWidgets.QFileDialog.getExistingDirectory(self, + QtWidgets.QApplication.translate("MainWindow", + 'Choose folder'), + curr_directory(), + QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) + if directory: + fl = QtCore.QFile(directory + '/toxygen_inline_' + curr_time().replace(':', '_') + '.png') + self._pixmap.save(fl, 'PNG') + + def mark_as_sent(self): + return False