feat: enhance drag-and-drop functionality to support multiple file paths and URLs

This commit is contained in:
claudi 2026-03-04 13:43:21 +01:00
parent 1e848e84b2
commit c612072dc8
5 changed files with 384 additions and 480 deletions

View file

@ -2,7 +2,7 @@
import logging
from pathlib import Path
from typing import List, Optional
from typing import List, Optional, Union
from PySide6.QtCore import QMimeData, Qt, QUrl, Signal
from PySide6.QtGui import QDrag
@ -21,14 +21,18 @@ class DragInterceptor(QWidget):
Intercepts drag events from web content, converts Azure Blob Storage URLs
to local paths, validates them, and initiates native Qt drag operations.
Supports both single and multiple file drag operations.
Signals:
drag_started: Emitted when a drag operation begins successfully
(source_urls_or_paths: str, local_paths: str - comma-separated for multiple)
drag_failed: Emitted when drag initiation fails
(source_urls_or_paths: str, error_message: str)
"""
# Signals with string parameters
drag_started = Signal(str, str) # (url_or_path, local_path)
drag_failed = Signal(str, str) # (url_or_path, error_message)
drag_started = Signal(str, str) # (source_urls_or_paths, local_paths)
drag_failed = Signal(str, str) # (source_urls_or_paths, error_message)
def __init__(self, config: Config, parent: Optional[QWidget] = None):
"""Initialize the drag interceptor.
@ -40,83 +44,123 @@ class DragInterceptor(QWidget):
super().__init__(parent)
self.config = config
self._validator = PathValidator(
config.allowed_roots,
check_file_exists=config.check_file_exists
config.allowed_roots, check_file_exists=config.check_file_exists
)
self._url_converter = URLConverter(config)
def handle_drag(self, text: str) -> bool:
"""Handle drag event from web view.
def handle_drag(self, text_or_list: Union[str, List[str]]) -> bool:
"""Handle drag event from web view (single or multiple files).
Determines if the text is an Azure URL or file path, converts if needed,
Determines if the text/list contains Azure URLs or file paths, converts if needed,
validates, and initiates native drag operation.
Supports:
- Single string (backward compatible)
- List of strings (multiple drag support)
Args:
text: Azure Blob Storage URL or file path from web drag
text_or_list: Azure URL/file path (str) or list of URLs/paths (List[str])
Returns:
True if native drag was initiated, False otherwise
"""
if not text or not text.strip():
# Normalize input to list
if isinstance(text_or_list, str):
text_list = [text_or_list]
elif isinstance(text_or_list, (list, tuple)):
text_list = list(text_or_list)
else:
error_msg = f"Unexpected drag data type: {type(text_or_list)}"
logger.error(error_msg)
self.drag_failed.emit("", error_msg)
return False
# Validate that we have content
if not text_list or all(not t or not str(t).strip() for t in text_list):
error_msg = "Empty drag text"
logger.warning(error_msg)
self.drag_failed.emit("", error_msg)
return False
text = text.strip()
logger.debug(f"Handling drag for text: {text}")
# Clean up text items
text_list = [str(t).strip() for t in text_list if str(t).strip()]
logger.debug(f"Handling drag for {len(text_list)} item(s)")
# Check if it's an Azure URL and convert to local path
if self._url_converter.is_azure_url(text):
local_path = self._url_converter.convert_url_to_path(text)
if local_path is None:
error_msg = "No mapping found for URL"
logger.warning(f"{error_msg}: {text}")
# Convert each text to local path
local_paths = []
source_texts = []
for text in text_list:
# Check if it's an Azure URL and convert to local path
if self._url_converter.is_azure_url(text):
local_path = self._url_converter.convert_url_to_path(text)
if local_path is None:
error_msg = f"No mapping found for URL: {text}"
logger.warning(error_msg)
self.drag_failed.emit(text, error_msg)
return False
source_texts.append(text)
else:
# Treat as direct file path
local_path = Path(text)
source_texts.append(text)
# Validate the path
try:
self._validator.validate(local_path)
except ValidationError as e:
error_msg = str(e)
logger.warning(f"Validation failed for {local_path}: {error_msg}")
self.drag_failed.emit(text, error_msg)
return False
source_text = text
else:
# Treat as direct file path
local_path = Path(text)
source_text = text
# Validate the path
try:
self._validator.validate(local_path)
except ValidationError as e:
error_msg = str(e)
logger.warning(f"Validation failed for {local_path}: {error_msg}")
self.drag_failed.emit(source_text, error_msg)
return False
local_paths.append(local_path)
logger.info(f"Initiating drag for: {local_path}")
logger.info(
f"Initiating drag for {len(local_paths)} file(s): {[str(p) for p in local_paths]}"
)
# Create native file drag
success = self._create_native_drag(local_path)
# Create native file drag with all paths
success = self._create_native_drag(local_paths)
if success:
self.drag_started.emit(source_text, str(local_path))
source_str = " | ".join(source_texts) if len(source_texts) > 1 else source_texts[0]
paths_str = (
" | ".join(str(p) for p in local_paths)
if len(local_paths) > 1
else str(local_paths[0])
)
self.drag_started.emit(source_str, paths_str)
else:
error_msg = "Failed to create native drag operation"
logger.error(error_msg)
self.drag_failed.emit(source_text, error_msg)
source_str = " | ".join(source_texts) if len(source_texts) > 1 else source_texts[0]
self.drag_failed.emit(source_str, error_msg)
return success
def _create_native_drag(self, file_path: Path) -> bool:
def _create_native_drag(self, file_paths: Union[Path, List[Path]]) -> bool:
"""Create a native file system drag operation.
Args:
file_path: Local file path to drag
file_paths: Single local file path or list of local file paths
Returns:
True if drag was created successfully
"""
try:
# Create MIME data with file URL
# Normalize to list
if isinstance(file_paths, Path):
paths_list = [file_paths]
else:
paths_list = list(file_paths)
# Create MIME data with file URLs
mime_data = QMimeData()
file_url = QUrl.fromLocalFile(str(file_path))
mime_data.setUrls([file_url])
file_urls = [QUrl.fromLocalFile(str(p)) for p in paths_list]
mime_data.setUrls(file_urls)
logger.debug(f"Creating drag with {len(file_urls)} file(s)")
# Create and execute drag
drag = QDrag(self)