#!/usr/bin/env python

# Part of the PsychoPy library
# Copyright (C) 2009 Jonathan Peirce
# Distributed under the terms of the GNU General Public License (GPL).

import wx
from wx import grid
from psychopy import monitors
from psychopy.app import dialogs
import time, os
DEBUG=True
NOTEBOOKSTYLE = False
NO_MEASUREMENTS=False

try:
    import matplotlib
    matplotlib.use('WXAgg')
    from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
    from matplotlib.figure import Figure
except:
    pass
import numpy
#wx IDs for menu items
[idMenuSave] = \
    map(lambda _makeID: wx.NewId(), range(1))
#wx IDs for controllers (admin panel)
[idCtrlMonList, idCtrlCalibList,
    idBtnCopyCalib,idBtnSaveMon,
    idBtnNewMon, idBtnDeleteMon,
    idBtnNewCalib, idBtnDeleteCalib] = \
    map(lambda _makeID: wx.NewId(), range(8))
#wx IDs for controllers (info panel)
[idCtrlScrDist, idCtrlScrWidth,
    idCtrlCalibDate, idCtrlCalibNotes] = \
    map(lambda _makeID: wx.NewId(), range(4))


class SimpleGrid(grid.Grid): ##, wxGridAutoEditMixin):
    def __init__(self, parent, id=-1, rows=[], cols=[], data=None):
        self.moveTo = None
        self.nRows, self.nCols = len(rows), len(cols)
        grid.Grid.__init__(self, parent, -1, wx.Point( 0, 0 ))#,wx.Size( 300, 150 ))        
        
        self.numEditor = grid.GridCellFloatEditor()
        self.CreateGrid(self.nRows,self.nCols)
        for nCol, col in enumerate(cols): 
            self.SetColLabelValue(nCol,col)
            self.SetColFormatFloat(nCol,4,4)
            #self.SetColMinimalWidth(nCol,800)
        for nRow, row in enumerate(rows): 
            self.SetRowLabelValue(nRow,row)
        for nRow in range(self.nRows):
            for nCol in range(self.nCols):
                self.SetCellEditor(nRow,nCol, self.numEditor)
        self.setData(data)
        #self.SetMargins(-5,-5)
        wx.EVT_IDLE(self, self.OnIdle)
        
    def OnIdle(self, evt):
        if self.moveTo != None:
            self.SetGridCursor(self.moveTo[0], self.moveTo[1])
            self.moveTo = None
        evt.Skip()
        
    def setData(self, data=None):
        #update the data for the grid
        for nRow in range(self.nRows):
            for nCol in range(self.nCols):
                if type(data)==numpy.ndarray or data!=None: 
                    self.SetCellValue(nRow, nCol, '%f' %data[nRow, nCol])
                else: self.SetCellValue(nRow,nCol,'0.000')
        self.AutoSize()

class PlotFrame(wx.Dialog):
    def __init__(self, parent, ID, title, plotCanvas=None, pos=wx.DefaultPosition,
                 size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE):
        wx.Dialog.__init__(self, parent, ID, title, pos, size, style)
        panel = wx.Panel(self, -1)
        #wx.EVT_CLOSE(self, self.OnCloseWindow)
        self.sizer = wx.BoxSizer(wx.HORIZONTAL)
        if not plotCanvas==None:
            self.addCanvas(plotCanvas)
        
    def addCanvas(self, canvas):
        self.sizer.Add(canvas)
        self.SetSizerAndFit(self.sizer)
        self.ShowModal()
        
    #def OnCloseWindow(self, event):
        #self.Destroy()
        
    
class MainFrame(wx.Frame):
    def __init__(self, parent, title):
        #create a default monitor with no name
        self.currentMon = monitors.Monitor('', verbose=False)
        self.currentMonName=None #can use this to test whether monitor is placeholder
        self.currentCalibName=None
        self.unSavedMonitor=False
        self.comPort = 1
        self.PR650=None
        
        #start building the frame
        wx.Frame.__init__(self, parent, -1, title,size=wx.DefaultSize,
                            style=wx.DEFAULT_FRAME_STYLE )
        
        self.makeMenuBar()
        
        if NOTEBOOKSTYLE:
            #make the notebook
            self.noteBook = wx.Notebook(self, -1)
            self.noteBookSizer = wx.NotebookSizer(self.noteBook)
            
            #add the info page
            self.infoPanel = wx.Panel(self.noteBook,-1)
            self.noteBook.AddPage(self.infoPanel, 'Monitor Info')
            infoSizer = wx.BoxSizer(wx.HORIZONTAL)
            infoSizer.Add(self.makeAdminBox(self.infoPanel), 1, wx.EXPAND)
            infoSizer.Add(self.makeInfoBox(self.infoPanel), 1, wx.EXPAND)
            self.infoPanel.SetAutoLayout(True)
            self.infoPanel.SetSizerAndFit(infoSizer)
                    
            #add the calibration page
            self.calibPanel = wx.Panel(self.noteBook,-1)
            self.noteBook.AddPage(self.calibPanel, 'Calibration')
            calibSizer = self.makeCalibBox(self.calibPanel)
            self.calibPanel.SetAutoLayout(True)
            self.calibPanel.SetSizerAndFit(calibSizer)
            
            self.noteBookSizer.Layout()
            self.noteBookSizer.Fit(self)
            
        else:	    
            #just one page
            self.infoPanel = wx.Panel(self,-1)
            infoSizer = wx.BoxSizer(wx.HORIZONTAL)
            infoSizer.Add(self.makeAdminBox(self.infoPanel), 1, wx.EXPAND)
            infoSizer.Add(self.makeInfoBox(self.infoPanel), 1, wx.EXPAND)
            infoSizer.Add(self.makeCalibBox(self.infoPanel), 1, wx.EXPAND)
            infoSizer.Layout()
            self.infoPanel.SetAutoLayout(True)
            self.infoPanel.SetSizerAndFit(infoSizer)
            
            
        #if wx version 2.5+:
        self.SetSize(self.GetBestSize())
        self.CreateStatusBar()
        #self.SetStatusText("Maybe put tooltips down here one day")
        if os.path.isfile('psychopy.ico'):
            try:
                self.SetIcon(wx.Icon('psychopy.ico', wx.BITMAP_TYPE_ICO))
            except:
                pass
          
        wx.EVT_CLOSE(self, self.onCloseWindow)
        self.updateMonList()
        
    def makeMenuBar(self):
        menuBar = wx.MenuBar()
        fileMenu = wx.Menu()
        fileMenu.Append(idMenuSave,'Save\tCtrl+S', 'Save the current monitor')
        wx.EVT_MENU(self, idMenuSave, self.onSaveMon)
        fileMenu.Append(wx.ID_CLOSE,'Close Monitor Center\tCtrl+W', 'Close Monitor Center but (not other PsychoPy windows)')
        wx.EVT_MENU(self, wx.ID_CLOSE, self.onCloseWindow)
        menuBar.Append(fileMenu, '&File')
        self.SetMenuBar(menuBar)

    def makeAdminBox(self, parent):
        #make the box for the controls
        boxLabel = wx.StaticBox(parent, -1, 'Choose Monitor')
        boxLabel.SetFont(wx.Font(14, wx.SWISS, wx.NORMAL, wx.NORMAL))  
        adminBox = wx.StaticBoxSizer(boxLabel)      
        
        #build the controls
        self.ctrlMonList = wx.ListBox(parent, idCtrlMonList,
            choices=['iiyama571','sonyG500'],
            size=(100,100))
        wx.EVT_LISTBOX(self,idCtrlMonList, self.onChangeMonSelection)

        monButtonsBox = wx.BoxSizer(wx.VERTICAL)

        self.btnNewMon = wx.Button(parent,idBtnNewMon,'New...')
        wx.EVT_BUTTON(self, idBtnNewMon, self.onNewMon)
        monButtonsBox.Add(self.btnNewMon)
        self.btnNewMon.SetToolTipString(
            "Create a new monitor")

        self.btnSaveMon = wx.Button(parent,idBtnSaveMon,'Save')
        wx.EVT_BUTTON(self, idBtnSaveMon, self.onSaveMon)
        monButtonsBox.Add(self.btnSaveMon)
        self.btnSaveMon.SetToolTipString(
            "Save all calibrations for this monitor")

        self.btnDeleteMon = wx.Button(parent,idBtnDeleteMon,'Delete')
        wx.EVT_BUTTON(self, idBtnDeleteMon, self.onDeleteMon)
        monButtonsBox.Add(self.btnDeleteMon)
        self.btnDeleteMon.SetToolTipString(
            "Delete this monitor entirely")

        self.ctrlCalibList = wx.ListBox(parent, idCtrlCalibList,
            choices=[''],
            size=(150,100))
        wx.EVT_LISTBOX(self,idCtrlCalibList, self.onChangeCalibSelection)
        calibButtonsBox = wx.BoxSizer(wx.VERTICAL)

        self.btnCopyCalib = wx.Button(parent,idBtnCopyCalib,'Copy...')
        wx.EVT_BUTTON(self, idBtnCopyCalib, self.onCopyCalib)
        calibButtonsBox.Add(self.btnCopyCalib)
        self.btnCopyCalib.SetToolTipString(
            "Creates a new calibration entry for this monitor")

        self.btnDeleteCalib = wx.Button(parent,idBtnDeleteCalib,'Delete')
        wx.EVT_BUTTON(self, idBtnDeleteCalib, self.onDeleteCalib)
        calibButtonsBox.Add(self.btnDeleteCalib)
        self.btnDeleteCalib.SetToolTipString(
            "Remove this calibration entry (finalised when monitor is saved)")

        #add controls to box        
        adminBoxMainSizer = wx.FlexGridSizer(cols=2, hgap=6, vgap=6)
        adminBoxMainSizer.AddMany([
            (1,10),(1,10), #a pair of empty boxes each 1x10pix
            self.ctrlMonList, monButtonsBox,
            self.ctrlCalibList, calibButtonsBox,
            ])
        adminBox.Add(adminBoxMainSizer)
        return adminBox

    def makeInfoBox(self, parent):
        #create the box
        infoBox = wx.StaticBox(parent, -1, 'Monitor Info')
        infoBox.SetFont(wx.Font(14, wx.SWISS, wx.NORMAL, wx.NORMAL))
        infoBoxSizer = wx.StaticBoxSizer(infoBox,wx.VERTICAL)
        
        #scr distance
        labl_scrDist = wx.StaticText(parent, -1,
            "Screen Distance (cm):",style=wx.ALIGN_RIGHT)
        self.ctrlScrDist = wx.TextCtrl(parent, idCtrlScrDist, "")
        wx.EVT_TEXT(self, idCtrlScrDist, self.onChangeScrDist)

        #scr width
        labl_scrWidth = wx.StaticText(parent, -1,
            "Screen Width (cm):",style=wx.ALIGN_RIGHT)
        self.ctrlScrWidth = wx.TextCtrl(parent, idCtrlScrWidth, "")
        wx.EVT_TEXT(self, idCtrlScrWidth, self.onChangeScrWidth)
        
        ##scr pixels
        labl_ScrPixels = wx.StaticText(parent, -1,
            "Size (pixels; Horiz,Vert):",style=wx.ALIGN_RIGHT)
        self.ctrlScrPixHoriz = wx.TextCtrl(parent, -1, "", size=(50,20))
        wx.EVT_TEXT(self, self.ctrlScrPixHoriz.GetId(), self.onChangeScrPixHoriz)
        self.ctrlScrPixVert = wx.TextCtrl(parent, -1, "", size=(50,20))
        wx.EVT_TEXT(self, self.ctrlScrPixVert.GetId(), self.onChangeScrPixVert)
        ScrPixelsSizer = wx.BoxSizer(wx.HORIZONTAL)
        ScrPixelsSizer.AddMany([ self.ctrlScrPixHoriz, self.ctrlScrPixVert])

        #date
        labl_calibDate = wx.StaticText(parent, -1,
            "Calibration Date:",style=wx.ALIGN_RIGHT)
        self.ctrlCalibDate = wx.TextCtrl(parent, idCtrlCalibDate, "",
            size=(150,20))
        self.ctrlCalibDate.Disable()
        #notes
        labl_calibNotes = wx.StaticText(parent, -1,
            "Notes:",style=wx.ALIGN_RIGHT)
        self.ctrlCalibNotes = wx.TextCtrl(parent, idCtrlCalibNotes, "",
            size=(150,200),
            style=wx.TE_MULTILINE)
        wx.EVT_TEXT(self, idCtrlCalibNotes, self.onChangeCalibNotes)
        
        #bits++
        self.ctrlUseBits = wx.CheckBox(parent, -1,'Use Bits++')
        wx.EVT_CHECKBOX(self, self.ctrlUseBits.GetId(), self.onChangeUseBits)

        infoBoxGrid = wx.FlexGridSizer(cols=2, hgap=6, vgap=6)
        infoBoxGrid.AddMany([
            (1,10),(1,10), #a pair of empty boxes each 1x10pix
            (1,10),self.ctrlUseBits,
            labl_scrDist, self.ctrlScrDist,
            labl_ScrPixels, ScrPixelsSizer,
            labl_scrWidth, self.ctrlScrWidth,
            labl_calibDate, self.ctrlCalibDate
            ])
        infoBoxGrid.Layout()
        infoBoxSizer.Add(infoBoxGrid)
        #put the notes box below the main grid sizer
        infoBoxSizer.Add(labl_calibNotes)
        infoBoxSizer.Add(self.ctrlCalibNotes, 1, wx.EXPAND)
        return infoBoxSizer

    def makeCalibBox(self, parent):
                
        boxLabel = wx.StaticBox(parent, -1, 'Calibration')
        boxLabel.SetFont(wx.Font(14, wx.SWISS, wx.NORMAL, wx.NORMAL))
        calibBox = wx.StaticBoxSizer(boxLabel)
                
        photometerBox = wx.FlexGridSizer(cols=2, hgap=6, vgap=6)
        #com port entry number
        self.comPortLabel =  wx.StaticText(parent, -1, " ", size=(150,20))
        #self.comPortLabel.Disable()
        #PR650 button
        self.btnGetPR650 = wx.Button(parent, -1, "Get PR650")
        wx.EVT_BUTTON(self, self.btnGetPR650.GetId(), self.onBtnGetPR650)
        
        #gamma controls
        self.btnCalibrateGamma = wx.Button(
            parent, -1, "Gamma Calibration...")
        wx.EVT_BUTTON(self, self.btnCalibrateGamma.GetId(), self.onCalibGammaBtn)
        self.btnTestGamma = wx.Button(
            parent, -1, "Gamma Test...")
        if not DEBUG: self.btnTestGamma.Enable(False)
        #self.choiceLinearMethod = wx.Choice(parent, -1, 
                                              #choices=['a+(bx)^gamma','(a+bx)^gamma','interpolate'])
        #wx.EVT_CHOICE(self, self.choiceLinearMethod.GetId(), self.onChangeLinearMethod)
        
        
        #color controls
        wx.EVT_BUTTON(self, self.btnTestGamma.GetId(), self.onCalibTestBtn)
        self.btnCalibrateColor = wx.Button(
            parent, -1, "Chromatic Calibration...")
        if not DEBUG: self.btnCalibrateColor.Enable(False)
        wx.EVT_BUTTON(self, self.btnCalibrateColor.GetId(), self.onCalibColorBtn)
        self.btnPlotGamma = wx.Button(
            parent, -1, "Plot gamma")
        wx.EVT_BUTTON(self, self.btnPlotGamma.GetId(), self.plotGamma)
        self.btnPlotSpectra = wx.Button(
            parent, -1, "Plot spectra")
        wx.EVT_BUTTON(self, self.btnPlotSpectra.GetId(), self.plotSpectra)
        
        photometerBox.AddMany([self.btnGetPR650,self.comPortLabel,
                               self.btnCalibrateGamma, (0,0),#self.choiceLinearMethod,
                               self.btnTestGamma, self.btnPlotGamma,
                               self.btnCalibrateColor, self.btnPlotSpectra])
        
        #-----------------------
        #----GAMMA------------
        #-----------------------
        #calibration grid     
        gammaBox = wx.StaticBox(parent,-1,'Linearisation')
        gammaBox.SetFont(wx.Font(14, wx.SWISS, wx.NORMAL, wx.NORMAL))
        gammaBoxSizer = wx.StaticBoxSizer(gammaBox, wx.VERTICAL)
        
        self.gammaGrid = SimpleGrid(parent, id=-1, 
                                    cols=['min','max','Gamma'],
                                    rows=['lum','R','G','B'])
        gammaBoxSizer.Add(self.gammaGrid)
        grid.EVT_GRID_CELL_CHANGE(self.gammaGrid,self.onChangeGammaGrid)
        gammaBoxSizer.Layout()
        
        #LMS grid        
        LMSbox = wx.StaticBox(parent,-1,'LMS->RGB')
        LMSboxSizer = wx.StaticBoxSizer(LMSbox, wx.VERTICAL)
        self.LMSgrid = SimpleGrid(parent, id=-1, 
                                  cols=['L','M','S'],
                                  rows=['R','G','B'])
        LMSboxSizer.Add(self.LMSgrid)
        LMSboxSizer.Layout()
        grid.EVT_GRID_CELL_CHANGE(self.LMSgrid,self.onChangeLMSgrid)
        
        #DKL grid
        DKLbox = wx.StaticBox(parent,-1,'DKL->RGB')
        DKLboxSizer = wx.StaticBoxSizer(DKLbox, wx.VERTICAL)
        self.DKLgrid = SimpleGrid(parent, id=-1, 
                                  cols=['Lum','L-M','L+M-S'],
                                  rows=['R','G','B'])
        DKLboxSizer.Add(self.DKLgrid)
        DKLboxSizer.Layout()
        grid.EVT_GRID_CELL_CHANGE(self.DKLgrid,self.onChangeDKLgrid)
        
        
        calibBoxMainSizer = wx.BoxSizer(wx.VERTICAL)
        calibBoxMainSizer.AddMany([photometerBox,
                                   gammaBoxSizer,
                                   LMSboxSizer,
                                   DKLboxSizer,
                                  ])
        calibBoxMainSizer.Layout()
            
        if NOTEBOOKSTYLE:
            return calibBoxMainSizer
        else:
            #put the main sizer into a labeled box	
            calibBox.Add(calibBoxMainSizer)
            return calibBox
    
    def loadMonitor(self, name=None):
        self.currentMon = monitors.Monitor(name, verbose=False)
        self.currentCalibName= self.currentMon.setCurrent(-1)
        self.updateCalibList()
        self.unSavedMonitor=False
        
    def updateMonList(self):
        #refresh list of all available monitors on path
        monList = monitors.getAllMonitors()
        self.ctrlMonList.Set(monList)
        #if we had selected a monitor, make sure it's still selected
        if len(monList)>0:
            if self.currentMonName!=None:
                self.ctrlMonList.SetStringSelection(self.currentMonName)
            else:
                self.ctrlMonList.SetSelection(0)
                self.onChangeMonSelection(event=-1)
            #do we need to update the calibList always after this?
            return 1
        else:
            #there are no monitors - create an empty one to popoulate the fields
            self.currentMon = monitors.Monitor('',verbose=False)
            self.currentMonName=None
            return 0 #there were no monitors on the path
    def updateCalibList(self, thisList=None):
        """update the list of calibrations either from the current
        monitor or to a given list
        """
        if thisList==None: #fetch it from monitor file
            thisList = self.currentMon.calibNames
        #populate the listbox
        self.ctrlCalibList.Set(thisList)
        #select the current calib
        if self.currentCalibName in thisList:
            self.ctrlCalibList.SetStringSelection(self.currentCalibName)
            self.onChangeCalibSelection(event=-1)

#application callbacks
    def onCloseWindow(self, event):
        if self.unSavedMonitor:
            #warn user that data will be lost
            dlg = dialogs.MessageDialog(self,message='Save changes to monitor settings before quitting?',type='Warning')
            resp = dlg.ShowModal()
            if resp  == wx.ID_CANCEL:
                return 1 #return before quitting
            elif resp == wx.ID_YES:
                #save then quit
                self.currentMon.saveMon()
            elif resp == wx.ID_NO:
                pass #don't save just quit
            dlg.Destroy()
        self.Destroy()

#admin callbacks
    def onChangeMonSelection(self, event):
        if self.unSavedMonitor:
            if self.currentMonName == self.ctrlMonList.GetStringSelection():
                #it didnt' really change
                return 1
            #warn user that data will be lost
            dlg = dialogs.MessageDialog(self, 'Save changes to monitor?',
                type='Warning')
            resp = dlg.ShowModal()
            dlg.Destroy()
            if resp  == wx.ID_CANCEL:
                #revert and return
                self.ctrlMonList.SetStringSelection(self.currentMonName)
                return False #return before quitting
            elif resp == wx.ID_YES:
                #save then change
                self.currentMon.saveMon()
            elif resp == wx.ID_NO:
                pass #don't save just change
        self.currentMonName = self.ctrlMonList.GetStringSelection()
        self.loadMonitor(self.currentMonName)
    def onChangeCalibSelection(self, event, newCalib=None):
        #get data under current calibration
        if newCalib == None:
            #probably came from an event - check the new name
            newCalib = self.ctrlCalibList.GetStringSelection()
        #do the load and check new name
        self.currentCalibName= self.currentMon.setCurrent(newCalib)
        
        #keys that may not exist
        #todo remove this code - only needed for monitor objects made pre version 0.63
        if not self.currentMon.currentCalib.has_key('gammaGrid'):
            self.currentMon.currentCalib['gammaGrid']=monitors.numpy.ones((4,3), 'd')
        if not self.currentMon.currentCalib.has_key('lms_rgb'):
            self.currentMon.currentCalib['lms_rgb']=monitors.numpy.ones((3,3), 'd')
        if not self.currentMon.currentCalib.has_key('dkl_rgb'):
            self.currentMon.currentCalib['dkl_rgb']=monitors.numpy.ones((3,3), 'd')
        if not self.currentMon.currentCalib.has_key('sizePix'):
            self.currentMon.currentCalib['sizePix']= [1024,768]

        #insert values from new calib into GUI
        self.ctrlCalibDate.SetValue(
            monitors.strFromDate(self.currentMon.getCalibDate()))
        self.ctrlScrDist.SetValue(str(self.currentMon.getDistance()))
        self.ctrlScrWidth.SetValue(str(self.currentMon.getWidth()))
        self.ctrlScrPixHoriz.SetValue(str(self.currentMon.currentCalib['sizePix'][0]))
        self.ctrlScrPixVert.SetValue(str(self.currentMon.currentCalib['sizePix'][1]))
        #self.ctrlScrGamma.SetValue(str(self.currentMon.getGamma()))
        self.ctrlCalibNotes.SetValue(str(self.currentMon.getNotes()))
        self.ctrlUseBits.SetValue(self.currentMon.getUseBits())                    
        self.gammaGrid.setData(self.currentMon.getGammaGrid())
        #self.choiceLinearMethod.SetSelection(self.currentMon.getLineariseMethod()-1)
        self.LMSgrid.setData(self.currentMon.getLMS_RGB())
        self.DKLgrid.setData(self.currentMon.getDKL_RGB())
        
        if not self.currentMon.currentCalib.has_key('lumsPre'):
            self.btnPlotGamma.Enable(True)
        else: self.btnPlotGamma.Enable(True)
        if not self.currentMon.currentCalib.has_key('spectraRGB'):
            self.btnPlotSpectra.Enable(False)
        else: self.btnPlotSpectra.Enable(True)
        
        self.unSavedMonitor=False
        return 1

    def onSaveMon(self, event):
        """Saves calibration entry to location.
        Note that the calibration date will reflect the save date/time"""
        self.currentMon.saveMon()
        self.unSavedMonitor=False
    def onCopyCalib(self, event):
        """Creates a new calibration entry for the monitor.
        Note that the calibration date will reflect the save date/time"""

        #use time as initial guess at name
        calibTime = time.localtime()
        calibTimeStr = monitors.strFromDate(calibTime)

        #then use dialogue so user can override
        infoStr='Name of this calibration (for monitor "%s") will be:' \
            % self.currentMon.name
        dlg = wx.TextEntryDialog(self, message=infoStr, defaultValue=calibTimeStr)
        if dlg.ShowModal() == wx.ID_OK:
            newCalibName= dlg.GetValue()
            #update the GUI to reflect new calibration
            self.currentMon.copyCalib(newCalibName)
            self.currentMon.setCalibDate(calibTime)

            self.onChangeCalibSelection(1,newCalibName)
            self.updateCalibList()
            self.unSavedMonitor=True
        dlg.Destroy()
    def onNewMon(self, event):
        #open a dialogue to get the name
        dlg = wx.TextEntryDialog(self, 'New monitor name:')
        if dlg.ShowModal() == wx.ID_OK:
            self.currentMonName= dlg.GetValue()
            self.ctrlMonList.Append(self.currentMonName)
            self.ctrlMonList.SetStringSelection(self.currentMonName)
            self.currentMon = monitors.Monitor(self.currentMonName, verbose=True)
            self.updateCalibList()
            self.onChangeCalibSelection(event=1)
            self.unSavedMonitor=True
        dlg.Destroy()

    def onDeleteMon(self, event):
        monToDel = self.currentMonName
        dlg = dialogs.MessageDialog(parent=self, message='Are you sure you want to delete all details for? '+\
            monToDel + ' (cannot be undone)',
            type='Warning')
        response = dlg.ShowModal()
        dlg.Destroy()
        if response == wx.ID_YES:
            #delete it
            monitorFileName = os.path.join(monitors.monitorFolder,
                monToDel+".calib")
            os.remove(monitorFileName)
            self.currentMon=None
            self.currentMonName=None
            self.updateMonList()
            #load most recent calibration instead
            self.onChangeMonSelection(event=None)#this will load calibration "-1" (last calib)
            self.updateCalibList()

    def onDeleteCalib(self, event):
        calToDel = self.ctrlCalibList.GetStringSelection()
        #warn user that data will be lost
        dlg = dialogs.MessageDialog(parent=self, message='Are you sure you want to delete this calibration? '+\
            '(cannot be undone)',
            type='Warning')
        if dlg.ShowModal() == wx.ID_YES:
            #delete it
            self.currentMon.delCalib(calToDel)
            #load most recent calibration instead
            self.onChangeCalibSelection(event=None, newCalib=-1)#this will load calibration "-1" (last calib)
            self.updateCalibList()
        dlg.Destroy()

#info callbacks
    def onChangeCalibDate(self, event):
        #do we want the user to change a calib date?
        pass
    def onChangeCalibNotes(self, event):
        newVal = self.ctrlCalibNotes.GetValue()
        self.currentMon.setNotes(newVal)
        self.unSavedMonitor=True
    def onChangeScrDist(self, event):
        newVal = self.ctrlScrDist.GetValue()
        #convert to float
        if newVal=='None': newVal=None
        else:
            try: newVal=float(newVal)
            except: pass #ignore values that can't be a float
        #insert in calibration file
        self.currentMon.setDistance( newVal )
        self.unSavedMonitor=True
    def onChangeScrWidth(self, event):
        newVal = self.ctrlScrWidth.GetValue()
        #convert to float
        if newVal=='None': newVal=None
        else:
            try: newVal=float(newVal)
            except: pass #ignore values that can't be a float
        #insert in calibration file
        self.currentMon.setWidth( newVal )
        self.unSavedMonitor=True
    def onChangeScrPixHoriz(self, event):   
        newVal = self.ctrlScrPixHoriz.GetValue()  
        #convert to float
        if newVal=='None': newVal=None
        else:
            try: newVal=float(newVal)
            except: pass #ignore values that can't be a float
        self.currentMon.currentCalib['sizePix'][0] = newVal
        self.unSavedMonitor=True
    def onChangeScrPixVert(self, event):
        newVal = self.ctrlScrPixVert.GetValue()  
        #convert to float
        if newVal=='None': newVal=None
        else:
            try: newVal=float(newVal)
            except: pass #ignore values that can't be a float
        self.currentMon.currentCalib['sizePix'][1] = newVal
        self.unSavedMonitor=True
    
#calib callbacks        
    def onChangeGammaGrid(self, event):
        #convert to float
        newVal = self.gammaGrid.GetCellValue(event.GetRow(), event.GetCol())
        try: newVal=float(newVal)
        except: pass #ignore values that can't be a float
        #isnert in grid
        self.currentMon.currentCalib['gammaGrid'][event.GetRow(), event.GetCol()] = newVal
        self.unSavedMonitor=True
                
    def onChangeLMSgrid(self, event):
        #convert to float
        newVal = self.LMSgrid.GetCellValue(event.GetRow(), event.GetCol())
        try: newVal=float(newVal)
        except: pass #ignore values that can't be a float
        #isnert in grid
        self.currentMon.currentCalib['lms_rgb'][event.GetRow(), event.GetCol()] = newVal
        self.unSavedMonitor=True
        
    def onChangeDKLgrid(self, event):
        #convert to float
        newVal = self.DKLgrid.GetCellValue(event.GetRow(), event.GetCol())
        try: newVal=float(newVal)
        except: pass #ignore values that can't be a float
        #isnert in grid
        self.currentMon.currentCalib['dkl_rgb'][event.GetRow(), event.GetCol()] = newVal
        self.unSavedMonitor=True
        
    def onCalibGammaBtn(self, event):

        if NO_MEASUREMENTS:
            #recalculate from previous measure
            lumsPre = self.currentMon.getLumsPre()
            lumLevels = self.currentMon.getLevelsPre()
        else:
            #present a dialogue to get details for calibration
            calibDlg = GammaDlg(self, self.currentMon)
            if calibDlg.ShowModal()!=wx.ID_OK:
                calibDlg.Destroy()
                return 1
            nPoints = int(calibDlg.ctrlNPoints.GetStringSelection())
            stimSize = float(calibDlg.ctrlStimSize.GetValue())
            useBits = calibDlg.ctrlUseBits.GetValue()
            calibDlg.Destroy()
            autoMode = calibDlg.methodChoiceBx.GetStringSelection()        
            #run the calibration itself
            lumLevels=monitors.DACrange(nPoints)
            lumsPre = monitors.getLumSeriesPR650(photometer=self.PR650,
                                                 lumLevels=lumLevels,
                                                 useBits=useBits,
                                                 autoMode=autoMode,
                                                 winSize=self.currentMon.getSizePix(),
                                                 stimSize=stimSize)
            
            #allow user to type in values
            if autoMode=='semi':
                inputDlg = GammaLumValsDlg(lumLevels, parent=self)
                print 'built dlg'
                lumsPre = inputDlg.show()#will be [] if user cancels
                inputDlg.Destroy()
                
        #fit the gamma curves
        if len(lumsPre)>1:
            self.onCopyCalib(1)#create a new dated calibration
            self.currentMon.setLumsPre(lumsPre)#save for future
            self.currentMon.setLevelsPre(lumLevels)#save for future
            self.btnPlotGamma.Enable(True)
            #do the fits
            self.doGammaFits(lumLevels,lumsPre)

        else:
            print 'No lum values captured/entered'

    def doGammaFits(self, levels, lums):
        linMethod = self.currentMon.getLineariseMethod()
        currentCal = self.currentMon.currentCalib['gammaGrid']
        if linMethod == 3:
            #create new interpolator functions for the monitor
            print 'Creating linear interpolation for gamma'
            self.currentMon.lineariseLums(0.5, newInterpolators=True)
            for gun in [0,1,2,3]:
                currentCal[gun,0]=lums[gun,0]#min
                currentCal[gun,1]=lums[gun,-1]#max
                currentCal[gun,2]=-1.0#gamma makes no sense for this method
        else:
            print 'Fitting gamma equation(%i) to luminance data' %linMethod
            for gun in [0,1,2,3]:
                gamCalc = monitors.GammaCalculator(levels, lums[gun,:], eq=linMethod)
                currentCal[gun,0]=lums[gun,0]#min
                currentCal[gun,1]=lums[gun,-1]#max
                currentCal[gun,2]=gamCalc.gammaVal#gamma
                
        self.gammaGrid.setData(currentCal)
        self.unSavedMonitor=True
            
    def onChangeLinearMethod(self,event):
        newMethod = self.choiceLinearMethod.GetSelection()+1
        self.currentMon.setLineariseMethod(newMethod)
        self.unSavedMonitor=True
        if self.currentMon.getLumsPre()!=None:
            self.doGammaFits(self.currentMon.getLevelsPre(), self.currentMon.getLumsPre())
            
    def onCalibTestBtn(self,event):
        #set the gamma and test calibration
        currentCal = self.currentMon.currentCalib['gammaGrid']
        
        calibDlg = GammaDlg(self, self.currentMon)
        if calibDlg.ShowModal()!=wx.ID_OK:
            calibDlg.Destroy()
            return 1
        nPoints = int(calibDlg.ctrlNPoints.GetStringSelection())
        stimSize = float(calibDlg.ctrlStimSize.GetValue())
        useBits = calibDlg.ctrlUseBits.GetValue()
        calibDlg.Destroy()
        autoMode = calibDlg.methodChoiceBx.GetStringSelection()
        
        lumLevels=monitors.DACrange(nPoints)
        lumsPost = monitors.getLumSeriesPR650(photometer=self.PR650,
                                              lumLevels=lumLevels,
                                              useBits=useBits,
                                              autoMode=autoMode,
                                              winSize=self.currentMon.getSizePix(),
                                              stimSize=stimSize,
                                              monitor = self.currentMon,
                                              gamma=None,#causes the function to use monitor settings
                                              )
        
        if len(lumsPost)>1:
            self.currentMon.setLumsPost(lumsPost)#save for future
            self.currentMon.setLevelsPost(lumLevels)#save for future
            self.unSavedMonitor=True
            
    def onCalibColorBtn(self, event):
        if NO_MEASUREMENTS:
            #get previous spectra:
            nm, spectra = self.currentMon.getSpectra()
        else:
            #do spectral measurement:
            useBits=self.currentMon.getUseBits()
            nm, spectra = monitors.getRGBspectra(stimSize=0.5, photometer=self.PR650, winSize=self.currentMon.getSizePix())
            self.currentMon.setSpectra(nm,spectra)
            self.btnPlotSpectra.Enable(True)#can definitely now plot spectra
            self.unSavedMonitor=True
            
        self.onCopyCalib(1)#create a new dated calibration
        
        #dkl
        dkl_rgb = monitors.makeDKL2RGB(nm,spectra)
        self.currentMon.setDKL_RGB(dkl_rgb)
        self.DKLgrid.setData(dkl_rgb)
        #lms
        lms_rgb = monitors.makeLMS2RGB(nm,spectra)
        self.currentMon.setLMS_RGB(lms_rgb)
        self.LMSgrid.setData(lms_rgb)        
        
    def onChangeUseBits(self, event):
        newVal = self.ctrlUseBits.GetValue()
        self.currentMon.setUseBits(newVal)
        self.unSavedMonitor=True
        
    def onBtnGetPR650(self, event):
        #search all ports
        self.PR650 = monitors.findPR650()
        
        if self.PR650!=None and self.PR650.OK:
            self.btnGetPR650.Disable()
            self.btnCalibrateGamma.Enable(True)
            self.btnTestGamma.Enable(True)
            self.btnCalibrateColor.Enable(True)
            self.comPortLabel.SetLabel('PR650 found on %s' %self.PR650.portString)
        else:
            self.PR650=None
        
    #def onChgComPort(self, event):
        #thisVal = self.setComPort.GetValue()
        #if type(thisVal)==int:
            #self.comPort=thisVal
            #self.userSetComPort = True
            
    def plotGamma(self, event=None):  
        figTitle = '%s %s Gamma Functions' %(self.currentMonName, self.currentCalibName)
        plotWindow = PlotFrame(self,1003,figTitle)
        
        figure = Figure(figsize=(5,5), dpi=80)
        figureCanvas = FigureCanvas(plotWindow, -1, figure)          
        plt = figure.add_subplot(111)
        plt.hold('off')
        
        gammaGrid = self.currentMon.getGammaGrid()
        lumsPre = self.currentMon.getLumsPre()
        levelsPre = self.currentMon.getLevelsPre()
        lumsPost = self.currentMon.getLumsPost()
        if lumsPre!=None:
            colors='krgb'
            xxSmooth = monitors.numpy.arange(0,255.5, 0.5)
            for gun in range(4): #includes lum
                gamma = gammaGrid[gun,2]
                minLum = gammaGrid[gun,0]	
                maxLum = gammaGrid[gun,1]	
                if self.currentMon.getLineariseMethod()!=3:
                    #plot fitted curve
                    curve = monitors.gammaFun(xxSmooth, minLum, maxLum, gamma)
                    plt.plot(xxSmooth, curve, colors[gun]+'-', linewidth=1.5)
                else:
                    pass
                    #polyFit = self.currentMon._gammaInterpolator[gun]
                    #curve = xxSmooth*0.0
                    #for expon, coeff in enumerate(polyFit):
                        #curve += coeff*xxSmooth**expon
                    #plt.plot(xxSmooth, curve, colors[gun]+'-', linewidth=1.5)
                #plot POINTS
                plt.plot(levelsPre, lumsPre[gun,:], colors[gun]+'o', linewidth=1.5)
             
            lumsPost = self.currentMon.getLumsPost()
            levelsPost = self.currentMon.getLevelsPost()
        if lumsPost!=None:
            for gun in range(4): #includes lum,r,g,b
                lums=lumsPost[gun,:]
                gamma = gammaGrid[gun,2]
                gamma = gammaGrid[gun,2]
                minLum = min(lums)
                maxLum = max(lums)
                #plot CURVE
                plt.plot([levelsPost[0], levelsPost[-1]], [minLum, maxLum], colors[gun]+'--', linewidth=1.5)  
                #plot POINTS
                plt.plot(levelsPost,lums,'o', markerfacecolor = 'w', markeredgecolor=colors[gun], linewidth=1.5)   
        figureCanvas.draw()#update the canvas
        
        plotWindow.addCanvas(figureCanvas)
        
    def plotSpectra(self, event=None): 
        figTitle = '%s %s Spectra' %(self.currentMonName, self.currentCalibName)
        plotWindow = PlotFrame(self,1003,figTitle)
        figure = Figure(figsize=(5,5), dpi=80)
        figureCanvas = FigureCanvas(plotWindow, -1, figure)        
        plt = figure.add_subplot(111)
        plt.hold('off')
        
        nm, spectraRGB = self.currentMon.getSpectra()
        if nm!=None:      
            plt.plot(nm,spectraRGB[0,:], 'r-', linewidth=1.5)
            plt.hold('on')
            plt.plot(nm,spectraRGB[1,:], 'g-', linewidth=2)
            plt.plot(nm,spectraRGB[2,:], 'b-', linewidth=2)   
        figureCanvas.draw()#update the canvas
        plotWindow.addCanvas(figureCanvas)
        

class GammaLumValsDlg(wx.Dialog):
    #a dialogue to get the luminance values recorded for each level
    def __init__(self, levels):
        print 'trying to create lum vals dlg'
        wx.Dialog.__init__(self, parent, -1, 'Input recorded luminance values',
            style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER
            )
        mainSizer =  wx.BoxSizer(cols=2, hgap=10, vgap=10)
        rFields=[]
        gFields=[]
        bFields=[]
        lFields=[]
        allFields = [lFields, rFields, gFields, bFields]
        #add the necessary input boxes and labels
        for thisLevel in levels:
            thisRow=wx.BoxSizer(wx.HORIZONTAL)    
            inputLabel = wx.StaticText(self,-1,label,
                                            size=labelLength,
                                            style=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL)
            inputBox = wx.TextCtrl(self,-1,size=(30,25))
            inputFields.append(inputBox)
            thisRow.Add(inputLabel, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT)
            thisRow.Add(inputBox,1)
            mainSizer.Add(thisRow, 1, wx.ALIGN_CENTER)
            
        #add buttons for OK and Cancel
        buttons = wx.BoxSizer(wx.HORIZONTAL)
        OK = wx.Button(self, wx.ID_OK, " OK ")
        OK.SetDefault()
        buttons.Add(OK)	
        CANCEL = wx.Button(self, wx.ID_CANCEL, " Cancel ")
        buttons.Add(CANCEL)
        self.sizer.Add(buttons,1,flag=wx.ALIGN_RIGHT|wx.ALIGN_BOTTOM,border=5)
        
        self.SetSizerAndFit(self.sizer)     
        
    def show(self):
        #show dialog and retrieve data   
        print 'showing'
        ok = self.ShowModal()
        if  ok == wx.ID_OK:
            #get data from input fields
            for thisField in allFields:#select the particular gun/column
                for n in range(len(self.inputFields)):
                    thisVal = self.inputFields[n].GetValue()
                    thisField.append(float(thisVal))
        else:
            allFields= []
            
        return allFields
        
class GammaDlg(wx.Dialog):
    def __init__(self, parent, monitor):
        self.method = 'auto'
        self.nPoints = 8
        assert isinstance(monitor, monitors.Monitor)
        self.useBits=monitor.getUseBits()


        wx.Dialog.__init__(self, parent, -1, 'Gamma Calibration',
            style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER
            )
        mainSizer =  wx.FlexGridSizer(cols=2, hgap=10, vgap=10)

        #select method of calib (auto, semi-auto, manual)
        #todo: make the input  tablefor manual method
        self.methodChoiceBx = wx.Choice(self, -1,
            choices=['auto','semi'])
        self.methodChoiceBx.SetStringSelection('auto')
        wx.EVT_CHOICE(self, self.methodChoiceBx.GetId(),self.onMethodChange)

        self.ctrlUseBits = wx.CheckBox(self, -1,'Use Bits++')
        self.ctrlUseBits.SetValue(self.useBits)

        self.labelNPoints = wx.StaticText(self, -1, 'Number of calibration points:')
        self.ctrlNPoints = wx.Choice(self, -1,
            choices=['3','4','5','6','7','8','10','16','48','64','256'])
        self.ctrlNPoints.SetStringSelection('8')

        self.labelStimSize = wx.StaticText(self, -1, 'Patch size (fract of screen):')
        self.ctrlStimSize = wx.TextCtrl(self, -1,'0.3')

        mainSizer.AddMany([(0,0),self.methodChoiceBx,
            self.labelNPoints, self.ctrlNPoints,
            self.labelStimSize, self.ctrlStimSize,
            (0,0),self.ctrlUseBits
            ])

        btnOK = wx.Button(self, wx.ID_OK, " OK ")
        btnOK.SetDefault()
        mainSizer.Add(btnOK, flag=wx.ALIGN_RIGHT)
        btnCANC = wx.Button(self, wx.ID_CANCEL, " Cancel ")
        mainSizer.Add(btnCANC, flag=wx.ALIGN_RIGHT)

        #mainSizer.Fit(self)
        self.SetAutoLayout(True)
        self.SetSizerAndFit(mainSizer)

    def onMethodChange(self, event):
        pass

class MonitorCenter(wx.App):
    def OnInit(self):
        print 'running MonitorCenter'
        frame = MainFrame(None,'PsychoPy Monitor Center')
        frame.Show(True)
        self.SetTopWindow(frame)
        return True

if __name__ == '__main__':
    app = MonitorCenter(0)
    app.MainLoop()
