Newer
Older
"""
B-ASIC Signal Flow Graph Editor Module.
Angus Lothian
committed
This file opens the main SFG editor window of the GUI for B-ASIC when run.
Angus Lothian
committed
"""
import importlib.util
Angus Lothian
committed
import logging
Angus Lothian
committed
import sys
from typing import TYPE_CHECKING, Deque, Dict, List, Optional, Sequence, Tuple, cast
Angus Lothian
committed
from qtpy.QtCore import QCoreApplication, QFileInfo, QSettings, QSize, Qt, QThread, Slot
from qtpy.QtGui import QCursor, QIcon, QKeySequence, QPainter
from qtpy.QtWidgets import (
QAction,
QApplication,
QFileDialog,
QGraphicsScene,
QGraphicsTextItem,
QGraphicsView,
QInputDialog,
QLineEdit,
import b_asic.core_operations
import b_asic.special_operations
from b_asic.GUI._preferences import GAP, GRID, MINBUTTONSIZE, PORTHEIGHT
Angus Lothian
committed
from b_asic.GUI.drag_button import DragButton
from b_asic.GUI.gui_interface import Ui_main_window
from b_asic.GUI.port_button import PortButton
from b_asic.GUI.precedence_graph_window import PrecedenceGraphWindow
from b_asic.GUI.select_sfg_window import SelectSFGWindow
from b_asic.GUI.simulate_sfg_window import SimulateSFGWindow
from b_asic.GUI.simulation_worker import SimulationWorker
from b_asic.GUI.util_dialogs import FaqWindow, KeybindingsWindow
from b_asic.gui_utils.about_window import AboutWindow
from b_asic.gui_utils.decorators import decorate_class, handle_error
from b_asic.gui_utils.plot_window import PlotWindow
from b_asic.port import InputPort, OutputPort
from b_asic.save_load_structure import python_to_sfg, sfg_to_python
from b_asic.simulation import Simulation
Angus Lothian
committed
from b_asic.special_operations import Input, Output
if TYPE_CHECKING:
from qtpy.QtWidgets import QGraphicsProxyWidget
Angus Lothian
committed
logging.basicConfig(level=logging.INFO)
QCoreApplication.setOrganizationName("Linköping University")
QCoreApplication.setOrganizationDomain("liu.se")
QCoreApplication.setApplicationName("B-ASIC SFG GUI")
QCoreApplication.setApplicationVersion(__version__)
Angus Lothian
committed
@decorate_class(handle_error)
class SFGMainWindow(QMainWindow):
Angus Lothian
committed
def __init__(self):
self._logger = logging.getLogger(__name__)
self._window = self
self._ui = Ui_main_window()
self._ui.setupUi(self)
self._scene = QGraphicsScene(self._ui.splitter)
self._operations_from_name: Dict[str, Operation] = {}
self._drag_operation_scenes: Dict[DragButton, "QGraphicsProxyWidget"] = {}
self._drag_buttons: Dict[Operation, DragButton] = {}
self._mouse_pressed = False
self._mouse_dragging = False
self._starting_port = None
self._pressed_operations: List[DragButton] = []
self._arrow_ports: Dict[Arrow, List[Tuple[PortButton, PortButton]]] = {}
self._operation_to_sfg: Dict[DragButton, SFG] = {}
self._pressed_ports: List[PortButton] = []
self._sfg_dict: Dict[str, SFG] = {}
self._plot: Dict[Simulation, PlotWindow] = {}
self._ports: Dict[DragButton, List[PortButton]] = {}
self._graphics_view = QGraphicsView(self._scene, self._ui.splitter)
self._graphics_view.setRenderHint(QPainter.Antialiasing)
self._graphics_view.setGeometry(
self._ui.operation_box.width(), 20, self.width(), self.height()
self._graphics_view.setDragMode(QGraphicsView.RubberBandDrag)
self._toolbar = self.addToolBar("Toolbar")
self._toolbar.addAction(get_icon('open'), "Load SFG", self.load_work)
self._toolbar.addAction(get_icon('save'), "Save SFG", self.save_work)
self._toolbar.addSeparator()
self._toolbar.addAction(
get_icon('new-sfg'), "Create SFG", self.create_sfg_from_toolbar
)
self._toolbar.addAction(
get_icon('close'), "Clear workspace", self._clear_workspace
)
# Create status bar
self._statusbar = QStatusBar(self)
self.setStatusBar(self._statusbar)
self._recent_files_actions: List[QAction] = []
self._recent_files_paths: Deque[str] = deque(maxlen=self._max_recent_files)
Angus Lothian
committed
self.add_operations_from_namespace(
b_asic.core_operations, self._ui.core_operations_list
Angus Lothian
committed
self.add_operations_from_namespace(
b_asic.special_operations, self._ui.special_operations_list
Angus Lothian
committed
self._shortcut_refresh_operations = QShortcut(
QKeySequence("Ctrl+R"), self._ui.operation_box
)
self._shortcut_refresh_operations.activated.connect(
self._scene.selectionChanged.connect(self._select_operations)
Angus Lothian
committed
self.move_button_index = 0
self._ui.actionShowPC.triggered.connect(self._show_precedence_graph)
self._ui.actionSimulateSFG.triggered.connect(self.simulate_sfg)
self._ui.faqBASIC.triggered.connect(self.display_faq_page)
self._ui.faqBASIC.setShortcut(QKeySequence("Ctrl+?"))
self._ui.aboutBASIC.triggered.connect(self.display_about_page)
self._ui.keybindsBASIC.triggered.connect(self.display_keybindings_page)
self._ui.documentationBASIC.triggered.connect(self._open_documentation)
self._ui.core_operations_list.itemClicked.connect(
self._on_list_widget_item_clicked
self._ui.special_operations_list.itemClicked.connect(
self._on_list_widget_item_clicked
self._ui.custom_operations_list.itemClicked.connect(
self._on_list_widget_item_clicked
self._ui.save_menu.triggered.connect(self.save_work)
self._ui.save_menu.setShortcut(QKeySequence("Ctrl+S"))
self._ui.load_menu.triggered.connect(self.load_work)
self._ui.load_menu.setShortcut(QKeySequence("Ctrl+O"))
self._ui.load_operations.triggered.connect(self.add_namespace)
self._ui.load_operations.setIcon(get_icon('add-operations'))
self._ui.exit_menu.triggered.connect(self.exit_app)
self._ui.select_all.triggered.connect(self._select_all)
self._ui.select_all.setShortcut(QKeySequence("Ctrl+A"))
self._ui.unselect_all.triggered.connect(self._unselect_all)
self._shortcut_signal = QShortcut(QKeySequence(Qt.Key_Space), self)
self._shortcut_signal.activated.connect(self._connect_callback)
self._create_recent_file_actions_and_menus()
Angus Lothian
committed
# View menu
# Operation names
self._show_names = True
self._check_show_names = QAction("&Operation names")
self._check_show_names.triggered.connect(self.view_operation_names)
self._check_show_names.setCheckable(True)
self._check_show_names.setChecked(True)
self._ui.view_menu.addAction(self._check_show_names)
self._ui.view_menu.addSeparator()
# Toggle toolbar
self._ui.view_menu.addAction(self._toolbar.toggleViewAction())
# Toggle status bar
self._statusbar_visible = QAction("&Status bar")
self._statusbar_visible.setCheckable(True)
self._statusbar_visible.setChecked(True)
self._statusbar_visible.triggered.connect(self._toggle_statusbar)
self._ui.view_menu.addAction(self._statusbar_visible)
# Zoom to fit
self._ui.view_menu.addSeparator()
self._zoom_to_fit_action = QAction(get_icon('zoom-to-fit'), "Zoom to &fit")
self._zoom_to_fit_action.triggered.connect(self._zoom_to_fit)
self._ui.view_menu.addAction(self._zoom_to_fit_action)
self._fullscreen_action = QAction(
get_icon('full-screen'), "Toggle f&ull screen"
)
self._fullscreen_action.setCheckable(True)
self._fullscreen_action.triggered.connect(self._toggle_fullscreen)
self._fullscreen_action.setShortcut(QKeySequence("F11"))
self._ui.view_menu.addAction(self._fullscreen_action)
self._keybindings_page = None
self._about_page = None
self._faq_page = None
self._logger.info("Finished setting up GUI")
self._logger.info(
"For questions please refer to 'Ctrl+?', or visit the 'Help' "
Angus Lothian
committed
ui_width = self._ui.operation_box.width()
self._ui.operation_box.setGeometry(10, 10, ui_width, self.height())
self._graphics_view.setGeometry(
super().resizeEvent(event)
Angus Lothian
committed
if event.modifiers() == Qt.KeyboardModifier.ControlModifier:
old_zoom = self._zoom
self._zoom += event.angleDelta().y() / 2500
self._graphics_view.scale(self._zoom, self._zoom)
self._zoom = old_zoom
Angus Lothian
committed
def view_operation_names(self, event=None) -> None:
self._show_names = self._check_show_names.isChecked()
Angus Lothian
committed
for operation in self._drag_operation_scenes:
operation.label.setOpacity(self._show_names)
operation.show_name = self._show_names
Angus Lothian
committed
if not self.sfg_widget.sfg:
self.update_statusbar("No SFG selected - saving cancelled")
sfg = cast(SFG, self.sfg_widget.sfg)
Angus Lothian
committed
file_dialog = QFileDialog()
file_dialog.setDefaultSuffix(".py")
module, accepted = file_dialog.getSaveFileName()
if not accepted:
return
self._logger.info("Saving SFG to path: " + str(module))
for op_drag, op_scene in self._drag_operation_scenes.items():
int(op_scene.x()),
int(op_scene.y()),
Angus Lothian
committed
try:
with open(module, "w+") as file_obj:
sfg_to_python(sfg, suffix=f"positions = {operation_positions}")
Angus Lothian
committed
except Exception as e:
self._logger.error(
f"Failed to save SFG to path: {module}, with error: {e}."
)
Angus Lothian
committed
return
self._logger.info("Saved SFG to path: " + str(module))
self.update_statusbar("Saved SFG to path: " + str(module))
Angus Lothian
committed
if not self._sfg_dict:
self.update_statusbar("No SFG to save")
return
Angus Lothian
committed
self.sfg_widget = SelectSFGWindow(self)
self.sfg_widget.show()
# Wait for input to dialog.
self.sfg_widget.ok.connect(self._save_work)
Angus Lothian
committed
module, accepted = QFileDialog().getOpenFileName()
if not accepted:
return
self._load_from_file(module)
Angus Lothian
committed
def _load_from_file(self, module) -> None:
self._logger.info("Loading SFG from path: " + str(module))
Angus Lothian
committed
try:
sfg, positions = python_to_sfg(module)
except ImportError as e:
f"Failed to load module: {module} with the following error: {e}."
Angus Lothian
committed
return
while sfg.name in self._sfg_dict:
self._logger.warning(
Angus Lothian
committed
name, accepted = QInputDialog.getText(
self, "Change SFG Name", "Name: ", QLineEdit.Normal
)
Angus Lothian
committed
if not accepted:
return
sfg.name = name
self._load_sfg(sfg, positions)
self._logger.info("Loaded SFG from path: " + str(module))
self.update_statusbar(f"Loaded SFG from {module}")
def _load_sfg(self, sfg: SFG, positions=None) -> None:
if positions is None:
positions = {}
Angus Lothian
committed
for op in sfg.split():
positions[op.graph_id][0:2] if op.graph_id in positions else None,
positions[op.graph_id][-1] if op.graph_id in positions else None,
Angus Lothian
committed
def connect_ports(ports: Sequence[InputPort]):
Angus Lothian
committed
for port in ports:
for signal in port.signals:
for source in self._drag_buttons[
signal.source_operation
].port_list
destinations = [
for destination in self._drag_buttons[
signal.destination.operation
].port_list
Angus Lothian
committed
if sources and destinations:
self._connect_button(sources[0], destinations[0])
Angus Lothian
committed
for pressed_port in self._pressed_ports:
pressed_port.select_port()
Angus Lothian
committed
for op in sfg.split():
connect_ports(op.inputs)
for op in sfg.split():
self._drag_buttons[op].setToolTip(sfg.name)
self._operation_to_sfg[self._drag_buttons[op]] = sfg
Angus Lothian
committed
Angus Lothian
committed
self.update()
def _create_recent_file_actions_and_menus(self):
for i in range(self._max_recent_files):
recent_file_action = QAction(self._ui.recent_sfg)
recent_file_action.setVisible(False)
recent_file_action.triggered.connect(
lambda b=0, x=recent_file_action: self._open_recent_file(x)
self._recent_files_actions.append(recent_file_action)
self._ui.recent_sfg.addAction(recent_file_action)
def _toggle_fullscreen(self, event=None):
"""Callback for toggling full screen mode."""
if self.isFullScreen():
self.showNormal()
self._fullscreen_action.setIcon(get_icon('full-screen'))
else:
self.showFullScreen()
self._fullscreen_action.setIcon(get_icon('full-screen-exit'))
rfp = cast(deque, settings.value("SFG/recentFiles"))
# print(rfp)
if rfp:
dequelen = len(rfp)
if dequelen > 0:
for i in range(dequelen):
action = self._recent_files_actions[i]
action.setText(rfp[i])
action.setData(QFileInfo(rfp[i]))
for i in range(dequelen, self._max_recent_files):
self._recent_files_actions[i].setVisible(False)
def _open_recent_file(self, action):
rfp = cast(deque, settings.value("SFG/recentFiles"))
if module not in rfp:
rfp.append(module)
rfp = deque(maxlen=self._max_recent_files)
self._logger.info("Exiting the application.")
Angus Lothian
committed
QApplication.quit()
def update_statusbar(self, msg: str) -> None:
"""
Write *msg* to the statusbar with temporarily policy.
Parameters
----------
msg : str
The message to write.
"""
self._statusbar.showMessage(msg)
def _clear_workspace(self) -> None:
self._logger.info("Clearing workspace from operations and SFGs.")
self._pressed_operations.clear()
self._pressed_ports.clear()
self._drag_buttons.clear()
self._drag_operation_scenes.clear()
self._arrow_ports.clear()
self._ports.clear()
self._sfg_dict.clear()
self._scene.clear()
self._logger.info("Workspace cleared.")
self.update_statusbar("Workspace cleared.")
Angus Lothian
committed
Angus Lothian
committed
inputs = []
outputs = []
for pressed_op in self._pressed_operations:
if isinstance(pressed_op.operation, Input):
inputs.append(pressed_op.operation)
elif isinstance(pressed_op.operation, Output):
outputs.append(pressed_op.operation)
Angus Lothian
committed
name, accepted = QInputDialog.getText(
Angus Lothian
committed
if not accepted:
return
self._logger.warning("Failed to initialize SFG with empty name.")
Angus Lothian
committed
return
self._logger.info("Creating SFG with name: %s from selected operations." % name)
Angus Lothian
committed
sfg = SFG(inputs=inputs, outputs=outputs, name=name)
self._logger.info("Created SFG with name: %s from selected operations." % name)
self.update_statusbar(f"Created SFG: {name}")
Angus Lothian
committed
def check_equality(signal: Signal, signal_2: Signal) -> bool:
source_operation = cast(Operation, signal.source_operation)
source_operation2 = cast(Operation, signal_2.source_operation)
dest_operation = cast(Operation, signal.destination_operation)
dest_operation2 = cast(Operation, signal_2.destination_operation)
source_operation.type_name() == source_operation2.type_name()
and dest_operation.type_name() == dest_operation2.type_name()
Angus Lothian
committed
return False
hasattr(source_operation, "value")
and hasattr(source_operation2, "value")
and hasattr(dest_operation, "value")
and hasattr(dest_operation2, "value")
source_operation.value == source_operation2.value
and dest_operation.value == dest_operation2.value
Angus Lothian
committed
return False
hasattr(source_operation, "name")
and hasattr(source_operation2, "name")
and hasattr(dest_operation, "name")
and hasattr(dest_operation2, "name")
source_operation.name == source_operation2.name
and dest_operation.name == dest_operation2.name
Angus Lothian
committed
return False
try:
signal_source_index = [
source_operation.outputs.index(port)
for port in source_operation.outputs
signal_2_source_index = [
source_operation2.outputs.index(port)
for port in source_operation2.outputs
Angus Lothian
committed
except ValueError:
return False # Signal output connections not matching
try:
signal_destination_index = [
dest_operation.inputs.index(port)
for port in dest_operation.inputs
signal_2_destination_index = [
dest_operation2.inputs.index(port)
for port in dest_operation2.inputs
Angus Lothian
committed
except ValueError:
return False # Signal input connections not matching
signal_source_index == signal_2_source_index
and signal_destination_index == signal_2_destination_index
Angus Lothian
committed
for _pressed_op in self._pressed_operations:
Angus Lothian
committed
for operation in sfg.operations:
for input_ in operation.inputs:
for signal in input_.signals:
for arrow in self._arrow_ports:
if check_equality(arrow.signal, signal):
arrow.set_source_operation(signal.source_operation)
arrow.set_destination_operation(
signal.destination_operation
Angus Lothian
committed
for output_ in operation.outputs:
for signal in output_.signals:
for arrow in self._arrow_ports:
if check_equality(arrow.signal, signal):
arrow.set_source_operation(signal.source_operation)
arrow.set_destination_operation(
signal.destination_operation
Angus Lothian
committed
for pressed_op in self._pressed_operations:
pressed_op.setToolTip(sfg.name)
self._operation_to_sfg[pressed_op] = sfg
Angus Lothian
committed
Angus Lothian
committed
def _show_precedence_graph(self, event=None) -> None:
"""Callback for showing precedence graph."""
if not self._sfg_dict:
self.update_statusbar("No SFG to show")
return
self._precedence_graph_dialog = PrecedenceGraphWindow(self)
self._precedence_graph_dialog.add_sfg_to_dialog()
self._precedence_graph_dialog.show()
Angus Lothian
committed
"""Callback for toggling the status bar."""
self._statusbar.setVisible(self._statusbar_visible.isChecked())
def get_operations_from_namespace(self, namespace: ModuleType) -> List[str]:
"""
Return a list of all operations defined in a namespace (module).
Parameters
----------
namespace : module
A loaded Python module containing operations.
Returns
-------
list
A list of names of all the operations in the module.
"""
"Fetching operations from namespace: " + str(namespace.__name__)
)
return [
comp
for comp in dir(namespace)
if hasattr(getattr(namespace, comp), "type_name")
]
Angus Lothian
committed
def add_operations_from_namespace(
self, namespace: ModuleType, list_widget: QListWidget
) -> None:
"""
Add operations from namespace (module) to a list widget.
Parameters
----------
namespace : module
A loaded Python module containing operations.
list_widget : QListWidget
The widget to add operations to.
"""
Angus Lothian
committed
for attr_name in self.get_operations_from_namespace(namespace):
attr = getattr(namespace, attr_name)
try:
attr.type_name()
item = QListWidgetItem(attr_name)
Angus Lothian
committed
self._operations_from_name[attr_name] = attr
except NotImplementedError:
pass
self._logger.info("Added operations from namespace: " + str(namespace.__name__))
Angus Lothian
committed
Angus Lothian
committed
module, accepted = QFileDialog().getOpenFileName()
if not accepted:
return
Angus Lothian
committed
Angus Lothian
committed
spec = importlib.util.spec_from_file_location(
Angus Lothian
committed
namespace = importlib.util.module_from_spec(spec)
spec.loader.exec_module(namespace)
self.add_operations_from_namespace(namespace, self._ui.custom_operations_list)
Angus Lothian
committed
def _update(self):
self._scene.update()
self._graphics_view.update()
self,
op: Operation,
position: Optional[Tuple[float, float]] = None,
is_flipped: bool = False,
) -> None:
"""
Angus Lothian
committed
try:
if op in self._drag_buttons:
self._logger.warning("Multiple instances of operation with same name")
attr_button = DragButton(op, True, window=self)
Angus Lothian
committed
if position is None:
attr_button.move(GRID * 3, GRID * 2)
Angus Lothian
committed
else:
attr_button.move(*position)
max_ports = max(op.input_count, op.output_count)
button_height = max(
MINBUTTONSIZE, max_ports * PORTHEIGHT + (max_ports - 1) * GAP
attr_button.setFixedSize(MINBUTTONSIZE, button_height)
attr_button.setStyleSheet(
"background-color: white; border-style: solid;"
self._ports[attr_button] = attr_button.port_list
Angus Lothian
committed
icon_path = os.path.join(
os.path.dirname(__file__),
"operation_icons",
f"{op.type_name().lower()}.png",
)
icon_path = os.path.join(
os.path.dirname(__file__),
"operation_icons",
"custom_operation.png",
)
Angus Lothian
committed
attr_button.setIcon(QIcon(icon_path))
attr_button.setIconSize(QSize(MINBUTTONSIZE, MINBUTTONSIZE))
"QToolTip { background-color: white; color: black }"
Angus Lothian
committed
attr_button.setParent(None)
attr_button_scene = self._scene.addWidget(attr_button)
Angus Lothian
committed
if position is None:
attr_button_scene.moveBy(
int(self._scene.width() / 4), int(self._scene.height() / 4)
attr_button_scene.setFlag(QGraphicsItem.ItemIsSelectable, True)
Angus Lothian
committed
operation_label = QGraphicsTextItem(op.name, attr_button_scene)
if not self._show_names:
Angus Lothian
committed
operation_label.setOpacity(0)
operation_label.setTransformOriginPoint(
Angus Lothian
committed
operation_label.moveBy(10, -20)
attr_button.add_label(operation_label)
if isinstance(is_flipped, bool):
if is_flipped:
attr_button._flip()
self._drag_buttons[op] = attr_button
self._drag_operation_scenes[attr_button] = attr_button_scene
Angus Lothian
committed
except Exception as e:
"Unexpected error occurred while creating operation: " + str(e)
Angus Lothian
committed
self._logger.info("Creating operation of type: %s" % str(item.text()))
Angus Lothian
committed
try:
attr_operation = self._operations_from_name[item.text()]()
self.update_statusbar(f"{item.text()} added.")
Angus Lothian
committed
except Exception as e:
"Unexpected error occurred while creating operation: " + str(e)
Angus Lothian
committed
def _refresh_operations_list_from_namespace(self) -> None:
self._logger.info("Refreshing operation list.")
self._ui.core_operations_list.clear()
self._ui.special_operations_list.clear()
Angus Lothian
committed
self.add_operations_from_namespace(
b_asic.core_operations, self._ui.core_operations_list
Angus Lothian
committed
self.add_operations_from_namespace(
b_asic.special_operations, self._ui.special_operations_list
self._logger.info("Finished refreshing operation list.")
Angus Lothian
committed
def _on_list_widget_item_clicked(self, item) -> None:
Angus Lothian
committed
self._create_operation_item(item)
for pressed_op in self._pressed_operations:
Angus Lothian
committed
pressed_op.remove()
self.move_button_index -= 1
Angus Lothian
committed
super().keyPressEvent(event)
"""Callback for connecting operation buttons."""
if len(self._pressed_ports) < 2:
self._logger.warning(
"Cannot connect less than two ports. Please select at least two."
Angus Lothian
committed
return
pressed_op_inports = [
pressed
if isinstance(pressed.port, InputPort)
]
pressed_op_outports = [
pressed
if isinstance(pressed.port, OutputPort)
]
Angus Lothian
committed
if len(pressed_op_outports) != 1:
raise ValueError("Exactly one output port must be selected!")
Angus Lothian
committed
pressed_op_outport = pressed_op_outports[0]
for pressed_op_input_port in pressed_op_inports:
self._connect_button(pressed_op_outport, pressed_op_input_port)
Angus Lothian
committed
Angus Lothian
committed
port.select_port()
def _connect_button(self, source: PortButton, destination: PortButton) -> None:
"""
Connect two PortButtons with an Arrow.
Parameters
----------
source : PortButton
The PortButton to start the signal at.
destination : PortButton
The PortButton to end the signal at.
Returns
-------
None.
"""
Angus Lothian
committed
signal_exists = (
signal
for signal in source.port.signals
if signal.destination is destination.port
)
"Connecting: %s -> %s."
% (
source.operation.type_name(),
destination.operation.type_name(),
Angus Lothian
committed
try:
arrow = Arrow(source, destination, self, signal=next(signal_exists))
Angus Lothian
committed
except StopIteration:
arrow = Arrow(source, destination, self)
Angus Lothian
committed
if arrow not in self._arrow_ports:
self._arrow_ports[arrow] = []
Angus Lothian
committed
self._arrow_ports[arrow].append((source, destination))
Angus Lothian
committed
self.update()
for arrow in self._arrow_ports:
arrow.update_arrow()
Angus Lothian
committed
selected = [button.widget() for button in self._scene.selectedItems()]
Angus Lothian
committed
for button in selected:
button._toggle_button(pressed=False)
for button in self._pressed_operations:
Angus Lothian
committed
if button not in selected:
button._toggle_button(pressed=True)
Angus Lothian
committed
"""Callback for selecting all operation buttons."""
if not self._drag_buttons:
self.update_statusbar("No operations to select")
return
for operation in self._drag_buttons.values():
operation._toggle_button(pressed=False)
self.update_statusbar("Selected all operations")
def _unselect_all(self, event=None) -> None:
"""Callback for unselecting all operation buttons."""
if not self._drag_buttons:
self.update_statusbar("No operations to unselect")
return
for operation in self._drag_buttons.values():
operation._toggle_button(pressed=True)
self.update_statusbar("Unselected all operations")
def _zoom_to_fit(self, event=None):
"""Callback for zoom to fit SFGs in window."""
self._graphics_view.fitInView(
self._scene.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio
)
"""Callback for simulating SFGs in separate threads."""
self._thread = dict()
self._sim_worker = dict()
for sfg, properties in self._simulation_dialog._properties.items():
self._logger.info("Simulating SFG with name: %s" % str(sfg.name))
self._sim_worker[sfg] = SimulationWorker(sfg, properties)
self._thread[sfg] = QThread()
self._sim_worker[sfg].moveToThread(self._thread[sfg])
self._thread[sfg].started.connect(self._sim_worker[sfg].start_simulation)
self._sim_worker[sfg].finished.connect(self._thread[sfg].quit)
self._sim_worker[sfg].finished.connect(self._show_plot_window)
self._sim_worker[sfg].finished.connect(self._sim_worker[sfg].deleteLater)
self._thread[sfg].finished.connect(self._thread[sfg].deleteLater)
self._thread[sfg].start()
def _show_plot_window(self, sim: Simulation):
"""Callback for displaying simulation results window."""
self._plot[sim] = PlotWindow(sim.results, sfg_name=sim.sfg.name)
Angus Lothian
committed
def simulate_sfg(self, event=None) -> None:
"""Callback for showing simulation dialog."""
if not self._sfg_dict:
self.update_statusbar("No SFG to simulate")
return
self._simulation_dialog = SimulateSFGWindow(self)
Angus Lothian
committed
for _, sfg in self._sfg_dict.items():
self._simulation_dialog.add_sfg_to_dialog(sfg)
Angus Lothian
committed
Angus Lothian
committed
# Wait for input to dialog.
# Kinda buggy because of the separate window in the same thread.
self._simulation_dialog.simulate.connect(self._simulate_sfg)
Angus Lothian
committed
@Slot()
def _open_documentation(self, event=None) -> None:
"""Callback to open documentation web page."""
webbrowser.open_new_tab("https://da.gitlab-pages.liu.se/B-ASIC/")
def display_faq_page(self, event=None) -> None:
"""Callback for displaying FAQ dialog."""
if self._faq_page is None:
self._faq_page = FaqWindow(self)
Angus Lothian
committed
def display_about_page(self, event=None) -> None:
"""Callback for displaying about dialog."""
if self._about_page is None:
self._about_page = AboutWindow(self)
Angus Lothian
committed
def display_keybindings_page(self, event=None) -> None:
"""Callback for displaying keybindings dialog."""
if self._keybindings_page is None:
self._keybindings_page = KeybindingsWindow(self)
self._keybindings_page.show()
Angus Lothian
committed
def start_editor(sfg: Optional[SFG] = None) -> Dict[str, SFG]:
"""
Start the SFG editor.
Parameters
----------
sfg : SFG, optional
The SFG to start the editor with.
Returns
-------
dict
All SFGs currently in the editor.
"""
if not QApplication.instance():
app = QApplication(sys.argv)
else:
app = QApplication.instance()
window = SFGMainWindow()
if sfg:
window._load_sfg(sfg)
Angus Lothian
committed
window.show()
app.exec_()
return window._sfg_dict
Angus Lothian
committed
if __name__ == "__main__":