#!/usr/bin/env python
"""
Epics XRF Display App
"""

import sys
import os

import time
import copy
from functools import partial

import wx
import wx.lib.mixins.inspection
import wx.lib.scrolledpanel as scrolled
import wx.dataview as dv

import numpy as np
import matplotlib

from wxmplot import PlotPanel
from wxutils import (SimpleText, EditableListBox, Font, FloatCtrl,
                     pack, Popup, Button, get_icon, Check, MenuItem,
                     Choice, FileOpen, FileSave, fix_filename, HLine,
                     GridPanel, CEN, LEFT, RIGHT)

import larch
from larch.site_config import icondir
from larch.wxlib import PeriodicTablePanel, LarchWxApp
from larch.wxlib.xrfdisplay import (XRFDisplayFrame, XRFCalibrationFrame,
                                    FILE_WILDCARDS)
from larch.utils import get_cwd

ROI_WILDCARD = 'Data files (*.dat)|*.dat|ROI files (*.roi)|*.roi|All files (*.*)|*.*'
try:
    from epics import caget
    from .xrf_detectors import Epics_MultiXMAP, Epics_Xspress3
except:
    pass

class DetectorSelectDialog(wx.Dialog):
    """Connect to an Epics MCA detector
    Can be either XIA xMAP  or Quantum XSPress3
    """
    msg = '''Select XIA xMAP or Quantum XSPress3 MultiElement MCA detector'''
    det_types = ('SXD-7', 'ME-7', 'ME-4', 'other')
    ioc_types = ('Xspress3.1', 'xMAP', 'Xspress3.0')
    def_prefix = '13QX7:'   # SDD1:'
    def_nelem  =  4

    def __init__(self, parent=None, prefix=None, det_type='ME-4',
                 ioc_type='Xspress3', nmca=4,
                 title='Select Epics MCA Detector'):
        if prefix is None:
            prefix = self.def_prefix
        if det_type not in self.det_types:
            det_type = self.det_types[0]

        wx.Dialog.__init__(self, parent, wx.ID_ANY, title=title)

        self.SetBackgroundColour((240, 240, 230))
        self.SetFont(Font(9))
        if parent is not None:
            self.SetFont(parent.GetFont())

        self.ioctype = Choice(self,size=(120, -1), choices=self.ioc_types)
        self.ioctype.SetStringSelection(ioc_type)

        self.dettype = Choice(self,size=(120, -1), choices=self.det_types)
        self.dettype.SetStringSelection(det_type)

        self.prefix = wx.TextCtrl(self, -1, prefix, size=(120, -1))
        self.nelem = FloatCtrl(self, value=nmca, precision=0, minval=1,
                               size=(120, -1))

        btnsizer = wx.StdDialogButtonSizer()

        if wx.Platform != "__WXMSW__":
            btn = wx.ContextHelpButton(self)
            btnsizer.AddButton(btn)

        btn = wx.Button(self, wx.ID_OK)
        btn.SetHelpText("Use this detector")
        btn.SetDefault()
        btnsizer.AddButton(btn)

        btn = wx.Button(self, wx.ID_CANCEL)
        btnsizer.AddButton(btn)
        btnsizer.Realize()

        hline = wx.StaticLine(self, size=(225, 3), style=wx.LI_HORIZONTAL)
        sty = LEFT
        sizer = wx.GridBagSizer(5, 2)
        def txt(label):
            return SimpleText(self, label, size=(120, -1), style=LEFT)

        sizer.Add(txt('  Detector Type'),  (0, 0), (1, 1), sty, 2)
        sizer.Add(txt('  Uses Xspress3?'), (1, 0), (1, 1), sty, 2)
        sizer.Add(txt('  Epics Prefix'),  (2, 0), (1, 1), sty, 2)
        sizer.Add(txt('  # Elements'),    (3, 0), (1, 1), sty, 2)
        sizer.Add(self.dettype,         (0, 1), (1, 1), sty, 2)
        sizer.Add(self.ioctype,         (1, 1), (1, 1), sty, 2)
        sizer.Add(self.prefix,          (2, 1), (1, 1), sty, 2)
        sizer.Add(self.nelem,           (3, 1), (1, 1), sty, 2)

        sizer.Add(hline,                (4, 0), (1, 2), sty, 2)
        sizer.Add(btnsizer,             (5, 0), (1, 2), sty, 2)
        self.SetSizer(sizer)
        sizer.Fit(self)


class EpicsXRFDisplayFrame(XRFDisplayFrame):
    _about = """Epics XRF Spectra Display
  Matt Newville <newville @ cars.uchicago.edu>
  """
    me4_layout = ((0, 0), (1, 0), (1, 1), (0, 1))
    main_title = 'Epics XRF Control'

    def __init__(self, parent=None, _larch=None, prefix=None,
                 det_type='ME-4', ioc_type='Xspress3', nmca=4,
                 size=(725, 580), environ_file=None,
                 title='Epics XRF Display', output_title='XRF', **kws):

        self.det_type = det_type
        self.ioc_type = ioc_type
        self.nmca = nmca
        self.det_fore = 1
        self.det_back = 0
        self.environ = []
        if environ_file is not None:
            self.read_environfile(environ_file)

        self.onConnectEpics(event=None, prefix=prefix)

        self.icon_file = os.path.join(icondir, 'ptable.ico')

        XRFDisplayFrame.__init__(self, parent=parent, _larch=_larch,
                                 title=title, size=size, **kws)

    def read_environfile(self, filename):
        """read environmnet file"""
        if os.path.exists(filename):
            textlines = []
            try:
                with open(filename, 'r') as fh:
                    textlines = fh.readlines()
            except IOError:
                return
            self.environ = []
            for line in textlines:
                line = line[:-1].replace('\t', ' ')
                pvname, desc = line.split(None, 1)
                desc = desc.strip()
                self.environ.append((pvname, desc))

    def onConnectEpics(self, event=None, prefix=None, **kws):
        if prefix is None:
            res  = self.prompt_for_detector(prefix=prefix,
                                            ioc_type=self.ioc_type,
                                            nmca=self.nmca)
            self.prefix, self.det_type, self.ioc_type, self.nmca = res
        else:
            self.prefix = prefix
        self.det_fore = 1
        self.det_back = 0
        self.clear_mcas()
        self.connect_to_detector(prefix=self.prefix, ioc_type=self.ioc_type,
                                 det_type=self.det_type, nmca=self.nmca)

    def onSaveMCAFile(self, event=None, **kws):
        tmp = '''
        # print('SaveMCA File')
        deffile = ''
        if hasattr(self.mca, 'sourcefile'):
            deffile = "%s%s" % (deffile, getattr(self.mca, 'sourcefile'))
        if hasattr(self.mca, 'areaname'):
            deffile = "%s%s" % (deffile, getattr(self.mca, 'areaname'))
        if deffile == '':
            deffile ='test'
        if not deffile.endswith('.mca'):
            deffile = deffile + '.mca'
        '''

        deffile = 'save.mca' # fix_filename(str(deffile))
        outfile = FileSave(self, "Save MCA File",
                           default_file=deffile,
                           wildcard=FILE_WILDCARDS)

        env = []
        if len(self.environ) > 0:
            for pvname, desc in self.environ:
                val  = caget(pvname, as_string=True)
                env.append((pvname, val, desc))

        if outfile is not None:
            self.det.save_mcafile(outfile, environ=env)

    def onSaveColumnFile(self, event=None, **kws):
        print( '  EPICS-XRFDisplay onSaveColumnFile not yet implemented  ')
        pass

    def prompt_for_detector(self, prefix=None, ioc_type='Xspress3',  nmca=4):
        dlg = DetectorSelectDialog(prefix=prefix, ioc_type=ioc_type, nmca=nmca)
        dlg.Raise()
        if dlg.ShowModal() == wx.ID_OK:
            dpref = dlg.prefix.GetValue()
            atype = dlg.ioctype.GetStringSelection()
            dtype = dlg.dettype.GetStringSelection()
            nmca = dlg.nelem.GetValue()
            dlg.Destroy()
        return dpref, dtype, atype, nmca

    def connect_to_detector(self, prefix=None, ioc_type='Xspress3',
                            det_type=None, nmca=4):
        self.det = None
        ioc_type = ioc_type.lower()
        if ioc_type.startswith('xspress3'):
            version = 2
            if 'old' in ioc_type:
                version = 1
            self.det = Epics_Xspress3(prefix=prefix, nmca=nmca, version=version)
            self.det.connect()
            time.sleep(0.5)
            self.det.get_mca(mca=1)
            self.needs_newplot=True
        else:
            self.det = Epics_MultiXMAP(prefix=prefix, nmca=nmca)
        time.sleep(0.05)

    def show_mca(self, init=False):
        self.needs_newplot = False
        if self.mca is None or self.needs_newplot:
            self.mca = self.det.get_mca(mca=self.det_fore)

        self.plotmca(self.mca, set_title=False, init=init)
        title = "Foreground: MCA{:d}".format(self.det_fore)
        if self.det_back  > 0:
            if self.mca2 is None:
                self.mca2 = self.det.get_mca(mca=self.det_back)

            c2 = self.det.get_array(mca=self.det_back)
            e2 = self.det.get_energy(mca=self.det_back)
            title = "{:s}  Background: MCA{:d}".format(title, self.det_back)
            try:
                self.oplot(e2, c2)
            except ValueError:
                pass

        roiname = self.get_roiname()

        if roiname in self.wids['roilist'].GetStrings():
            i = self.wids['roilist'].GetStrings().index(roiname)
            self.wids['roilist'].EnsureVisible(i)
            self.onROI(label=roiname)
        deadtime = self.det.get_deadtime(mca=self.det_fore)
        if deadtime is not None:
            self.wids['deadtime'].SetLabel("%.1f" % deadtime)
        self.SetTitle("%s: %s" % (self.main_title, title))
        self.needs_newplot = False

    def onSaveROIs(self, event=None, **kws):
        dlg = wx.FileDialog(self, message="Save ROI File",
                            defaultDir=get_cwd(),
                            wildcard=ROI_WILDCARD,
                            style = wx.FD_SAVE|wx.FD_CHANGE_DIR)

        if dlg.ShowModal() == wx.ID_OK:
            roifile = dlg.GetPath()

        self.det.save_rois(roifile)

    def onRestoreROIs(self, event=None, **kws):
        dlg = wx.FileDialog(self, message="Read ROI File",
                            defaultDir=get_cwd(),
                            wildcard=ROI_WILDCARD,
                            style = wx.FD_OPEN|wx.FD_CHANGE_DIR)

        if dlg.ShowModal() == wx.ID_OK:
            roifile = dlg.GetPath()
            self.det.restore_rois(roifile)
            self.set_roilist(mca=self.mca)
            self.show_mca()
            self.onSelectDet(event=None, index=0)

    def createCustomMenus(self):
        menu = wx.Menu()
        MenuItem(self, menu, "Connect to Detector\tCtrl+D",
                 "Connect to MCA or XSPress3 Detector",
                 self.onConnectEpics)
        menu.AppendSeparator()
        self._menus.insert(1, (menu, 'Detector'))

    def createMainPanel(self):
        epicspanel = self.createEpicsPanel()
        ctrlpanel  = self.createControlPanel()
        plotpanel  = self.panel = self.createPlotPanel()
        self.panel.SetName('plotpanel')
        tx, ty = self.wids['ptable'].GetBestSize()
        cx, cy = ctrlpanel.GetBestSize()
        px, py = plotpanel.GetBestSize()

        self.SetSize((950, 625))
        self.SetMinSize((450, 350))

        style = wx.ALIGN_LEFT|wx.EXPAND|wx.ALL

        bsizer = wx.BoxSizer(wx.HORIZONTAL)
        bsizer.Add(ctrlpanel, 0, style, 1)
        bsizer.Add(plotpanel, 1, style, 1)
        hline = wx.StaticLine(self, size=(425, 2), style=wx.LI_HORIZONTAL|style)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(epicspanel, 0, style, 1)
        sizer.Add(hline,      0, style, 1)
        sizer.Add(bsizer,     1, style, 1)
        pack(self, sizer)

        try:
            self.SetIcon(wx.Icon(self.icon_file, wx.BITMAP_TYPE_ICO))
        except:
            pass
        self.set_roilist(mca=None)

    def create_detbuttons(self, pane):
        btnpanel = wx.Panel(pane, name='buttons')
        btnsizer = wx.GridBagSizer(1, 1)
        btns = {}
        sx = 36
        sy = int(sx/2)
        for i in range(1, self.nmca+1):
            b = Button(btnpanel, f'{i}', size=(sx, sx),
                       action=partial(self.onSelectDet, index=i))
            b.SetFont(Font(9))
            self.wids['det%i' % i] = b
            btns[i] = b
        dtype = self.det_type.lower().replace('-', '').replace(' ', '').replace('_', '')
        if dtype.startswith('sxd7') and self.nmca == 7:
            btnsizer.Add((sx, sy), (0, 0), (1, 2), wx.ALIGN_LEFT, 1)
            btnsizer.Add(btns[6],  (1, 0), (2, 2), wx.ALIGN_LEFT, 1)
            btnsizer.Add(btns[7],  (3, 0), (2, 2), wx.ALIGN_LEFT, 1)
            btnsizer.Add((sx, sy), (5, 0), (1, 2), wx.ALIGN_LEFT, 1)
            btnsizer.Add(btns[5],  (0, 2), (2, 2), wx.ALIGN_LEFT, 1)
            btnsizer.Add(btns[4],  (2, 2), (2, 2), wx.ALIGN_LEFT, 1)
            btnsizer.Add(btns[1],  (4, 2), (2, 2), wx.ALIGN_LEFT, 1)
            btnsizer.Add((sx, sy), (0, 4), (1, 2), wx.ALIGN_LEFT, 1)
            btnsizer.Add(btns[3],  (1, 4), (2, 2), wx.ALIGN_LEFT, 1)
            btnsizer.Add(btns[2],  (3, 4), (2, 2), wx.ALIGN_LEFT, 1)
            btnsizer.Add((sx, sy), (5, 4), (1, 2), wx.ALIGN_LEFT, 1)
        elif dtype.startswith('me7') and self.nmca == 7:
            btnsizer.Add((sx, sy), (0, 0), (1, 2), wx.ALIGN_LEFT, 1)
            btnsizer.Add(btns[7],  (1, 0), (2, 2), wx.ALIGN_LEFT, 1)
            btnsizer.Add(btns[6],  (3, 0), (2, 2), wx.ALIGN_LEFT, 1)
            btnsizer.Add((sx, sy), (5, 0), (1, 2), wx.ALIGN_LEFT, 1)
            btnsizer.Add(btns[2],  (0, 2), (2, 2), wx.ALIGN_LEFT, 1)
            btnsizer.Add(btns[1],  (2, 2), (2, 2), wx.ALIGN_LEFT, 1)
            btnsizer.Add(btns[5],  (4, 2), (2, 2), wx.ALIGN_LEFT, 1)
            btnsizer.Add((sx, sy), (0, 4), (1, 2), wx.ALIGN_LEFT, 1)
            btnsizer.Add(btns[3],  (1, 4), (2, 2), wx.ALIGN_LEFT, 1)
            btnsizer.Add(btns[4],  (3, 4), (2, 2), wx.ALIGN_LEFT, 1)
            btnsizer.Add((sx, sy), (5, 4), (1, 2), wx.ALIGN_LEFT, 1)
        elif dtype.startswith('me4') and self.nmca == 4:
            btnsizer.Add(btns[1],  (0, 0), (1, 1), wx.ALIGN_LEFT, 1)
            btnsizer.Add(btns[2],  (1, 0), (1, 1), wx.ALIGN_LEFT, 1)
            btnsizer.Add(btns[3],  (1, 1), (1, 1), wx.ALIGN_LEFT, 1)
            btnsizer.Add(btns[4],  (0, 1), (1, 1), wx.ALIGN_LEFT, 1)
        else:
            NPERROW = 4
            icol, irow = 0, 0
            for nmca  in range(1, self.nmca+1):
                btnsizer.Add(btns[nmca],  (irow, icol), (1, 1), wx.ALIGN_LEFT, 1)
                icol += 1
                if icol > NPERROW-1:
                    icol = 0
                    irow += 1

        pack(btnpanel, btnsizer)
        return btnpanel

    def createEpicsPanel(self):
        pane = wx.Panel(self, name='epics panel')
        style  = wx.ALIGN_LEFT
        rstyle = wx.ALIGN_RIGHT

        det_btnpanel = self.create_detbuttons(pane)

        bkg_choices = ['None'] + ["%d" % (i+1) for i in range(self.nmca)]

        self.wids['det_status'] = SimpleText(pane, ' ', size=(120, -1), style=style)
        self.wids['deadtime']   = SimpleText(pane, ' ', size=(120, -1), style=style)

        self.wids['bkg_det'] = Choice(pane, size=(100, -1), choices=bkg_choices,
                                      action=self.onSelectDet)

        self.wids['dwelltime'] = FloatCtrl(pane, value=0.0, precision=1, minval=0,
                                           size=(80, -1), act_on_losefocus=True,
                                           action=self.onSetDwelltime)
        self.wids['elapsed'] = SimpleText(pane, ' ', size=(80, -1),  style=style)

        self.wids['mca_sum'] = Choice(pane, size=(100, -1),
                                      choices=['Single', 'Accumulate'],
                                      action=self.onMcaSumChoice,
                                      default=1 )

        b1 =  Button(pane, 'Start',      size=(90, -1), action=self.onStart)
        b2 =  Button(pane, 'Stop',       size=(90, -1), action=self.onStop)
        b3 =  Button(pane, 'Erase',      size=(90, -1), action=self.onErase)
        b4 =  Button(pane, 'Continuous', size=(90, -1), action=partial(self.onStart,
                                                                       dtime=0.0))

        sum_lab = SimpleText(pane, 'Accumulate Mode:',   size=(150, -1))
        bkg_lab = SimpleText(pane, 'Background MCA:',   size=(150, -1))
        pre_lab = SimpleText(pane, 'Dwell Time (s):',   size=(125, -1))
        ela_lab = SimpleText(pane, 'Elapsed Time (s):', size=(125, -1))
        sta_lab = SimpleText(pane, 'Status :',          size=(100, -1))
        dea_lab = SimpleText(pane, '% Deadtime:',       size=(100, -1))

        psizer = wx.GridBagSizer(5, 5)
        psizer.Add(SimpleText(pane, ' MCAs: '),  (0, 0), (1, 1), style, 1)
        psizer.Add(det_btnpanel,           (0, 1), (2, 1), style, 1)
        psizer.Add(bkg_lab,                (0, 2), (1, 1), style, 1)
        psizer.Add(self.wids['bkg_det'],   (0, 3), (1, 1), style, 1)
        psizer.Add(sum_lab,                (1, 2), (1, 1), style, 1)
        psizer.Add(self.wids['mca_sum'],   (1, 3), (1, 1), style, 1)
        psizer.Add(pre_lab,                (0, 4), (1, 1),  style, 1)
        psizer.Add(ela_lab,                (1, 4), (1, 1),  style, 1)
        psizer.Add(self.wids['dwelltime'], (0, 5), (1, 1),  style, 1)
        psizer.Add(self.wids['elapsed'],   (1, 5), (1, 1),  style, 1)

        psizer.Add(b1, (0, 6), (1, 1), style, 1)
        psizer.Add(b4, (0, 7), (1, 1), style, 1)
        psizer.Add(b2, (1, 6), (1, 1), style, 1)
        psizer.Add(b3, (1, 7), (1, 1), style, 1)

        psizer.Add(sta_lab,                  (0, 8), (1, 1), style, 1)
        psizer.Add(self.wids['det_status'],  (0, 9), (1, 1), style, 1)
        psizer.Add(dea_lab,                  (1, 8), (1, 1), style, 1)
        psizer.Add(self.wids['deadtime'],    (1, 9), (1, 1), style, 1)
        pack(pane, psizer)
        # pane.SetMinSize((500, 53))
        self.det.connect_displays(status=self.wids['det_status'],
                                  elapsed=self.wids['elapsed'])

        wx.CallAfter(self.onSelectDet, index=1, init=True)
        self.timer_counter = 0
        self.mca_timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.UpdateData, self.mca_timer)
        self.mca_timer.Start(250)
        return pane

    def UpdateData(self, event=None, force=False):
        self.timer_counter += 1
        if self.mca is None or self.needs_newplot:
            self.show_mca()
        # self.elapsed_real = self.det.elapsed_real
        self.mca.real_time = self.det.elapsed_real
        # print("Update Data  ", force, self.det.needs_refresh)

        if force or self.det.needs_refresh:
            self.det.needs_refresh = False
            if self.det_back > 0:
                if self.mca2 is None:
                    self.mca2 = self.det.get_mca(mca=self.det_back)

                counts = self.det.get_array(mca=self.det_back)
                energy = self.det.get_energy(mca=self.det_back)
                try:
                    self.update_mca(counts, energy=energy, is_mca2=True, draw=False)
                except ValueError:
                    pass

            if self.mca is None:
                self.mca = self.det.get_mca(mca=self.det_fore)

            dtime = self.det.get_deadtime(mca=self.det_fore)
            if dtime is not None:
                self.wids['deadtime'].SetLabel("%.1f" % dtime)

            counts = self.det.get_array(mca=self.det_fore)*1.0
            energy = self.det.get_energy(mca=self.det_fore)
            if max(counts) < 1.0:
                counts    = 1e-4*np.ones(len(counts))
                counts[0] = 2.0
            self.update_mca(counts, energy=energy)

    def ShowROIStatus(self, left, right, name='', panel=0):
        if left > right:
            return
        sum = self.ydata[left:right].sum()

        try:
            ftime, nframes = self.det.get_frametime()
        except:
            ftime   = self.det.frametime
            nframes = self.det.nframes
        self.det.elapsed_real = nframes * ftime

        mca_counts = self.det.mcas[self.det_fore-1].get('VAL')
        sum =  mca_counts[left:right].sum()
        # print("ROI STATUS ", name, ftime, nframes, sum, cps, mca_counts.sum(),  mca_counts)
        if name in (None, ''):
            name = 'Selected'
        else:
            for roi in self.det.mcas[self.det_fore-1].rois:
                if name.lower() == roi.name.lower():
                    try:
                        sum = roi.sum
                    except:
                        pass
        cps = sum/ftime
        if cps < 0: cps = 0
        # print("ROI STATUS ", name, _counts, cps)
        fmt = " {:s}: Cts={:10,.0f} :{:10,.1f} Hz"
        self.write_message(fmt.format(name, sum, cps), panel=panel)

    def onSelectDet(self, event=None, index=0, init=False, **kws):
        if index > 0:
            self.det_fore = index
        self.det_back = self.wids['bkg_det'].GetSelection()
        if self.det_fore  == self.det_back:
            self.det_back = 0

        for i in range(1, self.nmca+1):
            dname = 'det%i' % i
            bcol = (210, 210, 210)
            fcol = (0, 0, 0)
            if i == self.det_fore:
                fcol = (200,  20,  20)
                bcol = (250, 250, 250)
            self.wids[dname].SetBackgroundColour(bcol)
            self.wids[dname].SetForegroundColour(fcol)
        self.clear_mcas()
        self.show_mca(init=init)
        self.Refresh()

    def swap_mcas(self, event=None):
        if self.mca2 is None:
            return
        self.mca, self.mca2 = self.mca2, self.mca
        fore, back = self.det_fore, self.det_back
        self.wids['bkg_det'].SetSelection(fore)
        self.onSelectDet(index=back)

    def onMcaSumChoice(self, event=None):
        wid = self.wids['mca_sum']
        self.det.set_usesum('accum' in wid.GetStringSelection().lower())

    def onSetDwelltime(self, event=None, **kws):
        if 'dwelltime' in self.wids:
            self.det.set_dwelltime(dtime=self.wids['dwelltime'].GetValue())

    def clear_mcas(self):
        self.mca = self.mca2 = None
        self.x2data = self.y2data = None
        self.needs_newplot = True

    def onStart(self, event=None, dtime=None, **kws):
        if dtime is not None:
            self.wids['dwelltime'].SetValue("%.1f" % dtime)
            self.det.set_dwelltime(dtime=dtime)
        else:
            self.det.set_dwelltime(dtime=self.wids['dwelltime'].GetValue())
        self.det.start()

    def onStop(self, event=None, **kws):
        self.det.stop()
        self.det.needs_refresh = True
        time.sleep(0.05)
        self.UpdateData(event=None, force=True)

    def onErase(self, event=None, **kws):
        self.needs_newplot = True
        self.det.erase()

    def onDelROI(self, event=None):
        roiname = self.get_roiname()
        errmsg = None
        t0 = time.time()
        if self.roilist_sel is None:
            errmsg = 'No ROI selected to delete.'
        if errmsg is not None:
            return Popup(self, errmsg, 'Cannot Delete ROI')

        self.det.del_roi(roiname)
        XRFDisplayFrame.onDelROI(self)


    def onNewROI(self, event=None):
        roiname = self.get_roiname()
        errmsg = None
        if self.xmarker_left is None or self.xmarker_right is None:
            errmsg = 'Must select right and left markers to define ROI'
        elif roiname in self.wids['roilist'].GetStrings():
            errmsg = '%s is already in ROI list - use a unique name.' % roiname
        if errmsg is not None:
            return Popup(self, errmsg, 'Cannot Define ROI')

        confirmed = XRFDisplayFrame.onNewROI(self)
        if confirmed:
            self.det.add_roi(roiname, lo=self.xmarker_left,
                             hi=self.xmarker_right)

    def onRenameROI(self, event=None):
        roiname = self.get_roiname()
        errmsg = None
        if roiname in self.wids['roilist'].GetStrings():
            errmsg = '%s is already in ROI list - use a unique name.' % roiname
        elif self.roilist_sel is None:
            errmsg = 'No ROI selected to rename.'
        if errmsg is not None:
            return Popup(self, errmsg, 'Cannot Rename ROI')

        if self.roilist_sel < len(self.det.mcas[0].rois):
            self.det.rename_roi(self.roilist_sel, roiname)
            names = self.wids['roilist'].GetStrings()
            names[self.roilist_sel] = roiname
            self.wids['roilist'].Clear()
            for sname in names:
                self.wids['roilist'].Append(sname)
            self.wids['roilist'].SetSelection(self.roilist_sel)

    def onCalibrateEnergy(self, event=None, **kws):
        try:
            self.win_calib.Raise()
        except:
            self.win_calib = XRFCalibrationFrame(self, mca=self.mca,
                                              larch=self.larch,
                                              callback=self.onSetCalib)

    def onSetCalib(self, offset, slope, mca=None):
        print('XRFControl Set Energy Calibratione' , offset, slope, mca)

    def onClose(self, event=None):
        self.onStop()
        XRFDisplayFrame.onClose(self)

    def onExit(self, event=None):
        self.onStop()
        XRFDisplayFrame.onExit(self)

class EpicsXRFApp(LarchWxApp):
    def __init__(self, _larch=None, prefix=None,
                 det_type='ME-4', ioc_type='Xspress3', nmca=4,
                 size=(725, 580), environ_file=None,
                 title='Epics XRF Display', output_title='XRF', **kws):
        self.prefix = prefix
        self.det_type = det_type
        self.ioc_type = ioc_type
        self.nmca = nmca
        self.size = size
        self.environ_file = environ_file
        self.title = title
        self.output_title = output_title
        LarchWxApp.__init__(self, _larch=_larch, **kws)

    def createApp(self):
        frame = EpicsXRFDisplayFrame(prefix=self.prefix,
                                     det_type=self.det_type,
                                     ioc_type=self.ioc_type,
                                     nmca=self.nmca, size=self.size,
                                     environ_file=self.environ_file,
                                     title=self.title,
                                     output_title=self.output_title,
                                     _larch=self._larch)
        frame.Show()
        self.SetTopWindow(frame)
        return True

if __name__ == "__main__":
    EpicsXRFApp().MainLoop()
