diff --git a/src/callbacks.py b/src/callbacks.py index 97d68d4..d0413de 100644 --- a/src/callbacks.py +++ b/src/callbacks.py @@ -68,6 +68,7 @@ def friend_status(tox, friend_num, new_status, user_data): if friend.status is None and Settings.get_instance()['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']: sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS']) invoke_in_main_thread(friend.set_status, new_status) + invoke_in_main_thread(profile.send_files, friend_num) invoke_in_main_thread(profile.update_filtration) @@ -85,7 +86,6 @@ def friend_connection_status(tox, friend_num, new_status, user_data): sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS']) elif friend.status is None: invoke_in_main_thread(profile.send_avatar, friend_num) - profile.friend_online(friend_num) invoke_in_main_thread(PluginLoader.get_instance().friend_online, friend_num) diff --git a/src/friend.py b/src/friend.py index 5e4fef7..585a2bd 100644 --- a/src/friend.py +++ b/src/friend.py @@ -116,6 +116,18 @@ class Friend(contact.Contact): self._corr = filter(lambda x: x.get_type() in (2, 3) and x.get_status() >= 2, self._corr) self._unsaved_messages = 0 + def get_curr_text(self): + return self._curr_text + + def set_curr_text(self, value): + self._curr_text = value + + curr_text = property(get_curr_text, set_curr_text) + + # ----------------------------------------------------------------------------------------------------------------- + # File transfers support + # ----------------------------------------------------------------------------------------------------------------- + def update_transfer_data(self, file_number, status, inline=None): """ Update status of active transfer and load inline if needed @@ -131,13 +143,15 @@ class Friend(contact.Contact): except: pass - def get_curr_text(self): - return self._curr_text + def get_unsent_files(self): + messages = filter(lambda x: type(x) is UnsentFile, self._corr) + return messages - def set_curr_text(self, value): - self._curr_text = value + def clear_unsent_files(self): + self._corr = filter(lambda x: type(x) is not UnsentFile, self._corr) - curr_text = property(get_curr_text, set_curr_text) + def delete_one_unsent_file(self, time): + self._corr = filter(lambda x: not (type(x) is UnsentFile and x.get_data()[2] == time), self._corr) # ----------------------------------------------------------------------------------------------------------------- # Alias support diff --git a/src/images/search.png b/src/images/search.png old mode 100755 new mode 100644 index ec24d66..bf0dff6 Binary files a/src/images/search.png and b/src/images/search.png differ diff --git a/src/list_items.py b/src/list_items.py index 3989381..a6a5cd4 100644 --- a/src/list_items.py +++ b/src/list_items.py @@ -356,6 +356,60 @@ class FileTransferItem(QtGui.QListWidget): self.state = state +class UnsentFileItem(QtGui.QListWidget): + + def __init__(self, file_name, size, user, time, width, parent=None): + QtGui.QListWidget.__init__(self, parent) + self.resize(QtCore.QSize(width, 34)) + self.time = time + self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') + + self.name = DataLabel(self) + self.name.setGeometry(QtCore.QRect(3, 7, 95, 20)) + self.name.setTextFormat(QtCore.Qt.PlainText) + font = QtGui.QFont() + font.setFamily("Times New Roman") + font.setPointSize(11) + font.setBold(True) + self.name.setFont(font) + self.name.setText(user) + + self.time = QtGui.QLabel(self) + self.time.setGeometry(QtCore.QRect(width - 53, 7, 50, 20)) + font.setPointSize(10) + font.setBold(False) + self.time.setFont(font) + self.time.setText(convert_time(time)) + + self.cancel = QtGui.QPushButton(self) + self.cancel.setGeometry(QtCore.QRect(width - 120, 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.clicked.connect(self.cancel_transfer) + self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}') + + self.file_name = DataLabel(self) + self.file_name.setGeometry(QtCore.QRect(210, 7, width - 400, 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 = u'{} {}'.format(file_size, file_name) + self.file_name.setText(file_data) + self.setFocusPolicy(QtCore.Qt.NoFocus) + + def cancel_transfer(self): + pr = profile.Profile.get_instance() + pr.cancel_not_started_transfer(self.time) + + class InlineImageItem(QtGui.QWidget): def __init__(self, data, width, parent=None): diff --git a/src/mainscreen.py b/src/mainscreen.py index bb84bdd..cde69e2 100644 --- a/src/mainscreen.py +++ b/src/mainscreen.py @@ -3,7 +3,7 @@ from menu import * from profile import * from list_items import * -from widgets import QRightClickButton, RubberBand, MultilineEdit +from widgets import MultilineEdit import plugin_support from mainscreen_widgets import * @@ -142,33 +142,34 @@ class MainWindow(QtGui.QMainWindow): def setup_left_center_menu(self, Form): Form.resize(270, 25) self.search_label = QtGui.QLabel(Form) - self.search_label.setGeometry(QtCore.QRect(3, 0, 25, 25)) - pixmap = QtGui.QPixmap(QtCore.QSize(25, 25)) + self.search_label.setGeometry(QtCore.QRect(3, 2, 20, 20)) + pixmap = QtGui.QPixmap() pixmap.load(curr_directory() + '/images/search.png') self.search_label.setScaledContents(False) - self.search_label.setPixmap(pixmap.scaled(25, 25, QtCore.Qt.KeepAspectRatio)) + self.search_label.setPixmap(pixmap) self.contact_name = QtGui.QLineEdit(Form) - self.contact_name.setGeometry(QtCore.QRect(30, 0, 120, 25)) + self.contact_name.setGeometry(QtCore.QRect(0, 0, 150, 25)) self.contact_name.setObjectName("contact_name") self.contact_name.textChanged.connect(self.filtering) self.online_contacts = QtGui.QComboBox(Form) self.online_contacts.setGeometry(QtCore.QRect(150, 0, 120, 25)) self.online_contacts.activated[int].connect(lambda x: self.filtering()) + self.search_label.raise_() QtCore.QMetaObject.connectSlotsByName(Form) def setup_left_top(self, Form): Form.setObjectName("left_top") Form.setCursor(QtCore.Qt.PointingHandCursor) - Form.setMinimumSize(QtCore.QSize(270, 80)) - Form.setMaximumSize(QtCore.QSize(270, 80)) - Form.setBaseSize(QtCore.QSize(270, 80)) + Form.setMinimumSize(QtCore.QSize(270, 100)) + Form.setMaximumSize(QtCore.QSize(270, 100)) + Form.setBaseSize(QtCore.QSize(270, 100)) self.avatar_label = Form.avatar_label = QtGui.QLabel(Form) - self.avatar_label.setGeometry(QtCore.QRect(5, 15, 64, 64)) + self.avatar_label.setGeometry(QtCore.QRect(5, 30, 64, 64)) self.avatar_label.setScaledContents(True) self.name = Form.name = DataLabel(Form) - Form.name.setGeometry(QtCore.QRect(80, 25, 150, 25)) + Form.name.setGeometry(QtCore.QRect(80, 40, 150, 25)) font = QtGui.QFont() font.setFamily("Times New Roman") font.setPointSize(14) @@ -176,7 +177,7 @@ class MainWindow(QtGui.QMainWindow): Form.name.setFont(font) Form.name.setObjectName("name") self.status_message = Form.status_message = DataLabel(Form) - Form.status_message.setGeometry(QtCore.QRect(80, 55, 170, 20)) + Form.status_message.setGeometry(QtCore.QRect(80, 60, 170, 25)) font.setPointSize(12) font.setBold(False) Form.status_message.setFont(font) @@ -194,9 +195,9 @@ class MainWindow(QtGui.QMainWindow): def setup_right_top(self, Form): Form.setObjectName("Form") - Form.resize(650, 80) + Form.resize(650, 100) self.account_avatar = QtGui.QLabel(Form) - self.account_avatar.setGeometry(QtCore.QRect(10, 17, 64, 64)) + self.account_avatar.setGeometry(QtCore.QRect(10, 30, 64, 64)) self.account_avatar.setScaledContents(True) self.account_name = DataLabel(Form) self.account_name.setGeometry(QtCore.QRect(100, 25, 400, 25)) @@ -224,7 +225,7 @@ class MainWindow(QtGui.QMainWindow): self.videocallButton.clicked.connect(lambda: self.profile.call_click(True, True)) self.update_call_state('call') self.typing = QtGui.QLabel(Form) - self.typing.setGeometry(QtCore.QRect(500, 40, 50, 30)) + self.typing.setGeometry(QtCore.QRect(500, 50, 50, 30)) pixmap = QtGui.QPixmap(QtCore.QSize(50, 30)) pixmap.load(curr_directory() + '/images/typing.png') self.typing.setScaledContents(False) @@ -299,11 +300,13 @@ class MainWindow(QtGui.QMainWindow): self.setup_left_center(main_list) grid.addWidget(main_list, 2, 1, 2, 1) grid.setColumnMinimumWidth(0, 500) - grid.setColumnMinimumWidth(1, 270) - grid.setRowMinimumHeight(0, 82) + grid.setColumnMinimumWidth(1, 280) + grid.setSpacing(0) + grid.setContentsMargins(0, 0, 0, 0) + grid.setRowMinimumHeight(0, 100) grid.setRowMinimumHeight(1, 25) - grid.setRowMinimumHeight(2, 410) - grid.setRowMinimumHeight(3, 60) + grid.setRowMinimumHeight(2, 320) + grid.setRowMinimumHeight(3, 55) grid.setColumnStretch(1, 1) grid.setRowStretch(2, 1) main.setLayout(grid) @@ -321,19 +324,19 @@ class MainWindow(QtGui.QMainWindow): QtGui.QApplication.closeAllWindows() def resizeEvent(self, *args, **kwargs): - self.messages.setGeometry(0, 0, self.width() - 294, self.height() - 172) - self.friends_list.setGeometry(0, 0, 270, self.height() - 135) + self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155) + self.friends_list.setGeometry(0, 0, 270, self.height() - 125) - self.videocallButton.setGeometry(QtCore.QRect(self.width() - 350, 20, 50, 50)) - self.callButton.setGeometry(QtCore.QRect(self.width() - 410, 20, 50, 50)) - self.typing.setGeometry(QtCore.QRect(self.width() - 470, 30, 50, 30)) + self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 40, 50, 50)) + self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 40, 50, 50)) + self.typing.setGeometry(QtCore.QRect(self.width() - 450, 50, 50, 30)) - self.messageEdit.setGeometry(QtCore.QRect(60, 2, self.width() - 430, 55)) - self.menuButton.setGeometry(QtCore.QRect(0, 2, 55, 55)) - self.sendMessageButton.setGeometry(QtCore.QRect(self.width() - 360, 2, 60, 55)) + self.messageEdit.setGeometry(QtCore.QRect(55, 0, self.width() - 395, 55)) + self.menuButton.setGeometry(QtCore.QRect(0, 0, 55, 55)) + self.sendMessageButton.setGeometry(QtCore.QRect(self.width() - 340, 0, 70, 55)) - self.account_name.setGeometry(QtCore.QRect(100, 30, self.width() - 600, 25)) - self.account_status.setGeometry(QtCore.QRect(100, 50, self.width() - 520, 25)) + self.account_name.setGeometry(QtCore.QRect(100, 40, self.width() - 560, 25)) + self.account_status.setGeometry(QtCore.QRect(100, 60, self.width() - 560, 25)) self.messageEdit.setFocus() self.profile.update() @@ -391,9 +394,10 @@ class MainWindow(QtGui.QMainWindow): if hasattr(self, 'menu') and self.menu.isVisible(): self.menu.hide() return - self.menu = DropdownMenu(self) + elif not hasattr(self, 'menu'): + self.menu = DropdownMenu(self) self.menu.setGeometry(QtCore.QRect(0 if Settings.get_instance()['mirror_mode'] else 270, - self.height() - 160, + self.height() - 100, 150, 100)) self.menu.show() @@ -408,20 +412,18 @@ class MainWindow(QtGui.QMainWindow): def send_file(self): self.menu.hide() - if self.profile.is_active_online(): # active friend exists and online - choose_file = QtGui.QApplication.translate("MainWindow", 'Choose file', None, QtGui.QApplication.UnicodeUTF8) - choose = QtGui.QApplication.translate("MainWindow", choose_file, None, QtGui.QApplication.UnicodeUTF8) - name = QtGui.QFileDialog.getOpenFileName(self, choose) - if name[0]: - self.profile.send_file(name[0]) + choose_file = QtGui.QApplication.translate("MainWindow", 'Choose file', None, QtGui.QApplication.UnicodeUTF8) + choose = QtGui.QApplication.translate("MainWindow", choose_file, None, QtGui.QApplication.UnicodeUTF8) + name = QtGui.QFileDialog.getOpenFileName(self, choose) + if name[0]: + self.profile.send_file(name[0]) def send_screenshot(self, hide=False): self.menu.hide() - if self.profile.is_active_online(): # active friend exists and online - self.sw = ScreenShotWindow(self) - self.sw.show() - if hide: - self.hide() + self.sw = ScreenShotWindow(self) + self.sw.show() + if hide: + self.hide() def send_smiley(self): self.menu.hide() diff --git a/src/messages.py b/src/messages.py index 2208e70..c8234d0 100644 --- a/src/messages.py +++ b/src/messages.py @@ -80,6 +80,18 @@ class TransferMessage(Message): return self._file_name, self._size, self._time, self._owner, self._friend_number, self._file_number, self._status +class UnsentFile(Message): + def __init__(self, path, data, time): + super(UnsentFile, self).__init__(MESSAGE_TYPE['FILE_TRANSFER'], 0, time) + self._data, self._path = data, path + + def get_data(self): + return self._path, self._data, self._time + + def get_status(self): + return None + + class InlineImage(Message): """ Inline image diff --git a/src/profile.py b/src/profile.py index 2144072..293ce25 100644 --- a/src/profile.py +++ b/src/profile.py @@ -1,4 +1,4 @@ -from list_items import MessageItem, ContactItem, FileTransferItem, InlineImageItem +from list_items import * try: from PySide import QtCore, QtGui except ImportError: @@ -169,6 +169,9 @@ class Profile(contact.Contact, Singleton): friend.name if data[1] == MESSAGE_OWNER['FRIEND'] else self._name, data[3]) elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: + if message.get_status() is None: + self.create_unsent_file_item(message) + continue item = self.create_file_transfer_item(message) if message.get_status() >= 2: # active file transfer try: @@ -246,9 +249,23 @@ class Profile(contact.Contact, Singleton): # Friend connection status callbacks # ----------------------------------------------------------------------------------------------------------------- - def friend_online(self, friend_number): - for key in filter(lambda x: x[0] == friend_number, self._file_transfers.keys()): - self.resume_transfer(key[0], key[1], True) + def send_files(self, friend_number): + # for key in filter(lambda x: x[0] == friend_number, self._file_transfers.keys()): + # self.resume_transfer(key[0], key[1], True) + friend = self.get_friend_by_number(friend_number) + files = friend.get_unsent_files() + try: + for fl in files: + data = fl.get_data() + if data[1] is not None: + self.send_inline(data[1], data[0], friend_number, True) + else: + self.send_file(data[0], friend_number, True) + friend.clear_unsent_files() + if friend_number == self.get_active_number(): + self.update() + except: + pass def friend_exit(self, friend_number): """ @@ -483,6 +500,21 @@ class Profile(contact.Contact, Singleton): self._messages.setItemWidget(elem, item) return item + def create_unsent_file_item(self, message, append=True): + data = message.get_data() + item = UnsentFileItem(os.path.basename(data[0]), + os.path.getsize(data[0]) if data[1] is None else len(data[1]), + self.name, + data[2], + self._messages.width()) + elem = QtGui.QListWidgetItem() + elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34)) + if append: + self._messages.addItem(elem) + else: + self._messages.insertItem(0, elem) + self._messages.setItemWidget(elem, item) + def create_inline_item(self, data, append=True): item = InlineImageItem(data, self._messages.width()) elem = QtGui.QListWidgetItem() @@ -786,6 +818,10 @@ class Profile(contact.Contact, Singleton): else: self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL']) + def cancel_not_started_transfer(self, time): + self._friends[self._active_friend].delete_one_unsent_file(time) + self.update() + def pause_transfer(self, friend_number, file_number, by_friend=False): """ Pause transfer with specified data @@ -840,10 +876,16 @@ class Profile(contact.Contact, Singleton): data = fl.read() self.send_inline(data, 'sticker.png') - def send_inline(self, data, file_name): - friend = self._friends[self._active_friend] - if friend.status is None: + def send_inline(self, data, file_name, friend_number=None, is_resend=False): + friend_number = friend_number or self.get_active_number() + friend = self.get_friend_by_number(friend_number) + if friend.status is None and not is_resend: + m = UnsentFile(file_name, data, time.time()) + friend.append_message(m) + self.update() return + elif friend.status is None and is_resend: + raise Exception() st = SendFromBuffer(self._tox, friend.number, data, file_name) self._file_transfers[(friend.number, st.get_file_number())] = st tm = TransferMessage(MESSAGE_OWNER['ME'], @@ -858,15 +900,21 @@ class Profile(contact.Contact, Singleton): st.set_state_changed_handler(item.update) self._messages.scrollToBottom() - def send_file(self, path, number=None): + def send_file(self, path, number=None, is_resend=False): """ Send file to current active friend :param path: file path :param number: friend_number """ friend_number = number or self.get_active_number() - if self.get_friend_by_number(friend_number).status is None: + friend = self.get_friend_by_number(friend_number) + if friend.status is None and not is_resend: + m = UnsentFile(path, None, time.time()) + friend.append_message(m) + self.update() return + elif friend.status is None and is_resend: + raise Exception() st = SendTransfer(path, self._tox, friend_number) self._file_transfers[(friend_number, st.get_file_number())] = st tm = TransferMessage(MESSAGE_OWNER['ME'], diff --git a/src/smileys.py b/src/smileys.py index 2a2ad7e..ebcb115 100644 --- a/src/smileys.py +++ b/src/smileys.py @@ -72,7 +72,7 @@ class SmileyLoader(util.Singleton): def sticker_loader(): """ - :return dict of stickers + :return list of stickers """ result = [] d = util.curr_directory() + '/stickers/' diff --git a/src/styles/style.qss b/src/styles/style.qss index 75d0fce..0eddfde 100644 --- a/src/styles/style.qss +++ b/src/styles/style.qss @@ -1271,4 +1271,9 @@ QCheckBox QListWidget > QLabel { color: #A9A9A9; +} + +#contact_name +{ + padding-left: 22px; } \ No newline at end of file