Beispiel #1
0
def test_2():
    """Create network that matches test definition file 2"""
    names = Names()
    devices = Devices(names)
    network = Network(names, devices)
    monitors = Monitors(names, devices, network)

    CK1, CK2, AND1, NAND1, OR1, NOR1 = names.lookup(
        ["CK1", "CK2", "AND1", "NAND1", "OR1", "NOR1"])
    devices.make_device(CK1, devices.CLOCK, 2)
    devices.make_device(CK2, devices.CLOCK, 1)
    devices.make_device(AND1, devices.AND, 2)
    devices.make_device(NAND1, devices.NAND, 2)
    devices.make_device(OR1, devices.OR, 2)
    devices.make_device(NOR1, devices.NOR, 2)

    network.make_connection(CK1, None, AND1, names.query("I1"))
    network.make_connection(CK2, None, AND1, names.query("I2"))
    network.make_connection(CK2, None, NAND1, names.query("I2"))
    network.make_connection(CK2, None, OR1, names.query("I2"))
    network.make_connection(CK2, None, NOR1, names.query("I2"))
    network.make_connection(AND1, None, NAND1, names.query("I1"))
    network.make_connection(NAND1, None, OR1, names.query("I1"))
    network.make_connection(OR1, None, NOR1, names.query("I1"))

    monitors.make_monitor(NOR1, None)
    return names, devices, network, monitors
Beispiel #2
0
def test_1():
    """Create network that matches test definition file 1"""
    names = Names()
    devices = Devices(names)
    network = Network(names, devices)
    monitors = Monitors(names, devices, network)

    [SW1, SW2, SW3, SW4, D1, CK1, XOR1] = \
        names.lookup(["SW1", "SW2", "SW3", "SW4", "D1", "CK1", "XOR1"])
    print(names.query("SW1"))
    devices.make_device(SW1, devices.SWITCH, 1)
    devices.make_device(SW2, devices.SWITCH, 1)
    devices.make_device(SW3, devices.SWITCH, 1)
    devices.make_device(SW4, devices.SWITCH, 0)
    devices.make_device(D1, devices.D_TYPE)
    devices.make_device(CK1, devices.CLOCK, 2)
    devices.make_device(XOR1, devices.XOR)

    network.make_connection(SW1, None, XOR1, names.query("I1"))
    network.make_connection(SW2, None, XOR1, names.query("I2"))
    network.make_connection(XOR1, None, D1, names.query("DATA"))
    network.make_connection(CK1, None, D1, names.query("CLK"))
    network.make_connection(SW3, None, D1, names.query("SET"))
    network.make_connection(SW4, None, D1, names.query("CLEAR"))

    monitors.make_monitor(D1, names.query("Q"))
    monitors.make_monitor(D1, names.query("QBAR"))

    return names, devices, network, monitors
Beispiel #3
0
def main():

    # Initialise instances of the four inner simulator classes
    names = Names()
    devices = Devices(names)
    network = Network(names, devices)
    monitors = Monitors(names, devices, network)

    [s1, s2] = devices.names.lookup(["S1", "S2"])

    devices.make_switch(s1, 0)
    devices.make_switch(s2, 0)
    devices.make_gate(1, devices.names.query("NAND"), 2)
    devices.make_gate(2, devices.names.query("NAND"), 2)

    network.make_connection(s1, None, 1, devices.names.query("I1"))
    network.make_connection(s2, None, 2, devices.names.query("I2"))
    network.make_connection(1, None, 2, devices.names.query("I1"))
    network.make_connection(2, None, 1, devices.names.query("I2"))
    print(network.check_network())

    print(monitors.make_monitor(1, None))
    monitors.display_signals()

    userint = UserInterface(names, devices, network, monitors)
    userint.command_interface()
def new_monitors():
    """Return a Monitors class instance with monitors set on three outputs."""
    new_names = Names()
    new_devices = Devices(new_names)
    new_network = Network(new_names, new_devices)
    new_monitors = Monitors(new_names, new_devices, new_network)

    [SW1_ID, SW2_ID, OR1_ID, I1,
     I2] = new_names.lookup(["Sw1", "Sw2", "Or1", "I1", "I2"])
    # Add 2 switches and an OR gate
    new_devices.make_device(SW1_ID, new_devices.SWITCH, [0])
    new_devices.make_device(SW2_ID, new_devices.SWITCH, [0])
    new_devices.make_device(OR1_ID, new_devices.OR, [2])

    # Make connections
    new_network.make_connection(SW1_ID, None, OR1_ID, I1)
    new_network.make_connection(SW2_ID, None, OR1_ID, I2)

    # Set monitors
    new_monitors.make_monitor(SW1_ID, None)
    new_monitors.make_monitor(SW2_ID, None)
    new_monitors.make_monitor(OR1_ID, None)

    return new_monitors
Beispiel #5
0
def test_4():
    """Create network that matches test definition file 4, a ripple counter"""
    names = Names()
    devices = Devices(names)
    network = Network(names, devices)
    monitors = Monitors(names, devices, network)

    SW, CK, D1, D2, D3, D4 = names.lookup(["SW", "CK", "D1", "D2", "D3", "D4"])
    devices.make_device(SW, devices.SWITCH, 0)
    devices.make_device(CK, devices.CLOCK, 1)
    devices.make_device(D1, devices.D_TYPE)
    devices.make_device(D2, devices.D_TYPE)
    devices.make_device(D3, devices.D_TYPE)
    devices.make_device(D4, devices.D_TYPE)

    network.make_connection(SW, None, D1, names.query("SET"))
    network.make_connection(SW, None, D1, names.query("CLEAR"))
    network.make_connection(SW, None, D2, names.query("SET"))
    network.make_connection(SW, None, D2, names.query("CLEAR"))
    network.make_connection(SW, None, D3, names.query("SET"))
    network.make_connection(SW, None, D3, names.query("CLEAR"))
    network.make_connection(SW, None, D4, names.query("SET"))
    network.make_connection(SW, None, D4, names.query("CLEAR"))
    network.make_connection(CK, None, D1, names.query("CLK"))
    network.make_connection(D1, names.query("QBAR"), D2, names.query("CLK"))
    network.make_connection(D2, names.query("QBAR"), D3, names.query("CLK"))
    network.make_connection(D3, names.query("QBAR"), D4, names.query("CLK"))
    network.make_connection(D1, names.query("QBAR"), D1, names.query("DATA"))
    network.make_connection(D2, names.query("QBAR"), D2, names.query("DATA"))
    network.make_connection(D3, names.query("QBAR"), D3, names.query("DATA"))
    network.make_connection(D4, names.query("QBAR"), D4, names.query("DATA"))

    monitors.make_monitor(CK, None)
    monitors.make_monitor(D1, names.query("Q"))
    monitors.make_monitor(D2, names.query("Q"))
    monitors.make_monitor(D3, names.query("Q"))
    monitors.make_monitor(D4, names.query("Q"))

    return names, devices, network, monitors
Beispiel #6
0
def test_3():
    """Create network that matches test definition file 3"""
    names = Names()
    devices = Devices(names)
    network = Network(names, devices)
    monitors = Monitors(names, devices, network)

    S, R, CK, D = names.lookup(["S", "R", "CK", "D"])
    devices.make_device(S, devices.SWITCH, 0)
    devices.make_device(R, devices.SWITCH, 0)
    devices.make_device(CK, devices.CLOCK, 1)
    devices.make_device(D, devices.D_TYPE)

    network.make_connection(CK, None, D, names.query("CLK"))
    network.make_connection(S, None, D, names.query("SET"))
    network.make_connection(R, None, D, names.query("CLEAR"))
    network.make_connection(D, names.query("QBAR"), D, names.query("DATA"))

    monitors.make_monitor(CK, None)
    monitors.make_monitor(D, names.query("Q"))
    monitors.make_monitor(S, None)
    monitors.make_monitor(R, None)

    return names, devices, network, monitors
Beispiel #7
0
def monitors(names, devices, network):
    mon = Monitors(names, devices, network)
    mon.make_monitor = lambda a, b, c=0: mon.NO_ERROR
    return mon
Beispiel #8
0
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()
Beispiel #9
0
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()