Skip to content

Commit 0c89e48

Browse files
refactor: Refactor main window (#26)
* refactor: Add rotation and status bar utilities, refactor main window navigation - Implemented `compute_next_after_rotation` in `rotation_utils.py` to determine the next path after rotations. - Created `StatusBarInfo` class and `build_status_bar_info` function in `statusbar_utils.py` for structured status messages. - Refactored `_group_images_by_cluster` in `main_window.py` to use `ClusterUtils`. - Updated navigation methods in `main_window.py` to utilize new `navigate_group_cyclic` and `navigate_linear` functions. - Removed obsolete navigation methods `_index_below` and `_index_above` from `main_window.py`. - Added tests for rotation utilities, status bar utilities, cluster utilities, and navigation utilities. - Ensured proper handling of file paths and metadata in the new utility functions. * refactor: Ruff check and format * fix: Update system dependencies for headless PyQt6 usage and ensure Qt runs without an X server * fix: Update pytest artifact upload step to ignore missing files * Add comprehensive tests for file deletion, filtering, navigation, and similarity controllers - Implement tests for FileDeletionController to verify single and multi-selection deletion behavior, including header pruning. - Create tests for FilterController to ensure search and cluster filtering functionality works as expected. - Add deferred initialization tests for FilterController to check behavior when proxy models are set after controller creation. - Update tests for finding proxy indices to avoid pytest collection warnings by renaming test classes. - Introduce integration tests for HotkeyController to validate skip_deleted flag propagation. - Develop tests for MetadataController to confirm refresh behavior on selection changes. - Implement navigation controller tests to validate group and linear navigation, including handling of deleted images. - Add tests for PreviewController to ensure correct preload behavior and status messaging. - Create tests for RotationController to verify acceptance and refusal of rotation paths. - Implement selection controller tests to confirm correct retrieval of selected file paths. - Add similarity controller tests to validate clustering and embedding functionality. - Introduce tests for smart navigation to ensure correct cycling behavior within groups. * Add tests for selection visibility and navigation in file system model - Implemented tests for finding first and last visible items in both list and tree contexts. - Created a new test suite for smart down navigation, ensuring sequential navigation through image items. - Added tests for smart up navigation, verifying sequential behavior and preventing wrap-around within groups. - Introduced simple and fixed test cases for both down and up navigation scenarios. - Refactored navigation logic to work without proxy models, simplifying the selection process. * refactor: Enhance file deletion and navigation tests with improved state management and selection preservation * Add comprehensive tests for image rotation and metadata processing - Introduced `test_image_rotator.py` to validate the functionality of the ImageRotator class, including rotation support, orientation calculations, and error handling. - Enhanced `test_metadata_controller.py` with parameterized tests to ensure metadata refresh behavior on selection changes and sidebar visibility. - Created `test_metadata_processor_constants.py` to verify the integrity of metadata-related constants. - Developed `test_metadata_processor_core.py` for extensive testing of the MetadataProcessor class, covering batch metadata extraction, caching, and error handling. - Added `test_metadata_processor_helpers.py` to validate helper functions used in metadata processing. - Implemented `test_metadata_processor_rotation.py` to test rotation methods and their integration with caching. - Updated `test_navigation_controller.py` to improve navigation tests, ensuring correct behavior with deleted items. - Introduced `test_rotation_integration.py` to validate the complete rotation system through integration tests. * style: Format code for consistency and readability in test files * style: Clean up import statements for consistency across caching and UI modules
1 parent 342a2c5 commit 0c89e48

File tree

60 files changed

+5145
-2192
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+5145
-2192
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ jobs:
3535
- name: Install system deps
3636
run: |
3737
sudo apt-get update
38-
sudo apt-get install -y libgl1 libglib2.0-0
38+
# Base GL/GLib plus EGL for headless PyQt6 usage
39+
sudo apt-get install -y libgl1 libglib2.0-0 libegl1
3940
4041
- name: Cache pip
4142
uses: actions/cache@v4
@@ -59,6 +60,8 @@ jobs:
5960
- name: Run Tests
6061
env:
6162
PYTHONWARNINGS: default
63+
# Ensure Qt runs without an X server
64+
QT_QPA_PLATFORM: offscreen
6265
run: |
6366
pytest -q --cov=src --cov-report=xml:coverage.xml --cov-report=term
6467
# Single coverage dataset only; no secondary copying.
@@ -77,8 +80,9 @@ jobs:
7780
with:
7881
name: pytest-artifacts
7982
path: |
80-
.pytest_cache
83+
.pytest_cache/
8184
./**/pytest-*.log
85+
if-no-files-found: ignore
8286
retention-days: 5
8387

8488

DEVELOPER_GUIDE.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,30 @@ The application is structured into two main packages: `core` and `ui`.
2727
- **`app_controller.py`**: The controller that mediates between the UI and the `core` logic. It handles user actions, calls the appropriate `core` services, and updates the UI.
2828
- **`app_state.py`**: Holds the application's runtime state, including caches and loaded data. This object is shared across the application.
2929
- **`worker_manager.py`**: Manages all background threads and workers, decoupling the UI from long-running tasks.
30+
- **Controller Layer (Encapsulation Refactor)**: Non-trivial UI behaviors previously embedded in `MainWindow` have been extracted into focused controllers under `src/ui/controllers/`:
31+
- `navigation_controller.py`: Linear & group-aware navigation (honors skip-deleted logic, smart up/down traversal). Consumes a minimal protocol for selection & model interrogation.
32+
- `hotkey_controller.py`: Central mapping of key events to actions (allows headless tests to exercise hotkey dispatch logic).
33+
- `similarity_controller.py`: Orchestrates similarity analysis workflow (start, embeddings, clustering) AND (post-refactor) prepares cluster structures via `prepare_clusters()` returning pure data used by the view. Sorting strategies (Default / Time / Similarity then Time) live here; PCA fallback rationale documented inline. Uses only a protocol subset of `AppState` (see `AppStateSimilarityView`) for loose coupling.
34+
- `preview_controller.py`: Handles preview image loading / refresh separation from navigation triggers.
35+
- `metadata_controller.py`: Updates metadata sidebar without polluting MainWindow event handlers.
36+
- `filter_controller.py`: Applies user-entered filter text / rating filters to proxy model.
37+
- `selection_controller.py`: Shared selection operations & multi-select semantics.
38+
- `deletion_mark_controller.py`: Non-destructive mark/unmark & presentation (text color/blur). Distinguished from actual deletion for clarity & test isolation.
39+
- `file_deletion_controller.py`: Destructive operations (move to trash), reverse-order removal, prunes empty headers, restores deterministic selection.
40+
41+
Extension Pattern:
42+
1. Identify a cohesive behavior cluster in `MainWindow` (heuristics, branching logic, side-effect orchestration).
43+
2. Define a minimal Protocol capturing only what the controller needs (attributes + methods). Avoid passing full MainWindow if possible.
44+
3. Implement controller with pure helpers; return data structures instead of mutating widgets directly where feasible.
45+
4. Add targeted tests for new controller focusing on logic not easily covered via GUI.
46+
5. Replace in-place logic with controller delegation. Remove obsolete helpers after tests pass.
47+
6. Document rationale (fallbacks, sentinels like `date_obj.max`) inline for future maintainers.
48+
49+
Benefits:
50+
- Smaller `main_window.py` surface area.
51+
- Faster unit tests (controllers testable without QApplication event loop).
52+
- Clear separation of destructive vs non-destructive operations (mark vs delete).
53+
- Easier future feature toggles or re-use in alternate front-ends.
3054
- **`dialog_manager.py`**: Manages the creation and display of all dialog boxes. `show_about_dialog(block: bool = True)` supports a non-blocking mode (`block=False`) used by tests; prefer blocking mode in production UI code.
3155
- **`menu_manager.py`**: Manages the main menu bar and its actions.
3256
- **`left_panel.py`**, **`metadata_sidebar.py`**, **`advanced_image_viewer.py`**: Reusable UI components.
@@ -174,6 +198,17 @@ When you:
174198

175199
Update the relevant sections instead of appending ad hoc notes at the bottom.
176200

201+
### 8.1 Recent Refactor Summary (Encapsulation Phase)
202+
203+
Refactors completed:
204+
- Extracted navigation & hotkey handling (legacy behaviors restored: Ctrl includes deleted, cyclic horizontal navigation, smart vertical grouping).
205+
- Split deletion responsibilities: presentation/marking vs filesystem deletion.
206+
- Moved clustering grouping & sorting to `SimilarityController.prepare_clusters()`; `MainWindow` now only renders structure.
207+
- Introduced protocol-driven design (e.g., `AppStateSimilarityView`) for type clarity and decoupling.
208+
- Added deterministic cluster sorting strategies with PCA + timestamp fallback (see docstring in `ClusterUtils.sort_clusters_by_similarity_time`).
209+
210+
When adding new controllers, follow the listed Extension Pattern to maintain consistency.
211+
177212
## 9. Common Pitfalls
178213

179214
| Scenario | Recommended Action |

src/core/caching/exif_cache.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import diskcache
22
import os
33
import logging
4-
import time # Added for startup timing
4+
import time
55
from typing import Optional, Dict, Any
66

77
# Import the settings functions to get the cache size limit

src/core/caching/preview_cache.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import diskcache
22
import os
33
import logging
4-
import time # Added for startup timing
4+
import time
55
from PIL import Image
66
from typing import Optional, Tuple
77

src/core/caching/rating_cache.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import diskcache
22
import os
33
import logging
4-
import time # Added for startup timing
4+
import time
55
from typing import Optional
66
from src.core.app_settings import DEFAULT_RATING_CACHE_SIZE_LIMIT_MB
77

src/core/caching/thumbnail_cache.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import diskcache
22
import os
33
import logging
4-
import time # Added for startup timing
4+
import time
55
from PIL import Image
66
from typing import Optional, Tuple
77
from src.core.app_settings import (

src/core/file_scanner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def scan_directory(
141141
)
142142
continue
143143

144-
is_blurred = None # Initialize as None
144+
is_blurred = None
145145
if perform_blur_detection:
146146
# Perform blur detection
147147
# Pass the apply_auto_edits flag to control RAW preview generation for blur detection

src/core/image_processing/raw_image_processor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import logging
66
import time
77
from typing import Optional, Set
8-
from PIL import ImageEnhance # Added for brightness adjustment on PIL images
8+
from PIL import ImageEnhance
99
from src.core.app_settings import (
1010
RAW_AUTO_EDIT_BRIGHTNESS_STANDARD,
1111
RAW_AUTO_EDIT_BRIGHTNESS_ENHANCED,

src/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import pyexiv2 # noqa: F401 # This must be the first import or else it will cause a silent crash on windows
22
import sys
33
import os
4-
import logging # Added for startup logging
5-
import time # Added for startup timing
4+
import logging
5+
import time
66
import argparse
77
import traceback # For global exception handler
88
from PyQt6.QtWidgets import (

src/ui/app_controller.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,6 @@ def start_auto_rotation_analysis(self):
312312
self.main_window.show_loading_overlay("Starting rotation analysis...")
313313
self.main_window.menu_manager.auto_rotate_action.setEnabled(False)
314314

315-
# Initialize the rotation suggestions storage
316315
self.main_window.rotation_suggestions.clear()
317316

318317
image_paths = [fd["path"] for fd in self.app_state.image_files_data]

0 commit comments

Comments
 (0)