def devices_with_items(): """Return a Devices class instance with four devices in the network.""" new_names = Names() new_devices = Devices(new_names) [AND1_ID, NOR1_ID, SW1_ID, SIGGEN_ID] = new_names.lookup(["And1", "Nor1", "Sw1", "Siggen1"]) new_devices.make_device(AND1_ID, new_devices.AND, [2]) new_devices.make_device(NOR1_ID, new_devices.NOR, [16]) new_devices.make_device(SW1_ID, new_devices.SWITCH, [0]) new_devices.make_device(SIGGEN_ID, new_devices.SIGGEN, [0, 1, 1, 1, 0]) print(new_devices.find_devices()) return new_devices
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()