Source code for viewer.log

"""
Module: log_viewer
==================

This module provides a graphical log viewer using PySide6. The `LogViewer` class is a
QMainWindow-based widget that displays the log file written by Decologr in a styled QTextEdit
widget. It automatically refreshes to show new log entries.

Classes:
--------
- `LogViewer`: A main window that displays the log file and refreshes automatically.

Features:
---------
- Dark theme with a modern red-accented styling.
- Automatically reads and displays the log file written by Decologr.
- Refreshes periodically to show new log entries.
- Provides a "Clear Log" button to reset the log display.
- Auto-scrolls to the bottom to show the latest entries.

Usage Example:
--------------
>>> viewer = LogViewer()
>>> viewer.show()

"""

from pathlib import Path

from PySide6.QtCore import QTimer
from PySide6.QtWidgets import (
    QHBoxLayout,
    QMainWindow,
    QPushButton,
    QTextEdit,
    QVBoxLayout,
    QWidget,
)

from jdxi_editor.jdxi.style import JDXiStyle
from jdxi_editor.project import __package_name__


[docs] class LogViewer(QMainWindow): def __init__(self, midi_helper=None, parent=None): super().__init__(parent) self.setWindowTitle("Log Viewer") self.setMinimumSize(980, 400) # Apply dark theme styling self.setStyleSheet(JDXiStyle.LOG_VIEWER) # Determine log file path (Decologr writes to ~/.{package_name}/logs/{package_name}.log) log_dir = Path.home() / f".{__package_name__}" / "logs"
[docs] self.log_file = log_dir / f"{__package_name__}.log"
# Track the last position we read from in the file
[docs] self.last_position = 0
# Track refresh mode: True = fast (500ms), False = slow (30s)
[docs] self.fast_refresh_mode = True
# Create central widget and layout main_widget = QWidget() layout = QVBoxLayout(main_widget) # Create log text area
[docs] self.log_text = QTextEdit()
self.log_text.setLineWrapMode(QTextEdit.NoWrap) self.log_text.setReadOnly(True) layout.addWidget(self.log_text) # Create button layout button_layout = QHBoxLayout() # Create clear button clear_button = QPushButton("Clear Log") clear_button.clicked.connect(self.clear_log) button_layout.addWidget(clear_button) # Create refresh mode toggle button
[docs] self.refresh_mode_button = QPushButton("Fast Refresh (500ms)")
self.refresh_mode_button.setCheckable(True) self.refresh_mode_button.setChecked(True) self.refresh_mode_button.clicked.connect(self.toggle_refresh_mode) button_layout.addWidget(self.refresh_mode_button) layout.addLayout(button_layout) # Set central widget self.setCentralWidget(main_widget) # Load initial log content self.load_log_file() # Set up timer to refresh log file periodically
[docs] self.refresh_timer = QTimer(self)
self.refresh_timer.timeout.connect(self.refresh_log_file) self.refresh_timer.start(500) # Start with fast refresh (500ms)
[docs] def load_log_file(self): """Load the entire log file into the text widget""" if self.log_file.exists(): try: with open(self.log_file, "r", encoding="utf-8") as f: content = f.read() self.log_text.setPlainText(content) self.last_position = len(content) # Scroll to bottom cursor = self.log_text.textCursor() cursor.movePosition(cursor.MoveOperation.End) self.log_text.setTextCursor(cursor) except Exception as e: self.log_text.setPlainText(f"Error reading log file: {e}\n\nLog file path: {self.log_file}") else: self.log_text.setPlainText(f"Log file not found.\n\nExpected location: {self.log_file}")
[docs] def refresh_log_file(self): """Read new lines from the log file and append them""" if not self.log_file.exists(): return try: with open(self.log_file, "r", encoding="utf-8") as f: # Seek to last position f.seek(self.last_position) # Read new content new_content = f.read() if new_content: # Append new content self.log_text.moveCursor(self.log_text.textCursor().MoveOperation.End) self.log_text.insertPlainText(new_content) # Update position self.last_position = f.tell() # Scroll to bottom cursor = self.log_text.textCursor() cursor.movePosition(cursor.MoveOperation.End) self.log_text.setTextCursor(cursor) except Exception: # Silently ignore errors (file might be locked or deleted) pass
[docs] def toggle_refresh_mode(self): """Toggle between fast refresh (500ms) and slow refresh (30s)""" self.fast_refresh_mode = not self.fast_refresh_mode if self.fast_refresh_mode: self.refresh_timer.setInterval(500) # 500ms self.refresh_mode_button.setText("Fast Refresh (500ms)") else: self.refresh_timer.setInterval(30000) # 30 seconds self.refresh_mode_button.setText("Slow Refresh (30s)") # Restart timer with new interval self.refresh_timer.start()
[docs] def clear_log(self): """Clear the log display""" self.log_text.clear() self.last_position = 0
[docs] def closeEvent(self, event): """Stop the refresh timer when window is closed""" if hasattr(self, "refresh_timer"): self.refresh_timer.stop() event.accept()