feat: console log dock
This commit is contained in:
parent
ea352a5de5
commit
1d21006281
1 changed files with 108 additions and 0 deletions
108
src/otko/views/docks/console.py
Normal file
108
src/otko/views/docks/console.py
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
# Ported from otko-development (MIT), (c) 2026 OTKO contributors
|
||||||
|
"""The console dock: the timestamped analysis log."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from PySide6.QtGui import QFont, QFontDatabase
|
||||||
|
from PySide6.QtWidgets import (
|
||||||
|
QApplication,
|
||||||
|
QDockWidget,
|
||||||
|
QHBoxLayout,
|
||||||
|
QPlainTextEdit,
|
||||||
|
QPushButton,
|
||||||
|
QVBoxLayout,
|
||||||
|
QWidget,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = ["ConsoleDock"]
|
||||||
|
|
||||||
|
#: Recognised severities; anything else is treated as information.
|
||||||
|
_SEVERITIES = ("info", "warning", "error", "success")
|
||||||
|
|
||||||
|
|
||||||
|
class ConsoleDock(QDockWidget):
|
||||||
|
"""Show analysis log lines with a timestamp and severity label.
|
||||||
|
|
||||||
|
The dock never decides anything about the model: it only renders text the
|
||||||
|
viewmodel or worker emits. Lines are appended, so the log is a faithful
|
||||||
|
running record of the session.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parent: QWidget | None = None) -> None:
|
||||||
|
"""Create the console dock with a read-only, monospaced log view."""
|
||||||
|
super().__init__("Console", parent)
|
||||||
|
self.setObjectName("consoleDock")
|
||||||
|
|
||||||
|
self._view = QPlainTextEdit(self)
|
||||||
|
self._view.setReadOnly(True)
|
||||||
|
self._view.setObjectName("consoleView")
|
||||||
|
self._view.setMaximumBlockCount(5000)
|
||||||
|
self._view.setPlaceholderText("Logs and OpenSeesPy output will appear here.")
|
||||||
|
self._view.setFont(self._monospace_font())
|
||||||
|
|
||||||
|
clear_button = QPushButton("Clear", self)
|
||||||
|
clear_button.setToolTip("Clear the console log")
|
||||||
|
clear_button.setStatusTip("Clear the console log")
|
||||||
|
clear_button.clicked.connect(self.clear)
|
||||||
|
copy_button = QPushButton("Copy All", self)
|
||||||
|
copy_button.setToolTip("Copy the whole log to the clipboard")
|
||||||
|
copy_button.setStatusTip("Copy the whole log to the clipboard")
|
||||||
|
copy_button.clicked.connect(self._copy_all)
|
||||||
|
|
||||||
|
buttons = QHBoxLayout()
|
||||||
|
buttons.addStretch(1)
|
||||||
|
buttons.addWidget(copy_button)
|
||||||
|
buttons.addWidget(clear_button)
|
||||||
|
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
layout.setContentsMargins(4, 4, 4, 4)
|
||||||
|
layout.setSpacing(4)
|
||||||
|
layout.addWidget(self._view, 1)
|
||||||
|
layout.addLayout(buttons)
|
||||||
|
|
||||||
|
container = QWidget(self)
|
||||||
|
container.setLayout(layout)
|
||||||
|
self.setWidget(container)
|
||||||
|
|
||||||
|
# -- API -----------------------------------------------------------
|
||||||
|
|
||||||
|
def log(self, message: str, severity: str = "info") -> None:
|
||||||
|
"""Append ``message`` with a timestamp and ``severity`` tag.
|
||||||
|
|
||||||
|
Every line carries a wall-clock time and a severity so a failure is
|
||||||
|
easy to spot after a run.
|
||||||
|
"""
|
||||||
|
tag = severity if severity in _SEVERITIES else "info"
|
||||||
|
stamp = datetime.now().strftime("%H:%M:%S")
|
||||||
|
self._view.appendPlainText(f"{stamp} [{tag.upper():<7}] {message}")
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
"""Remove every line from the log."""
|
||||||
|
self._view.clear()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def text(self) -> str:
|
||||||
|
"""Return the full log text (used by tests and copy-all)."""
|
||||||
|
return self._view.toPlainText()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def view(self) -> QPlainTextEdit:
|
||||||
|
"""Return the inner text view (for legacy append-only call sites)."""
|
||||||
|
return self._view
|
||||||
|
|
||||||
|
# -- Internals -----------------------------------------------------
|
||||||
|
|
||||||
|
def _copy_all(self) -> None:
|
||||||
|
"""Copy the whole log to the clipboard."""
|
||||||
|
clipboard = QApplication.clipboard()
|
||||||
|
if clipboard is not None:
|
||||||
|
clipboard.setText(self.text)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _monospace_font() -> QFont:
|
||||||
|
"""Return a fixed-width font so timestamps align."""
|
||||||
|
font = QFontDatabase.systemFont(QFontDatabase.SystemFont.FixedFont)
|
||||||
|
font.setPointSize(10)
|
||||||
|
return font
|
||||||
Loading…
Reference in a new issue