Copy/paste on canvas
This commit is contained in:
@@ -5,7 +5,7 @@ import argparse
|
|||||||
|
|
||||||
from PySide6.QtWidgets import QApplication, QMessageBox
|
from PySide6.QtWidgets import QApplication, QMessageBox
|
||||||
|
|
||||||
from bedit_gui.controllers.clipboard_controller import ClipboardController, DocumentTreeClipboardHandler, TextClipboardHandler
|
from bedit_gui.controllers.clipboard_controller import ClipboardController, DocumentTreeClipboardHandler, GraphEditorClipboardHandler, TextClipboardHandler
|
||||||
from bedit_gui.controllers.document_controller import DocumentController
|
from bedit_gui.controllers.document_controller import DocumentController
|
||||||
from bedit_gui.controllers.log_controller import LogController
|
from bedit_gui.controllers.log_controller import LogController
|
||||||
from bedit_gui.controllers.settings_controller import SettingsController
|
from bedit_gui.controllers.settings_controller import SettingsController
|
||||||
@@ -59,7 +59,7 @@ def main() -> int:
|
|||||||
ViewMenuController(window)
|
ViewMenuController(window)
|
||||||
document_tree_controller = DocumentTreeController(document, window)
|
document_tree_controller = DocumentTreeController(document, window)
|
||||||
clipboard = ClipboardService(app)
|
clipboard = ClipboardService(app)
|
||||||
ClipboardController(window, clipboard, [TextClipboardHandler(clipboard), DocumentTreeClipboardHandler(document, window.ui.documentTree, document_tree_controller.model, clipboard)])
|
ClipboardController(window, clipboard, [TextClipboardHandler(clipboard), DocumentTreeClipboardHandler(document, window.ui.documentTree, document_tree_controller.model, clipboard), GraphEditorClipboardHandler(document, window.graph_editor, clipboard)])
|
||||||
|
|
||||||
window_state_controller = WindowStateController(app, window)
|
window_state_controller = WindowStateController(app, window)
|
||||||
window_state_controller.restore()
|
window_state_controller.restore()
|
||||||
|
|||||||
@@ -108,15 +108,20 @@ class DeleteComponent(QUndoCommand):
|
|||||||
|
|
||||||
|
|
||||||
class PasteComponents(QUndoCommand):
|
class PasteComponents(QUndoCommand):
|
||||||
def __init__(self, document: object, target: dict[ComponentID, Component], components: dict[ComponentID, Component], icons: dict[ComponentID, Icon]) -> None:
|
def __init__(self, document: object, target: dict[ComponentID, Component], components: dict[ComponentID, Component], icons: dict[ComponentID, Icon], graph_id: ComponentID | None = None, positions: dict[ComponentID, tuple[int, int]] | None = None) -> None:
|
||||||
super().__init__("Paste components")
|
super().__init__("Paste components")
|
||||||
self.document = document
|
self.document = document
|
||||||
self.target = target
|
self.target = target
|
||||||
self.components = components
|
self.components = components
|
||||||
self.icons = icons
|
self.icons = icons
|
||||||
|
self.graph_id = graph_id
|
||||||
|
self.positions = positions or {}
|
||||||
|
|
||||||
def redo(self) -> None:
|
def redo(self) -> None:
|
||||||
self.target.update(self.components)
|
self.target.update(self.components)
|
||||||
|
if self.graph_id is not None:
|
||||||
|
for component_id, position in self.positions.items():
|
||||||
|
self.document._set_graph_component_position(self.graph_id, component_id, position)
|
||||||
self.document.model_changed.emit(self.document.model)
|
self.document.model_changed.emit(self.document.model)
|
||||||
for component_id, icon in self.icons.items():
|
for component_id, icon in self.icons.items():
|
||||||
self.document._set_component_icon(component_id, icon)
|
self.document._set_component_icon(component_id, icon)
|
||||||
@@ -124,6 +129,9 @@ class PasteComponents(QUndoCommand):
|
|||||||
def undo(self) -> None:
|
def undo(self) -> None:
|
||||||
for component_id in self.components:
|
for component_id in self.components:
|
||||||
self.target.pop(component_id, None)
|
self.target.pop(component_id, None)
|
||||||
|
if self.graph_id is not None:
|
||||||
|
for component_id in self.positions:
|
||||||
|
self.document._set_graph_component_position(self.graph_id, component_id, None)
|
||||||
self.document.model_changed.emit(self.document.model)
|
self.document.model_changed.emit(self.document.model)
|
||||||
for component_id in self.icons:
|
for component_id in self.icons:
|
||||||
self.document._set_component_icon(component_id, None)
|
self.document._set_component_icon(component_id, None)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from bedit_core.models import Document as CoreDocument
|
|||||||
from bedit_gui.documents import Document
|
from bedit_gui.documents import Document
|
||||||
from bedit_gui.services.clipboard import ClipboardService
|
from bedit_gui.services.clipboard import ClipboardService
|
||||||
from bedit_gui.services.component_clipboard import export_components, import_components
|
from bedit_gui.services.component_clipboard import export_components, import_components
|
||||||
|
from bedit_gui.views.graph_editor_widget import GraphEditorWidget
|
||||||
from bedit_gui.views.models.document_tree_model import DocumentTreeModel
|
from bedit_gui.views.models.document_tree_model import DocumentTreeModel
|
||||||
|
|
||||||
|
|
||||||
@@ -28,6 +29,9 @@ class ClipboardHandler(QObject):
|
|||||||
def can_paste(self) -> bool:
|
def can_paste(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def can_delete(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
def copy(self) -> None:
|
def copy(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -37,6 +41,9 @@ class ClipboardHandler(QObject):
|
|||||||
def paste(self) -> None:
|
def paste(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def delete(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TextClipboardHandler(ClipboardHandler):
|
class TextClipboardHandler(ClipboardHandler):
|
||||||
def __init__(self, clipboard: ClipboardService, parent: QObject | None = None) -> None:
|
def __init__(self, clipboard: ClipboardService, parent: QObject | None = None) -> None:
|
||||||
@@ -106,6 +113,9 @@ class DocumentTreeClipboardHandler(ClipboardHandler):
|
|||||||
def can_paste(self) -> bool:
|
def can_paste(self) -> bool:
|
||||||
return self._target() is not None and self.clipboard.has_format(ClipboardService.COMPONENTS_MIME)
|
return self._target() is not None and self.clipboard.has_format(ClipboardService.COMPONENTS_MIME)
|
||||||
|
|
||||||
|
def can_delete(self) -> bool:
|
||||||
|
return bool(self._selected_components())
|
||||||
|
|
||||||
def copy(self) -> None:
|
def copy(self) -> None:
|
||||||
components = self._selected_components()
|
components = self._selected_components()
|
||||||
if components:
|
if components:
|
||||||
@@ -129,6 +139,9 @@ class DocumentTreeClipboardHandler(ClipboardHandler):
|
|||||||
return
|
return
|
||||||
self.document.paste_components(target, components, icons)
|
self.document.paste_components(target, components, icons)
|
||||||
|
|
||||||
|
def delete(self) -> None:
|
||||||
|
self.document.delete_components(self._selected_components())
|
||||||
|
|
||||||
def _write_components(self, components: list[Component]) -> None:
|
def _write_components(self, components: list[Component]) -> None:
|
||||||
payload = export_components(self.document, components)
|
payload = export_components(self.document, components)
|
||||||
self.clipboard.set_json(ClipboardService.COMPONENTS_MIME, payload, "\n".join(component.name for component in components))
|
self.clipboard.set_json(ClipboardService.COMPONENTS_MIME, payload, "\n".join(component.name for component in components))
|
||||||
@@ -162,6 +175,73 @@ class DocumentTreeClipboardHandler(ClipboardHandler):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class GraphEditorClipboardHandler(ClipboardHandler):
|
||||||
|
def __init__(self, document: Document, editor: GraphEditorWidget, clipboard: ClipboardService, parent: QObject | None = None) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
self.document = document
|
||||||
|
self.editor = editor
|
||||||
|
self.clipboard = clipboard
|
||||||
|
editor.scene.selectionChanged.connect(self.availability_changed)
|
||||||
|
|
||||||
|
def owns_focus(self, widget: QWidget) -> bool:
|
||||||
|
return widget is self.editor or self.editor.isAncestorOf(widget)
|
||||||
|
|
||||||
|
def can_copy(self) -> bool:
|
||||||
|
return bool(self._selected_components())
|
||||||
|
|
||||||
|
def can_cut(self) -> bool:
|
||||||
|
return self.can_copy()
|
||||||
|
|
||||||
|
def can_paste(self) -> bool:
|
||||||
|
return self._target() is not None and self.clipboard.has_format(ClipboardService.COMPONENTS_MIME)
|
||||||
|
|
||||||
|
def can_delete(self) -> bool:
|
||||||
|
return self.can_copy()
|
||||||
|
|
||||||
|
def copy(self) -> None:
|
||||||
|
components = self._selected_components()
|
||||||
|
if components:
|
||||||
|
self._write_components(components)
|
||||||
|
|
||||||
|
def cut(self) -> None:
|
||||||
|
components = self._selected_components()
|
||||||
|
if components:
|
||||||
|
self._write_components(components)
|
||||||
|
self.document.delete_components(components)
|
||||||
|
|
||||||
|
def paste(self) -> None:
|
||||||
|
graph_component = self.editor.component()
|
||||||
|
payload = self.clipboard.get_json(ClipboardService.COMPONENTS_MIME)
|
||||||
|
if graph_component is None or not isinstance(graph_component.implementation, GraphImplementation) or payload is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
components, icons = import_components(payload)
|
||||||
|
except (TypeError, ValueError) as exc:
|
||||||
|
QMessageBox.critical(self.editor, "Could not paste components", str(exc))
|
||||||
|
return
|
||||||
|
x, y = self.editor.paste_position()
|
||||||
|
spacing = self.editor.snap_to_grid_size * 4
|
||||||
|
positions = {component_id: (x + index * spacing, y + index * spacing) for index, component_id in enumerate(components)}
|
||||||
|
self.document.paste_graph_components(graph_component, components, icons, positions)
|
||||||
|
|
||||||
|
def delete(self) -> None:
|
||||||
|
self.document.delete_components(self._selected_components())
|
||||||
|
|
||||||
|
def _write_components(self, components: list[Component]) -> None:
|
||||||
|
payload = export_components(self.document, components)
|
||||||
|
self.clipboard.set_json(ClipboardService.COMPONENTS_MIME, payload, "\n".join(component.name for component in components))
|
||||||
|
|
||||||
|
def _selected_components(self) -> list[Component]:
|
||||||
|
target = self._target()
|
||||||
|
if target is None:
|
||||||
|
return []
|
||||||
|
return [target[component_id] for component_id in self.editor.selected_component_ids() if component_id in target]
|
||||||
|
|
||||||
|
def _target(self) -> dict | None:
|
||||||
|
component = self.editor.component()
|
||||||
|
return component.implementation.graph.components if component is not None and isinstance(component.implementation, GraphImplementation) else None
|
||||||
|
|
||||||
|
|
||||||
class ClipboardController(QObject):
|
class ClipboardController(QObject):
|
||||||
def __init__(self, window: QMainWindow, clipboard: ClipboardService, handlers: list[ClipboardHandler]) -> None:
|
def __init__(self, window: QMainWindow, clipboard: ClipboardService, handlers: list[ClipboardHandler]) -> None:
|
||||||
super().__init__(window)
|
super().__init__(window)
|
||||||
@@ -171,6 +251,7 @@ class ClipboardController(QObject):
|
|||||||
window.ui.actionCopy.triggered.connect(self.copy)
|
window.ui.actionCopy.triggered.connect(self.copy)
|
||||||
window.ui.actionCut.triggered.connect(self.cut)
|
window.ui.actionCut.triggered.connect(self.cut)
|
||||||
window.ui.actionPaste.triggered.connect(self.paste)
|
window.ui.actionPaste.triggered.connect(self.paste)
|
||||||
|
window.ui.actionDelete.triggered.connect(self.delete)
|
||||||
application = QApplication.instance()
|
application = QApplication.instance()
|
||||||
application.focusChanged.connect(self.update_actions)
|
application.focusChanged.connect(self.update_actions)
|
||||||
application.installEventFilter(self)
|
application.installEventFilter(self)
|
||||||
@@ -209,8 +290,15 @@ class ClipboardController(QObject):
|
|||||||
handler.paste()
|
handler.paste()
|
||||||
self.update_actions()
|
self.update_actions()
|
||||||
|
|
||||||
|
def delete(self) -> None:
|
||||||
|
handler = self.active_handler()
|
||||||
|
if handler is not None and handler.can_delete():
|
||||||
|
handler.delete()
|
||||||
|
self.update_actions()
|
||||||
|
|
||||||
def update_actions(self, *_args: object) -> None:
|
def update_actions(self, *_args: object) -> None:
|
||||||
handler = self.active_handler()
|
handler = self.active_handler()
|
||||||
self.window.ui.actionCopy.setEnabled(handler is not None and handler.can_copy())
|
self.window.ui.actionCopy.setEnabled(handler is not None and handler.can_copy())
|
||||||
self.window.ui.actionCut.setEnabled(handler is not None and handler.can_cut())
|
self.window.ui.actionCut.setEnabled(handler is not None and handler.can_cut())
|
||||||
self.window.ui.actionPaste.setEnabled(handler is not None and handler.can_paste())
|
self.window.ui.actionPaste.setEnabled(handler is not None and handler.can_paste())
|
||||||
|
self.window.ui.actionDelete.setEnabled(handler is not None and handler.can_delete())
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ class DocumentTreeController(QObject):
|
|||||||
document.equation_text_changed.connect(self._on_equation_text_changed)
|
document.equation_text_changed.connect(self._on_equation_text_changed)
|
||||||
self.model.rename_document_requested.connect(self.document.rename)
|
self.model.rename_document_requested.connect(self.document.rename)
|
||||||
self.model.rename_component_requested.connect(self.document.rename_component)
|
self.model.rename_component_requested.connect(self.document.rename_component)
|
||||||
window.ui.actionDelete.triggered.connect(self.delete_selected_component)
|
|
||||||
window.graph_editor.component_move_requested.connect(self.document.move_graph_component)
|
window.graph_editor.component_move_requested.connect(self.document.move_graph_component)
|
||||||
|
window.graph_editor.component_context_menu_requested.connect(self._show_graph_component_context_menu)
|
||||||
|
|
||||||
# Add deselection with esc to this widget
|
# Add deselection with esc to this widget
|
||||||
window.ui.actionEscape.setShortcutContext(Qt.ShortcutContext.WidgetWithChildrenShortcut)
|
window.ui.actionEscape.setShortcutContext(Qt.ShortcutContext.WidgetWithChildrenShortcut)
|
||||||
@@ -98,13 +98,14 @@ class DocumentTreeController(QObject):
|
|||||||
|
|
||||||
def _on_document_changed(self, model: CoreDocument) -> None:
|
def _on_document_changed(self, model: CoreDocument) -> None:
|
||||||
"""Rebuild the tree whenever New/Open replaces the core document."""
|
"""Rebuild the tree whenever New/Open replaces the core document."""
|
||||||
self._show_component(None)
|
displayed_component = self.window.graph_editor.component() or self.window.equation_editor.component()
|
||||||
self.model.set_document(model)
|
self.model.set_document(model)
|
||||||
self._components = {}
|
self._components = {}
|
||||||
self._collect_components(model.root)
|
self._collect_components(model.root)
|
||||||
for component_id, component in self._components.items():
|
for component_id, component in self._components.items():
|
||||||
icon = self.document.component_icon(component_id)
|
icon = self.document.component_icon(component_id)
|
||||||
self.model.set_component_icon(component_id, render_fitted_icon(icon, component.interface.ports, ICON_SIZE))
|
self.model.set_component_icon(component_id, render_fitted_icon(icon, component.interface.ports, ICON_SIZE))
|
||||||
|
self._show_component(displayed_component if any(component is displayed_component for component in self._components.values()) else None)
|
||||||
|
|
||||||
# Optional presentation behavior. Later, you could instead remember
|
# Optional presentation behavior. Later, you could instead remember
|
||||||
# expanded component IDs and restore only those nodes.
|
# expanded component IDs and restore only those nodes.
|
||||||
@@ -161,27 +162,36 @@ class DocumentTreeController(QObject):
|
|||||||
if not isinstance(component, Component):
|
if not isinstance(component, Component):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self._show_component_context_menu(component, self.window.ui.documentTree.viewport().mapToGlobal(position))
|
||||||
|
|
||||||
|
def _show_graph_component_context_menu(self, component_id: ComponentID, global_position: QPoint) -> None:
|
||||||
|
component = self._components.get(component_id)
|
||||||
|
if component is not None:
|
||||||
|
self._show_component_context_menu(component, global_position)
|
||||||
|
|
||||||
|
def _show_component_context_menu(self, component: Component, global_position: QPoint) -> None:
|
||||||
menu = QMenu(self.window.ui.documentTree)
|
menu = QMenu(self.window.ui.documentTree)
|
||||||
edit_interface = menu.addAction("Edit Interface")
|
edit_interface = menu.addAction("Edit Interface")
|
||||||
edit_params = menu.addAction("Edit Parameters")
|
edit_params = menu.addAction("Edit Parameters")
|
||||||
edit_icon = menu.addAction("Edit Icon")
|
edit_icon = menu.addAction("Edit Icon")
|
||||||
menu.addSeparator()
|
menu.addSeparator()
|
||||||
|
add_graph_component = None
|
||||||
|
add_equation_component = None
|
||||||
|
if isinstance(component.implementation, GraphImplementation):
|
||||||
add_graph_component = menu.addAction("Add Graph Component")
|
add_graph_component = menu.addAction("Add Graph Component")
|
||||||
add_equation_component = menu.addAction("Add Equation Component")
|
add_equation_component = menu.addAction("Add Equation Component")
|
||||||
menu.addSeparator()
|
menu.addSeparator()
|
||||||
delete_component = menu.addAction("Delete Component")
|
delete_component = menu.addAction("Delete Component")
|
||||||
selected = menu.exec(
|
selected = menu.exec(global_position)
|
||||||
self.window.ui.documentTree.viewport().mapToGlobal(position)
|
|
||||||
)
|
|
||||||
if selected is edit_interface:
|
if selected is edit_interface:
|
||||||
self._edit_interface(component)
|
self._edit_interface(component)
|
||||||
elif selected is edit_params:
|
elif selected is edit_params:
|
||||||
self._edit_params(component)
|
self._edit_params(component)
|
||||||
elif selected is edit_icon:
|
elif selected is edit_icon:
|
||||||
self._edit_icon(component)
|
self._edit_icon(component)
|
||||||
elif selected is add_graph_component:
|
elif add_graph_component is not None and selected is add_graph_component:
|
||||||
self._add_graph_component(component)
|
self._add_graph_component(component)
|
||||||
elif selected is add_equation_component:
|
elif add_equation_component is not None and selected is add_equation_component:
|
||||||
self._add_equation_component(component)
|
self._add_equation_component(component)
|
||||||
elif selected is delete_component:
|
elif selected is delete_component:
|
||||||
self._delete_component(component)
|
self._delete_component(component)
|
||||||
|
|||||||
@@ -322,6 +322,16 @@ class Document(QObject):
|
|||||||
names.add(component.name)
|
names.add(component.name)
|
||||||
self.undo_stack.push(PasteComponents(self, target, components, icons))
|
self.undo_stack.push(PasteComponents(self, target, components, icons))
|
||||||
|
|
||||||
|
def paste_graph_components(self, graph_component: Component, components: dict[ComponentID, Component], icons: dict[ComponentID, Icon], positions: dict[ComponentID, tuple[int, int]]) -> None:
|
||||||
|
if not isinstance(graph_component.implementation, GraphImplementation) or not components:
|
||||||
|
return
|
||||||
|
target = graph_component.implementation.graph.components
|
||||||
|
names = {component.name for component in target.values()}
|
||||||
|
for component in components.values():
|
||||||
|
component.name = self._unique_name(names, component.name)
|
||||||
|
names.add(component.name)
|
||||||
|
self.undo_stack.push(PasteComponents(self, target, components, icons, self.component_id(graph_component), positions))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _unique_component_name(components: dict[ComponentID, Component], name: str) -> str:
|
def _unique_component_name(components: dict[ComponentID, Component], name: str) -> str:
|
||||||
return Document._unique_name({component.name for component in components.values()}, name)
|
return Document._unique_name({component.name for component in components.values()}, name)
|
||||||
|
|||||||
@@ -41,9 +41,8 @@
|
|||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionZoomToFit"/>
|
<addaction name="actionZoomToFit"/>
|
||||||
<addaction name="actionAddComponent"/>
|
<addaction name="actionMouseMode"/>
|
||||||
<addaction name="actionAddConnection"/>
|
<addaction name="actionConnectionMode"/>
|
||||||
<addaction name="actionDelete"/>
|
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@@ -72,28 +71,28 @@
|
|||||||
<string>Zoom canvas to fit</string>
|
<string>Zoom canvas to fit</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionAddComponent">
|
<action name="actionMouseMode">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/edit-select.png</normaloff>:/icons/icons/edit-select.png</iconset>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Add Component</string>
|
<string>Mouse Mode</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Add a component</string>
|
<string>Mouse Mode</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionAddConnection">
|
<action name="actionConnectionMode">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../resources/resources.qrc">
|
||||||
|
<normaloff>:/icons/icons/network-connect.png</normaloff>:/icons/icons/network-connect.png</iconset>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Add Connection</string>
|
<string>Connection Mode</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Add a connection</string>
|
<string>Connection Mode</string>
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionDelete">
|
|
||||||
<property name="text">
|
|
||||||
<string>Delete</string>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Delete selected graph items</string>
|
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
|
|||||||
@@ -30,12 +30,16 @@ class Ui_graphEditorWidget(object):
|
|||||||
icon = QIcon()
|
icon = QIcon()
|
||||||
icon.addFile(u":/icons/icons/zoom-original.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon.addFile(u":/icons/icons/zoom-original.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionZoomToFit.setIcon(icon)
|
self.actionZoomToFit.setIcon(icon)
|
||||||
self.actionAddComponent = QAction(graphEditorWidget)
|
self.actionMouseMode = QAction(graphEditorWidget)
|
||||||
self.actionAddComponent.setObjectName(u"actionAddComponent")
|
self.actionMouseMode.setObjectName(u"actionMouseMode")
|
||||||
self.actionAddConnection = QAction(graphEditorWidget)
|
icon1 = QIcon()
|
||||||
self.actionAddConnection.setObjectName(u"actionAddConnection")
|
icon1.addFile(u":/icons/icons/edit-select.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.actionDelete = QAction(graphEditorWidget)
|
self.actionMouseMode.setIcon(icon1)
|
||||||
self.actionDelete.setObjectName(u"actionDelete")
|
self.actionConnectionMode = QAction(graphEditorWidget)
|
||||||
|
self.actionConnectionMode.setObjectName(u"actionConnectionMode")
|
||||||
|
icon2 = QIcon()
|
||||||
|
icon2.addFile(u":/icons/icons/network-connect.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
|
self.actionConnectionMode.setIcon(icon2)
|
||||||
self.verticalLayout = QVBoxLayout(graphEditorWidget)
|
self.verticalLayout = QVBoxLayout(graphEditorWidget)
|
||||||
self.verticalLayout.setSpacing(0)
|
self.verticalLayout.setSpacing(0)
|
||||||
self.verticalLayout.setObjectName(u"verticalLayout")
|
self.verticalLayout.setObjectName(u"verticalLayout")
|
||||||
@@ -57,9 +61,8 @@ class Ui_graphEditorWidget(object):
|
|||||||
|
|
||||||
|
|
||||||
self.graphToolBar.addAction(self.actionZoomToFit)
|
self.graphToolBar.addAction(self.actionZoomToFit)
|
||||||
self.graphToolBar.addAction(self.actionAddComponent)
|
self.graphToolBar.addAction(self.actionMouseMode)
|
||||||
self.graphToolBar.addAction(self.actionAddConnection)
|
self.graphToolBar.addAction(self.actionConnectionMode)
|
||||||
self.graphToolBar.addAction(self.actionDelete)
|
|
||||||
|
|
||||||
self.retranslateUi(graphEditorWidget)
|
self.retranslateUi(graphEditorWidget)
|
||||||
|
|
||||||
@@ -72,17 +75,13 @@ class Ui_graphEditorWidget(object):
|
|||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.actionZoomToFit.setToolTip(QCoreApplication.translate("graphEditorWidget", u"Zoom canvas to fit", None))
|
self.actionZoomToFit.setToolTip(QCoreApplication.translate("graphEditorWidget", u"Zoom canvas to fit", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.actionAddComponent.setText(QCoreApplication.translate("graphEditorWidget", u"Add Component", None))
|
self.actionMouseMode.setText(QCoreApplication.translate("graphEditorWidget", u"Mouse Mode", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.actionAddComponent.setToolTip(QCoreApplication.translate("graphEditorWidget", u"Add a component", None))
|
self.actionMouseMode.setToolTip(QCoreApplication.translate("graphEditorWidget", u"Mouse Mode", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.actionAddConnection.setText(QCoreApplication.translate("graphEditorWidget", u"Add Connection", None))
|
self.actionConnectionMode.setText(QCoreApplication.translate("graphEditorWidget", u"Connection Mode", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.actionAddConnection.setToolTip(QCoreApplication.translate("graphEditorWidget", u"Add a connection", None))
|
self.actionConnectionMode.setToolTip(QCoreApplication.translate("graphEditorWidget", u"Connection Mode", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.actionDelete.setText(QCoreApplication.translate("graphEditorWidget", u"Delete", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.actionDelete.setToolTip(QCoreApplication.translate("graphEditorWidget", u"Delete selected graph items", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.graphToolBar.setWindowTitle(QCoreApplication.translate("graphEditorWidget", u"Graph tools", None))
|
self.graphToolBar.setWindowTitle(QCoreApplication.translate("graphEditorWidget", u"Graph tools", None))
|
||||||
# retranslateUi
|
# retranslateUi
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
from math import hypot
|
from math import hypot
|
||||||
|
|
||||||
from PySide6.QtCore import QEvent, QObject, QPointF, QRectF, QSize, QTimer, Qt, Signal
|
from PySide6.QtCore import QEvent, QObject, QPointF, QRectF, QSize, QTimer, Qt, Signal
|
||||||
from PySide6.QtGui import QColor, QPainter, QPainterPath, QPen, QWheelEvent
|
from PySide6.QtGui import QColor, QCursor, QPainter, QPainterPath, QPen, QWheelEvent
|
||||||
from PySide6.QtWidgets import QGraphicsItem, QGraphicsPathItem, QGraphicsPixmapItem, QGraphicsScene, QGraphicsSceneMouseEvent, QGraphicsView, QWidget
|
from PySide6.QtWidgets import QGraphicsItem, QGraphicsPathItem, QGraphicsPixmapItem, QGraphicsScene, QGraphicsSceneMouseEvent, QGraphicsView, QWidget
|
||||||
|
|
||||||
from bedit_core.models import BondCausality, BondConnection, Component, ComponentID, ConnectionID, GraphImplementation, SignalConnection
|
from bedit_core.models import BondCausality, BondConnection, Component, ComponentID, ConnectionID, GraphImplementation, SignalConnection
|
||||||
@@ -55,7 +55,6 @@ class GraphConnectionItem(QGraphicsPathItem):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.setPath(self._connection_path(points, half_arrow, tick_at_source))
|
self.setPath(self._connection_path(points, half_arrow, tick_at_source))
|
||||||
pen = QPen(QColor("#202020"), CONNECTION_WIDTH)
|
pen = QPen(QColor("#202020"), CONNECTION_WIDTH)
|
||||||
pen.setCosmetic(True)
|
|
||||||
self.setPen(pen)
|
self.setPen(pen)
|
||||||
self.setZValue(-1)
|
self.setZValue(-1)
|
||||||
|
|
||||||
@@ -137,9 +136,17 @@ class GraphComponentItem(QGraphicsPixmapItem):
|
|||||||
if self.pos() != self._drag_start:
|
if self.pos() != self._drag_start:
|
||||||
self.editor.finish_component_move(self.component_id, position)
|
self.editor.finish_component_move(self.component_id, position)
|
||||||
|
|
||||||
|
def contextMenuEvent(self, event) -> None:
|
||||||
|
if not self.isSelected():
|
||||||
|
self.scene().clearSelection()
|
||||||
|
self.setSelected(True)
|
||||||
|
self.editor.component_context_menu_requested.emit(self.component_id, event.screenPos())
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
|
||||||
class GraphEditorWidget(QWidget):
|
class GraphEditorWidget(QWidget):
|
||||||
component_move_requested = Signal(object, object, object)
|
component_move_requested = Signal(object, object, object)
|
||||||
|
component_context_menu_requested = Signal(object, object)
|
||||||
|
|
||||||
def __init__(self, parent: QWidget | None = None, snap_to_grid_size: int = 4) -> None:
|
def __init__(self, parent: QWidget | None = None, snap_to_grid_size: int = 4) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@@ -298,6 +305,18 @@ class GraphEditorWidget(QWidget):
|
|||||||
def component(self) -> Component | None:
|
def component(self) -> Component | None:
|
||||||
return self._component
|
return self._component
|
||||||
|
|
||||||
|
def selected_component_ids(self) -> list[ComponentID]:
|
||||||
|
return [item.component_id for item in self.scene.selectedItems() if isinstance(item, GraphComponentItem)]
|
||||||
|
|
||||||
|
def paste_position(self) -> tuple[int, int]:
|
||||||
|
viewport = self.ui.graphicsView.viewport()
|
||||||
|
viewport_position = viewport.mapFromGlobal(QCursor.pos())
|
||||||
|
if not viewport.rect().contains(viewport_position):
|
||||||
|
viewport_position = viewport.rect().center()
|
||||||
|
scene_position = self.ui.graphicsView.mapToScene(viewport_position)
|
||||||
|
size = self.snap_to_grid_size
|
||||||
|
return round(scene_position.x() / size) * size, round(scene_position.y() / size) * size
|
||||||
|
|
||||||
def zoom_to_fit(self) -> None:
|
def zoom_to_fit(self) -> None:
|
||||||
bounds = self.scene.itemsBoundingRect()
|
bounds = self.scene.itemsBoundingRect()
|
||||||
if bounds.isEmpty():
|
if bounds.isEmpty():
|
||||||
|
|||||||
Reference in New Issue
Block a user