#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Log display panel - collapsible log output area """ from datetime import datetime from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QPlainTextEdit, QPushButton, QFrame, QLabel ) from PyQt6.QtCore import Qt, pyqtSlot from PyQt6.QtGui import QTextCursor from .constants import LOG_MIN_HEIGHT, BUTTON_HEIGHT_SMALL, BUTTON_SPACING class LogWidget(QWidget): """Log display panel""" def __init__(self, parent=None): super().__init__(parent) self.setObjectName("log_widget") self._collapsed = False self._max_lines = 500 self._setup_ui() def _setup_ui(self): """Setup UI""" layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) # Header bar header = QFrame() header.setFixedHeight(36) header.setStyleSheet("background-color: rgba(0,0,0,0.03); border-bottom: 1px solid rgba(0,0,0,0.08);") header_layout = QHBoxLayout(header) header_layout.setContentsMargins(16, 0, 16, 0) header_layout.setSpacing(BUTTON_SPACING) # Title title = QLabel("📜 日志输出") title.setStyleSheet("font-weight: bold; font-size: 13px;") header_layout.addWidget(title) header_layout.addStretch() # Clear button self._clear_btn = QPushButton("清空") self._clear_btn.setFixedWidth(60) self._clear_btn.setFixedHeight(BUTTON_HEIGHT_SMALL) self._clear_btn.clicked.connect(self.clear) header_layout.addWidget(self._clear_btn) # Toggle button self._toggle_btn = QPushButton("▼") self._toggle_btn.setFixedWidth(36) self._toggle_btn.setFixedHeight(BUTTON_HEIGHT_SMALL) self._toggle_btn.clicked.connect(self._toggle_collapse) header_layout.addWidget(self._toggle_btn) layout.addWidget(header) # Log text area self._text_edit = QPlainTextEdit() self._text_edit.setReadOnly(True) self._text_edit.setMinimumHeight(LOG_MIN_HEIGHT) self._text_edit.setMaximumHeight(250) self._text_edit.setStyleSheet(""" QPlainTextEdit { font-family: "Consolas", "Monaco", "Courier New", monospace; font-size: 12px; line-height: 1.5; padding: 8px 12px; } """) layout.addWidget(self._text_edit) def _toggle_collapse(self): """Toggle collapse state""" self._collapsed = not self._collapsed self._text_edit.setVisible(not self._collapsed) self._toggle_btn.setText("▲" if self._collapsed else "▼") @pyqtSlot(str) def append_log(self, message: str): """Append log message""" timestamp = datetime.now().strftime("%H:%M:%S") formatted = f"[{timestamp}] {message}" self._text_edit.appendPlainText(formatted) # Limit lines doc = self._text_edit.document() if doc.blockCount() > self._max_lines: cursor = QTextCursor(doc) cursor.movePosition(QTextCursor.MoveOperation.Start) cursor.select(QTextCursor.SelectionType.BlockUnderCursor) cursor.movePosition( QTextCursor.MoveOperation.Down, QTextCursor.MoveMode.KeepAnchor, doc.blockCount() - self._max_lines ) cursor.removeSelectedText() # Scroll to bottom self._text_edit.moveCursor(QTextCursor.MoveOperation.End) def log(self, message: str): """Log alias""" self.append_log(message) def clear(self): """Clear log""" self._text_edit.clear() def get_text(self) -> str: """Get all log text""" return self._text_edit.toPlainText()