Skip to content

Commit d0fe52b

Browse files
committed
V3.0.1: Fix clean exit and icon loading for packaged app
Ensures the application process exits cleanly by stopping animations and explicitly quitting the QApplication on window close. Adds a get_resource_path utility for robust icon loading in both PyInstaller onefile and onedir modes, and updates documentation and version numbers to 3.0.1 to reflect these bugfixes.
1 parent 3451f2d commit d0fe52b

4 files changed

Lines changed: 52 additions & 14 deletions

File tree

DESIGN_SYSTEM.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# ResearchFlow V3.0.0 Design System
1+
# ResearchFlow V3.0.1 Design System
22

33
This document serves as the Single Source of Truth (SSOT) for the visual design and user interface interactions of ResearchFlow V2.0.0.
44

README.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
</p>
1010

1111
<p align="center">
12-
<img src="https://img.shields.io/badge/version-3.0.0-blue.svg" alt="Version">
12+
<img src="https://img.shields.io/badge/version-3.0.1-blue.svg" alt="Version">
1313
<img src="https://img.shields.io/badge/python-3.10+-green.svg" alt="Python">
1414
<img src="https://img.shields.io/badge/platform-Windows-lightgrey.svg" alt="Platform">
1515
<img src="https://img.shields.io/badge/license-MIT-orange.svg" alt="License">
@@ -28,9 +28,13 @@ ResearchFlow is a portable, aesthetically pleasing desktop application designed
2828

2929
---
3030

31-
## ✨ What's New in V3.0.0
31+
## ✨ What's New in V3.0.1
3232

33-
### 📦 Portable Executable
33+
### 🐛 Bugfixes
34+
- **Clean Exit**: Fixed issue where application process remained running after closing the window.
35+
- **Icon Loading**: Fixed window icon not displaying in packaged .exe builds.
36+
37+
### 📦 V3.0.0 - Portable Executable
3438
- **One-Click .exe**: Download and run – no Python installation required!
3539
- **True Portable Design**: All project data stored next to the `.exe`, perfect for USB drives.
3640
- **PyInstaller Optimized**: Properly handles frozen environment paths.
@@ -132,12 +136,17 @@ Create a standalone portable executable that runs without Python installed:
132136

133137
```bash
134138
pip install pyinstaller
135-
pyinstaller --noconsole --onefile --icon=icon.ico --name="ResearchFlow" main.py
139+
140+
# Option 1: Single folder (recommended, faster startup)
141+
pyinstaller --noconsole --onedir --icon=icon.ico --add-data "icon.ico;." --name="ResearchFlow" main.py
142+
143+
# Option 2: Single file (slower startup, but just one file)
144+
pyinstaller --noconsole --onefile --icon=icon.ico --add-data "icon.ico;." --name="ResearchFlow" main.py
136145
```
137146

138-
The generated `ResearchFlow.exe` will be in the `dist/` folder.
147+
The generated files will be in the `dist/` folder.
139148

140-
> **✅ True Portable**: The `projects/` data folder is automatically created next to the `.exe` file, not in any temp or system folder. Copy the `.exe` and your `projects/` folder anywhere!
149+
> **✅ True Portable**: The `projects/` data folder is automatically created next to the `.exe` file, not in any temp or system folder. Copy the folder and your `projects/` anywhere!
141150
142151
---
143152

main.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
ProjectData, NodeData, NodeMetadata, Position, EdgeData, Snippet,
2727
generate_uuid
2828
)
29-
from utils import ProjectManager, extract_title_from_filename, get_app_root, ModernTheme
29+
from utils import ProjectManager, extract_title_from_filename, get_app_root, get_resource_path, ModernTheme
3030
from graphics_items import (
3131
BaseNodeItem, PipelineModuleItem, ReferenceNodeItem, EdgeItem,
3232
TempConnectionLine, Colors, SnippetItem
@@ -1073,7 +1073,7 @@ def _show_about(self) -> None:
10731073
QMessageBox.about(
10741074
self, "About ResearchFlow",
10751075
"<h2>ResearchFlow</h2>"
1076-
"<p>Version 3.0.0</p>"
1076+
"<p>Version 3.0.1</p>"
10771077
"<p>A portable research management tool for academics.</p>"
10781078
"<p>Built with Python and PyQt6.</p>"
10791079
"<hr>"
@@ -1129,10 +1129,24 @@ def _auto_save(self) -> None:
11291129
self.project_manager.save_project()
11301130

11311131
def closeEvent(self, event) -> None:
1132-
"""Handle window close."""
1132+
"""Handle window close - ensure clean shutdown."""
1133+
# Stop any running animations to prevent process hanging
1134+
if self._dock_animation:
1135+
self._dock_animation.stop()
1136+
self._dock_animation = None
1137+
1138+
# Stop view zoom animation
1139+
if hasattr(self, 'view') and self.view._zoom_animation:
1140+
self.view._zoom_animation.stop()
1141+
self.view._zoom_animation = None
1142+
1143+
# Save project if open
11331144
if self.project_manager.is_project_open:
11341145
self._save_project()
1146+
1147+
# Accept close and quit application
11351148
event.accept()
1149+
QApplication.instance().quit()
11361150

11371151

11381152
# ============================================================================
@@ -1145,9 +1159,10 @@ def main():
11451159

11461160
app = QApplication(sys.argv)
11471161

1148-
# Set Global Icon
1149-
icon_path = str(get_app_root() / "icon.ico")
1150-
app.setWindowIcon(QIcon(icon_path))
1162+
# Set Global Icon (use resource path for PyInstaller compatibility)
1163+
icon_path = get_resource_path("icon.ico")
1164+
if icon_path.exists():
1165+
app.setWindowIcon(QIcon(str(icon_path)))
11511166

11521167
# Apply Modern Theme Stylesheet
11531168
app.setStyleSheet(ModernTheme.get_stylesheet())

utils.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
def get_app_root() -> Path:
1919
"""
20-
Get the application root directory.
20+
Get the application root directory (for data storage like projects/).
2121
Handles both normal Python execution and PyInstaller bundled .exe.
2222
"""
2323
if getattr(sys, 'frozen', False):
@@ -29,6 +29,20 @@ def get_app_root() -> Path:
2929
return Path(__file__).parent.resolve()
3030

3131

32+
def get_resource_path(relative_path: str) -> Path:
33+
"""
34+
Get the path to a bundled resource file.
35+
Works both in normal Python and PyInstaller frozen environment.
36+
"""
37+
if getattr(sys, 'frozen', False):
38+
# PyInstaller creates a temp folder and stores path in _MEIPASS (onefile)
39+
# For onedir mode, resources are in the same folder as .exe
40+
base_path = getattr(sys, '_MEIPASS', Path(sys.executable).parent)
41+
return Path(base_path) / relative_path
42+
else:
43+
return Path(__file__).parent / relative_path
44+
45+
3246
def get_projects_dir() -> Path:
3347
"""Get the projects directory path."""
3448
return get_app_root() / "projects"

0 commit comments

Comments
 (0)