Files
renamer/gui_renamer.py
2025-12-14 22:21:51 +08:00

294 lines
12 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
图形界面文件重命名工具
"""
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import os
import threading
from file_renamer import FileRenamer
class GuiRenamer:
def __init__(self, root):
self.root = root
self.root.title("文件重命名工具")
self.root.geometry("800x600")
# 创建重命名工具实例
self.renamer = FileRenamer()
self.renamer.load_history()
# 当前选择的目录
self.current_directory = ""
# 创建界面
self.create_widgets()
def create_widgets(self):
"""创建界面组件"""
# 主框架
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 配置网格权重
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(1, weight=1)
main_frame.rowconfigure(4, weight=1)
# 目录选择
ttk.Label(main_frame, text="选择目录:").grid(row=0, column=0, sticky=tk.W, pady=5)
self.dir_var = tk.StringVar()
self.dir_entry = ttk.Entry(main_frame, textvariable=self.dir_var, width=50)
self.dir_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
ttk.Button(main_frame, text="浏览", command=self.browse_directory).grid(row=0, column=2, padx=(5, 0), pady=5)
# 分隔线
ttk.Separator(main_frame, orient=tk.HORIZONTAL).grid(row=1, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
# 重命名选项卡
self.notebook = ttk.Notebook(main_frame)
self.notebook.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
# 简单替换选项卡
self.create_replace_tab()
# 正则表达式选项卡
self.create_regex_tab()
# 添加前缀/后缀选项卡
self.create_prefix_suffix_tab()
# 序号选项卡
self.create_enumerate_tab()
# 大小写选项卡
self.create_case_tab()
# 预览按钮
self.preview_btn = ttk.Button(main_frame, text="预览", command=self.preview_rename)
self.preview_btn.grid(row=3, column=1, sticky=tk.E, pady=10)
# 执行按钮
self.execute_btn = ttk.Button(main_frame, text="执行重命名", command=self.execute_rename)
self.execute_btn.grid(row=3, column=2, padx=(5, 0), pady=10)
# 结果显示区域
result_frame = ttk.LabelFrame(main_frame, text="结果", padding="5")
result_frame.grid(row=4, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
result_frame.columnconfigure(0, weight=1)
result_frame.rowconfigure(0, weight=1)
self.result_text = scrolledtext.ScrolledText(result_frame, wrap=tk.WORD, height=15)
self.result_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 撤销按钮
self.undo_btn = ttk.Button(main_frame, text="撤销上次操作", command=self.undo_last_rename)
self.undo_btn.grid(row=5, column=0, pady=10)
# 清空结果按钮
ttk.Button(main_frame, text="清空结果", command=self.clear_result).grid(row=5, column=2, pady=10)
def create_replace_tab(self):
"""创建简单替换选项卡"""
frame = ttk.Frame(self.notebook, padding="10")
frame.columnconfigure(1, weight=1)
ttk.Label(frame, text="查找:").grid(row=0, column=0, sticky=tk.W, pady=5)
self.replace_search_var = tk.StringVar()
ttk.Entry(frame, textvariable=self.replace_search_var, width=30).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
ttk.Label(frame, text="替换为:").grid(row=1, column=0, sticky=tk.W, pady=5)
self.replace_replace_var = tk.StringVar()
ttk.Entry(frame, textvariable=self.replace_replace_var, width=30).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
self.notebook.add(frame, text="简单替换")
self.replace_frame = frame
def create_regex_tab(self):
"""创建正则表达式选项卡"""
frame = ttk.Frame(self.notebook, padding="10")
frame.columnconfigure(1, weight=1)
ttk.Label(frame, text="正则表达式:").grid(row=0, column=0, sticky=tk.W, pady=5)
self.regex_pattern_var = tk.StringVar()
ttk.Entry(frame, textvariable=self.regex_pattern_var, width=30).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
ttk.Label(frame, text="替换为:").grid(row=1, column=0, sticky=tk.W, pady=5)
self.regex_replace_var = tk.StringVar()
ttk.Entry(frame, textvariable=self.regex_replace_var, width=30).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
# 正则表达式说明
help_text = "常用正则表达式:\n\\d+ 匹配数字\n[a-zA-Z]+ 匹配字母\n.+ 匹配任意字符\n^ 匹配开头\n$ 匹配结尾"
ttk.Label(frame, text=help_text, foreground="gray").grid(row=2, column=0, columnspan=2, sticky=tk.W, pady=(10, 0))
self.notebook.add(frame, text="正则表达式")
self.regex_frame = frame
def create_prefix_suffix_tab(self):
"""创建前缀/后缀选项卡"""
frame = ttk.Frame(self.notebook, padding="10")
frame.columnconfigure(1, weight=1)
ttk.Label(frame, text="前缀:").grid(row=0, column=0, sticky=tk.W, pady=5)
self.prefix_var = tk.StringVar()
ttk.Entry(frame, textvariable=self.prefix_var, width=30).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
ttk.Label(frame, text="后缀:").grid(row=1, column=0, sticky=tk.W, pady=5)
self.suffix_var = tk.StringVar()
ttk.Entry(frame, textvariable=self.suffix_var, width=30).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
self.notebook.add(frame, text="前缀/后缀")
self.prefix_suffix_frame = frame
def create_enumerate_tab(self):
"""创建序号选项卡"""
frame = ttk.Frame(self.notebook, padding="10")
frame.columnconfigure(1, weight=1)
ttk.Label(frame, text="前缀:").grid(row=0, column=0, sticky=tk.W, pady=5)
self.enum_prefix_var = tk.StringVar()
ttk.Entry(frame, textvariable=self.enum_prefix_var, width=30).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
ttk.Label(frame, text="起始数字:").grid(row=1, column=0, sticky=tk.W, pady=5)
self.enum_start_var = tk.StringVar(value="1")
ttk.Entry(frame, textvariable=self.enum_start_var, width=30).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
ttk.Label(frame, text="数字位数:").grid(row=2, column=0, sticky=tk.W, pady=5)
self.enum_digits_var = tk.StringVar(value="3")
ttk.Entry(frame, textvariable=self.enum_digits_var, width=30).grid(row=2, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
self.notebook.add(frame, text="添加序号")
self.enumerate_frame = frame
def create_case_tab(self):
"""创建大小写选项卡"""
frame = ttk.Frame(self.notebook, padding="10")
ttk.Label(frame, text="选择大小写格式:").grid(row=0, column=0, sticky=tk.W, pady=5)
self.case_var = tk.StringVar(value="lower")
case_frame = ttk.Frame(frame)
case_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=5)
ttk.Radiobutton(case_frame, text="小写 (lowercase)", variable=self.case_var, value="lower").pack(anchor=tk.W)
ttk.Radiobutton(case_frame, text="大写 (UPPERCASE)", variable=self.case_var, value="upper").pack(anchor=tk.W)
ttk.Radiobutton(case_frame, text="首字母大写 (Title Case)", variable=self.case_var, value="title").pack(anchor=tk.W)
self.notebook.add(frame, text="大小写")
self.case_frame = frame
def browse_directory(self):
"""浏览目录"""
directory = filedialog.askdirectory()
if directory:
self.dir_var.set(directory)
self.current_directory = directory
def preview_rename(self):
"""预览重命名"""
self.perform_rename(preview=True)
def execute_rename(self):
"""执行重命名"""
self.perform_rename(preview=False)
def perform_rename(self, preview=True):
"""执行重命名操作"""
# 获取当前目录
directory = self.dir_var.get().strip()
if not directory:
messagebox.showerror("错误", "请选择一个目录")
return
if not os.path.exists(directory):
messagebox.showerror("错误", "选择的目录不存在")
return
self.current_directory = directory
# 获取当前选中的选项卡
current_tab = self.notebook.index(self.notebook.select())
# 在新线程中执行重命名以避免界面冻结
thread = threading.Thread(target=self._perform_rename_thread, args=(current_tab, preview))
thread.daemon = True
thread.start()
def _perform_rename_thread(self, tab_index, preview):
"""在后台线程中执行重命名"""
try:
# 根据选项卡索引执行不同的重命名操作
if tab_index == 0: # 简单替换
search_pattern = self.replace_search_var.get()
replace_pattern = self.replace_replace_var.get()
count = self.renamer.simple_rename(self.current_directory, search_pattern, replace_pattern, preview)
elif tab_index == 1: # 正则表达式
regex_pattern = self.regex_pattern_var.get()
replace_pattern = self.regex_replace_var.get()
count = self.renamer.regex_rename(self.current_directory, regex_pattern, replace_pattern, preview)
elif tab_index == 2: # 前缀/后缀
prefix = self.prefix_var.get()
suffix = self.suffix_var.get()
count = 0
if prefix:
count += self.renamer.add_prefix(self.current_directory, prefix, preview)
if suffix:
count += self.renamer.add_suffix(self.current_directory, suffix, preview)
elif tab_index == 3: # 序号
prefix = self.enum_prefix_var.get()
try:
start = int(self.enum_start_var.get() or "1")
digits = int(self.enum_digits_var.get() or "3")
count = self.renamer.enumerate_files(self.current_directory, prefix, start, digits, preview)
except ValueError:
self.show_result("错误: 起始数字或数字位数必须是整数")
return
elif tab_index == 4: # 大小写
case_type = self.case_var.get()
count = self.renamer.change_case(self.current_directory, case_type, preview)
# 显示结果
action = "预览" if preview else "重命名"
self.show_result(f"{action}完成,共处理了 {count} 个文件")
except Exception as e:
self.show_result(f"操作失败: {str(e)}")
def show_result(self, message):
"""在结果区域显示信息"""
self.result_text.insert(tk.END, f"{message}\n")
self.result_text.see(tk.END)
def clear_result(self):
"""清空结果区域"""
self.result_text.delete(1.0, tk.END)
def undo_last_rename(self):
"""撤销上次重命名操作"""
try:
if self.renamer.undo_last_rename():
self.show_result("成功撤销上次重命名操作")
else:
self.show_result("没有可撤销的操作")
except Exception as e:
self.show_result(f"撤销操作失败: {str(e)}")
def main():
"""主函数"""
root = tk.Tk()
app = GuiRenamer(root)
root.mainloop()
if __name__ == "__main__":
main()