[Python] 纯文本查看 复制代码
import os
import sys
import json
import datetime
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QPushButton, QFileDialog, QLabel,
QLineEdit, QComboBox, QCheckBox, QSpinBox, QTableView,
QMessageBox, QTreeView, QWidget, QHBoxLayout, QVBoxLayout, QGroupBox,
QProgressDialog, QFileSystemModel, QGridLayout, QFileIconProvider,
QStyledItemDelegate, QAbstractItemView
)
from PyQt5.QtCore import Qt, QDir, QModelIndex, QThread, pyqtSignal, QTimer, QUrl
from PyQt5.QtGui import QStandardItemModel, QStandardItem, QGuiApplication, QIcon
class RenameWorker(QThread):
"""文件重命名的后台工作线程"""
progress_updated = pyqtSignal(int)
rename_finished = pyqtSignal(bool)
error_occurred = pyqtSignal(str)
canceled = pyqtSignal()
def __init__(self, file_list, operations):
super().__init__()
self.file_list = file_list
self.operations = operations
self.backup_list = []
def run(self):
try:
count = len(self.file_list)
for i, file_info in enumerate(self.file_list, start=1):
if self.isInterruptionRequested():
self.canceled.emit()
return
old_path = file_info['path']
new_name = RenamerGUI.generate_preview_name(file_info, self.operations)
new_path = os.path.join(file_info['dir'], new_name)
if old_path == new_path:
continue
if os.path.exists(new_path):
raise FileExistsError(f"文件已存在:{new_name}")
os.rename(old_path, new_path)
self.backup_list.append((old_path, new_path))
self.progress_updated.emit(int(i / count * 100))
self.rename_finished.emit(True)
except Exception as e:
self.error_occurred.emit(str(e))
class CustomLabel(QLabel):
"""自定义标签,用于显示完整路径(鼠标悬停时)"""
def __init__(self, parent=None):
super().__init__(parent)
self.full_path = ""
def setFullPath(self, path):
self.full_path = path
def enterEvent(self, event):
if self.full_path:
self.setText(self.full_path)
def leaveEvent(self, event):
short_path = os.path.basename(self.full_path) if self.full_path else ""
self.setText(f"当前目录: {short_path}")
class MyItemDelegate(QStyledItemDelegate):
"""自定义表格项委托,用于显示文件和文件夹图标"""
def __init__(self, folder_icon, file_icon, parent=None):
super().__init__(parent)
self.folder_icon = folder_icon
self.file_icon = file_icon
def paint(self, painter, option, index):
if index.isValid():
if index.model().fileInfo(index).isDir():
icon = self.folder_icon
else:
icon = self.file_icon
pixmap = icon.pixmap(option.rect.size())
painter.drawPixmap(option.rect, pixmap)
text_rect = option.rect.adjusted(icon.actualSize(option.rect.size()).width(), 0, 0, 0)
painter.drawText(text_rect, Qt.AlignLeft | Qt.AlignVCenter, index.model().data(index, Qt.DisplayRole))
else:
super().paint(painter, option, index)
class RenamerGUI(QMainWindow):
"""文件批量重命名工具的主窗口类"""
def __init__(self):
super().__init__()
self.current_dir = QDir.homePath()
self.file_list = []
self.backup_info = []
self.backup_file = "rename_backup.json"
self.setAcceptDrops(True)
self.setWindowTitle("文件批量重命名工具 www.52pojie.cn")
self.resize(800, 650)
self.init_ui()
self.load_backup_from_file()
self.preview_timer = None
@staticmethod
def generate_preview_name(file_info, operations):
"""根据操作参数生成预览文件名"""
name = file_info['name']
# 处理文字替换
if operations.get('replace_enabled', False):
old_text = operations.get('replace_old', '')
new_text = operations.get('replace_new', '')
if old_text:
name = name.replace(old_text, new_text)
# 处理文字删除
if operations.get('delete_enabled', False):
delete_text = operations.get('delete_text', '')
if delete_text:
name = name.replace(delete_text, '')
parts = []
# 处理父目录名(可选择前置或后置)
parent_dirs = RenamerGUI.get_parent_dirs(file_info['dir'], operations.get('parent_level', 0))
if operations.get('use_parent', False):
if operations.get('parent_pos') == "前置":
parts.extend(parent_dirs)
# 添加前缀
if operations.get('prefix'):
parts.append(operations['prefix'])
# 添加序列号(可选择前置或后置)
if operations.get('sequence', False):
seq_text = f"{file_info['seq']:0{operations.get('seq_digits', 2)}d}"
if operations.get('seq_pos') == "前置":
parts.append(seq_text)
# 添加处理后的文件名(根据保留原文件名选项)
if operations.get('keep_original', False):
parts.append(name)
# 添加序列号(后置情况)
if operations.get('sequence', False) and operations.get('seq_pos') == "后置":
parts.append(seq_text)
# 添加后缀
if operations.get('suffix'):
parts.append(operations['suffix'])
# 添加日期
if operations.get('date_enabled', False) and operations.get('date_format'):
try:
date_str = datetime.datetime.now().strftime(operations['date_format'])
except ValueError:
date_str = "格式错误"
parts.append(date_str)
# 处理父目录名后置的情况
if operations.get('use_parent', False) and operations.get('parent_pos') == "后置":
parts.extend(parent_dirs)
# 处理扩展名
ext = operations.get('new_ext', file_info['ext'])
if operations.get('change_ext', False) and ext:
if not ext.startswith('.'):
ext = f".{ext}"
else:
ext = file_info['ext']
base = '_'.join(filter(None, parts))
# 组合最终文件名 - 确保总有一个名称部分
if base:
return f"{base}{ext}"
elif name:
return f"{name}{ext}"
else:
return f"{file_info['name']}{ext}"
@staticmethod
def get_parent_dirs(path, levels):
"""获取指定层级的父目录名列表"""
parents = []
for _ in range(levels):
path, dirname = os.path.split(path)
if dirname:
parents.insert(0, dirname)
else:
break
return parents[:levels]
def init_ui(self):
"""初始化用户界面"""
main_widget = QWidget()
self.setCentralWidget(main_widget)
layout = QVBoxLayout(main_widget)
# 目录选择区域
dir_layout = QHBoxLayout()
self.dir_label = CustomLabel()
self.dir_label.setFullPath(self.current_dir)
self.path_input = QLineEdit(self.current_dir)
self.path_input.returnPressed.connect(self.go_to_path)
go_btn = QPushButton("前往")
go_btn.clicked.connect(self.go_to_path)
select_btn = QPushButton("选择目录")
select_btn.clicked.connect(self.select_directory)
dir_layout.addWidget(self.dir_label, 1)
dir_layout.addWidget(self.path_input, 3)
dir_layout.addWidget(go_btn)
dir_layout.addWidget(select_btn)
# 文件树视图
self.file_system_model = QFileSystemModel()
self.file_tree = QTreeView()
self.file_tree.setModel(self.file_system_model)
self.file_tree.clicked.connect(self.on_dir_selected)
self.file_system_model.setRootPath(QDir.rootPath())
icon_provider = QFileIconProvider()
folder_icon = icon_provider.icon(QFileIconProvider.Folder)
file_icon = icon_provider.icon(QFileIconProvider.File)
self.file_tree.setItemDelegate(MyItemDelegate(folder_icon, file_icon))
# 重命名规则设置区域
settings_group = QGroupBox("重命名规则")
settings_layout = QGridLayout(settings_group)
settings_layout.setVerticalSpacing(5)
settings_layout.setHorizontalSpacing(5)
# 第三行设置:父目录、前缀、后缀
row1 = QHBoxLayout()
self.parent_cb = QCheckBox("使用父目录名")
self.parent_level = QSpinBox()
self.parent_level.setRange(1, 5)
self.parent_level.setFixedWidth(40)
self.parent_pos = QComboBox() # 父目录位置选择
self.parent_pos.addItems(["前置", "后置"])
row1.addWidget(self.parent_cb)
row1.addWidget(QLabel("层级"))
row1.addWidget(self.parent_level)
row1.addWidget(QLabel("位置"))
row1.addWidget(self.parent_pos)
row1.addWidget(QLabel("前缀"))
self.prefix_input = QLineEdit()
row1.addWidget(self.prefix_input)
row1.addWidget(QLabel("后缀"))
self.suffix_input = QLineEdit()
row1.addWidget(self.suffix_input)
settings_layout.addLayout(row1, 2, 0, 1, 7)
# 第二行设置:保留原文件名、序列号
row2 = QHBoxLayout()
self.keep_original = QCheckBox("保留原文件名")
self.sequence_cb = QCheckBox("序列号")
self.seq_start = QSpinBox()
self.seq_start.setRange(1, 9999)
self.seq_start.setFixedWidth(40)
self.seq_digits = QSpinBox()
self.seq_digits.setRange(2, 6)
self.seq_digits.setFixedWidth(40)
self.seq_pos = QComboBox() # 序列号位置选择
self.seq_pos.addItems(["前置", "后置"])
self.separate_seq = QCheckBox("每文件夹独立编号")
row2.addWidget(self.keep_original)
row2.addWidget(self.sequence_cb)
row2.addWidget(QLabel("起始"))
row2.addWidget(self.seq_start)
row2.addWidget(QLabel("位数"))
row2.addWidget(self.seq_digits)
row2.addWidget(QLabel("位置"))
row2.addWidget(self.seq_pos)
row2.addWidget(self.separate_seq)
settings_layout.addLayout(row2, 1, 0, 1, 4)
# 第一行设置:文件类型过滤、包含子目录、添加日期格式、修改扩展名
row3 = QHBoxLayout()
# 文件类型过滤
self.file_type = QComboBox()
self.file_type.addItems(["所有文件", "图片", "文档", "视频", "音频", "自定义类型"])
self.file_type.setFixedWidth(120)
row3.addWidget(self.file_type)
# 自定义扩展名输入
self.custom_ext = QLineEdit()
self.custom_ext.setPlaceholderText("如 jpg png")
self.custom_ext.setFixedWidth(100)
row3.addWidget(self.custom_ext)
# 包含子目录
self.recursive = QCheckBox("包含子目录")
row3.addWidget(self.recursive)
# 添加日期功能
self.date_cb = QCheckBox("添加日期")
row3.addWidget(self.date_cb)
# 日期格式
self.date_format = QComboBox()
self.date_format.setFixedWidth(120)
self.date_format.addItems([
"yyyy-mm-dd", "yyyy/mm/dd", "yyyymmdd",
"yy-mm-dd", "yy/mm/dd", "yyyymm",
"yyyy年mm月dd日", "自定义..."
])
self.date_format.setCurrentIndex(2)
self.date_format.currentIndexChanged.connect(self.on_date_format_changed)
row3.addWidget(self.date_format)
# 自定义日期格式输入框
self.custom_date_format = QLineEdit("%Y%m%d")
self.custom_date_format.setFixedWidth(120)
self.custom_date_format.setVisible(False)
row3.addWidget(self.custom_date_format)
# 修改扩展名
self.change_ext_cb = QCheckBox("修改扩展名")
row3.addWidget(self.change_ext_cb)
# 新扩展名输入框
self.new_ext_input = QLineEdit()
self.new_ext_input.setPlaceholderText("如 .png")
self.new_ext_input.setFixedWidth(80)
self.new_ext_input.setEnabled(False)
self.change_ext_cb.toggled.connect(lambda: self.new_ext_input.setEnabled(self.change_ext_cb.isChecked()))
row3.addWidget(self.new_ext_input)
settings_layout.addLayout(row3, 0, 0, 1, 5)
# 第四行设置:替换和删除文字
row4 = QHBoxLayout()
self.replace_enabled = QCheckBox("替换原文件名文字")
self.replace_old = QLineEdit()
self.replace_old.setPlaceholderText("原文件名中要替换的文字")
self.replace_new = QLineEdit()
self.replace_new.setPlaceholderText("替换为")
self.delete_enabled = QCheckBox("删除原文件名文字")
self.delete_text = QLineEdit()
self.delete_text.setPlaceholderText("原文件名中要删除的文字")
row4.addWidget(self.replace_enabled)
row4.addWidget(self.replace_old)
row4.addWidget(QLabel("→"))
row4.addWidget(self.replace_new)
row4.addWidget(self.delete_enabled)
row4.addWidget(self.delete_text)
settings_layout.addLayout(row4, 3, 0, 1, 7)
# 按钮区域
btn_layout = QHBoxLayout()
self.rename_btn = QPushButton("执行重命名")
self.rename_btn.clicked.connect(self.start_rename)
self.undo_btn = QPushButton("撤销")
self.undo_btn.clicked.connect(self.undo_rename)
self.delete_btn = QPushButton("删除选中")
self.delete_btn.clicked.connect(self.delete_selected)
btn_layout.addWidget(self.rename_btn)
btn_layout.addWidget(self.undo_btn)
btn_layout.addWidget(self.delete_btn)
# 预览表格
self.preview_model = QStandardItemModel()
self.preview_table = QTableView()
self.preview_table.setModel(self.preview_model)
self.preview_table.setSelectionBehavior(QAbstractItemView.SelectRows)
self.preview_table.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.preview_model.setHorizontalHeaderLabels(["原始名称", "新名称", "状态", "文件路径"])
# 组装界面
layout.addLayout(dir_layout)
layout.addWidget(self.file_tree, 1)
layout.addWidget(settings_group, 0)
layout.addLayout(btn_layout)
layout.addWidget(self.preview_table, 2)
# 连接信号和槽
self.parent_cb.stateChanged.connect(self.auto_update_preview)
self.parent_level.valueChanged.connect(self.auto_update_preview)
self.parent_pos.currentIndexChanged.connect(self.auto_update_preview)
self.prefix_input.textChanged.connect(self.auto_update_preview)
self.suffix_input.textChanged.connect(self.auto_update_preview)
self.keep_original.toggled.connect(self.auto_update_preview)
self.sequence_cb.toggled.connect(self.auto_update_preview)
self.seq_start.valueChanged.connect(self.auto_update_preview)
self.seq_digits.valueChanged.connect(self.auto_update_preview)
self.seq_pos.currentIndexChanged.connect(self.auto_update_preview)
self.separate_seq.toggled.connect(self.auto_update_preview)
self.date_cb.toggled.connect(self.auto_update_preview)
self.date_format.currentIndexChanged.connect(self.auto_update_preview)
self.custom_date_format.textChanged.connect(self.auto_update_preview)
self.file_type.currentIndexChanged.connect(self.auto_update_preview)
self.custom_ext.textChanged.connect(self.auto_update_preview)
self.recursive.toggled.connect(self.auto_update_preview)
self.change_ext_cb.toggled.connect(self.auto_update_preview)
self.new_ext_input.textChanged.connect(self.auto_update_preview)
self.replace_enabled.toggled.connect(self.auto_update_preview)
self.replace_old.textChanged.connect(self.auto_update_preview)
self.replace_new.textChanged.connect(self.auto_update_preview)
self.delete_enabled.toggled.connect(self.auto_update_preview)
self.delete_text.textChanged.connect(self.auto_update_preview)
self.status_bar = self.statusBar()
self.status_bar.showMessage("就绪")
def go_to_path(self):
"""跳转到用户输入的路径"""
input_path = self.path_input.text()
if os.path.isdir(input_path):
self.current_dir = input_path
self.dir_label.setFullPath(self.current_dir)
self.file_tree.setRootIndex(self.file_system_model.index(self.current_dir))
self.auto_update_preview()
else:
QMessageBox.warning(self, "路径错误", "输入的路径无效,请检查后重试。")
def validate_name(self, name, dir_path):
"""验证文件名是否有效"""
if not name:
return False
invalid_chars = ['\\', '/', ':', '*', '?', '"', '<', '>', '|']
if any(c in name for c in invalid_chars):
return False
return not os.path.exists(os.path.join(dir_path, name))
def auto_update_preview(self, *args):
"""自动更新预览(延迟执行,避免频繁操作)"""
if self.preview_timer:
self.preview_timer.stop()
self.preview_timer = QTimer(self)
self.preview_timer.setSingleShot(True)
self.preview_timer.timeout.connect(self.refresh_preview)
self.preview_timer.start(300)
def refresh_preview(self):
"""刷新预览表格"""
self.scan_files()
self.preview_model.setRowCount(0)
operations = self.get_operations()
seq = self.seq_start.value()
dir_groups = {}
for file_info in self.file_list:
dir_path = file_info['dir']
if dir_path not in dir_groups:
dir_groups[dir_path] = {'seq': seq, 'files': []}
dir_groups[dir_path]['files'].append(file_info)
for dir_path, group in dir_groups.items():
for file_info in group['files']:
file_info['seq'] = group['seq']
group['seq'] += 1
if not self.separate_seq.isChecked():
seq = group['seq']
for file_info in self.file_list:
try:
new_name = self.generate_preview_name(file_info, operations)
status = self.validate_name(new_name, file_info['dir'])
item_old = QStandardItem(file_info['name'] + file_info['ext'])
item_new = QStandardItem(new_name)
item_status = QStandardItem("有效" if status else "冲突")
item_status.setForeground(Qt.red if not status else Qt.black)
item_path = QStandardItem(file_info['path'])
item_path.setToolTip(file_info['path'])
self.preview_model.appendRow([item_old, item_new, item_status, item_path])
except Exception as e:
QMessageBox.warning(self, "预览错误", str(e))
self.preview_table.resizeColumnsToContents()
self.status_bar.showMessage(f"预览:{len(self.file_list)} 个文件")
def scan_files(self):
"""扫描目录中的文件"""
self.file_list = []
ext_list = self.get_filter_extensions()
start_seq = self.seq_start.value()
if self.current_dir:
try:
if self.recursive.isChecked():
# 递归模式下,按文件夹分组处理
seq = start_seq
for root, dirs, files in os.walk(self.current_dir):
valid_files = []
for filename in files:
if self.is_valid_file(filename, ext_list):
valid_files.append(filename)
if valid_files:
# 处理当前目录的文件
dir_seq = seq
for filename in valid_files:
base, ext = os.path.splitext(filename)
self.file_list.append({
'dir': root,
'name': base,
'ext': ext,
'path': os.path.join(root, filename),
'seq': dir_seq
})
dir_seq += 1
# 如果启用独立编号,则下一个目录从起始编号开始
if self.separate_seq.isChecked():
seq = start_seq
else:
seq = dir_seq
else:
# 非递归模式
files = [f for f in os.listdir(self.current_dir) if os.path.isfile(os.path.join(self.current_dir, f))]
seq = start_seq
for filename in files:
if self.is_valid_file(filename, ext_list):
base, ext = os.path.splitext(filename)
self.file_list.append({
'dir': self.current_dir,
'name': base,
'ext': ext,
'path': os.path.join(self.current_dir, filename),
'seq': seq
})
seq += 1
except Exception as e:
QMessageBox.critical(self, "扫描错误", f"文件扫描失败:{str(e)}")
def is_valid_file(self, filename, ext_list):
"""检查文件是否符合扩展名过滤条件"""
if not ext_list:
return True
ext = os.path.splitext(filename)[1].lower()
return ext in ext_list
def get_filter_extensions(self):
"""获取过滤扩展名列表"""
type_map = {
"图片": [".jpg", ".jpeg", ".png", ".gif", ".bmp"],
"文档": [".doc", ".docx", ".pdf", ".txt", ".xls"],
"视频": [".mp4", ".avi", ".mkv", ".mov"],
"音频": [".mp3", ".wav", ".flac", ".ogg"],
"自定义类型": self.parse_custom_ext()
}
key = self.file_type.currentText()
return type_map.get(key, []) if key != "所有文件" else []
def parse_custom_ext(self):
"""解析自定义扩展名输入"""
raw = self.custom_ext.text().strip().lower()
exts = []
for ext in raw.split():
ext = ext.lstrip('.')
if ext:
exts.append(f".{ext}")
return exts or []
def on_date_format_changed(self, index):
"""日期格式选择变化时的处理"""
if index == len(self.date_format) - 1:
self.custom_date_format.setVisible(True)
else:
self.custom_date_format.setVisible(False)
self.auto_update_preview()
def get_operations(self):
"""获取当前重命名操作参数"""
date_enabled = self.date_cb.isChecked()
if date_enabled:
if self.date_format.currentIndex() == self.date_format.count() - 1:
date_format = self.custom_date_format.text()
else:
date_format = self.get_date_format_string(self.date_format.currentIndex())
else:
date_format = None
replace_enabled = self.replace_enabled.isChecked()
replace_old = self.replace_old.text().strip()
replace_new = self.replace_new.text().strip()
delete_enabled = self.delete_enabled.isChecked()
delete_text = self.delete_text.text().strip()
return {
"use_parent": self.parent_cb.isChecked(),
"parent_level": self.parent_level.value(),
"parent_pos": self.parent_pos.currentText(),
"prefix": self.prefix_input.text(),
"suffix": self.suffix_input.text(),
"keep_original": self.keep_original.isChecked(),
"sequence": self.sequence_cb.isChecked(),
"seq_digits": self.seq_digits.value(),
"seq_pos": self.seq_pos.currentText(),
"date_enabled": date_enabled,
"date_format": date_format,
"change_ext": self.change_ext_cb.isChecked(),
"new_ext": self.new_ext_input.text().strip(),
"replace_enabled": replace_enabled,
"replace_old": replace_old,
"replace_new": replace_new,
"delete_enabled": delete_enabled,
"delete_text": delete_text,
}
def get_date_format_string(self, index):
"""获取日期格式字符串"""
formats = [
"%Y-%m-%d", "%Y/%m/%d", "%Y%m%d",
"%y-%m-%d", "%y/%m/%d", "%Y%m",
"%Y年%m月%d日"
]
return formats[index]
def select_directory(self):
"""选择目录对话框"""
dir_path = QFileDialog.getExistingDirectory(
self, "选择目录", self.current_dir, QFileDialog.ShowDirsOnly
)
if dir_path:
self.current_dir = dir_path
self.dir_label.setFullPath(self.current_dir)
self.file_tree.setRootIndex(self.file_system_model.index(dir_path))
self.auto_update_preview()
def on_dir_selected(self, index):
"""文件树中选择目录时的处理"""
path = self.file_system_model.filePath(index)
if os.path.isdir(path):
self.current_dir = path
self.dir_label.setFullPath(self.current_dir)
self.auto_update_preview()
def start_rename(self):
"""开始重命名操作"""
if not self.file_list:
QMessageBox.warning(self, "警告", "没有文件可重命名")
return
confirm = QMessageBox.question(
self, "确认", f"确定要重命名 {len(self.file_list)} 个文件吗?",
QMessageBox.Yes | QMessageBox.No
)
if confirm != QMessageBox.Yes:
return
self.progress_dialog = QProgressDialog("正在重命名...", "取消", 0, 100, self)
self.progress_dialog.setWindowTitle("请稍候")
self.progress_dialog.setWindowModality(Qt.WindowModal)
self.worker = RenameWorker(self.file_list.copy(), self.get_operations())
self.worker.progress_updated.connect(self.progress_dialog.setValue)
self.worker.rename_finished.connect(self.on_rename_finished)
self.worker.error_occurred.connect(self.show_error)
self.worker.canceled.connect(self.progress_dialog.close)
self.progress_dialog.canceled.connect(self.worker.requestInterruption)
self.worker.start()
def on_rename_finished(self, success):
"""重命名完成后的处理"""
self.progress_dialog.close()
if success:
self.backup_info = self.worker.backup_list
self.save_backup_to_file()
QMessageBox.information(self, "成功", "文件重命名完成")
self.auto_update_preview()
def show_error(self, message):
"""显示错误消息"""
self.progress_dialog.close()
QMessageBox.critical(self, "错误", message)
def undo_rename(self):
"""撤销上次重命名操作"""
if not self.backup_info:
QMessageBox.information(self, "提示", "没有可撤销的操作")
return
confirm = QMessageBox.question(
self, "确认", "确定要撤销上次重命名操作吗?",
QMessageBox.Yes | QMessageBox.No
)
if confirm != QMessageBox.Yes:
return
try:
for old_path, new_path in reversed(self.backup_info):
if os.path.exists(new_path):
os.rename(new_path, old_path)
self.backup_info = []
self.save_backup_to_file()
QMessageBox.information(self, "成功", "重命名已撤销")
self.auto_update_preview()
except Exception as e:
QMessageBox.critical(self, "错误", f"撤销失败: {str(e)}")
def save_backup_to_file(self):
"""保存备份信息到文件"""
try:
with open(self.backup_file, 'w', encoding='utf-8') as f:
json.dump(self.backup_info, f, ensure_ascii=False, indent=2)
except Exception as e:
print(f"保存备份失败: {str(e)}")
def load_backup_from_file(self):
"""从文件加载备份信息"""
try:
if os.path.exists(self.backup_file):
with open(self.backup_file, 'r', encoding='utf-8') as f:
self.backup_info = json.load(f)
except Exception as e:
print(f"加载备份失败: {str(e)}")
def delete_selected(self):
"""删除选中的文件条目"""
selected_rows = self.preview_table.selectionModel().selectedRows()
if not selected_rows:
QMessageBox.information(self, "提示", "请先选择要删除的文件")
return
rows_to_delete = sorted([index.row() for index in selected_rows], reverse=True)
for row in rows_to_delete:
if 0 <= row < len(self.file_list):
del self.file_list[row]
self.preview_model.removeRow(row)
self.status_bar.showMessage(f"已删除 {len(rows_to_delete)} 个文件条目")
def dragEnterEvent(self, event):
"""拖放事件处理 - 进入"""
if event.mimeData().hasUrls():
event.acceptProposedAction()
def dropEvent(self, event):
"""拖放事件处理 - 放下"""
urls = event.mimeData().urls()
file_paths = []
dir_paths = []
for url in urls:
path = url.toLocalFile()
if os.path.isdir(path):
dir_paths.append(path)
elif os.path.isfile(path):
file_paths.append(path)
if dir_paths:
self.current_dir = dir_paths[0]
self.dir_label.setFullPath(self.current_dir)
self.file_tree.setRootIndex(self.file_system_model.index(self.current_dir))
self.auto_update_preview()
elif file_paths:
self.current_dir = os.path.dirname(file_paths[0]) if file_paths else QDir.homePath()
self.file_list = []
ext_list = self.get_filter_extensions()
seq = self.seq_start.value()
for path in file_paths:
filename = os.path.basename(path)
if self.is_valid_file(filename, ext_list):
base, ext = os.path.splitext(filename)
self.file_list.append({
'dir': os.path.dirname(path),
'name': base,
'ext': ext,
'path': path,
'seq': seq
})
seq += 1
self.auto_update_preview()
event.acceptProposedAction()
def excepthook(exctype, value, traceback):
"""全局异常处理"""
error_msg = f"程序崩溃:\n类型: {exctype.__name__}\n信息: {str(value)}"
QMessageBox.critical(None, "致命错误", error_msg)
sys.__excepthook__(exctype, value, traceback)
sys.excepthook = excepthook
if __name__ == '__main__':
"""程序入口点"""
app = QApplication(sys.argv)
window = RenamerGUI()
window.show()
sys.exit(app.exec_())