def showFolder(self, path): "displays current path, truncating as needed" if not path: return ell, sl = '\u2026', os.path.sep # ellipsis, slash chars lfg, rfg = Qt.ElideLeft, Qt.ElideRight lst, wdh = os.path.basename(path), self.folderLabel.width() path = path.replace(os.path.altsep or '\\', sl) self.folderLabel.setToolTip(path) # truncate folder location fnt = QFontMetrics(self.folderLabel.font()) txt = str(fnt.elidedText(path, lfg, wdh)) if len(txt) <= 1: # label is way too short self.folderLabel.setText('\u22ee' if txt != sl else txt) return # but when would this happen? # truncate some more (don't show part of a folder name) if len(txt) < len(path) and txt[1] != sl: txt = ell + sl + txt.split(sl, 1)[-1] # don't truncate remaining folder name from the left if txt[2:] != lst and len(txt[2:]) < len(lst) + 2: txt = str(fnt.elidedText(ell + sl + lst, rfg, wdh)) # you'd think len(txt) < len(lst) would work, but no; you'd be wrong self.folderLabel.setText(txt)
def elidePath(self): "displays current path, truncating as needed" lfg, rfg = Qt.ElideLeft, Qt.ElideRight label = self.folderLabel wdh = label.width() el, sl = u'\u2026', os.sep # ellipsis, slash chrs base, sub = self.base, self.sub if self._pht and not sub: # using placeholder text as sub folder sub = str(self.subfolderInput.placeholderText()) use_sub = self.subfolderCB.isChecked() if not base: return # assemble path, determine bottom (last) folder if not sub or not use_sub: path, lst = base, os.path.basename(base) else: path, lst = os.path.join(base, sub), sub path = path.replace(os.altsep or '\\', sl) # truncate folder location fnt = QFontMetrics(self.folderLabel.font()) txt = str(fnt.elidedText(path, lfg, wdh)) if len(txt) <= 1: # label is way too short label.setToolTip(path) #; label.setText(u'\u22ee') label.setText(u'\u22ee' if txt != sl else txt) return # nothing more can be done about this, so... # truncate some more (don't show part of a folder name) if len(txt) < len(path) and txt[1] != sl: txt = el + sl + txt.split(sl, 1)[-1] # don't truncate remaining folder name from the left if txt[2:] != lst and len(txt) - 4 < len(lst): txt = str(fnt.elidedText(el + sl + lst, rfg, wdh)) if sub: sub = txt.split(sl, 1)[-1] if use_sub and sub: # highlight sub folder (if there's one) col = u'<span style="color:#ff5500;">{}</span>' #cd4400 txt = col.format(sub).join(txt.rsplit(sub, 1)) path = col.format(lst).join(path.rsplit(lst, 1)) self.subfolderInput.setToolTip(sub) label.setText(txt) label.setToolTip(path)
def updateText(self): count_tracks = 0 count_languages = 0 tracks = [] languages = [] tracks_text = [] languages_text = [] text = "" for i in range(self.model().rowCount()): if self.model().item(i).checkState() == Qt.Checked: if self.model().item(i).text().find("Track") != -1: count_tracks += 1 tracks.append(self.model().item(i).text()) tracks_text.append( self.model().item(i).text().split(" ")[1]) elif self.model().item(i).text() in AllSubtitlesLanguages: count_languages += 1 languages.append(self.model().item(i).text()) languages_text.append(self.model().item(i).text()) self.tracks = tracks_text self.languages = languages_text if count_tracks > 0: tracks_text = "Tracks: [" + ", ".join(tracks_text) + "]" text = tracks_text if count_languages > 0: languages_text = "Languages: [" + ", ".join(languages_text) + "]" if text != "": text = text + "," + languages_text else: text = languages_text # Compute elided text (with "...") metrics = QFontMetrics(self.lineEdit().font()) elided_text = metrics.elidedText(text, Qt.ElideRight, self.lineEdit().width()) if elided_text != "": non_italic_font = self.lineEdit().font() non_italic_font.setItalic(False) self.lineEdit().setFont(non_italic_font) self.lineEdit().setText(elided_text) if count_tracks > 0: self.hint = tracks_text if count_languages > 0: self.hint = self.hint + "<br>" + languages_text elif count_languages > 0: self.hint = languages_text else: italic_font = self.lineEdit().font() italic_font.setItalic(True) self.lineEdit().setFont(italic_font) self.lineEdit().setText(self.empty_selection_string) self.hint = self.empty_selection_hint_string self.setToolTip(self.hint)
def updateText(self): texts = [] for i in range(self.model().rowCount()): if self.model().item(i).checkState() == Qt.Checked: texts.append(self.model().item(i).text()) text = ", ".join(texts) # Compute elided text (with "...") metrics = QFontMetrics(self.lineEdit().font()) elidedText = metrics.elidedText(text, Qt.ElideRight, self.lineEdit().width()) self.lineEdit().setText(elidedText)
def resize_column(self, column_index): if column_index == self.column_ids["Name"]: for i in range(self.rowCount()): metrics = QFontMetrics( self.cellWidget(i, self.column_ids["Name"]).font()) elided_text = metrics.elidedText( self.data[i].video_name_with_spaces, Qt.ElideRight, self.columnWidth(self.column_ids["Name"])) self.data[i].video_name_displayed = chr(0x200E) + elided_text self.cellWidget(i, self.column_ids["Name"]).setText( self.data[i].video_name_displayed) self.update()
def elidePath(self): "displays current path, truncating as needed" Lflg, Rflg = Qt.ElideLeft, Qt.ElideRight labl = self.pathLabel vl, el, sl = u'\u22ee', u'\u2026', os.sep path, wdth = self.path, labl.width() base = os.path.basename(path) # truncate folder location font = QFontMetrics(labl.font()) text = str(font.elidedText(path, Lflg, wdth)) if len(text) <= 1: labl.setText(vl if text != sl else text) return # truncate some more (don't show part of a folder name) if len(text) < len(path) and text[1] != sl: text = el + sl + text.split(sl, 1)[-1] # don't truncate remaining folder name from the left if text[2:] != base and len(text) - 4 < len(base): text = str(font.elidedText(el + sl + base, Rflg, wdth)) labl.setText(text)
def updateText(self): extensions_text = [] for i in range(self.model().rowCount()): if self.model().item(i).checkState() == Qt.Checked: extensions_text.append(self.model().item(i).text()) text = ', '.join(extensions_text) # Compute elided text (with "...") metrics = QFontMetrics(self.lineEdit().font()) elided_text = metrics.elidedText(text, Qt.ElideRight, self.lineEdit().width()) if elided_text != "": non_italic_font = self.lineEdit().font() non_italic_font.setItalic(False) self.lineEdit().setFont(non_italic_font) self.lineEdit().setText(elided_text) self.hint = "<nobr>Extensions: [" + text + "]" self.setToolTip(self.hint)
def paintEvent(self, _: QPaintEvent) -> None: """Qt Paint Event: ensures the title-bar text is elided properly and stays centered""" painter = QPainter() painter.begin(self) painter.setPen(self.__font_pen) # bold font for macOS window titles font = self.font() if sys.platform in ["darwin", "linux"]: font.setBold(True) font_metrics = QFontMetrics(font) # calculate text properties text_width: int = self.width( ) - self.__button_bar_width - self.__margin text: str = font_metrics.elidedText(self.__original_text, Qt.ElideRight, text_width) # calculate height br: QRect = font_metrics.boundingRect(text) if br.height() > 0: self.__br_height = br.height() py = self.__ofs_y - self.__br_height / 2.0 br_width = br.width() # calculate width px = (self.width() - br_width - WIDTH_PADDING_PX) / 2.0 if px < self.__button_bar_width: if self.__window_buttons_position == WINDOW_BUTTONS_RIGHT: if text != self.__original_text: px = self.__margin else: px = px - (self.__button_bar_width - px) else: px = self.__button_bar_width # draw title rect = QRectF(px, py, br_width + WIDTH_PADDING_PX, self.__br_height) painter.setFont(font) if DEBUG: painter.setBrush(QBrush(QColor('#ff0000'))) painter.drawRect(rect) painter.drawText(rect, Qt.AlignLeft, text)
class MainWindow(QMainWindow): def __init__(self, model, logic): QMainWindow.__init__(self) self.logger = logging.getLogger(__name__) self.model = model self.logic = logic self.ui = Ui_MainWindow_Extend() self.ui.setupUi(self) self.close_window = CloseWindow() self.preview_window = PreviewWindow() self.ui.receiver_file_label.setVisible(False) self.ui.status.setText('You haven\'t sent anything!') self.fm = QFontMetrics( self.ui.to_email_label.font()) # for setting text ellipsis # self.ui.msg_entry.installEventFilter(self) compulsory_list = [] compulsory_list.append(self.ui.email_column_combo) compulsory_list.append(self.ui.receiver_file_label) compulsory_list.append(self.ui.my_password_entry) compulsory_list.append(self.ui.my_email_entry) optional_list = [] optional_list.append(self.ui.subject_entry) optional_list.append(self.ui.msg_entry) optional_list.append(self.ui.personalised_att_combo) # listen for ui event signals self.ui.msg_entry.cursorPositionChanged.connect(self.adjust_slider) self.ui.send_mail_btn.clicked.connect(self.switch_to_email_pg) self.ui.status_btn.clicked.connect(self.switch_to_status_pg) # connect ui to logic self.ui.my_email_entry.setFocus() self.hide_error_message() self.ui.browse_file_btn.clicked.connect(self.browse_file) self.ui.attach_btn.clicked.connect(self.attach_file) self.ui.send_btn.clicked.connect( lambda: self.send_email(compulsory_list, optional_list, True)) self.ui.preview_btn.clicked.connect( lambda: self.open_preview_window(compulsory_list, optional_list)) # listen for logic event signals getattr(self.logic, 'signal_' + str(4)).connect(lambda: self.show_no_subject_pop_up( compulsory_list, optional_list)) getattr(self.logic, 'signal_' + str(0)).connect(self.show_email_column_error) getattr(self.logic, 'signal_' + str(1)).connect(self.show_receiver_email_error) getattr(self.logic, 'signal_' + str(2)).connect(self.show_password_error) getattr(self.logic, 'signal_' + str(3)).connect(self.show_my_email_error) self.logic.attachment_limit_reached.connect( self.ui.invalid_byte_size_error.show) self.logic.sending_start.connect(self.show_sending_start) self.logic.success_email.connect(self.update_successful_status) self.logic.fail_email.connect(self.update_fail_status) self.logic.sending_done.connect(self.show_sending_finish) self.logic.send_progress.connect(self.update_progress_bar) self.logic.estimate_time_finish.connect( self.update_estimated_time_finish) def update_estimated_time_finish(self, duration): num_hour = int(duration / 3600) num_minute = int((duration % 3600) / 60) if duration == 0: self.ui.estimated_time_finish_label.setText("") elif duration < 60: self.ui.estimated_time_finish_label.setText( 'Estimated time remaining: less than 1 minute') elif num_hour == 0: self.ui.estimated_time_finish_label.setText( f'Estimated time remaining: {num_minute} minute(s)') else: self.ui.estimated_time_finish_label.setText( f'Estimated time remaining: {num_hour} hour(s) {num_minute} minute(s)' ) def open_preview_window(self, compulsory_list, optional_list): self.hide_error_message() self.preview_window.show() compulsory_text = self.get_compulsory_text(compulsory_list) optional_text = self.get_optional_text(optional_list) subject_preview, message_preview = self.logic.show_preview( compulsory_text, optional_text) self.preview_window.ui.subject_preview.setText(subject_preview) self.preview_window.ui.msg_preview.setText(message_preview) def switch_to_email_pg(self): self.ui.stackedWidget.setCurrentWidget(self.ui.mail_page) self.ui.label.setText("Send Email") def switch_to_status_pg(self): self.ui.stackedWidget.setCurrentWidget(self.ui.status_page) self.ui.label.setText("Send Status") def closeEvent(self, event): if self.logic.threadpool.activeThreadCount() > 0: button_clicked = self.ui.show_exit_confirmation() if button_clicked == QMessageBox.Yes: self.logger.info("User terminate program") self.close_window.show() self.hide() QApplication.processEvents() self.logic.kill_thread() while self.logic.threadpool.activeThreadCount() != 0: QApplication.processEvents() self.close_window.close() event.accept() else: event.ignore() else: self.logger.info('Close program without active thread') event.accept() def update_progress_bar(self, progress): self.ui.progress_bar.setValue(progress) def show_sending_finish(self, num_success, num_fail): self.ui.send_btn.setEnabled(True) self.ui.status.moveCursor(QTextCursor.End) self.ui.status.insertHtml( ('<br>All emails sent!' '<br><b>Number of successful sends: {}' '<br>Number of fail sends: {}</b>' '<br>Failed emails saved as \'unsuccessful_emails.xlsx\' ' 'on Desktop').format(num_success, num_fail)) self.ui.status.moveCursor(QTextCursor.End) def show_sending_start(self): self.ui.progress_bar.setValue(0) self.ui.send_btn.setDisabled(True) self.ui.status.setText('Sending emails...') self.ui.status_btn.click() self.ui.stackedWidget.setCurrentWidget(self.ui.status_page) def update_successful_status(self, email): self.ui.status.moveCursor(QTextCursor.End) self.ui.status.insertPlainText('\nsuccess: {}'.format(email)) self.ui.status.moveCursor(QTextCursor.End) def update_fail_status(self, email): self.ui.status.moveCursor(QTextCursor.End) self.ui.status.insertPlainText('\nfail: {}'.format(email)) self.ui.status.moveCursor(QTextCursor.End) def focus_on_attachment(self): position = self.ui.scrollArea.verticalScrollBar().maximum() self.ui.scrollArea.verticalScrollBar().setValue(position) # self.ui.scrollArea.verticalScrollBar().setValue(self.ui.attachment_grid.contentsRect().bottom()) def truncate_email_label_text(self): email_label = os.path.basename(self.ui.receiver_file_label.text()) label_width = self.ui.to_email_label.width() self.ui.to_email_label.setText( self.fm.elidedText(email_label, Qt.ElideRight, label_width)) # def eventFilter(self, source, event): # # adjust msg_entry height when it is resized by maximising or minimising window # if event.type() == QEvent.Resize and source is self.ui.msg_entry: # self.adjust_height() # return super().eventFilter(source, event) # overrides resizeEvent of QMainWindow def resizeEvent(self, event): super().resizeEvent(event) self.adjust_height() self.truncate_email_label_text() def adjust_slider(self): self.adjust_height() position = self.ui.msg_entry.cursorRect() # wait for 1 millisecond (for scroll bar height to re-adjust) before setting vertical scroll bar value QTimer.singleShot( 1, partial(self.ui.scrollArea.ensureVisible, position.x(), position.y(), 0, 30)) # less ideal method # self.ui.scrollArea.verticalScrollBar().setValue(self.ui.msg_entry.cursorRect().top()) def adjust_height(self): # expand height to remove scroll bar of QTextEdit height = self.ui.msg_entry.document().size().height() self.ui.msg_entry.setMinimumHeight(height) def get_compulsory_text(self, compulsory_list): compulsory_text = [] for ele in compulsory_list: if isinstance(ele, QComboBox): compulsory_text.append(ele.currentText()) else: compulsory_text.append(ele.text()) return compulsory_text def get_optional_text(self, optional_list): optional_text = [] for ele in optional_list: if isinstance(ele, QTextEdit): optional_text.append(ele.toPlainText()) elif isinstance(ele, QComboBox): optional_text.append(ele.currentText()) else: optional_text.append(ele.text()) return optional_text def browse_file(self): receiver_file_name, _ = QFileDialog.getOpenFileName( self, 'Select A File', os.path.join(os.environ["HOMEPATH"], "Desktop"), "excel files (*.xlsx)") if not receiver_file_name: return self.model.receiver_file_name = receiver_file_name label_width = self.ui.to_email_label.width() self.ui.to_email_label.setText( self.fm.elidedText( receiver_file_name.split('/')[-1], Qt.ElideRight, label_width)) self.ui.receiver_file_label.setText(receiver_file_name) receiver_df = pd.read_excel(receiver_file_name) self.update_combo_box(self.ui.email_column_combo, receiver_df.columns) self.update_combo_box(self.ui.personalised_att_combo, receiver_df.columns) def update_combo_box(self, combo_box, column_names): if combo_box.count() > 1: for idx in range(combo_box.count() - 1, 0, -1): combo_box.removeItem(idx) combo_box.addItems(column_names) def attach_file(self): attach_file_name, _ = QFileDialog.getOpenFileName( self, 'Select A File', os.path.join(os.environ["HOMEPATH"], "Desktop"), "all files (*.*)") if not attach_file_name: return attachment_label = AttachmentLabel(attach_file_name) self.ui.attachment_grid.addWidget(attachment_label) # wait for 10 milliseconds (for scroll bar ui to update) before setting vertical scroll bar value QTimer.singleShot(10, self.focus_on_attachment) def hide_error_message(self): self.ui.email_address_error.setVisible(False) self.ui.no_password_error.setVisible(False) self.ui.to_error.setVisible(False) self.ui.invalid_col_error.setVisible(False) self.ui.invalid_byte_size_error.setVisible(False) def send_email(self, compulsory_list, optional_list, send_with_subject): self.hide_error_message() compulsory_text = self.get_compulsory_text(compulsory_list) optional_text = self.get_optional_text(optional_list) self.logic.send_email(compulsory_text, optional_text, send_with_subject) def show_email_column_error(self): self.ui.invalid_col_error.show() def show_receiver_email_error(self): self.ui.to_error.show() def show_password_error(self): self.ui.no_password_error.show() self.ui.my_password_entry.setFocus() def show_my_email_error(self): self.ui.email_address_error.show() self.ui.my_email_entry.setFocus() def show_no_subject_pop_up(self, compulsory_list, optional_list): button_clicked = self.ui.show_no_subject_pop_up() if button_clicked == QMessageBox.Yes: self.send_email(compulsory_list, optional_list, False)
def elide_text(label: QLabel, text: str): metrics = QFontMetrics(label.font()) return metrics.elidedText(text, Qt.ElideRight, label.width())
def msg_signal_slot(self, msg: str): fontWidth = QFontMetrics(self.progress_msg.font()) elideNote = fontWidth.elidedText( msg, Qt.ElideRight, self.name.width() + self.action.width() - 30) self.progress_msg.setText(elideNote)
def elided(text, widget, width=None): metrix = QFontMetrics(widget.font()) if width is None: width = widget.width() elided_text = metrix.elidedText(text, Qt.ElideMiddle, width) return elided_text