feat: console log dock

This commit is contained in:
smillmorel 2026-09-16 12:03:07 -04:00
commit 1d21006281

View 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