fix: Implement drag-and-drop functionality with QWebChannel integration
This commit is contained in:
parent
b2681a9cbd
commit
f701247fab
4 changed files with 165 additions and 59 deletions
|
|
@ -5,7 +5,9 @@ import logging
|
|||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from PySide6.QtCore import QObject, QSize, Qt, QThread, QUrl, Signal
|
||||
from PySide6.QtCore import QObject, QPoint, QSize, Qt, QThread, QTimer, QUrl, Signal, Slot
|
||||
from PySide6.QtWebChannel import QWebChannel
|
||||
from PySide6.QtWebEngineCore import QWebEngineScript
|
||||
from PySide6.QtWidgets import QLabel, QMainWindow, QStatusBar, QToolBar, QVBoxLayout, QWidget
|
||||
|
||||
from webdrop_bridge.config import Config
|
||||
|
|
@ -170,6 +172,39 @@ DEFAULT_WELCOME_PAGE = """
|
|||
"""
|
||||
|
||||
|
||||
class _DragBridge(QObject):
|
||||
"""JavaScript bridge for drag operations via QWebChannel.
|
||||
|
||||
Exposed to JavaScript as 'bridge' object.
|
||||
"""
|
||||
|
||||
def __init__(self, window: 'MainWindow', parent: Optional[QObject] = None):
|
||||
"""Initialize the drag bridge.
|
||||
|
||||
Args:
|
||||
window: MainWindow instance
|
||||
parent: Parent QObject
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.window = window
|
||||
|
||||
@Slot(str)
|
||||
def start_file_drag(self, path_text: str) -> None:
|
||||
"""Start a native file drag for the given path.
|
||||
|
||||
Called from JavaScript when user drags a Z:\ path item.
|
||||
Defers execution to avoid Qt drag manager state issues.
|
||||
|
||||
Args:
|
||||
path_text: File path string to drag
|
||||
"""
|
||||
logger.debug(f"Bridge: start_file_drag called for {path_text}")
|
||||
|
||||
# Defer to avoid drag manager state issues
|
||||
# initiate_drag() handles validation internally
|
||||
QTimer.singleShot(0, lambda: self.window.drag_interceptor.initiate_drag([path_text]))
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
"""Main application window for WebDrop Bridge.
|
||||
|
||||
|
|
@ -221,7 +256,6 @@ class MainWindow(QMainWindow):
|
|||
|
||||
# Create drag interceptor
|
||||
self.drag_interceptor = DragInterceptor()
|
||||
|
||||
# Set up path validator
|
||||
validator = PathValidator(config.allowed_roots)
|
||||
self.drag_interceptor.set_validator(validator)
|
||||
|
|
@ -230,6 +264,15 @@ class MainWindow(QMainWindow):
|
|||
self.drag_interceptor.drag_started.connect(self._on_drag_started)
|
||||
self.drag_interceptor.drag_failed.connect(self._on_drag_failed)
|
||||
|
||||
# Set up JavaScript bridge with QWebChannel
|
||||
self._drag_bridge = _DragBridge(self)
|
||||
web_channel = QWebChannel(self)
|
||||
web_channel.registerObject("bridge", self._drag_bridge)
|
||||
self.web_view.page().setWebChannel(web_channel)
|
||||
|
||||
# Install the drag bridge script
|
||||
self._install_bridge_script()
|
||||
|
||||
# Set up central widget with layout
|
||||
central_widget = QWidget()
|
||||
layout = QVBoxLayout()
|
||||
|
|
@ -248,6 +291,7 @@ class MainWindow(QMainWindow):
|
|||
"""Load the web application.
|
||||
|
||||
Loads HTML from the configured webapp URL or from local file.
|
||||
Injects the WebChannel bridge JavaScript for drag-and-drop.
|
||||
Supports both bundled apps (PyInstaller) and development mode.
|
||||
Falls back to default welcome page if webapp not found.
|
||||
"""
|
||||
|
|
@ -282,15 +326,55 @@ class MainWindow(QMainWindow):
|
|||
self.web_view.setHtml(welcome_html)
|
||||
return
|
||||
|
||||
# Load local file as file:// URL
|
||||
file_url = file_path.as_uri()
|
||||
self.web_view.load(QUrl(file_url))
|
||||
# Load local file
|
||||
html_content = file_path.read_text(encoding='utf-8')
|
||||
|
||||
# Inject WebChannel bridge JavaScript
|
||||
injected_html = self._inject_drag_bridge(html_content)
|
||||
|
||||
# Load the modified HTML
|
||||
self.web_view.setHtml(injected_html, QUrl.fromLocalFile(file_path.parent))
|
||||
|
||||
except (OSError, ValueError) as e:
|
||||
# Show welcome page on error
|
||||
welcome_html = DEFAULT_WELCOME_PAGE.format(version=self.config.app_version)
|
||||
self.web_view.setHtml(welcome_html)
|
||||
|
||||
def _install_bridge_script(self) -> None:
|
||||
"""Install the drag bridge JavaScript via QWebEngineScript.
|
||||
|
||||
Follows the POC pattern for proper script injection and QWebChannel setup.
|
||||
"""
|
||||
script = QWebEngineScript()
|
||||
script.setName("webdrop-bridge")
|
||||
script.setInjectionPoint(QWebEngineScript.InjectionPoint.DocumentReady)
|
||||
script.setWorldId(QWebEngineScript.ScriptWorldId.MainWorld)
|
||||
script.setRunsOnSubFrames(False)
|
||||
|
||||
# Load bridge script from file
|
||||
script_path = Path(__file__).parent / "bridge_script.js"
|
||||
try:
|
||||
with open(script_path, 'r', encoding='utf-8') as f:
|
||||
script.setSourceCode(f.read())
|
||||
self.web_view.page().scripts().insert(script)
|
||||
logger.debug(f"Installed bridge script from {script_path}")
|
||||
except (OSError, IOError) as e:
|
||||
logger.warning(f"Failed to load bridge script: {e}")
|
||||
|
||||
def _inject_drag_bridge(self, html_content: str) -> str:
|
||||
"""Return HTML content unmodified.
|
||||
|
||||
The drag bridge script is now injected via QWebEngineScript in _install_bridge_script().
|
||||
This method is kept for compatibility but does nothing.
|
||||
|
||||
Args:
|
||||
html_content: Original HTML content
|
||||
|
||||
Returns:
|
||||
HTML unchanged
|
||||
"""
|
||||
return html_content
|
||||
|
||||
def _apply_stylesheet(self) -> None:
|
||||
"""Apply application stylesheet if available."""
|
||||
stylesheet_path = Path(__file__).parent.parent.parent.parent / \
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue