Skip to content

Latest commit

 

History

History
364 lines (296 loc) · 15.1 KB

File metadata and controls

364 lines (296 loc) · 15.1 KB

Node Sizing and Pin Positioning Fix Plan

Problem Summary

There are two related bugs affecting node display and layout:

  1. Pin Positioning Bug: When nodes are created, deleted, and undone, pins don't position correctly until the node is manually resized
  2. Node Sizing Bug: When loading nodes with size smaller than minimum required for GUI + pins, the node is crushed and GUI elements are compressed

Root Cause Analysis

Pin Positioning Issue

  • Located in src/node.py _update_layout() method (lines 218-269)
  • Pin positioning is calculated correctly but visual update doesn't trigger properly
  • pin.update_label_pos() is called but pin visual refresh may not occur
  • Issue manifests during undo operations when nodes are recreated from serialized state

Node Sizing Issue

  • Located in src/node.py _calculate_minimum_height() and fit_size_to_content() methods
  • Minimum size calculation occurs but enforcement is inconsistent
  • During undo restoration in src/commands/node_commands.py DeleteNodeCommand.undo() (lines 250-384)
  • Size is set multiple times but may not respect GUI content minimum requirements

Comprehensive Fix Plan

Phase 1: Core Layout System Fixes

Task 1.1: Improve Pin Position Update Mechanism

Location: src/pin.py

  • Issue: update_label_pos() method (lines 61-68) only updates label position, not pin visual state
  • Fix: Add explicit pin visual refresh after position updates
  • Implementation:
    • Add update_visual_state() method to Pin class
    • Call self.update() to trigger Qt repaint
    • Ensure pin connections are also updated (update_connections())

Task 1.2: Enhance Node Layout Update Process

Location: src/node.py _update_layout() method

  • Issue: Layout calculation is correct but visual update chain is incomplete
  • Fix: Ensure complete visual refresh after layout changes
  • Implementation:
    • Call self.prepareGeometryChange() before any position changes
    • Force pin visual updates after positioning
    • Trigger self.update() to refresh node visual state
    • Update all pin connections after layout changes

Task 1.3: Fix Minimum Size Enforcement

Location: src/node.py _calculate_minimum_height() and fit_size_to_content()

  • Issue: Minimum size calculation doesn't account for all content properly
  • Fix: Improve minimum size calculation and enforcement
  • Implementation:
    • Include proxy widget minimum size requirements
    • Add safety margins for GUI content
    • Ensure width calculation includes pin labels and content
    • Prevent size from being set below calculated minimum

Task 1.4: Comprehensive Minimum Size Calculation System

Location: src/node.py

  • Issue: No comprehensive method to calculate absolute minimum node size for all content
  • Fix: Create robust minimum size calculation that accounts for all node components
  • Implementation:
    • Add calculate_absolute_minimum_size() method that returns (min_width, min_height)
    • Calculate minimum width based on:
      • Title text width
      • Longest pin label width (input and output sides)
      • GUI content minimum width
      • Minimum node padding and margins
    • Calculate minimum height based on:
      • Title bar height
      • Pin area height (max of input/output pin counts × pin_spacing)
      • GUI content minimum height
      • Required spacing and margins
    • Include safety margins for visual clarity
    • Account for resize handle area

Phase 2: File Loading and Undo/Redo System Fixes

Task 2.1: Add Minimum Size Validation on Node Loading

Location: src/file_operations.py and node creation/loading functions

  • Issue: Nodes can be loaded with sizes smaller than their minimum requirements, causing layout issues
  • Fix: Validate and correct node sizes during loading operations
  • Implementation:
    • Add validation check in node loading/deserialization functions
    • Call calculate_absolute_minimum_size() for each loaded node
    • Compare loaded size against calculated minimum size
    • If loaded size is smaller than minimum, automatically adjust to minimum
    • Log size corrections for debugging purposes
    • Apply this validation in:
      • Graph file loading (.md and .json formats)
      • Node creation from templates
      • Import operations
      • Any node deserialization process

Task 2.2: Improve Node Restoration Process

Location: src/commands/node_commands.py DeleteNodeCommand.undo()

  • Issue: Node recreation process doesn't properly trigger layout updates
  • Fix: Ensure proper initialization sequence during node restoration
  • Implementation:
    • Call fit_size_to_content() after all properties are set
    • Force _update_layout() after pin creation
    • Add explicit visual refresh after restoration
    • Ensure GUI state is applied before size calculations
    • Validate restored size against minimum requirements using new calculate_absolute_minimum_size()

Task 2.3: Add Post-Restoration Layout Validation

Location: src/commands/node_commands.py

  • Issue: No validation that restored node layout is correct
  • Fix: Add validation and correction step after node restoration
  • Implementation:
    • Check if node size meets minimum requirements using calculate_absolute_minimum_size()
    • Verify pin positions are within node bounds
    • Validate GUI content fits within allocated space
    • Force layout recalculation if validation fails
    • Apply minimum size corrections if necessary

Phase 3: Proactive Layout Management

Task 3.1: Add Layout Refresh Method

Location: src/node.py

  • Issue: No centralized way to force complete layout refresh
  • Fix: Create comprehensive refresh method
  • Implementation:
    • Add refresh_layout() method to Node class
    • Include pin positioning, size validation, and visual updates
    • Call from critical points: after undo, after loading, after code changes
    • Incorporate minimum size validation using calculate_absolute_minimum_size()
    • Auto-correct size if it's below minimum requirements

Task 3.2: Improve Content Widget Sizing

Location: src/node.py _update_layout() method

  • Issue: Proxy widget sizing logic is fragile (lines 256-264)
  • Fix: Make widget sizing more robust
  • Implementation:
    • Calculate content area more precisely
    • Add minimum content height enforcement
    • Handle edge cases where content is larger than available space

Phase 4: Integration and Testing

Task 4.1: Integration Testing

  • Goal: Ensure all components work together correctly
  • Tests:
    • Create node → delete → undo sequence
    • Load graphs with small node sizes
    • Resize nodes with different content types
    • Test with nodes containing GUI elements

Task 4.2: Performance Optimization

  • Goal: Ensure layout updates don't impact performance
  • Implementation:
    • Batch layout updates when possible
    • Avoid redundant calculations
    • Use lazy evaluation for expensive operations

Implementation Priority

High Priority (Fix Immediately)

  1. Task 1.4: Comprehensive Minimum Size Calculation System
  2. Task 2.1: Add Minimum Size Validation on Node Loading
  3. Task 1.2: Enhanced Node Layout Update Process
  4. Task 2.2: Improved Node Restoration Process

Medium Priority (Fix Soon)

  1. Task 1.1: Pin Position Update Mechanism
  2. Task 1.3: Minimum Size Enforcement
  3. Task 3.1: Layout Refresh Method
  4. Task 2.3: Post-Restoration Validation

Low Priority (Quality of Life)

  1. Task 3.2: Content Widget Sizing Improvements
  2. Task 4.2: Performance Optimization

Expected Outcomes

Bug Resolution

  • Pins will position correctly immediately after undo operations
  • Nodes will maintain proper minimum size during all operations
  • GUI elements will never be crushed or compressed
  • Nodes loaded from files will automatically resize to minimum requirements if saved too small
  • Comprehensive minimum size calculation prevents layout issues across all node types

Code Quality Improvements

  • More robust layout calculation system
  • Better separation of concerns between layout and visual updates
  • Improved error handling and validation

User Experience

  • Eliminated need for manual node resizing to fix layout
  • Consistent node appearance across all operations
  • More reliable undo/redo functionality

Technical Implementation Details

Minimum Size Calculation Algorithm

The calculate_absolute_minimum_size() method should implement the following logic:

def calculate_absolute_minimum_size(self) -> tuple[int, int]:
    """Calculate the absolute minimum size needed for this node's content."""
    
    # Base measurements
    title_height = 32
    pin_spacing = 25
    pin_margin_top = 15
    node_padding = 10
    resize_handle_size = 15
    
    # Calculate minimum width
    title_width = self._title_item.boundingRect().width() + 20  # Title + padding
    
    # Pin label widths (find longest on each side)
    max_input_label_width = max([pin.label.boundingRect().width() 
                                for pin in self.input_pins] or [0])
    max_output_label_width = max([pin.label.boundingRect().width() 
                                 for pin in self.output_pins] or [0])
    
    pin_label_width = max_input_label_width + max_output_label_width + 40  # Labels + pin spacing
    
    # GUI content minimum width
    gui_min_width = 0
    if self.content_container:
        gui_min_width = self.content_container.minimumSizeHint().width()
    
    min_width = max(
        self.base_width,  # Default base width
        title_width,
        pin_label_width,
        gui_min_width + node_padding
    )
    
    # Calculate minimum height
    max_pins = max(len(self.input_pins), len(self.output_pins))
    pin_area_height = (max_pins * pin_spacing) if max_pins > 0 else 0
    
    # GUI content minimum height
    gui_min_height = 0
    if self.content_container:
        gui_min_height = self.content_container.minimumSizeHint().height()
    
    min_height = (title_height + 
                  pin_margin_top + 
                  pin_area_height + 
                  gui_min_height + 
                  resize_handle_size +
                  node_padding)
    
    return (min_width, min_height)

Load-Time Size Validation

During node loading, implement this validation:

def validate_and_correct_node_size(node_data: dict) -> dict:
    """Validate node size against minimum requirements and correct if needed."""
    
    # Create temporary node to calculate minimum size
    temp_node = create_node_from_data(node_data)
    min_width, min_height = temp_node.calculate_absolute_minimum_size()
    
    loaded_width = node_data.get('size', [200, 150])[0]
    loaded_height = node_data.get('size', [200, 150])[1]
    
    corrected_width = max(loaded_width, min_width)
    corrected_height = max(loaded_height, min_height)
    
    if corrected_width != loaded_width or corrected_height != loaded_height:
        print(f"Node '{node_data['title']}' size corrected from "
              f"{loaded_width}x{loaded_height} to {corrected_width}x{corrected_height}")
        node_data['size'] = [corrected_width, corrected_height]
    
    return node_data

Implementation Notes

Code Patterns to Follow

  • Always call prepareGeometryChange() before modifying positions/sizes
  • Use consistent method naming: update_*() for calculations, refresh_*() for visual updates
  • Include proper error handling and fallback behavior
  • Follow existing code style and commenting patterns

Debugging Strategy

Important: These issues are highly dependent on GUI rendering, Qt layout systems, and real-time visual updates. Traditional unit tests are insufficient for debugging these problems.

Primary Debugging Approach: Debug Print Statements

  • Add comprehensive debug prints throughout the layout and sizing methods
  • Focus on key methods:
    • Node._update_layout() - track pin positioning calculations
    • Node.calculate_absolute_minimum_size() - verify size calculations
    • Node.fit_size_to_content() - monitor size adjustments
    • Pin.update_label_pos() - track pin position updates
    • DeleteNodeCommand.undo() - monitor restoration sequence

Debug Print Examples

def _update_layout(self):
    print(f"DEBUG: _update_layout() called for node '{self.title}'")
    print(f"DEBUG: Current size: {self.width}x{self.height}")
    print(f"DEBUG: Pin counts - input: {len(self.input_pins)}, output: {len(self.output_pins)}")
    
    # ... existing layout code ...
    
    for i, pin in enumerate(self.input_pins):
        print(f"DEBUG: Input pin {i} positioned at {pin.pos()}")
    
    print(f"DEBUG: _update_layout() completed")

Strategic Debug Points

  1. Size Validation Points:

    • Before and after fit_size_to_content()
    • During node loading/restoration
    • When size constraints are applied
  2. Pin Positioning Points:

    • Before and after pin position calculations
    • During visual updates
    • After undo operations
  3. Layout Trigger Points:

    • When _update_layout() is called
    • During GUI widget creation/rebuilding
    • After property changes

Live Testing Approach

  • Run the application with debug prints enabled
  • Perform the exact user scenario: create node → delete → undo
  • Monitor console output for layout sequence issues
  • Manually resize node to trigger correct layout, compare debug output
  • Use debug prints to identify where the layout chain breaks

Debug Print Management

  • During Development: Use extensive debug prints to trace execution flow
  • Conditional Debugging: Consider using a debug flag to enable/disable prints
DEBUG_LAYOUT = True  # Set to False for production

def _update_layout(self):
    if DEBUG_LAYOUT:
        print(f"DEBUG: _update_layout() called for node '{self.title}'")
    # ... rest of method
  • Post-Fix Cleanup: Remove or disable debug prints once issues are resolved
  • Keep Key Diagnostics: Retain essential debug prints that could help with future issues

Testing Strategy (Secondary)

While debug prints are primary, these tests support the debugging process:

  • Create unit tests for layout calculation methods (pure calculation testing)
  • Add integration tests for undo/redo scenarios
  • Include visual regression tests for node appearance
  • Test with various node types: code-only, GUI-enabled, different sizes
  • New minimum size tests:
    • Test calculate_absolute_minimum_size() with various content types
    • Load graphs with intentionally small node sizes and verify auto-correction
    • Test nodes with complex GUI content (many widgets, large content)
    • Verify minimum size calculations with different pin configurations
    • Test edge cases: no pins, many pins, long pin labels, wide titles

Maintenance Considerations

This plan addresses both immediate bugs and underlying architectural issues that could cause similar problems in the future. The proposed changes create a more robust foundation for node layout management while maintaining backward compatibility with existing functionality.

Regular testing of the undo/redo system and node layout should be performed, especially when making changes to the node system, pin system, or command system.