import tkinter as tk
from tkinter import Menu, filedialog, messagebox
import os
import fnmatch # ? 用于通配符匹配
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)
# 固定内容区尺寸
self.content_width = 1200
self.content_height = 840
self.current_font_size = 10
# 状态
self.file_paths = []
self.current_index = -1
self.modified = []
# 创建菜单
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.root.destroy)
self.recent_menu = Menu(self.menu_bar, tearoff=0)
self.menu_bar.add_cascade(label="\u6253\u5F00\u6587\u4EF6", menu=self.recent_menu)
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="white")
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.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)
# 路径过滤
filter_frame = tk.Frame(self.toolbar)
filter_frame.grid(row=0, column=1, sticky='ew', padx=(0, 10))
tk.Label(filter_frame, text="\u8DEF\u5F84\u8FC3\u6EE4", font=("Arial", 9), width=10, anchor="e").pack(side='left')
self.filter_var = tk.StringVar()
self.filter_entry = tk.Entry(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())
# 定位内容
goto_frame = tk.Frame(self.toolbar)
goto_frame.grid(row=0, column=2, sticky='ew')
tk.Label(goto_frame, text="\u5B9A\u4F4D\uFF1A", font=("Arial", 9), width=8, anchor="e").pack(side='left')
self.goto_content_var = tk.StringVar()
self.goto_content_entry = tk.Entry(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())
# --- 内容区 ---
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="white")
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="white"
)
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
)
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("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") # 绿
self.bind_font_zoom(self.text_widget)
# --- 状态栏 ---
self.status_frame = tk.Frame(root, height=25, bg="#f0f0f0", 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="#f0f0f0", anchor="w", font=("Arial", 9))
self.status_size = tk.Label(self.status_frame, text=f"1200 \u00D7 {self.content_height}", bg="#f0f0f0", anchor="center", font=("Arial", 9))
self.status_cursor = tk.Label(self.status_frame, text="", bg="#f0f0f0", anchor="e", font=("Arial", 9))
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.refresh_files()
self.root.protocol("WM_DELETE_WINDOW", self.root.destroy)
def bind_font_zoom(self, text_widget):
"""Ctrl + \u9F20\u6807\u6EDA\u8F6E \u7F29\u653E\u5B57\u4F53"""
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)
def show_about(self):
"""\u663E\u793A\u5173\u4E8E\u4FE1\u606F\uFF0C\u4F7F\u7528\u81EA\u5B9A\u4E49\u7A97\u53E3\u4EE5\u52A0\u5BBD"""
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\uFF08*\u5339\u914D\u4EFB\u610F\u5B57\u7B26\uFF0C?\u5339\u914D\u5355\u4E2A\u5B57\u7B26\uFF09\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 < -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"
)
about_window = tk.Toplevel(self.root)
about_window.title("\u5173\u4E8E")
about_window.resizable(False, False)
about_window.geometry("580x420")
about_window.transient(self.root)
about_window.grab_set()
tk.Label(
about_window,
text="\u5173\u4E8E 0qor.rpt \u67E5\u770B\u5668",
font=("Microsoft YaHei", 12, "bold")
).pack(pady=(15, 5))
text_widget = tk.Text(
about_window,
wrap='word',
font=("Microsoft YaHei", 9),
padx=15,
pady=10,
bg="white",
relief="flat"
)
text_widget.insert("1.0", about_text)
text_widget.configure(state='disabled')
text_widget.pack(expand=True, fill='both', padx=20, pady=10)
tk.Button(about_window, text="\u786E\u5B9A", command=about_window.destroy, width=10).pack(pady=10)
def find_files(self):
matches = []
pattern = self.filter_var.get().strip()
if not pattern:
# 无过滤,返回所有
for root_dir, _, files in os.walk("."):
if "0qor.rpt" in files:
matches.append(os.path.abspath(os.path.join(root_dir, "0qor.rpt")))
else:
# 通配符匹配
for root_dir, _, files in os.walk("."):
if "0qor.rpt" in files:
full_path = os.path.abspath(os.path.join(root_dir, "0qor.rpt"))
# 使用 fnmatch 进行通配符匹配(不区分大小写)
if 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
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_command(
label=rel_path,
command=lambda idx=i: self.switch_to(idx)
)
self.modified.append(False)
self.switch_to(0)
def colorize_numbers(self):
"""\u4E3A\u6587\u672C\u4E2D\u7684\u8D1F\u5C0F\u6570\u7740\u8272"""
content = self.text_widget.get("1.0", tk.END)
pattern = r'-0\.\d+'
import re
matches = re.finditer(pattern, content)
for match in matches:
num_str = match.group()
try:
num = float(num_str)
except:
continue
start_pos = match.start()
end_pos = match.end()
start_index = self.text_widget.index(f"1.0 + {start_pos} chars")
end_index = self.text_widget.index(f"1.0 + {end_pos} 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"
elif num < 0:
tag = "neg_005_to_0"
else:
continue
self.text_widget.tag_add(tag, start_index, end_index)
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
for tag in ["match_line", "goto_line", "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)
goto_text = self.goto_content_var.get().strip()
if goto_text:
pos = '1.0'
first_match = 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_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
self.colorize_numbers()
self.update_window_title(file_path)
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 relocate_current(self):
self.switch_to(self.current_index)
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
if __name__ == "__main__":
root = tk.Tk()
app = FixedSizeViewer(root)
root.mainloop()
|