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()
|