#!/usr/bin/env python # -*- coding: utf-8 -*- """ 日志文本框组件 带颜色支持的日志显示组件。 """ import tkinter as tk from datetime import datetime class LogText(tk.Frame): """日志文本框组件(带滚动条)""" # 日志级别颜色配置 LOG_COLORS = { 'INFO': '#000000', # 黑色 'SUCCESS': '#008000', # 绿色 'WARNING': '#FF8C00', # 深橙色 'ERROR': '#FF0000', # 红色 'DEBUG': '#808080', # 灰色 } def __init__(self, parent, readonly=True, **kwargs): """ 初始化日志文本框 Args: parent: 父容器 readonly: 是否为只读模式 **kwargs: 其他参数 """ super().__init__(parent) # 创建文本框和滚动条 self.text = tk.Text(self, **kwargs) self.scrollbar = tk.Scrollbar(self, orient=tk.VERTICAL, command=self.text.yview) self.text.configure(yscrollcommand=self.scrollbar.set) # 布局 self.text.grid(row=0, column=0, sticky="nsew") self.scrollbar.grid(row=0, column=1, sticky="ns") # 配置网格权重 self.grid_rowconfigure(0, weight=1) self.grid_columnconfigure(0, weight=1) # 标记 tags 是否已配置 self._tags_configured = False # 设置为只读模式 if readonly: self._make_readonly() def _ensure_tags_configured(self): """确保文本标签已配置(延迟配置,在首次使用时)""" if not self._tags_configured: try: for level, color in self.LOG_COLORS.items(): self.text.tag_config(level.lower(), foreground=color) self._tags_configured = True except Exception: # 如果配置失败,标记为已尝试,避免重复尝试 self._tags_configured = True def _make_readonly(self): """通过绑定事件使文本框只读""" # 允许复制、全选等常用操作,阻止其他编辑操作 self.text.bind('', self._handle_key) self.text.bind('', self._allow_click) # 允许左键点击选择 def _handle_key(self, event): """处理按键事件,允许复制操作,阻止编辑""" # 允许的快捷键 allowed_keys = [ 'Control-c', # 复制 'Control-C', # 复制(大写) 'Control-a', # 全选 'Control-A', # 全选(大写) 'Control-x', # 剪切(虽然剪不了,但不报错) 'Control-X', ] # 检查是否是允许的快捷键 key_sym = event.keysym state = event.state # 检查 Ctrl 组合键 if state & 0x4: # Ctrl 键被按下 full_key = f"Control-{key_sym}" if full_key in allowed_keys: return # 允许执行 # 其他所有按键都阻止 return 'break' def _allow_click(self, event): """允许点击和选择文本""" # 不打断事件,允许正常的选择操作 return def log(self, message: str, level: str = 'INFO') -> None: """ 添加日志消息 Args: message: 日志消息 level: 日志级别 (INFO, SUCCESS, WARNING, ERROR, DEBUG) """ # 确保 tags 已配置 self._ensure_tags_configured() timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') log_message = f"[{timestamp}] [{level}] {message}\n" # 插入文本 tag = level.lower() if self._tags_configured else None if tag: try: self.text.insert('end', log_message, (tag,)) except Exception: # 如果带标签插入失败,尝试不带标签 self.text.insert('end', log_message) else: self.text.insert('end', log_message) # 自动滚动到底部 self.text.see('end') def info(self, message: str) -> None: """添加 INFO 级别日志""" self.log(message, 'INFO') def success(self, message: str) -> None: """添加 SUCCESS 级别日志""" self.log(message, 'SUCCESS') def warning(self, message: str) -> None: """添加 WARNING 级别日志""" self.log(message, 'WARNING') def error(self, message: str) -> None: """添加 ERROR 级别日志""" self.log(message, 'ERROR') def debug(self, message: str) -> None: """添加 DEBUG 级别日志""" self.log(message, 'DEBUG') def clear(self) -> None: """清空日志""" self.text.delete('1.0', 'end') def save_to_file(self, file_path: str) -> bool: """ 保存日志到文件 Args: file_path: 保存路径 Returns: 是否成功 """ try: with open(file_path, 'w', encoding='utf-8') as f: f.write(self.text.get('1.0', 'end-1c')) return True except Exception as e: self.error(f"保存日志失败: {e}") return False # 委托其他常用方法到内部 text 组件 def pack(self, **kwargs): """Pack 布局""" super().pack(**kwargs) def grid(self, **kwargs): """Grid 布局""" super().grid(**kwargs)