from PyQt5.QtGui import QTextCursor from PyQt5.QtWidgets import QPlainTextEdit, QWidget, QPushButton, QFileDialog, QVBoxLayout class ConsoleWindow(QWidget): def __init__(self): super(ConsoleWindow, self).__init__() self.layout = QVBoxLayout() self.setGeometry(0, 0, 600, 400) self.console = Console() self.layout.addWidget(self.console) self.save_button = QPushButton(self.tr("Save output to file")) self.layout.addWidget(self.save_button) self.save_button.clicked.connect(self.save) self.setLayout(self.layout) def save(self): file, ok = QFileDialog.getSaveFileName(self, "Save output", "", "Log Files (*.log);;All Files (*)") if ok: if "." not in file: file += ".log" with open(file, "w") as f: f.write(self.console.toPlainText()) f.close() self.save_button.setText(self.tr("Saved")) def log(self, text): self.console.log(text) def error(self, text): self.console.error(text) class Console(QPlainTextEdit): def __init__(self): super().__init__() self.setReadOnly(True) self._cursor_output = self.textCursor() def log(self, text): self._cursor_output.insertText(text) self.scroll_to_last_line() def error(self, text): self._cursor_output.insertHtml(f"{text}") self.scroll_to_last_line() def scroll_to_last_line(self): cursor = self.textCursor() cursor.movePosition(QTextCursor.End) cursor.movePosition(QTextCursor.Up if cursor.atBlockStart() else QTextCursor.StartOfLine) self.setTextCursor(cursor)