diff --git a/src/simudator/core/modules/memory.py b/src/simudator/core/modules/memory.py index 017c2de2f4084dd3f52b73e757b2b915aa31f0ec..090f0e3aef59b242a4906050e58f87d229bf4475 100644 --- a/src/simudator/core/modules/memory.py +++ b/src/simudator/core/modules/memory.py @@ -65,9 +65,12 @@ class Memory(Module): def get_output_signal(self) -> Signal: return self.output_s - def get_adress_siganl(self) -> Signal: + def get_adress_signal(self) -> Signal: return self.adress_s + def get_control_signal(self) -> Signal: + return self.control_s + def print_module(self) -> None: print("", self.name, "\n -----") for adress, value in enumerate(self.memory): diff --git a/src/simudator/core/modules/register.py b/src/simudator/core/modules/register.py index 401c8e2310fb9da8484eb7d8d4ccc8a87cdd0550..4a3441b1aaaf58e4b44ee869cdab7727b2d590cc 100644 --- a/src/simudator/core/modules/register.py +++ b/src/simudator/core/modules/register.py @@ -59,6 +59,10 @@ class Register(Module): state["value"] = self.value return state + def get_gui_state(self) -> dict: + state = self.get_state() + return state + def set_state(self, state: dict[str: Any]) -> None: """ Sets the register state to one given in dict. @@ -72,6 +76,12 @@ class Register(Module): """ self.value = 0 + def get_output_signals(self) -> []: + return [self.output_s] + + def get_input_signals(self) -> []: + return [self.input_s] + def save_state_to_file(self, file_path: str) -> None: """ Tries to save the modules state to a given file. diff --git a/src/simudator/gui/dialogs/memory_content_dialog.py b/src/simudator/gui/dialogs/memory_content_dialog.py index e33ed95a4b0b308bcca0a30384005566d5e41a39..a84779edf4d8303a5213c60c9aa2439183bdc904 100644 --- a/src/simudator/gui/dialogs/memory_content_dialog.py +++ b/src/simudator/gui/dialogs/memory_content_dialog.py @@ -1,7 +1,6 @@ from typing import Optional from PyQt5.QtCore import Qt, pyqtSignal -from PyQt5.QtGui import QValidator from PyQt5.QtWidgets import ( QComboBox, QDialog, @@ -14,39 +13,6 @@ from PyQt5.QtWidgets import ( from simudator.core import Module -class HexValidator(QValidator): - """ - A QValidator for constricting user input to a hex number that - lies within a specified (exclusive) range. - """ - def __init__(self, bottom: int, top: int) -> None: - super().__init__(None) - self.bottom = bottom - self.top = top - - def validate(self, a0: str, a1: int) -> tuple['QValidator.State', str, int]: - # A input string representing a hex number must begin with '0x' - # The user must also be able to type '0x' if the input was empty before - if a0[0:2] not in "0x": - return (QValidator.Invalid, a0, a1) - if not a0[2:]: - return (QValidator.Intermediate, a0, a1) - - # The input string is converted to an int to check if it is a hex - # number and to use later to check that it is within bounds - try: - parsed_int = int(a0, 16) - except ValueError: - return (QValidator.Invalid, a0, a1) - - # The input string converted to a hex number must be withing the - # specified range of this validor to be accepted - else: - if self.bottom <= parsed_int < self.top: - return (QValidator.Acceptable, a0, a1) - return (QValidator.Invalid, a0, a1) - - class MemoryContentDialog(QDialog): accepted = pyqtSignal(str, str, str, name="accepted") @@ -60,14 +26,12 @@ class MemoryContentDialog(QDialog): self.memory = memory self.memory_name =memory.get_state()['name'] - adresses = [hex(i) for i in range(len(memory.get_state()['memory']))] - adressValidator = HexValidator(0, len(adresses)) + adresses = [str(i) for i in range(len(memory.get_state()['memory']))] self.adressSelectWidget = QComboBox() self.adressSelectWidget.addItems(adresses) self.adressSelectWidget.activated.connect(self.updateValue) self.adressSelectWidget.setEditable(True) - self.adressSelectWidget.setValidator(adressValidator) self.valuesWidget = QLineEdit() @@ -90,7 +54,7 @@ class MemoryContentDialog(QDialog): selectedAdress = int(self.adressSelectWidget.currentText(), 16) memory_content = self.memory.get_state()['memory'] value = memory_content[selectedAdress] - self.valuesWidget.setText(hex(value)) + self.valuesWidget.setText(str(value)) def signalAccepted(self) -> None: diff --git a/src/simudator/gui/gui.py b/src/simudator/gui/gui.py index 3024c4eabc525432f61c92a25eb8503cf0d92d36..b274814095e739e96771a8b716a4d3f51c3a28e2 100644 --- a/src/simudator/gui/gui.py +++ b/src/simudator/gui/gui.py @@ -25,8 +25,6 @@ from simudator.gui.breakpoint_window import BreakpointWindow from simudator.gui.cpu_graphics_scene import CpuGraphicsScene from simudator.gui.custom_toolbar import CustomToolBar from simudator.gui.dialogs.lambda_breakpoint_dialog import LambdaBreakpointDialog -from simudator.gui.dialogs.memory_content_dialog import MemoryContentDialog -from simudator.gui.dialogs.module_state_dialog import ModuleStateDialog from simudator.gui.module_graphics_item.module_graphics_item import ModuleGraphicsItem from simudator.gui.orientation import Orientation from simudator.gui.port_graphics_item import PortGraphicsItem @@ -72,10 +70,6 @@ class GUI(QMainWindow): # Using the cpu's internal status directly could case problems self.cpu_running = False - # Create the default actions used in module widgets that - # need to be performed by the GUI main window - self.createModuleActions() - # Set Style, THESE ARE TEST AND DONT WORK app = QApplication.instance() app.setStyleSheet("Qwidget.QMainWindow { background-color: yellow }") @@ -213,40 +207,19 @@ class GUI(QMainWindow): self.clock_label = QLabel("Clockcycle: " + str(self.cpu.get_clock()), self) toolbar.addWidget(self.clock_label) - def createModuleActions(self) -> None: - """ - Creates the popup windows that allows user to add break points - and edit the modules state when the users left click on a - graphic module. - """ - state_br_action = QAction('Add breakpoint', self) - state_br_action.triggered.connect(self.stateBreakpointDialog) - - lambda_br_action = QAction('Add lambda breakpoint', self) - lambda_br_action.triggered.connect(self.lambdaBreakpointDialog) - if not self.cpu.get_breakpoint_lambdas(): - lambda_br_action.setEnabled(False) - - memory_br_action = QAction('Add memory breakpoint', self) - # TODO: Use a better data type to indicate if only a specific - # graphics item subtype should use an action - # (See shouldIgnoreAction() in module_graphics_item.py) - memory_br_action.setData("MEM_ONLY") - memory_br_action.triggered.connect(self.memoryBreakpointDialog) - - edit_state_action = QAction('Edit module state', self) - edit_state_action.triggered.connect(self.editModuleStateDialog) - - edit_memory_action = QAction('Edit memory content', self) - edit_memory_action.setData("MEM_ONLY") - edit_memory_action.triggered.connect(self.editMemoryContentDialog) - - self.moduleActions = {'add_br': state_br_action, - 'add_lambda_br': lambda_br_action, - 'add_mem_br': memory_br_action, - 'edit_state': edit_state_action, - 'edit_memory': edit_memory_action - } + def connectModuleActions(self, action_signals: []) -> None: + for action_id, signal in action_signals: + match action_id: + case "BP": + signal.connect(self.addStateBreakpoint) + case "MEM_BP": + signal.connect(self.addMemoryBreakpoint) + case "LMB_BP": + # LambdaBPs are not tested since we dont + # actually have any modules that make them yet + signal.connect(self.addLambdaBreakpoint) + case "UPDATE": + signal.connect(self.updateCpuListeners) def updateCpuListeners(self) -> None: """ @@ -256,15 +229,6 @@ class GUI(QMainWindow): self.cpu_graphics_scene.updateGraphicsItems() self.clock_label.setText("Clockcycle: " + str(self.cpu.get_clock())) - def stateBreakpointDialog(self) -> None: - """ - Opens dialog window for user to create a breakpoint. - """ - action = self.moduleActions['add_br'] - module_name = action.data() - module = self.cpu.get_module(module_name) - state_br_dialog = ModuleStateDialog(module, self) - state_br_dialog.okSignal.connect(self.addStateBreakpoint) def lambdaBreakpointDialog(self) -> None: """ @@ -274,16 +238,6 @@ class GUI(QMainWindow): lambda_br_dialog = LambdaBreakpointDialog(lambdas, self) lambda_br_dialog.accepted.connect(self.addLambdaBreakpoint) - def memoryBreakpointDialog(self) -> None: - """ - Opens dialog window for user to create a breakpoint. - """ - action = self.moduleActions['add_mem_br'] - module_name = action.data() - module = self.cpu.get_module(module_name) - memory_br_dialog = MemoryContentDialog(module, self) - memory_br_dialog.accepted.connect(self.addMemoryBreakpoint) - def updateBreakpointWindow(self) -> None: """ Updates the breakpoint window when new breakpoints are added. @@ -292,26 +246,6 @@ class GUI(QMainWindow): if self.breakpoint_window is not None: self.breakpoint_window.update() - def editModuleStateDialog(self) -> None: - """ - Opens dialog where user can edit the state of a module. - """ - action = self.moduleActions['edit_state'] - module_name = action.data() - module = self.cpu.get_module(module_name) - state_br_dialog = ModuleStateDialog(module, self) - state_br_dialog.okSignal.connect(self.editModuleState) - - def editMemoryContentDialog(self) -> None: - """ - Opens dialog where user can edit the contents of a memory. - """ - action = self.moduleActions['edit_memory'] - module_name = action.data() - module = self.cpu.get_module(module_name) - memory_br_dialog = MemoryContentDialog(module, self) - memory_br_dialog.accepted.connect(self.editMemoryContent) - """ @pyqtSlot is used to explicitly mark a python method as a Qt slot and specify a C++ signature for it, which is used most commonly @@ -373,16 +307,13 @@ class GUI(QMainWindow): @pyqtSlot(str, str, str) def addMemoryBreakpoint(self, module_name: str, adress: str, value: str) -> None: try: - parsed_adress = int(adress, 16) - parsed_value = ast.literal_eval(value) + parsed_adress = int(adress) + parsed_value = int(value) self.cpu.add_memory_breakpoint(module_name, parsed_adress, parsed_value) self.updateBreakpointWindow() except SyntaxError as e: self.errorMessageWidget.showMessage(str(e)) - except ValueError: - self.errorMessageWidget.showMessage("You must enter a hexadecimal" - "number preceeded by '0x' (e.g." - "0xc3).") + def setDisabledWhenRunning(self, is_disable): """ This function greys out buttons for actions @@ -797,7 +728,7 @@ class GUI(QMainWindow): Adds an item to the graphics scene. """ self.cpu_graphics_scene.addModuleGraphicsItem(graphics_item) - graphics_item.addActions(self.moduleActions.values()) + self.connectModuleActions(graphics_item.getActionSignals()) def addAllSignals(self) -> None: """ diff --git a/src/simudator/gui/module_graphics_item/memory_graphic.py b/src/simudator/gui/module_graphics_item/memory_graphic.py new file mode 100644 index 0000000000000000000000000000000000000000..31c5f64ecc79410be706c3bba308783b67fb900a --- /dev/null +++ b/src/simudator/gui/module_graphics_item/memory_graphic.py @@ -0,0 +1,190 @@ +from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot +from PyQt5.QtWidgets import ( + QAction, + QGraphicsRectItem, + QGraphicsSimpleTextItem, + QTextEdit, + QVBoxLayout, + QWidget, +) + +from simudator.core.modules import Memory +from simudator.gui.dialogs.memory_content_dialog import MemoryContentDialog +from simudator.gui.module_graphics_item.module_graphics_item import ModuleGraphicsItem +from simudator.gui.orientation import Orientation +from simudator.gui.port_graphics_item import PortGraphicsItem + + +class MemoryWindow(QWidget): + """ + Widget for showing content of memory + """ + + ROW_LENGTH = 5 + + def __init__(self, memory_module: Memory): + super().__init__() + self.module = memory_module + + self.text = QTextEdit("") + layout = QVBoxLayout() + layout.addWidget(self.text) + self.setLayout(layout) + + self.update() + + def update(self): + memory_str = "" + for adress, value in enumerate(self.module.memory): + + # Add adress and content to string + # Make sure its unifrom lenght so rows are consistent + memory_str += f"{adress}" + ": " + f"{value}" + " " + "\t" + + # Make new line when we reach end of row + if adress % self.ROW_LENGTH == self.ROW_LENGTH-1: + memory_str += "\n" + + self.text.setText(memory_str) + + +class MemoryGraphicsItem(ModuleGraphicsItem): + """ + Graphics module for a Memory module. + """ + + RECT_WIDTH = 150 + RECT_HEIGHT= 200 + + new_memory_breakpoint_signal = pyqtSignal(str, str, str) + + def __init__(self, memory_module: Memory): + super().__init__(memory_module) + self.memory_window = None + + def draw_graphics_item(self) -> None: + + # Create base backgrond rect + self.baserect = QGraphicsRectItem(0, 0, self.RECT_WIDTH, self.RECT_HEIGHT, self) + + # Make ports + self.input = PortGraphicsItem(self.module.get_input_signal(), + Orientation.RIGHT, self) + self.input.setPos(self.RECT_WIDTH, self.RECT_HEIGHT/8) + + self.output = PortGraphicsItem(self.module.get_output_signal(), + Orientation.RIGHT, self) + self.output.setPos(self.RECT_WIDTH, self.RECT_HEIGHT/4) + + self.adress = PortGraphicsItem(self.module.get_adress_signal(), + Orientation.DOWN, self) + self.adress.setPos(self.RECT_WIDTH/4, self.RECT_HEIGHT) + + self.control = PortGraphicsItem(self.module.get_control_signal(), + Orientation.DOWN, self) + self.control.setPos(self.RECT_WIDTH*3/4, self.RECT_HEIGHT) + + self.ports = [self.input, self.output, self.adress, self.control] + + # Create name lable + name_text = QGraphicsSimpleTextItem(self.state["name"], self) + name_text.setPos(self.RECT_WIDTH/2 + - len(self.state["name"])*self.CHAR_LEN, 0) + + # Create adress lable + self.adress_text = QGraphicsSimpleTextItem("adress: " + + str(self.state["current_adress"]), self) + self.adress_text.setPos(self.RECT_WIDTH/20, self.RECT_HEIGHT/8) + + def update(self): + """ + Also update memory window here. + """ + self.state = self.module.get_state() + self.adress_text.setText("adress: " + str(self.state["current_adress"])) + if self.memory_window is not None: + self.memory_window.update() + + + def mousePressEvent(self, event): + """ + Show content window if module is right clicked + """ + return + # TODO: comment =/= what fuction does + if event.button() == Qt.MouseButton.RightButton: + self.memory_window = MemoryWindow(self.module) + self.memory_window.show() + super().mousePressEvent(event) + + def showMemoryContents(self) -> None: + """ + Create and show a MemoryWindow that displays the contents + of the memory module associated with this graphics item. + """ + self.memory_window = MemoryWindow(self.module) + self.memory_window.show() + + def shouldIgnoreAction(self, action: QAction) -> bool: + """ + Returns True if the given action should not be used in the context menu + of this graphics item. + """ + if action.data() == "MEM_ONLY": + return False + return super().shouldIgnoreAction(action) + + def generateActions(self) -> []: + super().generateActions() + + memory_br_action = QAction('Add memory breakpoint', self) + memory_br_action.triggered.connect(self.memoryBreakpointDialog) + self.actions.append(memory_br_action) + + edit_memory_action = QAction('Edit memory content', self) + edit_memory_action.triggered.connect(self.editMemoryContentDialog) + self.actions.append(edit_memory_action) + + show_content_action = QAction('Show memory content', self) + show_content_action.triggered.connect(self.showMemoryContents) + self.actions.append(show_content_action) + + def memoryBreakpointDialog(self) -> None: + """ + Opens dialog window for user to create a breakpoint. + """ + self.memory_br_dialog = MemoryContentDialog(self.module) + self.memory_br_dialog.accepted.connect(self.memoryBreakpointAccepted) + + def editMemoryContentDialog(self) -> None: + """ + Opens dialog window for user to edit memory. + """ + self.memory_edit_dialog= MemoryContentDialog(self.module) + self.memory_edit_dialog.accepted.connect(self.editMemoryAccepted) + + @pyqtSlot(str, str, str) + def memoryBreakpointAccepted(self, module_name: str, + adress: str, + value: str) -> None: + try: + self.new_memory_breakpoint_signal.emit(module_name, adress, value) + except SyntaxError as e: + self.errorMessageWidget.showMessage(str(e)) + + @pyqtSlot(str, str, str) + def editMemoryAccepted(self, module_name: str, adress: str, value: str) -> None: + try: + parsed_adress = int(adress) + except SyntaxError as e: + self.errorMessageWidget.showMessage(str(e)) + else: + module_state = self.module.get_state() + module_state['memory'][parsed_adress] = value + self.module.set_state(module_state) + self.update_graphics_signal.emit() + + def getActionSignals(self) -> []: + signals = super().getActionSignals() + signals.append(("MEM_BP", self.new_memory_breakpoint_signal)) + return signals diff --git a/src/simudator/gui/module_graphics_item/mia/__init__.py b/src/simudator/gui/module_graphics_item/mia/__init__.py index bc78898684ff12f5378b199280b8750a9ff7d94e..7764a5bb844d3010072865d202bc174f5dbcbe14 100644 --- a/src/simudator/gui/module_graphics_item/mia/__init__.py +++ b/src/simudator/gui/module_graphics_item/mia/__init__.py @@ -6,20 +6,22 @@ from simudator.gui.module_graphics_item.mia.mia_alu_graphic import AluGraphicsIt from simudator.gui.module_graphics_item.mia.mia_flag_graphic import FlagGraphicsItem from simudator.gui.module_graphics_item.mia.mia_grx_graphic import GrxGraphicsItem from simudator.gui.module_graphics_item.mia.mia_ir_graphic import IrGraphicsItem -from simudator.gui.module_graphics_item.mia.mia_memory_graphic import MemoryGraphicsItem +from simudator.gui.module_graphics_item.mia.mia_memory_graphic import ( + MiaMemoryGraphicsItem, +) from simudator.gui.module_graphics_item.mia.mia_micro_memory_graphic import ( MicroMemoryGraphicsItem, ) from simudator.gui.module_graphics_item.mia.mia_register_graphic import ( - RegisterGraphicsItem, + MiaRegisterGraphicsItem, ) from simudator.gui.module_graphics_item.mia.pc_graphic import PcGraphicsItem from simudator.gui.module_graphics_item.mia.supc_graphic import SupcGraphicsItem from simudator.gui.module_graphics_item.mia.upc_graphic import uPcGraphicsItem -__all__ = ["MemoryGraphicsItem", "MicroMemoryGraphicsItem", "ArGraphicsItem", +__all__ = ["MiaMemoryGraphicsItem", "MicroMemoryGraphicsItem", "ArGraphicsItem", "PcGraphicsItem", "AsrGraphicsItem", "HrGraphicsItem", "SupcGraphicsItem", "uPcGraphicsItem", "FlagGraphicsItem", "BusGraphicsItem", "AluGraphicsItem", "GrxGraphicsItem", "IrGraphicsItem", - "RegisterGraphicsItem" + "MiaRegisterGraphicsItem" ] diff --git a/src/simudator/gui/module_graphics_item/mia/mia_memory_content_dialog.py b/src/simudator/gui/module_graphics_item/mia/mia_memory_content_dialog.py new file mode 100644 index 0000000000000000000000000000000000000000..ab3d214a53f439cbd0e60d39e91185a59383ae85 --- /dev/null +++ b/src/simudator/gui/module_graphics_item/mia/mia_memory_content_dialog.py @@ -0,0 +1,100 @@ +from typing import Optional + +from PyQt5.QtCore import Qt, pyqtSignal +from PyQt5.QtGui import QValidator +from PyQt5.QtWidgets import ( + QComboBox, + QDialog, + QHBoxLayout, + QLineEdit, + QPushButton, + QWidget, +) + +from simudator.core import Module + + +class HexValidator(QValidator): + """ + A QValidator for constricting user input to a hex number that + lies within a specified (exclusive) range. + """ + def __init__(self, bottom: int, top: int) -> None: + super().__init__(None) + self.bottom = bottom + self.top = top + + def validate(self, a0: str, a1: int) -> tuple['QValidator.State', str, int]: + # A input string representing a hex number must begin with '0x' + # The user must also be able to type '0x' if the input was empty before + if a0[0:2] not in "0x": + return (QValidator.Invalid, a0, a1) + if not a0[2:]: + return (QValidator.Intermediate, a0, a1) + + # The input string is converted to an int to check if it is a hex + # number and to use later to check that it is within bounds + try: + parsed_int = int(a0, 16) + except ValueError: + return (QValidator.Invalid, a0, a1) + + # The input string converted to a hex number must be withing the + # specified range of this validor to be accepted + else: + if self.bottom <= parsed_int < self.top: + return (QValidator.Acceptable, a0, a1) + return (QValidator.Invalid, a0, a1) + + +class MiaMemoryContentDialog(QDialog): + accepted = pyqtSignal(str, str, str, name="accepted") + + def __init__(self, + memory: Module, + parent: Optional['QWidget'] = None, + flags: Qt.WindowFlags | Qt.WindowType = Qt.WindowFlags() + ) -> None: + super().__init__(parent, flags) + + self.memory = memory + self.memory_name =memory.get_state()['name'] + + adresses = [hex(i) for i in range(len(memory.get_state()['memory']))] + adressValidator = HexValidator(0, len(adresses)) + + self.adressSelectWidget = QComboBox() + self.adressSelectWidget.addItems(adresses) + self.adressSelectWidget.activated.connect(self.updateValue) + self.adressSelectWidget.setEditable(True) + self.adressSelectWidget.setValidator(adressValidator) + + self.valuesWidget = QLineEdit() + + okButton = QPushButton('OK') + okButton.clicked.connect(self.signalAccepted) + cancelButton = QPushButton('Cancel') + cancelButton.clicked.connect(self.close) + + hboxLayout = QHBoxLayout() + hboxLayout.addWidget(self.adressSelectWidget, 0) + hboxLayout.addWidget(self.valuesWidget, 1) + hboxLayout.addWidget(okButton) + hboxLayout.addWidget(cancelButton) + self.setLayout(hboxLayout) + + self.show() + self.updateValue() + + def updateValue(self) -> None: + selectedAdress = int(self.adressSelectWidget.currentText(), 16) + memory_content = self.memory.get_state()['memory'] + value = memory_content[selectedAdress] + self.valuesWidget.setText(hex(value)) + + + def signalAccepted(self) -> None: + adress = self.adressSelectWidget.currentText() + enteredValues = self.valuesWidget.text() + self.accepted.emit(self.memory_name, adress, enteredValues) + self.close() diff --git a/src/simudator/gui/module_graphics_item/mia/mia_memory_graphic.py b/src/simudator/gui/module_graphics_item/mia/mia_memory_graphic.py index a5cf3fcfb5de54d0c95a99130decc8d6bdfa53d5..1eb208e5ae67afbfbbccf97b07504be19e9e6cd2 100644 --- a/src/simudator/gui/module_graphics_item/mia/mia_memory_graphic.py +++ b/src/simudator/gui/module_graphics_item/mia/mia_memory_graphic.py @@ -1,21 +1,24 @@ -from PyQt5.QtCore import Qt +import ast + +from PyQt5.QtCore import pyqtSlot from PyQt5.QtWidgets import ( - QAction, QGraphicsRectItem, QGraphicsSimpleTextItem, - QMenu, QTextEdit, QVBoxLayout, QWidget, ) from simudator.core.modules import Memory -from simudator.gui.module_graphics_item.module_graphics_item import ModuleGraphicsItem +from simudator.gui.module_graphics_item.memory_graphic import MemoryGraphicsItem +from simudator.gui.module_graphics_item.mia.mia_memory_content_dialog import ( + MiaMemoryContentDialog, +) from simudator.gui.orientation import Orientation from simudator.gui.port_graphics_item import PortGraphicsItem -class MemoryWindow(QWidget): +class MiaMemoryWindow(QWidget): """ Widget for showing content of memory """ @@ -48,7 +51,7 @@ class MemoryWindow(QWidget): self.text.setText(memory_str) -class MemoryGraphicsItem(ModuleGraphicsItem): +class MiaMemoryGraphicsItem(MemoryGraphicsItem): """ Graphics module for a Memory module. """ @@ -61,6 +64,7 @@ class MemoryGraphicsItem(ModuleGraphicsItem): self.memory_window = None def draw_graphics_item(self) -> None: + # Same as normal memory but no control signal # Create base backgrond rect self.baserect = QGraphicsRectItem(0, 0, self.RECT_WIDTH, self.RECT_HEIGHT, self) @@ -74,7 +78,7 @@ class MemoryGraphicsItem(ModuleGraphicsItem): Orientation.RIGHT, self) self.output.setPos(self.RECT_WIDTH, self.RECT_HEIGHT/4) - self.adress = PortGraphicsItem(self.module.get_adress_siganl(), + self.adress = PortGraphicsItem(self.module.get_adress_signal(), Orientation.DOWN, self) self.adress.setPos(self.RECT_WIDTH/4, self.RECT_HEIGHT) @@ -90,51 +94,51 @@ class MemoryGraphicsItem(ModuleGraphicsItem): + str(self.state["current_adress"]), self) self.adress_text.setPos(self.RECT_WIDTH/20, self.RECT_HEIGHT/8) - def update(self): - """ - Also update memory window here. - """ - self.state = self.module.get_state() - self.adress_text.setText("adress: " + str(self.state["current_adress"])) - if self.memory_window is not None: - self.memory_window.update() - - - def mousePressEvent(self, event): - """ - Show content window if module is right clicked - """ - return - # TODO: comment =/= what fuction does - if event.button() == Qt.MouseButton.RightButton: - self.memory_window = MemoryWindow(self.module) - self.memory_window.show() - super().mousePressEvent(event) - - def getContextMenu(self) -> QMenu: - """ - Returns a QMenu instance of the context menu used when right - clicking this memory graphics item. - """ - menu = super().getContextMenu() - showContentsAction = QAction('Show contents', menu) - showContentsAction.triggered.connect(self.showMemoryContents) - menu.addAction(showContentsAction) - return menu - def showMemoryContents(self) -> None: """ Create and show a MemoryWindow that displays the contents of the memory module associated with this graphics item. """ - self.memory_window = MemoryWindow(self.module) + self.memory_window = MiaMemoryWindow(self.module) self.memory_window.show() - def shouldIgnoreAction(self, action: QAction) -> bool: + def memoryBreakpointDialog(self) -> None: """ - Returns True if the given action should not be used in the context menu - of this graphics item. + Opens dialog window for user to create a breakpoint. """ - if action.data() == "MEM_ONLY": - return False - return super().shouldIgnoreAction(action) + self.memory_br_dialog = MiaMemoryContentDialog(self.module) + self.memory_br_dialog.accepted.connect(self.memoryBreakpointAccepted) + + @pyqtSlot(str, str, str) + def memoryBreakpointAccepted(self, module_name: str, + adress: str, + value: str) -> None: + try: + parsed_adress = int(adress, 16) + parsed_value = ast.literal_eval(value) + self.new_memory_breakpoint_signal.emit(module_name, + str(parsed_adress), + str(parsed_value)) + except SyntaxError as e: + self.errorMessageWidget.showMessage(str(e)) + except ValueError: + self.errorMessageWidget.showMessage("You must enter a hexadecimal" + "number preceeded by '0x' (e.g." + "0xc3).") + + @pyqtSlot(str, str, str) + def editMemoryAccepted(self, module_name: str, adress: str, value: str) -> None: + try: + parsed_adress = int(adress, 16) + parsed_value = ast.literal_eval(value) + except SyntaxError as e: + self.errorMessageWidget.showMessage(str(e)) + except ValueError: + self.errorMessageWidget.showMessage("You must enter a hexadecimal" + "number preceeded by '0x' (e.g." + "0xc3).") + else: + module_state = self.module.get_state() + module_state['memory'][parsed_adress] = parsed_value + self.module.set_state(module_state) + self.update_graphics_signal.emit() diff --git a/src/simudator/gui/module_graphics_item/mia/mia_micro_memory_graphic.py b/src/simudator/gui/module_graphics_item/mia/mia_micro_memory_graphic.py index 259eff544f29614ec0e93231be56a298d376db80..1fa1d16467ae5acb83402d7a1aca58539a100434 100644 --- a/src/simudator/gui/module_graphics_item/mia/mia_micro_memory_graphic.py +++ b/src/simudator/gui/module_graphics_item/mia/mia_micro_memory_graphic.py @@ -1,12 +1,14 @@ from PyQt5.QtWidgets import QGraphicsRectItem, QGraphicsSimpleTextItem -from simudator.gui.module_graphics_item.mia.mia_memory_graphic import MemoryGraphicsItem +from simudator.gui.module_graphics_item.mia.mia_memory_graphic import ( + MiaMemoryGraphicsItem, +) from simudator.gui.orientation import Orientation from simudator.gui.port_graphics_item import PortGraphicsItem from simudator.processor.mia.micro_memory import MicroMemory -class MicroMemoryGraphicsItem(MemoryGraphicsItem): +class MicroMemoryGraphicsItem(MiaMemoryGraphicsItem): """ Graphics module for mia's micro memory module. """ diff --git a/src/simudator/gui/module_graphics_item/mia/mia_register_graphic.py b/src/simudator/gui/module_graphics_item/mia/mia_register_graphic.py index 92f0254e8902a250fec9828f3063595322277e9b..dc0f7db66bf60e0c5562b400c7bafbbceed0cad6 100644 --- a/src/simudator/gui/module_graphics_item/mia/mia_register_graphic.py +++ b/src/simudator/gui/module_graphics_item/mia/mia_register_graphic.py @@ -1,42 +1,13 @@ import math -from PyQt5.QtWidgets import QGraphicsRectItem, QGraphicsSimpleTextItem +from simudator.gui.module_graphics_item.register_graphic import RegisterGraphicsItem -from simudator.gui.module_graphics_item.module_graphics_item import ModuleGraphicsItem -from simudator.processor.mia.mia_register import MiaRegister - -class RegisterGraphicsItem(ModuleGraphicsItem): +class MiaRegisterGraphicsItem(RegisterGraphicsItem): """ A general garphics item for mia's register modules. Other mia register modules inherit this to get the same shape. """ - RECT_HEIGHT = 40 - RECT_WIDTH_PER_CHAR = 8 - RECT_HEIGHT_PER_CHAR = 16 - TEXT_HEIGHT_MARGIN = 10 - TEXT_WIDTH_MARGIN = 5 - - def __init__(self, module: MiaRegister): - super().__init__(module) - self.draw_rect() - - def draw_graphics_item(self) -> None: - self.draw_rect() - self.draw_ports() - - def draw_rect(self): - """ - Draw the register rect basd on contents of the register - """ - self.text = QGraphicsSimpleTextItem("", self) - self.text.setPos(self.TEXT_WIDTH_MARGIN, self.TEXT_HEIGHT_MARGIN) - - # Update here to fill text box, we can then use text box for lenght - self.update() - self.baserect = QGraphicsRectItem(0, 0, - len(self.text.text())*self.RECT_WIDTH_PER_CHAR + self.TEXT_WIDTH_MARGIN, - self.RECT_HEIGHT, self) def update(self): self.state = self.module.get_state() diff --git a/src/simudator/gui/module_graphics_item/module_graphics_item.py b/src/simudator/gui/module_graphics_item/module_graphics_item.py index 1ed7a58d804d2908f9510d805571eff7127df449..aea45a4e389928bfd61360194f81a06d190b53b7 100644 --- a/src/simudator/gui/module_graphics_item/module_graphics_item.py +++ b/src/simudator/gui/module_graphics_item/module_graphics_item.py @@ -1,6 +1,7 @@ -from collections.abc import Iterable +import ast from PyQt5 import QtCore +from PyQt5.QtCore import pyqtSignal, pyqtSlot from PyQt5.QtGui import QCursor from PyQt5.QtWidgets import ( QAction, @@ -14,7 +15,7 @@ from PyQt5.QtWidgets import ( ) from simudator.core.module import Module -from simudator.gui.gui import pyqtSignal +from simudator.gui.dialogs.module_state_dialog import ModuleStateDialog from simudator.gui.orientation import Orientation from simudator.gui.port_graphics_item import PortGraphicsItem @@ -32,6 +33,8 @@ class ModuleGraphicsItem(QGraphicsObject, QGraphicsItem): CHAR_HEIGHT = 8 WIDTH_MARGIN = 12 moved = pyqtSignal(name="moved") + new_state_breakpoint_signal = pyqtSignal(str, str, str) + update_graphics_signal = pyqtSignal() def __init__(self, module: Module, name: str = None): super().__init__() @@ -54,13 +57,14 @@ class ModuleGraphicsItem(QGraphicsObject, QGraphicsItem): # Have list of ports to reference for parent self.ports = [] + # start by setting ui lock of + self.isLocked = False + # Need this so ports always have something as a bounding rect self.baserect = QGraphicsRectItem(0, 0, 0, 0, self) self.actions = [] - - self.isLocked = False - print(self.module.get_gui_state()) + self.generateActions() # Do general draw self.draw_graphics_item() @@ -129,15 +133,17 @@ class ModuleGraphicsItem(QGraphicsObject, QGraphicsItem): height = self.baserect.rect().height() for signal_tuple in enumerate(self.input_signals): port = PortGraphicsItem(signal_tuple[1], Orientation.LEFT, self) - port.setPos(0, (signal_tuple[0])*(height/len(self.input_signals))) + height_spacing = height/(len(self.input_signals)) + port.setPos(0, (signal_tuple[0] + 0.5)*height_spacing) self.ports.append(port) # Add ports for output signals for signal_tuple in enumerate(self.output_signals): port = PortGraphicsItem(signal_tuple[1], parent=self) + height_spacing = height/(len(self.output_signals)) port.setPos( - width, (signal_tuple[0])*(height/len(self.output_signals))) + width, (signal_tuple[0] + 0.5)*height_spacing) self.ports.append(port) def showPorts(self) -> None: @@ -147,6 +153,32 @@ class ModuleGraphicsItem(QGraphicsObject, QGraphicsItem): for port in self.ports: port.setVisible(True) + def stateBreakpointDialog(self) -> None: + """ + Opens dialog window for user to create a breakpoint. + """ + self.state_br_dialog = ModuleStateDialog(self.module) + self.state_br_dialog.okSignal.connect(self.stateBreakpointAccepted) + + def editModuleStateDialog(self) -> None: + """ + Opens dialog where user can edit the state of a module. + """ + self.state_br_dialog = ModuleStateDialog(self.module) + self.state_br_dialog.okSignal.connect(self.editModuleState) + + @pyqtSlot(str, str, str) + def editModuleState(self, module_name, state, value) -> None: + try: + parsed_value = ast.literal_eval(value) + except SyntaxError as e: + self.errorMessageWidget.showMessage(str(e)) + else: + module_state = self.module.get_state() + module_state[state] = parsed_value + self.module.set_state(module_state) + self.update_graphics_signal.emit() + def update(self): """ Update the visuals of the graphics item to match it's module. @@ -202,12 +234,23 @@ class ModuleGraphicsItem(QGraphicsObject, QGraphicsItem): for action in self.actions: menu.addAction(action) action.setData(self.name) - show_ports_action = QAction("Show all ports", menu) - show_ports_action.triggered.connect(self.showPorts) - show_ports_action.setEnabled(not self.isLocked) - menu.addAction(show_ports_action) return menu + def generateActions(self) -> None: + + self.show_ports_action = QAction("Show all ports") + self.show_ports_action.triggered.connect(self.showPorts) + self.show_ports_action.setEnabled(not self.isLocked) + self.actions.append(self.show_ports_action) + + state_br_action = QAction('Add breakpoint', self) + state_br_action.triggered.connect(self.stateBreakpointDialog) + self.actions.append(state_br_action) + + edit_state_action = QAction('Edit module state', self) + edit_state_action.triggered.connect(self.editModuleStateDialog) + self.actions.append(edit_state_action) + def getPortNames(self) -> list[str]: """ Return a list of the names of all ports of this module graphics item. @@ -220,23 +263,6 @@ class ModuleGraphicsItem(QGraphicsObject, QGraphicsItem): """ return self.ports - def addAction(self, action: QAction) -> None: - """ - Add the given QAction as an action of this module graphics item. The - actions are used in the context menu of the graphics item. - """ - if not self.shouldIgnoreAction(action): - self.actions.append(action) - - def addActions(self, actions: Iterable[QAction]) -> None: - """ - Add the given QActions as actions of this module graphics item. The - actions are used in the context menu of the graphics item. - """ - for action in actions: - if not self.shouldIgnoreAction(action): - self.actions.append(action) - def shouldIgnoreAction(self, action: QAction) -> bool: """ Returns True if the given action should not be used in the context menu @@ -246,6 +272,7 @@ class ModuleGraphicsItem(QGraphicsObject, QGraphicsItem): def setLocked(self, locked: bool) -> None: self.isLocked = locked + self.show_ports_action.setEnabled(not self.isLocked) for port in self.ports: port.setLocked(locked) @@ -281,3 +308,23 @@ class ModuleGraphicsItem(QGraphicsObject, QGraphicsItem): def getName(self) -> str: return self.name + + def getActions(self) -> []: + """ + Will return a list of module specific actions that + the gui will make available it the context menu. + """ + return self.actions + + def getActionSignals(self) -> []: + signals = [] + signals.append(("BP", self.new_state_breakpoint_signal)) + signals.append(("UPDATE", self.update_graphics_signal)) + return signals + + @pyqtSlot(str, str, str) + def stateBreakpointAccepted(self, module_name: str, state: str, value: str) -> None: + try: + self.new_state_breakpoint_signal.emit(module_name, state, value) + except (ValueError, SyntaxError) as e: + self.errorMessageWidget.showMessage(str(e)) diff --git a/src/simudator/gui/module_graphics_item/register_graphic.py b/src/simudator/gui/module_graphics_item/register_graphic.py new file mode 100644 index 0000000000000000000000000000000000000000..4938ae84b88e63f927f93dccbac243c8bdb4f3fa --- /dev/null +++ b/src/simudator/gui/module_graphics_item/register_graphic.py @@ -0,0 +1,41 @@ +from PyQt5.QtWidgets import QGraphicsRectItem, QGraphicsSimpleTextItem + +from simudator.gui.module_graphics_item.module_graphics_item import ModuleGraphicsItem + + +class RegisterGraphicsItem(ModuleGraphicsItem): + """ + A general garphics item for register modules. + """ + RECT_HEIGHT = 40 + RECT_WIDTH_PER_CHAR = 8 + RECT_HEIGHT_PER_CHAR = 16 + TEXT_HEIGHT_MARGIN = 10 + TEXT_WIDTH_MARGIN = 5 + + def draw_graphics_item(self) -> None: + self.draw_rect() + self.draw_ports() + + def draw_rect(self): + """ + Draw the register rect basd on contents of the register + """ + self.text = QGraphicsSimpleTextItem("", self) + self.text.setPos(self.TEXT_WIDTH_MARGIN, self.TEXT_HEIGHT_MARGIN) + + # Update here to fill text box, we can then use text box for lenght + self.update() + self.baserect = QGraphicsRectItem(0, 0, + len(self.text.text())*self.RECT_WIDTH_PER_CHAR + self.TEXT_WIDTH_MARGIN, + self.RECT_HEIGHT, self) + + def update(self): + self.state = self.module.get_state() + name = self.state["name"] + value = self.state["value"] + + value_text = f"{value}" + full_text = name + ": " + value_text + + self.text.setText(full_text) diff --git a/src/simudator/processor/mia/mia.py b/src/simudator/processor/mia/mia.py index b31746688ee0fa453ac29a0253a0307a11dd2824..69637f7e0e3024d1f186779bbdb2a16311ba0ee4 100644 --- a/src/simudator/processor/mia/mia.py +++ b/src/simudator/processor/mia/mia.py @@ -15,7 +15,7 @@ from simudator.gui.module_graphics_item.mia import ( GrxGraphicsItem, HrGraphicsItem, IrGraphicsItem, - MemoryGraphicsItem, + MiaMemoryGraphicsItem, MicroMemoryGraphicsItem, PcGraphicsItem, SupcGraphicsItem, @@ -274,7 +274,7 @@ class MIA_CPU(Processor): memory_modules = ["PM", "K1", "K2"] for name in memory_modules: module = self.get_module(name) - widget = MemoryGraphicsItem(module) + widget = MiaMemoryGraphicsItem(module) gui.addModuleGraphicsItem(widget) gui.addAllSignals() diff --git a/src/simudator/processor/mia/mia_register.py b/src/simudator/processor/mia/mia_register.py index 08d577d9904a0fa4f4e6cc9087bdac5a2cb44e7e..ab2a279608c2b75ad21860d9d5092e0b990b2201 100644 --- a/src/simudator/processor/mia/mia_register.py +++ b/src/simudator/processor/mia/mia_register.py @@ -42,10 +42,6 @@ class MiaRegister(Register): state["mask"] = self.mask return state - def get_gui_state(self) -> dict: - state = super().get_state() - return state - def set_state(self, state: dict[str: Any]) -> None: """ Sets the register state to one given in dict. diff --git a/src/simudator/processor/simple/simple.py b/src/simudator/processor/simple/simple.py index 1f24ba721b10dd548ec998c5097a69922ad7c504..d11d47da2036f3287b941f3b0fe5eb8b3675396f 100644 --- a/src/simudator/processor/simple/simple.py +++ b/src/simudator/processor/simple/simple.py @@ -6,10 +6,8 @@ from simudator.cli.cli import CLI from simudator.core.modules import Memory, Register from simudator.core.processor import Processor, Signal from simudator.gui.gui import GUI -from simudator.gui.module_graphics_item.mia import ( - MemoryGraphicsItem, - RegisterGraphicsItem, -) +from simudator.gui.module_graphics_item.memory_graphic import MemoryGraphicsItem +from simudator.gui.module_graphics_item.register_graphic import RegisterGraphicsItem class SIMPLE_CPU(Processor): @@ -32,15 +30,17 @@ class SIMPLE_CPU(Processor): mem_output = Signal(self, "mem_output") mem_control = Signal(self, "mem_control") mem_adress = Signal(self, "mem_adress") - register_signal = Signal(self, "empty") + empty_input = Signal(self, "empty_input", value=None) + empty_output = Signal(self, "empty_output", value=None) self.memory = Memory(mem_input, mem_output,mem_control,mem_adress, 10) - self.input_register = Register(register_signal, mem_input, name="Input Reg") + self.input_register = Register(empty_input, mem_input, name="Input Reg") self.output_register = Register(mem_output, - register_signal, name="Output Reg") - self.control_register = Register(mem_control, - register_signal, name="Control Reg") - self.adress_register = Register(mem_adress, register_signal, name="Adress Reg") + empty_output, name="Output Reg") + self.control_register = Register(empty_input, + mem_control, name="Control Reg") + self.adress_register = Register(empty_input, + mem_adress, name="Adress Reg") ALLTHEMODULES = [self.memory, self.input_register, self.output_register,