Sync from development - prepare for v0.2.0

This commit is contained in:
Omni
2025-12-06 20:09:55 +00:00
parent fe14e4ecfb
commit ce969eba1b
277 changed files with 14059 additions and 3899 deletions

View File

@@ -2,6 +2,9 @@
GUI Utilities for Jackify Frontend
"""
import re
from typing import Tuple, Optional
from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtCore import QSize, QPoint
ANSI_COLOR_MAP = {
'30': 'black', '31': 'red', '32': 'green', '33': 'yellow', '34': 'blue', '35': 'magenta', '36': 'cyan', '37': 'white',
@@ -50,4 +53,272 @@ def ansi_to_html(text):
else:
result += chunk
result = result.replace('\n', '<br>')
return result
return result
def get_screen_geometry(widget: Optional[QWidget] = None) -> Tuple[int, int, int, int]:
"""
Get available screen geometry for a widget.
Args:
widget: Widget to get screen for (uses primary screen if None)
Returns:
Tuple of (x, y, width, height) for available screen geometry
"""
app = QApplication.instance()
if not app:
return (0, 0, 1920, 1080) # Fallback
if widget:
screen = None
window_handle = widget.windowHandle()
if window_handle and window_handle.screen():
screen = window_handle.screen()
else:
try:
global_pos = widget.mapToGlobal(widget.rect().center())
except Exception:
global_pos = QPoint(0, 0)
if app:
screen = app.screenAt(global_pos)
if not screen and app:
screen = app.primaryScreen()
else:
screen = app.primaryScreen()
if screen:
geometry = screen.availableGeometry()
return (geometry.x(), geometry.y(), geometry.width(), geometry.height())
return (0, 0, 1920, 1080) # Fallback
def calculate_window_size(
widget: Optional[QWidget] = None,
width_ratio: float = 0.7,
height_ratio: float = 0.6,
min_width: int = 900,
min_height: int = 500,
max_width: Optional[int] = None,
max_height: Optional[int] = None
) -> Tuple[int, int]:
"""
Calculate appropriate window size based on screen geometry.
Args:
widget: Widget to calculate size for (uses primary screen if None)
width_ratio: Fraction of screen width to use (0.0-1.0)
height_ratio: Fraction of screen height to use (0.0-1.0)
min_width: Minimum window width
min_height: Minimum window height
max_width: Maximum window width (None = no limit)
max_height: Maximum window height (None = no limit)
Returns:
Tuple of (width, height)
"""
_, _, screen_width, screen_height = get_screen_geometry(widget)
# Calculate size based on ratios
width = int(screen_width * width_ratio)
height = int(screen_height * height_ratio)
# Apply minimums
width = max(width, min_width)
height = max(height, min_height)
# Apply maximums
if max_width:
width = min(width, max_width)
if max_height:
height = min(height, max_height)
# Ensure we don't exceed screen bounds
width = min(width, screen_width)
height = min(height, screen_height)
return (width, height)
def calculate_window_position(
widget: QWidget,
window_width: int,
window_height: int,
parent: Optional[QWidget] = None
) -> QPoint:
"""
Calculate appropriate window position (centered on parent or screen).
Args:
widget: Widget to position
window_width: Width of window to position
window_height: Height of window to position
parent: Parent widget to center on (centers on screen if None)
Returns:
QPoint with x, y coordinates
"""
_, _, screen_width, screen_height = get_screen_geometry(widget)
if parent:
parent_geometry = parent.geometry()
x = parent_geometry.x() + (parent_geometry.width() - window_width) // 2
y = parent_geometry.y() + (parent_geometry.height() - window_height) // 2
else:
# Center on screen
x = (screen_width - window_width) // 2
y = (screen_height - window_height) // 2
# Ensure window stays on screen
x = max(0, min(x, screen_width - window_width))
y = max(0, min(y, screen_height - window_height))
return QPoint(x, y)
def set_responsive_minimum(window: Optional[QWidget], min_width: int = 960,
min_height: int = 520, margin: int = 32):
"""
Apply minimum size constraints that respect the current screen bounds.
Args:
window: Target window
min_width: Desired minimum width
min_height: Desired minimum height
margin: Pixels to subtract from available size to avoid full-screen overlap
"""
if window is None:
return
_, _, screen_width, screen_height = get_screen_geometry(window)
width_cap = min_width
height_cap = min_height
if screen_width:
available_width = max(640, screen_width - margin)
available_width = min(available_width, screen_width)
width_cap = min(min_width, available_width)
if screen_height:
available_height = max(520, screen_height - margin)
available_height = min(available_height, screen_height)
height_cap = min(min_height, available_height)
window.setMinimumSize(QSize(width_cap, height_cap))
def load_saved_window_size(window: QWidget) -> Optional[Tuple[int, int]]:
"""
Load saved window size from config if available.
Only returns sizes that are reasonable (compact menu size, not expanded).
Args:
window: Window widget (used to validate size against screen)
Returns:
Tuple of (width, height) if saved size exists and is valid, None otherwise
"""
try:
from ...backend.handlers.config_handler import ConfigHandler
config_handler = ConfigHandler()
saved_width = config_handler.get('window_width')
saved_height = config_handler.get('window_height')
if saved_width and saved_height:
# Validate saved size is reasonable (not too small, fits on screen)
_, _, screen_width, screen_height = get_screen_geometry(window)
min_width = 1200
min_height = 500
max_height = int(screen_height * 0.6) # Reject sizes larger than 60% of screen (expanded state)
# Ensure saved size is within reasonable bounds (compact menu size)
# Reject expanded sizes that are too tall
if (min_width <= saved_width <= screen_width and
min_height <= saved_height <= max_height):
return (saved_width, saved_height)
except Exception:
pass
return None
def save_window_size(window: QWidget):
"""
Save current window size to config.
Args:
window: Window widget to save size for
"""
try:
from ...backend.handlers.config_handler import ConfigHandler
config_handler = ConfigHandler()
size = window.size()
config_handler.set('window_width', size.width())
config_handler.set('window_height', size.height())
config_handler.save_config()
except Exception:
pass
def apply_window_size_and_position(
window: QWidget,
width_ratio: float = 0.7,
height_ratio: float = 0.6,
min_width: int = 900,
min_height: int = 500,
max_width: Optional[int] = None,
max_height: Optional[int] = None,
parent: Optional[QWidget] = None,
preserve_position: bool = False,
use_saved_size: bool = True
):
"""
Apply dynamic window sizing and positioning based on screen geometry.
Optionally uses saved window size if user has manually resized before.
Args:
window: Window widget to size/position
width_ratio: Fraction of screen width to use (if no saved size)
height_ratio: Fraction of screen height to use (if no saved size)
min_width: Minimum window width
min_height: Minimum window height
max_width: Maximum window width (None = no limit)
max_height: Maximum window height (None = no limit)
parent: Parent widget to center on (centers on screen if None)
preserve_position: If True, preserve current size and position (only set minimums)
use_saved_size: If True, check for saved window size first
"""
# Set minimum size first
window.setMinimumSize(QSize(min_width, min_height))
# If preserve_position is True, don't resize - just ensure minimums are set
if preserve_position:
# Only ensure current size meets minimums, don't change size
current_size = window.size()
if current_size.width() < min_width:
window.resize(min_width, current_size.height())
if current_size.height() < min_height:
window.resize(window.size().width(), min_height)
return
# Check for saved window size first
width = None
height = None
if use_saved_size:
saved_size = load_saved_window_size(window)
if saved_size:
width, height = saved_size
# If no saved size, calculate dynamically
if width is None or height is None:
width, height = calculate_window_size(
window, width_ratio, height_ratio, min_width, min_height, max_width, max_height
)
# Calculate and set position
pos = calculate_window_position(window, width, height, parent)
window.resize(width, height)
window.move(pos)