|
1 | 1 | import sys |
| 2 | +from pathlib import Path |
2 | 3 |
|
3 | 4 | from PyQt5.QtCore import QObject, pyqtSignal |
4 | 5 | from PyQt5.QtGui import QPalette, QColor |
5 | 6 | from PyQt5.QtWidgets import ( |
6 | 7 | QLabel, |
7 | 8 | QMainWindow, |
| 9 | + QMessageBox, |
8 | 10 | QToolBar, |
9 | 11 | QDockWidget, |
10 | 12 | QAction, |
@@ -217,9 +219,23 @@ def closeEvent(self, event): |
217 | 219 |
|
218 | 220 | if self.components["editor"].document().isModified(): |
219 | 221 |
|
220 | | - rv = confirm(self, "Confirm close", "Close without saving?") |
| 222 | + rv = QMessageBox.warning( |
| 223 | + self, |
| 224 | + "Unsaved changes", |
| 225 | + "Save changes before closing?", |
| 226 | + QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel, |
| 227 | + QMessageBox.Save, |
| 228 | + ) |
221 | 229 |
|
222 | | - if rv: |
| 230 | + if rv == QMessageBox.Save: |
| 231 | + self.components["editor"].save() |
| 232 | + if self.components["editor"].document().isModified(): |
| 233 | + # Save As was cancelled - abort the close. |
| 234 | + event.ignore() |
| 235 | + return |
| 236 | + event.accept() |
| 237 | + super(MainWindow, self).closeEvent(event) |
| 238 | + elif rv == QMessageBox.Discard: |
223 | 239 | event.accept() |
224 | 240 | super(MainWindow, self).closeEvent(event) |
225 | 241 | else: |
@@ -303,6 +319,16 @@ def prepare_menubar(self): |
303 | 319 | for comp in self.components.values(): |
304 | 320 | self.prepare_menubar_component(menus, comp.menuActions()) |
305 | 321 |
|
| 322 | + # Examples submenu |
| 323 | + menu_file.addSeparator() |
| 324 | + examples_menu = menu_file.addMenu("Examples") |
| 325 | + self._populate_examples_menu(examples_menu) |
| 326 | + |
| 327 | + menu_file.addSeparator() |
| 328 | + menu_file.addAction( |
| 329 | + QAction("Quit", self, shortcut="ctrl+Q", triggered=self.close) |
| 330 | + ) |
| 331 | + |
306 | 332 | # global menu elements |
307 | 333 | menu_view.addSeparator() |
308 | 334 | for d in self.findChildren(QDockWidget): |
@@ -505,6 +531,36 @@ def prepare_console(self): |
505 | 531 | } |
506 | 532 | ) |
507 | 533 |
|
| 534 | + def _examples_dir(self): |
| 535 | + # In a PyInstaller bundle examples are extracted alongside the package. |
| 536 | + # In development they live next to the cq_editor package directory. |
| 537 | + if getattr(sys, "frozen", False): |
| 538 | + return Path(sys._MEIPASS) / "examples" |
| 539 | + return Path(__file__).parent.parent / "examples" |
| 540 | + |
| 541 | + def _populate_examples_menu(self, menu): |
| 542 | + examples_dir = self._examples_dir() |
| 543 | + if not examples_dir.is_dir(): |
| 544 | + menu.setEnabled(False) |
| 545 | + return |
| 546 | + |
| 547 | + for path in sorted(examples_dir.glob("*.py")): |
| 548 | + # Strip the leading "NN_" numbering prefix for the menu label. |
| 549 | + label = path.stem |
| 550 | + if len(label) > 3 and label[2] == "_" and label[:2].isdigit(): |
| 551 | + label = label[3:] |
| 552 | + label = label.replace("_", " ").title() |
| 553 | + |
| 554 | + menu.addAction( |
| 555 | + QAction( |
| 556 | + label, |
| 557 | + self, |
| 558 | + triggered=lambda checked, p=path: self.components[ |
| 559 | + "editor" |
| 560 | + ].load_example(str(p)), |
| 561 | + ) |
| 562 | + ) |
| 563 | + |
508 | 564 | def fill_dummy(self): |
509 | 565 |
|
510 | 566 | self.components["editor"].set_text( |
|
0 commit comments