Files
Any2MIF/ui/file_selector.py
2025-03-07 15:32:58 +08:00

254 lines
9.2 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2025 Any2MIF Project
# All rights reserved.
"""
Any2MIF - 文件选择模块
负责文件的选择和管理
"""
import os
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
QListWidget, QListWidgetItem, QFileDialog, QLabel,
QMenu, QAbstractItemView
)
from PyQt6.QtCore import Qt, QSettings, pyqtSignal
from PyQt6.QtGui import QIcon, QDragEnterEvent, QDropEvent
class FileSelector(QWidget):
"""文件选择器组件"""
# 自定义信号
file_selected = pyqtSignal(str) # 文件被选择时发出的信号
convert_clicked = pyqtSignal() # 转换按钮被点击时发出的信号
batch_convert_clicked = pyqtSignal() # 批量转换按钮被点击时发出的信号
def __init__(self, settings: QSettings, parent=None):
"""初始化文件选择器"""
super().__init__(parent)
self.settings = settings
self.recent_files = self.settings.value("recent_files", [])
if not isinstance(self.recent_files, list):
self.recent_files = []
self._init_ui()
self._load_recent_files()
def _init_ui(self):
"""初始化UI"""
# 设置接受拖放
self.setAcceptDrops(True)
# 创建主布局
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
# 创建标题标签
title_label = QLabel("文件选择")
title_label.setStyleSheet("font-weight: bold; font-size: 14px;")
layout.addWidget(title_label)
# 创建文件列表
self.file_list = QListWidget()
self.file_list.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
self.file_list.itemDoubleClicked.connect(self._on_item_double_clicked)
self.file_list.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.file_list.customContextMenuRequested.connect(self._show_context_menu)
layout.addWidget(self.file_list)
# 创建按钮容器
button_container = QWidget()
button_container_layout = QVBoxLayout(button_container)
button_container_layout.setContentsMargins(0, 0, 0, 0)
button_container_layout.setSpacing(5)
layout.addWidget(button_container)
# 创建上方按钮布局
top_button_layout = QHBoxLayout()
top_button_layout.setContentsMargins(0, 0, 0, 0)
button_container_layout.addLayout(top_button_layout)
# 添加文件按钮
self.add_file_button = QPushButton("添加文件")
self.add_file_button.clicked.connect(self._add_files)
top_button_layout.addWidget(self.add_file_button)
# 添加文件夹按钮
self.add_folder_button = QPushButton("添加文件夹")
self.add_folder_button.clicked.connect(self._add_folder)
top_button_layout.addWidget(self.add_folder_button)
# 清除按钮
self.clear_button = QPushButton("清除")
self.clear_button.clicked.connect(self._clear_files)
top_button_layout.addWidget(self.clear_button)
# 创建下方按钮布局(转换和批量转换按钮)
bottom_button_layout = QHBoxLayout()
bottom_button_layout.setContentsMargins(0, 0, 0, 0)
button_container_layout.addLayout(bottom_button_layout)
# 转换按钮
self.convert_button = QPushButton("转换")
self.convert_button.clicked.connect(self._on_convert_clicked)
bottom_button_layout.addWidget(self.convert_button)
# 批量转换按钮
self.batch_convert_button = QPushButton("批量转换")
self.batch_convert_button.clicked.connect(self._on_batch_convert_clicked)
bottom_button_layout.addWidget(self.batch_convert_button)
# 确保两行按钮的总宽度相同
# 为上面三个按钮设置相同的最小宽度
min_width = 80
self.add_file_button.setMinimumWidth(min_width)
self.add_folder_button.setMinimumWidth(min_width)
self.clear_button.setMinimumWidth(min_width)
# 为下面两个按钮设置宽度,使它们的总宽度与上面三个按钮相同
convert_width = (min_width * 3) // 2
self.convert_button.setMinimumWidth(convert_width)
self.batch_convert_button.setMinimumWidth(convert_width)
def _load_recent_files(self):
"""加载最近使用的文件"""
for file_path in self.recent_files:
if os.path.exists(file_path):
self._add_file_to_list(file_path)
def _add_file_to_list(self, file_path):
"""将文件添加到列表中"""
# 检查文件是否已经在列表中
for i in range(self.file_list.count()):
if self.file_list.item(i).data(Qt.ItemDataRole.UserRole) == file_path:
return
# 创建列表项
item = QListWidgetItem(os.path.basename(file_path))
item.setData(Qt.ItemDataRole.UserRole, file_path)
item.setToolTip(file_path)
# 添加到列表
self.file_list.addItem(item)
# 更新最近文件列表
if file_path in self.recent_files:
self.recent_files.remove(file_path)
self.recent_files.insert(0, file_path)
# 限制最近文件数量
self.recent_files = self.recent_files[:10]
self.settings.setValue("recent_files", self.recent_files)
def _add_files(self):
"""添加文件"""
files, _ = QFileDialog.getOpenFileNames(
self,
"选择文件",
os.path.expanduser("~"),
"所有文件 (*.*)"
)
if files:
for file_path in files:
self._add_file_to_list(file_path)
# 发出信号
if len(files) == 1:
self.file_selected.emit(files[0])
def _add_folder(self):
"""添加文件夹中的所有文件"""
folder = QFileDialog.getExistingDirectory(
self,
"选择文件夹",
os.path.expanduser("~")
)
if folder:
files_added = 0
for root, _, files in os.walk(folder):
for file in files:
file_path = os.path.join(root, file)
self._add_file_to_list(file_path)
files_added += 1
if files_added == 1:
self.file_selected.emit(self.file_list.item(self.file_list.count() - 1).data(Qt.ItemDataRole.UserRole))
def _clear_files(self):
"""清除文件列表"""
self.file_list.clear()
# 清除最近文件列表
self.recent_files = []
# 更新设置
self.settings.setValue("recent_files", self.recent_files)
def _on_item_double_clicked(self, item):
"""处理项目双击事件"""
file_path = item.data(Qt.ItemDataRole.UserRole)
self.file_selected.emit(file_path)
def _show_context_menu(self, position):
"""显示上下文菜单"""
menu = QMenu()
# 添加菜单项
remove_action = menu.addAction("移除")
remove_action.triggered.connect(self._remove_selected_files)
# 显示菜单
menu.exec(self.file_list.mapToGlobal(position))
def _remove_selected_files(self):
"""移除选定的文件"""
for item in self.file_list.selectedItems():
self.file_list.takeItem(self.file_list.row(item))
def get_selected_files(self):
"""获取选定的文件列表"""
files = []
# 如果有选定的项目,返回选定的文件
if self.file_list.selectedItems():
for item in self.file_list.selectedItems():
files.append(item.data(Qt.ItemDataRole.UserRole))
# 否则返回所有文件
else:
for i in range(self.file_list.count()):
files.append(self.file_list.item(i).data(Qt.ItemDataRole.UserRole))
return files
def dragEnterEvent(self, event: QDragEnterEvent):
"""处理拖动进入事件"""
if event.mimeData().hasUrls():
event.acceptProposedAction()
def dropEvent(self, event: QDropEvent):
"""处理放置事件"""
urls = event.mimeData().urls()
for url in urls:
path = url.toLocalFile()
if os.path.isfile(path):
self._add_file_to_list(path)
elif os.path.isdir(path):
for root, _, files in os.walk(path):
for file in files:
file_path = os.path.join(root, file)
self._add_file_to_list(file_path)
# 如果只添加了一个文件,发出信号
if len(urls) == 1 and os.path.isfile(urls[0].toLocalFile()):
self.file_selected.emit(urls[0].toLocalFile())
def _on_convert_clicked(self):
"""处理转换按钮点击事件"""
self.convert_clicked.emit()
def _on_batch_convert_clicked(self):
"""处理批量转换按钮点击事件"""
self.batch_convert_clicked.emit()