def main(arg_list): """Parse the command line options and arguments specified in arg_list. Run either the command line user interface, the graphical user interface, or display the usage message. """ usage_message = ("Usage:\n" "Show help: logsim.py -h\n" "Command line user interface: logsim.py -c <file path>\n" "Graphical user interface: logsim.py <file path>") try: options, arguments = getopt.getopt(arg_list, "hc:") except getopt.GetoptError: print("Error: invalid command line arguments\n") print(usage_message) sys.exit() # Initialise instances of the four inner simulator classes names = Names() devices = Devices(names) network = Network(names, devices) monitors = Monitors(names, devices, network) for option, path in options: if option == "-h": # print the usage message print(usage_message) sys.exit() elif option == "-c": # use the command line user interface scanner = Scanner(path, names) parser = Parser(names, devices, network, monitors, scanner) if parser.parse_network(): # Initialise an instance of the userint.UserInterface() class userint = UserInterface(names, devices, network, monitors) userint.command_interface() if not options: # no option given, use the graphical user interface if len(arguments) != 1: # wrong number of arguments print("Error: one file path required\n") print(usage_message) sys.exit() [path] = arguments scanner = Scanner(path, names) parser = Parser(names, devices, network, monitors, scanner) if parser.parse_network(): # Initialise an instance of the gui.Gui() class app = wx.App() builtins._ = wx.GetTranslation locale = wx.Locale() locale.Init(wx.LANGUAGE_DEFAULT) locale.AddCatalogLookupPathPrefix('./locale') locale.AddCatalog('zh_CN') gui = Gui("Logic Simulator", path, names, devices, network, monitors) gui.Show(True) app.MainLoop()
def test_error_location(names, devices, network, monitors, capsys): """Test if error detection correctly prints out location of error """ sc = Scanner(error_location, names) parser = Parser(names, devices, network, monitors, sc) parser.parse_network() captured = capsys.readouterr() line_number = "line 10" try: assert (line_number in captured.out) except AttributeError: assert (line_number in captured[0])
def test_semantic_errors(bad_file, error_message, names, devices, network, monitors, capsys): """Going through all the files which should throw semantic errors and seeing if correct error message is displayed by looking as system output """ sc = Scanner(bad_file, names) parser = Parser(names, devices, network, monitors, sc) parser.parse_network() captured = capsys.readouterr() try: assert (error_message in captured.out) except AttributeError: assert (error_message in captured[0])
def test_parse_network_correct(): """Test the overall parse.py flow with a complicated enough but correct definition file.""" my_names = Names() my_scanner = Scanner("test_def_files/sequential.txt", my_names) my_devices = Devices(my_names) my_network = Network(my_names, my_devices) my_monitors = Monitors(my_names, my_devices, my_network) my_parser = Parser( my_names, my_devices, my_network, my_monitors, my_scanner) # Check that no semantic or syntax errors were made assert my_parser.parse_network()
def test_parse_network_incorrect_semantics12(): """Test the overall parse.py flow with a definition file with incorrect semantics. Basically, this file opens a block comment but does not close it. Ensure that parser does not get stuck in infinite loop.""" my_names = Names() my_scanner = Scanner("test_def_files/for_check_infiniteloop.txt", my_names) my_devices = Devices(my_names) my_network = Network(my_names, my_devices) my_monitors = Monitors(my_names, my_devices, my_network) my_parser = Parser( my_names, my_devices, my_network, my_monitors, my_scanner) # Check that parse_network() returns False assert not my_parser.parse_network()
def test_parse_network_incorrect_syntax(): """Test the overall parse.py flow with a definition file with incorrect syntax.""" my_names = Names() my_scanner = Scanner("test_def_files/srbistablewrong.txt", my_names) my_devices = Devices(my_names) my_network = Network(my_names, my_devices) my_monitors = Monitors(my_names, my_devices, my_network) my_parser = Parser( my_names, my_devices, my_network, my_monitors, my_scanner) assert not my_parser.parse_network() assert len(my_parser.syntax_errors_list) == 2 assert my_parser.syntax_errors_list[0] == "start" assert my_parser.syntax_errors_list[1] == my_scanner.DEVICES_ID
def main(arg_list): """Parse the command line options and arguments specified in arg_list. Run either the command line user interface, the graphical user interface, or display the usage message. """ usage_message = ("Usage:\n" "Show help: logsim.py -h\n" "Command line user interface: logsim.py -c <file path>\n" "Graphical user interface: logsim.py <file path>") try: options, arguments = getopt.getopt(arg_list, "hc:") except getopt.GetoptError: print("Error: invalid command line arguments\n") print(usage_message) sys.exit() # Initialise instances of the four inner simulator classes names = Names() devices = Devices(names) network = Network(names, devices) monitors = Monitors(names, devices, network) for option, path in options: if option == "-h": # print the usage message print(usage_message) sys.exit() elif option == "-c": # use the command line user interface scanner = Scanner(path, names) parser = Parser(names, devices, network, monitors, scanner) if parser.parse_network(): # Initialise an instance of the userint.UserInterface() class userint = UserInterface(names, devices, network, monitors) userint.command_interface() if not options: # no option given, use the graphical user interface # Initialise an instance of the gui.Gui() class app = wx.App() gui = Gui("Logic Simulator") # gui = Gui("Logic Simulator", None, names, devices, network, monitors) gui.Show(True) app.MainLoop()
def test_parse_network_incorrect_semantics11(): """Test the overall parse.py flow with a definition file with incorrect semantics.""" my_names = Names() my_scanner = Scanner("test_def_files/monitordeviceabsent.txt", my_names) my_devices = Devices(my_names) my_network = Network(my_names, my_devices) my_monitors = Monitors(my_names, my_devices, my_network) my_parser = Parser( my_names, my_devices, my_network, my_monitors, my_scanner) # Check that parse_network() returns False assert not my_parser.parse_network() # Check that there are no syntax errors assert len(my_parser.syntax_errors_list) == 0 # Check that 1 semantic error is logged (from actual error) assert len(my_parser.semantic_errors_list) == 1 assert my_parser.semantic_errors_list[0] == my_network.DEVICE_ABSENT
def main(arg_parser): """Parse the command line options and arguments specified in arg_list. Run either the command line user interface, the graphical user interface, or display the usage message. """ # Add check to see if path leads to a .txt file if arg_parser.path is not None: if arg_parser.path[-3:] != "txt": raise TypeError("***ERROR: Please load in a .txt file") # Check if user tries to input translation into command line # interface if arg_parser.c and arg_parser.r: print("Cannot launch command line mode with translator") sys.exit() # Initialise instances of the four inner simulator classes names = Names() devices = Devices(names) network = Network(names, devices) monitors = Monitors(names, devices, network) scanner = Scanner(arg_parser.path, names) parser = Parser(names, devices, network, monitors, scanner) if parser.parse_network(): if arg_parser.c: # Initialise instance of the userint.UserInterface() class userint = UserInterface(names, devices, network, monitors) userint.command_interface() else: if arg_parser.r: lang = u"el" else: lang = u"en" # Initialise an instance of the gui.Gui() class file_name = get_file_name(arg_parser.path) title = "Logic Simulator - " + file_name app = wx.App() gui = Gui(title, arg_parser.path, names, devices, network, monitors, lang) gui.Show(True) app.MainLoop()
def test_parse_network_incorrect_semantics6(): """Test the overall parse.py flow with a definition file with incorrect semantics.""" my_names = Names() my_scanner = Scanner("test_def_files/connectionportabsent.txt", my_names) my_devices = Devices(my_names) my_network = Network(my_names, my_devices) my_monitors = Monitors(my_names, my_devices, my_network) my_parser = Parser( my_names, my_devices, my_network, my_monitors, my_scanner) # Check that parse_network() returns False assert not my_parser.parse_network() # Check that there are no syntax errors assert len(my_parser.syntax_errors_list) == 0 # Check that 2 semantic errors are logged (one from actual error, one from # network.check_network()) assert len(my_parser.semantic_errors_list) == 2 assert my_parser.semantic_errors_list[0] == my_network.PORT_ABSENT
def test_parse_network_incorrect_semantics3(): """Test the overall parse.py flow with a definition file with incorrect semantics.""" my_names = Names() my_scanner = Scanner("test_def_files/devicequalifierpresent.txt", my_names) my_devices = Devices(my_names) my_network = Network(my_names, my_devices) my_monitors = Monitors(my_names, my_devices, my_network) my_parser = Parser( my_names, my_devices, my_network, my_monitors, my_scanner) # Check that parse_network() returns False assert not my_parser.parse_network() # Check that there are no syntax errors assert len(my_parser.syntax_errors_list) == 0 # Check that 2 semantic errors are raised (one from # network.check_network()) assert len(my_parser.semantic_errors_list) == 2 assert my_parser.semantic_errors_list[0] == my_devices.QUALIFIER_PRESENT
def main(arg_list): """Parse the command line options and arguments specified in arg_list. Run either the command line user interface, the graphical user interface, or display the usage message. """ usage_message = ( "Usage:\n" "Show help: logsim.py -h\n" "Command line user interface: logsim.py -c <file path>\n" "Graphical user interface: logsim.py <file path> or logsim.py") try: options, arguments = getopt.getopt(arg_list, "hc:") except getopt.GetoptError: print("Error: invalid command line arguments\n") print(usage_message) sys.exit() # Initialise instances of the four inner simulator classes names = Names() devices = Devices(names) network = Network(names, devices) monitors = Monitors(names, devices, network) #device = Device(self.names.lookup([names])) #names = None #devices = None #network = None #monitors = None for option, path in options: if option == "-h": # print the usage message print(usage_message) sys.exit() elif option == "-c": # use the command line user interface scanner = Scanner(path, names) parser = Parser(names, devices, network, monitors, scanner) if parser.parse_network(): # Initialise an instance of the userint.UserInterface() class userint = UserInterface(names, devices, network, monitors) userint.command_interface() if not options: # no option given, use the graphical user interface app = ab.BaseApp(redirect=False) error = ErrorFrame() if len(arguments) == 0: # wrong number of arguments # Initialise an instance of the gui.Gui() class path = None #print('len(arguments) = 0 if statement is accessed') scanner = Scanner(path, names) parser = Parser(names, devices, network, monitors, scanner) gui = Gui("LogicSim", path, names, devices, network, monitors) gui.Show(True) elif len(arguments) != 0 and len(arguments) != 1: print("Error: one or no file path required\n") print(usage_message) sys.exit() else: [path] = arguments scanner = Scanner(path, names) parser = Parser(names, devices, network, monitors, scanner) if parser.parse_network(): # Initialise an instance of the gui.Gui() class #import app_base as ab #app = ab.BaseApp(redirect=False) #app = wx.App() gui = Gui("LogicSim", path, names, devices, network, monitors) gui.Show(True) error.ShowModal() app.MainLoop()
def execute(self, command_manager): """Execute load command Return NO_ERROR, None if successful. """ self.command_manager = command_manager self.gui = command_manager.gui if self.path.split('.')[-1] == "defb": # File is an already built network with open(self.path, 'rb') as fp: try: monitors, devices, network, names, completed_cycles = \ pickle.load(fp) except pickle.UnpicklingError: return self.command_manager.INVALID_DEFINITION_FILE, None else: # File is a definition file names = Names() devices = Devices(names) network = Network(names, devices) monitors = Monitors(names, devices, network) scanner = Scanner(self.path, names) parser = Parser(names, devices, network, monitors, scanner) completed_cycles = 0 if not parser.parse_network(): errors = parser.error_to_gui self.gui.log_text('\n'.join(errors)) error_message = errors[0] return self.command_manager.INVALID_DEFINITION_FILE, \ error_message # Set new instances self.command_manager.monitors = monitors self.command_manager.devices = devices self.command_manager.network = network self.command_manager.names = names self.command_manager.undo_stack.clear() self.command_manager.redo_stack.clear() self.gui.monitors = self.command_manager.monitors self.gui.devices = self.command_manager.devices self.gui.network = self.command_manager.network self.gui.names = self.command_manager.names self.gui.switches = self.gui.devices.find_devices(devices.SWITCH) swithces_names = [ names.get_name_string(switch_id) for switch_id in self.gui.switches ] self.gui.switches_select.Clear() for switch_name in swithces_names: self.gui.switches_select.Append(switch_name) self.gui.canvas_2D.monitors = self.command_manager.monitors self.gui.canvas_3D.monitors = self.command_manager.monitors self.gui.monitors_select.Clear() for monitor_name in self.gui.monitors.get_signal_names()[0] + \ self.gui.monitors.get_signal_names()[1]: self.gui.monitors_select.Append(monitor_name) self.gui.canvas_2D.devices = self.command_manager.devices self.gui.canvas_3D.devices = self.command_manager.devices self.gui.update_cycles(completed_cycles) self.gui.switches_select.SetValue("") self.gui.switches_update_toggle() self.gui.monitors_select.SetValue("") self.gui.monitors_update_toggle() self.gui.log_text(_("Load file ") + self.path) self.gui.path = self.path self.gui.load_file_text_box.SetValue(self.path.split('/')[-1]) self.gui.canvas.render() return self.command_manager.NO_ERROR, None
class ParserTestCase: """Receive an input file (as strings), run the parser and check the error code.""" def __init__(self): self.testfile_name = 'testfile.txt' self.testfile = '' self.input_lines = [] self.expected_output = [] self.actual_output = [] self.parser = None self.ErrorTuple = namedtuple('ErrorTuple', 'error, linum, pos') def add_input_line(self, line): """Add a line to the testfile.""" if not isinstance(line, str): raise TypeError("the line to be added should be a str") self.input_lines.append(line) def add_expected_error(self, error_name, line_number, cursor_pos): """Add an expected error tuple to the output list.""" error_tuple = self.ErrorTuple(error_name, line_number, cursor_pos) self.expected_output.append(error_tuple) def make_testfile(self): """Write the input lines into the testfile.""" self.testfile = '\n'.join(self.input_lines) with open(self.testfile_name, 'w') as fout: fout.write(self.testfile) self.input_lines = [] def make_parser(self): """Initialise a parser for the testcase.""" if self.parser is not None: return None names = Names() scanner = Scanner(self.testfile_name, names) devices = Devices(names) network = Network(names, devices) monitors = Monitors(names, devices, network) self.parser = Parser(names, devices, network, monitors, scanner, test_mode=True) def execute(self): """Run parser to produce the output.""" self.make_testfile() self.make_parser() self.parser.parse_network() self.actual_output = self.parser.error_tuple_list os.remove(self.testfile_name) def passed(self): """Check whether the testcase passes.""" if self.actual_output == self.expected_output: return True print("\nInput file:") print(self.testfile + '\n') print("actual: [", end='') print(("\n" + " " * 9).join(map(str, self.actual_output)), end=']\n') print("expect: [", end='') print(("\n" + " " * 9).join(map(str, self.expected_output)), end=']\n') return False
def main(arg_list): """Parse the command line options and arguments specified in arg_list. Run either the command line user interface, the graphical user interface, or display the usage message. """ usage_message = ("Usage:\n" "Show help: logsim.py -h\n" "Command line user interface: logsim.py -c <file path>\n" "Graphical user interface: logsim.py <file path>") try: options, arguments = getopt.getopt(arg_list, "hc:") except getopt.GetoptError: print("Error: invalid command line arguments\n") print(usage_message) sys.exit() for option, path in options: if option == "-h": # print the usage message print(usage_message) sys.exit() elif option == "-c": # use the command line user interface names = Names() devices = Devices(names) network = Network(names, devices) monitors = Monitors(names, devices, network) scanner = Scanner(path, names) parser = Parser(names, devices, network, monitors, scanner) if parser.parse_network(): error_list = scanner.error_list for error in error_list: print(error) # Initialise an instance of the userint.UserInterface() class userint = UserInterface(names, devices, network, monitors) userint.command_interface() else: error_list = scanner.error_list for error in error_list: print(error) if not options: # no option given, use the graphical user interface path = None names = None devices = None network = None monitors = None filename = None app = wx.App() # Internationalisation builtins._ = wx.GetTranslation locale = wx.Locale() locale.Init(wx.LANGUAGE_DEFAULT) locale.AddCatalogLookupPathPrefix('./locale') locale.AddCatalog('messages') gui = Gui(_("பனி Logic Simulator"), path, names, devices, network, monitors, filename, True) gui.Show(True) gui.startup_load() app.MainLoop() while gui.load_new is True: path = gui.current_pathname filename = gui.current_filename names = Names() devices = Devices(names) network = Network(names, devices) monitors = Monitors(names, devices, network) scanner = Scanner(path, names) parser = Parser(names, devices, network, monitors, scanner) if parser.parse_network(): # Initialise an instance of the gui.Gui() class gui = Gui( _("பனி Logic Simulator - {}").format(filename), path, names, devices, network, monitors, filename) gui.Show(True) app.MainLoop()
def test_fulladder_semantic(request, names, devices, network, monitors): """Testing error-less file doesn't throw errors""" sc = Scanner(fulladderpath, names) parser = Parser(names, devices, network, monitors, sc) assert parser.parse_network() == True
def main(arg_list): """Parse the command line options and arguments specified in arg_list. Run either the command line user interface, the graphical user interface, or display the usage message. """ usage_message = ("Usage:\n" "Show help: logsim.py -h\n" "Command line user interface: logsim.py -c <file path>\n" "Graphical user interface: logsim.py <file path>") try: options, arguments = getopt.getopt(arg_list, "hc:") except getopt.GetoptError: print("Error: invalid command line arguments\n") print(usage_message) sys.exit() # Initialise instances of the four inner simulator classes names = Names() devices = Devices(names) network = Network(names, devices) monitors = Monitors(names, devices, network) for option, path in options: if option == "-h": # print the usage message print(usage_message) sys.exit() elif option == "-c": # use the command line user interface scanner = Scanner(path, names) parser = Parser(names, devices, network, monitors, scanner) if parser.parse_network(): # Initialise an instance of the userint.UserInterface() class userint = UserInterface(names, devices, network, monitors) userint.command_interface() if not options: # no option given, use the graphical user interface if len(arguments) != 1: # wrong number of arguments print("Error: one file path required\n") print(usage_message) sys.exit() [path] = arguments scanner = Scanner(path, names) parser = Parser(names, devices, network, monitors, scanner) if parser.parse_network(): # Initialise an instance of the gui.Gui() class app = wx.App() gui = Gui("Logic Simulator", path, names, devices, network, monitors) gui.Show(True) app.MainLoop() while hasattr(gui, 'edit_restart'): # GUI terminated and set the edit_restart flag. # We open the file in an editor to allow the user # to change the file, then restart the GUI. del app run_editor(path) # Re-initialise everything names = Names() devices = Devices(names) network = Network(names, devices) monitors = Monitors(names, devices, network) scanner = Scanner(path, names) parser = Parser(names, devices, network, monitors, scanner) if parser.parse_network(): app = wx.App() gui = Gui("Logic Simulator", path, names, devices, network, monitors) gui.Show(True) app.MainLoop() else: # Parser error messages would be enough, no need to # re-inform users of parser failure. break
import scanner from parse import Parser from names import Names from network import Network from monitors import Monitors import devices import sys my_names=Names() my_scanner=scanner.Scanner(sys.argv[1], my_names) # my_scanner=scanner.Scanner("connectioninputconnected.txt", my_names) my_devices=devices.Devices(my_names) my_network=Network(my_names, my_devices) my_monitors=Monitors(my_names, my_devices, my_network) my_parser=Parser(my_names, my_devices, my_network, my_monitors, my_scanner) print(my_parser.parse_network())
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. Contains About, Open and Close. on_spin_run(self, event): Event handler for when the user changes the spin control value for number of cycles to run. on_spin_run(self, event): Event handler for when the user changes the spin control value for number of cycles to continue. on_run_button(self, event): Event handler for when the user clicks the run button. on_run_button(self, event): Event handler for when the user clicks the continue button. on_switch_button(self, event): Event handler for when the user clicks the switch button. on_monitor_button(self, event): Event handler for when the user clicks the monitor button. on_restore_view_button(self, event): Event handler for when the user clicks the restore view button. Resets the canvas to its original zoom level and orientation on_three_d_button(self, event): Event handler for when the user clicks the Enable 3D/Disable 3D button. on_quit_button(self, event): Event handler for when the user clicks the Quit button run_command(self): Runs the simulation from scratch. continue_command(self): Continues a previously run simulation. run_network(self, cycles): Runs the network for the specified number of simulation cycles. monitor_command(self): Sets the specified monitor. zap_command(self): Removes the specified monitor. draw_signals(self): Obtain the monitored outputs as arrays to be drawn onto the canvas load_file(self): Loads the selected definition file and reinitialises the GUI parameters. print(self): Print messages onto the status message box """ def __init__(self, title, path, names, devices, network, monitors, app, logging=False): """Initialise widgets and layout.""" super().__init__(parent=None, title=title, size=(1200, 600)) # Replicate userint.py's functionality self.names = names self.devices = devices self.monitors = monitors self.network = network self.path = path self.cycles_completed = 0 # number of simulation cycles completed # Configure the file menu fileMenu = wx.Menu() menuBar = wx.MenuBar() fileMenu.Append(wx.ID_ABOUT, _(u"&About")) fileMenu.Append(wx.ID_OPEN, _(u"&Open")) fileMenu.Append(wx.ID_EXIT, _(u"&Exit")) menuBar.Append(fileMenu, _(u"&File")) self.SetMenuBar(menuBar) # Canvas for drawing signals self.canvas = MyGLCanvas(self, self.devices, self.monitors) # Configure the widgets self.text_run_cycle = wx.StaticText(self, wx.ID_ANY, _(u"Cycles")) self.text_continue_cycle = wx.StaticText(self, wx.ID_ANY, _(u"Cycles")) self.spin_run = wx.SpinCtrl(self, wx.ID_ANY, "10") self.spin_continue = wx.SpinCtrl(self, wx.ID_ANY, "10") self.run_button = wx.Button(self, wx.ID_ANY, _(u"Run")) self.continue_button = wx.Button(self, wx.ID_ANY, _(u"Continue")) self.continue_button.Enable(False) self.restore_view_button = wx.Button(self, wx.ID_ANY, _(u"Restore view")) self.threeD_button = wx.Button(self, wx.ID_ANY, _(u"Enable 3D")) self.quit_button = wx.Button(self, wx.ID_ANY, _(u"Quit")) self.switch_button = wx.Button(self, wx.ID_ANY, _(u"Set Switches On/Off")) self.monitor_button = wx.Button(self, wx.ID_ANY, _(u"Set/Zap Monitors")) self.status_text = wx.StaticText(self, wx.ID_ANY, _(u"Status Messages:")) self.message = "" self.message_box = wx.TextCtrl(self, wx.ID_ANY, self.message, style=wx.TE_MULTILINE | wx.TE_READONLY) self.print(self.path) # Bind events to widgets self.Bind(wx.EVT_MENU, self.on_menu) self.spin_run.Bind(wx.EVT_SPINCTRL, self.on_spin_run) self.spin_continue.Bind(wx.EVT_SPINCTRL, self.on_spin_continue) self.run_button.Bind(wx.EVT_BUTTON, self.on_run_button) self.continue_button.Bind(wx.EVT_BUTTON, self.on_continue_button) self.switch_button.Bind(wx.EVT_BUTTON, self.on_switch_button) self.monitor_button.Bind(wx.EVT_BUTTON, self.on_monitor_button) self.restore_view_button.Bind(wx.EVT_BUTTON, self.on_restore_view_button) self.threeD_button.Bind(wx.EVT_BUTTON, self.on_three_d_button) self.quit_button.Bind(wx.EVT_BUTTON, self.on_quit_button) # Configure sizers for layout self.main_sizer = wx.BoxSizer(wx.HORIZONTAL) self.side_sizer = wx.BoxSizer(wx.VERTICAL) simulate_run_sizer = wx.BoxSizer(wx.HORIZONTAL) simulate_continue_sizer = wx.BoxSizer(wx.HORIZONTAL) button_sizer = wx.BoxSizer(wx.VERTICAL) text_sizer = wx.BoxSizer(wx.VERTICAL) self.main_sizer.Add(self.canvas, 5, wx.EXPAND | wx.ALL, 5) self.main_sizer.Add(self.side_sizer, 1, wx.ALL, 5) self.side_sizer.Add(simulate_run_sizer, 0, wx.TOP, 5) self.side_sizer.Add(simulate_continue_sizer, 0, wx.TOP, 5) self.side_sizer.Add(button_sizer, 1, wx.TOP, 5) self.side_sizer.Add(text_sizer, 5, wx.ALIGN_CENTRE | wx.EXPAND, 5) simulate_run_sizer.Add(self.run_button, 2, wx.ALL, 5) simulate_run_sizer.Add(self.spin_run, 2, wx.ALL, 5) simulate_run_sizer.Add(self.text_run_cycle, 1, wx.ALL, 5) simulate_continue_sizer.Add(self.continue_button, 2, wx.ALL, 5) simulate_continue_sizer.Add(self.spin_continue, 2, wx.ALL, 5) simulate_continue_sizer.Add(self.text_continue_cycle, 1, wx.ALL, 5) button_sizer.Add(self.switch_button, 1, wx.ALL, 5) button_sizer.Add(self.monitor_button, 1, wx.ALL, 5) button_sizer.Add(self.restore_view_button, 1, wx.ALL, 5) button_sizer.Add(self.threeD_button, 1, wx.ALL, 5) button_sizer.Add(self.quit_button, 1, wx.ALL, 5) text_sizer.Add(0, 0, 1) text_sizer.Add(self.status_text, 0, wx.ALL, 5) text_sizer.Add(self.message_box, 3, wx.EXPAND, 5) self.SetSizeHints(800, 400) self.SetSizer(self.main_sizer) # Configure default/initial values self.spin_value_run = "10" # must be same as initial display on spin widget self.spin_value_continue = "10" self.switch_selections = [] self.list_of_monitors = [] self.monitor_selections = [] self.monitor = [] # sets whether the list of monitors in system can be changed self.toggle_list_of_monitors = 1 self.toggle_run = False self.threeD_toggle = 0 # enables printing to console/terminal self.logging = False 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) if Id == wx.ID_ABOUT: wx.MessageBox(_(u"Logic Simulator\nCreated by Team 10\n2019"), _(u"About Logsim"), wx.ICON_INFORMATION | wx.OK) if Id == wx.ID_OPEN: self.load_file() def on_spin_run(self, event): """Handle the event when the user changes the spin control value.""" self.spin_value_run = self.spin_run.GetValue() text = "".join( [_(u"New spin control value: "), str(self.spin_value_run)]) self.print(text) def on_spin_continue(self, event): """Handle the event when the user changes the spin control value.""" self.spin_value_continue = self.spin_continue.GetValue() text = "".join( [_(u"New spin control value: "), str(self.spin_value_continue)]) self.print(text) def on_run_button(self, event): """Handle the event when the user clicks the run button.""" self.print(_(u"Run button pressed.")) if self.toggle_run is False: self.toggle_run = True self.continue_button.Enable(True) self.run_command() def on_continue_button(self, event): """Handle the event when the user clicks the continue button.""" self.print(_(u"Continue button pressed.")) self.continue_command() def on_switch_button(self, event): """Handle the event when the user clicks the switch button.""" self.print(_(u"Setting switches")) switch_list = [] switch_id = self.devices.find_devices(self.devices.SWITCH) for i in range(len(switch_id)): k = self.names.get_name_string(switch_id[i]) if k is not None: switch_list.append(k) switch_dialog = wx.MultiChoiceDialog(self, _(u"Set switches On/Off"), _(u"Set switches"), switch_list, style=wx.OK) switch_dialog.SetSelections(self.switch_selections) if switch_dialog.ShowModal() == wx.ID_OK: self.switch_selections = switch_dialog.GetSelections() switch_state = [0] * len(switch_list) for i in range(len(self.switch_selections)): switch_state[self.switch_selections[i]] = 1 for i in range(len(switch_list)): state = self.devices.set_switch(switch_id[i], switch_state[i]) if self.logging is True: if state: print("Successfully set switch.") else: print("Error! Invalid switch.") def on_monitor_button(self, event): """Handle the event when the user clicks the monitor button.""" text = _(u"Setting/Zapping which signals to monitor") self.canvas.render(text) monitor_id_list = list(self.monitors.monitors_dictionary) # append devices with outputs that are not defined as monitorable in definition file. for device_id in self.devices.find_devices(): device = self.devices.get_device(device_id) for output_id in device.outputs: if (device_id, output_id) not in self.monitors.monitors_dictionary: monitor_id_list.append((device_id, output_id)) for i in range(len(monitor_id_list)): if monitor_id_list[i][0] is None: return None elif monitor_id_list[i][1] is not None: port_id = monitor_id_list[i][1] if port_id is None: return None else: port_id = None string_name = self.names.get_name_string(monitor_id_list[i][0]) if port_id is not None: port_name = self.names.get_name_string(monitor_id_list[i][1]) else: port_name = "" if string_name is not None: # only append to this list the first time button is clicked if self.toggle_list_of_monitors == 1: self.list_of_monitors.append( str(string_name) + "." + str(port_name)) self.monitor.append([monitor_id_list[i][0], port_id]) # prevent self.list_of_monitors and self.monitors from being overwritten when zapped self.toggle_list_of_monitors = 0 monitor_dialog = wx.MultiChoiceDialog( self, _(u"Set/Zap which signals to monitor"), _(u"Set/Zap signals"), self.list_of_monitors, style=wx.OK) monitor_dialog.SetSelections(self.monitor_selections) signal_state = [0] * len(self.list_of_monitors) if monitor_dialog.ShowModal() == wx.ID_OK: self.monitor_selections = monitor_dialog.GetSelections() for i in range(len(self.monitor_selections)): signal_state[self.monitor_selections[i]] = 1 if sum(signal_state) == 0: self.print(_(u"All monitors are zapped.")) for i in range(len(signal_state)): if signal_state[i] == 1: self.monitor_command(self.monitor[i]) elif signal_state[i] == 0: self.zap_command(self.monitor[i]) else: raise ValueError def on_restore_view_button(self, event): """" Handles the event when restore view button is clicked. Resets the canvas to its original zoom level and orientation""" self.canvas.reset_view() def on_three_d_button(self, event): """ Handles the event for when the user clicks the Enable 3D/Disable 3D button. renders the signals in 2D or 3D. """ if self.threeD_toggle: self.threeD_button.SetLabel(_(u"Enable 3D")) self.threeD_toggle = False self.canvas.toggle_3D = False self.canvas.init = False self.canvas.Refresh() self.canvas.reset_view() else: self.threeD_button.SetLabel(_(u"Disable 3D")) self.threeD_toggle = True self.canvas.toggle_3D = True self.canvas.init = False self.canvas.Refresh() self.canvas.reset_view() def on_quit_button(self, event): """ Handles the event when Quit button is clicked. Ends the program and closes the window.""" self.Close() # ported from userint.py and changed slightly def run_command(self): """Run the simulation from scratch.""" self.cycles_completed = 0 cycles = int(self.spin_value_run) if cycles is not None: # if the number of cycles provided is valid self.monitors.reset_monitors() self.print("".join( [_(u"Running for "), str(cycles), _(u" cycles")])) self.devices.cold_startup() if self.run_network(cycles): self.cycles_completed += cycles self.draw_signals() def continue_command(self): """Continue a previously run simulation.""" cycles = int(self.spin_value_continue) if cycles is not None: # if the number of cycles provided is valid if self.cycles_completed == 0: self.print(_(u"Error! Nothing to continue. Run first.")) elif self.run_network(cycles): self.cycles_completed += cycles self.print(" ".join([ _(u"Continuing for"), str(cycles), _(u"cycles."), _(u"Total:"), str(self.cycles_completed) ]), append=True) self.draw_signals() def run_network(self, cycles): """Run the network for the specified number of simulation cycles. Return True if successful. """ for _ in range(cycles): if self.network.execute_network(): self.monitors.record_signals() else: self.print(_(u"Error! Network oscillating.")) return False # self.monitors.display_signals() return True def monitor_command(self, monitor): """Set the specified monitor.""" if monitor is not None: [device, port] = monitor monitor_error = self.monitors.make_monitor(device, port, self.cycles_completed) if self.logging: if monitor_error == self.monitors.NO_ERROR or self.monitors.MONITOR_PRESENT: print("Successfully made monitor.") else: print("Error! Could not make monitor.") def zap_command(self, monitor): """Remove the specified monitor.""" if monitor is not None: [device, port] = monitor if self.monitors.get_monitor_signal(device, port) is not None: if self.monitors.remove_monitor(device, port): if self.logging: print("Successfully zapped monitor") else: if self.logging: print("Error! Could not zap monitor.") else: if self.logging: print("Monitor was already zapped") def draw_signals(self): """"Obtain the monitored outputs as arrays to be drawn onto the canvas""" margin = self.monitors.get_margin() text_array = [] signal_array = [] for device_id, output_id in self.monitors.monitors_dictionary: monitor_name = self.devices.get_signal_name(device_id, output_id) name_length = len(monitor_name) signal_list = self.monitors.monitors_dictionary[(device_id, output_id)] text = monitor_name + (margin - name_length) * " " signal_array.append(signal_list) text_array.append(text) self.canvas.draw_signals(text_array, signal_array) def load_file(self): """Loads the selected definition file and reinitialises the GUI parameters. """ with wx.FileDialog(self, "Open test definition file", wildcard="TXT files (*.txt)|*.txt", style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog: if fileDialog.ShowModal() == wx.ID_CANCEL: return # the user changed their mind # Proceed loading the file chosen by the user self.path = fileDialog.GetPath() try: 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.path, self.names) self.parser = Parser(self.names, self.devices, self.network, self.monitors, self.scanner) self.parser.parse_network() self.cycles_completed = 0 # number of simulation cycles completed self.canvas.devices = self.devices # Configure default/initial values self.switch_selections = [] self.list_of_monitors = [] self.monitor_selections = [] self.monitor = [] # sets whether the list of monitors in system can be changed self.toggle_list_of_monitors = 1 self.toggle_run = False except IOError: self.print(_(u"Cannot open file '%s'.") % self.path) def print(self, text, append=False): """Print messages onto the status message box""" if append: self.message_box.WriteText(text) else: self.message_box.Clear() self.message_box.WriteText(text)
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()
def main(arg_list): """Parse the command line options and arguments specified in arg_list. Run either the command line user interface, the graphical user interface, or display the usage message. """ print('arg_list', arg_list) usage_message = ("Usage:\n" "Show help: logsim.py -h\n" "Command line user interface: logsim.py -c <file path>\n" "Graphical user interface: logsim.py <file path>") try: options, arguments = getopt.getopt(arg_list, "hc:") except getopt.GetoptError: print(_(u"Error: invalid command line arguments\n")) print(usage_message) sys.exit() # Initialise instances of the four inner simulator classes names = Names() devices = Devices(names) network = Network(names, devices) monitors = Monitors(names, devices, network) for option, path in options: if option == "-h": # print the usage message print(usage_message) sys.exit() elif option == "-c": # use the command line user interface scanner = Scanner(path, names) parser = Parser(names, devices, network, monitors, scanner) if parser.parse_network(): # Initialise an instance of the userint.UserInterface() class userint = UserInterface(names, devices, network, monitors) userint.command_interface() if not options: # no option given, use the graphical user interface if len(arguments) != 1: if len(arguments) == 2: # wrong number of arguments [path] = [arguments[0]] else: print(_(u"Error: one file path required\./logsim.yn")) print(usage_message) sys.exit() else: [path] = arguments scanner = Scanner(path, names) parser = Parser(names, devices, network, monitors, scanner) if parser.parse_network(): # Initialise an instance of the gui.Gui() class app = ab.BaseApp(redirect=False) language = LanguageGui() language.Show(True) app.MainLoop() from gui_3D import lang app = ab.BaseApp(redirect=False) if lang == "Chinese": app.updateLanguage(u"zh_CN") gui = Gui(_(u"Logic Simulator"), path, names, devices, network, monitors, app) gui.Show(True) gui.load_file() app.MainLoop()
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. path: Path of circuit specification file names: instance of the names.Names() class. devices: instance of the devices.Devices() class. monitors: instance of the monitors.Monitors() class. network: instance of the network.Network() class. lang: Language specification. Public methods -------------- on_menu(self, event): Event handler for the file menu. on_run_button(self, event): Event handler for when the user clicks the run button. on_continue_button(self, event): Event handler for when the user clicks the continue button. on_config_button(self, event): Event handler for when user clicks apply button. on_show(self, event): Event handler for when user clicks show button. on_remove(self, event): Event handler for when user clicks remove button. on_conf_select(self, event): Update current conf var shown in gui when new config device selected. on_open(self): Handles event of loading a new circuit file in GUI. on_2D(self, event): Handles event when user clicks 2D view button. on_3D(self, event):Handles event when user clicks 3D view button. on_reset(self, event): Handles event when user clicks reset button. updateLanguage(self, lang): Update language shown in GUI. Private methods --------------- _re_render(self): Rerenders traces and labels after any action. _gen_lists(self): Set self.outputs, self.configurable, self.monitored. _run_network(self, cycles, warm_flag): Run network in response to some change. _verify_number(self, num, lower_bound, upper_bound): Validate config var. _regen_monitored(self): Update monitor display in GUI. _get_file_name(self, path_name): Extracts file name from path. _update_labels(self): Updates widget labels with selected language. """ def __init__(self, title, path, names, devices, network, monitors, lang=u"en"): """Initialise widgets and layout.""" super().__init__(parent=None, title=title, size=(928, 700)) # Set locale self.locale = None wx.Locale.AddCatalogLookupPathPrefix('locale') self.updateLanguage(lang) # Configure the file menu self.fileMenu = wx.Menu() self.fileMenu.Append(wx.ID_ABOUT, _(u"&About")) self.fileMenu.Append(wx.ID_EXIT, _(u"&Exit")) self.fileMenu.Append(wx.ID_OPEN, _(u"&Open")) # Configure the language menu self.langMenu = wx.Menu() self.id_ro = wx.NewId() self.id_en = wx.NewId() self.langMenu.Append(self.id_en, _(u"&English")) self.langMenu.Append(self.id_ro, _(u"&Română")) # Configure view menu self.viewMenu = wx.Menu() self.viewMenu.Append(wx.ID_NO, _(u"2D Display")) self.viewMenu.Append(wx.ID_YES, _(u"3D Display")) self.currentview = wx.ID_NO # Create menu bar self.menuBar = wx.MenuBar() self.menuBar.Append(self.fileMenu, _(u"&File")) self.menuBar.Append(self.viewMenu, _(u"View")) self.menuBar.Append(self.langMenu, _(u"Language")) self.SetMenuBar(self.menuBar) # number of simulation cycles completed self.cycles_completed = 0 # current file path self.path = path self.devices = devices self.monitors = monitors self.names = names self.network = network self.title = title # Creating 2D arrays with names and IDs of devices/signals # for all devices, configurable devices, and monitored signals. # self.monitored can change but self.outputs and # self.configurable can't change. [self.outputs, self.configurable, self.monitored] = self._gen_lists() # Configure the widgets/canvas: # Canvas for drawing signals self.my3D_canvas = My3DGLCanvas(self, devices, monitors, names) self.traces_canvas = MyTraces(self, devices, monitors, names) self.labels_canvas = MyLabels(self, devices, monitors, names) self.axis_canvas = MyAxis(self, self.cycles_completed) self.axis_title = wx.StaticText(self, wx.ID_ANY, "") # Connecting canva to share attributes. self.traces_canvas.labels = self.labels_canvas self.traces_canvas.axis = self.axis_canvas self.labels_canvas.traces = self.traces_canvas self.labels_canvas.SetInitialSize(wx.Size(110, -1)) # Widget for error reporting/user feedback self.act_log = wx.TextCtrl(self, wx.ID_ANY, style=(wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_LEFT | wx.TE_BESTWRAP), name="Activity Log Panel") font1 = wx.Font(10, wx.TELETYPE, wx.NORMAL, wx.NORMAL) self.act_log.SetFont(font1) # Widgets to control number of cycles. self.cycles = wx.SpinCtrl(self, wx.ID_ANY, "10") self.run_button = wx.Button(self, wx.ID_ANY, _("Run")) self.continue_button = wx.Button(self, wx.ID_ANY, _("Continue")) # Widgets to control configuration of clocks and switches. self.config_button = wx.Button(self, wx.ID_ANY, _("Apply")) self.config_list = wx.ComboBox(self, wx.ID_ANY, "", choices=self.configurable[0], style=(wx.CB_READONLY)) # Deal with situation where circuit has no configurable devices. if len(self.configurable[0]) > 0: # Select first device in list as default self.config_list.SetSelection(0) self.config_var = wx.SpinCtrl(self, wx.ID_ANY, str(self.configurable[2][0])) else: # Initialise widget but disable button. self.config_var = wx.SpinCtrl(self, wx.ID_ANY) self.config_button.Disable() # Widgets to display/select all possible outputs. self.outs = wx.ListBox(self, wx.ID_ANY, style=(wx.LB_MULTIPLE | wx.LB_NEEDED_SB), choices=self.outputs[0]) self.add_outs = wx.Button(self, wx.ID_ANY, _("Show")) # Widgets to display/remove all current monitored outputs. self.mons = wx.ListBox(self, wx.ID_ANY, style=(wx.LB_MULTIPLE | wx.LB_NEEDED_SB), choices=self.monitored[0]) self.dele_mons = wx.Button(self, wx.ID_ANY, _("Remove")) # Toggle view and reload file buttons. self.view2D = wx.Button(self, wx.ID_ANY, _("2D View")) self.view3D = wx.Button(self, wx.ID_ANY, _("3D View")) self.reset = wx.Button(self, wx.ID_ANY, _("Reset View")) self.reload = wx.Button(self, wx.ID_ANY, _("Reload File")) # Styling text of titles in widgets self.title_font = wx.Font(12, wx.MODERN, wx.NORMAL, wx.BOLD) # self.cyc_title.SetFont(self.title_font) # self.config_title.SetFont(self.title_font) # self.out_title.SetFont(self.title_font) # self.mon_title.SetFont(self.title_font) # self.uf_title.SetFont(self.title_font) self.axis_title.SetFont(self.title_font) # Bind events to widgets self.Bind(wx.EVT_MENU, self.on_menu) self.run_button.Bind(wx.EVT_BUTTON, self.on_run_button) self.continue_button.Bind(wx.EVT_BUTTON, self.on_continue_button) self.config_button.Bind(wx.EVT_BUTTON, self.on_config_button) self.add_outs.Bind(wx.EVT_BUTTON, self.on_show) self.dele_mons.Bind(wx.EVT_BUTTON, self.on_remove) self.config_list.Bind(wx.EVT_COMBOBOX, self.on_conf_select) self.view2D.Bind(wx.EVT_BUTTON, self.on_2D) self.view3D.Bind(wx.EVT_BUTTON, self.on_3D) self.reset.Bind(wx.EVT_BUTTON, self.on_reset) self.reload.Bind(wx.EVT_BUTTON, self.on_reload) # Root level app section splitter. self.main_sizer = wx.BoxSizer(wx.HORIZONTAL) canv = wx.StaticBox(self, wx.ID_ANY) self.canv_maj_sizer = wx.StaticBoxSizer(canv, wx.VERTICAL) # Sizer to horizontally align labels canvas and trace # canvas. canv_sizer = wx.BoxSizer(wx.HORIZONTAL) # Sizer to horizontally align axis title and axis. canv_axis_sizer = wx.BoxSizer(wx.HORIZONTAL) # Sizer to vertically arrange 2D/3D canvas and user # feedback section. self.canv_feed_sizer = wx.BoxSizer(wx.VERTICAL) # Sizer to organise sidebar elements vertically. side_sizer = wx.BoxSizer(wx.VERTICAL) # Sizers and Boxes to organise sidebar elements. # StaticBoxes are parents of StaticBoxSizers. # Horizontal BoxSizers used to arrange side by side # elements in a StaticBox. self.uf = wx.StaticBox(self, wx.ID_ANY, _("Activity Log")) self.cyc = wx.StaticBox(self, wx.ID_ANY, _("Cycles")) self.conf = wx.StaticBox(self, wx.ID_ANY, _("Configure Devices")) self.out = wx.StaticBox(self, wx.ID_ANY, _("Outputs")) self.mon = wx.StaticBox(self, wx.ID_ANY, _("Monitored Outputs")) self.ax = wx.StaticBox(self, wx.ID_ANY) self.tog = wx.StaticBox(self, wx.ID_ANY) # To align with labels canvas self.ax.SetMinSize(wx.Size(110, 20)) uf_sizer = wx.StaticBoxSizer(self.uf, wx.VERTICAL) cyc_sizer = wx.StaticBoxSizer(self.cyc, wx.VERTICAL) cyc_but_sizer = wx.BoxSizer(wx.HORIZONTAL) config_sizer = wx.StaticBoxSizer(self.conf, wx.VERTICAL) config_but_sizer = wx.BoxSizer(wx.HORIZONTAL) out_sizer = wx.StaticBoxSizer(self.out, wx.VERTICAL) mon_sizer = wx.StaticBoxSizer(self.mon, wx.VERTICAL) tog_sizer = wx.StaticBoxSizer(self.tog, wx.VERTICAL) dim_but_sizer = wx.BoxSizer(wx.HORIZONTAL) reset_but_sizer = wx.BoxSizer(wx.HORIZONTAL) ax_title = wx.StaticBoxSizer(self.ax, wx.HORIZONTAL) # Add widgets to relevent sizers/boxes. Add sub-sizers # to parent sizers. 'Add' method allows behaviour modes # of widgets to be specified using wx Stock Constants. # Order in which widgets are added to sizers are determined # by order in which Add methods called. # Box.Add(control, proportion, flag, border) # Left and Right sections of GUI self.main_sizer.Add(self.canv_feed_sizer, 4, wx.EXPAND | wx.ALL, 5) self.main_sizer.Add(side_sizer, 0, wx.ALL | wx.EXPAND, 5) # Canva and activity log self.canv_feed_sizer.Add(self.canv_maj_sizer, 4, wx.EXPAND | wx.ALL, 5) self.canv_feed_sizer.Add(self.my3D_canvas, 4, wx.EXPAND | wx.ALL, 5) self.canv_feed_sizer.Add(uf_sizer, 1, wx.EXPAND | wx.ALL, 1) self.canv_feed_sizer.Hide(1) # Trace/Labels sections and axis section self.canv_maj_sizer.Add(canv_sizer, 1, wx.EXPAND) self.canv_maj_sizer.Add(canv_axis_sizer, 0, wx.EXPAND) # Labels canvas and traces canvas canv_sizer.Add(self.labels_canvas, 0, wx.ALL | wx.EXPAND, 5) canv_sizer.Add(self.traces_canvas, 6, wx.EXPAND | wx.ALL, 5) # Axis title and axis. canv_axis_sizer.Add(ax_title, 0, wx.EXPAND | wx.ALL, 5) canv_axis_sizer.Add(self.axis_canvas, 6, wx.EXPAND | wx.ALL, 5) # To size and align axis title container. ax_title.Add(self.axis_title, 1, wx.EXPAND | wx.ALIGN_CENTER) # Activity log title and output box. # uf_sizer.Add(self.uf_title, 0, wx.LEFT, 5) uf_sizer.Add(self.act_log, 1, wx.EXPAND | wx.ALL, 5) # Right sidebar. side_sizer.Add(cyc_sizer, 0, wx.ALIGN_CENTER | wx.EXPAND) # Cycles side_sizer.Add(config_sizer, 0, wx.ALIGN_CENTER | wx.EXPAND) # Configure devices side_sizer.Add(out_sizer, 1, wx.ALIGN_CENTER | wx.EXPAND) # Outputs side_sizer.Add(mon_sizer, 1, wx.ALIGN_CENTER | wx.EXPAND) # Monitored Outputs side_sizer.Add(tog_sizer, 0, wx.ALIGN_CENTER | wx.EXPAND) # Toggle Buttons # Cycles # cyc_sizer.Add(self.cyc_title, 0, wx.BOTTOM, 10) # Title cyc_sizer.Add(self.cycles, 0, wx.EXPAND | wx.ALL, 10) # Textbox cyc_sizer.Add(cyc_but_sizer, 0, wx.ALIGN_CENTER, 1) # Buttons # Organising run and continue button horizontally cyc_but_sizer.Add(self.run_button, 1, wx.ALL, 5) # Run button cyc_but_sizer.Add(self.continue_button, 1, wx.ALL, 5) # Continue Button # Configure devices dropdown list # config_sizer.Add(self.config_title, 0, wx.BOTTOM, 10) # Title # Second Interim Report Feedback Update: Swap widget order. config_sizer.Add(self.config_list, 1, wx.EXPAND | wx.ALL, 16) # Dropdown List config_sizer.Add(config_but_sizer, 0, wx.ALL | wx.ALIGN_CENTER, 5) # Apply button # and textbox # Organising apply button and text box horizontally config_but_sizer.Add(self.config_button, 0, wx.RIGHT, 6) # Apply Button config_but_sizer.Add(self.config_var, 1, wx.BOTTOM | wx.LEFT, 5) # Textbox # Output list and add monitor button # out_sizer.Add(self.out_title, 0, wx.BOTTOM, 10) # Title out_sizer.Add(self.outs, 1, wx.ALL | wx.EXPAND, 10) # List out_sizer.Add(self.add_outs, 0, wx.ALIGN_CENTER | wx.ALL, 5) # Button # Monitor list and delete button. # mon_sizer.Add(self.mon_title, 0, wx.BOTTOM, 10) # Title mon_sizer.Add(self.mons, 1, wx.ALL | wx.EXPAND, 10) # List mon_sizer.Add(self.dele_mons, 0, wx.ALIGN_CENTER | wx.ALL, 5) # Button # 2D, 3D, Reset, Reload Buttons tog_sizer.Add(dim_but_sizer, 0, wx.ALIGN_CENTER, 1) tog_sizer.Add(reset_but_sizer, 0, wx.ALIGN_CENTER, 1) # 2D, 3D, View Buttons dim_but_sizer.Add(self.view2D, 1, wx.ALL, 5) dim_but_sizer.Add(self.view3D, 1, wx.ALL, 5) # reset, reload, View Buttons reset_but_sizer.Add(self.reset, 1, wx.ALL, 5) reset_but_sizer.Add(self.reload, 1, wx.ALL, 5) # Set min size of window. self.SetMinSize(wx.Size(1100, 900)) self.SetInitialSize(wx.Size(1100, 900)) # Set main_sizer as the main sizer of the wx.Frame self.SetSizer(self.main_sizer) def _re_render(self): """ Private function that updates monitors and devices in classes in canva.py and then calls its render method to replot traces taking into account updates. """ print('rendering') if self.currentview == wx.ID_NO: self.traces_canvas.SetColour("grey") self.traces_canvas.monitors = self.monitors self.traces_canvas.devices = self.devices self.traces_canvas.render() self.labels_canvas.monitors = self.monitors self.labels_canvas.devices = self.devices self.labels_canvas.render() self.axis_canvas.cycles = self.cycles_completed self.axis_canvas.render() if self.currentview == wx.ID_YES: self.my3D_canvas.monitors = self.monitors self.my3D_canvas.devices = self.devices self.my3D_canvas.render() def _gen_lists(self): """ Create arrays of two sub-arrays for: all possible outputs, configurable outputs and monitored outputs. The first sub-array stores name strings of outputs and the second sub_array stores the device_id and output_id of the corresponding output. """ outputs = [[] for i in range(2)] configurable = [[] for i in range(3)] monitored = [[] for i in range(2)] for device in self.devices.devices_list: # Get device name. dev_name = self.names.get_name_string(device.device_id) # Append output name to device name if output name explicit. if device.device_kind == self.devices.D_TYPE: for output in device.outputs: out_name = self.names.get_name_string(output) outputs[0].append(dev_name + "." + out_name) outputs[1].append((device.device_id, output)) else: outputs[0].append(dev_name) outputs[1].append((device.device_id, None)) # Recording devices that are clocks or switches. if (device.device_kind == self.devices.CLOCK or device.device_kind == self.devices.SWITCH): configurable[0].append(dev_name) configurable[1].append(device.device_id) if device.device_kind == self.devices.CLOCK: configurable[2].append(device.clock_half_period) if device.device_kind == self.devices.SWITCH: configurable[2].append(device.switch_state) # Recording monitored outputs. for monitor in self.monitors.monitors_dictionary: dev_name = self.names.get_name_string(monitor[0]) out_name = self.names.get_name_string(monitor[1]) if out_name is not None: monitored[0].append(dev_name + "." + out_name) else: monitored[0].append(dev_name) monitored[1].append(monitor) return [outputs, configurable, monitored] def _regen_monitored(self, monitor): """ Private method to regenerate monitored outputs list in GUI. """ dev_name = self.names.get_name_string(monitor[0]) out_name = self.names.get_name_string(monitor[1]) if out_name is not None: # For DTYPE devices self.monitored[0].append(dev_name + "." + out_name) self.monitored[1].append(monitor) # Update Monitored Outputs List in GUI self.mons.InsertItems([dev_name + "." + out_name], self.mons.GetCount()) else: self.monitored[0].append(dev_name) self.monitored[1].append(monitor) # Update Monitored Outputs List in GUI self.mons.InsertItems([dev_name], self.mons.GetCount()) def _run_network(self, cycles, warm_flag=False): """ Run the network for the specified number of simulation cycles. Return True if successful. """ # If running network only to update traces. if warm_flag: self.monitors.reset_monitors() # Clear previously stored signals for _ in range(cycles): # Network executed and signal recorded one cycle # at a time. if self.network.execute_network(): self.monitors.record_signals() else: print("Error! Network oscillating.") self.act_log.AppendText( _("***Error: Network oscillating.") + "\n") return False # Displays results in command line interface. self.monitors.display_signals() return True def _verify_number(self, num, lower_bound, upper_bound): """ Return the current number if it is within provided bounds. Return None if no number is provided or if it falls outside the valid range. """ if upper_bound is not None: if num > upper_bound: print("Number out of range.") return None if lower_bound is not None: if num < lower_bound: print("Number out of range.") return None return num def on_conf_select(self, event): """ Handle event when user selects a configurable device and accordingly update default value in config var input box to current device config var value """ config_var = self.configurable[2][ self.config_list.GetCurrentSelection()] self.config_var.SetValue(config_var) def on_menu(self, event): # TODO DAVID """Handle the event when the user selects a menu item.""" Id = event.GetId() if Id == wx.ID_EXIT: self.Close(True) if Id == wx.ID_ABOUT: wx.MessageBox( _("Logic Simulator\nCreated by ") + "David Almasan, " + "Vatsal Raina, Karthik Suresh\nGF2 Software\n" + _("2019 IIB Summer Term"), _("About Logsim"), wx.ICON_INFORMATION | wx.OK) if Id == wx.ID_OPEN: self.on_open() if Id == wx.ID_YES: self.on_3D(None) if Id == wx.ID_NO: self.on_2D(None) if Id == self.id_en: self.updateLanguage(u"en") self._update_Labels() if Id == self.id_ro: self.updateLanguage(u"el") self._update_Labels() def on_2D(self, event): """Handle event when user clicks 2D view button""" if self.currentview == wx.ID_YES: # Hide 3D view self.canv_feed_sizer.Hide(1) # Show 2D view self.canv_feed_sizer.Show(0) # Reload self.main_sizer.Layout() self.Fit() # Update view record self.currentview = wx.ID_NO # Force axis update self.axis_canvas.cycles = self.cycles_completed self.axis_canvas.render() def on_3D(self, event): """Handle event when user clicks 3D view button""" if self.currentview == wx.ID_NO: # Hide 2D view self.canv_feed_sizer.Hide(0) # Show 3D view self.canv_feed_sizer.Show(1) # Reload self.main_sizer.Layout() self.Fit() # Update view record self.currentview = wx.ID_YES def on_reset(self, event): """Handle event when user clicks rest view""" # If 3D, reset rotation and pan variables. if self.currentview == wx.ID_YES: self.my3D_canvas.reset_pan() # If 2D, reset pan vairbles in all canvases. if self.currentview == wx.ID_NO: MyOpenGLCanvas.pan_x = 0 MyOpenGLCanvas.pan_y = 0 self.traces_canvas.init = False self.axis_canvas.init = False self.labels_canvas.init = False self.traces_canvas.Refresh() self.axis_canvas.Refresh() self.labels_canvas.Refresh() def on_run_button(self, event): """Run the simulation from scratch when user clicks Run button.""" self.cycles_completed = 0 cycles = self.cycles.GetValue() if cycles is not None: # if the number of cycles provided is valid self.monitors.reset_monitors() # Clear previously stored signals self.devices.cold_startup() # Random initialisation if self._run_network(cycles): self.act_log.AppendText("".join( [_("Running for "), str(cycles), _(" cycles")]) + '\n') self.cycles_completed += cycles # Update pan variable to ensure new plot # not generated off screen. MyOpenGLCanvas.pan_x = 0 self.traces_canvas.init = False self.axis_canvas.init = False self.labels_canvas.init = False self._re_render() # Update plots def on_continue_button(self, event): """Continue a previously run simulation.""" cycles = self.cycles.GetValue() if cycles is not None: # if the number of cycles provided is valid if self.cycles_completed == 0: self.act_log.AppendText( _("Error! Nothing to continue. Run first.") + '\n') elif self._run_network(cycles): self.cycles_completed += cycles self._re_render() # Update plots self.act_log.AppendText("".join([ _("Continuing for "), str(cycles), _(" cycles."), _(" Total:"), str(self.cycles_completed) ]) + '\n') def on_config_button(self, event): """Handle the event when the user clicks the apply button.""" # Gather device id of device user wants to configure and # configure variable. config = self.config_var.GetValue() dev_id = self.configurable[1][self.config_list.GetSelection()] # Validate configure variable based on device type selected if self.devices.get_device(dev_id).device_kind == self.devices.SWITCH: switch_state = self._verify_number(config, 0, 1) if switch_state is not None: # Changing switch state. if self.devices.set_switch(dev_id, switch_state): print("Successfully set switch " + self.names.get_name_string(dev_id) + "\n") dv_id = dev_id # pep8 self.act_log.AppendText( _("Successfully set switch ") + self.names.get_name_string(dv_id) + "\n") self.configurable[2][ self.config_list.GetSelection()] = switch_state else: print("Error! Invalid switch." + "\n") self.act_log.AppendText(_("Error! Invalid switch.") + "\n") else: print("Error! Switch state must be " + "0 (OFF) or 1 (ON)." + "\n") self.act_log.AppendText( _("Error! Switch state must be ") + _("0 (OFF) or 1 (ON).") + "\n") elif (self.devices.get_device(dev_id).device_kind == self.devices.CLOCK ): print("Changing clock half-period") half_period = self._verify_number(config, 1, None) if half_period is not None: # Changing clock period. if self.devices.set_clock(dev_id, half_period): print("Successfully set CLOCK " + self.names.get_name_string(dev_id) + " half-period to " + str(config) + "\n") self.act_log.AppendText( _("Successfully set CLOCK ") + self.names.get_name_string(dev_id) + _(" half-period to ") + str(config) + "\n") self.configurable[2][ self.config_list.GetSelection()] = half_period else: print("Error! Invalid CLOCK." + "\n") self.act_log.AppendText(_("Error! Invalid CLOCK.") + "\n") else: print("Error! CLOCK half-period must be " + "positive integer" + "\n") self.act_log.AppendText( _("Error! CLOCK" + "half-period must be ") + _("positive integer") + "\n") self._re_render() # Update plots def on_show(self, event): """ Set the selected outputs to be monitored. Called in reponse to user clicking 'Show'. """ # Get list indicies of outputs selected by user selected = self.outs.GetSelections() for i in selected: # Find names id from GUI list id monitor = self.outputs[1][i] if monitor is not None: [device, port] = monitor monitor_error = self.monitors.make_monitor( device, port, self.cycles_completed) if monitor_error == self.monitors.NO_ERROR: # print("Successfully made monitor.") self.act_log.AppendText( _("Successfully made monitor.") + '\n') # Update monitored output gui list. self._regen_monitored(monitor) else: # print("Error! Could not make monitor.") self.act_log.AppendText( _("Error! Monitor already ") + _("selected.") + '\n') self._re_render() def on_remove(self, event): """ Remove selected monitored outputs. Called in reponse to user clicking 'Remove'. """ # Get list indicies of outputs selected by user to_remove = self.mons.GetSelections() # Remove selected monitors in reverse order to # ensure indicies are not out of range. for i in to_remove[::-1]: # Find names id from GUI list id monitor = self.monitored[1][i] if monitor is not None: [device, port] = monitor if self.monitors.remove_monitor(device, port): self.act_log.AppendText( _("Successfully zapped monitor") + '\n') # Remove from displayed and internal lists. self.mons.Delete(i) self.monitored[0].pop(i) self.monitored[1].pop(i) else: # print("Error! Could not zap monitor.") self.act_log.AppendText( _("Error! Could not zap monitor.") + '\n') # Remove relevant traces. self._re_render() def _get_file_name(self, path_name): """ Gets file name from file path """ file_name = "" for c in path_name[::-1]: if c != '/': file_name += c else: break file_name = file_name[::-1] return file_name def on_open(self): """Handles user event to open a new file""" # otherwise ask the user what new file to open with wx.FileDialog(self, _("Open Text file"), wildcard=_("Text files") + "(*.txt)|*.txt", style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) \ as fileDialog: if fileDialog.ShowModal() == wx.ID_EXIT: return # the user changed their mind # Proceed loading the file chosen by the user path_name = fileDialog.GetPath() # Extract file name from path, show in title. # Second Interim Report Feedback Update: Show current file. self.SetTitle("Logic Simulator - " + self._get_file_name(path_name)) self.path = path_name self.on_reload(None, True) def on_reload(self, event, new=False): """ Handles loading of circuit specification file. Called when new file loaded or current file reloaded. """ # Create new render self.cycles_completed = 0 self.start = (0, 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(self.path, self.names) self.parser = Parser(self.names, self.devices, self.network, self.monitors, self.scanner) if self.parser.parse_network(): self.run_button.Enable() self.continue_button.Enable() self.config_button.Enable() self.add_outs.Enable() self.dele_mons.Enable() self.view2D.Enable() self.view3D.Enable() self.reset.Enable() [self.outputs, self.configurable, self.monitored] = self._gen_lists() self.outs.Set(self.outputs[0]) # Added after discovering application crashes # when SIGGEN circuit loaded. Before this every # ciruit had to include a configurable device. if len(self.configurable[0]) > 0: self.config_list.Set(self.configurable[0]) self.config_list.SetSelection(0) self.config_var.SetValue(self.configurable[2][0]) else: self.config_var.SetValue(0) self.config_list.Clear() self.config_button.Disable() self.mons.Set(self.monitored[0]) # Ensure pan variables are restored, # and new file loaded in both views. self.on_reset(None) self._re_render() if self.currentview == wx.ID_YES: self.on_2D(None) else: self.on_3D(None) self.on_reset(None) self._re_render() if self.currentview == wx.ID_NO: self.on_3D(None) else: self.on_2D(None) self.act_log.Clear() if new: self.act_log.AppendText( _("Successfully loaded new file!") + "\n") else: self.act_log.AppendText( _("Successfully reloaded file!") + "\n") else: self.act_log.Clear() # if new: # self.act_log.AppendText(_("Unsuccessful Load!")+ "\n\n") # else: # self.act_log.AppendText(_("Unsuccessful Reload!")+ "\n\n") for err_msg in self.parser.print_gui: self.act_log.AppendText(_(err_msg)) self.act_log.AppendText(_(self.scanner.broken_comment_msg) + '\n') self.act_log.AppendText( _("***ERROR: Circuit could not ") + _("be parsed. Try again") + "\n\n\n") self.run_button.Disable() self.continue_button.Disable() self.config_button.Disable() self.add_outs.Disable() self.dele_mons.Disable() self.view2D.Disable() self.view3D.Disable() self.reset.Disable() # Delete traces self.names = None self.devices = None self.network = None # Set devices and monitors on the right canvas to None self.config_list.SetValue("") self.outputs = [[] for i in range(2)] self.configurable = [[] for i in range(2)] self.monitored = [[] for i in range(2)] self.outs.Clear() self.config_list.Clear() self.mons.Clear() self.on_reset(None) self._re_render() if self.currentview == wx.ID_YES: self.on_2D(None) else: self.on_3D(None) self.on_reset(None) self._re_render() if self.currentview == wx.ID_NO: self.on_3D(None) else: self.on_2D(None) def updateLanguage(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_DEFAULT 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) # self.act_log.AppendText("updated") else: self.locale = None def _update_Labels(self): """Updates labels in GUI to selected language.""" self.run_button.SetLabel(_("Run")) self.continue_button.SetLabel(_("Continue")) self.config_button.SetLabel(_("Apply")) self.add_outs.SetLabel(_("Show")) self.dele_mons.SetLabel(_("Remove")) self.view2D.SetLabel(_("2D View")) self.view3D.SetLabel(_("3D View")) self.reset.SetLabel(_("Reset View")) self.reload.SetLabel(_("Reload File")) self.uf.SetLabel(_("Activity Log")) self.cyc.SetLabel(_("Cycles")) self.conf.SetLabel(_("Configure Devices")) self.out.SetLabel(_("Outputs")) self.mon.SetLabel(_("Monitored Outputs")) self.fileMenu.SetLabel(wx.ID_ABOUT, _(u"&About")) self.fileMenu.SetLabel(wx.ID_EXIT, _(u"&Exit")) self.fileMenu.SetLabel(wx.ID_OPEN, _(u"&Open")) self.viewMenu.SetLabel(wx.ID_NO, _("2D Display")) self.viewMenu.SetLabel(wx.ID_YES, _("3D Display")) self.langMenu.SetLabel(self.id_en, _(u"&English")) self.langMenu.SetLabel(self.id_ro, _(u"&Română")) self.menuBar.SetMenuLabel(0, _(u"&File")) self.menuBar.SetMenuLabel(1, _(u"View")) self.menuBar.SetMenuLabel(2, _(u"Language")) self.main_sizer.Layout() self.Fit()
def main(arg_list): """Parse the command line options and arguments specified in arg_list. Run either the command line user interface, the graphical user interface, or display the usage message. """ usage_message = ( "Usage:\n" "Show help: logsim.py -h\n" "Command line user interface: logsim.py -c <file path>\n" "Graphical user interface with a blank circuit: logsim.py\n" "Graphical user interface with a predefined test circuit: logsim.py -t <circuit_number>\n" "Graphical user interface with loading a file: logsim.py <file path>") try: options, arguments = getopt.getopt(arg_list, "hct:") except getopt.GetoptError: print("Error: invalid command line arguments\n") print(usage_message) sys.exit() # Initialise instances of the four inner simulator classes names = Names() devices = Devices(names) network = Network(names, devices) monitors = Monitors(names, devices, network) import app_base as ab for option, path in options: if option == "-h": # print the usage message print(usage_message) sys.exit() elif option == "-c": # use the command line user interface scanner = Scanner(path, names) parser = Parser(names, devices, network, monitors, scanner) if parser.parse_network(): # Initialise an instance of the userint.UserInterface() class userint = UserInterface(names, devices, network, monitors) userint.command_interface() elif option == "-t": # manually create the devices, network, and monitors for testing if path == "1": names, devices, network, monitors = test_1() elif path == "2": names, devices, network, monitors = test_2() elif path == "3": names, devices, network, monitors = test_3() elif path == "4": names, devices, network, monitors = test_4() else: print("Error: invalid test number.\n") sys.exit() # Initialise an instance of the gui.Gui() class app = wx.App() gui = Gui("Logic Simulator", path, names, devices, network, monitors, "en", 0) gui.Show(True) app.MainLoop() if not options: # no option given, use the graphical user interface if len(arguments) == 0: # Open the blank gui # Initialise an instance of the gui.Gui() class app = wx.App() gui = Gui("Logic Simulator", None, names, devices, network, monitors, "en", 0) gui.Show(True) app.MainLoop() elif len(arguments) == 1: [path] = arguments scanner = Scanner(path, names) parser = Parser(names, devices, network, monitors, scanner) if parser.parse_network(): # Initialise an instance of the gui.Gui() class app = wx.App() gui = Gui("Logic Simulator", path, names, devices, network, monitors, "en", 0) gui.Show(True) app.MainLoop() else: # wrong number of arguments print("Error: Too many arguments given\n") print(usage_message) sys.exit()
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_spin(self, event): Event handler for when the user changes the spin control value. on_run_button(self, event): Event handler for when the user clicks the run button. on_text_box(self, event): Event handler for when the user enters text. """ def __init__(self, title, path, names, devices, network, monitors): """Initialise widgets and layout.""" super().__init__(parent=None, title=title, size=(800, 600)) """Initialise variables.""" self.names = names self.devices = devices self.monitors = monitors self.network = network # Setting up the file menu self.fileMenu = wx.Menu() self.fileMenu.Append(102, _(u"&About")) self.fileMenu.Append(wx.ID_OPEN, _(u"&Open")) self.fileMenu.Append(103, _(u"&Quit")) # Create the menu bar self.menuBar = wx.MenuBar() # Adding the "file menu" to the menu bar self.menuBar.Append(self.fileMenu, _(u"&File")) # Adding the menu bar to the frame content self.SetMenuBar(self.menuBar) # Canvas for drawing signals self.canvas_2d = MyGLCanvas2D(self, devices, monitors) self.canvas_3d = MyGLCanvas3D(self, devices, monitors) self.canvas_2d.Show() self.canvas_3d.Hide() # Configure the widgets self.lblLogWindow = wx.StaticText(self, -1, label=_(u"Console")) self.logWindow = wx.TextCtrl(self, -1, style=wx.TE_MULTILINE | wx.TE_READONLY, size=(100, 500)) self.lblList = ['2D', '3D'] self.render = wx.RadioBox(self, label=_(u"Render"), choices=self.lblList, majorDimension=1, style=wx.RA_SPECIFY_ROWS) self.render.SetSelection(0) self.state = self.render.GetSelection() self.languagelist = ["English", "Chinese"] self.language = wx.RadioBox(self, label=_(u"Language"), choices=self.languagelist, majorDimension=1, style=wx.RA_SPECIFY_ROWS) global lang_sel self.language.SetSelection(lang_sel) self.current_lang = self.language.GetSelection() self.text_run = wx.StaticText(self, wx.ID_ANY, _(u"Cycles to run")) self.text_cont = wx.StaticText(self, wx.ID_ANY, _(u"Cycles to continue")) self.spin_run = wx.SpinCtrl(self, wx.ID_ANY, "10", max=2147483647) self.spin_cont = wx.SpinCtrl(self, wx.ID_ANY, "2", max=2147483647) self.run = wx.Button(self, wx.ID_ANY, _(u"Run")) self.cont = wx.Button(self, wx.ID_ANY, _(u"Continue")) self.ResetButton = wx.Button(self, wx.ID_ANY, _(u"Clear")) self.set_switch = wx.Button(self, wx.ID_ANY, _(u"Set Switches")) self.select_monitor = wx.Button(self, wx.ID_ANY, _(u"Monitor")) self.default_position = wx.Button(self, wx.ID_ANY, _(u"Default Position")) # Bind events to widgets self.Bind(wx.EVT_MENU, self.on_menu) self.render.Bind(wx.EVT_RADIOBOX, self.on_radiobox) self.language.Bind(wx.EVT_RADIOBOX, self.on_sel_language) self.spin_run.Bind(wx.EVT_SPINCTRL, self.on_spin) self.spin_cont.Bind(wx.EVT_SPINCTRL, self.on_spin_cont) self.run.Bind(wx.EVT_BUTTON, self.on_run_button) self.cont.Bind(wx.EVT_BUTTON, self.on_continue_button) self.ResetButton.Bind(wx.EVT_BUTTON, self.on_reset_button) self.set_switch.Bind(wx.EVT_BUTTON, self.check_box) self.select_monitor.Bind(wx.EVT_BUTTON, self.check_box_monitor) self.default_position.Bind(wx.EVT_BUTTON, self.on_default_pos) # Configure sizers for layout self.main_sizer = wx.BoxSizer(wx.HORIZONTAL) side_sizer = wx.BoxSizer(wx.VERTICAL) self.main_sizer.Add(self.canvas_2d, 5, wx.EXPAND | wx.ALL, 5) self.main_sizer.Add(side_sizer, 1, wx.ALL, 5) side_sizer.Add(self.language, 0, wx.TOP, 5) side_sizer.Add(self.render, 0, wx.TOP, 5) side_sizer.Add(self.text_run, 1, wx.TOP, 10) side_sizer.Add(self.spin_run, 1, wx.ALL, 5) side_sizer.Add(self.run, 1, wx.EXPAND, 5) side_sizer.Add(self.text_cont, 1, wx.TOP, 10) side_sizer.Add(self.spin_cont, 1, wx.ALL, 5) side_sizer.Add(self.cont, 1, wx.EXPAND, 5) side_sizer.Add(self.set_switch, 1, wx.EXPAND, 5) side_sizer.Add(self.select_monitor, 1, wx.EXPAND, 5) side_sizer.Add(self.ResetButton, 1, wx.EXPAND, 5) side_sizer.Add(self.default_position, 1, wx.EXPAND, 5) side_sizer.Add(self.lblLogWindow, 1, wx.TOP, 5) side_sizer.Add(self.logWindow, 1, wx.EXPAND, 5) self.SetSizeHints(600, 600) self.SetSizer(self.main_sizer) # A modal show will lock out the other windows until it has been dealth with # Very useful in some programming tasks to ensure that things happen in an order # that the programmer expects, but can be very frustrating to the user if it is # used to excess self.exitconfirmation = wx.MessageDialog( self, _(u"Are you sure you want to quit the simulation? \n"), _(u"Confirmation"), wx.YES_NO) self.openFileDialog = wx.FileDialog( self, _(u"Select Logic Definition File"), "", "", _(u"Logic definition files (*.txt)|*.txt"), wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) sys.stdout = self.logWindow def on_sel_language(self, event): self.current_lang = self.language.GetSelection() if self.current_lang == 0: app = ab.BaseApp(redirect=False) app.updateLanguage(u"en") print(_(u"English is selected")) global lang_sel lang_sel = 0 else: app = ab.BaseApp(redirect=False) app.updateLanguage(u"zh_CN") print(_(u"Chinese is selected")) lang_sel = 1 self.text_run.SetLabel(_(u"Cycles to run")) self.text_cont.SetLabel(_(u"Cycles to continue")) self.run.SetLabel(_(u"Run")) self.cont.SetLabel(_(u"Continue")) self.ResetButton.SetLabel(_(u"Clear")) self.set_switch.SetLabel(_(u"Set Switches")) self.select_monitor.SetLabel(_(u"Monitor")) self.default_position.SetLabel(_(u"Default Position")) self.language.SetLabel(_(u"Language")) self.render.SetLabel(_(u"Render")) self.lblLogWindow.SetLabel(_(u"Console")) self.fileMenu.SetLabel(102, _(u"About")) self.fileMenu.SetLabel(103, _(u"Quit")) self.menuBar.SetMenuLabel(0, _(u"File")) self.fileMenu.SetLabel(wx.ID_OPEN, _(u"&Open")) def on_radiobox(self, event): self.state = self.render.GetSelection() print(self.lblList[self.state], _(u" was selected")) if self.state == 0: #Change from 3D to 2D self.main_sizer.Replace(self.canvas_3d, self.canvas_2d) self.canvas_3d.Hide() self.canvas_2d.Show() else: #Change from 2D to 3D self.main_sizer.Replace(self.canvas_2d, self.canvas_3d) self.canvas_2d.Hide() self.canvas_3d.Show() self.main_sizer.Layout() def on_default_pos(self, event): self.canvas_2d.default_position() self.canvas_3d.default_position() def check_box_monitor(self, event): MyDialog_monitor(self, -1, _(u"Select signals to monitor"), self.names, self.devices, self.monitors).Show() def check_box(self, event): MyDialog(self, -1, _(u"Set switches to 1"), self.names, self.devices).Show() def on_menu(self, event): """Handle the event when the user selects a menu item.""" Id = event.GetId() if Id == 103: exitconf = self.exitconfirmation.ShowModal() if exitconf == wx.ID_YES: self.Close(True) if Id == 102: wx.MessageBox( _(u"Display the signal traces at different monitored outputs. \nRed trace represents '1', blue trace represents '0'.\nOutputs to be monitored can be selected by clicking 'Monitor'.\nSwitches levels can be selected by clicking 'Set Switches'" ), _(u"About Logsim"), wx.ICON_INFORMATION | wx.OK) if Id == wx.ID_OPEN: with wx.FileDialog(self) as fileDialog: if fileDialog.ShowModal() == wx.ID_CANCEL: return path = fileDialog.GetPath() self.Close(True) app = wx.App() error = ErrorFrame() names = Names() devices = Devices(names) network = Network(names, devices) monitors = Monitors(names, devices, network) self.names = names self.devices = devices self.network = network self.monitors = monitors self.scanner = Scanner(path, self.names) self.parser = Parser(self.names, self.devices, self.network, self.monitors, self.scanner) global global_cycles_completed global hold global hold_monitor global_cycles_completed = 0 hold = {} hold_monitor = {} try: self.parser.parse_network() gui = Gui("LogicSim", path, self.names, self.devices, self.network, self.monitors) gui.Show(True) except: pass error.ShowModal() app.MainLoop() def on_spin(self, event): """Handle the event when the user changes the spin control value.""" spin_value = self.spin_run.GetValue() text = "".join([_(u"New run spin control value: "), str(spin_value)]) if self.state == 0: self.canvas_2d.render(text) else: self.canvas_3d.render() return spin_value def on_spin_cont(self, event): """Handle the event when the user changes the spin control value.""" spin_value_cont = self.spin_cont.GetValue() text = "".join( [_(u"New continue spin control value: "), str(spin_value_cont)]) if self.state == 0: self.canvas_2d.render(text) else: self.canvas_3d.render() return spin_value_cont def on_run_button(self, event): """Handle the event when the user clicks the run button.""" text = _(u"Run button pressed.") if self.state == 0: self.canvas_2d.render(text) else: self.canvas_3d.render() self.run_command() def on_continue_button(self, event): """Handle the event when the user clicks the Continue button.""" text = _(u"Continue button pressed.") if self.state == 0: self.canvas_2d.render(text) else: self.canvas_3d.render() self.continue_command() def on_reset_button(self, event): """Handle the event when the user clicks the reset button.""" text = _(u"Reset button pressed.") if self.state == 0: self.canvas_2d.render(text) else: self.canvas_3d.render() dialog_box = MyDialog_monitor(self, -1, _(u"Select signals to monitor"), self.names, self.devices, self.monitors) dialog_box.Destroy() global hold_monitor differential = hold_monitor hold_monitor = dict.fromkeys(hold_monitor, False) dialog_box = MyDialog_monitor(self, -1, _(u"Select signals to monitor"), self.names, self.devices, self.monitors) hold_monitor = differential dialog_box.ok_button(wx.EVT_BUTTON) dialog_box.Destroy() hold_monitor = dict.fromkeys(hold_monitor, False) if self.state == 0: self.canvas_2d.Refresh() else: self.canvas_3d.Refresh() global global_cycles_completed global_cycles_completed = 0 def on_text_box(self, event): """Handle the event when the user enters text.""" text_box_value = self.text_box.GetValue() text = "".join([_(u"New text box value: "), text_box_value]) if self.state == 0: self.canvas_2d.render(text) else: self.canvas_3d.render() def run_command(self): """Run the simulation from scratch.""" global global_cycles_completed global_cycles_completed = 0 cycles = self.on_spin(wx.SpinCtrl) # check that this function has been called on pressing run button text = "".join([ _(u"run_command function has been called, number of cycles is: "), str(cycles) ]) if self.state == 0: self.canvas_2d.render(text, True) else: self.canvas_3d.render() if cycles is not None: # if the number of cycles provided is valid self.monitors.reset_monitors() print("".join([_(u"Running for "), str(cycles), _(u" cycles")])) self.devices.cold_startup() if self.run_network(cycles): global_cycles_completed += cycles def continue_command(self): """Continue a previously run simulation.""" cycles_cont = self.on_spin_cont(wx.SpinCtrl) global global_cycles_completed # check that this function has been called on pressing continue button text = "".join([ _(u"continue_command function has been called, number of cycles is: " ), str(cycles_cont) ]) if self.state == 0: self.canvas_2d.render(text, True) else: self.canvas_3d.render() if cycles_cont is not None: # if the number of cycles provided is valid if global_cycles_completed == 0: print(_(u"Error! Nothing to continue. Run first.")) elif self.run_network(cycles_cont): global_cycles_completed += cycles_cont print(" ".join([ _(u"Continuing for"), str(cycles_cont), _(u"cycles."), _(u"Total:"), str(global_cycles_completed) ])) def run_network(self, cycles): """Run the network for the specified number of simulation cycles. Return True if successful. """ for _ in range(cycles): if self.network.execute_network(): self.monitors.record_signals() else: print(_(u"Error! Network oscillating.")) return False self.monitors.display_signals() text = "The signal trace is printed" if self.state == 0: self.canvas_2d.render(text, True) else: self.canvas_3d.render() return True
def on_load_button(self, event): """Handle the event when the user clicks load button.""" with wx.FileDialog(self, _("Open Definition file"), wildcard="Definition files (*.txt)|*.txt", style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog: if fileDialog.ShowModal() == wx.ID_CANCEL: return self.pathname = fileDialog.GetPath() self.filename = self.path_leaf(self.pathname) names = Names() devices = Devices(names) network = Network(names, devices) monitors = Monitors(names, devices, network) scanner = Scanner(self.pathname, names) parser = Parser(names, devices, network, monitors, scanner) parser.parse_network() error_list = scanner.error_list num_errors = len(error_list) pages = math.ceil(num_errors / 4) if num_errors != 0: text_list = [] tab_labels = [] for i in range(pages - 1): tab_labels.append("{}-{}".format(1 + i * 4, 4 + i * 4)) label = 4 + i * 4 if num_errors == 1: tab_labels.append("1") elif num_errors <= 4: tab_labels.append("1-{}".format(num_errors)) else: if (label + 1) == num_errors: tab_labels.append("{}".format(num_errors)) else: tab_labels.append("{}-{}".format( label + 1, num_errors)) if num_errors == 1: overview = _("\nDefinition file '{}' contains {} error.")\ .format(self.filename, num_errors) else: overview = _("\nDefinition file '{}' contains {} errors.")\ .format(self.filename, num_errors) for i in range(pages): if i == 0: text = '\n' + '*' * 76 + '\n' else: text = "".format(self.filename, num_errors) for j in range(4): try: text += (error_list[j + i * 4] + "\n") except IndexError: text += ('\n' * 8) text_list.append(text) frame = DefinitionErrors(self, title=_("Error!"), text=text_list, tabs=tab_labels, overview=overview) return self.current_filename = self.filename self.current_pathname = self.pathname self.load_new = True self.Show(False) self.Destroy()