class _LocatorDialog(QDialog): """Locator widget and implementation """ def __init__(self, parent, commandClasses): QDialog.__init__(self, parent) self._terminated = False self._commandClasses = commandClasses self._createUi() self._loadingTimer = QTimer(self) self._loadingTimer.setSingleShot(True) self._loadingTimer.setInterval(200) self._loadingTimer.timeout.connect(self._applyLoadingCompleter) self._completerLoaderThread = _CompleterLoaderThread(self) self.finished.connect(self._terminate) self._command = None self._updateCurrentCommand() def _createUi(self): self.setWindowTitle(core.project().path().replace(os.sep, '/') or 'Locator') self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().setSpacing(1) biggerFont = self.font() biggerFont.setPointSizeF(biggerFont.pointSizeF() * 2) self.setFont(biggerFont) self._edit = _CompletableLineEdit(self) self._edit.updateCurrentCommand.connect(self._updateCurrentCommand) self._edit.enterPressed.connect(self._onEnterPressed) self._edit.installEventFilter(self) # catch Up, Down self._edit.setFont(biggerFont) self.layout().addWidget(self._edit) self.setFocusProxy(self._edit) self._table = QTreeView(self) self._table.setFont(biggerFont) self._model = _CompleterModel() self._table.setModel(self._model) self._table.setItemDelegate(HTMLDelegate(self._table)) self._table.setRootIsDecorated(False) self._table.setHeaderHidden(True) self._table.clicked.connect(self._onItemClicked) self._table.setAlternatingRowColors(True) self._table.installEventFilter(self) # catch focus and give to the edit self.layout().addWidget(self._table) width = QFontMetrics(self.font()).width('x' * 64) # width of 64 'x' letters self.resize(width, width * 0.62) def _terminate(self): if not self._terminated: if self._command is not None: self._command.terminate() self._command = None self._edit.terminate() self._completerLoaderThread.terminate() if self._model: self._model.terminate() core.workspace().focusCurrentDocument() self._terminated = True def _updateCurrentCommand(self): """Try to parse line edit text and set current command """ if self._terminated: return newCommand = self._parseCurrentCommand() if newCommand is not self._command: if self._command is not None: self._command.updateCompleter.disconnect(self._updateCompletion) self._command.terminate() self._command = newCommand if self._command is not None: self._command.updateCompleter.connect(self._updateCompletion) self._updateCompletion() def _updateCompletion(self): """User edited text or moved cursor. Update inline and TreeView completion """ if self._command is not None: completer = self._command.completer() if completer is not None and completer.mustBeLoaded: self._loadingTimer.start() self._completerLoaderThread.loadCompleter(self._command, completer) else: self._applyCompleter(self._command, completer) else: self._applyCompleter(None, _HelpCompleter(self._commandClasses)) def _applyLoadingCompleter(self): """Set 'Loading...' message """ self._applyCompleter(None, StatusCompleter('<i>Loading...</i>')) def onCompleterLoaded(self, command, completer): """The method called from _CompleterLoaderThread when the completer is ready This code works in the GUI thread """ self._applyCompleter(command, completer) def _applyCompleter(self, command, completer): """Apply completer. Called by _updateCompletion or by thread function when Completer is constructed """ self._loadingTimer.stop() if command is not None: command.onCompleterLoaded(completer) if completer is None: completer = _HelpCompleter([command]) if self._edit.cursorPosition() == len(self._edit.text()): # if cursor at the end of text self._edit.setInlineCompletion(completer.inline()) self._model.setCompleter(completer) if completer.columnCount() > 1: self._table.resizeColumnToContents(0) self._table.setColumnWidth(0, self._table.columnWidth(0) + 20) # 20 px spacing between columns selItem = completer.autoSelectItem() if selItem: index = self._model.createIndex(selItem[0], selItem[1]) self._table.setCurrentIndex(index) def _onItemClicked(self, index): """Item in the TreeView has been clicked. Open file, if user selected it """ if self._command is not None: fullText = self._model.completer.getFullText(index.row()) if fullText is not None: self._command.onItemClicked(fullText) if self._tryExecCurrentCommand(): self.accept() return else: self._edit.setText(self._command.lineEditText()) self._updateCurrentCommand() self._edit.setFocus() def _onEnterPressed(self): """User pressed Enter or clicked item. Execute command, if possible """ if self._table.currentIndex().isValid(): self._onItemClicked(self._table.currentIndex()) else: self._tryExecCurrentCommand() def _tryExecCurrentCommand(self): if self._command is not None and self._command.isReadyToExecute(): self._command.execute() self.accept() return True else: return False def _chooseCommand(self, words): for cmd in self._commandClasses: if cmd.command == words[0]: return cmd, words[1:] isPath = words and (words[0].startswith('/') or words[0].startswith('./') or words[0].startswith('../') or words[0].startswith('~/') or words[0][1:3] == ':\\' or words[0][1:3] == ':/' ) isNumber = len(words) == 1 and all([c.isdigit() for c in words[0]]) def matches(cmd): if isPath: return cmd.isDefaultPathCommand elif isNumber: return cmd.isDefaultNumericCommand else: return cmd.isDefaultCommand for cmd in self._commandClasses: if matches(cmd): return cmd, words def _parseCurrentCommand(self): """ Parse text and try to get (command, completable word index) Return None if failed to parse """ # Split line text = self._edit.commandText() words = splitLine(text) if not words: return None # Find command cmdClass, args = self._chooseCommand(words) if isinstance(self._command, cmdClass): command = self._command else: command = cmdClass() # Try to make command object try: command.setArgs(args) except InvalidCmdArgs: return None else: return command def eventFilter(self, obj, event): if obj is self._edit: if event.type() == QEvent.KeyPress and \ event.key() in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown): return self._table.event(event) elif obj is self._table: if event.type() == QEvent.FocusIn: self._edit.setFocus() return True return False
class _LocatorDialog(QDialog): """Locator widget and implementation """ def __init__(self, parent, commandClasses): QDialog.__init__(self, parent) self._terminated = False self._commandClasses = commandClasses self._createUi() self._loadingTimer = QTimer(self) self._loadingTimer.setSingleShot(True) self._loadingTimer.setInterval(200) self._loadingTimer.timeout.connect(self._applyLoadingCompleter) self._completerLoaderThread = _CompleterLoaderThread(self) self.finished.connect(self._terminate) self._command = None self._updateCurrentCommand() def _createUi(self): self.setWindowTitle(core.project().path() or 'Locator') self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().setSpacing(1) biggerFont = self.font() biggerFont.setPointSizeF(biggerFont.pointSizeF() * 2) self.setFont(biggerFont) self._edit = _CompletableLineEdit(self) self._edit.updateCurrentCommand.connect(self._updateCurrentCommand) self._edit.enterPressed.connect(self._onEnterPressed) self._edit.installEventFilter(self) # catch Up, Down self.layout().addWidget(self._edit) self.setFocusProxy(self._edit) self._table = QTreeView(self) self._table.setFont(biggerFont) self._model = _CompleterModel() self._table.setModel(self._model) self._table.setItemDelegate(HTMLDelegate(self._table)) self._table.setRootIsDecorated(False) self._table.setHeaderHidden(True) self._table.clicked.connect(self._onItemClicked) self._table.setAlternatingRowColors(True) self._table.installEventFilter( self) # catch focus and give to the edit self.layout().addWidget(self._table) width = QFontMetrics(self.font()).width('x' * 64) # width of 64 'x' letters self.resize(width, width * 0.62) def _terminate(self): if not self._terminated: if self._command is not None: self._command.terminate() self._command = None self._edit.terminate() self._completerLoaderThread.terminate() core.workspace().focusCurrentDocument() self._terminated = True def _updateCurrentCommand(self): """Try to parse line edit text and set current command """ if self._terminated: return newCommand = self._parseCurrentCommand() if newCommand is not self._command: if self._command is not None: self._command.updateCompleter.disconnect( self._updateCompletion) self._command.terminate() self._command = newCommand if self._command is not None: self._command.updateCompleter.connect(self._updateCompletion) self._updateCompletion() def _updateCompletion(self): """User edited text or moved cursor. Update inline and TreeView completion """ if self._command is not None: completer = self._command.completer() if completer is not None and completer.mustBeLoaded: self._loadingTimer.start() self._completerLoaderThread.loadCompleter( self._command, completer) else: self._applyCompleter(self._command, completer) else: self._applyCompleter(None, _HelpCompleter(self._commandClasses)) def _applyLoadingCompleter(self): """Set 'Loading...' message """ self._applyCompleter(None, StatusCompleter('<i>Loading...</i>')) def onCompleterLoaded(self, command, completer): """The method called from _CompleterLoaderThread when the completer is ready This code works in the GUI thread """ self._applyCompleter(command, completer) def _applyCompleter(self, command, completer): """Apply completer. Called by _updateCompletion or by thread function when Completer is constructed """ self._loadingTimer.stop() if command is not None: command.onCompleterLoaded(completer) if completer is None: completer = _HelpCompleter([command]) if self._edit.cursorPosition() == len( self._edit.text()): # if cursor at the end of text self._edit.setInlineCompletion(completer.inline()) self._model.setCompleter(completer) if completer.columnCount() > 1: self._table.resizeColumnToContents(0) self._table.setColumnWidth(0, self._table.columnWidth(0) + 20) # 20 px spacing between columns selItem = completer.autoSelectItem() if selItem: index = self._model.createIndex(selItem[0], selItem[1]) self._table.setCurrentIndex(index) def _onItemClicked(self, index): """Item in the TreeView has been clicked. Open file, if user selected it """ if self._command is not None: fullText = self._model.completer.getFullText(index.row()) if fullText is not None: self._command.onItemClicked(fullText) if self._tryExecCurrentCommand(): self.accept() return else: self._edit.setText(self._command.lineEditText()) self._updateCurrentCommand() self._edit.setFocus() def _onEnterPressed(self): """User pressed Enter or clicked item. Execute command, if possible """ if self._table.currentIndex().isValid(): self._onItemClicked(self._table.currentIndex()) else: self._tryExecCurrentCommand() def _tryExecCurrentCommand(self): if self._command is not None and self._command.isReadyToExecute(): self._command.execute() self.accept() return True else: return False def _chooseCommand(self, words): for cmd in self._commandClasses: if cmd.command == words[0]: return cmd, words[1:] isPath = words and (words[0].startswith('/') or words[0].startswith('./') or words[0].startswith('../') or words[0].startswith('~/') or words[0][1:3] == ':\\' or words[0][1:3] == ':/') isNumber = len(words) == 1 and all([c.isdigit() for c in words[0]]) def matches(cmd): if isPath: return cmd.isDefaultPathCommand elif isNumber: return cmd.isDefaultNumericCommand else: return cmd.isDefaultCommand for cmd in self._commandClasses: if matches(cmd): return cmd, words def _parseCurrentCommand(self): """ Parse text and try to get (command, completable word index) Return None if failed to parse """ # Split line text = self._edit.commandText() words = splitLine(text) if not words: return None # Find command cmdClass, args = self._chooseCommand(words) if isinstance(self._command, cmdClass): command = self._command else: command = cmdClass() # Try to make command object try: command.setArgs(args) except InvalidCmdArgs: return None else: return command def eventFilter(self, obj, event): if obj is self._edit: if event.type() == QEvent.KeyPress and \ event.key() in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown): return self._table.event(event) elif obj is self._table: if event.type() == QEvent.FocusIn: self._edit.setFocus() return True return False
class App(QWidget): FROM, TO, DATE, SUBJECT, MESSAGE = range(5) def __init__(self, mails_lst=[], my_mail="", send=None, receive=None, labels=None): super().__init__() # self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.sendClass = send self.receiveClass = receive self.labelsClass = labels self.mails_lst = mails_lst self.mails = mails_lst[1] self.my_mail = my_mail self.labels = ["All", "Inbox", "Sent", "Trash"] self.title = 'SMTP Email Client' self.left = 0 self.top = 0 self.width = 1024 self.height = 600 self.total_cols = 5 self.ret_val = False self.is_first_ret_val = True self.is_reload_mails = False self.messages_file = 'messages.data' self.initUI() def toolbarButtonClick(self, i): def buttonClick(): if self.send_button.isChecked(): self.send_button.setChecked(False) self.sendMenuToggleClick(False) # for l in self.label_buttons: # print(l.isChecked()) if self.label_buttons[i].isChecked(): # if self.is_reload_mails: # self.reloadMails() # print("mails reloaded") # self.is_reload_mails = False print(f"displaying label category '{self.labels[i]}'") for index, label_button in enumerate(self.label_buttons): if index != i: label_button.setChecked(False) self.mails = self.mails_lst[i] self.reloadMails() else: self.label_buttons[i].setChecked(True) return buttonClick def parallelReloading(self): temp_mails_lst = [] for label in self.labelsClass: temp_mails_lst.append( self.receiveClass.get_message(category=label)) index = [label.isChecked() for label in self.label_buttons].index(True) self.mails_lst = temp_mails_lst self.mails = temp_mails_lst[index] write_to_file(self.messages_file, self.mails_lst) self.is_reload_mails = True def parallelSending(self, to_addr, subj, msg): self.sendClass.send_message(to_addr, subj, msg) def reloadButtonClick(self, s): if self.receiveClass: if self.is_first_ret_val: print("reloading all mails") self.ret_val = threading.Thread(target=self.parallelReloading, args=()) self.ret_val.start() self.is_first_ret_val = False elif not self.ret_val.isAlive(): print("reloading all mails") self.ret_val = threading.Thread(target=self.parallelReloading, args=()) self.ret_val.start() else: print("reloading already taking place in background") else: print("unable to reload as 'receiveClass' missing") self.reload_button.setChecked(False) def sendMenuToggleClick(self, s): if s: self.toBox.setFixedWidth(self.contentView.width()) self.contentView.hide() self.toBox.show() self.subjectBox.show() self.messageBox.show() self.sendButtonBox.show() else: self.toBox.hide() self.subjectBox.hide() self.messageBox.hide() self.sendButtonBox.hide() self.contentView.show() def logoutButtonClick(self, s): print("loging out and closing app") sys.exit() def rowSelectionClick(self): if self.send_button.isChecked(): self.send_button.setChecked(False) self.sendMenuToggleClick(False) self.dataView.showColumn(1) self.dataView.showColumn(4) item = self.dataView.selectedIndexes() lst = [] for i in range(self.total_cols): text = item[i].model().itemFromIndex(item[i]).text() if i == 0 or i == 1: text = text.split() lst.append(text) self.dataView.hideColumn(1) self.dataView.hideColumn(4) msg = self.htmlString(lst) self.contentView.setPlainText("") self.contentView.textCursor().insertHtml(msg) def sendButtonClick(self): if self.sendClass: self.reloadButtonClick(True) # to_addr = re.split(r'[, ]', self.toBox.text()) to_addr = re.findall(r'[\w\.-]+@[\w\.-]+', self.toBox.text()) subj = self.subjectBox.text() msg = self.messageBox.toPlainText() send_ret_val = threading.Thread(target=self.parallelSending, args=(to_addr, subj, msg)) send_ret_val.start() else: print("unable to send as 'sendClass' missing") self.toBox.setText("") self.subjectBox.setText("") self.messageBox.setPlainText("") self.sendMenuToggleClick(False) self.send_button.setChecked(False) def initUI(self): # windows title and geometry set self.setWindowTitle(self.title) self.setGeometry(self.left, self.top, self.width, self.height) self.globalLayout = QVBoxLayout() menuLayout = QHBoxLayout() dataLayout = QHBoxLayout() labelLayout = QToolBar("Labels") self.label_buttons = [QAction(label, self) for label in self.labels] # button_action.setStatusTip("This is your button") for i, label_button in enumerate(self.label_buttons): label_button.triggered.connect(self.toolbarButtonClick(i)) labelLayout.addAction(label_button) label_button.setCheckable(True) # labelLayout.addAction(label_button) self.label_buttons[1].setChecked(True) optionLayout = QToolBar("Options") self.send_button = QAction(QIcon("images/icons8-email-60.png"), "Send Mail", self) self.reload_button = QAction(QIcon("images/icons8-reset-60.png"), "Reload Page", self) logout_button = QAction(QIcon("images/icons8-shutdown-60.png"), "Logout", self) self.send_button.triggered.connect(self.sendMenuToggleClick) self.reload_button.triggered.connect(self.reloadButtonClick) logout_button.triggered.connect(self.logoutButtonClick) optionLayout.addAction(self.send_button) optionLayout.addAction(self.reload_button) optionLayout.addAction(logout_button) self.send_button.setCheckable(True) self.reload_button.setCheckable(True) logout_button.setCheckable(True) # w1.setContentsMargins(0, 0, 0, 0) # w2.setContentsMargins(0, 0, 0, 0) # menuLayout.setSpacing(0) menuLayout.setContentsMargins(0, 0, 0, 0) optionLayout.setFixedWidth(106) menuLayout.addWidget(labelLayout, 10) menuLayout.addWidget(QLabel(self.my_mail), 1) menuLayout.addWidget(optionLayout) # dataview with non editable columns (from, date, subject etc) self.dataView = QTreeView() self.dataView.setRootIsDecorated(False) self.dataView.setAlternatingRowColors(True) self.dataView.setEditTriggers(QAbstractItemView.NoEditTriggers) # content view to display complete email message self.contentView = QPlainTextEdit() self.contentView.setReadOnly(True) self.sendLayout = QVBoxLayout() self.toBox = QLineEdit() self.subjectBox = QLineEdit() self.messageBox = QPlainTextEdit() self.sendButtonBox = QPushButton("Send") self.toBox.setPlaceholderText("To") self.subjectBox.setPlaceholderText("Subject") self.messageBox.setPlaceholderText("Message") self.sendLayout.addWidget(self.toBox) self.sendLayout.addWidget(self.subjectBox) self.sendLayout.addWidget(self.messageBox) self.sendLayout.addWidget(self.sendButtonBox) self.sendLayout.setSpacing(0) self.sendLayout.setContentsMargins(0, 0, 0, 0) # set layout of columns and content box horizontally dataLayout.addWidget(self.dataView, 3) dataLayout.addWidget(self.contentView, 2) dataLayout.addLayout(self.sendLayout) self.contentView.show() self.toBox.hide() self.subjectBox.hide() self.messageBox.hide() self.sendButtonBox.hide() self.sendButtonBox.clicked.connect(self.sendButtonClick) # create mail model to add to data view self.model = self.createMailModel(self) self.dataView.setModel(self.model) self.dataView.clicked.connect(self.rowSelectionClick) self.globalLayout.addLayout(menuLayout, 1) self.globalLayout.addLayout(dataLayout, 20) self.setLayout(self.globalLayout) self.addAllMails() self.autoColumnWidths() self.show() self.reloadButtonClick(True) def reloadMails(self): # self.mails = mails self.model.removeRows(0, self.model.rowCount()) self.addAllMails() self.autoColumnWidths() # set headers text for the created model def createMailModel(self, parent): model = QStandardItemModel(0, self.total_cols, parent) model.setHeaderData(self.FROM, Qt.Horizontal, "From") model.setHeaderData(self.TO, Qt.Horizontal, "To") model.setHeaderData(self.DATE, Qt.Horizontal, "Date") model.setHeaderData(self.SUBJECT, Qt.Horizontal, "Subject") model.setHeaderData(self.MESSAGE, Qt.Horizontal, "Message") return model # add content of mail to data view def addAllMails(self): today = date.today() today_date = today.strftime('%a, %d %b %Y') for mail in self.mails: if today_date == mail[2]: date_temp = mail[3] else: date_temp = mail[2] self.addMail(self.model, mail[0], mail[1], date_temp, mail[4], mail[5]) if self.mails: # msg = self.htmlString( # [self.mails[-1][0], self.mails[-1][1], self.mails[-1][4], self.mails[-1][5]]) msg = self.htmlString(self.mails[-1]) self.contentView.setPlainText("") self.contentView.textCursor().insertHtml(msg) def addMail(self, model, mailFrom, mailTo, date, subject, message): model.insertRow(0) mailFrom = ' '.join(map(str, mailFrom)) mailTo = ' '.join(map(str, mailTo)) model.setData(model.index(0, self.FROM), mailFrom) model.setData(model.index(0, self.TO), mailTo) model.setData(model.index(0, self.DATE), date) model.setData(model.index(0, self.SUBJECT), subject) model.setData(model.index(0, self.MESSAGE), message) def autoColumnWidths(self): width_plus = 30 self.dataView.setColumnWidth(1, 0) self.dataView.setColumnWidth(4, 0) for i in range(self.total_cols): self.dataView.resizeColumnToContents(i) width = self.dataView.columnWidth(i) self.dataView.setColumnWidth(i, width + width_plus) self.dataView.hideColumn(1) self.dataView.hideColumn(4) def htmlString(self, lst): fr_addr = "<b>From</b><br>" for l in lst[0]: fr_addr += f"{l}<br>" fr_addr += "<br>" to_addr = "<b>To</b><br>" for l in lst[1]: to_addr += f"{l}<br>" to_addr += "<br>" subject = f"<b>Subject</b><br>{lst[-2]}<br><br>" message = lst[-1].replace("\n", "<br>") message = f"<b>Message</b><br>{message}<br><br>" return fr_addr + to_addr + subject + message