示例#1
0
class MyFrame(wx.Frame):
    def __init__(self, parent, ID, title):
        wx.Frame.__init__(self, parent, ID, title, wx.DefaultPosition,
                          wx.Size(600, 400))
        # Controls
        self.tin = wx.TextCtrl(self,
                               size=wx.Size(600, 400),
                               style=wx.TE_MULTILINE)
        self.test_panel = ScrolledPanel(self, size=wx.Size(600, 400))
        self.test_panel.SetupScrolling()
        self.tin2 = wx.StaticText(self.test_panel)

        # Layout
        # -- Scrolled Window
        self.panel_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.panel_sizer.Add(self.tin2, 0, wx.EXPAND)
        self.test_panel.SetSizer(self.panel_sizer)
        self.panel_sizer.Fit(self.test_panel)
        # -- Main Frame
        self.inner_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.inner_sizer.Add(self.tin, 1, wx.LEFT | wx.RIGHT | wx.EXPAND, 50)
        self.inner_sizer.Add(self.test_panel, 1,
                             wx.LEFT | wx.RIGHT | wx.EXPAND, 50)

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.inner_sizer, 1, wx.ALL | wx.EXPAND, 20)
        self.SetSizer(self.sizer)
        self.sizer.Fit(self)
        self.sizer.Layout()

        self.test_panel.SetAutoLayout(1)

        # Bind Events
        self.tin.Bind(wx.EVT_TEXT, self.TextChange)

    def TextChange(self, event):
        self.tin2.SetLabel(self.tin.GetValue())
        self.test_panel.FitInside()
示例#2
0
class InjectionInputsPanel(pdsim_panels.PDPanel):
    """
    The container panel for all the injection ports and injection data 
    """
    def __init__(self, parent, **kwargs):
        pdsim_panels.PDPanel.__init__(self, parent, **kwargs)

        #Now we are going to put everything into a scrolled window
        main_sizer = wx.BoxSizer(wx.VERTICAL)

        self.scrolled_panel = ScrolledPanel(self,
                                            size=(-1, -1),
                                            style=wx.TAB_TRAVERSAL,
                                            name="panel1")
        self.scrolled_panel.SetScrollbars(1, 1, 1, 1)
        self.scrolled_panel.SetupScrolling()

        #Add the header row of buttons
        self.View = wx.Button(self.scrolled_panel, label='View')
        self.View.Bind(wx.EVT_BUTTON, self.OnView)
        self.AddInjection = wx.Button(self.scrolled_panel,
                                      label='Add Injection Line')
        self.AddInjection.Bind(wx.EVT_BUTTON, self.OnAddInjection)
        self.PlotExistence = wx.Button(self.scrolled_panel,
                                       label='Plot Existence')
        self.PlotExistence.Bind(wx.EVT_BUTTON, self.OnPlotExistence)
        buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
        buttons_sizer.Add(self.AddInjection)
        buttons_sizer.Add(self.View)
        buttons_sizer.Add(self.PlotExistence)

        sizer = wx.FlexGridSizer(cols=1)
        sizer.Add(buttons_sizer)
        sizer.AddSpacer(10)
        sizer.Layout()

        self.scrolled_panel.SetAutoLayout(1)

        #Do the layout of all the panels
        self.scrolled_panel.SetSizer(sizer)
        main_sizer.Add(self.scrolled_panel, 1, wx.EXPAND)
        self.SetSizer(main_sizer)
        main_sizer.Layout()

        #Set some local variables
        self.Nterms = 0
        self.Lines = []

    def OnAddInjection(self, event=None):
        """
        Add an injection line to the injection panel
        """
        IE = InjectionElementPanel(self.scrolled_panel, self.Nterms + 1)
        #Put the panel within the scrolled panel and refresh
        self.scrolled_panel.GetSizer().Add(IE, 0)
        self.scrolled_panel.FitInside()
        self.GetSizer().Layout()

        #Update the local variables
        self.Lines.append(IE)
        self.Nterms += 1

        self.Refresh()

    def remove_all(self):
        while self.Lines:
            self.RemoveInjection(self.Lines[0])

    def RemoveInjection(self, injection):
        """
        Remove the given injection term
        """
        self.Lines.remove(injection)
        injection.Destroy()
        self.Nterms -= 1
        #Renumber the injection panels that are contained in scrolled_panel
        I = 1
        for child in self.scrolled_panel.Children:
            if isinstance(child, InjectionElementPanel):
                child.SizerBox.SetLabel("Injection line #" + str(I))
                I += 1
        self.GetSizer().Layout()
        self.scrolled_panel.FitInside()
        self.Refresh()

    def OnView(self, event):

        geo = self.GetTopLevelParent().MTB.InputsTB.panels[0].Scroll.geo
        SAF = ScrollAnimForm(geo, start=False)

        #IEPs are children that are instances of InjectionElementPanel class
        IEPs = [
            child for child in self.scrolled_panel.Children
            if isinstance(child, InjectionElementPanel)
        ]
        for IEP in IEPs:
            for child in IEP.Children:
                if isinstance(child, InjectionPortPanel):
                    # Get the values from the panel
                    vals = child.get_values()
                    # Overlay the port on the scroll wrap plot
                    scroll_geo.overlay_injection_port(0,
                                                      geo,
                                                      vals['phi'],
                                                      SAF.ax,
                                                      vals['involute'],
                                                      rport=vals['D'] / 2,
                                                      offset=vals['offset'])
        SAF.start()
        SAF.Show()

    def OnPlotExistence(self, event=None):
        """
        Plot a 2D line plot showing which control volume is connected to 
        each injection port as a function of the crank angle
        """
        import pylab
        import numpy as np

        _Scroll = self.GetTopLevelParent().MTB.InputsTB.panels[0].Scroll

        Iport = 1
        #IEPs are children that are instances of InjectionElementPanel class
        IEPs = [
            child for child in self.scrolled_panel.Children
            if isinstance(child, InjectionElementPanel)
        ]
        for IEP in IEPs:
            for child in IEP.Children:
                if isinstance(child, InjectionPortPanel):
                    #Get the values from the port panel
                    v = child.get_values()

                    partner_list = []

                    theta = np.linspace(0, 2 * pi, 1000)
                    for th in theta:
                        partner_list.append(
                            _Scroll._get_injection_CVkey(
                                v['phi'], th, v['involute']))

                    #Find the break points in each segment
                    dividers = [
                        i for i in range(len(theta) - 1)
                        if not partner_list[i] == partner_list[i + 1]
                    ]
                    #Add end and beginning indices
                    dividers = [0] + dividers + [len(theta) - 1]

                    for i in range(len(dividers) - 1):
                        L = dividers[i]
                        R = dividers[i + 1]
                        M = int((L + R) / 2)
                        pylab.plot(np.r_[theta[L], theta[R]], np.r_[Iport,
                                                                    Iport])
                        pylab.plot(np.r_[theta[L], theta[L]],
                                   np.r_[Iport - 0.02, Iport + 0.02], 'k')
                        pylab.plot(np.r_[theta[R], theta[R]],
                                   np.r_[Iport - 0.02, Iport + 0.02], 'k')
                        pylab.text(theta[M],
                                   Iport + .02,
                                   partner_list[M],
                                   ha='center',
                                   va='bottom')

                    #Increase the counter
                    Iport += 1

        pylab.xticks([0, pi / 2, pi, 3 * pi / 2, 2 * pi],
                     [0, r'$\pi/2$', r'$\pi$', r'$3\pi/2$', r'$2\pi$'])
        pylab.xlim(0, 2 * pi)
        pylab.ylim(0.5, Iport - 1 + 0.5)
        pylab.yticks(range(1, Iport + 1))
        pylab.show()

    def build_from_configfile(self, config):
        """
        Get parameters from the configfile section for this plugin
        
        Parameters
        ----------
        config : yaml configuration section for the plugin
        
        """

        if config:
            self.remove_all()
            for line in config:
                # Add an injection line panel
                self.OnAddInjection()
                #Get a pointer to the last IEP (the one just added)
                IEP = self.Lines[-1]
                #Set the line length in the GUI [m]
                IEP.Lval.SetValue(str(line['Length']))
                #Set the line ID in the GUI
                IEP.IDval.SetValue(str(line['ID']))
                #Set the State in the GUI
                State = line['inletState']
                IEP.state.set_state(State['Fluid'],
                                    T=State['T'],
                                    D=State['rho'])
                if 'ports' in line and line['ports']:
                    for i, port in enumerate(line['ports']):
                        if i > 0: IEP.OnAddPort()
                        # Get a pointer to the port panel
                        portpanel = IEP.ports_list[-1]
                        # Set the values in the panel
                        portpanel.set_values(port)

    def get_additional_parametric_terms(self):

        #: the list of terms
        _T = []

        #IEPs are children of injection_panel that are instances of InjectionElementPanel class
        IEPs = [
            child for child in self.scrolled_panel.Children
            if isinstance(child, InjectionElementPanel)
        ]
        for i, IEP in enumerate(IEPs):
            I = str(i + 1)

            _T += [
                dict(attr='injection_state_pressure_' + I,
                     text='Injection pressure #' + I + ' [kPa]',
                     parent=self),
                dict(attr='injection_state_sat_temp_' + I,
                     text='Injection saturated temperature (dew) #' + I +
                     ' [K]',
                     parent=self),
                dict(attr='injection_state_temp_' + I,
                     text='Injection temperature #' + I + ' [K]',
                     parent=self),
                dict(attr='injection_state_superheat_' + I,
                     text='Injection superheat #' + I + ' [K]',
                     parent=self),
            ]

            Ports = [
                c for c in IEP.Children if isinstance(c, InjectionPortPanel)
            ]
            for j, child in enumerate(Ports):
                J = str(j + 1)
                _T += [
                    dict(attr='injection_phi_' + I + '_' + J,
                         text='Injection port angle #' + I + ':' + J +
                         ' [rad]',
                         parent=self)
                ]

        return _T

    def apply_additional_parametric_terms(self, attrs, vals, panel_items):
        """
        Set the terms in the injection panel based on the additional parametric
        terms provided by the get_additional_parametric_terms() function
        """
        def apply_line_terms(attrs, vals):
            def is_int(i):
                """ Returns True if it is an integer """
                try:
                    i = int(i)
                    return True
                except ValueError:
                    return False

            def is_line_term(attr):
                """
                Check if it is a line type term of the form injection_xxxxx_1'
                and is not a port term of the form injection_xxxxx_1_1
                """
                if not attr.startswith('injection'):
                    return False

                #If there are no underscores, return false
                if len(attr.rsplit('_', 1)) == 1:
                    return False

                #Try to split twice
                attr, i, j = attr.rsplit('_', 2)

                # If the far right one is an integer and the left part isn't you are
                # ok, its an injection line
                if not is_int(i) and is_int(j):
                    return True
                else:
                    return False

            # First check about the injection state; if two state related terms are
            # provided, use them to fix the injection state
            inj_state_params = [(par, val) for par, val in zip(attrs, vals)
                                if is_line_term(par)]
            num_inj_state_params = len(inj_state_params)

            for i in range(len(self.Lines)):

                #Find the injection state terms that apply for this line
                state_params = [
                    (par, val) for par, val in zip(attrs, vals)
                    if par.find('state') > -1 and par.endswith(str(i + 1))
                ]
                num_state_params = len(state_params)

                #Get a copy of the state from the StatePanel
                inletState = self.Lines[i].state.GetState()

                if num_state_params > 0:
                    #Unzip the parameters (List of tuples -> tuple of lists)
                    state_attrs, state_vals = zip(*state_params)

                if num_state_params == 2:
                    # Remove all the entries that correspond to the injection state -
                    # we need them and don't want to set them in the conventional way
                    for a in state_attrs:
                        vals.pop(attrs.index(a))
                        attrs.pop(attrs.index(a))

                    #: The string representation of the index (1-based)
                    I = str(i + 1)

                    #Temperature and pressure provided
                    if 'injection_state_temp_' + I in state_attrs and 'injection_state_pressure_' + I in state_attrs:
                        injection_temp = state_vals[state_attrs.index(
                            'injection_state_temp_' + I)]
                        injection_pressure = state_vals[state_attrs.index(
                            'injection_state_pressure_' + I)]
                        self.Lines[i].state.set_state(inletState.Fluid,
                                                      T=injection_temp,
                                                      P=injection_pressure)

                    #Dew temperature and superheat provided
                    elif 'injection_state_sat_temp_' + I in state_attrs and 'injection_state_superheat_' + I in state_attrs:
                        injection_sat_temp = state_vals[state_attrs.index(
                            'injection_state_sat_temp_' + I)]
                        injection_superheat = state_vals[state_attrs.index(
                            'injection_state_superheat_' + I)]
                        injection_temp = injection_sat_temp + injection_superheat
                        import CoolProp.CoolProp as CP
                        injection_pressure = CP.PropsSI(
                            'P', 'T', injection_sat_temp, 'Q', 1.0,
                            inletState.Fluid) / 1000.0
                        self.Lines[i].state.set_state(inletState.Fluid,
                                                      T=injection_temp,
                                                      P=injection_pressure)

                    else:
                        raise ValueError(
                            'Invalid combination of injection states: ' +
                            str(state_attrs))

                elif num_inj_state_params == 1:
                    import textwrap
                    string = textwrap.dedent("""
                             Sorry but you need to provide two variables for the injection
                             state in parametric table to fix the state.  
                             
                             If you want to just modify the saturated temperature, add the superheat as a
                             variable and give it one element in the parametric table
                             """)
                    dlg = wx.MessageDialog(None, string)
                    dlg.ShowModal()
                    dlg.Destroy()
                    raise ValueError(
                        'Must provide two state variables in the parametric table for injection line'
                    )

                elif num_inj_state_params > 2:
                    raise ValueError(
                        'Only two inlet state parameters can be provided in parametric table'
                    )

            return attrs, vals

        def apply_port_terms(attrs, vals):
            phi_params = [(par, val) for par, val in zip(attrs, vals)
                          if par.startswith('injection_phi')]
            num_phi_params = len(phi_params)

            if num_phi_params > 0:
                #Unzip the parameters (List of tuples -> tuple of lists)
                phi_attrs, phi_vals = zip(*phi_params)

                # Remove all the entries that correspond to the angles
                # we need them and don't want to set them in the conventional way
                for a in phi_attrs:
                    i = attrs.index(a)
                    vals.pop(i)
                    attrs.pop(i)

                for attr, val in zip(phi_attrs, phi_vals):

                    # Term might look like something like 'injection_phi_1_2'
                    # i would be 0, j would be 1
                    #indices are zero-based
                    j = int(attr.rsplit('_', 1)[1]) - 1
                    i = int(attr.rsplit('_', 2)[1]) - 1

                    self.Lines[i].ports_list[j].phi_inj_port.SetValue(str(val))

            return attrs, vals

        #Apply all the line terms and get back the lists
        attrs, vals = apply_line_terms(attrs, vals)
        #Apply all the line terms and get back the lists
        attrs, vals = apply_port_terms(attrs, vals)

        return attrs, vals
示例#3
0
文件: config.py 项目: idfah/cebl
class Config(Page):
    """Page for making configuration changes.  This includes source, channel
    configuration as well as a logging console.

    Note:
        This page constructs all source instances and adds them to the manager.
    """
    def __init__(self, *args, **kwargs):
        """Construct a new Config page.
        """
        Page.__init__(self, name='Config', *args, **kwargs)

        self.scrolledPanel = ScrolledPanel(self)

        self.initSourceConfig()
        self.initChannelConfig()
        self.initMessageArea()
        self.initLayout()

        self.selectSource()

    def initSourceConfig(self):
        """Initialize the source configuration area.
        """
        # Generate a dictionary of configuration panels for each source.
        # dictionary mapping source name to a configuration panel
        self.srcPanels = {
            src.getName(): src.genConfigPanel(parent=self.scrolledPanel)
            for src in self.mgr.getAllSources()
        }

        self.curSrcPanel = self.srcPanels[self.src.getName()]

        # sizer for source configuration options general to all sources
        sourceGeneralSizer = wx.BoxSizer(orient=wx.HORIZONTAL)

        # source selector
        sourceControlBox = widgets.ControlBox(self.scrolledPanel,
                                              label='Source',
                                              orient=wx.VERTICAL)
        choices = sorted(list(self.srcPanels.keys()),
                         key=lambda k: k.lower(),
                         reverse=True)
        self.sourceComboBox = wx.ComboBox(self.scrolledPanel,
                                          id=wx.ID_ANY,
                                          choices=choices,
                                          value=self.src.getName(),
                                          style=wx.CB_READONLY)
        self.sourceComboBox.Bind(wx.EVT_COMBOBOX, self.selectSource,
                                 self.sourceComboBox)
        sourceControlBox.Add(self.sourceComboBox,
                             proportion=0,
                             flag=wx.ALL,
                             border=10)

        # query button
        self.sourceQueryButton = wx.Button(self.scrolledPanel, label='Query')
        sourceControlBox.Add(self.sourceQueryButton,
                             proportion=0,
                             flag=wx.LEFT | wx.BOTTOM | wx.RIGHT | wx.EXPAND,
                             border=10)
        self.Bind(wx.EVT_BUTTON, self.querySource, self.sourceQueryButton)

        # reset button
        self.sourceResetButton = wx.Button(self.scrolledPanel, label='Reset')
        sourceControlBox.Add(self.sourceResetButton,
                             proportion=0,
                             flag=wx.LEFT | wx.BOTTOM | wx.RIGHT | wx.EXPAND,
                             border=10)
        #self.Bind(wx.EVT_BUTTON, self.resetSource, self.sourceResetButton)
        sourceGeneralSizer.Add(sourceControlBox,
                               proportion=0,
                               flag=wx.RIGHT | wx.TOP | wx.LEFT,
                               border=10)

        bufferSizer = wx.BoxSizer(orient=wx.VERTICAL)

        # buffer seconds selector
        bufferSecsControlBox = widgets.ControlBox(self.scrolledPanel,
                                                  label='Buffer Size',
                                                  orient=wx.HORIZONTAL)

        self.bufferRollSpinCtrl = wx.SpinCtrl(self.scrolledPanel,
                                              style=wx.SP_WRAP,
                                              value=str(3),
                                              min=2,
                                              max=10,
                                              size=(100, 28))
        bufferSecsControlBox.Add(self.bufferRollSpinCtrl,
                                 proportion=0,
                                 flag=wx.ALL | wx.CENTER,
                                 border=10)

        xStaticText = wx.StaticText(self.scrolledPanel, label='X')
        bufferSecsControlBox.Add(xStaticText, proportion=0, flag=wx.CENTER)

        self.bufferSecsSpinCtrl = wx.SpinCtrl(self.scrolledPanel,
                                              style=wx.SP_WRAP,
                                              value=str(300),
                                              min=60,
                                              max=1000,
                                              size=(125, 28))
        bufferSecsControlBox.Add(self.bufferSecsSpinCtrl,
                                 proportion=0,
                                 flag=wx.ALL | wx.CENTER,
                                 border=10)

        bufferSizer.Add(bufferSecsControlBox,
                        proportion=1,
                        flag=wx.TOP | wx.RIGHT | wx.EXPAND,
                        border=10)

        precisControlBox = widgets.ControlBox(self.scrolledPanel,
                                              label='Data Precision',
                                              orient=wx.HORIZONTAL)
        self.precisSingleButton = wx.RadioButton(self.scrolledPanel,
                                                 label='Single',
                                                 style=wx.RB_GROUP)
        precisControlBox.Add(self.precisSingleButton,
                             proportion=0,
                             flag=wx.TOP | wx.LEFT | wx.BOTTOM | wx.CENTER,
                             border=10)

        self.precisDoubleButton = wx.RadioButton(self.scrolledPanel,
                                                 label='Double')
        precisControlBox.Add(self.precisDoubleButton,
                             proportion=0,
                             flag=wx.ALL | wx.CENTER,
                             border=10)
        self.precisDoubleButton.SetValue(True)
        #self.Bind(wx.EVT_RADIOBUTTON, self.setExtendedPrecision, self.precisExtendedButton)

        bufferSizer.Add(precisControlBox,
                        proportion=1,
                        flag=wx.TOP | wx.RIGHT | wx.EXPAND,
                        border=10)

        sourceGeneralSizer.Add(bufferSizer, proportion=0, flag=wx.EXPAND)

        sourceSpecificSizer = wx.BoxSizer(orient=wx.VERTICAL)

        # add each source configuration panel (we'll hide/show as needed)
        for sp in self.srcPanels.values():
            sourceSpecificSizer.Add(sp, proportion=1)  #, flag=wx.EXPAND)

        # sizer for both general and specific configurations
        self.sourceSizer = wx.BoxSizer(orient=wx.VERTICAL)
        self.sourceSizer.Add(sourceGeneralSizer, proportion=0, flag=wx.EXPAND)
        self.sourceSizer.Add(sourceSpecificSizer, proportion=1, flag=wx.EXPAND)

    def initChannelConfig(self):
        """Initialize the channel configuration area.
        """
        # controlbox to surround the area
        chanControlBox = widgets.ControlBox(self.scrolledPanel,
                                            label='Channels',
                                            orient=wx.HORIZONTAL)

        # only supports two columns, this could probably be done better XXX - idfah
        ## # left column
        ## leftChanSizer = wx.BoxSizer(orient=wx.VERTICAL)

        ## # create text controls
        ## self.chanTextCtrls = []
        ## for c in range(16):
        ##     curChanTextCtrl = wx.TextCtrl(self.scrolledPanel)
        ##     self.chanTextCtrls.append(curChanTextCtrl)
        ##     leftChanSizer.Add(curChanTextCtrl, proportion=0,
        ##         flag=wx.RIGHT | wx.TOP | wx.LEFT, border=10)

        ## # add to sizer
        ## chanControlBox.Add(leftChanSizer)

        ## # right column
        ## rightChanSizer = wx.BoxSizer(orient=wx.VERTICAL)

        ## # create text controls
        ## for c in range(16):
        ##     curChanTextCtrl = wx.TextCtrl(self.scrolledPanel)
        ##     self.chanTextCtrls.append(curChanTextCtrl)
        ##     rightChanSizer.Add(curChanTextCtrl, proportion=0,
        ##         flag=wx.RIGHT | wx.TOP | wx.LEFT, border=10)

        ## # add to sizer
        ## chanControlBox.Add(rightChanSizer)

        self.chanSizer = wx.GridSizer(40, 2, 10, 10)
        #self.chanSizer = wx.BoxSizer(orient=wx.VERTICAL)

        self.chanTextCtrls = [
            wx.TextCtrl(self.scrolledPanel) for i in range(40 * 2)
        ]
        self.chanSizer.AddMany(self.chanTextCtrls)
        #for ctc in self.chanTextCtrls:
        #    self.chanSizer.Add(ctc, proportion=0, flag=wx.TOP | wx.LEFT | wx.RIGHT, border=2)

        chanControlBox.Add(self.chanSizer, flag=wx.ALL, border=10)

        # sizer for channel configuration area
        self.chanSizer = wx.BoxSizer(orient=wx.VERTICAL)
        self.chanSizer.Add(chanControlBox,
                           proportion=1,
                           flag=wx.TOP | wx.BOTTOM,
                           border=10)

    def initMessageArea(self):
        """Initialize the message log area.
        """
        # font for messages
        msgFont = wx.Font(pointSize=11,
                          family=wx.FONTFAMILY_MODERN,
                          style=wx.FONTSTYLE_NORMAL,
                          weight=wx.FONTWEIGHT_NORMAL,
                          underline=False)

        # font for CEBL introduction message
        helloFont = wx.Font(pointSize=24,
                            family=wx.FONTFAMILY_ROMAN,
                            style=wx.FONTSTYLE_NORMAL,
                            weight=wx.FONTWEIGHT_BOLD,
                            underline=True)

        # the message log
        messageControlBox = widgets.ControlBox(self.scrolledPanel,
                                               label='Message Log',
                                               orient=wx.VERTICAL)
        self.messageArea = wx.TextCtrl(self.scrolledPanel,
                                       style=wx.TE_MULTILINE | wx.TE_READONLY
                                       | wx.TE_RICH)
        self.messageArea.SetMinSize((150, 150))
        messageControlBox.Add(self.messageArea,
                              proportion=1,
                              flag=wx.ALL | wx.EXPAND,
                              border=10)

        # intro message
        self.messageArea.SetDefaultStyle(
            wx.TextAttr(colText=wx.Colour('black'), font=helloFont))
        self.messageArea.AppendText('Welcome to CEBL!\n\n')

        # setup message style
        self.messageArea.SetDefaultStyle(wx.TextAttr())
        self.messageArea.SetDefaultStyle(
            wx.TextAttr(colText=wx.Colour('black'), font=msgFont))

        # add the message area text ctrl widget as a log target
        self.mgr.logger.addTextCtrl(self.messageArea)

        messageControlSizer = wx.BoxSizer(orient=wx.HORIZONTAL)

        # button for saving the message log to a file
        self.saveMessagesButton = wx.Button(self.scrolledPanel, label='Save')
        messageControlSizer.Add(self.saveMessagesButton,
                                proportion=0,
                                flag=wx.LEFT | wx.BOTTOM | wx.RIGHT,
                                border=10)
        self.Bind(wx.EVT_BUTTON, self.saveMessages, self.saveMessagesButton)

        # button for clearing the message log
        self.clearMessagesButton = wx.Button(self.scrolledPanel, label='Clear')
        messageControlSizer.Add(self.clearMessagesButton,
                                proportion=0,
                                flag=wx.BOTTOM | wx.RIGHT,
                                border=10)
        self.Bind(wx.EVT_BUTTON, self.clearMessages, self.clearMessagesButton)

        # set up verbose logging
        self.verboseMessagesCheckBox = wx.CheckBox(self.scrolledPanel,
                                                   label='Verbose')
        messageControlSizer.Add(self.verboseMessagesCheckBox,
                                proportion=0,
                                flag=wx.BOTTOM | wx.RIGHT,
                                border=10)

        messageControlBox.Add(messageControlSizer,
                              proportion=0,
                              flag=wx.EXPAND)

        # sizer for message log area
        self.messageSizer = wx.BoxSizer(orient=wx.VERTICAL)
        self.messageSizer.Add(messageControlBox,
                              proportion=1,
                              flag=wx.ALL | wx.EXPAND,
                              border=10)

    def initLayout(self):
        """Initialize the page layout.
        """
        scrolledSizer = wx.BoxSizer(orient=wx.HORIZONTAL)
        scrolledSizer.Add(self.sourceSizer, proportion=0)  #, flag=wx.EXPAND)
        scrolledSizer.Add(self.chanSizer, proportion=0, flag=wx.EXPAND)
        scrolledSizer.Add(self.messageSizer, proportion=1, flag=wx.EXPAND)
        self.scrolledPanel.SetSizer(scrolledSizer)

        # main sizer
        sizer = wx.BoxSizer(orient=wx.HORIZONTAL)
        sizer.Add(self.scrolledPanel, proportion=1, flag=wx.EXPAND)
        self.SetSizer(sizer)

        self.scrolledPanel.Layout()
        self.scrolledPanel.FitInside()
        self.scrolledPanel.SetupScrolling()

        # hide after layout (prevents gtk warnings)
        for sp in self.srcPanels.values():
            sp.deselect()

    def updateChanText(self):
        for ctc in self.chanTextCtrls:
            ctc.Clear()
            ctc.Hide()

        chanNames = self.src.getChanNames()
        ctls = self.chanTextCtrls[:len(chanNames)]

        for chan, ctl in zip(chanNames, ctls[0::2] + ctls[1::2]):
            ctl.Show()
            ctl.AppendText(chan)

    def afterUpdateSource(self):
        self.updateChanText()

    def selectSource(self, event=None):
        """Set a new source from the source selection combobox.
        """
        srcName = self.sourceComboBox.GetValue()

        # deselect previous source config panel
        self.curSrcPanel.deselect()

        # select new source config panel
        self.curSrcPanel = self.srcPanels[srcName]
        self.curSrcPanel.select()

        # set the source in the manager
        self.mgr.setSource(srcName)

        # add source description to the message log
        wx.LogMessage(repr(self.src) + '\n')

        # update the text in the channel configuration area
        self.updateChanText()

        # adjust layout since we have shown and hidden panels
        self.scrolledPanel.Layout()
        self.scrolledPanel.FitInside()

    def querySource(self, event=None):
        """Call the query method on the current source
        and put the output in the message log.
        """
        try:
            wx.LogMessage(self.src.query())
        except Exception as e:
            wx.LogError('Failed to query source: ' + str(e))

    def clearMessages(self, event=None):
        """Clear the message log.
        """
        self.messageArea.Clear()

    def saveMessages(self, event=None):
        """Save the message log to file.
        """
        saveDialog = wx.FileDialog(
            self.scrolledPanel,
            message='Save Message Log',
            wildcard='Text Files (*.txt)|*.txt|All Files|*',
            style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)

        try:
            if saveDialog.ShowModal() == wx.ID_CANCEL:
                return
            with open(saveDialog.GetPath(), 'w') as fileHandle:
                fileHandle.write(self.messageArea.GetValue())
        except Exception as e:
            wx.LogError('Save failed!')
            raise
        finally:
            saveDialog.Destroy()