Esempio n. 1
0
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
Esempio n. 2
0
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
Esempio n. 3
0
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