#!/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()