"""
TearOffRegistry - Global registry for tear-off tab drag operations.
Tracks the current drag operation and all tear-off capable widgets
for cross-window drag-and-drop support.
"""
import logging
from typing import Optional, List, TYPE_CHECKING
from PyQt6.QtCore import QObject, QPoint
from PyQt6.QtWidgets import QApplication
if TYPE_CHECKING:
from .tear_off_tab_widget import TearOffTabWidget, FloatingTabWindow, TabDragData
logger = logging.getLogger(__name__)
[docs]
class TearOffRegistry(QObject):
"""
Singleton registry for tear-off tab operations.
Tracks:
- Current drag operation (tab being dragged)
- All tear-off capable widgets (potential drop targets)
- Floating windows (current drag sources)
This enables dragging tabs between different windows.
"""
_instance: Optional['TearOffRegistry'] = None
[docs]
def __init__(self):
super().__init__()
self._current_drag: Optional['TabDragData'] = None
self._floating_window: Optional['FloatingTabWindow'] = None
self._targets: List['TearOffTabWidget'] = []
self._current_hover_target: Optional['TearOffTabWidget'] = None
[docs]
@classmethod
def instance(cls) -> 'TearOffRegistry':
"""Get singleton instance."""
if cls._instance is None:
cls._instance = cls()
return cls._instance
[docs]
@classmethod
def register_drag(cls, drag_data: 'TabDragData',
floating_window: 'FloatingTabWindow'):
"""Register a new drag operation."""
registry = cls.instance()
registry._current_drag = drag_data
registry._floating_window = floating_window
logger.debug(f"Registered drag for tab: {drag_data.tab_text}")
[docs]
@classmethod
def clear_drag(cls):
"""Clear current drag operation."""
registry = cls.instance()
registry._current_drag = None
registry._floating_window = None
registry._current_hover_target = None
logger.debug("Cleared drag")
[docs]
@classmethod
def get_current_drag(cls) -> Optional['TabDragData']:
"""Get current drag data."""
return cls.instance()._current_drag
[docs]
@classmethod
def register_target(cls, target: 'TearOffTabWidget'):
"""Register a widget as a potential drop target."""
registry = cls.instance()
if target not in registry._targets:
registry._targets.append(target)
logger.debug(f"Registered tear-off target: {target}")
[docs]
@classmethod
def unregister_target(cls, target: 'TearOffTabWidget'):
"""Unregister a drop target."""
registry = cls.instance()
if target in registry._targets:
registry._targets.remove(target)
logger.debug(f"Unregistered tear-off target: {target}")
[docs]
@classmethod
def check_hover(cls, floating_window: 'FloatingTabWindow', global_pos: QPoint):
"""
Check if floating window is hovering over a drop target.
Called continuously during drag to update visual feedback.
"""
registry = cls.instance()
# Find widget under cursor
widget = QApplication.widgetAt(global_pos)
if not widget:
# Clear hover if we were hovering
if registry._current_hover_target:
registry._current_hover_target._hide_drop_indicator()
registry._current_hover_target = None
return
# Find TearOffTabWidget ancestor
from pyqt_reactive.widgets.shared.tear_off_tab_widget import TearOffTabWidget
target = None
temp = widget
while temp:
if isinstance(temp, TearOffTabWidget):
target = temp
break
temp = temp.parent()
# Update hover state
if target != registry._current_hover_target:
# Clear old hover
if registry._current_hover_target:
registry._current_hover_target._hide_drop_indicator()
# Set new hover
registry._current_hover_target = target
if target:
# Convert global pos to target local pos
local_pos = target.mapFromGlobal(global_pos)
target._show_drop_indicator(local_pos)
[docs]
@classmethod
def get_drop_target(cls, floating_window: 'FloatingTabWindow') \
-> Optional['TearOffTabWidget']:
"""Get the current drop target (if any)."""
registry = cls.instance()
return registry._current_hover_target