new alginment options and fix for multiselect
This commit is contained in:
parent
375e87ec84
commit
3805b6b913
@ -360,3 +360,87 @@ class AlignmentManager:
|
|||||||
current_y += elem.size[1] + spacing
|
current_y += elem.size[1] + spacing
|
||||||
|
|
||||||
return changes
|
return changes
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fit_to_page_width(element: BaseLayoutElement, page_width: float) -> Tuple[BaseLayoutElement, Tuple[float, float], Tuple[float, float]]:
|
||||||
|
"""
|
||||||
|
Resize element to fit page width while maintaining aspect ratio.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
element: The element to resize
|
||||||
|
page_width: The page width in mm
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (element, old_position, old_size) for undo
|
||||||
|
"""
|
||||||
|
old_pos = element.position
|
||||||
|
old_size = element.size
|
||||||
|
|
||||||
|
# Calculate aspect ratio
|
||||||
|
aspect_ratio = old_size[1] / old_size[0]
|
||||||
|
|
||||||
|
# Set new size
|
||||||
|
new_width = page_width
|
||||||
|
new_height = page_width * aspect_ratio
|
||||||
|
element.size = (new_width, new_height)
|
||||||
|
|
||||||
|
return (element, old_pos, old_size)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fit_to_page_height(element: BaseLayoutElement, page_height: float) -> Tuple[BaseLayoutElement, Tuple[float, float], Tuple[float, float]]:
|
||||||
|
"""
|
||||||
|
Resize element to fit page height while maintaining aspect ratio.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
element: The element to resize
|
||||||
|
page_height: The page height in mm
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (element, old_position, old_size) for undo
|
||||||
|
"""
|
||||||
|
old_pos = element.position
|
||||||
|
old_size = element.size
|
||||||
|
|
||||||
|
# Calculate aspect ratio
|
||||||
|
aspect_ratio = old_size[0] / old_size[1]
|
||||||
|
|
||||||
|
# Set new size
|
||||||
|
new_height = page_height
|
||||||
|
new_width = page_height * aspect_ratio
|
||||||
|
element.size = (new_width, new_height)
|
||||||
|
|
||||||
|
return (element, old_pos, old_size)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fit_to_page(element: BaseLayoutElement, page_width: float, page_height: float) -> Tuple[BaseLayoutElement, Tuple[float, float], Tuple[float, float]]:
|
||||||
|
"""
|
||||||
|
Resize element to fit within page dimensions while maintaining aspect ratio.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
element: The element to resize
|
||||||
|
page_width: The page width in mm
|
||||||
|
page_height: The page height in mm
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (element, old_position, old_size) for undo
|
||||||
|
"""
|
||||||
|
old_pos = element.position
|
||||||
|
old_size = element.size
|
||||||
|
|
||||||
|
# Calculate aspect ratios
|
||||||
|
element_aspect = old_size[0] / old_size[1]
|
||||||
|
page_aspect = page_width / page_height
|
||||||
|
|
||||||
|
# Determine which dimension to fit to
|
||||||
|
if element_aspect > page_aspect:
|
||||||
|
# Element is wider than page - fit to width
|
||||||
|
new_width = page_width
|
||||||
|
new_height = page_width / element_aspect
|
||||||
|
else:
|
||||||
|
# Element is taller than page - fit to height
|
||||||
|
new_height = page_height
|
||||||
|
new_width = page_height * element_aspect
|
||||||
|
|
||||||
|
element.size = (new_width, new_height)
|
||||||
|
|
||||||
|
return (element, old_pos, old_size)
|
||||||
|
|||||||
@ -177,16 +177,16 @@ class GLWidget(UndoableInteractionMixin, QOpenGLWidget):
|
|||||||
element._page_renderer = renderer
|
element._page_renderer = renderer
|
||||||
break
|
break
|
||||||
|
|
||||||
# Draw selection handles if element is selected
|
# Draw selection handles for all selected elements
|
||||||
if self.selected_element:
|
for selected_elem in self.selected_elements:
|
||||||
self._draw_selection_handles()
|
self._draw_selection_handles(selected_elem)
|
||||||
|
|
||||||
# Render text overlays using QPainter after OpenGL rendering
|
# Render text overlays using QPainter after OpenGL rendering
|
||||||
self._render_text_overlays()
|
self._render_text_overlays()
|
||||||
|
|
||||||
def _draw_selection_handles(self):
|
def _draw_selection_handles(self, element):
|
||||||
"""Draw selection handles around the selected element"""
|
"""Draw selection handles around the given element"""
|
||||||
if not self.selected_element:
|
if not element:
|
||||||
return
|
return
|
||||||
|
|
||||||
main_window = self.window()
|
main_window = self.window()
|
||||||
@ -194,14 +194,14 @@ class GLWidget(UndoableInteractionMixin, QOpenGLWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Get the PageRenderer for this element (stored when element was selected)
|
# Get the PageRenderer for this element (stored when element was selected)
|
||||||
if not hasattr(self.selected_element, '_page_renderer'):
|
if not hasattr(element, '_page_renderer'):
|
||||||
return
|
return
|
||||||
|
|
||||||
renderer = self.selected_element._page_renderer
|
renderer = element._page_renderer
|
||||||
|
|
||||||
# Get element position and size in page-local coordinates
|
# Get element position and size in page-local coordinates
|
||||||
elem_x, elem_y = self.selected_element.position
|
elem_x, elem_y = element.position
|
||||||
elem_w, elem_h = self.selected_element.size
|
elem_w, elem_h = element.size
|
||||||
handle_size = 8
|
handle_size = 8
|
||||||
|
|
||||||
# Convert to screen coordinates using PageRenderer
|
# Convert to screen coordinates using PageRenderer
|
||||||
@ -215,10 +215,10 @@ class GLWidget(UndoableInteractionMixin, QOpenGLWidget):
|
|||||||
|
|
||||||
# Apply rotation if element is rotated
|
# Apply rotation if element is rotated
|
||||||
from OpenGL.GL import glPushMatrix, glPopMatrix, glTranslatef, glRotatef
|
from OpenGL.GL import glPushMatrix, glPopMatrix, glTranslatef, glRotatef
|
||||||
if self.selected_element.rotation != 0:
|
if element.rotation != 0:
|
||||||
glPushMatrix()
|
glPushMatrix()
|
||||||
glTranslatef(center_x, center_y, 0)
|
glTranslatef(center_x, center_y, 0)
|
||||||
glRotatef(self.selected_element.rotation, 0, 0, 1)
|
glRotatef(element.rotation, 0, 0, 1)
|
||||||
glTranslatef(-w / 2, -h / 2, 0)
|
glTranslatef(-w / 2, -h / 2, 0)
|
||||||
# Now draw as if at origin
|
# Now draw as if at origin
|
||||||
x, y = 0, 0
|
x, y = 0, 0
|
||||||
@ -307,7 +307,7 @@ class GLWidget(UndoableInteractionMixin, QOpenGLWidget):
|
|||||||
glEnd()
|
glEnd()
|
||||||
|
|
||||||
# Restore matrix if we applied rotation
|
# Restore matrix if we applied rotation
|
||||||
if self.selected_element.rotation != 0:
|
if element.rotation != 0:
|
||||||
glPopMatrix()
|
glPopMatrix()
|
||||||
|
|
||||||
def _render_text_overlays(self):
|
def _render_text_overlays(self):
|
||||||
@ -614,8 +614,28 @@ class GLWidget(UndoableInteractionMixin, QOpenGLWidget):
|
|||||||
main_window.show_status(f"Rotation: {angle:.1f}°", 100)
|
main_window.show_status(f"Rotation: {angle:.1f}°", 100)
|
||||||
|
|
||||||
elif self.resize_handle:
|
elif self.resize_handle:
|
||||||
total_dx = (x - self.drag_start_pos[0]) / self.zoom_level
|
# Get mouse movement in screen pixels
|
||||||
total_dy = (y - self.drag_start_pos[1]) / self.zoom_level
|
screen_dx = x - self.drag_start_pos[0]
|
||||||
|
screen_dy = y - self.drag_start_pos[1]
|
||||||
|
|
||||||
|
# Convert to page-local coordinates
|
||||||
|
total_dx = screen_dx / self.zoom_level
|
||||||
|
total_dy = screen_dy / self.zoom_level
|
||||||
|
|
||||||
|
# If element is rotated, transform the deltas through inverse rotation
|
||||||
|
if self.selected_element.rotation != 0:
|
||||||
|
import math
|
||||||
|
angle_rad = -math.radians(self.selected_element.rotation)
|
||||||
|
cos_a = math.cos(angle_rad)
|
||||||
|
sin_a = math.sin(angle_rad)
|
||||||
|
|
||||||
|
# Rotate the delta vector
|
||||||
|
rotated_dx = total_dx * cos_a - total_dy * sin_a
|
||||||
|
rotated_dy = total_dx * sin_a + total_dy * cos_a
|
||||||
|
|
||||||
|
total_dx = rotated_dx
|
||||||
|
total_dy = rotated_dy
|
||||||
|
|
||||||
self._resize_element(total_dx, total_dy)
|
self._resize_element(total_dx, total_dy)
|
||||||
else:
|
else:
|
||||||
# Check if mouse is over a different page (for cross-page dragging)
|
# Check if mouse is over a different page (for cross-page dragging)
|
||||||
@ -800,6 +820,31 @@ class GLWidget(UndoableInteractionMixin, QOpenGLWidget):
|
|||||||
ew = elem_w * renderer.zoom
|
ew = elem_w * renderer.zoom
|
||||||
eh = elem_h * renderer.zoom
|
eh = elem_h * renderer.zoom
|
||||||
|
|
||||||
|
# Calculate center point
|
||||||
|
center_x = ex + ew / 2
|
||||||
|
center_y = ey + eh / 2
|
||||||
|
|
||||||
|
# If element is rotated, transform mouse coordinates through inverse rotation
|
||||||
|
if self.selected_element.rotation != 0:
|
||||||
|
import math
|
||||||
|
# Translate mouse to origin (relative to center)
|
||||||
|
rel_x = x - center_x
|
||||||
|
rel_y = y - center_y
|
||||||
|
|
||||||
|
# Apply inverse rotation
|
||||||
|
angle_rad = -math.radians(self.selected_element.rotation)
|
||||||
|
cos_a = math.cos(angle_rad)
|
||||||
|
sin_a = math.sin(angle_rad)
|
||||||
|
|
||||||
|
# Rotate the point
|
||||||
|
rotated_x = rel_x * cos_a - rel_y * sin_a
|
||||||
|
rotated_y = rel_x * sin_a + rel_y * cos_a
|
||||||
|
|
||||||
|
# Translate back
|
||||||
|
x = center_x + rotated_x
|
||||||
|
y = center_y + rotated_y
|
||||||
|
|
||||||
|
# Now check handles in non-rotated coordinate system
|
||||||
handles = {
|
handles = {
|
||||||
'nw': (ex - handle_size/2, ey - handle_size/2),
|
'nw': (ex - handle_size/2, ey - handle_size/2),
|
||||||
'ne': (ex + ew - handle_size/2, ey - handle_size/2),
|
'ne': (ex + ew - handle_size/2, ey - handle_size/2),
|
||||||
|
|||||||
@ -76,3 +76,97 @@ class SizeOperationsMixin:
|
|||||||
self.project.history.execute(cmd)
|
self.project.history.execute(cmd)
|
||||||
self.update_view()
|
self.update_view()
|
||||||
self.show_status(f"Resized {len(elements)} elements to same height", 2000)
|
self.show_status(f"Resized {len(elements)} elements to same height", 2000)
|
||||||
|
|
||||||
|
@ribbon_action(
|
||||||
|
label="Fit Width",
|
||||||
|
tooltip="Fit selected element to page width",
|
||||||
|
tab="Arrange",
|
||||||
|
group="Size",
|
||||||
|
requires_selection=True,
|
||||||
|
min_selection=1
|
||||||
|
)
|
||||||
|
def fit_to_width(self):
|
||||||
|
"""Fit selected element to page width"""
|
||||||
|
if not self.require_selection(min_count=1):
|
||||||
|
return
|
||||||
|
|
||||||
|
page = self.get_current_page()
|
||||||
|
if not page:
|
||||||
|
self.show_warning("No Page", "Please create a page first.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get the first selected element
|
||||||
|
element = next(iter(self.gl_widget.selected_elements))
|
||||||
|
|
||||||
|
# Fit to page width
|
||||||
|
page_width = page.size[0]
|
||||||
|
change = AlignmentManager.fit_to_page_width(element, page_width)
|
||||||
|
|
||||||
|
if change:
|
||||||
|
cmd = ResizeElementsCommand([change])
|
||||||
|
self.project.history.execute(cmd)
|
||||||
|
self.update_view()
|
||||||
|
self.show_status("Fitted element to page width", 2000)
|
||||||
|
|
||||||
|
@ribbon_action(
|
||||||
|
label="Fit Height",
|
||||||
|
tooltip="Fit selected element to page height",
|
||||||
|
tab="Arrange",
|
||||||
|
group="Size",
|
||||||
|
requires_selection=True,
|
||||||
|
min_selection=1
|
||||||
|
)
|
||||||
|
def fit_to_height(self):
|
||||||
|
"""Fit selected element to page height"""
|
||||||
|
if not self.require_selection(min_count=1):
|
||||||
|
return
|
||||||
|
|
||||||
|
page = self.get_current_page()
|
||||||
|
if not page:
|
||||||
|
self.show_warning("No Page", "Please create a page first.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get the first selected element
|
||||||
|
element = next(iter(self.gl_widget.selected_elements))
|
||||||
|
|
||||||
|
# Fit to page height
|
||||||
|
page_height = page.size[1]
|
||||||
|
change = AlignmentManager.fit_to_page_height(element, page_height)
|
||||||
|
|
||||||
|
if change:
|
||||||
|
cmd = ResizeElementsCommand([change])
|
||||||
|
self.project.history.execute(cmd)
|
||||||
|
self.update_view()
|
||||||
|
self.show_status("Fitted element to page height", 2000)
|
||||||
|
|
||||||
|
@ribbon_action(
|
||||||
|
label="Fit to Page",
|
||||||
|
tooltip="Fit selected element to page dimensions",
|
||||||
|
tab="Arrange",
|
||||||
|
group="Size",
|
||||||
|
requires_selection=True,
|
||||||
|
min_selection=1
|
||||||
|
)
|
||||||
|
def fit_to_page(self):
|
||||||
|
"""Fit selected element to page dimensions"""
|
||||||
|
if not self.require_selection(min_count=1):
|
||||||
|
return
|
||||||
|
|
||||||
|
page = self.get_current_page()
|
||||||
|
if not page:
|
||||||
|
self.show_warning("No Page", "Please create a page first.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get the first selected element
|
||||||
|
element = next(iter(self.gl_widget.selected_elements))
|
||||||
|
|
||||||
|
# Fit to page
|
||||||
|
page_width = page.size[0]
|
||||||
|
page_height = page.size[1]
|
||||||
|
change = AlignmentManager.fit_to_page(element, page_width, page_height)
|
||||||
|
|
||||||
|
if change:
|
||||||
|
cmd = ResizeElementsCommand([change])
|
||||||
|
self.project.history.execute(cmd)
|
||||||
|
self.update_view()
|
||||||
|
self.show_status("Fitted element to page", 2000)
|
||||||
|
|||||||
187
test_multiselect.py
Normal file
187
test_multiselect.py
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test script to verify multiselect visual feedback functionality
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from unittest.mock import Mock, patch, MagicMock
|
||||||
|
from PyQt6.QtWidgets import QApplication
|
||||||
|
from pyPhotoAlbum.gl_widget import GLWidget
|
||||||
|
from pyPhotoAlbum.models import ImageData
|
||||||
|
from pyPhotoAlbum.project import Project, Page
|
||||||
|
from pyPhotoAlbum.page_layout import PageLayout
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiselect_visual_feedback():
|
||||||
|
"""Test that all selected elements get selection handles drawn"""
|
||||||
|
|
||||||
|
print("Testing multiselect visual feedback...")
|
||||||
|
|
||||||
|
# Create a project with a page
|
||||||
|
project = Project("Test Project")
|
||||||
|
page_layout = PageLayout(width=200, height=200)
|
||||||
|
page = Page(layout=page_layout, page_number=1)
|
||||||
|
project.add_page(page)
|
||||||
|
|
||||||
|
# Create GL widget
|
||||||
|
widget = GLWidget()
|
||||||
|
|
||||||
|
# Mock the main window to return our project
|
||||||
|
mock_window = Mock()
|
||||||
|
mock_window.project = project
|
||||||
|
widget.window = Mock(return_value=mock_window)
|
||||||
|
|
||||||
|
# Create test elements
|
||||||
|
element1 = ImageData(image_path="test1.jpg", x=10, y=10, width=50, height=50)
|
||||||
|
element2 = ImageData(image_path="test2.jpg", x=70, y=70, width=50, height=50)
|
||||||
|
element3 = ImageData(image_path="test3.jpg", x=130, y=130, width=50, height=50)
|
||||||
|
|
||||||
|
# Set up page renderer mock for each element
|
||||||
|
mock_renderer = Mock()
|
||||||
|
mock_renderer.page_to_screen = Mock(side_effect=lambda x, y: (x, y))
|
||||||
|
mock_renderer.zoom = 1.0
|
||||||
|
|
||||||
|
element1._parent_page = page
|
||||||
|
element2._parent_page = page
|
||||||
|
element3._parent_page = page
|
||||||
|
|
||||||
|
element1._page_renderer = mock_renderer
|
||||||
|
element2._page_renderer = mock_renderer
|
||||||
|
element3._page_renderer = mock_renderer
|
||||||
|
|
||||||
|
# Add elements to page
|
||||||
|
page.layout.add_element(element1)
|
||||||
|
page.layout.add_element(element2)
|
||||||
|
page.layout.add_element(element3)
|
||||||
|
|
||||||
|
print(f"Created 3 test elements")
|
||||||
|
|
||||||
|
# Test 1: Single selection
|
||||||
|
print("\nTest 1: Single selection")
|
||||||
|
widget.selected_elements = {element1}
|
||||||
|
|
||||||
|
with patch.object(widget, '_draw_selection_handles') as mock_draw:
|
||||||
|
# Simulate paintGL call (only the relevant part)
|
||||||
|
for selected_elem in widget.selected_elements:
|
||||||
|
widget._draw_selection_handles(selected_elem)
|
||||||
|
|
||||||
|
assert mock_draw.call_count == 1, f"Expected 1 call, got {mock_draw.call_count}"
|
||||||
|
assert mock_draw.call_args[0][0] == element1, "Wrong element passed"
|
||||||
|
print(f"✓ Single selection: _draw_selection_handles called 1 time with element1")
|
||||||
|
|
||||||
|
# Test 2: Multiple selection (2 elements)
|
||||||
|
print("\nTest 2: Multiple selection (2 elements)")
|
||||||
|
widget.selected_elements = {element1, element2}
|
||||||
|
|
||||||
|
with patch.object(widget, '_draw_selection_handles') as mock_draw:
|
||||||
|
for selected_elem in widget.selected_elements:
|
||||||
|
widget._draw_selection_handles(selected_elem)
|
||||||
|
|
||||||
|
assert mock_draw.call_count == 2, f"Expected 2 calls, got {mock_draw.call_count}"
|
||||||
|
called_elements = {call[0][0] for call in mock_draw.call_args_list}
|
||||||
|
assert called_elements == {element1, element2}, f"Wrong elements passed: {called_elements}"
|
||||||
|
print(f"✓ Multiple selection (2): _draw_selection_handles called 2 times with correct elements")
|
||||||
|
|
||||||
|
# Test 3: Multiple selection (3 elements)
|
||||||
|
print("\nTest 3: Multiple selection (3 elements)")
|
||||||
|
widget.selected_elements = {element1, element2, element3}
|
||||||
|
|
||||||
|
with patch.object(widget, '_draw_selection_handles') as mock_draw:
|
||||||
|
for selected_elem in widget.selected_elements:
|
||||||
|
widget._draw_selection_handles(selected_elem)
|
||||||
|
|
||||||
|
assert mock_draw.call_count == 3, f"Expected 3 calls, got {mock_draw.call_count}"
|
||||||
|
called_elements = {call[0][0] for call in mock_draw.call_args_list}
|
||||||
|
assert called_elements == {element1, element2, element3}, f"Wrong elements passed: {called_elements}"
|
||||||
|
print(f"✓ Multiple selection (3): _draw_selection_handles called 3 times with correct elements")
|
||||||
|
|
||||||
|
# Test 4: No selection
|
||||||
|
print("\nTest 4: No selection")
|
||||||
|
widget.selected_elements = set()
|
||||||
|
|
||||||
|
with patch.object(widget, '_draw_selection_handles') as mock_draw:
|
||||||
|
for selected_elem in widget.selected_elements:
|
||||||
|
widget._draw_selection_handles(selected_elem)
|
||||||
|
|
||||||
|
assert mock_draw.call_count == 0, f"Expected 0 calls, got {mock_draw.call_count}"
|
||||||
|
print(f"✓ No selection: _draw_selection_handles not called")
|
||||||
|
|
||||||
|
# Test 5: Verify _draw_selection_handles receives correct element parameter
|
||||||
|
print("\nTest 5: Verify _draw_selection_handles uses passed element")
|
||||||
|
widget.selected_elements = {element2}
|
||||||
|
|
||||||
|
# Mock OpenGL functions
|
||||||
|
with patch('pyPhotoAlbum.gl_widget.glColor3f'), \
|
||||||
|
patch('pyPhotoAlbum.gl_widget.glLineWidth'), \
|
||||||
|
patch('pyPhotoAlbum.gl_widget.glBegin'), \
|
||||||
|
patch('pyPhotoAlbum.gl_widget.glEnd'), \
|
||||||
|
patch('pyPhotoAlbum.gl_widget.glVertex2f'), \
|
||||||
|
patch('pyPhotoAlbum.gl_widget.glPushMatrix'), \
|
||||||
|
patch('pyPhotoAlbum.gl_widget.glPopMatrix'), \
|
||||||
|
patch('pyPhotoAlbum.gl_widget.glTranslatef'), \
|
||||||
|
patch('pyPhotoAlbum.gl_widget.glRotatef'):
|
||||||
|
|
||||||
|
# Call the actual method
|
||||||
|
widget._draw_selection_handles(element2)
|
||||||
|
|
||||||
|
# Verify it used element2's properties
|
||||||
|
assert element2._page_renderer.page_to_screen.called, "page_to_screen should be called"
|
||||||
|
print(f"✓ _draw_selection_handles correctly uses the passed element parameter")
|
||||||
|
|
||||||
|
print("\n✓ All multiselect visual feedback tests passed!")
|
||||||
|
|
||||||
|
|
||||||
|
def test_regression_old_code_bug():
|
||||||
|
"""
|
||||||
|
Regression test: Verify the old bug (only first element gets handles)
|
||||||
|
would have been caught by this test
|
||||||
|
"""
|
||||||
|
print("\nRegression test: Simulating old buggy behavior...")
|
||||||
|
|
||||||
|
widget = GLWidget()
|
||||||
|
|
||||||
|
# Create mock elements
|
||||||
|
element1 = Mock()
|
||||||
|
element2 = Mock()
|
||||||
|
element3 = Mock()
|
||||||
|
|
||||||
|
# Select multiple elements
|
||||||
|
widget.selected_elements = {element1, element2, element3}
|
||||||
|
|
||||||
|
# OLD BUGGY CODE (what we fixed):
|
||||||
|
# if self.selected_element: # This only returns first element!
|
||||||
|
# self._draw_selection_handles()
|
||||||
|
|
||||||
|
# Simulate old behavior
|
||||||
|
call_count_old = 0
|
||||||
|
if widget.selected_element: # This property returns only first element
|
||||||
|
call_count_old = 1
|
||||||
|
|
||||||
|
# NEW CORRECT CODE:
|
||||||
|
# for selected_elem in self.selected_elements:
|
||||||
|
# self._draw_selection_handles(selected_elem)
|
||||||
|
|
||||||
|
# Simulate new behavior
|
||||||
|
call_count_new = 0
|
||||||
|
for selected_elem in widget.selected_elements:
|
||||||
|
call_count_new += 1
|
||||||
|
|
||||||
|
print(f"Old buggy code: would call _draw_selection_handles {call_count_old} time(s)")
|
||||||
|
print(f"New fixed code: calls _draw_selection_handles {call_count_new} time(s)")
|
||||||
|
|
||||||
|
assert call_count_old == 1, "Old code should only handle 1 element"
|
||||||
|
assert call_count_new == 3, "New code should handle all 3 elements"
|
||||||
|
|
||||||
|
print("✓ Regression test confirms the bug would have been caught!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Initialize Qt application (needed for PyQt6 widgets)
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
|
||||||
|
test_multiselect_visual_feedback()
|
||||||
|
test_regression_old_code_bug()
|
||||||
|
|
||||||
|
print("\n" + "="*60)
|
||||||
|
print("All tests completed successfully!")
|
||||||
|
print("="*60)
|
||||||
Loading…
x
Reference in New Issue
Block a user