/
Flipper
/
Marginal
Обзор
Документация
Войти
/
Flipper
/
Marginal
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
1
CI/CD
Аналитика
Безопасность
master
src/UI/CustomUI/DragNDropArea.py
288 строк
8 KB
Alex
Bug fixes.
12 янв 2026, 13:17
12 янв 2026, 13:17
e907759
Код
Авторство
О чём код?
import os from PySide6.QtWidgets import QLabel, QFileDialog, QMenu from PySide6.QtGui import QDragEnterEvent, QDropEvent, QMouseEvent from PySide6.QtCore import Qt from collections.abc import Callable class DragDropArea(QLabel): """ Custom QLabel that functions as a drag-and-drop file selection area. Supports file validation, visual feedback, and event callbacks. """ def __init__(self, text: str): """ Initialize DragDropArea with display text. Args: text (str): Default text to display in the area. """ super().__init__(text) self.setMinimumHeight(80) self.defaultText = text self.setAlignment(Qt.AlignCenter) self.translateFunc = None # Translation function for internationalization self.setAcceptDrops(True) # Enable drag-and-drop self.fileFilter = None self.acceptedExtensions = tuple() self.filePath = None # Currently selected file path self.onSetEvent = Callable # Callback when file is set self.onClearEvent = Callable # Callback when file is cleared self.occupied = False # Whether a file is currently selected self.defaultDir = str() # Default directory for file dialog def dragEnterEvent(self, event: QDragEnterEvent): """ Handle drag enter event - accept if URLs are present. Args: event (QDragEnterEvent): Drag enter event. Returns: None """ if event.mimeData().hasUrls(): event.acceptProposedAction() def dropEvent(self, event: QDropEvent): """ Handle drop event - process dropped file URL. Args: event (QDropEvent): Drop event. Returns: None """ urls = event.mimeData().urls() if urls: filePath = urls[0].toLocalFile() self.processSelection(filePath) def mousePressEvent(self, event: QMouseEvent): """ Handle mouse press events for file selection and clearing. Args: event (QMouseEvent): Mouse press event. Returns: None """ if event.button() == Qt.MiddleButton: self.clear() # Middle-click clears selection if event.button() == Qt.LeftButton: defaultDir = os.path.expanduser(self.defaultDir) fileFilter = self.fileFilter or "All Files (*)" filePath = QFileDialog.getOpenFileName( self, "Select file", defaultDir, fileFilter )[0] self.processSelection(filePath) def contextMenuEvent(self, event): """ Handle context menu (right-click) for file operations. Args: event: Context menu event. Returns: None """ menu = QMenu(self) # Add translated or default menu items openFileAction = ( menu.addAction(self.translateFunc("Open")) if self.translateFunc else menu.addAction("Open") ) clearAction = ( menu.addAction(self.translateFunc("Clear")) if self.translateFunc else menu.addAction("Clear") ) # Disable open action if no file selected if self.filePath is None: openFileAction.setDisabled(True) selectedAction = menu.exec(event.globalPos()) if selectedAction == clearAction: self.clear() if selectedAction == openFileAction: self.openFile() def setFilter(self, acceptedExtensions: list | str, filterName: str = "Files"): """ Set file extension filter for validation and file dialog. Args: acceptedExtensions (list | str): List of extensions or single extension string. filterName (str): Display name for the filter. Returns: None """ if isinstance(acceptedExtensions, list): # Convert list of extensions to tuple with leading dots self.acceptedExtensions = tuple(f".{ext.strip('.')}" for ext in acceptedExtensions) fileString = " ".join(f"*{ext}" for ext in self.acceptedExtensions) else: # Single extension string self.acceptedExtensions = (f".{acceptedExtensions.strip('.')}",) fileString = f"*{acceptedExtensions}" self.fileFilter = f"{filterName} ({fileString})" def processSelection(self, filePath: str): """ Process a file path selection (from dialog or drag-and-drop). Args: filePath (str): Selected file path. Returns: None """ checkResult = self.checkPath(filePath) if checkResult == True: self.setAccepted(filePath) elif checkResult == False: self.setDeclined() elif checkResult == None: self.clear() def setAccepted(self, filePath: str): """ Set file as accepted and update UI accordingly. Args: filePath (str): Valid file path. Returns: None """ self.filePath = filePath self.setText(os.path.basename(filePath)) self.setProperty("occupied", True) self.setProperty("declined", False) self.refreshStyle() self.occupied = True self.onSet(filePath) def setDeclined(self): """ Set file as declined (invalid type) and update UI accordingly. Returns: None """ self.setProperty("occupied", False) self.setProperty("declined", True) self.refreshStyle() self.setText(self.translateFunc("Invalid file type")) if self.translateFunc else self.setText("Invalid file type") self.occupied = False def clear(self): """ Clear current file selection and reset UI. Returns: None """ self.filePath = None self.setText(self.defaultText) self.setProperty("occupied", False) self.setProperty("declined", False) self.refreshStyle() self.occupied = False self.onClear() def openFile(self): """ Open the currently selected file with system default application. Returns: None """ if self.filePath: os.startfile(self.filePath) def checkPath(self, filePath: str) -> bool | None: """ Check if file path has accepted extension. Args: filePath (str): File path to check. Returns: bool | None -> True if valid, False if invalid, None if empty/None. """ if filePath and filePath.endswith(self.acceptedExtensions): return True elif not filePath or filePath == str(): return None else: return False def subscribeSetEvent(self, func: Callable): """ Subscribe a callback function for when a file is set/accepted. Args: func (Callable): Callback function to execute. Returns: None """ self.onSetEvent = func def subscribeClearEvent(self, func: Callable): """ Subscribe a callback function for when a file is cleared. Args: func (Callable): Callback function to execute. Returns: None """ self.onClearEvent = func def onSet(self, args=None): """ Internal method to trigger set event callback. Args: args: Arguments to pass to callback. Returns: None """ if self.onSetEvent != Callable: self.onSetEvent(args) def onClear(self): """ Internal method to trigger clear event callback. Returns: None """ if self.onClearEvent != Callable: self.onClearEvent() def refreshStyle(self): """ Refresh widget style to apply property changes. Returns: None """ self.style().unpolish(self) self.style().polish(self)