-
Notifications
You must be signed in to change notification settings - Fork 0
Add SuccessDialog with celebration animation, IDE launchers, and cross-platform file manager #60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| """Dialog components for the Plugin Configurator application.""" | ||
|
|
||
| from ui.dialogs.preset_management_dialog import PresetManagementDialog | ||
| from ui.dialogs.success_dialog import SuccessDialog | ||
|
|
||
| __all__ = ["PresetManagementDialog"] | ||
| __all__ = ["PresetManagementDialog", "SuccessDialog"] |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,274 @@ | ||||||
| """Success dialog shown after a project is generated successfully.""" | ||||||
|
|
||||||
| from __future__ import annotations | ||||||
|
|
||||||
| import platform | ||||||
| import shutil | ||||||
| import subprocess | ||||||
| from pathlib import Path | ||||||
|
|
||||||
| from PySide6.QtCore import Qt, QTimer, QUrl, Slot | ||||||
| from PySide6.QtGui import QDesktopServices | ||||||
| from PySide6.QtWidgets import ( | ||||||
| QDialog, | ||||||
| QDialogButtonBox, | ||||||
| QFrame, | ||||||
| QHBoxLayout, | ||||||
| QLabel, | ||||||
| QPushButton, | ||||||
| QSizePolicy, | ||||||
| QVBoxLayout, | ||||||
| QWidget, | ||||||
| ) | ||||||
|
|
||||||
| # Emoji frames for the celebration animation | ||||||
| _CELEBRATION_FRAMES = ["🎉", "🎊", "✨", "🌟", "⭐", "✨", "🎊", "🎉"] | ||||||
|
|
||||||
| # IDE definitions: (display_name, executable, args_before_path) | ||||||
| _IDE_DEFINITIONS: list[tuple[str, str, list[str]]] = [ | ||||||
| ("VSCode", "code", ["."]), | ||||||
| ("CLion", "clion", ["."]), | ||||||
| ("Xcode", "xcode-select", []), # macOS only - we handle separately | ||||||
| ] | ||||||
|
|
||||||
|
|
||||||
| def _detect_ides(project_path: str) -> list[tuple[str, callable]]: | ||||||
| """Return a list of (label, open_callable) for IDEs available on this machine. | ||||||
|
|
||||||
| Args: | ||||||
| project_path: Absolute path to the generated project directory. | ||||||
|
|
||||||
| Returns: | ||||||
| List of (display_label, callable) tuples where calling the callable | ||||||
| opens the project in the corresponding IDE. | ||||||
| """ | ||||||
| available: list[tuple[str, callable]] = [] | ||||||
| current_os = platform.system() | ||||||
|
Comment on lines
+35
to
+46
|
||||||
|
|
||||||
| # VSCode | ||||||
| if shutil.which("code"): | ||||||
| def _open_vscode(path: str = project_path) -> None: | ||||||
| subprocess.Popen(["code", path]) | ||||||
|
|
||||||
| available.append(("VSCode", _open_vscode)) | ||||||
|
|
||||||
| # CLion | ||||||
| if shutil.which("clion"): | ||||||
| def _open_clion(path: str = project_path) -> None: | ||||||
| subprocess.Popen(["clion", path]) | ||||||
|
|
||||||
| available.append(("CLion", _open_clion)) | ||||||
|
|
||||||
| # Xcode (macOS only) | ||||||
| if current_os == "Darwin" and shutil.which("xcodebuild"): | ||||||
| def _open_xcode(path: str = project_path) -> None: | ||||||
| # Look for an .xcodeproj or .xcworkspace in the project directory | ||||||
| p = Path(path) | ||||||
| for pattern in ("*.xcworkspace", "*.xcodeproj"): | ||||||
| matches = list(p.glob(pattern)) | ||||||
| if matches: | ||||||
| subprocess.Popen(["open", str(matches[0])]) | ||||||
| return | ||||||
| # Fall back to opening the folder | ||||||
| subprocess.Popen(["open", path]) | ||||||
|
|
||||||
| available.append(("Xcode", _open_xcode)) | ||||||
|
|
||||||
| return available | ||||||
|
|
||||||
|
|
||||||
| def _open_in_file_manager(path: str) -> None: | ||||||
| """Open *path* in the native file manager, cross-platform.""" | ||||||
| current_os = platform.system() | ||||||
| if current_os == "Darwin": | ||||||
| subprocess.Popen(["open", path]) | ||||||
| elif current_os == "Windows": | ||||||
| subprocess.Popen(["explorer", path]) | ||||||
| elif shutil.which("xdg-open"): | ||||||
| # Linux / other POSIX - try xdg-open, fall back to QDesktopServices | ||||||
| subprocess.Popen(["xdg-open", path]) | ||||||
| else: | ||||||
| QDesktopServices.openUrl(QUrl.fromLocalFile(path)) | ||||||
|
|
||||||
|
|
||||||
| def _file_manager_label() -> str: | ||||||
| """Return the platform-appropriate label for the file manager action.""" | ||||||
| current_os = platform.system() | ||||||
| if current_os == "Darwin": | ||||||
| return "Open in Finder" | ||||||
| if current_os == "Windows": | ||||||
| return "Open in Explorer" | ||||||
| return "Open in Files" | ||||||
|
|
||||||
|
|
||||||
| class SuccessDialog(QDialog): | ||||||
| """Dialog displayed after a project is generated successfully. | ||||||
|
|
||||||
| Features: | ||||||
| - Animated celebration header | ||||||
| - Project name and output location display | ||||||
| - "Open in IDE" buttons for each detected IDE (VSCode, Xcode, CLion) | ||||||
| - Platform-aware "Open in Finder/Explorer/Files" button | ||||||
| - "Close" button | ||||||
| - Green-accented styling for the success state | ||||||
| """ | ||||||
|
|
||||||
| def __init__( | ||||||
| self, | ||||||
| project_name: str, | ||||||
| output_directory: str, | ||||||
| parent: QWidget | None = None, | ||||||
| ) -> None: | ||||||
| super().__init__(parent) | ||||||
| self._project_name = project_name | ||||||
| self._output_directory = output_directory | ||||||
| self._animation_index = 0 | ||||||
|
|
||||||
| self.setWindowTitle("Project Generated Successfully") | ||||||
| self.setMinimumWidth(480) | ||||||
| self.setMinimumHeight(280) | ||||||
| self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred) | ||||||
|
|
||||||
| self._setup_ui() | ||||||
| self._start_animation() | ||||||
|
|
||||||
| # ------------------------------------------------------------------ | ||||||
| # UI construction | ||||||
| # ------------------------------------------------------------------ | ||||||
|
|
||||||
| def _setup_ui(self) -> None: | ||||||
| """Build the dialog layout.""" | ||||||
| root = QVBoxLayout(self) | ||||||
| root.setContentsMargins(24, 24, 24, 20) | ||||||
| root.setSpacing(16) | ||||||
|
|
||||||
| # Celebration header | ||||||
| header_frame = QFrame() | ||||||
| header_frame.setObjectName("successHeader") | ||||||
| header_frame.setStyleSheet( | ||||||
| "#successHeader {" | ||||||
| " background-color: #1e7e34;" | ||||||
| " border-radius: 8px;" | ||||||
| "}" | ||||||
| ) | ||||||
| header_layout = QVBoxLayout(header_frame) | ||||||
| header_layout.setContentsMargins(16, 12, 16, 12) | ||||||
| header_layout.setSpacing(4) | ||||||
|
|
||||||
| self._animation_label = QLabel("🎉") | ||||||
| self._animation_label.setAlignment(Qt.AlignmentFlag.AlignCenter) | ||||||
| self._animation_label.setStyleSheet("font-size: 32px;") | ||||||
| header_layout.addWidget(self._animation_label) | ||||||
|
|
||||||
| success_text = QLabel("Project Generated Successfully!") | ||||||
| success_text.setAlignment(Qt.AlignmentFlag.AlignCenter) | ||||||
| success_text.setStyleSheet( | ||||||
| "color: #ffffff; font-size: 16px; font-weight: bold;" | ||||||
| ) | ||||||
| header_layout.addWidget(success_text) | ||||||
|
|
||||||
| root.addWidget(header_frame) | ||||||
|
|
||||||
| # Project info panel | ||||||
| info_frame = QFrame() | ||||||
| info_frame.setFrameShape(QFrame.Shape.StyledPanel) | ||||||
| info_layout = QVBoxLayout(info_frame) | ||||||
| info_layout.setContentsMargins(12, 10, 12, 10) | ||||||
| info_layout.setSpacing(6) | ||||||
|
|
||||||
| name_row = QHBoxLayout() | ||||||
| name_title = QLabel("<b>Project:</b>") | ||||||
| name_title.setFixedWidth(80) | ||||||
| self._name_label = QLabel(self._project_name or "\u2014") | ||||||
| self._name_label.setTextInteractionFlags( | ||||||
| Qt.TextInteractionFlag.TextSelectableByMouse | ||||||
| ) | ||||||
| name_row.addWidget(name_title) | ||||||
| name_row.addWidget(self._name_label, stretch=1) | ||||||
| info_layout.addLayout(name_row) | ||||||
|
|
||||||
| path_row = QHBoxLayout() | ||||||
| path_title = QLabel("<b>Location:</b>") | ||||||
| path_title.setFixedWidth(80) | ||||||
| self._path_label = QLabel(self._output_directory or "\u2014") | ||||||
| self._path_label.setWordWrap(True) | ||||||
| self._path_label.setTextInteractionFlags( | ||||||
| Qt.TextInteractionFlag.TextSelectableByMouse | ||||||
| ) | ||||||
| path_row.addWidget(path_title) | ||||||
| path_row.addWidget(self._path_label, stretch=1) | ||||||
| info_layout.addLayout(path_row) | ||||||
|
|
||||||
| root.addWidget(info_frame) | ||||||
|
|
||||||
| # Action buttons | ||||||
| actions_layout = QVBoxLayout() | ||||||
| actions_layout.setSpacing(8) | ||||||
|
|
||||||
| # File-manager button (always shown) | ||||||
| fm_btn = QPushButton(f"\U0001f4c1 {_file_manager_label()}") | ||||||
| fm_btn.setMinimumHeight(36) | ||||||
| fm_btn.setToolTip(f"Open the project folder:\n{self._output_directory}") | ||||||
| fm_btn.clicked.connect(self._on_open_in_file_manager) | ||||||
| actions_layout.addWidget(fm_btn) | ||||||
|
|
||||||
| # IDE buttons (only for detected IDEs) | ||||||
| self._ide_actions = _detect_ides(self._output_directory) | ||||||
| if self._ide_actions: | ||||||
| ide_row = QHBoxLayout() | ||||||
| ide_row.setSpacing(8) | ||||||
| for ide_label, ide_fn in self._ide_actions: | ||||||
| btn = self._make_ide_button(ide_label, ide_fn) | ||||||
| ide_row.addWidget(btn) | ||||||
| actions_layout.addLayout(ide_row) | ||||||
|
Comment on lines
+215
to
+223
|
||||||
|
|
||||||
| root.addLayout(actions_layout) | ||||||
|
|
||||||
| # Dialog close button | ||||||
| button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Close) | ||||||
| button_box.rejected.connect(self.accept) | ||||||
|
||||||
| button_box.rejected.connect(self.accept) | |
| button_box.rejected.connect(self.reject) |
Copilot
AI
Apr 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_make_ide_button annotates callback as callable, which is not a valid typing annotation under mypy. Use collections.abc.Callable with an appropriate signature (e.g., Callable[[], None]) to match what clicked.connect expects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_IDE_DEFINITIONSis declared but never used. Either remove it to avoid confusing future readers, or refactor_detect_idesto build from this table so the definitions stay in one place.