import matplotlib
matplotlib.use("WXAgg")
matplotlib.interactive(False)
from wx.lib.agw import floatspin
from PhysioMonitor.Threads import ComediThread, SerialThread
from PhysioMonitor.TrendPlot import TrendPlot, doGetAVG, doGetHR, \
    doGetLastPeakValue
from PhysioMonitor.TriggerPlot import triggerTypes
from PhysioMonitor.Utils import convertInVolts, UpdateCanvasEvent, \
    EVT_UPDATE_CANVAS
from SurgicalLogValidators import *
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
from matplotlib.figure import Figure
from platform import system
from wx import xrc
import comedi
import gst
import sys

DEBUG = True
MAXINT = 0x7fffffff


###############################################################################
# GUI classes
###############################################################################
class customValueDialog(wx.Dialog):
    def __init__(self, parent, pos, value, id=-1, title='', name=wx.DialogNameStr):  # @ReservedAssignment
        """Constructor"""
        style = wx.CAPTION
        size = wx.Size(125, 20)

        # call the parent constructor
        wx.Dialog.__init__(self, parent, id, title, pos, wx.DefaultSize, style, name)

        # add spin box
        self.customValueBox = wx.SpinCtrl(self, size=size)
        self.customValueBox.SetRange(0, MAXINT)
        self.customValueBox.SetValue(value)
        self.customValueBox.SetFocus()
        self.customValueBox.SetSelection(0, MAXINT)

        self.Fit()

        # bind keyboard listener
        if system() == 'Windows':
            self.customValueBox.Bind(wx.EVT_KEY_UP, self.onChar)
        else:
            for child in self.customValueBox.GetChildren():
                child.Bind(wx.EVT_KEY_UP, self.onChar)
            self.customValueBox.Bind(wx.EVT_KEY_UP, self.onChar)
            self.Bind(wx.EVT_KEY_UP, self.onChar)

    def onChar(self, event):
        # print "in onChar"
        key = event.GetKeyCode()
        # print "got code %d"%key

        if key == wx.WXK_ESCAPE:
            # print "got ESCAPE, closing modal with return code %d"%wx.ID_CANCEL
            self.EndModal(wx.ID_CANCEL)
            return

        if key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER:
            # print "got RETURN, closing modal with return code %d"%wx.ID_OK
            self.EndModal(wx.ID_OK)
            return

        # print "got uninteresting key, skipping"
        event.Skip()


class drugPanelClass():
    def __init__(self, parent, drugName, drugDose=0.0, logBox=None):
        """Constructor"""
        # drug info
        self.drugName = drugName
        self.drugDose = drugDose
        self.customDose = 0.0

        self.ALARM_DURATION = 10
        self.__alarmPipeline = gst.Pipeline("mypipeline")
        self.__alarmSound = gst.element_factory_make("audiotestsrc", "audio")
        self.__alarmPipeline.add(self.__alarmSound)
        self.__alarmSink = gst.element_factory_make("alsasink", "__alarmSink")
        self.__alarmPipeline.add(self.__alarmSink)
        self.__alarmSound.link(self.__alarmSink)
        self.__alarmSound.set_property("freq", 500)

        # GUI windows
        self.logBox = logBox
        self.panel = xrc.XmlResource.Get().LoadPanel(parent, 'drugPanel')
        self.label = self.panel.FindWindowByName("drugNameLabel")
        self.drugTimerLabel = self.panel.FindWindowByName("drugTimerLabel")
        self.fullDoseButton = self.panel.FindWindowByName("fullDoseButton")
        self.halfDoseButton = self.panel.FindWindowByName("halfDoseButton")
        self.quartDoseButton = self.panel.FindWindowByName("quartDoseButton")
        self.customDoseButton = self.panel.FindWindowByName("customDoseButton")
        self.alarmsRadioBox = self.panel.FindWindowByName("drugAlarmsRadioBox")

        # timers
        self.drugTimer = wx.Timer(self.panel, -1)
        self.alarmTimer = wx.Timer(self.panel, -1)
        # event times
        self.drugTime = None
        self.alarmTime = None

        # other variables
        self.normalLabelForeground = self.drugTimerLabel.GetForegroundColour()
        self.alarmLabelForeground = "#FF0000"

        label = "%s (%d uL)" % (self.drugName, self.drugDose)
        self.setLabel(label)
        self.panel.Bind(wx.EVT_BUTTON, self.onFullDoseButton, id=self.fullDoseButton.GetId())
        self.panel.Bind(wx.EVT_BUTTON, self.onHalfDoseButton, id=self.halfDoseButton.GetId())
        self.panel.Bind(wx.EVT_BUTTON, self.onQuartDoseButton, id=self.quartDoseButton.GetId())
        self.panel.Bind(wx.EVT_BUTTON, self.onCustomDoseButton, id=self.customDoseButton.GetId())
        self.panel.Bind(wx.EVT_TIMER, self.onDrugTimer, id=self.drugTimer.GetId())
        self.panel.Bind(wx.EVT_TIMER, self.onAlarmTimer, id=self.alarmTimer.GetId())
        self.drugTimerLabel.Bind(wx.EVT_LEFT_DCLICK, self.onDrugTimerLabelClick)
        self.label.Bind(wx.EVT_LEFT_DCLICK, self.onLabelClick)
        self.alarmsRadioBox.Bind(wx.EVT_RADIOBOX, self.onAlarmChoose, id=self.alarmsRadioBox.GetId())

    def getDrugName(self):
        return self.drugName

    def getDrugDose(self):
        return self.drugDose

    def setDrugName(self, value):
        self.drugName = value

    def setDrugDose(self, value):
        self.drugDose = value

    def getPanel(self):
        return self.panel

    def setLabel(self, value):
        self.label.SetLabel(value)

    def onLabelClick(self, event):
        editDrugDlg = xrc.XmlResource.Get().LoadDialog(None, "drugOptionsDlg")
        drugNameBox = editDrugDlg.FindWindowByName("drugNameTextBox")
        drugDoseBox = editDrugDlg.FindWindowByName("drugDoseSpinCtrl")
        editDrugDlg.FindWindowByName('ID_OK').SetId(wx.ID_OK)
        editDrugDlg.FindWindowByName('ID_CANCEL').SetId(wx.ID_CANCEL)
        drugNameBox.SetValue(self.drugName)
        drugDoseBox.SetValue(self.drugDose)
        if editDrugDlg.ShowModal() == wx.ID_OK:
            self.drugName = drugNameBox.GetValue()
            self.drugDose = drugDoseBox.GetValue()
            label = "%s (%d uL)" % (self.drugName, self.drugDose)
            self.setLabel(label)
        editDrugDlg.Destroy()

    def onFullDoseButton(self, event):
        #        print "in drugPanelClass.onFullDoseButton()" #DEBUG
        self.printDose(self.drugDose)
        self.startDrugTimer()

    def onHalfDoseButton(self, event):
        self.printDose(self.drugDose / 2)
        self.startDrugTimer()

    def onQuartDoseButton(self, event):
        self.printDose(self.drugDose / 4)
        self.startDrugTimer()

    def onCustomDoseButton(self, event):
        buttonRect = event.GetEventObject().GetScreenRect()
        newPos = wx.Point()
        newPos.x = buttonRect.GetLeft() + buttonRect.GetWidth() / 2
        newPos.y = buttonRect.GetTop() + buttonRect.GetHeight() / 2
        customValueDlg = customValueDialog(None, newPos, self.customDose)
        if customValueDlg.ShowModal() == wx.ID_OK:
            self.customDose = customValueDlg.customValueBox.GetValue()
            self.printDose(self.customDose)
            self.startDrugTimer()
        customValueDlg.Destroy()

    def startDrugTimer(self):
        #        print "drugPanelClass.startDrugTimer()" #DEBUG
        self.drugTime = time.time()
        self.drugTimer.Start(1000)  # clock event every 1sec
        # reset the alarm
        self.alarmTime = None
        self.drugTimerLabel.SetForegroundColour(self.normalLabelForeground)

    def printDose(self, dose):
        message = time.strftime("%H:%M\t", time.localtime())
        message += "%4.0f uL" % (dose)
        message += "\t%s\t\t| \n" % (self.drugName)

        if isinstance(self.logBox, wx.TextCtrl):
            if (not self.logBox.IsEmpty()) and self.logBox.GetValue()[-1] <> '\n':
                self.logBox.AppendText('\n')
            self.logBox.AppendText(message)

            # AppendText doesn't seem to send a wx.EVT_TEXT to logBox
            # generate one
            event = wx.PyEvent()
            event.SetEventType(wx.wxEVT_COMMAND_TEXT_UPDATED)
            self.logBox.GetEventHandler().ProcessEvent(event)

        else:
            print message

    def onDrugTimer(self, event):
        if self.drugTime is not None:
            timeDiff = time.time() - self.drugTime
            self.drugTimerLabel.SetLabel(time.strftime("%M:%S", time.gmtime(timeDiff)))
            if self.alarmsRadioBox.GetSelection() > 0:
                try:
                    alarmTimeInMin = int(self.alarmsRadioBox.GetStringSelection(), 10)
                    if timeDiff / 60 >= alarmTimeInMin and self.alarmTime is None:
                        # alerts the user that the timer has reached the time set by their alarm
                        self.drugTimerAlarm()
                        # maybe do something here
                        #                        print "time limit has been reached!" #DEBUG
                        self.alarmAction()
                except:
                    pass

    def drugTimerAlarm(self):
        self.alarmTime = time.time()
        self.alarmTimer.Start(500)

    def onAlarmTimer(self, event):
        if (self.alarmTime is None):  # alarm has been reset while the timer was running
            self.drugTimerLabel.SetForegroundColour(self.normalLabelForeground)
            self.__alarmPipeline.set_state(gst.STATE_NULL)
            self.alarmTimer.Stop()
        elif (self.alarmTimer.IsRunning()) and (time.time() - self.alarmTime < self.ALARM_DURATION):
            if self.drugTimerLabel.GetForegroundColour() == self.normalLabelForeground:
                self.__alarmPipeline.set_state(gst.STATE_PLAYING)
                self.drugTimerLabel.SetForegroundColour(self.alarmLabelForeground)
            else:
                self.__alarmPipeline.set_state(gst.STATE_NULL)
                self.drugTimerLabel.SetForegroundColour(self.normalLabelForeground)
        else:
            self.drugTimerLabel.SetForegroundColour(self.alarmLabelForeground)
            self.__alarmPipeline.set_state(gst.STATE_NULL)
            self.alarmTimer.Stop()

    def alarmAction(self):
        pass

    def onDrugTimerLabelClick(self, event):
        self.drugTimerLabel.SetForegroundColour(self.normalLabelForeground)
        self.alarmTimer.Stop()
        self.alarmTime = None
        self.drugTimer.Stop()
        self.drugTimerLabel.SetLabel("00:00")
        event.Skip()

    def onAlarmChoose(self, event):
        if self.alarmsRadioBox.GetSelection() == 3:  # custom button
            buttonRect = event.GetEventObject().GetScreenRect()
            newPos = wx.Point()
            newPos.x = buttonRect.GetLeft() + buttonRect.GetWidth() / 2
            newPos.y = buttonRect.GetTop() + buttonRect.GetHeight() / 2
            try:
                customValue = int(self.alarmsRadioBox.GetItemLabel(3), 10)
            except ValueError:
                customValue = 0
            customValueDlg = customValueDialog(None, newPos, customValue)
            if customValueDlg.ShowModal() == wx.ID_OK:
                self.alarmsRadioBox.SetItemLabel(3, str(customValueDlg.customValueBox.GetValue()))
            customValueDlg.Destroy()

        # reset the alarm timer
        self.alarmTimer.Stop()
        self.alarmTime = None
        self.drugTimerLabel.SetForegroundColour(self.normalLabelForeground)
        event.Skip()

    _drugName = property(getDrugName, setDrugName, None,
                         "The name of the drug. This is the text that will be written in the log file")
    _drugDose = property(getDrugDose, setDrugDose, None, "The dosage in units of weight of the drug")


class drugPumpPanelClass(drugPanelClass):
    def __init__(self, parent, drugName, drugDose, logBox, pumpInterface):
        """Constructor"""
        # @type pump syringePumpClass
        self.pump = pumpInterface

        # drug info
        self.drugName = drugName
        self.drugDose = drugDose
        self.customDose = 0.0
        self.INJECTION_RATE = 0.25
        self.INJECTION_UNITS = 1
        self.ALARM_DURATION = 10

        self.__alarmPipeline = gst.Pipeline("mypipeline")
        self.__alarmSound = gst.element_factory_make("audiotestsrc", "audio")
        self.__alarmPipeline.add(self.__alarmSound)
        self.__alarmSink = gst.element_factory_make("alsasink", "__alarmSink")
        self.__alarmPipeline.add(self.__alarmSink)
        self.__alarmSound.link(self.__alarmSink)
        self.__alarmSound.set_property("freq", 500)

        self.currDirection = None
        self.currRate = None
        self.currUnits = None

        # GUI windows
        self.logBox = logBox
        self.panel = xrc.XmlResource.Get().LoadPanel(parent, 'drugPumpPanel')
        self.label = self.panel.FindWindowByName("drugNameLabel")
        self.startPerfButton = self.panel.FindWindowByName("startPerfButton")
        self.perfRateSpinCtrl = self.panel.FindWindowByName("perfRateSpinCtrl")
        self.perfUnitChoice = self.panel.FindWindowByName("perfUnitChoice")
        self.perfAmountLabel = self.panel.FindWindowByName("perfAmountLabel")
        self.drugTimerLabel = self.panel.FindWindowByName("drugTimerLabel")
        self.fullDoseButton = self.panel.FindWindowByName("fullDoseButton")
        self.halfDoseButton = self.panel.FindWindowByName("halfDoseButton")
        self.quartDoseButton = self.panel.FindWindowByName("quartDoseButton")
        self.customDoseButton = self.panel.FindWindowByName("customDoseButton")
        self.alarmsRadioBox = self.panel.FindWindowByName("drugAlarmsRadioBox")
        self.autoInjectCheckBox = self.panel.FindWindowByName("autoInjectCheckBox")

        # this is a hack to replace the wxSpinCtrl with a FloatSpin, but I don't
        # know how to put the FloatSpin in the XRC, so I will destroy the SpinCtrl
        # create a FloatSpin and put it in its place.
        self.sizer = self.perfRateSpinCtrl.GetContainingSizer()
        self.perfRateSpinCtrl.Destroy()
        self.perfRateSpinCtrl = floatspin.FloatSpin(self.panel, id=wx.ID_ANY,
                                                    pos=wx.DefaultPosition, size=(95, -1), style=0, value=0.0,
                                                    min_val=None, max_val=None, increment=0.1, digits=2,
                                                    #            extrastyle=floatspin.FS_RIGHT, name="FloatSpin")
                                                    name="FloatSpin")
        self.sizer.Add(self.perfRateSpinCtrl, (1, 1))

        # timers
        self.drugTimer = wx.Timer(self.panel, -1)
        self.alarmTimer = wx.Timer(self.panel, -1)
        # event times
        self.drugTime = None
        self.alarmTime = None

        # other variables
        self.normalLabelForeground = self.drugTimerLabel.GetForegroundColour()
        self.alarmLabelForeground = "#FF0000"

        label = "%s (%d uL)" % (self.drugName, self.drugDose)
        self.setLabel(label)

        if self.pump is not None:
            self.perfUnitChoice.SetItems(self.pump.getPossibleUnits())

        # event binding
        self.panel.Bind(wx.EVT_TOGGLEBUTTON, self.onStartPerfButton, id=self.startPerfButton.GetId())
        self.panel.Bind(wx.EVT_SPINCTRL, self.onPerfRateChange, id=self.perfRateSpinCtrl.GetId())
        self.panel.Bind(wx.EVT_CHOICE, self.onPerfUnitChange, id=self.perfUnitChoice.GetId())
        self.panel.Bind(wx.EVT_BUTTON, self.onFullDoseButton, id=self.fullDoseButton.GetId())
        self.panel.Bind(wx.EVT_BUTTON, self.onHalfDoseButton, id=self.halfDoseButton.GetId())
        self.panel.Bind(wx.EVT_BUTTON, self.onQuartDoseButton, id=self.quartDoseButton.GetId())
        self.panel.Bind(wx.EVT_BUTTON, self.onCustomDoseButton, id=self.customDoseButton.GetId())
        self.panel.Bind(wx.EVT_TIMER, self.onDrugTimer, id=self.drugTimer.GetId())
        self.panel.Bind(wx.EVT_TIMER, self.onAlarmTimer, id=self.alarmTimer.GetId())
        self.drugTimerLabel.Bind(wx.EVT_LEFT_DCLICK, self.onDrugTimerLabelClick)
        self.label.Bind(wx.EVT_LEFT_DCLICK, self.onLabelClick)
        self.alarmsRadioBox.Bind(wx.EVT_RADIOBOX, self.onAlarmChoose, id=self.alarmsRadioBox.GetId())

        self.updateGUI()

    def updateGUI(self):
        pass  # FIXME
        if self.pump is not None:
            # fill in properties based on the state of the pump
            self.currDirection = 1
            self.pump.setDirection(self.currDirection)
            self.currRate = self.pump.getRate()
            self.currUnits = self.pump.getUnits()

            # update GUI elements in consequence
            if not self.pump.isRunning():
                self.startPerfButton.SetValue(False)
                self.perfRateSpinCtrl.Enable(True)
                self.perfUnitChoice.Enable(True)
            else:
                self.startPerfButton.SetValue(True)
                self.perfRateSpinCtrl.Enable(False)
                self.perfUnitChoice.Enable(False)

            self.perfRateSpinCtrl.SetValue(self.currRate)
            self.perfUnitChoice.SetSelection(self.currUnits)

        else:
            self.panel.Enable(False)

    def onStartPerfButton(self, event):
        if self.startPerfButton.GetValue():  # Value is TRUE when pressed
            rate = float(self.perfRateSpinCtrl.GetValue())
            unitsIndex = self.perfUnitChoice.GetSelection()
            if self.pump is not None:
                try:
                    self.pump.setRate(rate, unitsIndex)
                    self.pump.clearTargetVolume()

                    #                    print "setting NULL color in onStartPerfButton()" #DEBUG
                    self.perfRateSpinCtrl.SetBackgroundColour(wx.NullColour)
                    # self.perfRateSpinCtrl.SetForegroundColour(wx.NullColour)

                    self.currRate = self.pump.getRate()
                    self.currUnits = self.pump.getUnits()

                    self.pump.start()

                    # write in the log
                    message = time.strftime("%H:%M\t", time.localtime())
                    message += "Start perf %s [%.3f %s]\t\t|\n" % (
                        self.drugName, self.currRate, self.pump._units[self.currUnits])
                    self.logBox.AppendText(message)

                except Exception as e:
                    print e
                    wx.Bell()

                    self.perfRateSpinCtrl.SetFocus()
                    #                    print "setting RED color in onStartPerfButton()" #FIXME-this does not work
                    self.perfRateSpinCtrl.SetBackgroundColour(wx.RED)
                    # self.perfRateSpinCtrl.SetForegroundColour(wx.RED)

            else:  # there is no pump
                # write in the log
                message = time.strftime("%H:%M\t", time.localtime())
                message += "Start perf %s [%.3f %s]\t\t|\n" % (self.drugName, rate, '')  # units
                self.logBox.AppendText(message)

            # disable the rate and unit controls
            self.perfRateSpinCtrl.Enable(False)
            self.perfUnitChoice.Enable(False)


        else:  # unclick button
            if self.pump is not None:
                if self.pump.isRunning(): self.pump.stop()

            # reactivate rate and unit buttons
            self.perfRateSpinCtrl.Enable(True)
            self.perfUnitChoice.Enable(True)

            # write in the log
            message = time.strftime("%H:%M\t", time.localtime())
            message += "Stop perf %s\t\t\t|\n" % self.drugName
            self.logBox.AppendText(message)

        # finally, update GUI
        self.updateGUI()

    def onAlarmTimer(self, event):
        # i have to overload this function because I'm not calling the parent constructor
        # and therefore the gst objects are created in this object and not the parent's
        if (self.alarmTime is None):  # alarm has been reset while the timer was running
            self.drugTimerLabel.SetForegroundColour(self.normalLabelForeground)
            self.__alarmPipeline.set_state(gst.STATE_NULL)
            self.alarmTimer.Stop()
        elif (self.alarmTimer.IsRunning()) and (time.time() - self.alarmTime < self.ALARM_DURATION):
            if self.drugTimerLabel.GetForegroundColour() == self.normalLabelForeground:
                self.__alarmPipeline.set_state(gst.STATE_PLAYING)
                self.drugTimerLabel.SetForegroundColour(self.alarmLabelForeground)
            else:
                self.__alarmPipeline.set_state(gst.STATE_NULL)
                self.drugTimerLabel.SetForegroundColour(self.normalLabelForeground)
        else:
            self.drugTimerLabel.SetForegroundColour(self.alarmLabelForeground)
            self.__alarmPipeline.set_state(gst.STATE_NULL)
            self.alarmTimer.Stop()

    def onPerfRateChange(self, event):
        pass

    def onPerfUnitChange(self, event):
        pass

    def alarmAction(self):
        #        print "in drugPumpPanelClass.alarmAction()" #DEBUG
        drugPanelClass.alarmAction(self)
        # if auto-inject check box is checked
        if self.autoInjectCheckBox.GetValue():
            #            print "injecting full dose" #DEBUG
            time.sleep(1)
            self.onFullDoseButton(None)

    def onFullDoseButton(self, event):
        #        print "in drugPumpPanelClass.onFullDoseButton()" #DEBUG
        drugPanelClass.onFullDoseButton(self, event)
        self.injectVolume(self.drugDose)

    def onHalfDoseButton(self, event):
        drugPanelClass.onHalfDoseButton(self, event)
        self.injectVolume(self.drugDose / 2)

    def onQuartDoseButton(self, event):
        drugPanelClass.onQuartDoseButton(self, event)
        self.injectVolume(self.drugDose / 4)

    def onCustomDoseButton(self, event):
        drugPanelClass.onCustomDoseButton(self, event)
        self.injectVolume(self.customDose)

    def injectVolume(self, injVolume):
        if self.pump is not None:
            if DEBUG: print "direction is currently %d" % self.currDirection
            oldDirection = self.pump.getDirection()
            if self.pump.isRunning(): self.pump.stop()

            self.pump.setRate(self.INJECTION_RATE, self.INJECTION_UNITS)
            tempVolume = injVolume / 1000.0
            self.pump.setTargetVolume(tempVolume)
            self.pump.start()

            # wait for the injection to finish
            while self.pump.isRunning():
                #                    print "injection still going, waiting..." #DEBUG
                time.sleep(0.25)

            self.pump.clearTargetVolume()
            self.pump.setRate(self.currRate, self.currUnits)

            if oldDirection > 0:
                if DEBUG: print "direction before injection was %d, so restarting the pump" % oldDirection
                self.pump.start()
            else:
                if self.pump.isRunning():
                    self.pump.stop()

            self.updateGUI()


class otherDrugPanelClass(drugPanelClass):
    # this class is used when injecting a customizable amount of an other drug
    # than those defined in drugList
    def __init__(self, parent, logBox):
        # call parent constructor
        drugPanelClass.__init__(self, parent, "", 0.0, logBox)

        # remove ununsed dose buttons
        self.halfDoseButton.Destroy()
        self.quartDoseButton.Destroy()
        self.customDoseButton.Destroy()
        self.setLabel("Other drug...")
        self.fullDoseButton.SetLabel("Inject bolus")

    def onFullDoseButton(self, event):
        customValueDlg = customDrugDoseDialog(None, self.drugName, self.drugDose)
        if customValueDlg.ShowModal() == wx.ID_OK:
            self.drugDose = customValueDlg.customValueBox.GetValue()
            self.drugName = customValueDlg.customNameBox.GetValue()
            self.printDose(self.drugDose)
            self.startDrugTimer()
        customValueDlg.Destroy()

    def onLabelClick(self, event):
        # event when dbl click on the label
        # in this case, do nothing
        pass


class customDrugDoseDialog(customValueDialog):
    def __init__(self, parent, drugName, drugDose):
        wx.Dialog.__init__(self, parent)

        sizer1 = wx.BoxSizer(wx.VERTICAL)
        sizer2 = wx.BoxSizer(wx.HORIZONTAL)
        sizer3 = wx.BoxSizer(wx.HORIZONTAL)

        label = wx.StaticText(self, id=-1, label="Inject custom drug")

        self.customValueBox = wx.SpinCtrl(self, id=-1)
        self.customValueBox.SetRange(0, MAXINT)
        self.customValueBox.SetValue(drugDose)
        self.customValueBox.SetFocus()
        self.customValueBox.SetSelection(0, MAXINT)

        self.customNameBox = wx.TextCtrl(self, id=-1, value=drugName, size=(400, -1))

        self.okButton = wx.Button(self, id=wx.ID_OK, label="OK")

        self.cancelButton = wx.Button(self, id=wx.ID_CANCEL, label="Cancel")

        sizer2.Add(self.customValueBox, proportion=0)
        sizer2.Add(self.customNameBox, proportion=1, flag=wx.EXPAND)

        sizer3.Add(self.cancelButton)
        sizer3.Add(self.okButton)

        sizer1.Add(label, flag=wx.EXPAND | wx.ALIGN_LEFT | wx.BOTTOM | wx.TOP, border=10)
        sizer1.Add(sizer2, flag=wx.EXPAND | wx.BOTTOM, border=10)
        sizer1.Add(sizer3, flag=wx.ALIGN_RIGHT | wx.BOTTOM, border=10)

        self.SetSizer(sizer1)
        self.Center()
        self.Fit()

        self.Bind(wx.EVT_KEY_UP, self.onChar)
        for child in self.GetChildren():
            child.Bind(wx.EVT_KEY_UP, self.onChar)
            for child in self.GetChildren():
                child.Bind(wx.EVT_KEY_UP, self.onChar)


class clockPanelClass():
    def __init__(self, parent, timeFormat="%H:%M", dateFormat="%b %d %Y", refreshPeriod=500):
        self.timeFormat = timeFormat
        self.dateFormat = dateFormat
        self.refreshPeriod = refreshPeriod
        self.panel = xrc.XmlResource.Get().LoadPanel(parent, 'clockPanel')
        self.curTimeLabel = self.panel.FindWindowByName('curTimeLabel')
        self.curDateLabel = self.panel.FindWindowByName('curDateLabel')
        self.clockTimer = wx.Timer(self.panel, -1)
        self.clockTimer.Start(refreshPeriod)
        self.panel.Bind(wx.EVT_TIMER, self.onTimer)

    def getPanel(self):
        return self.panel

    def getTimeFormat(self):
        return self.timeFormat

    def getDateFormat(self):
        return self.dateFormat

    def getRefreshPeriod(self):
        return self.refreshPeriod

    def setTimeFormat(self, value):
        self.timeFormat = value

    def setDateFormat(self, value):
        self.dateFormat = value

    def setRefreshPeriod(self, value):
        self.refreshPeriod = value
        if self.clockTimer.IsRunning():
            self.clockTimer.Stop()
        self.clockTimer.Start(value)

    def onTimer(self, event):
        currentTimeDate = time.localtime()
        self.curTimeLabel.SetLabel(time.strftime(self.getTimeFormat(), currentTimeDate))
        self.curDateLabel.SetLabel(time.strftime(self.getDateFormat(), currentTimeDate))

    _timeFormat = property(getTimeFormat, setTimeFormat, None, "The format to output the time, in strftime format")
    _dateFormat = property(getDateFormat, setDateFormat, None, "The format to output the date, in strftime format")
    _refreshPeriod = property(getRefreshPeriod, setRefreshPeriod, None, "The refresh period of the clock, in ms")


class mainFrameClass(wx.Frame):
    def __init__(self, exp=expClass(), info=None, config=surgicalLogConfigClass(), pCO2Monitor=None, syringePump=None,
                 aiConfig=None):
        wx.Frame.__init__(self, parent=None, id=-1, title="Surgical Log", size=(800, 600), style=wx.DEFAULT_FRAME_STYLE)

        self.previousComment = ""
        self.exp = exp
        self.config = config
        self.pCO2Monitor = pCO2Monitor
        self.pump = syringePump
        self.aiConfig = aiConfig
        self.appInfo = info
        self.diagFile = None
        self.plots = []
        self.maxChanValues = []
        self.chanRanges = []
        self.arrayLock = threading.Semaphore(1)
        self.canvasLock = threading.Semaphore(1)
        self.comediThread = None
        self.dev = None
        self.subdev = None

        self.diagTimer = wx.Timer(self, -1)
        self.Bind(wx.EVT_TIMER, self.onDiagTimer, id=self.diagTimer.GetId())

        self.leftPanel = wx.Panel(self, id=-1)
        self.logBox = wx.TextCtrl(self, id=-1, style=wx.TE_MULTILINE)
        self.logBox.Hide()
        #        font = self.logBox.GetFont()
        #        if system()=='Darwin':
        #            font.SetFaceName('Monaco')
        #        if system()=='Windows':
        #                font.SetFaceName('Courier New')
        #        self.logBox.SetFont(font)

        clockPanel = clockPanelClass(self.leftPanel)
        self.newExpButton = wx.Button(self.leftPanel, -1, "Start new experiment")

        self.hbox = wx.BoxSizer(wx.HORIZONTAL)  # sizer for left and right part
        self.vbox = wx.BoxSizer(wx.VERTICAL)  # stack for clock and new exp button
        self.vbox2 = wx.BoxSizer(wx.VERTICAL)  # stack for drugPanels
        self.vbox3 = wx.BoxSizer(wx.VERTICAL)  # sizer for top button and canvas
        self.hbox2 = wx.BoxSizer(wx.HORIZONTAL)  # sizer for the button on top

        # create matplotlib figure to put all the plots in
        self.figure = Figure()
        self.canvas = FigureCanvasWxAgg(self, -1, self.figure)

        # adjust color FIXME: this does not work as expected
        rgbtuple = clockPanel.panel.GetBackgroundColour().Get()
        clr = [c / 255. for c in rgbtuple]
        self.figure.set_facecolor(clr)
        self.figure.set_edgecolor(clr)
        self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple))

        #        self.figure.set_facecolor( 'w' )
        #        self.figure.set_edgecolor( 'w' )
        #        self.canvas.SetBackgroundColour( wx.Colour(255,255,255) )

        # if DEBUG: print '### Creating plots'
        PLOT_MARGIN = 0.05
        PLOT_TOTAL_WIDTH = 1.0
        PLOT_TOTAL_HEIGHT = 1.0
        N = int(self.aiConfig['nbChans'])
        # if DEBUG: print "there are %d plot to fit on a 1.0 height"%(self.aiConfig['nbChans'])
        xLow = PLOT_MARGIN * PLOT_TOTAL_WIDTH
        width = PLOT_TOTAL_WIDTH - 2 * PLOT_TOTAL_WIDTH * PLOT_MARGIN
        height = (PLOT_TOTAL_HEIGHT - (N + 1) * PLOT_MARGIN * PLOT_TOTAL_HEIGHT) / N
        yLow = (1.0 - PLOT_MARGIN) * PLOT_TOTAL_HEIGHT - height
        for i in range(int(self.aiConfig['nbChans'])):  # @UnusedVariable
            # if DEBUG: print "plot [%d] rect is"%i,(xLow,yLow,width,height)
            plot = TrendPlot(self.figure, (xLow, yLow, width, height), sampleFreq=self.aiConfig['sampleFreq'])
            self.plots.append(plot)
            self.figure.add_axes(plot)
            yLow -= height + 1 * PLOT_MARGIN * PLOT_TOTAL_HEIGHT

        # if DEBUG: print '### Done creating plots'

        ### Configure the different plots
        ### TODO: This should be pulled from a config dialog somewhere

        # plot 0: heart rate
        # if DEBUG: print '### adjusting plot 0 parameters [%s]'%self.plots[0]
        self.plots[0].set_autoscale = False
        self.plots[0].timeWidth = 30.0
        self.plots[0].set_ylim(250.0, 650.0)
        self.plots[0].triggerMode = triggerTypes.AUTO
        self.plots[0].autoDefineTrigger = True
        self.plots[0].set_title('HR')
        self.plots[0].units = 'bpm'
        self.plots[0].scaling = 1.0
        self.plots[0].offset = 0.0
        self.plots[0].set_ylabel('%s (%s)' % (self.plots[0].get_title(), self.plots[0].units))
        self.plots[0].linecolor = 'None'
        self.plots[0].measurementFunc = doGetAVG
        self.plots[0].measurementParameters = ()
        self.plots[0].measurementUnits = 'bpm'
        self.plots[0].measurePeriod = 5.
        self.plots[0].set_trend_ylim(250.0, 650.0)
        self.plots[0].enableAlarm()
        self.plots[0].setAlarmHigh(600.0)
        self.plots[0].setAlarmLow(300.0)
        self.plots[0].setAlarmPitch(2000)

        # plot 1: SpO2
        # if DEBUG: print '### adjusting plot 2 parameters [%s]'%self.plots[1]
        self.plots[1].set_autoscale_on(False)
        self.plots[1].timeWidth = 30
        self.plots[1].remanence = 1
        self.plots[1].set_ylim(0.0, 100)
        self.plots[1].triggerMode = triggerTypes.AUTO
        self.plots[1].autoDefineTrigger = True
        self.plots[1].set_title('SpO2')
        self.plots[1].units = '%'
        self.plots[1].scaling = 1
        self.plots[1].offset = 0
        self.plots[1].set_ylabel('%s (%s)' % (self.plots[1].get_title(), self.plots[1].units))
        self.plots[1].linecolor = 'r'
        self.plots[1].measurementFunc = doGetAVG
        self.plots[1].measurementParameters = ()
        self.plots[1].measurementUnits = self.plots[1].units
        self.plots[1].measurePeriod = 5.
        self.plots[1].set_trend_ylim(0.0, 100.0)
        # self.plots[1].disableAlarm()
        self.plots[1].setAlarmHigh(110)
        self.plots[1].setAlarmLow(80.0)
        self.plots[1].setAlarmPitch(1500)

        # plot 2: pCO2
        # if DEBUG: print '### adjusting plot 2 parameters [%s]'%self.plots[2]
        self.plots[2].set_autoscale_on(False)
        self.plots[2].timeWidth = 30
        self.plots[2].remanence = 1
        self.plots[2].set_ylim(0.0, 6.0)
        self.plots[2].triggerMode = triggerTypes.AUTO
        self.plots[2].autoDefineTrigger = True
        self.plots[2].set_title('pCO2')
        self.plots[2].units = '%'
        self.plots[2].scaling = 1
        self.plots[2].offset = 0
        self.plots[2].set_ylabel('%s (%s)' % (self.plots[2].get_title(), self.plots[2].units))
        self.plots[2].linecolor = 'g'
        self.plots[2].measurementFunc = doGetLastPeakValue
        self.plots[2].measurementParameters = (0.3, -1000)
        self.plots[2].measurementUnits = self.plots[2].units
        self.plots[2].measurePeriod = 5.0  # integrate measurement over 5sec
        self.plots[2].set_trend_ylim(0.0, 6.0)
        self.plots[2].enableAlarm()
        self.plots[2].setAlarmHigh(5.5)
        self.plots[2].setAlarmLow(3.0)
        self.plots[2].setAlarmPitch(1500)

        # plot 3: Lung Pres
        self.plots[3].set_autoscale_on(False)
        self.plots[3].timeWidth = 30.0
        self.plots[3].remanence = 1
        self.plots[3].set_ylim(0.0, 30.0)
        self.plots[3].triggerMode = triggerTypes.AUTO
        self.plots[3].autoDefineTrigger = True
        self.plots[3].set_title('Lung Pres')
        self.plots[3].units = 'cmH2O'
        self.plots[3].scaling = 1
        self.plots[3].offset = 0
        self.plots[3].set_ylabel('%s (%s)' % (self.plots[3].get_title(), self.plots[3].units))
        self.plots[3].linecolor = 'g'
        self.plots[3].measurementFunc = doGetLastPeakValue
        self.plots[3].measurementParameters = (0.3, 0)
        self.plots[3].measurementUnits = self.plots[3].units
        self.plots[3].measurePeriod = 5.0  # integrate measurement over 5sec
        self.plots[3].set_trend_ylim(0.0, 20.0)
        self.plots[3].disableAlarm()
        self.plots[3].setAlarmHigh(20.0)
        self.plots[3].setAlarmLow(5.0)
        self.plots[3].setAlarmPitch(1500)

        # plot 4: Temperature
        # if DEBUG: print '### adjusting plot 3 parameters [%s]'%self.plots[4]
        self.plots[4].set_autoscale_on(False)
        self.plots[4].timeWidth = 30.0
        self.plots[4].remanence = 1
        self.plots[4].set_ylim(36, 39)
        self.plots[4].triggerMode = triggerTypes.AUTO
        self.plots[4].set_title('Temperature')
        self.plots[4].units = 'C'
        self.plots[4].scaling = 1
        self.plots[4].offset = 0
        self.plots[4].set_ylabel('%s (%s)' % (self.plots[4].get_title(), self.plots[4].units))
        self.plots[4].linecolor = 'c'
        self.plots[4].measurementFunc = doGetAVG
        self.plots[4].measurementParameters = ()
        self.plots[4].measurementUnits = self.plots[4].units
        self.plots[4].measurePeriod = 5.
        self.plots[4].set_trend_ylim(36.0, 39.0)
        self.plots[4].enableAlarm()
        self.plots[4].setAlarmHigh(37.5)
        self.plots[4].setAlarmLow(36.5)
        self.plots[4].setAlarmPitch(1000)

        #        if DEBUG:
        #            print '### Done adjusting parameters'
        #            for plot in self.plots:
        #                print "plot %s:"%plot
        #                print "    autoscale: %s"%plot.get_autoscaley_on()
        #                print "    linecolor: %s"%plot.linecolor


        # self.openDiagFileButton = wx.Button(self, -1, "Diagnostic file...")
        # self.openDiagFileButton.Disable();
        # self.diagFileLabel = wx.StaticText(self, -1)
        self.startButton = wx.Button(self, -1, "Start")
        self.stopButton = wx.Button(self, -1, "Stop")
        self.resetAlarmsButton = wx.Button(self, -1, "Reset All Alarms")
        self.startButton.Enable(False)
        self.stopButton.Enable(False)

        self.hbox2.Add(self.startButton, 1, flag=wx.EXPAND | wx.ALIGN_BOTTOM | wx.TOP, border=10)
        self.hbox2.Add(self.stopButton, 1, flag=wx.EXPAND | wx.ALIGN_BOTTOM | wx.TOP, border=10)
        self.hbox2.Add(self.resetAlarmsButton, 1, flag=wx.EXPAND | wx.ALIGN_BOTTOM | wx.TOP, border=10)

        self.vbox3.Add(self.hbox2)
        # self.vbox3.Add(self.logBox,1,wx.EXPAND|wx.TOP, border=10)
        self.vbox3.Add(self.canvas, 1, wx.EXPAND)

        self.vbox.Add(clockPanel.getPanel(), 0, wx.EXPAND)
        self.vbox.Add(self.newExpButton, 0, flag=wx.EXPAND | wx.TOP | wx.BOTTOM, border=10)

        self.createLeftPanel(False)

        self.vbox.Add(self.vbox2)
        self.leftPanel.SetSizer(self.vbox)

        self.hbox.Add(self.leftPanel, 0, flag=wx.EXPAND | wx.ALL, border=10)
        self.hbox.Add(self.vbox3, 1, wx.EXPAND)

        self.SetSizer(self.hbox)
        self.Centre()

        # Menu Bar
        mainMenuBar = wx.MenuBar()
        fileMenu = wx.Menu()
        editMenu = wx.Menu()
        fileMenu.Append(id=wx.ID_SAVEAS)
        fileMenu.Append(id=wx.ID_ABOUT)
        fileMenu.AppendSeparator()
        fileMenu.Append(id=wx.ID_EXIT)
        editMenu.Append(id=wx.ID_PREFERENCES)
        mainMenuBar.Append(fileMenu, "&File")
        mainMenuBar.Append(editMenu, "&Edit")
        self.SetMenuBar(mainMenuBar)

        self.Bind(wx.EVT_MENU, self.onQuitMenu, id=wx.ID_EXIT)
        self.Bind(wx.EVT_MENU, self.onAboutMenu, id=wx.ID_ABOUT)
        self.Bind(wx.EVT_MENU, self.onPreferencesMenu, id=wx.ID_PREFERENCES)
        self.Bind(wx.EVT_MENU, self.onSaveAsMenu, id=wx.ID_SAVEAS)
        self.Bind(wx.EVT_CLOSE, self.onClose)
        self.Bind(wx.EVT_BUTTON, self.onNewExpButton, None, self.newExpButton.GetId())
        self.Bind(wx.EVT_BUTTON, self.onStartButton, None, id=self.startButton.GetId())
        self.Bind(wx.EVT_BUTTON, self.onStopButton, None, id=self.stopButton.GetId())
        self.Bind(wx.EVT_BUTTON, self.onResetAlarmsButton, None, id=self.resetAlarmsButton.GetId())
        self.logBox.Bind(wx.EVT_TEXT, self.onUpdateLog)
        # Bind the update canvas event to some function:
        self.Bind(EVT_UPDATE_CANVAS, self.onUpdateCanvas)

        # start PCO2Monitor thread
        if self.pCO2Monitor is not None:
            self.pCO2Monitor.start()

    def onStartButton(self, event):
        # reset plot
        for plot in self.plots:
            plot.reset()

        # if self.aiConfig is None:
        #     print "ai device not configured, doing nothing"
        #     return
        #
        # if DEBUG: print "Start button has been pressed"
        # self.startButton.Enable(0)
        #
        # # configure the comedi device
        # self.dev = comedi.comedi_open(str(self.aiConfig['device']))
        # if not self.dev:
        #     print "FATAL ERROR: cannot open comedi device %s: %s" % \
        #           (self.aiConfig['device'], comedi.comedi_strerror(comedi.comedi_errno()))
        #     sys.exit(1)
        # # and get an appropriate subdevice
        # self.subdev = self.aiConfig['subDevice']
        #
        # # get a file-descriptor for reading
        # self.fd = comedi.comedi_fileno(self.dev)
        # if self.fd <= 0: raise Exception(
        #     "Error obtaining Comedi device file descriptor: %s" % (comedi.comedi_strerror(comedi.comedi_errno())))
        #
        # # create channel list
        # myChanList = comedi.chanlist(int(self.aiConfig['nbChans']))
        # for i in range(int(self.aiConfig['nbChans'])):
        #     myChanList[i] = comedi.cr_pack(self.aiConfig['channels'][i],
        #                                    self.aiConfig['gains'][i],
        #                                    self.aiConfig['refs'][i])
        # # create a command structure
        # cmd = comedi.comedi_cmd_struct()
        # ret = comedi.comedi_get_cmd_generic_timed(self.dev,
        #                                           self.subdev,
        #                                           cmd,
        #                                           int(self.aiConfig['nbChans']),
        #                                           int(1.e9 / self.aiConfig['sampleFreq']))
        # if ret: raise Exception("Error comedi_get_cmd_generic failed")
        # cmd.chanlist = myChanList  # adjust for our particular context
        # cmd.chanlist_len = int(self.aiConfig['nbChans'])
        # cmd.scan_end_arg = int(self.aiConfig['nbChans'])
        # cmd.stop_src = comedi.TRIG_NONE  # never stop
        #
        # # test our comedi command a few times.
        # ret = 0
        # for i in range(2):
        #     ret = comedi.comedi_command_test(self.dev, cmd)
        #     if ret < 0: raise Exception(
        #         "comedi_command_test failed: %s" % (comedi.comedi_strerror(comedi.comedi_errno())))
        #
        # # Start the command
        # ret = comedi.comedi_command(self.dev, cmd)
        # if ret <> 0: raise Exception("comedi_command failed... %s" % (comedi.comedi_strerror(comedi.comedi_errno())))
        #
        # for chanNum, chanGain in zip(self.aiConfig['channels'], self.aiConfig['gains']):
        #     self.maxChanValues.append(comedi.comedi_get_maxdata(self.dev, self.subdev, chanNum))
        #     self.chanRanges.append(comedi.comedi_get_range(self.dev, self.subdev, chanNum, chanGain))
        #
        # # start reader thread
        # if DEBUG: print "creating reader thread"
        # self.comediThread = ComediThread(self, self.fd, int(self.aiConfig['nbChans']))
        # self.comediThread.start()
        self.dev = None
        self.comediThread = SerialThread(frame=self, indices=self.aiConfig['channels'])
        self.comediThread.start()

        self.stopButton.Enable(1)
        if DEBUG: print "Start button complete."

    def onStopButton(self, event):
        self.stopButton.Enable(0)
        if self.comediThread is not None:
            self.comediThread.reading = False
            self.comediThread.join()
            if DEBUG: print "Background thread stopped."
            self.comediThread = None
        if self.dev is not None and self.subdev is not None:
            comedi.comedi_cancel(self.dev, self.subdev)

        # make sure the alarms are stopped as well
        for plot in self.plots:
            plot.resetAlarm()

        self.startButton.Enable(1)

    def onResetAlarmsButton(self, event):
        if DEBUG: print "Resetting all alarms..."
        for plot in self.plots:
            plot.resetAlarm()

    def onClearButton(self, event):
        self.arrayLock.acquire()
        self.currPos = 0
        self.trigPlot1.cla()
        self.trigPlot2.cla()
        self.arrayLock.release()
        evt = UpdateCanvasEvent()
        wx.PostEvent(self, evt)

    def initialize(self):
        self.onClearButton(None)

    def processNewData(self, inData):
        self.arrayLock.acquire()
        # inData = convertInVolts(inData, self.maxChanValues, self.chanRanges)
        for plot, i in zip(self.plots, range(self.aiConfig['nbChans'])):
            plot.processNewData(inData[i])
            plot.relim()
        self.arrayLock.release()

    def onUpdateCanvas(self, evt):
        # if DEBUG: print "Updating the canvas..."
        self.canvasLock.acquire()
        self.canvas.draw()
        self.canvasLock.release()

    def createLeftPanel(self, enabled):
        # @type pumpDrug drugClass
        if len(self.exp.drugList) > 0:
            pumpDrug = self.exp.drugList[0]
        else:
            pumpDrug = drugClass("dummy", 0)
        self.drugPumpPanel = drugPumpPanelClass(self.leftPanel, pumpDrug.name, pumpDrug.dose, self.logBox, self.pump)
        self.drugPumpPanel.getPanel().Enable(enabled)
        self.vbox2.Add(self.drugPumpPanel.getPanel(), 0, flag=wx.BOTTOM, border=10)

        self.drugPanels = []
        for drug in self.exp.drugList[1:]:
            drugPanel = drugPanelClass(self.leftPanel, drug.name, drug.dose, self.logBox)
            drugPanel.getPanel().Enable(enabled)
            self.drugPanels.append(drugPanel)
            self.vbox2.Add(drugPanel.getPanel(), 0, flag=wx.BOTTOM, border=10)

        self.otherDrugPanel = otherDrugPanelClass(self.leftPanel, self.logBox)
        self.otherDrugPanel.getPanel().Enable(enabled)
        self.vbox2.Add(self.otherDrugPanel.getPanel(), 0, flag=wx.BOTTOM, border=10)

        self.addCommentButton = wx.Button(self.leftPanel, -1, "Add comment...")
        self.addCommentButton.Enable(enabled)
        self.vbox2.Add(self.addCommentButton, 0, flag=wx.EXPAND)

        self.Bind(wx.EVT_BUTTON, self.onCommentButton, id=self.addCommentButton.GetId())

    def onClose(self, event):
        if self.pCO2Monitor is not None:
            self.pCO2Monitor.stop()
        self.onStopButton(event)
        if self.dev is not None:
            comedi.comedi_close(self.dev)
        self.Destroy()

    def onQuitMenu(self, event):
        self.Close()

    def onCommentButton(self, event):
        tempComment = wx.GetTextFromUser("Enter a comment:", "Enter a comment", self.previousComment)
        # @type tempComment str
        if len(tempComment) > 0:
            self.previousComment = tempComment
            message = time.strftime("%H:%M\t", time.localtime())
            message += "%s\t\t| \n" % (tempComment)

            if isinstance(self.logBox, wx.TextCtrl):
                self.logBox.AppendText(message)
                # AppendText doesn't seem to send a wx.EVT_TEXT to logBox
                # generate one
                _ = wx.PyEvent()
            else:
                print tempComment

    def onSaveAsMenu(self, event):
        fileSaveDlg = wx.FileDialog(None, "Choose file name", wildcard="*.txt",
                                    style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
        if fileSaveDlg.ShowModal() == wx.ID_OK:
            filePath = fileSaveDlg.GetPath();
            if not self.logBox.SaveFile(filePath):
                errorDlg = wx.MessageDialog(None, "Error when trying to save file", style=wx.OK | wx.ICON_ERROR);
                errorDlg.ShowModal()
                errorDlg.Destroy()
        fileSaveDlg.Destroy()

    def onLoadDiagFile(self, event):
        openDiagFileDlg = wx.FileDialog(None, "Open diagnostic file")
        if openDiagFileDlg.ShowModal() == wx.ID_OK:
            self.diagFileName = openDiagFileDlg.GetPath();
            try:
                self.diagFile = open(self.diagFileName, 'r')
                self.diagFileLabel.SetLabel(self.diagFileName)
                print "creating diagLogClass()"  # DEBUG
                self.diagLog = diagLogClass(self.diagFile)
                self.diagTimer.Start(60000)  # 1min
                self.onDiagTimer(None)

                self.openDiagFileButton.SetLabel("Stop parsing file")
                self.Unbind(wx.EVT_BUTTON, id=self.openDiagFileButton.GetId())
                self.Bind(wx.EVT_BUTTON, self.onStopParseDiagFile, id=self.openDiagFileButton.GetId())
            except Exception as ex:
                print ex  # DEBUG
                self.diagFile = None
        openDiagFileDlg.Destroy()

    def onStopParseDiagFile(self, event):
        self.diagTimer.Stop()
        self.diagFile = None
        self.Unbind(wx.EVT_BUTTON, id=self.openDiagFileButton.GetId())
        self.openDiagFileButton.SetLabel("Diagnostic file...")
        self.diagFileLabel.SetLabel("")
        self.Bind(wx.EVT_BUTTON, self.onLoadDiagFile, id=self.openDiagFileButton.GetId())

    def onDiagTimer(self, event):
        print "%s - in mainFrameClass.onDiagTimer()" % time.strftime("%H:%M:%S")  # DEBUG

        # FIXME - this does not work very well
        if (not self.logBox.IsEmpty()) and self.logBox.GetValue()[-1] <> '\n':
            self.logBox.AppendText('\n')
        oxymeterData = self.diagLog.printNext()
        if len(oxymeterData) > 0:
            self.logBox.AppendText(oxymeterData)
            if self.pCO2Monitor is not None:
                # remove last carriage return to print CO2 monitor on same line
                self.logBox.Remove(self.logBox.GetInsertionPoint() - 2, self.logBox.GetInsertionPoint())
                avgCO2, avgBR = self.pCO2Monitor.getMeanValues()
                self.logBox.AppendText("%1.2f %% | %3.0f bpm | \n" % (avgCO2, avgBR))

    def onAboutMenu(self, event):
        wx.AboutBox(self.appInfo)

    def onNewExpButton(self, event):
        newExpDlg = xrc.XmlResource.Get().LoadDialog(self, "newExpDlg")
        newExpDlg.Fit()
        newExpDlg.Refresh()

        # find controls
        self.expDateBox = newExpDlg.FindWindowByName("expDateBox")
        self.expCommentsBox = newExpDlg.FindWindowByName("expCommentsBox")
        self.expDrugListBox = newExpDlg.FindWindowByName("expDrugListBox")

        self.mouseTypeBox = newExpDlg.FindWindowByName("mouseTypeBox")
        self.mouseSexBox = newExpDlg.FindWindowByName("mouseSexBox")
        self.mouseDoBBox = newExpDlg.FindWindowByName("mouseDoBBox")
        self.mouseWeightBox = newExpDlg.FindWindowByName("mouseWeightBox")

        self.addDrugButton = newExpDlg.FindWindowByName("addDrugButton")
        self.delDrugButton = newExpDlg.FindWindowByName("delDrugButton")
        self.editDrugButton = newExpDlg.FindWindowByName("editDrugButton")

        self.mouseTypeBox.Clear()
        self.mouseTypeBox.AppendItems(self.exp.mouseTypes)

        font = self.expDrugListBox.GetFont()
        if system() == 'Darwin':
            font.SetFaceName('Monaco')
        if system() == 'Windows':
            font.SetFaceName('Courier New')
        self.expDrugListBox.SetFont(font)

        # Bind buttons
        newExpDlg.FindWindowByName('ID_OK').SetId(wx.ID_OK)
        newExpDlg.FindWindowByName('ID_CANCEL').SetId(wx.ID_CANCEL)
        newExpDlg.Bind(wx.EVT_BUTTON, self.onAddDrug, id=self.addDrugButton.GetId())
        newExpDlg.Bind(wx.EVT_BUTTON, self.onDelDrug, id=self.delDrugButton.GetId())
        newExpDlg.Bind(wx.EVT_BUTTON, self.onEditDrug, id=self.editDrugButton.GetId())
        self.expDrugListBox.Bind(wx.EVT_LEFT_DCLICK, self.onEditDrug)

        # load validator in controls
        self.expDateBox.SetValidator(ObjectAttrDateValidator(self.exp, "date", flRequired=True))
        self.expCommentsBox.SetValidator(ObjectAttrTextValidator(self.exp, "comments", flRequired=False))
        self.expDrugListBox.SetValidator(drugListValidator(self.exp, "drugList", flRequired=True))

        self.mouseTypeBox.SetValidator(ObjectAttrBellTextValidator(self.exp.mouse, "type", flRequired=True))
        self.mouseSexBox.SetValidator(ObjectAttrRadioBoxValidator(self.exp.mouse, "sex", formatter=mouseSexFormater()))
        self.mouseDoBBox.SetValidator(ObjectAttrDateValidator(self.exp.mouse, "dob", flRequired=False))
        self.mouseWeightBox.SetValidator(ObjectAttrSpinValidator(self.exp.mouse, "weight"))

        if newExpDlg.ShowModal() == wx.ID_OK:
            self.vbox2.Clear(True)
            self.createLeftPanel(True)

            self.startButton.Enable(True)
            self.leftPanel.SetSizerAndFit(self.vbox)
            self.leftPanel.Layout()
            self.Refresh()

            # keep mouseTypes
            try:
                self.exp.mouseTypes.remove(self.mouseTypeBox.GetValue())
            except ValueError:
                # mouse type not in the list
                pass
            self.exp.mouseTypes.insert(0, self.mouseTypeBox.GetValue())

            # FIXME check if value already exist in array


            # create record directory if needed
            if self.config.createFolder():
                folder = path.join(self.config.getBaseFolder(), self.exp.date.Format("%Y_%m_%d"))
                if not path.exists(folder):
                    os.makedirs(folder)

            # create registry entry if needed
            if self.config.updateReg():
                reg = wx.Config("Spike2", "CED")
                reg.SetPath("/Perso")
                reg.Write("recordPath", self.config.getBaseFolder())
                reg.Write("date", self.exp.date.Format("%Y_%m_%d"))
                reg.Flush()
                del reg

            # check if logFile already exists, and load content
            fileName = path.join(self.config.getBaseFolder(), self.exp.date.Format("%Y_%m_%d"),
                                 self.config.getLogFileName())
            if path.exists(fileName):
                self.logBox.LoadFile(fileName)
                self.logBox.AppendText('\n')
                self.logBox.AppendText('\n')
                self.logBox.AppendText('#' * 80)
                self.logBox.AppendText('\n')
                self.logBox.AppendText('\n')

            # write in text box
            if (not self.logBox.IsEmpty()) and self.logBox.GetValue()[-1] <> '\n':
                self.logBox.AppendText('\n')
            self.logBox.AppendText(self.exp.writeHeader())
            self.logBox.AppendText("\n")
            self.logBox.AppendText("\n")
            self.onUpdateLog(wx.PyEvent())

            self.startLogPos = self.logBox.GetInsertionPoint()

        newExpDlg.Destroy()

    def onAddDrug(self, event):
        editDrugDlg = xrc.XmlResource.Get().LoadDialog(None, "drugOptionsDlg")
        drugNameBox = editDrugDlg.FindWindowByName("drugNameTextBox")
        drugDoseBox = editDrugDlg.FindWindowByName("drugDoseSpinCtrl")
        editDrugDlg.FindWindowByName('ID_OK').SetId(wx.ID_OK)
        editDrugDlg.FindWindowByName('ID_CANCEL').SetId(wx.ID_CANCEL)
        if editDrugDlg.ShowModal() == wx.ID_OK:
            drug = drugClass(drugNameBox.GetValue(), drugDoseBox.GetValue())
            self.exp.drugList.append(drug)
            self.expDrugListBox.Append(drug.printForList())
        editDrugDlg.Destroy()

    def onDelDrug(self, event):
        self.expDrugListBox.Delete(self.expDrugListBox.GetSelection())

    def onEditDrug(self, event):
        selected = self.expDrugListBox.GetSelection()
        if selected > wx.NOT_FOUND:
            drug = self.exp.drugList[selected]

            editDrugDlg = xrc.XmlResource.Get().LoadDialog(None, "drugOptionsDlg")
            drugNameBox = editDrugDlg.FindWindowByName("drugNameTextBox")
            drugDoseBox = editDrugDlg.FindWindowByName("drugDoseSpinCtrl")
            editDrugDlg.FindWindowByName('ID_OK').SetId(wx.ID_OK)
            editDrugDlg.FindWindowByName('ID_CANCEL').SetId(wx.ID_CANCEL)

            drugNameBox.SetValue(drug.name)
            drugDoseBox.SetValue(drug.dose)

            if editDrugDlg.ShowModal() == wx.ID_OK:
                drug = drugClass(drugNameBox.GetValue(), drugDoseBox.GetValue())
                self.expDrugListBox.Delete(selected)
                self.expDrugListBox.Insert(drug.printForList(), selected)
                self.expDrugListBox.SetSelection(selected)
            editDrugDlg.Destroy()

    def onUpdateLog(self, event):
        # print "onUpdateLog"
        if self.config.autoSave:
            fileName = path.join(self.config.getBaseFolder(), self.exp.date.Format("%Y_%m_%d"),
                                 self.config.getLogFileName())
            if not self.logBox.SaveFile(fileName):
                errorDlg = wx.MessageDialog(None, "Error writing file '%s' to disk" % fileName,
                                            style=wx.OK | wx.ICON_ERROR)
                errorDlg.ShowModal()
                errorDlg.Destroy()
        event.Skip()

    def onPreferencesMenu(self, event):
        configDlg = xrc.XmlResource.Get().LoadDialog(None, "configDlg")

        baseFolderPicker = configDlg.FindWindowByName("baseFolderPicker")
        baseFolderBrowseButton = configDlg.FindWindowByName("baseFolderBrowseButton")  # @UnusedVariable
        createFolderCheck = configDlg.FindWindowByName("createFolderCheck")
        updateRegCheck = configDlg.FindWindowByName("updateRegCheck")
        autoSaveCheck = configDlg.FindWindowByName("autoSaveCheck")
        configDlg.FindWindowByName('ID_OK').SetId(wx.ID_OK)
        configDlg.FindWindowByName('ID_CANCEL').SetId(wx.ID_CANCEL)

        # configDlg.Bind(wx.EVT_BUTTON, self.onBaseFolderBrowse, id=baseFolderBrowseButton.GetId(), id2=baseFolderPicker.GetId())

        # init
        baseFolderPicker.SetPath(self.config.getBaseFolder())
        createFolderCheck.SetValue(self.config.createFolder())
        updateRegCheck.SetValue(self.config.updateReg())
        autoSaveCheck.SetValue(self.config.autoSave())

        if configDlg.ShowModal() == wx.ID_OK:
            # do stuff
            self.config.setBaseFolder(path.normcase(path.normpath(baseFolderPicker.GetPath())))
            self.config.setCreateFolder(createFolderCheck.GetValue())
            self.config.setUpdateReg(updateRegCheck.GetValue())
            self.config.setAutoSave(autoSaveCheck.GetValue())
        configDlg.Destroy()


class otherDrugDlgClass(wx.Dialog):
    def __init__(self, parent):
        """Constructor"""
        # call the parent constructor
        wx.Dialog.__init__(parent);


class PreferencesFrame(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, -1, title)

        # p = wx.Panel(self, -1)
        vertSizer = wx.BoxSizer(wx.VERTICAL)
        titleLabel = wx.StaticText(self, -1, "Comedi Configuration")
        okButton = wx.Button(self, -1, "Save Changes")
        wx.EVT_BUTTON(self, okButton.GetId(), self.PressOkButton)

        sizer = wx.GridSizer(6, 2, 2, 2)  # rows, columns, hgap, vgap

        vertSizer.Add(titleLabel, 0, wx.ALIGN_CENTER | wx.ALL, 8)
        vertSizer.Add(sizer)
        vertSizer.Add(okButton, 0, wx.ALIGN_RIGHT | wx.ALL, 15)

        deviceNameLabel = wx.StaticText(self, -1, "Device: ")
        self.deviceNameTextBox = wx.TextCtrl(self, -1, "/dev/comedi0")
        sizer.Add(deviceNameLabel, 0, wx.ALIGN_RIGHT)
        sizer.Add(self.deviceNameTextBox)

        xInputLabel = wx.StaticText(self, -1, "X Input Channel: ")
        self.xInputBox = wx.Choice(self, -1, choices=['0', '1', '2', '3', '4', '5', '6', '7'])
        self.xInputBox.SetSelection(0)
        sizer.Add(xInputLabel, 0, wx.ALIGN_RIGHT)
        sizer.Add(self.xInputBox)

        xInputRangeLabel = wx.StaticText(self, -1, "X Input Range: ")
        self.xInputRangeBox = wx.Choice(self, -1, choices=['0', '1', '2', '3'])
        self.xInputRangeBox.SetSelection(0)
        sizer.Add(xInputRangeLabel, 0, wx.ALIGN_RIGHT)
        sizer.Add(self.xInputRangeBox)

        xInputTypeLabel = wx.StaticText(self, -1, "X Input Type: ")
        self.xInputTypeBox = wx.Choice(self, -1, choices=['AREF_GROUND', 'AREF_COMMON', 'AREF_DIFF'])
        self.xInputTypeBox.SetSelection(0)
        sizer.Add(xInputTypeLabel, 0, wx.ALIGN_RIGHT)
        sizer.Add(self.xInputTypeBox)

        yInputLabel = wx.StaticText(self, -1, "Y Input Channel: ")
        self.yInputBox = wx.Choice(self, -1, choices=['0', '1', '2', '3', '4', '5', '6', '7'])
        self.yInputBox.SetSelection(1)
        sizer.Add(yInputLabel, 0, wx.ALIGN_RIGHT)
        sizer.Add(self.yInputBox)

        yInputRangeLabel = wx.StaticText(self, -1, "Y Input Range: ")
        self.yInputRangeBox = wx.Choice(self, -1, choices=['0', '1', '2', '3'])
        self.yInputRangeBox.SetSelection(0)
        sizer.Add(yInputRangeLabel, 0, wx.ALIGN_RIGHT)
        sizer.Add(self.yInputRangeBox)

        yInputTypeLabel = wx.StaticText(self, -1, "Y Input Type: ")
        self.yInputTypeBox = wx.Choice(self, -1, choices=['AREF_GROUND', 'AREF_COMMON', 'AREF_DIFF'])
        self.yInputTypeBox.SetSelection(0)
        sizer.Add(yInputTypeLabel, 0, wx.ALIGN_RIGHT)
        sizer.Add(self.yInputTypeBox)

        inputPeriodLabel = wx.StaticText(self, -1, "Samples per second: ")
        self.inputPeriodBox = wx.TextCtrl(self, -1, "10000")
        sizer.Add(inputPeriodLabel, 0, wx.ALIGN_RIGHT)
        sizer.Add(self.inputPeriodBox)

        self.SetSizer(vertSizer)
        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
        self.Fit()

    def PressOkButton(self, event):
        self.Close(1)

    def OnCloseWindow(self, event):
        self.MakeModal(False)
        self.Show(0)
        print self.getConfiguration()
        # self.Destroy()

    def getConfiguration(self):
        config = dict()
        config["device"] = self.deviceNameTextBox.GetValue()

        config["analog_period_ns"] = int(1e9 / float(self.inputPeriodBox.GetValue()))

        x_channel = int(self.xInputBox.GetStringSelection())
        x_range = int(self.xInputRangeBox.GetStringSelection())
        x_aref_string = self.xInputTypeBox.GetStringSelection()
        x_aref = 0
        if x_aref_string == 'AREF_GROUND':
            x_aref = comedi.AREF_GROUND
        elif x_aref_string == 'AREF_COMMON':
            x_aref = comedi.AREF_COMMON
        elif x_aref_string == 'AREF_DIFF':
            x_aref = comedi.AREF_DIFF
        else:
            x_aref = comedi.AREF_OTHER

        y_channel = int(self.yInputBox.GetStringSelection())
        y_range = int(self.yInputRangeBox.GetStringSelection())
        y_aref_string = self.yInputTypeBox.GetStringSelection()
        y_aref = 0
        if y_aref_string == 'AREF_GROUND':
            y_aref = comedi.AREF_GROUND
        elif y_aref_string == 'AREF_COMMON':
            y_aref = comedi.AREF_COMMON
        elif y_aref_string == 'AREF_DIFF':
            y_aref = comedi.AREF_DIFF
        else:
            y_aref = comedi.AREF_OTHER

        config["analog"] = [[x_channel, x_range, x_aref], [y_channel, y_range, y_aref]]
        return config
