feat: enhance drag-and-drop functionality to support multiple file paths and URLs
This commit is contained in:
parent
1e848e84b2
commit
c612072dc8
5 changed files with 384 additions and 480 deletions
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue