布局布线和时序分析
笔记本


2025-08-14 15:19:48增强改良版0qor.rpt查看器走来走去116.236.47.26

import tkinter as tk
from tkinter import Menu, filedialog, messagebox
import os
import fnmatch
import json
import re

# 配置文件
CONFIG_FILE = "qor_viewer_config.json"


class FixedSizeViewer:
    def __init__(self, root):
        self.root = root
        self.root.title("\u56FA\u5B9A\u5C3A\u5BF8\u67E5\u770B\u5668")
        self.root.geometry("1220x950")
        self.root.resizable(False, False)

        # 颜色主题:亮色工具栏和窗口背景为 #e0e0e0
        self.theme = {
            "light": {
                "bg": "#e0e0e0",           # 窗口背景
                "fg": "black",
                "toolbar_bg": "#e0e0e0",   # 工具栏背景
                "status_bg": "#f0f0f0",
                "text_bg": "white",
                "text_fg": "black"
            },
            "dark": {
                "bg": "#2b2b2b",
                "fg": "white",
                "toolbar_bg": "#3c3c3c",
                "status_bg": "#252525",
                "text_bg": "#1e1e1e",
                "text_fg": "#dcdcdc"
            }
        }

        # 加载配置
        self.load_config()
        self.current_theme = self.config.get("theme", "light")

        # 固定内容区尺寸
        self.content_width = 1200
        self.content_height = 840
        self.current_font_size = 10

        # 状态
        self.file_paths = []
        self.current_index = -1
        self.modified = []

        # 搜索状态
        self.last_find_pos = "1.0"

        # 创建菜单
        self.menu_bar = Menu(root)
        self.root.config(menu=self.menu_bar)

        self.file_menu = Menu(self.menu_bar, tearoff=0)
        self.menu_bar.add_cascade(label="\u6587\u4EF6", menu=self.file_menu)
        self.file_menu.add_command(label="\u5237\u65B0", command=self.refresh_files)
        self.file_menu.add_separator()
        self.file_menu.add_command(label="\u4FDD\u5B58", command=self.save_current)
        self.file_menu.add_command(label="\u53E6\u5B58\u4E3A", command=self.save_as_current)
        self.file_menu.add_separator()
        self.file_menu.add_command(label="\u9000\u51FA", command=self.on_closing)

        self.recent_menu = Menu(self.menu_bar, tearoff=0)
        self.menu_bar.add_cascade(label="\u6253\u5F00\u6587\u4EF6", menu=self.recent_menu)

        self.view_menu = Menu(self.menu_bar, tearoff=0)
        self.menu_bar.add_cascade(label="\u89C6\u56FE", menu=self.view_menu)
        self.view_menu.add_command(label="\u5207\u6362\u4E3B\u9898", command=self.toggle_theme)

        self.help_menu = Menu(self.menu_bar, tearoff=0)
        self.menu_bar.add_cascade(label="\u5E2E\u52A9", menu=self.help_menu)
        self.help_menu.add_command(label="\u5173\u4E8E", command=self.show_about)

        # --- 工具栏 ---
        self.toolbar = tk.Frame(root, height=30, bg=self.theme[self.current_theme]["toolbar_bg"])
        self.toolbar.grid(row=0, column=0, sticky='ew', padx=10, pady=5)
        self.toolbar.grid_propagate(False)
        self.toolbar.grid_columnconfigure(1, weight=2)
        self.toolbar.grid_columnconfigure(2, weight=2)
        self.toolbar.grid_columnconfigure(3, weight=3)
        self.toolbar.grid_columnconfigure(4, weight=1)

        # 第0列:刷新
        self.refresh_btn = tk.Button(self.toolbar, text="\u5237\u65B0", command=self.refresh_files, width=10)
        self.refresh_btn.grid(row=0, column=0, padx=(0, 10), pady=0)

        # 第1列:路径过滤
        self.filter_frame = tk.Frame(self.toolbar, bg=self.theme[self.current_theme]["toolbar_bg"])
        self.filter_frame.grid(row=0, column=1, sticky='ew', padx=(0, 10))
        self.filter_label = tk.Label(
            self.filter_frame, text="\u8DEF\u5F84\u8FC3\u6EE4\uFF1A", font=("Arial", 9), width=10, anchor="e",
            bg=self.theme[self.current_theme]["toolbar_bg"], fg=self.theme[self.current_theme]["fg"]
        )
        self.filter_label.pack(side='left')
        self.filter_var = tk.StringVar()
        self.filter_entry = tk.Entry(self.filter_frame, textvariable=self.filter_var, font=("Arial", 9))
        self.filter_entry.pack(side='left', fill='x', expand=True)
        self.filter_entry.bind("", lambda e: self.refresh_files())

        # 第2列:定位内容(支持通配符)
        self.goto_frame = tk.Frame(self.toolbar, bg=self.theme[self.current_theme]["toolbar_bg"])
        self.goto_frame.grid(row=0, column=2, sticky='ew', padx=(0, 10))
        self.goto_label = tk.Label(
            self.goto_frame, text="\u5B9A\u4F4D\uFF1A", font=("Arial", 9), width=8, anchor="e",
            bg=self.theme[self.current_theme]["toolbar_bg"], fg=self.theme[self.current_theme]["fg"]
        )
        self.goto_label.pack(side='left')
        self.goto_content_var = tk.StringVar()
        self.goto_content_entry = tk.Entry(self.goto_frame, textvariable=self.goto_content_var, font=("Arial", 9))
        self.goto_content_entry.pack(side='left', fill='x', expand=True)
        self.goto_content_entry.bind("", lambda e: self.relocate_current())

        # 第3列:搜索面板(支持通配符)
        self.search_frame = tk.Frame(self.toolbar, bg=self.theme[self.current_theme]["toolbar_bg"])
        self.search_frame.grid(row=0, column=3, sticky='ew', padx=(0, 10))
        self.search_label = tk.Label(
            self.search_frame, text="\u641C\u7D22\uFF1A", font=("Arial", 9), width=8, anchor="w",
            bg=self.theme[self.current_theme]["toolbar_bg"], fg=self.theme[self.current_theme]["fg"]
        )
        self.search_label.pack(side='left')
        self.search_var = tk.StringVar()
        self.search_entry = tk.Entry(self.search_frame, textvariable=self.search_var, font=("Arial", 9), width=15)
        self.search_entry.pack(side='left', padx=(0, 5))
        self.search_entry.bind("", lambda e: self.find_next())
        self.find_next_btn = tk.Button(self.search_frame, text="\u4E0B\u4E00\u4E2A", command=self.find_next, width=10)
        self.find_next_btn.pack(side='left', padx=(0, 2))
        self.find_prev_btn = tk.Button(self.search_frame, text="\u4E0A\u4E00\u4E2A", command=self.find_prev, width=10)
        self.find_prev_btn.pack(side='left')

        # 第4列:着色模式
        self.mode_frame = tk.Frame(self.toolbar, bg=self.theme[self.current_theme]["toolbar_bg"])
        self.mode_frame.grid(row=0, column=4, sticky='w')
        self.mode_label = tk.Label(
            self.mode_frame, text="\u7740\u8272\u6A21\u5F0F\uFF1A", font=("Arial", 9), anchor="w",
            bg=self.theme[self.current_theme]["toolbar_bg"], fg=self.theme[self.current_theme]["fg"]
        )
        self.mode_label.pack(side='left')
        self.color_mode = tk.StringVar(value=self.config.get("color_mode", "integer"))
        self.radio_integer = tk.Radiobutton(
            self.mode_frame, text="\u6574\u6570", variable=self.color_mode, value="integer",
            font=("Arial", 9), command=self.recolor_current,
            bg=self.theme[self.current_theme]["toolbar_bg"], fg=self.theme[self.current_theme]["fg"],
            selectcolor=self.theme[self.current_theme]["toolbar_bg"]
        )
        self.radio_integer.pack(side='left', padx=(5, 5))
        self.radio_decimal = tk.Radiobutton(
            self.mode_frame, text="\u5C0F\u6570", variable=self.color_mode, value="decimal",
            font=("Arial", 9), command=self.recolor_current,
            bg=self.theme[self.current_theme]["toolbar_bg"], fg=self.theme[self.current_theme]["fg"],
            selectcolor=self.theme[self.current_theme]["toolbar_bg"]
        )
        self.radio_decimal.pack(side='left')

        # --- 内容区 ---
        self.root.grid_rowconfigure(2, weight=0)
        self.root.grid_rowconfigure(3, weight=1)
        self.root.grid_columnconfigure(0, weight=1)

        self.content_container = tk.Frame(root, bg=self.theme[self.current_theme]["bg"])
        self.content_container.grid(row=2, column=0, pady=(0, 10), sticky="n")

        self.content_frame = tk.Frame(
            self.content_container,
            width=self.content_width,
            height=self.content_height,
            relief="sunken",
            bd=1,
            bg=self.theme[self.current_theme]["bg"]
        )
        self.content_frame.grid(row=0, column=0)
        self.content_frame.grid_propagate(False)
        self.content_frame.grid_rowconfigure(0, weight=1)
        self.content_frame.grid_columnconfigure(0, weight=1)

        self.text_widget = tk.Text(
            self.content_frame,
            wrap='word',
            font=("Consolas", self.current_font_size),
            undo=True,
            bd=0,
            highlightthickness=0,
            padx=6,
            pady=6,
            bg=self.theme[self.current_theme]["text_bg"],
            fg=self.theme[self.current_theme]["text_fg"],
            insertbackground="white" if self.current_theme == "dark" else "black"
        )

        v_scroll = tk.Scrollbar(self.content_frame, orient='vertical', command=self.text_widget.yview)
        h_scroll = tk.Scrollbar(self.content_frame, orient='horizontal', command=self.text_widget.xview)
        self.text_widget.configure(yscrollcommand=v_scroll.set, xscrollcommand=h_scroll.set)

        self.text_widget.grid(row=0, column=0, sticky='nsew', padx=(1, 0), pady=(1, 0))
        v_scroll.grid(row=0, column=1, sticky='ns')
        h_scroll.grid(row=1, column=0, sticky='ew')

        # 标签
        self.text_widget.tag_configure("goto_line", background="#d0e6ff", foreground="black")
        self.text_widget.tag_configure("match_line", background="#fffacd", foreground="black")
        self.text_widget.tag_configure("search_highlight", background="yellow", foreground="black")
        self.text_widget.tag_configure("neg_lt_minus200", foreground="#8B0000")
        self.text_widget.tag_configure("neg_200_to_100", foreground="red")
        self.text_widget.tag_configure("neg_100_to_50", foreground="blue")
        self.text_widget.tag_configure("neg_50_to_0", foreground="green")
        self.text_widget.tag_configure("neg_lt_minus02", foreground="#8B0000")
        self.text_widget.tag_configure("neg_02_to_01", foreground="red")
        self.text_widget.tag_configure("neg_01_to_005", foreground="blue")
        self.text_widget.tag_configure("neg_005_to_0", foreground="green")

        if self.current_theme == "dark":
            self.text_widget.tag_configure("search_highlight", background="#ffcc00", foreground="black")

        self.bind_font_zoom(self.text_widget)

        # --- 状态栏 ---
        self.status_frame = tk.Frame(root, height=25, bg=self.theme[self.current_theme]["status_bg"], relief="sunken", bd=1)
        self.status_frame.grid(row=3, column=0, sticky='ew', padx=10, pady=(0, 10))
        self.status_frame.grid_propagate(False)
        self.status_frame.grid_columnconfigure(1, weight=1)

        self.status_path = tk.Label(self.status_frame, text="\u5C31\u7EEA", bg=self.theme[self.current_theme]["status_bg"], anchor="w", font=("Arial", 9), fg=self.theme[self.current_theme]["fg"])
        self.status_size = tk.Label(self.status_frame, text=f"1200 \u00D7 {self.content_height}", bg=self.theme[self.current_theme]["status_bg"], anchor="center", font=("Arial", 9), fg=self.theme[self.current_theme]["fg"])
        self.status_cursor = tk.Label(self.status_frame, text="", bg=self.theme[self.current_theme]["status_bg"], anchor="e", font=("Arial", 9), fg=self.theme[self.current_theme]["fg"])

        self.status_path.grid(row=0, column=0, padx=(10, 5), pady=2, sticky='w')
        self.status_size.grid(row=0, column=1, padx=5, pady=2, sticky='ew')
        self.status_cursor.grid(row=0, column=2, padx=(5, 10), pady=2, sticky='e')

        # 应用主题
        self.apply_theme()

        # 初始化
        self.refresh_files()
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)

    def wildcard_to_regex(self, pattern):
        """将通配符模式转换为正则表达式"""
        regex = re.escape(pattern)
        regex = regex.replace(r'\*', '.*')   # * → 任意字符
        regex = regex.replace(r'\?', '.')    # ? → 单个字符
        return f"(?i){regex}"  # (?i) 忽略大小写

    def apply_theme(self):
        theme = self.theme[self.current_theme]
        self.root.config(bg=theme["bg"])
        self.toolbar.config(bg=theme["toolbar_bg"])
        self.status_frame.config(bg=theme["status_bg"])
        self.status_path.config(bg=theme["status_bg"], fg=theme["fg"])
        self.status_size.config(bg=theme["status_bg"], fg=theme["fg"])
        self.status_cursor.config(bg=theme["status_bg"], fg=theme["fg"])
        self.text_widget.config(bg=theme["text_bg"], fg=theme["text_fg"], insertbackground="white" if self.current_theme == "dark" else "black")
        if self.current_theme == "dark":
            self.text_widget.tag_configure("search_highlight", background="#ffcc00", foreground="black")
        else:
            self.text_widget.tag_configure("search_highlight", background="yellow", foreground="black")

        # 更新所有子控件
        for widget in [self.filter_frame, self.goto_frame, self.search_frame, self.mode_frame]:
            widget.config(bg=theme["toolbar_bg"])
        for label in [self.filter_label, self.goto_label, self.search_label, self.mode_label]:
            label.config(bg=theme["toolbar_bg"], fg=theme["fg"])
        for entry in [self.filter_entry, self.goto_content_entry, self.search_entry]:
            entry.config(bg="white" if self.current_theme == "light" else theme["toolbar_bg"], fg=theme["fg"], insertbackground="white" if self.current_theme == "dark" else "black")
        for radio in [self.radio_integer, self.radio_decimal]:
            radio.config(bg=theme["toolbar_bg"], fg=theme["fg"], selectcolor=theme["toolbar_bg"])

    def toggle_theme(self):
        self.current_theme = "dark" if self.current_theme == "light" else "light"
        self.apply_theme()

    def on_closing(self):
        self.save_config()
        self.root.destroy()

    def save_config(self):
        self.config["color_mode"] = self.color_mode.get()
        self.config["theme"] = self.current_theme
        config_path = os.path.join(os.getcwd(), CONFIG_FILE)
        try:
            with open(config_path, 'w', encoding='utf-8') as f:
                json.dump(self.config, f, ensure_ascii=False, indent=2)
        except Exception as e:
            messagebox.showerror("\u9519\u8BEF", f"\u65E0\u6CD5\u4FDD\u5B58\u914D\u7F6E\u6587\u4EF6: {str(e)}")

    def load_config(self):
        self.config = {"color_mode": "integer", "theme": "light"}
        config_path = os.path.join(os.getcwd(), CONFIG_FILE)
        try:
            if os.path.exists(config_path):
                with open(config_path, 'r', encoding='utf-8') as f:
                    loaded = json.load(f)
                    if isinstance(loaded, dict):
                        self.config.update(loaded)
        except Exception as e:
            print(f"[Config] 加载失败: {e}")

    def find_next(self):
        search_term = self.search_var.get().strip()
        if not search_term:
            return
        self.text_widget.tag_remove("search_highlight", "1.0", tk.END)

        regex_pattern = self.wildcard_to_regex(search_term)
        start = f"{self.last_find_pos} + 1c"
        pos = self.text_widget.search(regex_pattern, start, stopindex="end", regexp=True)
        if pos:
            self.highlight_and_scroll(pos)
            self.last_find_pos = pos
        else:
            pos = self.text_widget.search(regex_pattern, "1.0", stopindex="end", regexp=True)
            if pos:
                self.highlight_and_scroll(pos)
                self.last_find_pos = pos
            else:
                messagebox.showinfo("\u67E5\u627E", "\u672A\u627E\u5230")

    def find_prev(self):
        search_term = self.search_var.get().strip()
        if not search_term:
            return
        self.text_widget.tag_remove("search_highlight", "1.0", tk.END)

        regex_pattern = self.wildcard_to_regex(search_term)
        start = self.text_widget.index("insert") if self.text_widget.index("insert") != "1.0" else "end"
        pos = self.text_widget.search(regex_pattern, start, stopindex="1.0", regexp=True, backwards=True)
        if pos:
            self.highlight_and_scroll(pos)
            self.last_find_pos = pos
        else:
            pos = self.text_widget.search(regex_pattern, "end", stopindex="1.0", regexp=True, backwards=True)
            if pos:
                self.highlight_and_scroll(pos)
                self.last_find_pos = pos
            else:
                messagebox.showinfo("\u67E5\u627E", "\u672A\u627E\u5230")

    def highlight_and_scroll(self, pos):
        line_start = pos.split('.')[0] + '.0'
        line_end = line_start + ' lineend'
        self.text_widget.tag_add("search_highlight", line_start, line_end)
        self.text_widget.see(line_start)
        self.text_widget.mark_set(tk.INSERT, line_start)

    def relocate_current(self):
        goto_text = self.goto_content_var.get().strip()
        if not goto_text:
            return
        self.text_widget.tag_remove("goto_line", "1.0", tk.END)
        self.text_widget.tag_remove("match_line", "1.0", tk.END)

        regex_pattern = self.wildcard_to_regex(goto_text)
        pos = '1.0'
        first_match = True
        while True:
            pos = self.text_widget.search(regex_pattern, pos, stopindex='end', regexp=True)
            if not pos:
                break
            line_start = pos.split('.')[0] + '.0'
            line_end = line_start + ' lineend'

            if first_match:
                self.text_widget.mark_set(tk.INSERT, line_start)
                self.text_widget.see(line_start)
                self.text_widget.yview(line_start)
                self.text_widget.tag_add("goto_line", line_start, line_end)
                first_match = False
            else:
                self.text_widget.tag_add("match_line", line_start, line_end)

            pos = line_end

    def show_about(self):
        about_window = tk.Toplevel(self.root)
        about_window.title("\u5173\u4E8E")
        about_window.resizable(False, False)
        about_window.transient(self.root)
        about_window.grab_set()

        theme = self.theme[self.current_theme]
        about_window.config(bg=theme["bg"])

        tk.Label(
            about_window,
            text="\u5173\u4E8E 0qor.rpt \u67E5\u770B\u5668",
            font=("Microsoft YaHei", 12, "bold"),
            bg=theme["bg"], fg=theme["fg"]
        ).pack(pady=(20, 10))

        about_text = (
            "\u8FD9\u662F\u4E00\u4E2A0qor.rpt\u67E5\u770B\u5668\n\n"
            "\u25CF \u5237\u65B0\u6309\u94AE\uFF1A\u91CD\u65B0\u626B\u63CF\u5F53\u524D\u76EE\u5F55\u53CA\u5B50\u76EE\u5F55\u4E2D\u7684\u6240\u6709 0qor.rpt \u6587\u4EF6\n"
            "\u25CF \u8DEF\u5F84\u8FC3\u6EE4\uFF1A\u53EA\u663E\u793A\u8DEF\u5F84\u4E2D\u5339\u914D\u901A\u914D\u7B26\u7684\u6587\u4EF6\n"
            "\u25CF \u5B9A\u4F4D\u6587\u672C\uFF1A\u5207\u6362\u6587\u4EF6\u65F6\uFF0C\u5C06\u7B2C\u4E00\u4E2A\u5305\u542B\u8BE5\u5185\u5BB9\u7684\u884C\u6EDA\u5230\u7A97\u53E3\u7B2C\u4E00\u884C\u5E76\u9AD8\u4EAE\n\n"
            "\u652F\u6301\u529F\u80FD\uFF1A\n"
            "\u2022 \u56FA\u5B9A\u5C3A\u5BF8\u663E\u793A\uFF081200x840\uFF09\n"
            "\u2022 \u6587\u672C\u9AD8\u4EAE\uFF08\u6240\u6709\u5339\u914D\u884C\u6D45\u9EC4\uFF0C\u5B9A\u4F4D\u884C\u6D45\u84DD\uFF09\n"
            "\u2022 \u6570\u5B57\u7740\u8272\uFF1A\n"
            "    \u2022 \u6574\u6570\u6A21\u5F0F\uFF1A\n"
            "        \u2022 < -200 \u2192 \u6697\u7EA2\u8272\n"
            "        \u2022 -200 ~ -100 \u2192 \u7EA2\u8272\n"
            "        \u2022 -100 ~ -50 \u2192 \u84DD\u8272\n"
            "        \u2022 -50 ~ 0 \u2192 \u7EFF\u8272\n"
            "    \u2022 \u5C0F\u6570\u6A21\u5F0F\uFF1A\n"
            "        \u2022 < -0.2 \u2192 \u6697\u7EA2\u8272\n"
            "        \u2022 -0.2 ~ -0.1 \u2192 \u7EA2\u8272\n"
            "        \u2022 -0.1 ~ -0.05 \u2192 \u84DD\u8272\n"
            "        \u2022 -0.05 ~ 0 \u2192 \u7EFF\u8272\n"
            "\u2022 \u5B57\u4F53\u7F29\u653E\uFF08Ctrl + \u9F20\u6807\u6EDA\u8F6E\uFF09\n"
            "\u2022 \u7A97\u53E3\u6807\u9898\u663E\u793A\u5F53\u524D\u6587\u4EF6\u8DEF\u5F84\n"
            "\u2022 \u8BB0\u4F4F\u7528\u6237\u9009\u62E9\u7684\u8FC7\u6EE4\u3001\u5B9A\u4F4D\u3001\u7740\u8272\u6A21\u5F0F"
        )

        text_widget = tk.Text(
            about_window,
            wrap='word',
            font=("Microsoft YaHei", 9),
            padx=20,
            pady=15,
            bg=theme["text_bg"],
            fg=theme["text_fg"],
            relief="flat",
            width=70,
            height=25,
            insertbackground="white" if self.current_theme == "dark" else "black"
        )
        text_widget.insert("1.0", about_text)
        text_widget.configure(state='disabled')
        text_widget.pack(expand=True, fill='both', padx=30, pady=(0, 10))

        button = tk.Button(
            about_window,
            text="\u786E\u5B9A",
            command=about_window.destroy,
            width=10,
            bg="#4a6984", fg="white"
        )
        button.pack(pady=(0, 15))

        about_window.update_idletasks()
        req_width = text_widget.winfo_reqwidth() + 60
        req_height = text_widget.winfo_reqheight() + button.winfo_reqheight() + 80
        max_width, max_height = 800, 700
        width = min(req_width, max_width)
        height = min(req_height, max_height)
        x = (self.root.winfo_screenwidth() // 2) - (width // 2)
        y = (self.root.winfo_screenheight() // 2) - (height // 2)
        about_window.geometry(f"{int(width)}x{int(height)}+{int(x)}+{int(y)}")

    # --- 其余方法保持不变 ---
    def find_files(self):
        matches = []
        pattern = self.filter_var.get().strip()
        for root_dir, _, files in os.walk("."):
            if "0qor.rpt" in files:
                full_path = os.path.abspath(os.path.join(root_dir, "0qor.rpt"))
                if not pattern or fnmatch.fnmatch(full_path.lower(), pattern.lower()):
                    matches.append(full_path)
        return sorted(matches)

    def load_file_content(self, file_path):
        encodings = ['utf-8', 'gbk', 'cp936']
        content = None
        for encoding in encodings:
            try:
                with open(file_path, 'r', encoding=encoding) as f:
                    content = f.read()
                break
            except UnicodeDecodeError:
                continue
            except Exception as e:
                return f"\u9519\u8BEF\uFF1A{str(e)}"
        if content is None:
            return "\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6"
        if content.startswith('\ufeff'):
            content = content[1:]
        return content

    def refresh_files(self):
        self.recent_menu.delete(0, "end")
        self.file_paths.clear()
        self.modified.clear()

        self.file_paths = self.find_files()
        base_dir = os.path.abspath(".")

        if not self.file_paths:
            self.text_widget.delete(1.0, tk.END)
            self.text_widget.insert(tk.END, "# \u672A\u627E\u5230\u7B26\u5408\u6761\u4EF6\u7684 0qor.rpt \u6587\u4EF6")
            self.status_path.config(text="\u65E0\u5339\u914D\u6587\u4EF6")
            self.update_window_title("\u65E0\u5339\u914D\u6587\u4EF6")
            return

        self.current_file_var = tk.IntVar()
        for i, file_path in enumerate(self.file_paths):
            try:
                rel_path = os.path.relpath(file_path, start=base_dir)
            except:
                rel_path = file_path
            self.recent_menu.add_radiobutton(
                label=rel_path,
                variable=self.current_file_var,
                value=i,
                command=lambda idx=i: self.switch_to(idx)
            )
            self.modified.append(False)

        self.switch_to(0)

    def colorize_numbers(self):
        mode = self.color_mode.get()
        content = self.text_widget.get("1.0", tk.END)
        import re

        for tag in ["neg_lt_minus200", "neg_200_to_100", "neg_100_to_50", "neg_50_to_0",
                   "neg_lt_minus02", "neg_02_to_01", "neg_01_to_005", "neg_005_to_0"]:
            self.text_widget.tag_remove(tag, "1.0", tk.END)

        if mode == "integer":
            pattern = r'-\d+(?:\.\d+)?'
            matches = re.finditer(pattern, content)
            for match in matches:
                num_str = match.group()
                try:
                    num = float(num_str)
                except:
                    continue
                if num >= 0: continue
                start_index = self.text_widget.index(f"1.0 + {match.start()} chars")
                end_index = self.text_widget.index(f"1.0 + {match.end()} chars")
                if num < -200:
                    tag = "neg_lt_minus200"
                elif num < -100:
                    tag = "neg_200_to_100"
                elif num < -50:
                    tag = "neg_100_to_50"
                else:
                    tag = "neg_50_to_0"
                self.text_widget.tag_add(tag, start_index, end_index)
        elif mode == "decimal":
            pattern = r'-0\.\d+'
            matches = re.finditer(pattern, content)
            for match in matches:
                num_str = match.group()
                try:
                    num = float(num_str)
                except:
                    continue
                if num >= 0: continue
                start_index = self.text_widget.index(f"1.0 + {match.start()} chars")
                end_index = self.text_widget.index(f"1.0 + {match.end()} chars")
                if num < -0.2:
                    tag = "neg_lt_minus02"
                elif num < -0.1:
                    tag = "neg_02_to_01"
                elif num < -0.05:
                    tag = "neg_01_to_005"
                else:
                    tag = "neg_005_to_0"
                self.text_widget.tag_add(tag, start_index, end_index)

        mode_text = "\u6574\u6570\u89C4\u5219" if mode == "integer" else "\u5C0F\u6570\u89C4\u5219"
        self.status_cursor.config(text=f"\u6A21\u5F0F\uFF1A{mode_text}")

    def recolor_current(self):
        if self.current_index >= 0:
            self.colorize_numbers()

    def switch_to(self, index):
        if index < 0 or index >= len(self.file_paths):
            return

        file_path = self.file_paths[index]
        content = self.load_file_content(file_path)

        self.text_widget.delete(1.0, tk.END)
        self.text_widget.insert(tk.END, content)
        self.text_widget.edit_modified(False)

        try:
            rel_path = os.path.relpath(file_path, start=os.path.abspath("."))
        except:
            rel_path = file_path
        self.status_path.config(text=rel_path)
        self.current_index = index
        self.current_file_var.set(index)
        self.update_window_title(file_path)

        # --- 1. 定位并滚动 ---
        goto_text = self.goto_content_var.get().strip()
        first_match_line_start = None
        if goto_text:
            pos = '1.0'
            while True:
                pos = self.text_widget.search(goto_text, pos, stopindex='end', nocase=True)
                if not pos:
                    break
                line_start = pos.split('.')[0] + '.0'
                if not first_match_line_start:
                    first_match_line_start = line_start
                    self.text_widget.see(line_start)
                    self.text_widget.yview(line_start)
                    self.text_widget.mark_set(tk.INSERT, line_start)
                pos = self.text_widget.index(f"{line_start} + 1 line")
        if not first_match_line_start:
            self.text_widget.see('1.0')

        # --- 2. 高亮 ---
        for tag in ["match_line", "goto_line"]:
            self.text_widget.tag_remove(tag, "1.0", tk.END)
        if goto_text and first_match_line_start:
            pos = '1.0'
            first = True
            while True:
                pos = self.text_widget.search(goto_text, pos, stopindex='end', nocase=True)
                if not pos:
                    break
                line_start = pos.split('.')[0] + '.0'
                line_end = line_start + ' lineend'
                if first:
                    self.text_widget.tag_add("goto_line", line_start, line_end)
                    first = False
                else:
                    self.text_widget.tag_add("match_line", line_start, line_end)
                pos = line_end

        # --- 3. 着色 ---
        self.colorize_numbers()

        # 光标事件
        self.text_widget.bind("", lambda e: self.on_cursor_move())
        self.text_widget.bind("", lambda e: self.on_cursor_move())
        self.on_cursor_move()

    def update_window_title(self, file_path):
        self.root.title(f"\u5F53\u524D\u6587\u4EF6\uFF1A {file_path}")

    def on_cursor_move(self):
        cursor = self.text_widget.index(tk.INSERT)
        row, col = cursor.split('.')
        self.status_cursor.config(text=f"\u884C {row}, \u5217 {col}")

    def save_current(self):
        if self.current_index >= 0:
            self.save_file(self.current_index, force_save_as=False)

    def save_as_current(self):
        if self.current_index >= 0:
            self.save_file(self.current_index, force_save_as=True)

    def save_file(self, index, force_save_as=False):
        if index < 0 or index >= len(self.file_paths):
            return True
        file_path = self.file_paths[index]
        if force_save_as or not os.path.exists(file_path):
            initial_dir = os.path.dirname(file_path) if os.path.exists(file_path) else "."
            new_path = filedialog.asksaveasfilename(
                title="\u4FDD\u5B58",
                initialfile="0qor.rpt",
                initialdir=initial_dir,
                defaultextension=".rpt",
                filetypes=[("\u62A5\u544A\u6587\u4EF6", "*.rpt"), ("*.*", "*.*")]
            )
            if not new_path:
                return False
            self.file_paths[index] = new_path
            self.refresh_files()
        try:
            content = self.text_widget.get(1.0, tk.END).rstrip('\r\n') + '\n'
            with open(file_path, 'w', encoding='utf-8') as f:
                f.write(content)
            self.modified[index] = False
            messagebox.showinfo("\u4FDD\u5B58\u6210\u529F", "\u6587\u4EF6\u5DF2\u4FDD\u5B58")
            return True
        except Exception as e:
            messagebox.showerror("\u5931\u8D25", str(e))
            return False

    def bind_font_zoom(self, text_widget):
        def on_mousewheel(event):
            if event.state & 0x4:
                if event.delta > 0 and self.current_font_size < 24:
                    self.current_font_size += 1
                elif event.delta < 0 and self.current_font_size > 8:
                    self.current_font_size -= 1
                else:
                    return
                font_family = text_widget.cget("font").split()[0] if " " not in text_widget.cget("font") else "Consolas"
                new_font = (font_family, self.current_font_size)
                text_widget.config(font=new_font)
        text_widget.bind("", on_mousewheel)


if __name__ == "__main__":
    root = tk.Tk()
    app = FixedSizeViewer(root)
    root.mainloop()


回到首页时钟 , 联系信箱:yzbox#163.com(把#换成@) 粤ICP备18155639号