def test_2(): """Create network that matches test definition file 2""" names = Names() devices = Devices(names) network = Network(names, devices) monitors = Monitors(names, devices, network) CK1, CK2, AND1, NAND1, OR1, NOR1 = names.lookup( ["CK1", "CK2", "AND1", "NAND1", "OR1", "NOR1"]) devices.make_device(CK1, devices.CLOCK, 2) devices.make_device(CK2, devices.CLOCK, 1) devices.make_device(AND1, devices.AND, 2) devices.make_device(NAND1, devices.NAND, 2) devices.make_device(OR1, devices.OR, 2) devices.make_device(NOR1, devices.NOR, 2) network.make_connection(CK1, None, AND1, names.query("I1")) network.make_connection(CK2, None, AND1, names.query("I2")) network.make_connection(CK2, None, NAND1, names.query("I2")) network.make_connection(CK2, None, OR1, names.query("I2")) network.make_connection(CK2, None, NOR1, names.query("I2")) network.make_connection(AND1, None, NAND1, names.query("I1")) network.make_connection(NAND1, None, OR1, names.query("I1")) network.make_connection(OR1, None, NOR1, names.query("I1")) monitors.make_monitor(NOR1, None) return names, devices, network, monitors
def test_1(): """Create network that matches test definition file 1""" names = Names() devices = Devices(names) network = Network(names, devices) monitors = Monitors(names, devices, network) [SW1, SW2, SW3, SW4, D1, CK1, XOR1] = \ names.lookup(["SW1", "SW2", "SW3", "SW4", "D1", "CK1", "XOR1"]) print(names.query("SW1")) devices.make_device(SW1, devices.SWITCH, 1) devices.make_device(SW2, devices.SWITCH, 1) devices.make_device(SW3, devices.SWITCH, 1) devices.make_device(SW4, devices.SWITCH, 0) devices.make_device(D1, devices.D_TYPE) devices.make_device(CK1, devices.CLOCK, 2) devices.make_device(XOR1, devices.XOR) network.make_connection(SW1, None, XOR1, names.query("I1")) network.make_connection(SW2, None, XOR1, names.query("I2")) network.make_connection(XOR1, None, D1, names.query("DATA")) network.make_connection(CK1, None, D1, names.query("CLK")) network.make_connection(SW3, None, D1, names.query("SET")) network.make_connection(SW4, None, D1, names.query("CLEAR")) monitors.make_monitor(D1, names.query("Q")) monitors.make_monitor(D1, names.query("QBAR")) return names, devices, network, monitors
def main(): # Initialise instances of the four inner simulator classes names = Names() devices = Devices(names) network = Network(names, devices) monitors = Monitors(names, devices, network) [s1, s2] = devices.names.lookup(["S1", "S2"]) devices.make_switch(s1, 0) devices.make_switch(s2, 0) devices.make_gate(1, devices.names.query("NAND"), 2) devices.make_gate(2, devices.names.query("NAND"), 2) network.make_connection(s1, None, 1, devices.names.query("I1")) network.make_connection(s2, None, 2, devices.names.query("I2")) network.make_connection(1, None, 2, devices.names.query("I1")) network.make_connection(2, None, 1, devices.names.query("I2")) print(network.check_network()) print(monitors.make_monitor(1, None)) monitors.display_signals() userint = UserInterface(names, devices, network, monitors) userint.command_interface()
def new_monitors(): """Return a Monitors class instance with monitors set on three outputs.""" new_names = Names() new_devices = Devices(new_names) new_network = Network(new_names, new_devices) new_monitors = Monitors(new_names, new_devices, new_network) [SW1_ID, SW2_ID, OR1_ID, I1, I2] = new_names.lookup(["Sw1", "Sw2", "Or1", "I1", "I2"]) # Add 2 switches and an OR gate new_devices.make_device(SW1_ID, new_devices.SWITCH, [0]) new_devices.make_device(SW2_ID, new_devices.SWITCH, [0]) new_devices.make_device(OR1_ID, new_devices.OR, [2]) # Make connections new_network.make_connection(SW1_ID, None, OR1_ID, I1) new_network.make_connection(SW2_ID, None, OR1_ID, I2) # Set monitors new_monitors.make_monitor(SW1_ID, None) new_monitors.make_monitor(SW2_ID, None) new_monitors.make_monitor(OR1_ID, None) return new_monitors
def test_4(): """Create network that matches test definition file 4, a ripple counter""" names = Names() devices = Devices(names) network = Network(names, devices) monitors = Monitors(names, devices, network) SW, CK, D1, D2, D3, D4 = names.lookup(["SW", "CK", "D1", "D2", "D3", "D4"]) devices.make_device(SW, devices.SWITCH, 0) devices.make_device(CK, devices.CLOCK, 1) devices.make_device(D1, devices.D_TYPE) devices.make_device(D2, devices.D_TYPE) devices.make_device(D3, devices.D_TYPE) devices.make_device(D4, devices.D_TYPE) network.make_connection(SW, None, D1, names.query("SET")) network.make_connection(SW, None, D1, names.query("CLEAR")) network.make_connection(SW, None, D2, names.query("SET")) network.make_connection(SW, None, D2, names.query("CLEAR")) network.make_connection(SW, None, D3, names.query("SET")) network.make_connection(SW, None, D3, names.query("CLEAR")) network.make_connection(SW, None, D4, names.query("SET")) network.make_connection(SW, None, D4, names.query("CLEAR")) network.make_connection(CK, None, D1, names.query("CLK")) network.make_connection(D1, names.query("QBAR"), D2, names.query("CLK")) network.make_connection(D2, names.query("QBAR"), D3, names.query("CLK")) network.make_connection(D3, names.query("QBAR"), D4, names.query("CLK")) network.make_connection(D1, names.query("QBAR"), D1, names.query("DATA")) network.make_connection(D2, names.query("QBAR"), D2, names.query("DATA")) network.make_connection(D3, names.query("QBAR"), D3, names.query("DATA")) network.make_connection(D4, names.query("QBAR"), D4, names.query("DATA")) monitors.make_monitor(CK, None) monitors.make_monitor(D1, names.query("Q")) monitors.make_monitor(D2, names.query("Q")) monitors.make_monitor(D3, names.query("Q")) monitors.make_monitor(D4, names.query("Q")) return names, devices, network, monitors
def test_3(): """Create network that matches test definition file 3""" names = Names() devices = Devices(names) network = Network(names, devices) monitors = Monitors(names, devices, network) S, R, CK, D = names.lookup(["S", "R", "CK", "D"]) devices.make_device(S, devices.SWITCH, 0) devices.make_device(R, devices.SWITCH, 0) devices.make_device(CK, devices.CLOCK, 1) devices.make_device(D, devices.D_TYPE) network.make_connection(CK, None, D, names.query("CLK")) network.make_connection(S, None, D, names.query("SET")) network.make_connection(R, None, D, names.query("CLEAR")) network.make_connection(D, names.query("QBAR"), D, names.query("DATA")) monitors.make_monitor(CK, None) monitors.make_monitor(D, names.query("Q")) monitors.make_monitor(S, None) monitors.make_monitor(R, None) return names, devices, network, monitors
def monitors(names, devices, network): mon = Monitors(names, devices, network) mon.make_monitor = lambda a, b, c=0: mon.NO_ERROR return mon
class Gui(wx.Frame): # main options screen def __init__(self, title): """Initialise widgets and layout.""" super().__init__(parent=None, title=title) self.SetIcon(wx.Icon('GUI/CUED Software.png')) self.Maximize(True) self.SetBackgroundColour((186, 211, 255)) self.header_font = wx.Font(25, wx.FONTFAMILY_SWISS, wx.NORMAL, wx.FONTWEIGHT_BOLD, False) self.label_font = wx.Font(10, wx.FONTFAMILY_SWISS, wx.NORMAL, wx.NORMAL, False) self.makeLeftSizer() self.makeMiddleSizer() self.makeRightSizer() outer = wx.BoxSizer(wx.VERTICAL) self.main_sizer = wx.BoxSizer(wx.HORIZONTAL) self.main_sizer.Add(self.left_panel, 3, wx.ALL | wx.EXPAND, 20) self.main_sizer.Add(self.middle_panel, 3, wx.ALL | wx.EXPAND, 20) self.main_sizer.Add(self.right_panel, 3, wx.ALL | wx.EXPAND, 20) helpBtn = wx.Button(self, wx.ID_ANY, "Help") helpBtn.Bind(wx.EVT_BUTTON, self.open_help) outer.Add(helpBtn, 0, wx.ALL | wx.ALIGN_RIGHT, 0) outer.Add(self.main_sizer, 0, wx.EXPAND | wx.ALL, 0) self.SetSizer(outer) def makeLeftSizer(self): self.left_panel = wx.Panel(self) self.left_panel.SetBackgroundColour((37, 103, 209)) self.load_btn = wx.Button(self.left_panel, wx.ID_ANY, "Browse Files") self.check_btn = wx.Button(self.left_panel, wx.ID_ANY, 'Verify Code') left_heading = wx.StaticText(self.left_panel, -1, label="Editor") left_heading = self.style(left_heading, self.header_font) editor_font = wx.Font(14, wx.MODERN, wx.NORMAL, wx.NORMAL, False, u'Consolas') self.input_text = wx.stc.StyledTextCtrl(self.left_panel, size=(-1, wx.ALL)) self.input_text.SetMarginType(1, wx.stc.STC_MARGIN_NUMBER) self.input_text.SetMarginWidth(3, 15) self.input_text.SetUseHorizontalScrollBar(False) self.input_text.StyleSetFont(0, editor_font) self.input_text.AppendText("DEVICES {\n\n}\nCONNECTIONS {\n\n}") self.error_text = wx.TextCtrl(self.left_panel, wx.ID_ANY, size=(-1, wx.ALL), style=wx.TE_MULTILINE | wx.TE_READONLY, value="Click run to check for errors") self.error_text.SetFont(editor_font) self.error_text.SetStyle(0, -1, wx.TextAttr(wx.RED)) self.left_sizer = wx.BoxSizer(wx.VERTICAL) self.left_sizer.Add(left_heading, 0, wx.ALL | wx.ALIGN_CENTER, 10) row = wx.BoxSizer(wx.HORIZONTAL) row.Add(self.load_btn, 1, wx.ALIGN_LEFT, 5) row.Add(self.check_btn, 1, wx.ALIGN_RIGHT, 5) self.left_sizer.Add(row, 0, wx.ALL | wx.ALIGN_CENTER, 5) self.left_sizer.Add(self.input_text, 6, wx.EXPAND | wx.ALL, 10) self.left_sizer.Add(self.error_text, 1, wx.EXPAND | wx.ALL, 10) self.left_panel.SetSizer(self.left_sizer) self.load_btn.Bind(wx.EVT_BUTTON, self.LoadFile) self.check_btn.Bind(wx.EVT_BUTTON, self.CheckText) def makeMiddleSizer(self): self.middle_panel = wx.lib.scrolledpanel.ScrolledPanel(self) self.middle_panel.SetBackgroundColour((37, 103, 209)) self.middle_panel.SetupScrolling(scroll_x=False) self.middle_sizer = wx.BoxSizer(wx.VERTICAL) self.middle_panel.SetSizer(self.middle_sizer) self.middle_panel.Hide() self.Layout() def makeRightSizer(self): self.right_panel = wx.Panel(self) self.right_panel.SetBackgroundColour('white') self.right_sizer = wx.BoxSizer(wx.VERTICAL) self.right_panel.SetSizer(self.right_sizer) self.right_panel.Hide() self.Layout() def CheckText(self, event): self.names = Names() self.devices = Devices(self.names) self.network = Network(self.names, self.devices) self.monitors = Monitors(self.names, self.devices, self.network) self.scanner = Scanner(self.input_text.GetValue(), self.names, True) self.parser = Parser(self.names, self.devices, self.network, self.monitors, self.scanner) status = None # try: status = self.parser.parse_network() # except: # pass if self.scanner.total_error_string == "": self.error_text.AppendText("No errors found") else: self.error_text.Clear() self.error_text.AppendText(self.scanner.total_error_string) self.error_text.SetStyle(0, -1, wx.TextAttr(wx.RED)) self.middle_panel.Hide() self.right_panel.Hide() self.Layout() return if status == True and len(self.devices.devices_list) > 0: self.error_text.Clear() self.middle_sizer.Clear(True) self.middle_panel.Update() try: self.right_sizer.Remove(1) except: pass self.right_panel.Update() middle_heading = wx.StaticText(self.middle_panel, label="Options") middle_heading = self.style(middle_heading, self.header_font) self.middle_sizer.Add(middle_heading, 0, wx.ALL | wx.ALIGN_CENTER, 10) self.toggle_right_panel = wx.ToggleButton( self.middle_panel, label="show circuit (experimental)") self.toggle_right_panel.Bind(wx.EVT_TOGGLEBUTTON, self.OnRightPanelToggle) self.middle_sizer.Add(self.toggle_right_panel, 0, wx.ALL | wx.ALIGN_RIGHT, 5) device_info = wx.FlexGridSizer(4, 0, 10) # ------------- HEADINGS ------------- # label = wx.StaticText(self.middle_panel, label="Name") label = self.style(label, self.label_font) device_info.Add(label, 0, wx.EXPAND | wx.ALL, 0) label = wx.StaticText(self.middle_panel, label="Type") label = self.style(label, self.label_font) device_info.Add(label, 0, wx.EXPAND | wx.ALL, 0) label = wx.StaticText(self.middle_panel, label="Inputs") label = self.style(label, self.label_font) device_info.Add(label, 0, wx.EXPAND | wx.ALL, 0) label = wx.StaticText(self.middle_panel, label="Outputs") label = self.style(label, self.label_font) device_info.Add(label, 0, wx.EXPAND | wx.ALL, 0) # ---------------- TABLE --------------- # for device in self.devices.devices_list: name = self.devices.names.get_name_string(device.device_id) # DEVICE NAME label = wx.StaticText(self.middle_panel, label=self.devices.names.get_name_string( device.device_id)) label = self.style(label, self.label_font) device_info.Add(label, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) # DEVICE TYPE label = wx.StaticText(self.middle_panel, label=self.devices.names.get_name_string( device.device_kind)) label = self.style(label, self.label_font) device_info.Add(label, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) # INPUT NAMES s = "" for i in device.inputs: s = s + '{}.{}\n'.format(name, self.names.get_name_string(i)) s = s[:-1] label = wx.StaticText(self.middle_panel, label=s) label = self.style(label, self.label_font) device_info.Add(label, 0, wx.EXPAND | wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) # MONITOR OPTIONS # TODO: make them do somwthing if device.device_kind == self.devices.D_TYPE: device.monitor_btn = wx.ToggleButton( self.middle_panel, label="monitor {}.Q".format(name)) device.monitor_btn_bar = wx.ToggleButton( self.middle_panel, label="monitor {}.QBAR".format(name)) device.monitor_btn.Bind(wx.EVT_TOGGLEBUTTON, self.OnToggleClick) device.monitor_btn.SetForegroundColour('white') device.monitor_btn_bar.Bind(wx.EVT_TOGGLEBUTTON, self.OnToggleClick) device.monitor_btn_bar.SetForegroundColour('white') row = wx.BoxSizer(wx.VERTICAL) row.Add( device.monitor_btn, 1, wx.ALL | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL, 5) row.Add( device.monitor_btn_bar, 1, wx.ALL | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL, 5) if name + '.Q' in self.monitors.get_signal_names()[0]: device.monitor_btn.SetValue(True) device.monitor_btn.SetBackgroundColour('#3ac10d') else: device.monitor_btn.SetBackgroundColour('#e0473a') if name + '.QBAR' in self.monitors.get_signal_names()[0]: device.monitor_btn_bar.SetValue(True) device.monitor_btn_bar.SetBackgroundColour('#3ac10d') else: device.monitor_btn_bar.SetBackgroundColour('#e0473a') device_info.Add( row, 1, wx.ALL | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL, 5) else: device.monitor_btn = wx.ToggleButton( self.middle_panel, label="monitor {}".format(name)) device.monitor_btn.Bind(wx.EVT_TOGGLEBUTTON, self.OnToggleClick) device.monitor_btn.SetForegroundColour('white') if name in self.monitors.get_signal_names()[0]: device.monitor_btn.SetValue(True) device.monitor_btn.SetBackgroundColour('#3ac10d') else: device.monitor_btn.SetBackgroundColour('#e0473a') device_info.Add( device.monitor_btn, 1, wx.ALL | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL, 5) # ----------- SET INITIAL SWITCH STATES ------------ # self.switch_options = wx.FlexGridSizer(2, 0, 30) for device in self.devices.devices_list: if device.device_kind != self.devices.SWITCH: continue name = self.devices.names.get_name_string(device.device_id) label = wx.StaticText(self.middle_panel, 1, label=name) label = self.style(label, self.label_font) self.switch_options.Add(label, 1, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) device.switch_btn = wx.ToggleButton( self.middle_panel, label="initial switch state") device.switch_btn.Bind(wx.EVT_TOGGLEBUTTON, self.OnToggleClick) device.switch_btn.SetForegroundColour('white') if device.switch_state: device.switch_btn.SetValue(True) device.switch_btn.SetBackgroundColour('#3ac10d') else: device.switch_btn.SetBackgroundColour('#e0473a') self.switch_options.Add(device.switch_btn, 1, wx.ALL, 5) self.middle_sizer.Insert(1, self.switch_options, 0, wx.ALL | wx.ALIGN_CENTER, 30) self.middle_sizer.Insert(1, wx.StaticLine(self.middle_panel), 0, wx.EXPAND, 5) self.middle_sizer.Insert(1, device_info, 0, wx.ALL | wx.ALIGN_CENTER, 30) simulate_btn = wx.Button(self.middle_panel, label="Simulate!") simulate_btn.Bind(wx.EVT_BUTTON, self.newSimulate) self.middle_sizer.Add(simulate_btn, 0, wx.ALL | wx.EXPAND, 30) self.middle_panel.Show() self.SimulateWindow = SimulatePage(self) self.canvas = CircuitDiagram(self.right_panel, self.devices, self.network, self.names) self.right_sizer.Clear() self.right_sizer.Add(self.canvas, 1, wx.EXPAND | wx.ALL, 0) self.Layout() def newSimulate(self, event): self.SimulateWindow.Show() self.monitors.reset_monitors() for device in self.devices.devices_list: if hasattr(device, 'monitor_btn'): if device.monitor_btn.GetValue(): if device.device_kind == self.devices.D_TYPE: self.monitors.make_monitor(device.device_id, self.names.query("Q")) else: self.monitors.make_monitor(device.device_id, None) if hasattr(device, 'monitor_btn_bar'): if device.monitor_btn_bar.GetValue(): self.monitors.make_monitor(device.device_id, self.names.query("QBAR")) if hasattr(device, 'switch_btn'): if device.switch_btn.GetValue(): self.devices.set_switch(device.device_id, self.devices.HIGH) else: self.devices.set_switch(device.device_id, self.devices.LOW) self.SimulateWindow.run(5) def OnRightPanelToggle(self, event): obj = event.GetEventObject() if obj.GetValue(): self.right_panel.Show() else: self.right_panel.Hide() self.Layout() def OnToggleClick(self, event): obj = event.GetEventObject() if obj.GetValue(): obj.SetBackgroundColour('#3ac10d') else: obj.SetBackgroundColour('#e0473a') def style(self, obj, font, fgcolour='white', bgcolour=None): obj.SetForegroundColour(fgcolour) obj.SetBackgroundColour(bgcolour) obj.SetFont(font) return obj def LoadFile(self, event): # otherwise ask the user what new file to open with wx.FileDialog(self, "Open file", wildcard="TXT files (*.txt)|*.txt", style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog: fileDialog.SetSize((120, 80)) if fileDialog.ShowModal() == wx.ID_CANCEL: return # the user changed their mind # Proceed loading the file chosen by the user pathname = fileDialog.GetPath() try: with open(pathname, 'r') as f: self.input_text.ClearAll() self.input_text.AppendText(f.read()) except IOError: wx.LogError("Cannot open file '%s'." % pathname) def open_help(self, event): filepath = 'GUI/helpfile.pdf' import subprocess import os import platform if platform.system() == 'Darwin': # macOS subprocess.call(('open', filepath)) elif platform.system() == 'Windows': # Windows filepath = filepath.replace('/', '\\') os.startfile(filepath) else: # linux variants subprocess.call(('xdg-open', filepath)) event.Skip()
class Gui(wx.Frame): """Configure the main window and all the widgets. This class provides a graphical user interface for the Logic Simulator and enables the user to change the circuit properties and run simulations. Parameters ---------- title: title of the window. Public methods -------------- on_menu(self, event): Event handler for the file menu. on_size(self, event): Event handler for window resizing. on_reload(self): Handle the event when the user reloads the file. on_lang_change(self): Show dialog for language change. on_toggle_3d_vew(self): Toggle 3D view. on_help(self): Shows a help window with user instructions. on_center(self): Centers the canvas to its default state of zoom and panning. on_continue(self): Run the continue_command when continue button pressed. continue_command(self): Continue a previously run simulation. on_run(self): Run the run_command when run button pressed. run_command(self): Run the simulation from scratch. run_network(self, cycles): Run the network for the specified number of simulation cycles. on_open(self): Open the file browser and parse the file chosen. load_file(self, file_path): Load a file for parsing and running. run_parser(self, file_path): Reset modules and call parse_network. clear_log(self): Clear the activity log. log_message(self, text, style, no_new_line): Add message to the activity log. set_switch(self, switch_name, is_on): Turn a swtich on and off. set_monitor(self, monitor_name, is_active): Activate or deactivate a monitor. update_tabs(self): Update the right panel tabs with new values. update_texts(self): Updates the text fields of the application after a change of locale. make_right_sizer(self): Helper function that creates the right panel sizer. def update_language(self, lang): Update the language to the requested one. """ def __init__(self, title): """Initialise widgets and layout.""" super().__init__(parent=None, title=title, size=(800, 600)) self.names = Names() self.devices = Devices(self.names) self.network = Network(self.names, self.devices) self.monitors = Monitors(self.names, self.devices, self.network) # work around for Python stealing "_" sys.displayhook = _displayHook # Add locale path and update the language self.locale = None wx.Locale.AddCatalogLookupPathPrefix('locale') sys_lang = wx.Locale.GetSystemLanguage() lang_name = wx.Locale.GetLanguageCanonicalName(sys_lang) self.update_language(lang_name[:2]) # Add fonts self.NORMAL_FONT = wx.TextAttr() self.MONOSPACE_FONT = wx.TextAttr( 'BLACK', font=wx.Font(wx.FontInfo(10).Family(wx.FONTFAMILY_TELETYPE))) # Add IDs for menu and toolbar items self.ID_OPEN = 1001 self.ID_CENTER = 1002 self.ID_RUN = 1003 self.ID_CONTINUE = 1004 self.ID_CYCLES_CTRL = 1005 self.ID_HELP = 1006 self.ID_CLEAR = 1007 self.ID_TOGGLE_3D = 1008 self.ID_LANG = 1009 self.ID_RELOAD = 1010 # Configure the file menu fileMenu = wx.Menu() viewMenu = wx.Menu() runMenu = wx.Menu() optionsMenu = wx.Menu() helpMenu = wx.Menu() menuBar = wx.MenuBar() fileMenu.Append(self.ID_OPEN, _("&Open") + "\tCtrl+O") fileMenu.Append(self.ID_RELOAD, _("&Reload") + "\tCtrl+R") fileMenu.Append(wx.ID_EXIT, _("&Exit")) viewMenu.Append(self.ID_CENTER, _("&Center") + "\tCtrl+E") viewMenu.Append(self.ID_TOGGLE_3D, _("&Toggle 2D/3D view") + "\tCtrl+T") viewMenu.Append(self.ID_CLEAR, _("&Clear Activity Log") + "\tCtrl+L") runMenu.Append(self.ID_RUN, _("R&un") + "\tCtrl+Shift+R") runMenu.Append(self.ID_CONTINUE, _("&Continue") + "\tCtrl+Shift+C") optionsMenu.Append(self.ID_LANG, _("Change &Language")) helpMenu.Append(self.ID_HELP, _("&Help") + "\tCtrl+H") helpMenu.Append(wx.ID_ABOUT, _("&About")) menuBar.Append(fileMenu, _("&File")) menuBar.Append(viewMenu, _("&View")) menuBar.Append(runMenu, _("&Simulation")) menuBar.Append(optionsMenu, _("O&ptions")) menuBar.Append(helpMenu, _("&Help")) self.SetMenuBar(menuBar) # Load icons appIcon = wx.Icon("res/cylinder.png") self.SetIcon(appIcon) openIcon = wx.Bitmap("res/open_mat.png") reloadIcon = wx.Bitmap("res/reload_mat.png") centerIcon = wx.Bitmap("res/center_mat.png") runIcon = wx.Bitmap("res/run.png") continueIcon = wx.Bitmap("res/continue_mat.png") infoIcon = wx.Bitmap("res/info_mat_outline.png") self.layout2dIcon = wx.Bitmap("res/layout2d.png") self.layout3dIcon = wx.Bitmap("res/layout3d.png") flagIcon = langlc.GetLanguageFlag(self.locale.GetLanguage()) # Configure toolbar # Keep a reference to the toolBar to update its icons self.toolBar = self.CreateToolBar() # TODO Change names icons and event handling of tools # TODO Add Shorthelp option to tools (i.e. tooltip) # TODO Create matching options in the fileMenu and associate them # with shortcuts self.spin = wx.SpinCtrl(self.toolBar, value='10') self.toolBar.AddTool(self.ID_OPEN, "Tool2", openIcon) self.toolBar.AddTool(self.ID_RELOAD, "Tool8", reloadIcon) self.toolBar.AddSeparator() self.toolBar.AddTool(self.ID_CENTER, "Tool3", centerIcon) self.toolBar.AddTool(self.ID_TOGGLE_3D, "Tool6", self.layout3dIcon) self.toolBar.AddSeparator() self.toolBar.AddTool(self.ID_RUN, "Tool4", runIcon) self.toolBar.AddTool(self.ID_CONTINUE, "Tool5", continueIcon) self.toolBar.AddControl(self.spin, "SpinCtrl") self.toolBar.AddSeparator() self.toolBar.AddTool(self.ID_LANG, "Tool7", flagIcon) self.toolBar.AddSeparator() self.toolBar.AddTool(self.ID_HELP, "Tool1", infoIcon, shortHelp=_("Help")) self.SetToolBar(self.toolBar) # State variables self.current_file_path = None # set current file path self.parse_success = False # prevents run and continue if parse fails self.canvas_mode = '2d' # current display mode of canvas self.cycles_completed = 0 # number of simulation cycles completed # Canvas for drawing signals self.canvas = MyGLCanvasWrapper(self) # Configure the widgets self.activity_log = wx.TextCtrl(self, wx.ID_ANY, _("Ready. Please load a file."), style=wx.TE_MULTILINE | wx.TE_READONLY) self.activity_log_label = wx.StaticText(self, label=_("Activity Log")) # Bind events to widgets self.Bind(wx.EVT_MENU, self.on_menu) self.Bind(wx.EVT_SIZE, self.on_size) # Configure sizers for layout main_sizer = wx.BoxSizer(wx.HORIZONTAL) right_sizer = wx.BoxSizer(wx.VERTICAL) left_sizer = wx.BoxSizer(wx.VERTICAL) left_sizer.Add(self.canvas, 3, wx.EXPAND | wx.ALL, 5) left_sizer.Add(self.activity_log_label, 0.2, wx.EXPAND | wx.ALL, 5) left_sizer.Add(self.activity_log, 1, wx.EXPAND | wx.ALL, 5) right_sizer = self.make_right_sizer() main_sizer.Add(left_sizer, 5, wx.EXPAND | wx.ALL, 5) main_sizer.Add(right_sizer, 0, wx.EXPAND | wx.ALL, 5) self.SetSizeHints(1200, 800) self.SetSizer(main_sizer) def make_right_sizer(self): """Helper function that creates the right sizer""" right_sizer = wx.BoxSizer(wx.VERTICAL) # Create the notebook to hold tabs self.notebook = wx.Notebook(self, size=(200, -1)) # Create the tabs self.monitor_tab = CustomTab(self.notebook) self.switch_tab = CustomTab(self.notebook) self.monitor_tab.set_on_item_selected_listener(self.set_monitor) self.switch_tab.set_on_item_selected_listener(self.set_switch) self.notebook.AddPage(self.monitor_tab, _("Monitors")) self.notebook.AddPage(self.switch_tab, _("Switches")) right_sizer.Add(self.notebook, 1, wx.EXPAND | wx.ALL, 5) return right_sizer def update_language(self, lang): """ Update the language to the requested one. Make *sure* any existing locale is deleted before the new one is created. The old C++ object needs to be deleted before the new one is created, and if we just assign a new instance to the old Python variable, the old C++ locale will not be destroyed soon enough, likely causing a crash. :param string `lang`: one of the supported language codes """ # if an unsupported language is requested default to English if lang in appC.supLang: selLang = appC.supLang[lang] else: selLang = wx.LANGUAGE_ENGLISH if self.locale: assert sys.getrefcount(self.locale) <= 2 del self.locale # create a locale object for this language self.locale = wx.Locale(selLang) if self.locale.IsOk(): self.locale.AddCatalog(appC.langDomain) else: self.locale = None def update_texts(self): """Updates the text fields around the application after a change of locale. """ # Update menu items # WARNING: This update assumes a certain order of menus # Does NOT scale; Consider refactoring for robustness # 0 -> files # 1 -> view # 2 -> simulation # 3 -> options # 4 -> help menuBar = self.GetMenuBar() menuBar.SetMenuLabel(0, _("&File")) menuBar.SetMenuLabel(1, _("&View")) menuBar.SetMenuLabel(2, _("&Simulation")) menuBar.SetMenuLabel(3, _("O&ptions")) menuBar.SetMenuLabel(4, _("&Help")) # Update menu subitems menuBar.SetLabel(self.ID_OPEN, _("&Open") + "\tCtrl+O") menuBar.SetLabel(self.ID_RELOAD, _("&Reload") + "\tCtrl+R") menuBar.SetLabel(wx.ID_EXIT, _("&Exit")) menuBar.SetLabel(self.ID_CENTER, _("&Center") + "\tCtrl+E") menuBar.SetLabel(self.ID_TOGGLE_3D, _("&Toggle 2D/3D vew") + "\tCtrl+T") menuBar.SetLabel(self.ID_CLEAR, _("&Clear Activity Log") + "\tCtrl+L") menuBar.SetLabel(self.ID_LANG, _("Change &Language")) menuBar.SetLabel(self.ID_RUN, _("&Run") + "\tCtrl+Shift+R") menuBar.SetLabel(self.ID_CONTINUE, _("&Continue") + "\tCtrl+Shift+C") menuBar.SetLabel(self.ID_HELP, _("&Help") + "\tCtrl+H") menuBar.SetLabel(wx.ID_ABOUT, _("&About")) # Update toolbar tooltips # TODO # Update flag icon flagIcon = langlc.GetLanguageFlag(self.locale.GetLanguage()) self.GetToolBar().SetToolNormalBitmap(self.ID_LANG, flagIcon) # Update right panel self.notebook.SetPageText(0, _("Monitors")) self.notebook.SetPageText(1, _("Switches")) self.monitor_tab.update_texts() self.switch_tab.update_texts() # Update static texts self.activity_log_label.SetLabel(_("Activity Log")) def update_tabs(self): """Update the tabs with new values.""" # Get monitor names [mons, non_mons] = self.monitors.get_signal_names() # Get switch names switch_ids = self.devices.find_devices(self.devices.SWITCH) switch_names = [ self.names.get_name_string(sw_id) for sw_id in switch_ids ] switch_signals = [ self.devices.get_device(sw_id).switch_state for sw_id in switch_ids ] switch_states = [ True if sig in [self.devices.HIGH, self.devices.RISING] else False for sig in switch_signals ] # Reset tab elements self.monitor_tab.clear() self.monitor_tab.append(list(zip(mons, [True for i in mons]))) self.monitor_tab.append(list(zip(non_mons, [False for i in non_mons]))) self.switch_tab.clear() self.switch_tab.append(list(zip(switch_names, switch_states))) def set_monitor(self, monitor_name, is_active): """Activate or deactivate a monitor. Parameters ---------- monitor_id: The id of the monitor to change state is_active: The state of the monitor; True to activate and False to deactivate """ # Split the monitor to device name and port name if it exists splt = monitor_name.split('.') if len(splt) == 1: # No port given device_id = self.names.query(splt[0]) port_id = None elif len(splt) == 2: # Port given device_id = self.names.query(splt[0]) port_id = self.names.query(splt[1]) else: # TODO: Print error pass if device_id is None: # TODO: Reformat error text for consistency with parser self.log_message( _("Error: Monitor {} not found.").format(monitor_name)) return # Add/remove monitor if is_active: action = _("activated") monitor_error = self.monitors.make_monitor(device_id, port_id, self.cycles_completed) if monitor_error == self.monitors.NO_ERROR: self.log_message( _("Monitor {} was {}.").format(monitor_name, action)) else: # TODO: Print error return else: action = _("deactivated") if self.monitors.remove_monitor(device_id, port_id): self.log_message("Monitor {} was {}.".format( monitor_name, action)) else: # TODO: Print error return self.canvas.restore_state() self.canvas.render(_("Monitor changed")) def set_switch(self, switch_name, is_on): """Turn a swtich on and off. Parameters ---------- switch_id: The id of the switch to change output is_on: The state of the monitor; True to turn on and False to turn off """ # Get the switch id switch_id = self.names.query(switch_name) if switch_id is None: # TODO: Reformat error text for consistency with parser self.log_message( _("Error: Monitor {} not found.").format(monitor_name)) return # Turn on/off the switch if is_on: switch_state = 1 state_text = _("ON") else: switch_state = 0 state_text = _("OFF") if self.devices.set_switch(switch_id, switch_state): self.log_message( _("Switch {} was switched {}").format(switch_name, state_text)) else: # TODO: Print error return def clear_log(self): """Clear the activity log.""" self.activity_log.Clear() def log_message(self, text, style=None, no_new_line=False): """Add message to the activity log.""" if style is not None: self.activity_log.SetDefaultStyle(style) if no_new_line: self.activity_log.AppendText(str(text)) else: self.activity_log.AppendText("\n" + str(text)) self.activity_log.ShowPosition(self.activity_log.GetLastPosition()) self.activity_log.SetDefaultStyle(self.NORMAL_FONT) ################# # author: Jorge # ################# def run_parser(self, file_path): """Call parse_network() from path specified. To do so first reinitzialize all modules and cycles_completed. """ # clear all at the begging self.cycles_completed = 0 self.names = Names() self.devices = Devices(self.names) self.network = Network(self.names, self.devices) self.monitors = Monitors(self.names, self.devices, self.network) self.scanner = Scanner(file_path, self.names) self.parser = Parser(self.names, self.devices, self.network, self.monitors, self.scanner) # Capture the stdout from parse_network() captured_stdout = io.StringIO() with redirect_stdout(captured_stdout): if self.parser.parse_network(): self.parse_success = True self.log_message(_("Succesfully parsed network.")) else: self.parse_success = False self.log_message(_("Failed to parse network.")) # Show error messages captured in activity log self.log_message(captured_stdout.getvalue(), self.MONOSPACE_FONT) def load_file(self, file_path): """Load a file for parsing and running.""" self.run_parser(file_path) self.canvas.restore_state() self.update_tabs() def on_open(self): """Open the file browser and parse the file chosen.""" text = _("Open file dialog.") openFileDialog = wx.FileDialog( self, _("Open"), wildcard="Circuit Definition files (*.txt;*.lcdf)|*.txt;*.lcdf", style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) res = openFileDialog.ShowModal() if res == wx.ID_OK: # user selected a file self.current_file_path = openFileDialog.GetPath() self.clear_log() self.log_message(_("File opened: {}").format( self.current_file_path), no_new_line=True) self.load_file(self.current_file_path) self.canvas.render(_("Opened file")) def run_network(self, cycles): """Run the network for the specified number of simulation cycles. Return True if successful. """ for i in range(cycles): if self.network is None: self.log_message(_("Error! No file loaded.")) return False if self.network.execute_network(): self.monitors.record_signals() else: self.log_message(_("Error! Network oscillating.")) return False return True def run_command(self): """Run the simulation from scratch.""" if not self.parse_success: self.log_message(_("Error! Nothing to run. Parsing failed.")) return self.cycles_completed = 0 cycles = self.spin.GetValue() # this must get input from other box if cycles is not None: # if the number of cycles provided is valid self.monitors.reset_monitors() self.log_message(_("Running for {} cycles").format(cycles)) self.devices.cold_startup() if self.run_network(cycles): self.cycles_completed += cycles def on_run(self): """Run the run_command when run button pressed.""" self.run_command() self.canvas.recenter() self.canvas.render(_("RUN")) def continue_command(self): """Continue a previously run simulation.""" if not self.parse_success: self.log_message(_("Error! Nothing to continue. Parsing failed.")) return cycles = self.spin.GetValue() if cycles is not None: # if the number of cycles provided is valid if self.cycles_completed == 0: self.log_message(_("Error! Nothing to continue. Run first.")) elif self.run_network(cycles): self.cycles_completed += cycles self.log_message( _("Continuing for {} cycles. Total: {}").format( cycles, self.cycles_completed)) def on_continue(self): """Run the continue_command when run button pressed.""" self.continue_command() self.canvas.render(_("Continue")) self.canvas.recenter(pan_to_end=True) #################### # author: Dimitris # #################### def on_center(self): """Centers the canvas to its default state of zoom and panning.""" self.log_message(_("Center canvas.")) self.canvas.recenter() def on_help(self): """Shows a help window with user instructions.""" help_title = _("Help - Program controls ") # TODO Find a more elegant way to provide localisation for this text help_content = ("" + _("Shortcuts: \n") + _("Ctrl + O: Open file\n") + _("Ctrl + H: Help\n") + _("Ctrl + R: Run\n") + _("Ctrl + Shift + C: Continue\n") + _("Ctrl + E: Center canvas\n") + _("Ctrl + T: Toggle 2D/3D view\n") + _("Ctrl + L: Clear activity log\n\n") + _("User Instructions:\n") + _("Use the Open file button to select ") + _("the desired circuit defnition file.") + _("If the file contains no errors the activity") + _(" log at the bottom of the window") + _("will read \"Succesfully parsed network\". ") + _("If there are errors, the activity log") + _("will read \"Failed to parse network\".\n\n") + _("If the network was parsed correctly it can be" "ran. ") + _("Use the plus and minus on the") + _("cycle selector to select the desired number") + _(" of cycles for the simulation or") + _("type in th desired number. Press the Run ") + _("button to run the simulator for the number") + _("of cycles selected and display the waveforms ") + _("at the current monitor points (from a") + _("cold-startup of the circuit). Press the ") + _("Continue button to run the simulator") + _("for an additional number of cycles as selected ") + _("in the cycle selector and") + _("display the waveforms at the current monitor " "points.\n\n") + _("The canvas can be restored to its default state ") + _("of position and zoomby") + _("selecting the center button.\n\n") + _("Different monitor points can be set ") + _("and zapped by first selecting the") + _("Monitors tab on the right panel, and then ") + _("selecting the desired monitor") + _("point from the list.\n\n") + _("Switches can be operated by first selecting ") + _("the Switches tab on the right") + _("panel, and then selecting the desired switches.")) wx.MessageBox(help_content, help_title, wx.ICON_INFORMATION | wx.OK) def on_toggle_3d_vew(self): """Toggle 3D view.""" if self.canvas_mode == '2d': self.canvas_mode = '3d' self.toolBar.SetToolNormalBitmap(self.ID_TOGGLE_3D, self.layout2dIcon) else: self.canvas_mode = '2d' self.toolBar.SetToolNormalBitmap(self.ID_TOGGLE_3D, self.layout3dIcon) self.canvas.toggle_drawing_mode() ################## # author: George # ################## def on_lang_change(self): """Show dialog for language change.""" dlg = LangDialog( self, -1, _("Pick your language"), self.locale.GetLanguage(), size=(350, 200), style=wx.DEFAULT_DIALOG_STYLE, ) # This does not return until the dialog is closed. val = dlg.ShowModal() sel_lang = dlg.GetLangSelected() if val == wx.ID_OK: # User pressed OK self.update_language( wx.Locale.GetLanguageCanonicalName(sel_lang)[:2]) self.update_texts() dlg.Destroy() def on_reload(self): """Handle the event when the user reloads the file.""" if self.current_file_path is None: # No file has been loaded self.log_message(_("No file loaded. Please load a file.")) return self.clear_log() self.log_message(_("File reloaded: {}").format(self.current_file_path), no_new_line=True) self.load_file(self.current_file_path) def on_menu(self, event): """Handle the event when the user selects a menu item.""" Id = event.GetId() if Id == wx.ID_EXIT: self.Close(True) elif Id == wx.ID_ABOUT: wx.MessageBox(_("Logic Simulator\nCreated by Psylinders\n2019"), _("About Logsim"), wx.ICON_INFORMATION | wx.OK) elif Id == self.ID_OPEN: # file dialog self.on_open() elif Id == self.ID_RUN: # run button self.on_run() elif Id == self.ID_CONTINUE: # continue button self.on_continue() elif Id == self.ID_CENTER: # center button self.on_center() elif Id == self.ID_HELP: # help button self.on_help() elif Id == self.ID_CLEAR: # help button self.clear_log() elif Id == self.ID_TOGGLE_3D: # togge 3D view button self.on_toggle_3d_vew() elif Id == self.ID_LANG: self.on_lang_change() elif Id == self.ID_RELOAD: self.on_reload() def on_size(self, event): """Handle the event when the window resizes.""" self.Refresh() event.Skip()