Reorganized the application around a clear frontend/backend boundary.
src/bedit/
├── __main__.py
├── core/ # Pure Python, no PySide
│ ├── model.py
│ ├── port_types.py
│ ├── serializer.py
│ └── libraries.py
└── gui/ # All Qt-dependent code
├── app.py
├── main_window.py
├── preferences.py
├── controllers/
├── dialogs/
├── graphics/
├── models/
└── generated/ # Designer/resource output only
Notable improvements:
Domain models, serialization, port types, and library parsing are now Qt-free.
Qt signals and undo infrastructure are explicitly isolated under gui/controllers.
Library parsing is separated from the Qt repository and tree models.
All generated Python is contained in gui/generated.
Designer build tasks now write to the generated directory.
The application entry point and package metadata use the new paths.
README now documents the structure and dependency rules.
Removed the old mixed document, library, and workspace packages.
This commit is contained in:
561
BEdit/src/bedit/gui/main_window.py
Normal file
561
BEdit/src/bedit/gui/main_window.py
Normal file
@@ -0,0 +1,561 @@
|
||||
import json
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import QSize, Qt, Slot
|
||||
from PySide6.QtGui import QAction, QCloseEvent
|
||||
from PySide6.QtWidgets import QFileDialog, QMainWindow, QMenu, QMessageBox, QToolBar
|
||||
|
||||
from bedit.core.model import Component, Port
|
||||
from bedit.core.serializer import JsonDocumentSerializer
|
||||
from bedit.gui.controllers.document import DocumentController
|
||||
from bedit.gui.dialogs.component_options import ComponentOptionsDialog
|
||||
from bedit.gui.dialogs.item_options import ItemOptionsDialog
|
||||
from bedit.gui.models.library_repository import LibraryRepository
|
||||
from bedit.gui.models.library_tree import (
|
||||
COMPONENT_ID_ROLE,
|
||||
COMPONENT_INSTANCE_ROLE,
|
||||
ITEM_KIND_ROLE,
|
||||
DocumentTreeModel,
|
||||
LibraryTreeModel,
|
||||
)
|
||||
from bedit.gui.dialogs.settings import SettingsDialog
|
||||
from bedit.gui.dialogs.port_options import PortOptionsDialog
|
||||
from bedit.gui.preferences import application_settings
|
||||
from bedit.gui.generated.ui_main_window import Ui_MainWindow
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
"""Application shell and owner of the single active document."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.ui = Ui_MainWindow()
|
||||
self.ui.setupUi(self)
|
||||
self.ui.emptyPage.setStyleSheet("background-color: #9a9a9a;")
|
||||
self.ui.emptyWorkspaceLabel.setStyleSheet(
|
||||
"background: transparent; color: #202020;"
|
||||
)
|
||||
self._create_camera_toolbar()
|
||||
self.settings = application_settings()
|
||||
|
||||
self.libraries = LibraryRepository(self)
|
||||
self.document_controller = DocumentController(self)
|
||||
self.library_tree_model = LibraryTreeModel(
|
||||
self.libraries,
|
||||
self.document_controller,
|
||||
self,
|
||||
)
|
||||
self.document_tree_model = DocumentTreeModel(self.document_controller, self)
|
||||
self._configure_models()
|
||||
self._connect_actions()
|
||||
self._populate_view_menu()
|
||||
self._restore_window_geometry()
|
||||
|
||||
self.ui.leftDockHost.setWindowFlags(Qt.WindowType.Widget)
|
||||
self.ui.leftDockHost.show()
|
||||
self.ui.panel_libraries.show()
|
||||
self.ui.panel_document.show()
|
||||
self.ui.leftDockHost.splitDockWidget(
|
||||
self.ui.panel_document,
|
||||
self.ui.panel_libraries,
|
||||
Qt.Orientation.Vertical,
|
||||
)
|
||||
self.ui.workspaceSplitter.setSizes([280, 720])
|
||||
self.reload_libraries()
|
||||
self._active_graph_changed()
|
||||
self._update_title()
|
||||
|
||||
def _configure_models(self) -> None:
|
||||
self.ui.treeView.setModel(self.library_tree_model)
|
||||
self.ui.treeView.setIconSize(QSize(28, 28))
|
||||
self.ui.treeView.setStyleSheet("QTreeView::item { height: 32px; }")
|
||||
self.ui.treeView.setHeaderHidden(True)
|
||||
self.ui.treeView.setDragEnabled(True)
|
||||
self.ui.treeView.setDragDropMode(self.ui.treeView.DragDropMode.DragOnly)
|
||||
self.ui.treeView.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||
self.ui.treeView.customContextMenuRequested.connect(
|
||||
self.show_external_library_context_menu
|
||||
)
|
||||
self.library_tree_model.rebuilt.connect(self.ui.treeView.expandAll)
|
||||
self.ui.documentTreeView.setModel(self.document_tree_model)
|
||||
self.ui.documentTreeView.setIconSize(QSize(16, 16))
|
||||
self.ui.documentTreeView.setHeaderHidden(True)
|
||||
self.ui.documentTreeView.setDragEnabled(True)
|
||||
self.ui.documentTreeView.setDragDropMode(
|
||||
self.ui.documentTreeView.DragDropMode.DragOnly
|
||||
)
|
||||
self.ui.documentTreeView.setContextMenuPolicy(
|
||||
Qt.ContextMenuPolicy.CustomContextMenu
|
||||
)
|
||||
self.ui.documentTreeView.customContextMenuRequested.connect(
|
||||
self.show_library_context_menu
|
||||
)
|
||||
self.ui.documentTreeView.doubleClicked.connect(self.activate_tree_component)
|
||||
self.document_tree_model.rebuilt.connect(self.ui.documentTreeView.expandAll)
|
||||
self.ui.graphView.set_model(self.document_controller)
|
||||
self.ui.graphView.componentOptionsRequested.connect(self.show_component_options)
|
||||
self.ui.graphView.componentPortOptionsRequested.connect(self.show_component_port_options)
|
||||
self.ui.graphView.portOptionsRequested.connect(self.show_port_options)
|
||||
self.ui.graphView.connectionOptionsRequested.connect(self.show_connection_options)
|
||||
self.ui.graphView.selectionAvailabilityChanged.connect(
|
||||
lambda _available: self._update_edit_actions()
|
||||
)
|
||||
self.ui.navigateUpButton.clicked.connect(self.navigate_up)
|
||||
self.ui.pointerToolButton.clicked.connect(lambda: self.set_graph_tool("pointer"))
|
||||
self.ui.inputToolButton.hide()
|
||||
self.ui.outputToolButton.hide()
|
||||
self.ui.applyJsonButton.clicked.connect(self.apply_json)
|
||||
self.document_controller.activeGraphChanged.connect(self._active_graph_changed)
|
||||
|
||||
def _create_camera_toolbar(self) -> None:
|
||||
self.cameraToolbar = QToolBar("Camera", self)
|
||||
self.cameraToolbar.setObjectName("cameraToolbar")
|
||||
self.actionZoomIn = QAction("Zoom In", self)
|
||||
self.actionZoomIn.setShortcut("Ctrl++")
|
||||
self.actionZoomOut = QAction("Zoom Out", self)
|
||||
self.actionZoomOut.setShortcut("Ctrl+-")
|
||||
self.actionCenterView = QAction("Center", self)
|
||||
self.actionCenterView.setShortcut("Ctrl+0")
|
||||
self.cameraToolbar.addActions(
|
||||
(self.actionZoomIn, self.actionZoomOut, self.actionCenterView)
|
||||
)
|
||||
self.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.cameraToolbar)
|
||||
|
||||
def _connect_actions(self) -> None:
|
||||
self.ui.actionNew.triggered.connect(self.new_document)
|
||||
self.ui.actionOpen.triggered.connect(self.open_document)
|
||||
self.ui.actionSave.triggered.connect(self.save_document)
|
||||
self.ui.actionSaveAs.triggered.connect(self.save_document_as)
|
||||
self.ui.actionClose.triggered.connect(self.close_document)
|
||||
self.ui.actionExit.triggered.connect(self.close)
|
||||
self.ui.actionSettings.triggered.connect(self.show_settings)
|
||||
self.ui.actionAbout.triggered.connect(self.show_about)
|
||||
self.ui.actionAboutQt.triggered.connect(
|
||||
lambda: QMessageBox.aboutQt(self, "About Qt Framework")
|
||||
)
|
||||
self.ui.actionUndo.triggered.connect(self.document_controller.undo_stack.undo)
|
||||
self.ui.actionRedo.triggered.connect(self.document_controller.undo_stack.redo)
|
||||
self.ui.actionDelete.triggered.connect(self.ui.graphView.delete_selected)
|
||||
self.ui.actionCopy.triggered.connect(self.ui.graphView.copy_selection)
|
||||
self.ui.actionCut.triggered.connect(self.ui.graphView.cut_selection)
|
||||
self.ui.actionPaste.triggered.connect(self.ui.graphView.paste_selection)
|
||||
self.ui.actionSelectAll.triggered.connect(self.ui.graphView.select_all)
|
||||
self.ui.actionRotateClockwise.triggered.connect(self.ui.graphView.rotate_selected)
|
||||
self.actionZoomIn.triggered.connect(self.ui.graphView.zoom_in)
|
||||
self.actionZoomOut.triggered.connect(self.ui.graphView.zoom_out)
|
||||
self.actionCenterView.triggered.connect(self.ui.graphView.center_workspace)
|
||||
self.document_controller.undo_stack.canUndoChanged.connect(self.ui.actionUndo.setEnabled)
|
||||
self.document_controller.undo_stack.canRedoChanged.connect(self.ui.actionRedo.setEnabled)
|
||||
self.document_controller.modifiedChanged.connect(lambda _modified: self._update_title())
|
||||
self.document_controller.filePathChanged.connect(lambda _path: self._update_title())
|
||||
self.document_controller.documentOpenedChanged.connect(self._document_opened_changed)
|
||||
self.ui.actionUndo.setEnabled(False)
|
||||
self.ui.actionRedo.setEnabled(False)
|
||||
self._update_edit_actions()
|
||||
self._document_opened_changed(False)
|
||||
|
||||
def _populate_view_menu(self) -> None:
|
||||
for panel in (self.ui.panel_document, self.ui.panel_libraries):
|
||||
self.ui.menuPanels.addAction(panel.toggleViewAction())
|
||||
for toolbar in (
|
||||
self.ui.fileToolbar,
|
||||
self.ui.editToolbar,
|
||||
self.ui.transformToolbar,
|
||||
self.cameraToolbar,
|
||||
):
|
||||
self.ui.menuToolbars.addAction(toolbar.toggleViewAction())
|
||||
|
||||
def reload_libraries(self) -> None:
|
||||
self.libraries.load_paths(SettingsDialog.library_paths(self.settings))
|
||||
self.ui.treeView.expandAll()
|
||||
if self.libraries.load_warnings:
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Some libraries could not be loaded",
|
||||
"\n".join(self.libraries.load_warnings),
|
||||
)
|
||||
|
||||
def _restore_window_geometry(self) -> None:
|
||||
geometry = self.settings.value("window/geometry")
|
||||
if geometry is not None:
|
||||
self.restoreGeometry(geometry)
|
||||
|
||||
def _update_title(self) -> None:
|
||||
if self.document_controller.document is None:
|
||||
self.setWindowTitle("BEdit")
|
||||
return
|
||||
name = self.document_controller.file_path.name if self.document_controller.file_path else "Untitled"
|
||||
modified = "*" if not self.document_controller.undo_stack.isClean() else ""
|
||||
self.setWindowTitle(f"{modified}{name} — BEdit")
|
||||
|
||||
def _active_graph_changed(self) -> None:
|
||||
component = self.document_controller.active_component
|
||||
if component is None:
|
||||
self.ui.graphBreadcrumbLabel.setText("No component selected")
|
||||
self.ui.navigateUpButton.setEnabled(False)
|
||||
self.ui.workspaceModeLabel.setText("")
|
||||
self.ui.workspaceStack.setCurrentWidget(self.ui.emptyPage)
|
||||
self.ui.applyJsonButton.setVisible(False)
|
||||
for button in (
|
||||
self.ui.pointerToolButton,
|
||||
self.ui.inputToolButton,
|
||||
self.ui.outputToolButton,
|
||||
):
|
||||
button.setVisible(False)
|
||||
self._update_edit_actions()
|
||||
return
|
||||
self.ui.graphBreadcrumbLabel.setText(" › ".join(self.document_controller.breadcrumb()))
|
||||
self.ui.navigateUpButton.setEnabled(
|
||||
self.document_controller.document.find_parent(component.id) is not None
|
||||
)
|
||||
is_graph = component.implementation_kind == "graph"
|
||||
self.ui.workspaceModeLabel.setText("Graph" if is_graph else "Text")
|
||||
self.ui.workspaceStack.setCurrentWidget(self.ui.graphPage if is_graph else self.ui.jsonPage)
|
||||
self.ui.applyJsonButton.setVisible(not is_graph)
|
||||
self.ui.pointerToolButton.setVisible(is_graph)
|
||||
self.ui.inputToolButton.hide()
|
||||
self.ui.outputToolButton.hide()
|
||||
if is_graph:
|
||||
self.set_graph_tool("pointer")
|
||||
else:
|
||||
self._load_source_json()
|
||||
self._update_edit_actions()
|
||||
|
||||
def _update_edit_actions(self) -> None:
|
||||
component = self.document_controller.active_component
|
||||
is_graph = component is not None and component.implementation_kind == "graph"
|
||||
has_selection = bool(self.ui.graphView.scene() and self.ui.graphView.scene().selectedItems())
|
||||
for action in (self.ui.actionCut, self.ui.actionCopy, self.ui.actionDelete):
|
||||
action.setEnabled(is_graph and has_selection)
|
||||
self.ui.actionRotateClockwise.setEnabled(
|
||||
is_graph and self.ui.graphView.has_selected_components()
|
||||
)
|
||||
self.ui.actionSelectAll.setEnabled(is_graph)
|
||||
self.ui.actionPaste.setEnabled(is_graph)
|
||||
|
||||
@Slot()
|
||||
def navigate_up(self) -> None:
|
||||
if self._resolve_source_edits():
|
||||
self.document_controller.navigate_up()
|
||||
|
||||
def set_graph_tool(self, mode: str) -> None:
|
||||
self.ui.graphView.set_tool_mode("pointer")
|
||||
self.ui.pointerToolButton.setChecked(True)
|
||||
|
||||
def _load_source_json(self) -> None:
|
||||
component = self.document_controller.active_component
|
||||
if component is None:
|
||||
return
|
||||
text = json.dumps(
|
||||
{
|
||||
"inputs": [port.to_dict() for port in component.inputs],
|
||||
"outputs": [port.to_dict() for port in component.outputs],
|
||||
"source": component.source,
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
self.ui.jsonEditor.setPlainText(text)
|
||||
self.ui.jsonEditor.document().setModified(False)
|
||||
|
||||
def _resolve_source_edits(self) -> bool:
|
||||
component = self.document_controller.active_component
|
||||
if (
|
||||
component is None
|
||||
or component.implementation_kind != "text"
|
||||
or not self.ui.jsonEditor.document().isModified()
|
||||
):
|
||||
return True
|
||||
answer = QMessageBox.question(
|
||||
self,
|
||||
"Apply text component changes?",
|
||||
"The text component has unapplied input, output, or source changes.",
|
||||
QMessageBox.StandardButton.Apply
|
||||
| QMessageBox.StandardButton.Discard
|
||||
| QMessageBox.StandardButton.Cancel,
|
||||
)
|
||||
if answer == QMessageBox.StandardButton.Apply:
|
||||
return self.apply_json()
|
||||
return answer == QMessageBox.StandardButton.Discard
|
||||
|
||||
@Slot()
|
||||
def apply_json(self) -> bool:
|
||||
try:
|
||||
data = json.loads(self.ui.jsonEditor.toPlainText())
|
||||
if not isinstance(data, dict):
|
||||
raise ValueError("The text component JSON must be an object")
|
||||
if not isinstance(data.get("inputs"), list):
|
||||
raise ValueError("'inputs' must be a list")
|
||||
if not isinstance(data.get("outputs"), list):
|
||||
raise ValueError("'outputs' must be a list")
|
||||
if not isinstance(data.get("source"), dict):
|
||||
raise ValueError("'source' must be an object")
|
||||
inputs = [Port.from_dict(item) for item in data["inputs"]]
|
||||
outputs = [Port.from_dict(item) for item in data["outputs"]]
|
||||
self.document_controller.replace_active_text_definition(
|
||||
inputs, outputs, data["source"]
|
||||
)
|
||||
except (TypeError, ValueError, json.JSONDecodeError) as error:
|
||||
QMessageBox.critical(self, "Invalid text component JSON", str(error))
|
||||
return False
|
||||
self._load_source_json()
|
||||
return True
|
||||
|
||||
def _maybe_save(self) -> bool:
|
||||
if self.document_controller.document is None:
|
||||
return True
|
||||
if self.document_controller.undo_stack.isClean():
|
||||
return True
|
||||
answer = QMessageBox.warning(
|
||||
self,
|
||||
"Unsaved changes",
|
||||
"The current graph contains unsaved changes.",
|
||||
QMessageBox.StandardButton.Save
|
||||
| QMessageBox.StandardButton.Discard
|
||||
| QMessageBox.StandardButton.Cancel,
|
||||
)
|
||||
if answer == QMessageBox.StandardButton.Save:
|
||||
return self.save_document()
|
||||
return answer == QMessageBox.StandardButton.Discard
|
||||
|
||||
@Slot()
|
||||
def new_document(self) -> None:
|
||||
if self._resolve_source_edits() and self._maybe_save():
|
||||
self.document_controller.new_document()
|
||||
|
||||
@Slot()
|
||||
def close_document(self) -> None:
|
||||
if self._resolve_source_edits() and self._maybe_save():
|
||||
self.document_controller.close_document()
|
||||
|
||||
@Slot()
|
||||
def open_document(self) -> None:
|
||||
if not self._resolve_source_edits() or not self._maybe_save():
|
||||
return
|
||||
filename, _ = QFileDialog.getOpenFileName(
|
||||
self, "Open graph", "", "BEdit graphs (*.bedit.json *.json);;All files (*)"
|
||||
)
|
||||
if not filename:
|
||||
return
|
||||
try:
|
||||
self.document_controller.load(Path(filename))
|
||||
except (OSError, ValueError) as error:
|
||||
QMessageBox.critical(self, "Could not open graph", str(error))
|
||||
|
||||
@Slot()
|
||||
def save_document(self) -> bool:
|
||||
if self.document_controller.document is None:
|
||||
return False
|
||||
if self.document_controller.file_path is None:
|
||||
return self.save_document_as()
|
||||
try:
|
||||
self.document_controller.save()
|
||||
except OSError as error:
|
||||
QMessageBox.critical(self, "Could not save graph", str(error))
|
||||
return False
|
||||
return True
|
||||
|
||||
@Slot()
|
||||
def save_document_as(self) -> bool:
|
||||
if self.document_controller.document is None:
|
||||
return False
|
||||
filename, _ = QFileDialog.getSaveFileName(
|
||||
self,
|
||||
"Save graph",
|
||||
"untitled.bedit.json",
|
||||
"BEdit graphs (*.bedit.json);;JSON files (*.json);;All files (*)",
|
||||
)
|
||||
if not filename:
|
||||
return False
|
||||
try:
|
||||
self.document_controller.save(Path(filename))
|
||||
except OSError as error:
|
||||
QMessageBox.critical(self, "Could not save graph", str(error))
|
||||
return False
|
||||
return True
|
||||
|
||||
@Slot()
|
||||
def show_settings(self) -> None:
|
||||
dialog = SettingsDialog(self)
|
||||
dialog.settingsChanged.connect(self.reload_libraries)
|
||||
dialog.settingsChanged.connect(self.refresh_editor_settings)
|
||||
dialog.exec()
|
||||
|
||||
def refresh_editor_settings(self) -> None:
|
||||
scene = self.ui.graphView.scene()
|
||||
if scene is not None:
|
||||
scene.update()
|
||||
self.ui.graphView.viewport().update()
|
||||
|
||||
def _document_opened_changed(self, opened: bool) -> None:
|
||||
for action in (self.ui.actionClose, self.ui.actionSave, self.ui.actionSaveAs):
|
||||
action.setEnabled(opened)
|
||||
self._active_graph_changed()
|
||||
|
||||
@Slot(object)
|
||||
def activate_tree_component(self, index) -> None:
|
||||
if index.data(ITEM_KIND_ROLE) != "current-component":
|
||||
return
|
||||
component_id = index.data(COMPONENT_ID_ROLE)
|
||||
if component_id:
|
||||
self.document_controller.activate_component(component_id)
|
||||
|
||||
@Slot(object)
|
||||
def show_library_context_menu(self, position) -> None:
|
||||
tree_view = self.ui.documentTreeView
|
||||
index = tree_view.indexAt(position)
|
||||
kind = index.data(ITEM_KIND_ROLE)
|
||||
if kind == "current-component":
|
||||
component_id = index.data(COMPONENT_ID_ROLE)
|
||||
document = self.document_controller.document
|
||||
component = document.find_component(component_id) if document else None
|
||||
if component is None:
|
||||
return
|
||||
menu = QMenu(self)
|
||||
graph_action = None
|
||||
text_action = None
|
||||
if component.implementation_kind == "graph":
|
||||
graph_action = menu.addAction("New Graph Block")
|
||||
text_action = menu.addAction("New Text Block")
|
||||
menu.addSeparator()
|
||||
options_action = menu.addAction("Component Options…")
|
||||
ports_action = menu.addAction("Port Options…")
|
||||
delete_action = menu.addAction("Delete")
|
||||
selected = menu.exec(tree_view.viewport().mapToGlobal(position))
|
||||
if selected is graph_action:
|
||||
self.document_controller.add_child(component_id, "graph")
|
||||
elif selected is text_action:
|
||||
self.document_controller.add_child(component_id, "text")
|
||||
elif selected is options_action:
|
||||
self.show_component_options(component_id)
|
||||
elif selected is ports_action:
|
||||
self.show_component_port_options(component_id)
|
||||
elif selected is delete_action:
|
||||
answer = QMessageBox.question(
|
||||
self,
|
||||
"Delete component?",
|
||||
f"Delete {component.name!r} and all of its contents?",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
)
|
||||
if answer == QMessageBox.StandardButton.Yes:
|
||||
self.document_controller.delete_component(component_id)
|
||||
return
|
||||
if kind != "current-document":
|
||||
return
|
||||
menu = QMenu(self)
|
||||
graph_action = menu.addAction("New Graph Block")
|
||||
text_action = menu.addAction("New Text Block")
|
||||
selected = menu.exec(tree_view.viewport().mapToGlobal(position))
|
||||
if selected is graph_action:
|
||||
self.document_controller.add_root("graph")
|
||||
elif selected is text_action:
|
||||
self.document_controller.add_root("text")
|
||||
|
||||
@Slot(object)
|
||||
def show_external_library_context_menu(self, position) -> None:
|
||||
tree = self.ui.treeView
|
||||
index = tree.indexAt(position)
|
||||
component = index.data(COMPONENT_INSTANCE_ROLE)
|
||||
if not isinstance(component, Component):
|
||||
return
|
||||
menu = QMenu(self)
|
||||
ports_action = menu.addAction("Port Options…")
|
||||
if menu.exec(tree.viewport().mapToGlobal(position)) is ports_action:
|
||||
dialog = PortOptionsDialog(component, self)
|
||||
if dialog.exec() == dialog.DialogCode.Accepted:
|
||||
old_inputs, old_outputs = deepcopy(component.inputs), deepcopy(component.outputs)
|
||||
component.inputs = dialog.inputs
|
||||
component.outputs = dialog.outputs
|
||||
library = next(
|
||||
(
|
||||
library
|
||||
for library in self.libraries.libraries
|
||||
if any(item is component for item in library.document.all_components())
|
||||
),
|
||||
None,
|
||||
)
|
||||
try:
|
||||
if library is not None:
|
||||
library.document.validate()
|
||||
JsonDocumentSerializer.save(
|
||||
library.document, Path(library.source_path)
|
||||
)
|
||||
except (OSError, ValueError) as error:
|
||||
component.inputs, component.outputs = old_inputs, old_outputs
|
||||
QMessageBox.warning(self, "Cannot change library ports", str(error))
|
||||
self.library_tree_model.rebuild()
|
||||
|
||||
@Slot(str)
|
||||
def show_component_port_options(self, component_id: str) -> None:
|
||||
document = self.document_controller.document
|
||||
component = document.find_component(component_id) if document else None
|
||||
if component is None:
|
||||
return
|
||||
dialog = PortOptionsDialog(component, self)
|
||||
if dialog.exec() != dialog.DialogCode.Accepted:
|
||||
return
|
||||
try:
|
||||
self.document_controller.edit_component_ports(
|
||||
component_id, dialog.inputs, dialog.outputs
|
||||
)
|
||||
except ValueError as error:
|
||||
QMessageBox.warning(self, "Cannot change ports", str(error))
|
||||
|
||||
@Slot(str)
|
||||
def show_component_options(self, component_id: str) -> None:
|
||||
document = self.document_controller.document
|
||||
component = document.find_component(component_id) if document else None
|
||||
if component is None:
|
||||
return
|
||||
dialog = ComponentOptionsDialog(component, self)
|
||||
if dialog.exec() == dialog.DialogCode.Accepted:
|
||||
self.document_controller.edit_component_appearance(
|
||||
component_id,
|
||||
dialog.ui.nameEdit.text().strip(),
|
||||
dialog.edited_icon,
|
||||
dialog.edited_inputs,
|
||||
dialog.edited_outputs,
|
||||
dialog.ui.showSubtreeCheckBox.isChecked(),
|
||||
)
|
||||
|
||||
@Slot(str, str)
|
||||
def show_port_options(self, port_id: str, direction: str) -> None:
|
||||
owner = self.document_controller.active_component
|
||||
if owner is None:
|
||||
return
|
||||
ports = owner.inputs if direction == "input" else owner.outputs
|
||||
port = next((item for item in ports if item.id == port_id), None)
|
||||
if port is None:
|
||||
return
|
||||
dialog = ItemOptionsDialog(f"{direction.title()} Options", port.name, self)
|
||||
if dialog.exec() == dialog.DialogCode.Accepted:
|
||||
self.document_controller.rename_interface_port(port_id, dialog.name)
|
||||
|
||||
@Slot(str)
|
||||
def show_connection_options(self, connection_id: str) -> None:
|
||||
owner = self.document_controller.active_component
|
||||
if owner is None or owner.implementation_kind != "graph":
|
||||
return
|
||||
connection = owner.graph.connections.get(connection_id)
|
||||
if connection is None:
|
||||
return
|
||||
dialog = ItemOptionsDialog(
|
||||
"Connection Options", connection.name, self, name_required=False
|
||||
)
|
||||
if dialog.exec() == dialog.DialogCode.Accepted:
|
||||
self.document_controller.rename_connection(connection_id, dialog.name)
|
||||
|
||||
@Slot()
|
||||
def show_about(self) -> None:
|
||||
QMessageBox.about(
|
||||
self,
|
||||
"About BEdit",
|
||||
"<h3>BEdit</h3><p>A graphical editor built with Python and Qt.</p>",
|
||||
)
|
||||
|
||||
def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802 (Qt API name)
|
||||
if not self._resolve_source_edits() or not self._maybe_save():
|
||||
event.ignore()
|
||||
return
|
||||
self.settings.setValue("window/geometry", self.saveGeometry())
|
||||
event.accept()
|
||||
Reference in New Issue
Block a user