#!/usr/bin/env python 
# encoding: utf-8

"""
A set of utilities to use spike in NMR or FTMS within jupyter


First version MAD june 2017
definitive ? version MAD october 2019

"""

from __future__ import print_function, division
import unittest
import sys
import os
import os.path as op
import glob

import matplotlib.pylab as plt
import matplotlib.gridspec as gridspec
from matplotlib.widgets import MultiCursor
from ipywidgets import fixed, Layout, HBox, VBox, Label, Output, Button, Tab
import ipywidgets as widgets
from IPython.display import display, HTML, Javascript
import numpy as np

from spike.File.BrukerNMR import Import_1D
from spike import NPKData
from spike.NMR import NMRData
try:
    import spike.plugins.bcorr as bcorr
except:
    print('Baseline correction plugin not installed !')

try:
    from spike.plugins.PhaseMS import movepivot
    PHASEQUAD = True
except:
    print('Quadratic phase correction plugin not installed !')
    PHASEQUAD = False

# REACTIVE modify callback behaviour
# True is good for inline mode / False is better for notebook mode
REACTIVE = True
HEAVY = False

def hidecode(initial='show', message=True):
    """
    this func adds a button to hide/show the code on a jupyter page
    initial is either 'show' or 'hide'
    inspired from: https://stackoverflow.com/questions/27934885/how-to-hide-code-from-cells-in-ipython-notebook-visualized-with-nbviewer/28073228#28073228
    """
    from IPython.display import display, HTML, Markdown
    if initial == 'show':
        init = 'false'
    elif initial == 'hide':
        init = 'true'
    if message:
        msg = "<i>usefull to show/print a clean screen when processing is finished</i>"
    else:
        msg = ""
    display(HTML('''
<style>hr {height: 2px; border: 0;border-top: 1px solid #ccc;margin: 1em 0;padding: 0; }</style>
<script>
code_show=%s; 
function code_toggle()
    { if (code_show)
        { $('div.input').hide(); $('#but').val("show python code");
        } else { $('div.input').show(); $('#but').val("hide python code");
    }
    code_show = !code_show } 
$(document).ready(code_toggle);
</script>
<form action="javascript:code_toggle()">
<input id="but" type="submit" style="border:1px solid black; background-color:#DDD">
%s
</form>'''%(init, msg)))
def jsalert(msg):
    "send a javascript alert"
    display(Javascript("alert('%s')"%msg))

class FileChooser:
    """a simple file chooser for Jupyter - obsolete - not used"""
    def __init__(self, base=None, filetype=['fid','ser'], mode='r', show=True):
        if base is None:
            self.curdir = "/"
        else:
            self.curdir = base
        self.filetype = filetype
        self.mode = mode
        self.wfile = widgets.Text(layout=Layout(width='70%'),description='File to load')
        self.ldir = widgets.Label(value="Chosen dir:  "+self.curdir)
        self.wdir = widgets.Select(
            options=self.dirlist(),
            description='Choose Dir',
            layout=Layout(width='70%'))
        if mode=='r':
            self.wchooser = widgets.Select(
                options=self.filelist(),
                description='Choose File',
                layout=Layout(width='70%'))
            self.wchooser.observe(self.wob)
            self.wfile.disabled=True   # not mofiable in read mode
        elif mode=="w":
            self.wfile.description = 'File to create'
            self.wfile.disabled=False
            self.wchooser = widgets.Select(
                options=self.filelist(),
                description='Files',
                layout=Layout(width='70%'))
        else:
            raise Exception('Only "r" and  "w" modes supported')
        self.wsetdir = Button(description='❯',layout=Layout(width='20%'),
                button_style='success', # 'success', 'info', 'warning', 'danger' or ''
                tooltip='descend in directory')
        self.wup = Button(description='❮',layout=Layout(width='20%'),
                button_style='success', # 'success', 'info', 'warning', 'danger' or ''
                tooltip='up to previous directory')
        self.wsetdir.on_click(self.setdir)
        self.wup.on_click(self.updir)
        if show: self.show()
    def filelist(self):
        fl = []
        if self.mode == 'r':
            if type(self.filetype) is str:
                fl = glob.glob(op.join(self.curdir,self.filetype))
            elif type(self.filetype) in (tuple, list):
                for f in self.filetype:
                    fl += glob.glob(op.join(self.curdir,f))
            else:
                raise Exception('TypeError, filetype should be either a string or a list')
        else:   # 'w'
            fl = [f for f in glob.glob(op.join(self.curdir,'*')) if op.isfile(f)]
            self.wfile.value = op.join(self.curdir,self.filetype)
        if fl == []:
            fl = [" "]
        return fl
    def dirlist(self):
        base = self.curdir
        return [d for d in glob.glob(op.join(base,'*')) if (op.isdir(d) and not d.endswith('__pycache__'))]
    def wob(self, e):
        self.wfile.value = self.wchooser.value
    def updir(self, e):
        self.curdir = op.dirname(self.curdir)
        self.ldir.value = "Chosen dir:  "+self.curdir
        self.wdir.options = self.dirlist()
        self.wchooser.options = self.filelist()
    def setdir(self, e):
        self.curdir = self.wdir.value
        self.ldir.value = "Chosen dir:  "+self.curdir
        self.wdir.options = self.dirlist()
        self.wchooser.options = self.filelist()
    def show(self):
        display(VBox(
            [   self.ldir,
            HBox( [self.wdir, VBox([self.wup,self.wsetdir])] ),    
                self.wchooser,
                self.wfile
            ])
        )
        return self
    @property
    def file(self):
        "the chosen complete filename"
        return self.wfile.value
    @property
    def dirname(self):
        "the final dirname containing the chosen file"
        if op.isdir(self.wfile.value):
            return op.basename(self.wfile.value)
        else:
            return op.basename(op.dirname(self.wfile.value))
    @property
    def nmrname(self):
        "the final dirname containing the chosen file for TopSpin files"
        if op.isdir(self.wfile.value):
            return op.join(
                op.basename(op.dirname(self.wfile.value)), self.dirname)
        else:
            return op.join(
                op.basename(op.dirname(op.dirname(self.wfile.value))), self.dirname)
    @property
    def basename(self):
        "the basename of the chosen file"
        return op.basename(self.wfile.value)

class Show1D(HBox):
    """
    An interactive display, 1D NMR
        Show1D(spectrum)
    to be developped for peaks and integrals
    """
    def __init__(self, data, title=None, figsize=None, reverse_scroll=False, show=True):
        """
        data : to be displayed
        title : text for window
        figsize : size in inches (x,y)
        reverse_scroll : if True, reverses direction of mouse scroll
        """
        super().__init__()
        self.data = data
        self.title = title
        if figsize is None:
            figsize = (10,5)
        if reverse_scroll:
            self.reverse_scroll = -1
        else:
            self.reverse_scroll = 1
        self.blay = Layout(width='80px')  # default layout for buttons
        self.done = Button(description="Done", button_style='success',layout=self.blay)
        self.done.on_click(self.on_done)
        self.reset = Button(description="Reset", button_style='success',layout=self.blay,
            tooltip="Reset to full spectrum")
        self.reset.on_click(self.on_reset)
        self.scale = widgets.FloatSlider(description='scale:', value=1.0, min=1.0, max=200, step=0.1,
                            layout=Layout(width='80px', height=str(0.8*2.54*figsize[1])+'cm'), continuous_update=REACTIVE,
                            orientation='vertical')
        for widg in (self.scale,):
            widg.observe(self.ob)
        plt.ioff()
        fi,ax = plt.subplots(figsize=figsize)
        self.ax = ax
        self.fig = fi
        self.xb = self.ax.get_xbound()
        self.blank = widgets.HTML("&nbsp;",layout=self.blay)
        self.children = [  VBox([self.blank, self.reset, self.scale, self.done]), self.fig.canvas ]
        self.set_on_redraw()
        plt.ion()
        if show:
            display( self )
            self.data.display(figure=self.ax, title=self.title)
    def on_done(self, b):
        self.close()
        display(self.fig)   # shows spectrum
    def on_reset(self, b):
        self.scale.value = 1.0
        self.ax.set_xbound( (self.data.axis1.itop(0),self.data.axis1.itop(self.data.size1)) )
    def ob(self, event):
        "observe events and display"
        if event['name']=='value':
            self.disp()
    def scale_up(self, step):
        self.scale.value *= 1.1892**(self.reverse_scroll*step) # 1.1892 is 4th root of 2.0
    def set_on_redraw(self):
        def on_scroll(event):
            self.scale_up(event.step)
        cids = self.fig.canvas.mpl_connect('scroll_event', on_scroll)
    def disp(self):
        self.xb = self.ax.get_xbound()
        self.ax.clear()
        self.data.display(scale=self.scale.value, new_fig=False, figure=self.ax, title=self.title)
        self.ax.set_xbound(self.xb)
        #self.set_on_redraw()
class baseline1D(Show1D):
    def __init__(self, data, figsize=None, reverse_scroll=False, show=True):
        super().__init__( data, figsize=figsize, reverse_scroll=reverse_scroll, show=False)
        self.data.real()
        ppos = self.data.axis1.itop(self.data.axis1.size/2)
        #self.ax.plot([ppos,ppos], self.ax.get_ybound())
        self.select = widgets.FloatSlider(description='select:',
                        min=0.0, max=self.data.size1,
                        layout=Layout(width='100%'),
                        value=0.5*self.data.size1, readout=False, continuous_update=REACTIVE)
        self.smooth = widgets.IntSlider(description='smooth:', min=0, max=20,  layout=Layout(width='70%'),
                                        tooltip='apply a local smoothing for pivot points',
                                         value=1, readout=True, continuous_update=REACTIVE)
        self.bsl_points = []
        for w in [self.select, self.smooth]:
            w.observe(self.ob)
        bsize = '15%'
        self.cancel = widgets.Button(description="Cancel", button_style='warning', layout=Layout(width='10%'))
        self.cancel.on_click(self.on_cancel)
        self.auto = widgets.Button(description="Auto", button_style='success', layout=Layout(width=bsize),
            tooltip='Automatic set of points')
        self.auto.on_click(self.on_auto)
        self.set = widgets.Button(description="Add", button_style='success', layout=Layout(width=bsize),
            tooltip='Add a baseline point at the selector position')
        self.set.on_click(self.on_set)
        self.unset = widgets.Button(description="Rem", button_style='warning', layout=Layout(width=bsize),
            tooltip='Remove the baseline point closest to the selector')
        self.unset.on_click(self.on_unset)
        self.toshow = widgets.Dropdown( options=['baseline', 'corrected', 'points'],  description='Display:')
        self.toshow.observe(self.ob)
        self.Box = VBox([
            HBox([self.done, self.cancel, self.toshow,
                widgets.HTML('Choose baseline points')]),
            HBox([self.select,self.set, self.unset, self.auto, self.smooth]),
            self])
        def on_press(event):
            v = self.data.axis1.ptoi(event.xdata)
            self.select.value = abs(v)
        cids = self.fig.canvas.mpl_connect('button_press_event', on_press)

        if show: self.show()
    def show(self):
        "create the widget and display the spectrum"
        display(self.Box)
        self.data.display(figure=self.ax)
        self.xb = self.ax.get_xbound()  # initialize zoom
        ppos = self.data.axis1.itop(self.select.value)
        self.ax.plot([ppos,ppos], self.ax.get_ybound())
    def close(self):
        "close all widget"
        for w in [ self.select, self.auto, self.set, self.unset, self.cancel, self.toshow, self.smooth, self.Box]:
            w.close()
        super().close()
    def on_done(self, e):
        if self.bsl_points == []:
            jsalert('Please define control points before applying baseline correction')
            return
        self.close()
        print('Applied correction:\n', sorted(self.bsl_points))
        self.data.set_buffer( self.data.get_buffer() - self.correction() )
        super().disp()
        display(self.fig)   # shows spectrum
    def on_auto(self, e):
        "automatically set baseline points"
        self.bsl_points = [self.data.axis1.itop(x) for x in bcorr.autopoints(self.data)]
        self.disp()
    def on_cancel(self, e):
        self.close()
        super().disp()
        display(self.fig)   # shows spectrum
    def on_set(self, e):
        "add baseline points at selector"
        self.bsl_points.append( self.selpos() )
        self.disp()
    def on_unset(self, e):
        "remove baseline points closest from selector"
        here = self.selpos()
        distclose = np.inf
        pclose = np.NaN
        for i,p in enumerate(self.bsl_points):
            if abs(p-here)< distclose:
                pclose = p
                distclose = abs(p-here)
        self.bsl_points.remove(pclose)
        self.disp()
    def selpos(self):
        "returns selector pos in ppm"
        return self.data.axis1.itop(self.select.value)
    def smoothed(self):
        "returns a smoothed version of the data"
        from scipy.signal import fftconvolve
        buf = self.data.get_buffer()
        mask = np.array([1,1,1,1,1])
        return fftconvolve(buf, mask, mode='same')
    def correction(self):
        "returns the correction to apply as a numpy array"
        ibsl_points = self.data.axis1.ptoi( np.array(self.bsl_points) ).astype(int)
        x = np.arange(self.data.size1)
        yy = self.data.get_buffer()
        if len(self.bsl_points) == 0 :
            return 0.0
        elif len(self.bsl_points) == 1 :
            value = self.data[ibsl_points[0]] * np.ones( self.data.size1 )
        elif len(self.bsl_points) < 4 :
            corr = bcorr._linear_interpolate(yy, ibsl_points, nsmooth=self.smooth.value)
            value = corr(x)
        else:
            corr = bcorr._spline_interpolate(yy, ibsl_points, nsmooth=self.smooth.value)
            value = corr(x)
        return value
    def corrected(self):
        value = self.data.copy()
        value.set_buffer( value.get_buffer() - self.correction() )
        return value
    def disp(self):
        "compute and display the spectrum"
        self.xb = self.ax.get_xbound()
        # box
        super().disp()
        # data
        if len(self.bsl_points)>0:
            if self.toshow.value == 'baseline':
                ( self.data.copy()-self.corrected() ).display(new_fig=False, figure=self.ax, color='r')
            elif self.toshow.value == 'corrected':
                self.ax.clear()
                self.corrected().display(new_fig=False, figure=self.ax, color='r', scale=self.scale.value)
        # selector
        ppos = self.selpos()
        self.ax.plot([ppos,ppos], self.ax.get_ybound())
        # pivots
        y = bcorr.get_ypoints(  self.data.get_buffer(), 
                                self.data.axis1.ptoi( np.array(self.bsl_points)),
                                nsmooth=self.smooth.value )
        self.ax.scatter(self.bsl_points, y, c='r', marker='o')
        # set zoom
        self.ax.set_xbound(self.xb)

Colors = ('black','red','blue','green','orange',
'blueviolet','crimson','turquoise','indigo',
'magenta','gold','pink','purple','salmon','darkblue','sienna')

class SpforSuper(object):
    "a holder for SuperImpose"
    def __init__(self, i, name):
        j = i%len(Colors)
        self.name = widgets.Text(value=name, layout=Layout(width='40em'))
        self.color = widgets.Dropdown(options=Colors,value=Colors[j],layout=Layout(width='80px'))
        self.direct = widgets.Dropdown(options=['up','down','off'],value='off', layout=Layout(width='70px'))
        self.offset = widgets.FloatText(value=0.0, layout=Layout(width='70px'), tooltip="offset")
        self.me = HBox([widgets.HTML(value="<b>%d</b>"%i),self.name, self.offset, self.color,self.direct])
        self.fig = False
    def display(self, unit='ppm'):
        if self.name != 'None' and self.direct.value != 'off':
            scale = 1
            if self.direct.value == 'up':
                mult = 1
            elif self.direct.value == 'down':
                mult = -1
            else:
                return
            NMRData(name=self.name.value).set_unit(unit).mult(mult).display(
                new_fig=self.fig,
                scale=scale,
                color=self.color.value,
                label=op.basename(op.dirname(self.name.value)))
class _Show1Dplus(Show1D):
    def __init__(self, data, figsize=None, title=None, reverse_scroll=False):
        super().__init__( data, figsize=figsize, title=title, reverse_scroll=reverse_scroll)
        self.scaleint = widgets.FloatSlider(value=0.5, min=0.1, max=10, step=0.05,
                            layout=Layout(width='20%'), continuous_update=REACTIVE)
        self.offset = widgets.FloatSlider(value=0.3, min=0.0, max=1.0, step=0.01,
                            layout=Layout(width='20%'), continuous_update=REACTIVE)
        self.peaks = widgets.Checkbox(value=False, layout=Layout(width='15%'))
        self.integ = widgets.Checkbox(value=False, layout=Layout(width='15%'))

        for widg in (self.scaleint, self.offset, self.peaks, self.integ):
            widg.observe(self.ob)
        self.fullBox = VBox([  HBox([Label('Integral scale:'),self.scaleint,Label('offset:'),self.offset]),
                        HBox([Label('Show Peaks'),self.peaks,Label('integrals'),self.integ])
                    ])
        display(self.fullBox)
    def on_done(self, e):
        self.fullBox.close()
        super().close()
        self.disp(zoom=True)
        display(self.fig)
    def disp(self, zoom=False):
        "refresh display - if zoom is True, display only in xbound"
        self.xb = self.ax.get_xbound()
        # self.yb = self.ax.get_ybound()
        if zoom:
            zoom = self.xb
        else:
            zoom = None
        self.ax.clear()
        self.data.display(scale=self.scale.value, new_fig=False, figure=self.ax, title=self.title, zoom=zoom)
        if self.integ.value:
            try:
                self.data.display_integral(label=True, integscale=self.scaleint.value,
                    integoff=self.offset.value, figure=self.ax, zoom=zoom)
            except:
                print('no or wrong integrals')
                pass
        if self.peaks.value:
            try:
                self.data.display_peaks(peak_label=True, figure=self.ax, scale=self.scale.value, zoom=zoom)
            except:
                print('no or wrong peaklist')
                pass
        self.ax.set_xbound(self.xb)

class Show1Dplus(Show1D):
    def __init__(self, data, base='/DATA', N=9, figsize=None, title=None, reverse_scroll=False):
        from spike.Interactive.ipyfilechooser import FileChooser
        super().__init__( data, figsize=figsize, title=title, reverse_scroll=reverse_scroll)
        self.scaleint = widgets.FloatSlider(value=0.5, min=0.1, max=10, step=0.05,
                            layout=Layout(width='20%'), continuous_update=REACTIVE)
        self.offset = widgets.FloatSlider(value=0.3, min=0.0, max=1.0, step=0.01,
                            layout=Layout(width='20%'), continuous_update=REACTIVE)
        self.peaks = widgets.Checkbox(value=False, layout=Layout(width='15%'))
        self.integ = widgets.Checkbox(value=False, layout=Layout(width='15%'))

        self.Chooser = FileChooser(base=base, filetype="*.gs1", mode='r', show=False)
        self.bsel = widgets.Button(description='Copy',layout=self.blay,
                button_style='info', # 'success', 'info', 'warning', 'danger' or ''
                tooltip='copy selected data-set to entry below')
        self.to = widgets.IntText(value=1,min=1,max=N,layout=Layout(width='10%'))
        self.bsel.on_click(self.copy)
        self.DataList = [SpforSuper(i+1,'None') for i in range(N)]
        self.DataList[0].color.value = 'red'
        self.DataList[0].fig = True  # switches on the very first one

        for widg in (self.scaleint, self.offset, self.peaks, self.integ):
            widg.observe(self.ob)

        self.tabs = Tab()
        self.tabs.children = [
            VBox([  HBox([Label('Integral scale:'),self.scaleint,Label('offset:'),self.offset]),
                    HBox([Label('Show Peaks'),self.peaks,Label('integrals'),self.integ]),
                    HBox([VBox([self.blank, self.reset, self.scale]), self.fig.canvas])
                    ]),
            VBox([  Label("Choose spectra to superimpose"),
                    HBox([self.Chooser, self.bsel, self.to])]+
                [sp.me for sp in self.DataList]
                )
            ]
        self.tabs.set_title(0, 'Spectrum')
        self.tabs.set_title(1, 'Data-List')

        self.children = [self.tabs] 

    def copy(self, event):
        if self.to.value <1 or self.to.value >len(self.DataList):
            print('Destination is out of range !')
        else:
            self.DataList[self.to.value-1].name.value = self.Chooser.selected
            self.DataList[self.to.value-1].direct.value = 'up'
        self.to.value = min(self.to.value, len(self.DataList)) +1
    def on_done(self, e):
        self.close()
        self.disp(zoom=True)
        display(self.fig)
    def disp(self, zoom=False):
        "refresh display - if zoom is True, display only in xbound"
        self.xb = self.ax.get_xbound()
        # self.yb = self.ax.get_ybound()
        if zoom:
            zoom = self.xb
        else:
            zoom = None
        self.ax.clear()
        self.data.display(scale=self.scale.value, new_fig=False, figure=self.ax, title=self.title, zoom=zoom)
        if self.integ.value:
            try:
                self.data.display_integral(label=True, integscale=self.scaleint.value,
                    integoff=self.offset.value, figure=self.ax, zoom=zoom)
            except:
                print('no or wrong integrals')
                pass
        if self.peaks.value:
            try:
                self.data.display_peaks(peak_label=True, figure=self.ax, scale=self.scale.value, zoom=zoom)
            except:
                print('no or wrong peaklist')
                pass
        self.ax.set_xbound(self.xb)


class baseline2D_F2(baseline1D):
    def __init__(self, data, figsize=None):
        print('WARNING this tool is not functional/tested yet')
        self.data2D = data
        super().__init__( self.data2D.projF2, figsize=figsize)
    def on_done(self, e):
        super().on_done(e)
        ibsl_points = [int(self.data2D.axis2.ptoi(x)) for x in self.bsl_points]
        self.data2D.bcorr(method='spline', xpoints=ibsl_points)

class Show2D(object):
    """
    A display for 2D NMR with a scale cursor
    Show2D(spectrum) where spectrum is a NPKData object
    - special display for DOSY.
    """
    def __init__(self, data, title=None, figsize=None):
        self.data = data
        self.isDOSY =  isinstance(data.axis1, NPKData.LaplaceAxis)
        try:
            self.proj2 = data.projF2
        except:
            self.proj2 = data.proj(axis=2).real()
        try:
            self.proj1 = data.projF1
        except:
            self.proj1 = data.proj(axis=1).real()
        self.title = title
        self.scale = widgets.FloatLogSlider(description='scale:', value=1.0, min=-1, max=3,  base=10, step=0.01,
                            layout=Layout(width='80%'), continuous_update=HEAVY)
        self.posview = widgets.Checkbox(value=True,description='Positive', tooltip='Display Positive levels')
        self.negview = widgets.Checkbox(value=False,description='Negative', tooltip='Display Negative levels')
        self.cursors = widgets.Checkbox(value=False,description='Cursors', tooltip='show cursors (cpu intensive !)')
        for w in (self.scale, self.posview, self.negview, self.cursors):
            w.observe(self.ob)
        grid = {'height_ratios':[1,4],'hspace':0,'wspace':0}
        if self.isDOSY:
            fsize = (10,5)
            grid['width_ratios']=[7,1]
        else:
            fsize = (8,8)
            grid['width_ratios']=[4,1]
#        fig, self.axarr = plt.subplots(2, 1, sharex=True, figsize=fsize, gridspec_kw=grid)
        self.fig = plt.figure(figsize=fsize, constrained_layout=False)
        spec2 = gridspec.GridSpec(ncols=2, nrows=2, figure=self.fig, **grid)
        axarr = np.empty((2,2), dtype=object)
        axarr[0,0] = self.fig.add_subplot(spec2[0, 0])
        axarr[1,0] = self.fig.add_subplot(spec2[1, 0],sharex=axarr[0, 0])
        axarr[1,1] = self.fig.add_subplot(spec2[1, 1],sharey=axarr[1, 0])
        self.top_ax = axarr[0,0]
        self.spec_ax = axarr[1,0]
        self.side_ax = axarr[1,1]
        self.multitop = None
        self.multiside = None
        self.Box = HBox( [self.scale, self.posview, self.negview, self.cursors])
        display( self.Box )
        self.disp(new=True)
    def on_done(self, b):
        self.scale.close()
    def ob(self, event):
        "observe events and display"
        if event['name'] != 'value':
            return
        self.disp()
    def disp(self,new=False):
        if new:
            self.proj2.display(figure=self.top_ax, title=self.title)
            xb = self.top_ax.get_xbound()
            dataxis = self.proj1.axis1.itoc( self.proj1.axis1.points_axis() )
            self.side_ax.plot(self.proj1.get_buffer(),dataxis)
            yb = self.side_ax.get_ybound()
        else:
            yb = self.side_ax.get_ybound()
            xb = self.top_ax.get_xbound()
            self.spec_ax.clear()
        if self.cursors.value:
            self.multitop = MultiCursor(self.fig.canvas, (self.spec_ax, self.top_ax), color='r', lw=1, horizOn=False, vertOn=True)
            self.multiside = MultiCursor(self.fig.canvas, (self.spec_ax, self.side_ax), color='r', lw=1, horizOn=True, vertOn=False)
        else:
            self.multitop = None
            self.multiside = None
        if self.posview.value:
            self.data.display(scale=self.scale.value, new_fig=False, figure=self.spec_ax)
        if self.negview.value:
            self.data.display(scale=-self.scale.value, new_fig=False,
                figure=self.spec_ax, mpldic={'cmap':'Wistia'})
        self.spec_ax.set_xbound(xb)
        self.spec_ax.set_ybound(yb)

class Phaser1D(Show1D):
    """
    An interactive phaser in 1D MS

        Phaser1D(spectrum)

    requires %matplotlib widget

    """
    def __init__(self, data, figsize=None, title=None, reverse_scroll=False, show=True):
        data.check1D()
        self.list = []
        if data.itype == 0:
            jsalert('Data is Real - Please redo Fourier Transform')
            return
        super().__init__( data, figsize=figsize, title=title, reverse_scroll=reverse_scroll, show=False)
        self.p0 = widgets.FloatSlider(description='P0:',min=-200, max=200, step=1,
                            layout=Layout(width='100%'), continuous_update=REACTIVE)
        self.p1 = widgets.FloatSlider(description='P1:',min=-10000000, max=10000000, step=1000.0,
                            layout=Layout(width='100%'), continuous_update=REACTIVE)
        self.p2 = widgets.FloatSlider(description='P2:',min=-100000000, max=100000000, step=1000.0,
                            layout=Layout(width='100%'), continuous_update=REACTIVE)
        if self.data.axis1.currentunit == 'm/z':
            pvmin = self.data.axis1.lowmass
            pvmax = self.data.axis1.highmass
        else:
            pvmin = 0
            pvmax = self.data.axis1.itoc(self.data.size1)
        self.pivot = widgets.FloatSlider(description='pivot:',
                        min=pvmin, max=pvmax, step=1, layout=Layout(width='80%'),
                        value=self.data.axis1.itoc(0.5*self.data.size1), continuous_update=REACTIVE)
        self.cancel = widgets.Button(description="Cancel", button_style='warning')
        self.cancel.on_click(self.on_cancel)
        # remove done button and create an Apply one
        self.done.close()
        self.apply = widgets.Button(description="Done", button_style='success')
        self.apply.on_click(self.on_Apply)
        self.addlist = widgets.Button(description="Add P1", button_style='success')
        self.addlist.on_click(self.on_addlist)
        # draw HBox
        if PHASEQUAD:
            phbutton = [self.p0, self.p1, self.p2]
        else:
            phbutton = [self.p0, self.p1]
        self.children = [VBox([
                            HBox([self.apply, self.cancel, self.pivot, widgets.HTML('<i>set by clicking on spectrum</i>')]),
                            *phbutton,
                            HBox([VBox([self.blank, self.reset, self.scale]), self.fig.canvas]) ])]
        # add interaction
        for w in [self.p0, self.p1, self.p2, self.scale]:
            w.observe(self.ob)
        self.pivot.observe(self.on_movepivot)
        # add click event on spectral window
        def on_press(event):
            self.pivot.value = event.xdata
        cids = self.fig.canvas.mpl_connect('button_press_event', on_press)
        self.lp0, self.lp1, self.lp2, self.lpv = self.ppivot()
        if show: self.show()
    def on_addlist(self):
        import pprint
        self.list.append((self.pivot.value, self.p1.value))
        pprint.pprint(self.list)
    def show(self):
        self.data.display(figure=self.ax)
        self.xb = self.ax.get_xbound()  # initialize zoom
        ppos = self.pivot.value
        self.ax.plot([ppos,ppos], self.ax.get_ybound())
        display(self)
    def on_cancel(self, b):
        # self.p0.value = 0  # because widget remains active...
        # self.p1.value = 0
        self.close()
        print("no applied phase")
    def on_Apply(self, b):
        self.close()
        lp0, lp1, lp2, lpv = self.ppivot() # get centered values
        if PHASEQUAD:
            self.data.phase(lp0, lp1, lp2, lpv)
        else:
            self.data.phase(lp0, lp1)
        self.disp()
        self.on_done(b)
        print("Applied: phase(%.1f,  %.1f, %.1f, %.2f)"%(lp0, lp1, lp2, lpv))

    def ppivot(self):
        "converts current pivot values to centered ones - not used in PHASEQUAD"
        if PHASEQUAD:
                return self.p0.value, self.p1.value, self.p2.value, self.pivot.value
        else:
                pp = 1.0-(self.pivot.value/self.data.size1)
                return (self.p0.value + (pp-0.5)*self.p1.value, self.p1.value, self.p2.value, self.pivot.value)
    def ctopivot(self, p0, p1, p2, pv):
        "convert from centered to pivot values - not used in PHASEQUAD"
        if PHASEQUAD:
                return p0, p1, p2, pv
        else:
                pp = 1.0-(self.pivot.value/self.data.size1)
                return p0- (pp-0.5)*p1, p1, 0, 0
    def on_movepivot(self, event):
        if event['name']=='value':
            if PHASEQUAD:
                pvaft = self.data.axis1.ctoi(self.pivot.value)/self.data.size1 
                pvbef = self.data.axis1.ctoi(self.lpv)/self.data.size1
                self.p0.value, self.p1.value, self.p2.value, _ = movepivot(self.lp0, self.lp1, self.lp2, pvbef, pvaft)
            else:
                self.p0.value, self.p1.value = self.ctopivot(self.lp0, self.lp1)
            self.phase()
    def ob(self, event):
        "observe changes and start phasing"
        if event['name']=='value':
            self.phase()
    def phase(self):
        "apply phase and display"
        self.xb = self.ax.get_xbound()   # get current zoom
        self.ax.clear()
        self.lp0, self.lp1, self.lp2, self.lpv = self.ppivot()         # get centered values
        if PHASEQUAD:
            pv = self.data.axis1.ctoi(self.lpv)/self.data.size1  #  0...1
            self.data.copy().phase(self.lp0, self.lp1, self.lp2, pv).display(scale=self.scale.value, new_fig=False, figure=self.ax)
        else:
            self.data.copy().phase(self.lp0, self.lp1).display(scale=self.scale.value, new_fig=False, figure=self.ax)
        ppos = self.pivot.value
        self.ax.plot([ppos,ppos], self.ax.get_ybound())
        self.ax.set_xbound( self.xb )

class Phaser2D(object):
    """
    An interactive phaser in 2D NMR

        Phaser2D(spec)

    best when in %matplotlib inline

    """
    def __init__(self, data):
        print('WARNING this tool is not functional/tested yet')
        self.data = data
        self.scale = widgets.FloatLogSlider(description='scale:', value=1.0, min=-1, max=2,  base=10, step=0.01,
                            layout=Layout(width='80%'), continuous_update=HEAVY)
        self.F1p0 = widgets.FloatSlider(min=-180, max=180, step=0.1, description='F1 p0',continuous_update=HEAVY)
        self.F1p1 = widgets.FloatSlider(min=-250, max=250, step=1.0, description='F1 p1',continuous_update=HEAVY)
        self.F2p0 = widgets.FloatSlider(min=-180, max=180, step=0.1, description='F2 p0',continuous_update=HEAVY)
        self.F2p1 = widgets.FloatSlider(min=-250, max=250, step=1.0, description='F2 p1',continuous_update=HEAVY)
        for w in [self.F1p0, self.F1p1, self.F2p0, self.F2p1, self.scale]:
            w.observe(self.ob)
        self.button = widgets.Button(description="Apply correction",button_style='success')
        self.button.on_click(self.on_Apply)
        self.cancel = widgets.Button(description="Cancel",button_style='warning')
        self.cancel.on_click(self.on_cancel)
#       interact(self.phase, scale=self.scale, F1p0=self.F1p0, F1p1=self.F1p1, F2p0=self.F2p0, F2p1=self.F2p1)
        display(VBox([self.scale,
            HBox([VBox([self.F1p0, self.F1p1], layout=Layout(width='40%')), VBox([self.F2p0, self.F2p1],  layout=Layout(width='40%'))], layout=Layout(width='80%'))
            ], layout=Layout(width='100%')))
        display(HBox([self.button, self.cancel]))
        fi,ax = plt.subplots()
        self.ax = ax
        self.display()
        #self.data.display(figure=self.ax)
    def ob(self, event):
        "observe changes and start phasing"
        if event['name']=='value':
            self.phase()
    def close(self):
        for w in [self.F1p0, self.F1p1, self.F2p0, self.F2p1, self.scale, self.button, self.cancel]:
            w.close()
    def on_cancel(self, b):
        print("No action")
        self.ax.clear()
        self.data.display(figure=self.ax,scale=self.scale.value)
        self.ax.set_xlim(xmin=self.data.axis2.itop(0), xmax=self.data.axis2.itop(self.data.size2))
        self.ax.set_ylim(ymin=self.data.axis1.itop(0), ymax=self.data.axis1.itop(self.data.size1))
        self.close()
    def on_Apply(self, b):
        print("Applied: phase(%.1f,%.1f,axis='F1').phase(%.1f,%.1f,axis='F')"%(self.F1p0.value, self.F1p1.value, self.F2p0.value, self.F2p1.value))
        self.data.phase(self.F2p0.value, self.F2p1.value, axis='F2').phase(self.F1p0.value, self.F1p1.value, axis='F1')
        self.data.display(figure=self.ax,scale=self.scale.value)
        self.ax.set_xlim(xmin=self.data.axis2.itop(0), xmax=self.data.axis2.itop(self.data.size2))
        self.ax.set_ylim(ymin=self.data.axis1.itop(0), ymax=self.data.axis1.itop(self.data.size1))
        self.close()
    def display(self,todisplay=None):
        "display either the current data or the one provided - red and blue"
        self.ax.clear()
        if not todisplay:
            todisplay = self.data
        todisplay.display(scale=self.scale.value, new_fig=False, figure=self.ax,color='blue')
        todisplay.display(scale=-self.scale.value, new_fig=False, figure=self.ax, color='red')
        self.ax.set_xlim(xmin=self.data.axis2.itop(0), xmax=self.data.axis2.itop(self.data.size2))
        self.ax.set_ylim(ymin=self.data.axis1.itop(0), ymax=self.data.axis1.itop(self.data.size1))
    def phase(self):
        "compute phase and display"
        dp = self.data.copy().phase(self.F2p0.value, self.F2p1.value, axis='F2').phase(self.F1p0.value, self.F1p1.value, axis='F1')
        self.display(dp)
    # def phase(self, scale, F1p0, F1p1, F2p0, F2p1):
    #     self.data.copy().phase(F1p0,F1p1,axis='F1').phase(F2p0,F2p1,axis='F2').display(scale=scale);

class AvProc1D:
    "Detailed 1D NMR Processing"
    def __init__(self, filename=""):
        print('WARNING this tool is not functional/tested yet')
        self.wfile = widgets.Text(description='File to process',layout=Layout(width='80%'), value=filename)
        self.wapod = widgets.Dropdown(
            options=['None', 'apod_sin (sine bell)', 'apod_em (Exponential)', 'apod_gm (Gaussian)', 'gaussenh (Gaussian Enhacement)', 'kaiser'],
            value='apod_sin (sine bell)',
            description='Apodisation')
        self.wpapod_Hz = widgets.FloatText(
            value=1.0,
            min=0, # max exponent of base
            max=30, # min exponent of base
            description='Width in Hz',
            layout=Layout(width='15%'),
            disabled = True)
        self.wpapod_enh = widgets.FloatText(
            value=2.0,
            min=0.0, # max exponent of base
            max=5.0, # min exponent of base
            description='strength',
            layout=Layout(width='15%'),
            step=1,
            disabled = True)
        self.wpapod_sin = widgets.FloatText(
            value=0.0,
            min=0, # max exponent of base
            max=0.5, # min exponent of base
            description='bell shape',
            layout=Layout(width='15%'),
            step=0.01,
            tooltip='value is the maximum of the bell, 0 is pure cosine, 0.5 is pure sine',
            disabled = False)
        self.wzf = widgets.Dropdown(
            options=[0, 1, 2, 4, 8],
            value=1,
            description='Zero-Filling')
        self.wphase0 = widgets.FloatText(
            value=0, description='Phase : P0', layout=Layout(width='20%'), disabled = True)
        self.wphase1 = widgets.FloatText(
            value=0, description='P1', layout=Layout(width='20%'), disabled = True)
        self.wapmin = widgets.Checkbox(
            value=True, description='AutoPhasing', tooltip='Perform AutoPhasing')
        self.wapmin.observe(self.apmin_select)
        self.wbcorr = widgets.Checkbox(
            value=False, description='Baseline Correction', tooltip='Perform AutoPhasing')
        self.wapod.observe(self.apod_select)
        self.bapod = widgets.Button(description='Show effect on FID')
        self.bapod.on_click(self.show_apod)
        self.bdoit = widgets.Button(description='Process')
        self.bdoit.on_click(self.process)
        self.show()
        fi,ax = plt.subplots()
        self.ax = ax
        if os.path.exists(filename):
            self.load()
            #self.data.set_unit('sec')
            self.display()
    def apod_select(self, e):
        test = self.wapod.value.split()[0]
        self.wpapod_sin.disabled = True
        self.wpapod_Hz.disabled = True
        self.wpapod_enh.disabled = True
        if test == "apod_sin":
            self.wpapod_sin.disabled = False
        if test in ('apod_em', 'apod_gm','gaussenh'):
            self.wpapod_Hz.disabled = False
        if test == 'gaussenh':
            self.wpapod_enh.disabled = False
    def apmin_select(self, e):
        for w in self.wphase0, self.wphase1:
            w.disabled = self.wapmin.value
    def load(self):
        self.data = Import_1D(self.wfile.value)
    def apod(self):
        func = self.wapod.value.split()[0]
        todo = None
        if func == 'apod_sin':
            todo = 'self.data.apod_sin(%f)'%(self.wpapod_sin.value,)
        elif func in ('apod_em', 'apod_gm'):
            todo = 'self.data.%s(%f)'%(func, self.wpapod_Hz.value)
        elif func == 'gaussenh':
            todo = 'self.data.gaussenh(%f,enhancement=%f)'%(self.wpapod_Hz.value, self.wpapod_enh.value)
        if todo is not None:
            eval(todo)
        return self.data
    def show_apod(self, e):
        self.load()
        self.apod()
        self.display()
    def process(self, e):
        self.load()
        self.apod().zf(self.wzf.value).ft_sim().bk_corr().set_unit('ppm')
        if self.wapmin.value:
            self.data.apmin()
            self.wphase0.value = round(self.data.axis1.P0,1)
            self.wphase1.value = self.data.axis1.P1
        else:
            self.data.phase(self.wphase0.value, self.wphase1.value)
        self.display()
    def display(self):
        self.ax.clear()
        self.data.display(figure=self.ax)
    def show(self):
        display(
            VBox([self.wfile,
                HBox([self.wapod, self.wpapod_sin, self.wpapod_Hz, self.wpapod_enh, self.bapod]),
                self.wzf,
                HBox([self.wapmin, self.wphase0, self.wphase1]),
#                self.wbcorr,
                self.bdoit]) )

from spike.plugins import Peaks
class NMRPeaker1D(Show1D):
    """
    a peak-picker for NMR experiments
    """
    # self.peaks : the defined peaklis, copyied in and out of data
    # self.temppk : the last computed pklist
    def __init__(self, data, figsize=None, reverse_scroll=False, show=True):
        super().__init__( data, figsize=figsize, reverse_scroll=reverse_scroll, show=False)
        self.data = data.real()
        try:
            self.peaks = self.data.peaks
        except AttributeError:
            self.peaks = Peaks.Peak1DList()
        self.temppk = Peaks.Peak1DList()
        self.thresh = widgets.FloatLogSlider(value=20.0,
            min=-1, max=2.0, base=10, step=0.01, layout=Layout(width='30%'),
            continuous_update=False, readout=True, readout_format='.2f')
        try: 
            self.thresh.value = 100*self.data.peaks.threshold/self.data.absmax  # if already peak pickeds
        except:
            self.thresh.value = 20.0
            pass
        self.thresh.observe(self.pickpeak)
        self.peak_mode = widgets.Dropdown(options=['marker', 'bar'],value='marker',description='show as')
        self.peak_mode.observe(self.ob)
        self.out = Output(layout={'border': '1px solid red'})
#        self.done = widgets.Button(description="Done", button_style='success')
#        self.done.on_click(self.on_done)
        self.badd = widgets.Button(description="Add", button_style='success', layout=self.blay)
        self.badd.on_click(self.on_add)
        self.brem = widgets.Button(description="Rem", button_style='warning', layout=self.blay)
        self.brem.on_click(self.on_rem)
        self.cancel = widgets.Button(description="Cancel", button_style='warning', layout=self.blay)
        self.cancel.on_click(self.on_cancel)
        self.selval = widgets.FloatText(
            value=0.0, description='selection', layout=Layout(width='20%'), step=0.001, disabled = True)
        self.newval = widgets.FloatText(
            value=0.0, description='calibration', layout=Layout(width='20%'), step=0.001, disabled = True)
        self.setcalib = widgets.Button(description="Set", layout=Layout(width='10%'),
                button_style='success', tooltip='Set spectrum calibration')
        self.setcalib.on_click(self.on_setcalib)
        def on_press(event):
            v = event.xdata
            iv = self.data.axis1.ptoi(v)  # store position in index (peaks are internally in index)
            distclose = np.inf     # search closest peak

            pclose = 0.0
            for p in self.data.peaks:
                if abs(p.pos-iv) < distclose:
                    pclose = p.pos
                    distclose = abs(p.pos-iv)
            self.selval.value = self.data.axis1.itop(pclose)  # back to ppm
            for w in (self.selval, self.newval):
                w.disabled = False
        cids = self.fig.canvas.mpl_connect('button_press_event', on_press)
        # redefine Box
        self.tabs = Tab()
        self.tabs.children = [
            VBox([
                HBox([self.badd, self.brem, Label('threshold - % largest signal'), self.thresh, self.peak_mode]),
                HBox([VBox([self.blank, self.reset, self.scale]), self.fig.canvas])
                ]),
            VBox([
                HBox([ Label('Select a peak with mouse and set calibrated values'), self.selval, self.newval, self.setcalib]),
                HBox([VBox([self.blank, self.reset, self.scale]), self.fig.canvas])
                ]),
            self.out]
        self.tabs.set_title(0, 'Peak Picker')
        self.tabs.set_title(1, 'calibration')
        self.tabs.set_title(2, 'Peak Table')

        self.children = [VBox([HBox([self.done, self.cancel]),self.tabs])]

        if show: self.show()
    def show(self):
        self.data.display(figure=self.ax)
        self.xb = self.ax.get_xbound()  # initialize zoom
        self.pp()
        self.data.display(figure=self.ax)
        self.ax.set_xbound( (self.data.axis1.itop(0),self.data.axis1.itop(self.data.size1)) )
        self.disp()
        display(self)
    def on_add(self, b):
        self.peaks = Peaks.peak_aggreg(self.peaks + self.temppk, distance=1.0)
        self.temppk = Peaks.Peak1DList()
        self.disp()
    def on_rem(self, b):
        (up,down) = self.ax.get_xbound()
        iup = self.data.axis1.ptoi(up)
        idown = self.data.axis1.ptoi(down)
        iup,idown = (max(iup,idown), min(iup,idown))
        to_rem = []
        for pk in self.peaks:
            if pk.pos < iup and pk.pos>idown:
                to_rem.append(pk)
        for pk in to_rem:
            self.peaks.remove(pk)
        self.disp()
    def on_cancel(self, b):
        self.close()
        del self.data.peaks
        print("no Peak-Picking done")
    def on_done(self, b):
        self.temppk = Peaks.Peak1DList()  # clear temp peaks        
        self.close()
        # new figure
        self.disp()
        # and display        
        tabs = Tab()
        tabs.children = [self.fig.canvas, self.out]
        tabs.set_title(0, '1D Display')
        tabs.set_title(1, 'Peak Table')
        display(tabs)
        self.data.peaks = self.peaks  # and copy

    def on_setcalib(self, e):
        off = self.selval.value-self.newval.value
        self.data.axis1.offset -= off*self.data.axis1.frequency   # off is in ppm, axis1.offset is in Hz
        self.selval.value = self.newval.value
        self.pp()
    def pkprint(self,event):
        self.out.clear_output(wait=True)
        with self.out:
            if len(self.temppk)>0:
                self.data.peaks = self.temppk
                display(HTML("<p style=color:red> Transient peak list </p>"))
                display(HTML(self.data.pk2pandas().to_html()))
                display(HTML("<p style=color:blue> Defined peak list </p>"))
            self.data.peaks = self.peaks
            display(HTML(self.data.pk2pandas().to_html()))
    def _pkprint(self,event):
        self.out.clear_output(wait=True)
        with self.out:
            print(self.pklist())
    def pklist(self):
        "creates peaklist for printing or exporting"
        text = ["ppm\tInt.(%)\twidth (Hz)"]
        data = self.data
        intmax = max(data.peaks.intens)/100
        for pk in data.peaks:
            ppm = data.axis1.itop(pk.pos)
            width = 2*pk.width*data.axis1.specwidth/data.size1
            l = "%.3f\t%.1f\t%.2f"%(ppm, pk.intens/intmax, width)
            text.append(l)
        return "\n".join(text)
    def ob(self, event):
        if event['name']=='value':
            self.disp()        
    def disp(self):
        "interactive wrapper to peakpick"
        self.xb = self.ax.get_xbound()
        #self.yb = self.ax.get_ybound()
        self.ax.clear()
        #super().disp()
        self.data.display(scale=self.scale.value, new_fig=False, figure=self.ax, title=self.title)
        x = [self.data.axis1.itoc(z) for z in (0, self.data.size1) ]
        y = [self.data.absmax*self.thresh.value/100]*2
        self.ax.plot(x,y,':r')
        try:
            self.temppk.display(peak_label=False, peak_mode=self.peak_mode.value, f=self.data.axis1.itoc, figure=self.ax,color='red')
            self.peaks.display(peak_label=False, peak_mode=self.peak_mode.value, f=self.data.axis1.itoc, color='blue', figure=self.ax)
        except:
            rrr("problem")
        self.temppk.display(peak_label=True, peak_mode=self.peak_mode.value, color='red', figure=self.ax)
        self.ax.set_xbound(self.xb)
        self.ax.set_ylim(ymax=self.data.absmax/self.scale.value)
        self.pkprint({'name':'value'})  # send pseudo event to display peak table
    def pickpeak(self, event):
        "interactive wrapper to peakpick"
        if event['name']=='value':
            self.pp()
    def pp(self):
        "do the peak-picking calling pp().centroid()"
        #self.spec.clear_output(wait=True)
        th = self.data.absmax*self.thresh.value/100
        zm = self.ax.get_xbound()
        self.data.set_unit('ppm').peakpick(threshold=th, verbose=False, zoom=zm).centroid()
        self.temppk = self.data.peaks
        self.disp()
        self.ax.annotate('%d peaks detected'%len(self.data.peaks) ,(0.05,0.95), xycoords='figure fraction')

from spike.plugins.Integrate import Integrals, Integralitem
class NMRIntegrate(Show1D):
    "an integrator for NMR experiments"
    def __init__(self, data, figsize=None, reverse_scroll=False, show=True):
        super().__init__( data, figsize=figsize, reverse_scroll=reverse_scroll, show=False)
        try:
            self.Integ = data.integrals
        except:
            self.Integ = Integrals(data, compute=False)   # initialize with empty list
        self.thresh = widgets.FloatLogSlider(description='sensitivity',value=10.0,
            min=-1, max=2.0, base=10, step=0.01, layout=Layout(width='30%'),
            continuous_update=HEAVY, readout=False, readout_format='.2f')
        self.bias = widgets.FloatSlider(
            description='bias', layout=Layout(width='20%'),
            value=0.0,min=-10.0, max=10.0, step=0.1,
            continuous_update=HEAVY, readout=False, readout_format='.1f')
        self.sep = widgets.FloatSlider(
            description='separation', layout=Layout(width='30%'),
            value=3.0,min=0.0, max=20.0, step=0.1,
            continuous_update=HEAVY, readout=False, readout_format='.1f')
        self.wings = widgets.FloatSlider(
            description='extension', layout=Layout(width='30%'),
            value=5.0,min=0.5, max=20.0, step=0.1,
            continuous_update=HEAVY, readout=False, readout_format='.1f')
        for w in (self.bias, self.sep, self.wings):
            w.observe(self.integrate)
        self.thresh.observe(self.peak_and_integrate)
        self.cancel = widgets.Button(description="Cancel", button_style='warning', layout=self.blay)
        self.cancel.on_click(self.on_cancel)

        self.bprint = widgets.Button(description="Print", layout=self.blay,
                button_style='success', # 'success', 'info', 'warning', 'danger' or ''
                tooltip='Print to screen')
        self.bprint.on_click(self.print)
        self.badd = widgets.Button(description="Add", layout=self.blay,
                button_style='success', tooltip='Add an entry from current zoom')
        self.badd.on_click(self.on_add)
        self.brem = widgets.Button(description="Rem", layout=self.blay,
                button_style='warning', tooltip='Remove all entries in current zoom')
        self.brem.on_click(self.on_rem)
        self.bauto = widgets.Button(description="Compute", layout=self.blay,
                button_style='success', tooltip='Automatic definition of integrals')
        self.bauto.on_click(self.peak_and_integrate)

        self.entry = widgets.IntText(value=0,description='Entry',min=0,layout=Layout(width='15%'))
        self.value = widgets.FloatText(value=100,description='Value',layout=Layout(width='15%'))
        self.set = widgets.Button(description="Set", button_style='success', layout=Layout(width='10%'))
        self.set.on_click(self.set_value)
        self.out = Output(layout={'border': '1px solid red'})
        # redefine children
        self.tabs = Tab()
        self.tabs.children = [
            VBox([
                HBox([self.badd, self.brem,
                    Label("Use buttons to add and remove integrals in the current zoom window")]), 
                HBox([VBox([self.blank, self.reset, self.scale]), self.fig.canvas]),
                ]),
            VBox([HBox([self.bauto, Label("Define integral shapes using the sliders below")]),
                HBox([self.thresh,  self.sep, self.wings]),
                HBox([VBox([self.blank, self.reset, self.scale]), self.fig.canvas]),
                ]),
            VBox([HBox([Label('Choose an integral for calibration'),
                                self.entry, self.value, self.set, self.blank, self.bprint] ),
                self.out ])
            ]
        self.tabs.set_title(0, 'Manual integration')
        self.tabs.set_title(1, 'Automatic')
        self.tabs.set_title(2, 'Integral Table & Calibration')
        self.children = [VBox([HBox([self.done, self.cancel]),self.tabs])]
        # self.children = [VBox([HBox([self.done, self.cancel, self.bias, self.sep, self.wings]),
        #                     HBox([Label('Choose an integral for calibration'),
        #                         self.entry, self.value, self.set, self.blank, self.bprint] ),
        #                     HBox([VBox([self.blank, self.reset, self.scale]), self.fig.canvas]),
        #                     self.out ])]
        if show: self.show()
    def show(self):
        self.data.display(figure=self.ax)
        self.xb = self.ax.get_xbound()  # initialize zoom
        #self.int()
        self.data.display(figure=self.ax)
        self.ax.set_xbound( (self.data.axis1.itop(0),self.data.axis1.itop(self.data.size1)) )
        self.disp()
        display(self)
    def on_cancel(self, b):
        self.close()
        print("No integration")
    def on_done(self, b):
        self.close()
        self.disp(zoom=True)
        display(self.fig)
        display(self.out)
        self.data.integrals = self.Integ        # copy integrals
    def on_add(self, b):
        start, end = self.ax.get_xbound() 
        self.Integ.append( Integralitem(self.data.axis1.ptoi(start), self.data.axis1.ptoi(end), [], 0.0) )
        self.Integ.zonestocurves()
        self.disp()
        self.print(None)
    def on_rem(self, b):
        start, end = self.ax.get_xbound()
        start, end = self.data.axis1.ptoi(start), self.data.axis1.ptoi(end)
        start, end = (min(start, end), max(start, end))
        to_rem = []
        for ii in self.Integ:
            if ii.start>start and ii.end<end:
                to_rem.append(ii)
        for ii in to_rem:
            self.Integ.remove(ii)
        self.disp()
        self.print(None)
    def set_value(self,b):
        self.Integ.recalibrate(self.entry.value, self.value.value)
        self.disp()
        self.print(None)
    def print(self,event):
        self.out.clear_output()
        self.Integ.sort(key=lambda x: x.start)   # sort integrals
        with self.out:
            display(HTML( self.Integ.to_pandas().to_html() ))
    def peak_and_integrate(self, event):
        self.data.pp(threshold=self.data.absmax*self.thresh.value/100,
            verbose=False, zoom=self.ax.get_xbound()).centroid()
        if len(self.data.peaks)>0:
            self.int()
    def integrate(self, event):
        #"integrate from event"
        #if event['name']=='value':
        self.int()
    def int(self):
        "do the automatic integration from peaks and parameters"
        self.on_rem(None) 
        try:
            calib = self.Integ.calibration
        except:
            calib = None
        self.data.set_unit('ppm')
        try:
            self.data.peaks
        except:
            self.data.pp(threshold=self.data.absmax*self.thresh.value/100,
                verbose=False, zoom=self.ax.get_xbound()).centroid()
        # appending to the list, self and Integrals are equivalent to list -
        self.Integ +=  Integrals(self.data, separation=self.sep.value, wings=self.wings.value,
                bias=self.data.absmax*self.bias.value/100)
        self.Integ.calibrate(calibration=calib)
        self.print(None)
        self.disp()
    def ob(self, event):
        if event['name']=='value':
            self.disp()
    def disp(self, zoom=False):
        "refresh display from event - if zoom is True, display only in xbound"
        self.xb = self.ax.get_xbound()
        # self.yb = self.ax.get_ybound()
        self.ax.clear()
        if zoom:
            zoom = self.xb
        else:
            zoom = None
        self.data.display(new_fig=False, figure=self.ax, scale=self.scale.value, zoom=zoom)
        try:
            self.Integ.display(label=True, figure=self.ax, labelyposition=0.01, regions=False, zoom=zoom)
        except:
            pass
        self.ax.set_xbound(self.xb)
        # self.ax.set_ybound(self.yb)


# if __name__ == '__main__':
#    unittest.main()    
