#!/usr/bin/env python
# -*- coding: utf-8 -*-

## @file

u"""@package dbplot
Plot data curves in multiple tabs.
<p>@author J.M. Rius<br>Universitat Politècnica de Catalunya (UPC)
<p>@version 1.0
<p>@date November 25, 2010
<p>dBplot is copyright &copy; Juan Manuel Rius 2009, <br>Universitat Politècnica de Catalunya (UPC)<br> rius@tsc.upc.edu.
<p>
This program uses the Qwt library for data plotting, developed by the Qwt project (http://qwt.sf.net),
and pyqwt5, the python binding of qwt5 C++ library for Qt applications.
<p>
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
<p>
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
<p>
You should have received a copy of the GNU General Public License along with this program in file "LICENSE.GPL3"; 
if not, download it from http://www.gnu.org/licenses/gpl-3.0.html  .
""" 

import sys
import platform
from time import time
from math import log10,  ceil
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import numpy as np
import PyQt4.Qwt5 as Qwt
import Ui_dbplot
import Ui_dbplot_scaling

##
# Global variable useful only for MacOSX
MAC = "qt_mac_set_native_menubar" in dir()

##
# Application name, to be displayed in 'About' dialog
applicationName = 'dBplot'


class DbPlot(QMainWindow,  Ui_dbplot.Ui_Plot):
    """
    GUI dialog to contain plot.
    Uses pyqwt5, the python binding of qwt5 C++ library for Qt applications.
    """

    def __init__(self, XData, leftYData,  rightXData = None, rightYData = None,  leftWidth = None,  rightWidth = None,  leftYmin = None, leftYmax = None, rightYmin = None, rightYmax = None, Xmin = None, Xmax = None, windowTitle = None,  tabTitle = None,  title=None,  leftYNames=None,  rightYNames=None,  leftYVisible=None,  rightYVisible=None,  XLabel=None,  leftYLabel=None,  rightYLabel=None,  leftYColors=None,  rightYColors=None, textLabel=None, Xunits=None,  leftYunits=None,  rightYunits = None,  parent=None):
        """
        Constructor: Creates PyQt4.QtGui.Qdialog window containing PyQt4.Qwt5.QwtPlot widget.
        Uses pyqwt5, the python binding of qwt5 C++ library for Qt applications.
        
            @param XData        = List of X-axis data (list of numpy arrays).
                            Each element in the list is the x-axis data for the corresponding element in leftYData.
                            When there is only one curve, it is not necessary to create a list of curves: XData argument can be the curve x-data alone.
            @param leftYData    = List of curve-data to plot in left y-axis (list of numpy arrays).
                            Curve-data can be a list of int or float or a numpy array.
                            If a list element is a 2-D numpy array, it defines a curve family sharing the same curve properties.
                            Curves in a curve family have Ydata in the columns of the 2-D numpy array.
                            When there is only one curve, it is not necessary to create a list of curves: leftYData argument can be the curve-data alone.
            @param rightXData   = List of X-axis data (list of numpy arrays).
                            Each element in the list is the x-axis data for the corresponding element in rightYData.
                            When there is only one curve, it is not necessary to create a list of curves: rightXData argument can be the curve x-data alone.
                            If equal to None, XData is used both for left-y and right-y axis data.
                            Default None.
            @param rightYData    = List of curve-data to plot in right y-axis (list of numpy arrays).
                            Curve-data can be a list of int or float or a numpy array.
                            If a list element is a 2-D numpy array, it defines a curve family sharing the same curve properties.
                            Curves in a curve family have Ydata in the columns of the 2-D numpy array.
                            When there is only one curve, it is not necessary to create a list of curves: rightYData argument can be the curve-data alone.
                            If it is equal to None, there is no data in the right-Y axis. Default None.
            @param leftYmin  = Set manual scaling for left-y axis with this minimum value.
            @param leftYmax  = Set manual scaling for left-y axis with this maximum value.
            @param rightYmin = Set manual scaling for right-y axis with this minimum value.
            @param rightYmax = Set manual scaling for right-y axis with this maximum value.
            @param Xmin      = Set manual scaling for x axis with this minimum value.
            @param Xmax      = Set manual scaling for x axis with this maximum value.
            @param windowTitle  = Window title (string). Default None.
            @param tabTitle     = Tab title (string). Default None.
            @param title        = Plot title (string). Default None.
            @param leftYNames   = List of names corresponding to leftYData (list of strings).
                            If equal to None, no legend is created. Default None.
            
            @param rightYNames   = List of names corresponding to rightYData (list of strings).
                            If equal to None, no legend is created. Default None.
            @param leftYVisible   = List of visibility flags corresponding to leftYData (list of bool).
                            If equal to None, all curves are visible. Default None.
            @param rightYVisible   = List of visibility flags corresponding to rightYData (list of bool).
                            If equal to None, all curves are visible. Default None.
            @param XLabel       = X-axis label (string). Default None.
            @param leftYLabel   = Left y-axis label (string). Default None.            
            @param rightYLabel   = Right y-axis label (string). Default None.
            @param leftYColors  = List of colors corresponding to leftYData (list of Qt.QColor instances, like Qt.red or Qt.blue). 
                            If equal to None, default colors are used. Default None.            
            @param rightYColors  = List of colors corresponding to rightYData (list of Qt.QColor instances, like Qt.red or Qt.blue). 
                            If equal to None, default colors are used. Default None.
            @param Xunits = X-axis units for markers (string). If equal to None, no units are displayed. Default None.
            @param leftYunits = Ledt Y-axis units for markers and tracker (string). If equal to None, no units are displayed. Default None.
            @param rightYunits = Right Y-axis units for markers and tracker (string). If equal to None, no units are displayed. Default None.
            @param textLabel = Text to display as a label (string). Can be multiline. Default None.
            @param parent = Parent widget, in this case is mainWindow. Default None.
        """		

        super(DbPlot, self).__init__(parent)
        self.setupUi(self)
        self.setAttribute(Qt.WA_DeleteOnClose)
        
        if windowTitle is not None: self.setWindowTitle(windowTitle)
        self.statusBar.showMessage('Use toolbar buttons to zoom, add or delete markers')
        
        # Add what's this action
        self.toolBar.addAction(QWhatsThis.createAction(self.toolBar))
        
        ##
        # Move marker knob
        self.moveKnob = None
        
#        # Knob version
#        self.moveKnob = Qwt.QwtKnob(self)
#        self.moveKnob.setKnobWidth(24)
#        self.moveKnob.setTotalAngle(300)
#        self.toolBar.insertWidget(self.actionDeleteMarker, self.moveKnob)
        
        # Slider version
        self.moveKnob = Qwt.QwtSlider(self, Qt.Horizontal, Qwt.QwtSlider.BottomScale, Qwt.QwtSlider.BgSlot)
        self.moveKnob.setScaleMaxMajor(24)
        self.moveKnob.setScaleMaxMinor(5)
        self.verticalLayout.addWidget(self.moveKnob)
        
        # Common to Knob and Slider versions
        self.moveKnob.setVisible(False)
        self.connect(self.moveKnob, SIGNAL('valueChanged(double)'), self.on_moveKnob_valueChanged )
        
        # Display text labels
        if textLabel is not None: 
            # Multiline text in Qlabel does not work with html rich-text (why?). 
            # We have to split the text as a list of lines and create a Qlabel for each line.
            textLabel = textLabel.split('\n')
            self.label = []
            
            for st in textLabel:
                newLabel = QLabel(st,  self.centralWidget)
                self.label.append (newLabel)
                self.verticalLayout.addWidget(newLabel)
        
        ##
        # Number of TabWidgets
        self.Ntabs = 0
        
        ##
        # List of QwtPlot class instances
        self.hPlot = []
        
        ##
        # Left axis zoomer. List for all Tabs.
        self.zoomerLeft = []
        
        ##
        # Right axis zoomer. List for all Tabs.
        self.zoomerRight = []
        
        ##
        # Left axis picker. List for all Tabs.
        self.pickerLeft = []
        
        ##
        # Axis scaling dialog. List for all Tabs.
        self.scalingDlg = []
        
        ##
        # Current instance of scalingDlg class, if the scalingDlg window is open, None if it is closed.
        self.scalingDlgCurrent = None
        
        ##
        # Dialog for setting curves visibility. List for all Tabs.
        self.setCurves = []
        
        ##
        # Current instance of EditCurvesDlg class, if the setCurves window is open, None if it is closed.
        self.setCurvesCurrent = None
        
        ##
        # List of curves in left y-axis. List for all Tabs.
        self.leftYCurves = []
        
        ##
        # List of curves in right y-axis. List for all Tabs.
        self.rightYCurves = [] 
        
        ##
        # List of specification masks.  List for all Tabs.
        self.masks = []
        
        ##
        # List of markers. List for all Tabs.
        self.markers = []
        
        ##
        # List of XData. List for all Tabs.
        self.XData = []
        
        ##
        # List of leftYData. List for all Tabs.
        self.leftYData = []
        
        ##
        # List of rightXData. List for all Tabs.
        self.rightXData = []
        
        ##
        # List of rightYData. List for all Tabs.
        self.rightYData =  []
        
        ##
        # List of left y-axis curve names. List for all Tabs.
        self.leftYNames = []
        
        ##
        # List of right y-axis curve names. List for all Tabs.
        self.rightYNames = []
        
        ##
        # List of mask names. List for all Tabs.
        self.maskNames = []
        
        ##
        # Legend for each plot.  List for all Tabs.
        self.legends = []
        
        ##
        # Zoomer base for autoscaling. List for all Tabs.
        self.autoScaleBase = []
        
        ##
        # X-axis units for markers. List for all Tabs.
        self.Xunits = []
        
        ##
        # Left Y-axis units for markers. List for all Tabs.
        self.leftYunits = []
        
        ##
        # Right Y-axis units for markers. List for all Tabs.
        self.rightYunits = []
        
        ##
        # Marker to move
        self.markerMove = None
        
        ##
        # Size of curve symbols, when curve points are visible for adding new marker.
        self.curveSymbolSize = None
        
        ##
        # Number of x-axis significant digits required to display markers and tracker labels, depending on axis scaling. List for all Tabs.
        self.precX = []
        
        ##
        # Number of left- y axis significant digits required to display markers and tracker labels, depending on axis scaling. List for all Tabs.
        self.precY = []
        
        ##
        # Flag that is True when the program processes mouse dragging. If True, LeftButton clicks are discarded.
        self.draggingMode = False
        
        # Create Tab
        self.addTab(XData = XData, leftYData = leftYData,  rightXData = rightXData, rightYData = rightYData, leftWidth = leftWidth, rightWidth = rightWidth, leftYmin = leftYmin, leftYmax = leftYmax, rightYmin = rightYmin, rightYmax = rightYmax, Xmin = Xmin, Xmax = Xmax , tabTitle = tabTitle,  title = title,  leftYNames = leftYNames,  rightYNames = rightYNames,  leftYVisible = leftYVisible,  rightYVisible = rightYVisible,  XLabel = XLabel,  leftYLabel = leftYLabel,  rightYLabel = rightYLabel,  leftYColors = leftYColors,  rightYColors = rightYColors, Xunits = Xunits,  leftYunits = leftYunits,  rightYunits = rightYunits)
        
        # Show plot
        self.resize(800, 600)
        self.show()
        
        ##
        # Link to the mainWindow.
        self.mainWindow = parent
        
        ##
        # Flag that will be set to true when the window is closed, in order to allow reconstruction of the class 
        # instead of updating the plot when execute or compute buttons are clicked in other windows.
        self.closed = False
                
        
    def addTab(self,  XData, leftYData,  rightXData = None, rightYData = None, leftWidth = None,  rightWidth = None,  leftYmin = None, leftYmax = None, rightYmin = None, rightYmax = None, Xmin = None, Xmax = None, tabTitle = None,  title=None,  leftYNames=None,  rightYNames=None,  leftYVisible=None,  rightYVisible=None,  XLabel=None,  leftYLabel=None,  rightYLabel=None,  leftYColors=None,  rightYColors=None, Xunits=None, leftYunits=None, rightYunits=None, replaceTab=None):
        """
        Adds new tab with plot.
        
            @param XData        = List of X-axis data (list of numpy arrays).
                            Each element in the list is the x-axis data for the corresponding element in leftYData.
                            When there is only one curve, it is not necessary to create a list of curves: XData argument can be the curve x-data alone.
            @param leftYData    = List of curve-data to plot in left y-axis (list of numpy arrays).
                            Curve-data can be a list of int or float or a numpy array.
                            If a list element is a 2-D numpy array, it defines a curve family sharing the same curve properties.
                            Curves in a curve family have Ydata in the columns of the 2-D numpy array.
                            When there is only one curve, it is not necessary to create a list of curves: leftYData argument can be the curve-data alone.
            @param rightXData   = List of X-axis data (list of numpy arrays).
                            Each element in the list is the x-axis data for the corresponding element in rightYData.
                            When there is only one curve, it is not necessary to create a list of curves: rightXData argument can be the curve x-data alone.
                            If equal to None, XData is used both for left-y and right-y axis data.
                            Default None.
            @param rightYData    = List of curve-data to plot in right y-axis (list of numpy arrays).
                            Curve-data can be a list of int or float or a numpy array.
                            If a list element is a 2-D numpy array, it defines a curve family sharing the same curve properties.
                            Curves in a curve family have Ydata in the columns of the 2-D numpy array.
                            When there is only one curve, it is not necessary to create a list of curves: rightYData argument can be the curve-data alone.
                            If it is equal to None, there is no data in the right-Y axis. Default None.
            @param leftYmin  = Set manual scaling for left-y axis with this minimum value.
            @param leftYmax  = Set manual scaling for left-y axis with this maximum value.
            @param rightYmin = Set manual scaling for right-y axis with this minimum value.
            @param rightYmax = Set manual scaling for right-y axis with this maximum value.
            @param Xmin      = Set manual scaling for x axis with this minimum value.
            @param Xmax      = Set manual scaling for x axis with this maximum value.
            @param tabTitle     = Tab title (string). Default None.
            @param title        = Plot title (string). Default None.
            @param leftYNames   = List of names corresponding to leftYData (list of strings).
                            If equal to None, no legend is created. Default None.
            
            @param rightYNames   = List of names corresponding to rightYData (list of strings).
                            If equal to None, no legend is created. Default None.
            @param leftYVisible   = List of visibility flags corresponding to leftYData (list of bool).
                            If equal to None, all curves are visible. Default None.
            @param rightYVisible   = List of visibility flags corresponding to rightYData (list of bool).
                            If equal to None, all curves are visible. Default None.
            @param XLabel       = X-axis label (string). Default None.
            @param leftYLabel   = Left y-axis label (string). Default None.            
            @param rightYLabel   = Right y-axis label (string). Default None.
            @param leftYColors  = List of colors corresponding to leftYData (list of Qt.QColor instances, like Qt.red or Qt.blue). 
                            If equal to None, default colors are used. Default None.            
            @param rightYColors  = List of colors corresponding to rightYData (list of Qt.QColor instances, like Qt.red or Qt.blue). 
                            If equal to None, default colors are used. Default None.
            @param Xunits = X-axis units for markers (string). If equal to None, no units are displayed. Default None.
            @param leftYunits = Left Y-axis units for markers and tracker (string). If equal to None, no units are displayed. Default None.
            @param rightYunits = Right Y-axis units for markers and tracker (string). If equal to None, no units are displayed. Default None.
            @param replaceTab = If not None, it is the index of an already existing Tab and all the CurveFamilies have to be replaced with data from the function arguments. Default False. 
        """		
        if replaceTab is None:
            self.Ntabs += 1
            nTab = self.Ntabs-1     # Current Tab index in lists
            tab = QWidget()
            verticalLayout_tab = QVBoxLayout(tab)
            self.tabWidget.addTab(tab, "Plot") 
            
            # Add data
            self.XData.append(XData)
            self.leftYData.append(leftYData)
            self.rightXData.append(rightXData)
            self.rightYData.append(rightYData)
            
            # Add axis units
            self.Xunits.append(      ' ' + Xunits      if Xunits      is not None else '' )
            self.leftYunits.append( ' ' + leftYunits if leftYunits is not None else '' )
            self.rightYunits.append( ' ' + rightYunits if rightYunits is not None else '' )
    
            # Add layout for axis scaling controls
            scalingDlg = AxisScalingDlg(self)
            self.scalingDlg.append(scalingDlg)
            scalingDlg.setWindowTitle('Axis scaling: %s' % tabTitle)
                
            # Make a QwtPlot widget
            self.hPlot.append(Qwt.QwtPlot(tab)) 
            verticalLayout_tab.addWidget(self.hPlot[nTab])
            hPlot = self.hPlot[nTab]
            
            # Set grid
            hPlot.plotLayout().setAlignCanvasToScales(True)
            grid = Qwt.QwtPlotGrid()
            grid.attach(hPlot)
            grid.setPen(QPen(Qt.black, 0, Qt.DotLine))
            
            # Set canvas color
            hPlot.setCanvasBackground(Qt.white)
            
        else:   # Replace data in already existing Tab
            nTab= replaceTab
            self.XData[nTab] = XData
            self.leftYData[nTab] = leftYData
            self.rightXData[nTab] = rightXData
            self.rightYData[nTab] = rightYData
            
            # Replace axis units
            self.Xunits[nTab]        = ' ' + Xunits      if Xunits      is not None else '' 
            self.leftYunits[nTab]   =  ' ' + leftYunits if leftYunits is not None else '' 
            self.rightYunits[nTab] =  ' ' + rightYunits if rightYunits is not None else '' 
            
            hPlot = self.hPlot[nTab]
            scalingDlg = self.scalingDlg[nTab]
            
        # Set Tab abd Plot titles
        if tabTitle is not None: self.tabWidget.setTabText(nTab, tabTitle)
        if title is not None: hPlot.setTitle(title)
        if rightYData is not None: hPlot.enableAxis(Qwt.QwtPlot.yRight)
        
        # Set axis titles
        if XLabel is not None: hPlot.setAxisTitle(Qwt.QwtPlot.xBottom, XLabel)
        if leftYLabel is not None: hPlot.setAxisTitle(Qwt.QwtPlot.yLeft, leftYLabel)
        if rightYLabel is not None: hPlot.setAxisTitle(Qwt.QwtPlot.yRight, rightYLabel)
        
        # Set default colors
        defaultColors = [Qt.blue, Qt.red, QColor(0,  175,  0),  QColor(191,  0,  191),   Qt.black,  QColor(0,  191,  191),  QColor(191,  191,  0)  ]
        
        # When there is only one curve, make sure that x-data, y-data and Names are an one-element list.
        if type(leftYData[0]).__name__  not in ['ndarray',  'list']:  leftYData = [ leftYData ]
        elif type(leftYData).__name__  == 'ndarray' and leftYData.ndim == 2: leftYData = [ leftYData ]    # A single curve family containing a 2-D array
        
        if type(XData[0]).__name__  not in ['ndarray',  'list']:  XData = [ XData ]
        
        if rightYData is not None: 
            if type(rightYData[0]).__name__  not in ['ndarray',  'list']:  rightYData = [ rightYData ]
            elif type(rightYData).__name__  == 'ndarray' and rightYData.ndim == 2: rightYData = [ rightYData ]    # A single curve family containing a 2-D array
            
        if rightXData is not None:
            if type(rightXData[0]).__name__  not in ['ndarray',  'list']:  rightXData = [ rightXData ]
        
        if leftYNames is not None:
            if type(leftYNames).__name__  not in ['ndarray',  'list']:  leftYNames = [ leftYNames ]
            self.leftYNames.append(leftYNames)
        if rightYNames is not None:
            if type(rightYNames).__name__  not in ['ndarray',  'list']:  rightYNames = [ rightYNames ]
            self.rightYNames.append(rightYNames)
        if leftYVisible is not None:
            if type(leftYVisible).__name__  not in ['ndarray',  'list']:  leftYVisible = [ leftYVisible]
        if rightYVisible is not None:
            if type(rightYVisible).__name__  not in ['ndarray',  'list']:  rightYVisible = [ rightYVisible ]
        if leftYColors is not None:
            if type(leftYColors).__name__  not in ['ndarray',  'list']:  leftYColors = [ leftYColors ]
        if rightYColors is not None:
            if type(rightYColors).__name__  not in ['ndarray',  'list']:  rightYColors = [ rightYColors ]
        if leftWidth is not None:
            if type(leftWidth).__name__  not in ['ndarray',  'list']:  leftWidth = [ leftWidth ]
        if rightWidth is not None:
            if type(rightWidth).__name__  not in ['ndarray',  'list']:  rightWidth = [ rightWidth ]
            
            
        # in ['float', 'float64',  'int',  'complex']:
        
        
        # Delete old curve families. Must be called before CF = CurveFamily()
        if replaceTab is not None: 
            for CF in self.leftYCurves[nTab]: CF.deleteCurves()
            for CF in self.rightYCurves[nTab]: CF.deleteCurves()


        # Prepare visibility lists
        if leftYVisible is None: leftYVisible = [ True for n in leftYData ]             # By default, all curves are visible
        else: assert len(leftYVisible) == len(leftYData)
        
        if rightYData is not None:
            if rightYVisible is None: rightYVisible = [ True for n in rightYData ]      # By default, all curves are visible
            else: assert len(rightYVisible) == len(rightYData)
        
        # Set left-Y data
        leftYCurves = []      # List of curves in left y-axis
        if leftYNames is not None: assert len(leftYData) == len(leftYNames)
        if leftYColors is not None: assert len(leftYData) == len(leftYColors)
        if leftWidth is not None: assert len(leftYData) == len(leftWidth)
        
        # Create all the left y-axis curves
        for n in range(0,  len(leftYData)):
            # Check if x-axis data is the same for all curves
            xdata = XData[0] if len(XData)  == 1 else XData[n]
            
            assert len(xdata) == len(leftYData[n])  # If leftYData[n] is a 2-D array, len() returns the number of rows
            name = leftYNames[n] if leftYNames is not None else 'curve #%d' % n
            color = leftYColors[n] if leftYColors is not None else defaultColors[n % len(defaultColors)]
            width = leftWidth[n]   if leftWidth    is not None else None
            
            CF = CurveFamily(hPlot, name,  xdata, leftYData[n],  Qwt.QwtPlot.yLeft, leftYVisible[n], color,  width)
            leftYCurves.append(CF)
            
        # Set right-Y data
        rightYCurves = []      # List of curves in right y-axis
        if rightYData is not None:
            if rightYNames is not None: assert len(rightYData) == len(rightYNames)
            if rightYColors is not None: assert len(rightYData) == len(rightYColors)
            if rightWidth is not None: assert len(rightYData) == len(rightWidth)
            
            # Create all the right y-axis curves
            for n in range(0,  len(rightYData)):
                # Check if right-y curves use rightXData or XData
                tmp = XData if rightXData is None else rightXData
                
                # Check if x-axis data is the same for all curves
                xdata = tmp[0] if len(tmp)  == 1 else tmp[n]
                
                assert len(xdata ) == len(rightYData[n])
                name = rightYNames[n] if rightYNames is not None else 'curve #%d' % n
                color = rightYColors[n] if rightYColors is not None else defaultColors[(n + len(leftYData)) % len(defaultColors)]
                width = rightWidth[n] if rightWidth is not None else None
            
                CF = CurveFamily(hPlot, name,  xdata, rightYData[n],  Qwt.QwtPlot.yRight, rightYVisible[n], color,  width)
                rightYCurves.append(CF)
            
        if replaceTab is None:               
            # Install EventFilter in plot canvas.
            # The filter does not discard processed events, so it can be installed before or after creating a QwtPlotPicker without preventing the events from being sent to the QwtPlotPicker.
            # The filter that is installed last will process events first,
            hPlot.canvas().installEventFilter(CanvasEventFilter(self, hPlot))
    
            # Set zoomer and tracker
            zoomerLeft = Qwt.QwtPlotZoomer(Qwt.QwtPlot.xBottom, Qwt.QwtPlot.yLeft, Qwt.QwtPicker.DragSelection, Qwt.QwtPicker.AlwaysOff, hPlot.canvas())
            self.zoomerLeft.append(zoomerLeft)
            self.zoomerLeft[nTab].setRubberBandPen(QPen(Qt.green))
            self.zoomerLeft[nTab].setEnabled(False)   
            self.connect(zoomerLeft,  SIGNAL('zoomed(QwtDoubleRect)'), lambda value: self.getPrecision(nTab))
            self.connect(zoomerLeft,  SIGNAL('zoomed(QwtDoubleRect)'), lambda value: self.updateMoveKnobRange())        
            
            zoomerRight = Qwt.QwtPlotZoomer(Qwt.QwtPlot.xTop, Qwt.QwtPlot.yRight, Qwt.QwtPicker.PointSelection | Qwt.QwtPicker.DragSelection, Qwt.QwtPicker.AlwaysOff, hPlot.canvas())
            self.zoomerRight.append(zoomerRight)
            self.zoomerRight[nTab].setRubberBand(Qwt.QwtPicker.NoRubberBand)
            self.zoomerRight[nTab].setEnabled(False) 
            
            # Set picker selection type to Qwt.QwtPicker.PointSelection | Qwt.QwtPicker.ClickSelection to allow marker selection
            pickerLeft = MyPicker( self, nTab, Qwt.QwtPlot.xBottom, Qwt.QwtPlot.yLeft, Qwt.QwtPicker.PointSelection | Qwt.QwtPicker.ClickSelection, Qwt.QwtPlotPicker.CrossRubberBand, Qwt.QwtPicker.AlwaysOn, hPlot.canvas())
            pickerLeft.setTrackerPen(QPen(Qt.black))
            self.pickerLeft.append(pickerLeft)
        
            # Dialog for setting curves visibility
            self.setCurves.append(EditCurvesDlg(leftYCurves,  rightYCurves,  self))
        else:
            zoomerLeft = self.zoomerLeft[nTab]
            zoomerRight = self.zoomerRight[nTab]
            pickerLeft = self.pickerLeft[nTab]
            
            tmp = self.setCurves[nTab]
            del tmp     # Cannot delete directly del self.setCurves[nTab] because it would remove the list element also
            self.setCurves[nTab] = EditCurvesDlg(leftYCurves,  rightYCurves,  self)
        
        if replaceTab is None:  # Add lists of curves, masks and markers
            self.leftYCurves.append(leftYCurves)
            self.rightYCurves.append(rightYCurves)
        
            # Append empty masks list
            self.masks.append( [] )
            self.maskNames.append( [] )
            
            # Append empty markers list
            self.markers.append( [] )
            
            # Append element to precision lists
            self.precX.append(None)
            self.precY.append(None)
            
        else:   
            # Replace  lists of curves
            self.leftYCurves[nTab]   = leftYCurves
            self.rightYCurves[nTab] = rightYCurves
            
            # Detach masks to autoscale without the masks data, and save visibility data
            maskVisibility = []
            for curve in self.masks[nTab]: 
                maskVisibility.append(curve.isVisible())
                curve.detach()
            
            # Autoscale axis and get autoscale min and max for new data, based on Qwt autoscale
            self.hPlot[nTab].setAxisAutoScale(Qwt.QwtPlot.yLeft)
            self.hPlot[nTab].setAxisAutoScale(Qwt.QwtPlot.yRight)
            self.hPlot[nTab].setAxisAutoScale(Qwt.QwtPlot.xBottom)
            self.hPlot[nTab].replot()
            dataLeftYmax, dataLeftYmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.yLeft)
            dataRightYmax, dataRightYmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.yRight)
            dataXmax, dataXmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.xBottom)
        
            # Attach masks
            for n, curve in enumerate(self.masks[nTab]): 
                curve.attach(self.hPlot[nTab])
                curve.setVisible(maskVisibility[n])
                legendItem = self.legends[nTab].find(curve)
                legendItem.setChecked(maskVisibility[n])
            
            # Update zoomer base with data from new curves
            leftZoomerBaseRect,  rightZoomerBaseRect = self.autoScaleBase[nTab].updateZoomerBase(dataXmin,  dataXmax,  dataLeftYmin, dataLeftYmax, dataRightYmin, dataRightYmax)
            if leftZoomerBaseRect   is not None: self.zoomerLeft[nTab].setZoomBase( leftZoomerBaseRect )
            if rightZoomerBaseRect is not None: self.zoomerRight[nTab].setZoomBase( rightZoomerBaseRect )
            
            # Disable all marker, zoom and curve edit facilities
            self.on_actionZoom_toggled(False)
            self.on_actionNewMarker_toggled(False)
            self.on_actionDeleteMarker_toggled(False)
            self.on_actionMoveMarker_toggled(False)
            self.actionCurves.setChecked(False)

            # Delete markers (they were attached to old curve points)
            if len(self.markers[nTab]) > 0:
                for marker in self.markers[nTab]:
                    marker.detach()
                    del marker
                self.markers[nTab] = []
            
            # Replace element in precision lists
            self.precX[nTab] = None
            self.precY[nTab] = None
        
        
        # Initialise axis scaling controls
        for (axis,  groupBox,  min_Counter,  max_Counter) in zip( [ Qwt.QwtPlot.yLeft, Qwt.QwtPlot.yRight, Qwt.QwtPlot.xBottom ],  
                                                                                                [ self.scalingDlg[nTab].leftY_groupBox,  self.scalingDlg[nTab].rightY_groupBox, self.scalingDlg[nTab].X_groupBox ],  
                                                                                                [ self.scalingDlg[nTab].leftYmin_Counter,  self.scalingDlg[nTab].rightYmin_Counter,   self.scalingDlg[nTab].Xmin_Counter ],  
                                                                                                [ self.scalingDlg[nTab].leftYmax_Counter, self.scalingDlg[nTab].rightYmax_Counter,  self.scalingDlg[nTab].Xmax_Counter ] ):
            
            maxScale,  minScale = self.getScaleMaxMin(nTab, axis)
            minorTicks     = self.hPlot[nTab].axisScaleDiv(axis).ticks(Qwt.QwtScaleDiv.MinorTick)
            mediumTicks = self.hPlot[nTab].axisScaleDiv(axis).ticks(Qwt.QwtScaleDiv.MediumTick)
            majorTicks     = self.hPlot[nTab].axisScaleDiv(axis).ticks(Qwt.QwtScaleDiv.MajorTick)
            
            maxminScale = maxScale - minScale
            majorStep = majorTicks[1] - majorTicks[0]
            mediumStep = mediumTicks[0] - majorTicks[0] if len(mediumTicks) > 0 else majorStep   # The 1st tick is always majorTicks[0]
            minorStep = minorTicks[0] - majorTicks[0] if len(minorTicks) > 0 else mediumStep   # The 1st tick is always majorTicks[0]
            NminorSteps = int(round(majorStep/minorStep))
            
#            print 'nTab:', nTab,  'axis:',  axis,  'minorStep =',  minorStep,  'mediumStep =',  mediumStep,  'majorStep =',  majorStep,  'NminorSteps =',  NminorSteps
#            print 'minor', [ '%g' % tmp for tmp in minorTicks ] 
#            print 'medium', [ '%g' % tmp for tmp in mediumTicks ]  
#            print 'major', [ '%g' % tmp for tmp in majorTicks ]  
    
            min_Counter.setStepButton1(1)
            min_Counter.setStepButton2(NminorSteps)
            max_Counter.setStepButton1(1)
            max_Counter.setStepButton2(NminorSteps)
    
            max_Counter.setRange(minScale, maxScale, minorStep)
            max_Counter.setValue(maxScale)
            min_Counter.setRange(minScale, maxScale, minorStep)
            min_Counter.setValue(minScale)
            
            groupBox.setToolTip('Double arrow increments/decrements %g, simple arrow %g' % (minorStep*NminorSteps, minorStep) )
            groupBox.setChecked(False) # Start with manual scaling disabled
            

        if replaceTab is None:
            # Connect widgets to slots. For some reason, it does not work inside the for loop above
            self.connect(self.scalingDlg[nTab].leftYmax_Counter, SIGNAL('valueChanged(double)'),  lambda value: self.updateManualAxisScaling(Qwt.QwtPlot.yLeft) )
            self.connect(self.scalingDlg[nTab].leftYmin_Counter,  SIGNAL('valueChanged(double)'),  lambda value: self.updateManualAxisScaling(Qwt.QwtPlot.yLeft) )
            self.connect(self.scalingDlg[nTab].leftY_groupBox, SIGNAL('toggled(bool)'),  lambda value: self.setManualScaling(value, Qwt.QwtPlot.yLeft) )
            self.connect(self.scalingDlg[nTab].autoLeftY_pushButton,  SIGNAL('pressed()'), lambda : self.setAutoScaling(Qwt.QwtPlot.yLeft, self.scalingDlg[nTab].leftY_groupBox) )
            
            self.connect(self.scalingDlg[nTab].rightYmax_Counter, SIGNAL('valueChanged(double)'),  lambda value: self.updateManualAxisScaling(Qwt.QwtPlot.yRight) )
            self.connect(self.scalingDlg[nTab].rightYmin_Counter,  SIGNAL('valueChanged(double)'),  lambda value: self.updateManualAxisScaling(Qwt.QwtPlot.yRight) )
            self.connect(self.scalingDlg[nTab].rightY_groupBox, SIGNAL('toggled(bool)'),  lambda value: self.setManualScaling(value, Qwt.QwtPlot.yRight) )
            self.connect(self.scalingDlg[nTab].autoRightY_pushButton,  SIGNAL('pressed()'), lambda : self.setAutoScaling(Qwt.QwtPlot.yRight, self.scalingDlg[nTab].rightY_groupBox) )
            
            self.connect(self.scalingDlg[nTab].Xmax_Counter, SIGNAL('valueChanged(double)'),  lambda value: self.updateManualAxisScaling(Qwt.QwtPlot.xBottom) )
            self.connect(self.scalingDlg[nTab].Xmin_Counter,  SIGNAL('valueChanged(double)'),  lambda value: self.updateManualAxisScaling(Qwt.QwtPlot.xBottom) )
            self.connect(self.scalingDlg[nTab].X_groupBox, SIGNAL('toggled(bool)'),  lambda value: self.setManualScaling(value, Qwt.QwtPlot.xBottom) )
            self.connect(self.scalingDlg[nTab].autoX_pushButton,  SIGNAL('pressed()'), lambda : self.setAutoScaling(Qwt.QwtPlot.xBottom, self.scalingDlg[nTab].X_groupBox) )
        
        # Check is there is a manual scaling argument and update Counter. Set scale to counter values.
        # If there are no manual scaling arguments, the counter values should be equal to the autoscaling zoom base, resulting in auto scale.
        # This should work with replaceTab equal to None or to a replace Tab. In the later case, this is necessary to set scaling overriding the mask limits.
        if leftYmin:   self.scalingDlg[nTab].leftYmin_Counter.setValue(leftYmin)
        if leftYmax:  self.scalingDlg[nTab].leftYmax_Counter.setValue(leftYmax)
        if leftYmin or leftYmax: self.scalingDlg[nTab].leftY_groupBox.setChecked(True)
        else:                            self.scalingDlg[nTab].leftY_groupBox.setChecked(False)
        self.hPlot[nTab].setAxisScale(Qwt.QwtPlot.yLeft,  self.scalingDlg[nTab].leftYmin_Counter.value(),  self.scalingDlg[nTab].leftYmax_Counter.value())

        if rightYmin:  self.scalingDlg[nTab].rightYmin_Counter.setValue(rightYmin)
        if rightYmax: self.scalingDlg[nTab].rightYmax_Counter.setValue(rightYmax)
        if rightYmin or rightYmax: self.scalingDlg[nTab].rightY_groupBox.setChecked(True)
        else:                                self.scalingDlg[nTab].rightY_groupBox.setChecked(False)
        self.hPlot[nTab].setAxisScale(Qwt.QwtPlot.yRight, self.scalingDlg[nTab].rightYmin_Counter.value(), self.scalingDlg[nTab].rightYmax_Counter.value() )
        
        if Xmin:  self.scalingDlg[nTab].Xmin_Counter.setValue(Xmin)
        if Xmax: self.scalingDlg[nTab].Xmax_Counter.setValue(Xmax)
        if Xmin or Xmax:  self.scalingDlg[nTab].X_groupBox.setChecked(True)
        else:                    self.scalingDlg[nTab].X_groupBox.setChecked(False)
        self.hPlot[nTab].setAxisScale(Qwt.QwtPlot.xBottom, self.scalingDlg[nTab].Xmin_Counter.value(), self.scalingDlg[nTab].Xmax_Counter.value() )
            
        if replaceTab is None:
            # Create legend
            legend = Qwt.QwtLegend()
            legend.setItemMode(Qwt.QwtLegend.CheckableItem)
            
            hPlot.insertLegend(legend, Qwt.QwtPlot.RightLegend)
            self.legends.append(legend)
            self.connect(self.hPlot[nTab], SIGNAL('legendChecked(QwtPlotItem*, bool)'), self.showCurve)    
            updateLegend = False
        else:
            legend = self.legends[nTab]
            updateLegend = True # Trying to update legend size after replacing Tab data, but it does nothing
            
        # Legend layout
        legendLayout = legend.contentsWidget().children()[0]        
            
        # Set legend item checked / unchecked
        for family in leftYCurves + rightYCurves: family.setLegendItems(legend=legend, updateLegend=updateLegend)

        # Finally, plot with the data
        self.getPrecision(nTab)
        hPlot.replot()

        if replaceTab is None:
            # Store zoomer base, for later autoscaling. If replaceTab is not None, it has already been updated.
            self.autoScaleBase.append(AutoScaleZoomerBase(self.zoomerLeft[nTab].zoomBase(),  self.zoomerRight[nTab].zoomBase()))
        
        if replaceTab is None:
            # Set first tab active. It updates the markerMove knob since self.updateMoveKnobRange() is called by self.on_tabWidget_currentChanged().
            self.on_tabWidget_currentChanged(0)
        else: 
            # Update markerMove Knob limits, without changing the curent Tab
            self.updateMoveKnobRange()


    def addMask(self, nTab, XData,  YData,  axis, name, color,  type,  visible = True,  transpar = 64):
        """
        Add specification mask.
        In order to split the mask into separated parts, use numpy NaN in y-axis (numpy.nan), as show in the example at the end of this file.
        @param nTab = TabWidget page index.
        @param XData = x data (float numpy array).
        @param YData = y data (float numpy array).
        @param axis = 'left' or 'right' (string). Y-axis to which the YData values correspond.
        @param name = String to display in legend
        @param color = Qt.QColor instance, like Qt.red or Qt.blue
        @param type = 'upper' or 'lower' (string).
        @param visible = Visibility flag. If True the mask curve is initially visible, otherwise it is hidden. Default True.
        @param transpar = Alpha value for transparency (255=opaque, 0=transparent). Default 64.
        """
        
        # Get current axis scale, in order to restore it later
        yLeftScale = self.hPlot[nTab].axisScaleDiv(Qwt.QwtPlot.yLeft)
        yRightScale = self.hPlot[nTab].axisScaleDiv(Qwt.QwtPlot.yRight)
        xBottomScale = self.hPlot[nTab].axisScaleDiv(Qwt.QwtPlot.xBottom)
        leftYmin,  leftYmax,  rightYmin, rightYmax,  Xmin,  Xmax = yLeftScale.lowerBound(), yLeftScale.upperBound(), yRightScale.lowerBound(), yRightScale.upperBound(),  xBottomScale.lowerBound(), xBottomScale.upperBound()

        if      type == 'Lower': baseline = -1e7 # 1e8 or lager does not work
        elif   type == 'Upper': baseline =  1e7 # 1e8 or lager does not work
        
        assert type.lower() in ['upper',  'lower']
        assert axis.lower() in ['left',  'right']
        curve = Qwt.QwtPlotCurve(name)
#        curve.setRenderHint(Qwt.QwtPlotItem.RenderAntialiased)     # This was in an example
        semitrans = QColor(color)
        semitrans.setAlpha(transpar)
        curve.setPen(QPen(semitrans))
        curve.setBrush(semitrans)
        curve.setVisible(visible)
        curve.setBaseline( baseline)
        curve.attach(self.hPlot[nTab])
        if      axis.lower() == 'left': curve.setYAxis(Qwt.QwtPlot.yLeft)
        elif   axis.lower() == 'right': curve.setYAxis(Qwt.QwtPlot.yRight)
        
        # Set mask limits at baseline
        XData = np.concatenate(( np.array([ XData[0]] ), XData, np.array([ XData[-1]]) ))
        YData = np.concatenate(( np.array([ baseline ]), YData,  np.array([ baseline ]) ))
        
        curve.setData(XData, YData)
        legendItem = self.legends[nTab].find(curve)
        legendItem.setChecked(visible)   
        idenWidth = legendItem.identifierWidth() 
        legendItem.setIdentifierMode(Qwt.QwtLegendItem.ShowSymbol)
        symbol = Qwt.QwtSymbol( Qwt.QwtSymbol.Rect, semitrans, QPen(semitrans), QSize(2*idenWidth, 2*idenWidth) )
        legendItem.setSymbol(symbol)
        
        self.masks[nTab].append(curve)
        self.maskNames[nTab].append(name)
        
        # Restore axis scaling to previous manual mode
        self.hPlot[nTab].setAxisScale( Qwt.QwtPlot.yLeft, leftYmin,  leftYmax )
        self.hPlot[nTab].setAxisScale( Qwt.QwtPlot.yRight, rightYmin, rightYmax )
        self.hPlot[nTab].setAxisScale( Qwt.QwtPlot.xBottom, Xmin,  Xmax )
        
        self.hPlot[nTab].replot()
        
        
    def deleteMasks(self):
        """
        Delete all masks.
        Does nothing if there are no masks.
        """

        for nTab in range(self.Ntabs):
            for curve in self.masks[nTab]: curve.detach()
            self.masks[nTab] = []
            self.maskNames[nTab] = []
            self.hPlot[nTab].replot()
            
           
    
    def update(self,  nTab, XData, leftYData,  rightXData = None, rightYData=None,  textLabel=None, autoScaleBottomX=False, autoScaleLeftY=False, autoScaleRightY=False):
        """
        Update an existing plot with new data.
        The number of curves in the leftY and rightY axis must be the same as in the existing plot before update. 
        A change in the number of curves will result in an assertion error.
        @param nTab = TabWidget page index.
        @param XData = x-axis data (float numpy array or list of floats).
        @param leftYData = List of arrays to plot in left y-axis (list of numpy arrays or list of lists of floats).
        @param rightXData = x-axis data for right-axis curves (float numpy array or list of floats).
                            If equal to None, XData is used both for left-y and right-y axis data. Default None.
        @param rightYData = List of arrays to plot in right y-axis (list of numpy arrays or list of lists of floats). Default None.
        @param textLabel = Text to display as a label (string). Can be multiline. Default None.
        """
        
        # Get current axis scale, in order to restore it later
        yLeftScale = self.hPlot[nTab].axisScaleDiv(Qwt.QwtPlot.yLeft)
        yRightScale = self.hPlot[nTab].axisScaleDiv(Qwt.QwtPlot.yRight)
        xBottomScale = self.hPlot[nTab].axisScaleDiv(Qwt.QwtPlot.xBottom)
        leftYmax, leftYmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.yLeft)
        rightYmax, rightYmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.yRight)
        Xmax, Xmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.xBottom)
        
        # Update label
        if textLabel is not None:
            textLabel = textLabel.split('\n')
            assert len(textLabel) == len(self.label)
            for n in range(len(textLabel)): self.label[n].setText(textLabel[n])
        
        # Check if there is only one curve and it is not inside a curve list
        if type(XData[0]).__name__       not in ['ndarray',  'list']:  XData = [ XData ]
        
        if type(leftYData[0]).__name__  not in ['ndarray',  'list']:  leftYData = [ leftYData ]
        elif type(leftYData).__name__  == 'ndarray' and leftYData.ndim == 2: leftYData = [ leftYData ]    # A single curve family containing a 2-D array
        
        if rightXData is not None:
            if type(rightXData[0]).__name__  not in ['ndarray',  'list']:  rightXData = [ rightXData ]
            
        if rightYData is not None: 
            if type(rightYData[0]).__name__  not in ['ndarray',  'list']:  rightYData = [ rightYData ]
            elif type(rightYData).__name__  == 'ndarray' and rightYData.ndim == 2: rightYData = [ rightYData ]    # A single curve family containing a 2-D array
            
        dataXmin,  dataXmax,  dataLeftYmin, dataLeftYmax, dataRightYmin, dataRightYmax = np.inf, -np.inf, np.inf, -np.inf, np.inf, -np.inf
        
        assert len(self.leftYCurves[nTab]) == len(leftYData)
        for n in range(len(self.leftYCurves[nTab])):              
            # Check if x-axis data is the same for all curves
            xdata = XData[0] if len(XData)  == 1 else XData[n]
            
            # Update curve family and get data min and max
            CF = self.leftYCurves[nTab][n]
            cfXmin, cfXmax, cfYmin, cfYmax = CF.updateCurves(xdata, leftYData[n])
            
#           # Set axis max and min, based on curve data
#            if cfXmin < dataXmin:   dataXmin = cfXmin
#            if cfXmax > dataXmax: dataXmax = cfXmax
#            if cfYmin < dataLeftYmin:   dataLeftYmin = cfYmin
#            if cfYmax > dataLeftYmax: dataLeftYmax = cfYmax
            
        if rightYData is not None:
            assert len(self.rightYCurves[nTab]) == len(rightYData)
            for n in range(len(self.rightYCurves[nTab])):                  
                # Check if right-y curves use rightXData or XData
                tmp = XData if rightXData is None else rightXData
                
                # Check if x-axis data is the same for all curves
                xdata = tmp[0] if len(tmp)  == 1 else tmp[n]
                
                # Update curve family and get data min and max
                CF = self.rightYCurves[nTab][n]
                cfXmin, cfXmax, cfYmin, cfYmax = CF.updateCurves(xdata, rightYData[n])

#           # Set axis max and min, based on curve data
#                if cfXmin < dataXmin:   dataXmin = cfXmin
#                if cfXmax > dataXmax: dataXmax = cfXmax
#                if cfYmin < dataRightYmin:   dataRightYmin = cfYmin
#                if cfYmax > dataRightYmax: dataRightYmax = cfYmax
                
        # Detach masks to autoscale without the masks data, and save visibility data
        maskVisibility = []
        for curve in self.masks[nTab]: 
            maskVisibility.append(curve.isVisible())
            curve.detach()
        
        # Get autoscale min and max for new data, based on Qwt autoscale
        self.hPlot[nTab].setAxisAutoScale(Qwt.QwtPlot.yLeft)
        self.hPlot[nTab].setAxisAutoScale(Qwt.QwtPlot.yRight)
        self.hPlot[nTab].setAxisAutoScale(Qwt.QwtPlot.xBottom)
        self.hPlot[nTab].replot()
        dataLeftYmax, dataLeftYmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.yLeft)
        dataRightYmax, dataRightYmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.yRight)
        dataXmax, dataXmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.xBottom)
        
        # Attach masks
        for n, curve in enumerate(self.masks[nTab]): 
            curve.attach(self.hPlot[nTab])
            curve.setVisible(maskVisibility[n])
            legendItem = self.legends[nTab].find(curve)
            legendItem.setChecked(maskVisibility[n])
        
        # Update zoomer base with data from new curves
        leftZoomerBaseRect,  rightZoomerBaseRect = self.autoScaleBase[nTab].updateZoomerBase(dataXmin,  dataXmax,  dataLeftYmin, dataLeftYmax, dataRightYmin, dataRightYmax)
        if leftZoomerBaseRect   is not None: self.zoomerLeft[nTab].setZoomBase( leftZoomerBaseRect )
        if rightZoomerBaseRect is not None: self.zoomerRight[nTab].setZoomBase( rightZoomerBaseRect )
        
        # Update manual scaling counters with data from new curves
        self.scalingDlg[nTab].Xmin_Counter.setRange(dataXmin, dataXmax, self.scalingDlg[nTab].Xmin_Counter.step())
        self.scalingDlg[nTab].Xmax_Counter.setRange(dataXmin, dataXmax, self.scalingDlg[nTab].Xmax_Counter.step())
        if self.scalingDlg[nTab].Xmin_Counter.value() == dataXmax: self.scalingDlg[nTab].Xmin_Counter.setValue(dataXmin)
        if self.scalingDlg[nTab].Xmax_Counter.value() == dataXmin:self.scalingDlg[nTab].Xmax_Counter.setValue(dataXmax)
        
        self.scalingDlg[nTab].leftYmin_Counter.setRange(dataLeftYmin, dataLeftYmax, self.scalingDlg[nTab].leftYmin_Counter.step())
        self.scalingDlg[nTab].leftYmax_Counter.setRange(dataLeftYmin, dataLeftYmax, self.scalingDlg[nTab].leftYmax_Counter.step())
        if self.scalingDlg[nTab].leftYmin_Counter.value() == dataLeftYmax: self.scalingDlg[nTab].leftYmin_Counter.setValue(dataLeftYmin)
        if self.scalingDlg[nTab].leftYmax_Counter.value() == dataLeftYmin: self.scalingDlg[nTab].leftYmax_Counter.setValue(dataLeftYmax)
        
        self.scalingDlg[nTab].rightYmin_Counter.setRange(dataRightYmin, dataRightYmax, self.scalingDlg[nTab].rightYmin_Counter.step())
        self.scalingDlg[nTab].rightYmax_Counter.setRange(dataRightYmin, dataRightYmax, self.scalingDlg[nTab].rightYmax_Counter.step())
        if self.scalingDlg[nTab].rightYmin_Counter.value() == dataRightYmax: self.scalingDlg[nTab].rightYmin_Counter.setValue(dataRightYmin)
        if self.scalingDlg[nTab].rightYmax_Counter.value() == dataRightYmin: self.scalingDlg[nTab].rightYmax_Counter.setValue(dataRightYmax)
        
        # If autoscale, uncheck manual axis scaling, otherwise restore axis scaling to previous
        if autoScaleLeftY: 
            self.scalingDlg[nTab].leftY_groupBox.setChecked(False)
        else:    
            self.hPlot[nTab].setAxisScale( Qwt.QwtPlot.yLeft, leftYmin,  leftYmax )
        
        if autoScaleRightY: 
            self.scalingDlg[nTab].rightY_groupBox.setChecked(False)
        else: 
            self.hPlot[nTab].setAxisScale( Qwt.QwtPlot.yRight, rightYmin, rightYmax )
        
        if autoScaleBottomX: 
            self.scalingDlg[nTab].X_groupBox.setChecked(False)
        else: 
            self.hPlot[nTab].setAxisScale( Qwt.QwtPlot.xBottom, Xmin,  Xmax )
        
        # Move markers to new points position
        for marker in self.markers[nTab]: marker.updatePosition()
        
        self.hPlot[nTab].replot()
        
        
    def deleteLastMarker(self):
        """
        Deletes the last marker appended to current tab.
        """
        if not self.actionNewMarker.isChecked(): return  # Only delete last marker when New Marker action is checked
        
        nTab = self.tabWidget.currentIndex()
        
        if len(self.markers[nTab]) == 0:
            QMessageBox.warning(self, "WARNING",  'There are no markers to delete in this Tab.')
            return
        
        markerDel = self.markers[nTab][-1]
        
        message = 'Delete marker: %s' % markerDel.label().text()
        reply = QMessageBox.question(self, "%s - Delete Marker" % applicationName, message, QMessageBox.Yes | QMessageBox.No)
        
        if reply == QMessageBox.Yes:
            markerDel.detach()
            self.hPlot[nTab].replot()
            self.markers[nTab].remove(markerDel)
            del markerDel        
            
            
    def selectMarker(self,  nTab,  point):
        """
        Returns marker closer to the mouse cursor in screen coordinates, if distance is smaller than marker radius.
        @param nTab = Tab index
        @param point = Point coordinates (QPoint)
        @return markerSelected = QwtPlotMarker instance if marker is selected, None otherwise.
        """
        
        if len(self.markers[nTab]) == 0:
            QMessageBox.warning(self, "WARNING",  'There are no markers in this Tab.')
            return
            
        hPlot = self.hPlot[nTab]
        
        scX,  scY = point.x(),  point.y()
        
#        # This is necessary only when point is in graph coordinates and must be transformed to canvas pixel coordinates.
#        scX = hPlot.transform(Qwt.QwtPlot.xBottom, point.x()) 
#        scY = hPlot.transform(Qwt.QwtPlot.yLeft, point.y())

        axis = None
        
        minDist2 = np.inf       # Minimum distance squared
        maxLeftY, minLeftY = self.getScaleMaxMin(nTab, Qwt.QwtPlot.yLeft)
        maxRightY, minRightY = self.getScaleMaxMin(nTab, Qwt.QwtPlot.yRight)
        
        for marker in self.markers[nTab]:
            if not marker.curve.isVisible(): continue  # Do not select markers off non-visible curves
            
            if marker.axis == Qwt.QwtPlot.yLeft:
                if marker.y > maxLeftY or marker.y < minLeftY: continue   # Do not delete markers in non-visible points
                distY = hPlot.transform(Qwt.QwtPlot.yLeft, marker.y)  - scY  # Distance in screen pixels
            
            elif marker.axis == Qwt.QwtPlot.yRight:
                if marker.y > maxRightY or marker.y < minRightY: continue   # Do not delete markers in non-visible points
                distY = hPlot.transform(Qwt.QwtPlot.yRight, marker.y)  - scY  # Distance in screen pixels
                
            distX = hPlot.transform(Qwt.QwtPlot.xBottom, marker.x)  - scX  # Distance in screen pixels
            dist2 = distX**2 + distY**2
            if dist2 < minDist2:
                minDist2 = dist2       # Distance to closer marker squared
                markerSelected = marker
                xDel = marker.x
                yDel = marker.y
                
        markerRadius2 =  (markerSelected.symbol().size().height()**2 + markerSelected.symbol().size().width()**2) / 4     # Marker radius squared
        if minDist2 <= markerRadius2:
            return markerSelected
        else:
            QMessageBox.warning(self,  "WARNING",  "No marker selected")
            return None
        
        
    def deleteMarker(self,  point):
        """
        Deletes marker if a marker is selected.
        @param point = Point coordinates (QPoint)
        """
        if not self.actionDeleteMarker.isChecked(): return  # Only delete markers when Delete Marker action is checked
        
        nTab = self.tabWidget.currentIndex()
        hPlot = self.hPlot[nTab]
        
        # Test if a marker is selected
        markerDel = self.selectMarker(nTab,  point)
        
        if markerDel is not None:
            markerDel.setSymbol(markerDel.symbolDel)
            hPlot.replot()
        else:
            return
        
        message = 'Delete marker: %s' % markerDel.label().text()
        reply = QMessageBox.question(self, "%s - Delete Marker" % applicationName, message, QMessageBox.Yes | QMessageBox.No)
        
        if reply == QMessageBox.Yes:
            markerDel.detach()
            self.markers[nTab].remove(markerDel)
            del markerDel
        else:
            markerDel.setSymbol(markerDel.symbolNormal)
        
        hPlot.replot()
        
        
    def selectAndHighlightMarker(self,  point):
        """
        Selects a marker, highlights it and enables marker movement.
        @param point = Point coordinates (QPoint)
        """
        if not self.actionMoveMarker.isChecked(): return  # Only move markers when Move Marker action is checked
        
        nTab = self.tabWidget.currentIndex()
        markerMove = self.selectMarker(nTab,  point)
        self.hightlightMarker(nTab, markerMove)
        
        
    def hightlightMarker(self, nTab, markerMove):
        """
        Highlights marker and enables marker movement.
        @param nTab = Tab index
        @param markerMove = Marker to move (PlotMarker)
        """
        hPlot = self.hPlot[nTab]
        
        if markerMove is not None:
            markerMove.setSymbol(markerMove.symbolMove)
            if self.markerMove is not None: self.markerMove.setSymbol(markerMove.symbolNormal)
            self.markerMove = markerMove
            hPlot.replot()
            
            hPlot.canvas().setMouseTracking(False)
            self.draggingMode = True
            
            self.moveKnob.setVisible(True)
            self.moveKnob.setValue( markerMove.x )
            
            st = 'To move marker: Drag the mouse, use Left-arrow or Right-arrow keys, or use the bottom slider. Quit moving with Right-Click or Esc key.'
            self.statusBar.showMessage(st)
            hPlot.setStatusTip(st)
        else:
            return
            
    
    def deselectMarker(self):
        """
        Deselect marker and mouse dragging.
        """
        nTab = self.tabWidget.currentIndex()
        self.hPlot[nTab].canvas().setMouseTracking(True)        
        self.draggingMode = False
        
        if self.markerMove is not None: self.markerMove.setSymbol(self.markerMove.symbolNormal)
        self.markerMove = None
        self.moveKnob.setVisible(False)
        self.hPlot[nTab].replot()
        
        st = u'Left-click in a marker to move, Right-Click or Esc key to quit'
        self.statusBar.showMessage(st)
        self.hPlot[nTab].setStatusTip(st)
    
    
    def shiftMarker(self,  increment):
        """
        Shifts marker if a marker is selected.
        @param increment = Number of curve points to shift (int)
        """
        if not self.actionMoveMarker.isChecked(): return  # Only move markers when Move Marker action is checked
        if self.markerMove is None: return
        
        marker = self.markerMove

        # Check that marker index is not out of bounds
        N = len(marker.curve.data().xData())
        newindex = marker.index + increment
        if newindex < 0:     newindex= 0
        if newindex > N-1: newindex = N-1
        
        marker.index = newindex
        marker.updatePosition()
        self.moveKnob.setValue(marker.x)
        
        
    def dragMarker(self,  point):
        """
        Drags marker if a marker is selected.
        @param point = Point coordinates (QPoint)
        """
        if not self.actionMoveMarker.isChecked(): return  # Only move markers when Move Marker action is checked
        if self.markerMove is None: return
        
        nTab = self.tabWidget.currentIndex()
        hPlot = self.hPlot[nTab]
        value = hPlot.invTransform(Qwt.QwtPlot.xBottom, point.x())
        marker = self.markerMove
        
#        # Vectorized version
#        xCurve = np.array(marker.curve.data().xData())
#        marker.index = np.argmin(np.abs(xCurve-value))

        # Faster version
        curve = marker.curve
        N = len(curve.data().xData())
        maxValue = curve.x(N-1) 
        minValue = curve.x(0) 
        marker.index = int(round( (N-1) * (value - minValue) / (maxValue - minValue) ))
        
        marker.updatePosition()
        self.moveKnob.setValue(marker.x)
        hPlot.replot()
        
        
    def addMarker(self,  point):
        """
        Add marker at the curve point with is closer to the mouse cursor, if distance is smaller than marker radius.
        Both left-y and right-y axis curves are processed.
        @param point = Point coordinates (QPoint)
        """
        if not self.actionNewMarker.isChecked(): return  # Only add markers when New Marker action is checked
        
        nTab = self.tabWidget.currentIndex()
        hPlot = self.hPlot[nTab]
        
        scX,  scY = point.x(),  point.y()
        
#        # This is necessary only when point is in graph coordinates and must be transformed to canvas pixel coordinates.
#        scX = hPlot.transform(Qwt.QwtPlot.xBottom, point.x()) 
#        scY = hPlot.transform(Qwt.QwtPlot.yLeft, point.y())
         
        axis = None
        minDist = np.inf
        
        for CF in self.leftYCurves[nTab]:    # We cannot vectorize processing curves since the may have different number of points
            if not CF.visible: continue  # Do not add markers to non-visible curves
            
            for curve in CF.curveList:
                n, dist = curve.closestPoint(QPoint(scX, scY))
                if dist < minDist:
                    minDist = dist
                    curveMarker = curve
                    nMarker = n
                    axis= Qwt.QwtPlot.yLeft
                
        for CF in self.rightYCurves[nTab]:    # We cannot vectorize processing curves since the may have different number of points
            if not CF.visible: continue  # Do not add markers to non-visible curves
            
            for curve in CF.curveList:
                n, dist = curve.closestPoint(QPoint(scX, scY))
                if dist < minDist:
                    minDist = dist
                    curveMarker = curve
                    nMarker = n
                    axis= Qwt.QwtPlot.yRight
                
        if axis is None:
            QMessageBox.warning(self, "WARNING",  'There is not curve point within zoomed area. No marker added.')
            return
                    
        # Threshold to select a point (in pixels), equal to curve symbol radius
        distThreshold = self.curveSymbolSize/2 if self.curveSymbolSize/2 >=7 else 7
        if minDist > distThreshold:
            QMessageBox.warning(self, "WARNING",  'No curve point selected.')
            return
        
        Marker = PlotMarker(self, axis, nTab, curveMarker,  nMarker)
        Marker.attach(hPlot)
        hPlot.replot()
        self.markers[nTab].append(Marker)
        
        
    def closeEvent(self,  event):
        """
        Reimplementation of the window close function.
        Sets closed attribute to True.
        """
        self.closed = True
        event.accept()
        
        
    def getScaleMaxMin(self,  nTab, axis):
        """
        Get actual max and min values of scaling for a given axis.
        It is a separate function to encapsulate the different method names for qwt5.1 and qwt5.2
        @param nTab = Tab index
        @param axis = QwtPlot axis (Qwt.QwtPlot.yLeft, Qwt.QwtPlot.yRight, Qwt.QwtPlot.xBottom).
        @return maxScale = Maximum scale value.
        @return minScale = Minimum scale value.
        """
        
        try: # Attribute renamed from qwt5.1 to qwt5.2
            maxScale = self.hPlot[nTab].axisScaleDiv(axis).upperBound()
            minScale = self.hPlot[nTab].axisScaleDiv(axis).lowerBound()
        except AttributeError:
            maxScale = self.hPlot[nTab].axisScaleDiv(axis).hBound()
            minScale = self.hPlot[nTab].axisScaleDiv(axis).lBound()
            
        return maxScale,  minScale
        
        
    def getManualScale(self, nTab,  axis):
        """
        Get max and min manual scaling values for a given axis.
        @param nTab = tab index
        @param axis = QwtPlot axis (Qwt.QwtPlot.yLeft, Qwt.QwtPlot.yRight, Qwt.QwtPlot.xBottom).
        @return maxScale = Maximum scale value.
        @return minScale = Minimum scale value.
        """
        if axis is Qwt.QwtPlot.yLeft:
            maxScale = self.scalingDlg[nTab].leftYmax_Counter.value()
            minScale = self.scalingDlg[nTab].leftYmin_Counter.value()
        elif axis is Qwt.QwtPlot.yRight:
            maxScale = self.scalingDlg[nTab].rightYmax_Counter.value()
            minScale = self.scalingDlg[nTab].rightYmin_Counter.value()
        elif axis is Qwt.QwtPlot.xBottom:
            maxScale = self.scalingDlg[nTab].Xmax_Counter.value()
            minScale = self.scalingDlg[nTab].Xmin_Counter.value()
            
        return maxScale,  minScale
        
    
    def getPrecision(self,  nTab):
        """
        Compute the required number of significant digits of markers labels, depending on axis scaling.
        param nTab = Tab widget index.
        """
        Xmax,  Xmin           = self.getScaleMaxMin(nTab, Qwt.QwtPlot.xBottom)
        leftYmax,  leftYmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.yLeft)
        
        minPrec = 3     # Minimum number of significant digits
        
        if Xmax != 0.0:
            self.precX[nTab] = minPrec + int(ceil(log10(abs(Xmax))) - ceil(log10(abs(Xmax-Xmin))))
        else: 
            self.precX[nTab] = minPrec
            
        if leftYmax != 0.0:
            self.precY[nTab] = minPrec + int(ceil(log10(abs(leftYmax))) - ceil(log10(abs(leftYmax-leftYmin))))
        else:
            self.precY[nTab] = minPrec
            
        # Update precision in marker labels
        for marker in self.markers[nTab]: marker.updateLabel()
            
#        print 'prec =',  self.precX[nTab],  self.precY[nTab] 


    def  updateMoveKnobRange(self):
        """
        Update markerMove Knob limits according to actual xBottom scale
        """
        nTab = self.tabWidget.currentIndex()
        Xmax, Xmin = self.getScaleMaxMin(nTab, Qwt.QwtPlot.xBottom)
        self.moveKnob.setRange(Xmin,  Xmax)
        
        
    def changeCurveVisibility(self, nTab, leftYVisible = None, rightYVisible = None,  masksVisible = None  ):
        """
        Change visibility of curves of an existing plot.
        @param nTab         =   Tab index
        @param leftYVisible =   List of visibility flags corresponding to leftYData curves (list of bool or None values).
                                Instead of a list of flags, it can be a dictionary of the form { curveName: flag, ... }. Curves corresponding to a missing dictionary key are not changed.
                                If a flag is None, the visibility of the corresponding curve is not changed.
                                If the parameter is equal to None, leftYData curves visibility is not changed. Default None.
        @param rightYVisible =  List of visibility flags corresponding to rightYData curves (list of bool or None values).
                                Instead of a list of flags, it can be a dictionary of the form { curveName: flag, ... }. Curves corresponding to a missing dictionary key are not changed.
                                If a flag is None, the visibility of the corresponding curve is not changed.
                                If the parameter is equal to None, rightYData curves visibility is not changed. Default None.
        @param masksVisible  =  List of visibility flags corresponding to mask curves (list of bool or None values).
                                Instead of a list of flags, it can be a dictionary of the form { curveName: flag, ... }. Curves corresponding to a missing dictionary key are not changed.
                                If a flag is None, the visibility of the corresponding curve is not changed.
                                If the parameter is equal to None, mask curves visibility is not changed. Default None.
        """
        oldTab = self.tabWidget.currentIndex() 
        self.tabWidget.setCurrentIndex(nTab) 
        
        if type(leftYVisible).__name__ == 'list':
            for CF,  flag in zip( self.leftYCurves[nTab], leftYVisible ):
                if flag is None: continue
                CF.setVisible(flag)
                CF.legendItem.setChecked(flag)
                for k, v in self.setCurves[nTab].curvesCheckBoxesDict.items(): 
                    if v == CF: k.setChecked(flag)
        elif type(leftYVisible).__name__ == 'dict':
            for (name,  flag) in leftYVisible.items():
                if flag is None or name not in self.leftYNames[nTab]: continue
                CF = self.leftYCurves[nTab][self.leftYNames[nTab].index(name)]
                CF.setVisible(flag)
                CF.legendItem.setChecked(flag) 
                for k, v in self.setCurves[nTab].curvesCheckBoxesDict.items(): 
                    if v == CF: k.setChecked(flag)
                    
        if type(rightYVisible).__name__ == 'list':
            for CF,  flag in zip( self.rightYCurves[nTab], rightYVisible ):
                if flag is None: continue
                CF.setVisible(flag)
                CF.legendItem.setChecked(flag)
                for k, v in self.setCurves[nTab].curvesCheckBoxesDict.items(): 
                    if v == CF: k.setChecked(flag)
        elif type(rightYVisible).__name__ == 'dict':
            for (name,  flag) in rightYVisible.items():
                if flag is None or name not in self.rightYNames[nTab]: continue
                CF = self.rightYCurves[nTab][self.rightYNames[nTab].index(name)]
                CF.setVisible(flag)
                CF.legendItem.setChecked(flag) 
                for k, v in self.setCurves[nTab].curvesCheckBoxesDict.items(): 
                    if v == CF: k.setChecked(flag)
                    
        if type(masksVisible).__name__ == 'list':
            for curve,  flag in zip( self.masks[nTab], masksVisible ):
                if flag is None: continue
                curve.setVisible(flag)
                self.legends[nTab].find(curve).setChecked(flag)   
                for k, v in self.setCurves[nTab].curvesCheckBoxesDict.items(): 
                    if v == curve: k.setChecked(flag)
        elif type(masksVisible).__name__ == 'dict':
            for (name,  flag) in masksVisible.items():
                if flag is None or name not in self.maskNames[nTab]: continue
                curve = self.masks[nTab][self.maskNames[nTab].index(name)]
                curve.setVisible(flag)
                self.legends[nTab].find(curve).setChecked(flag) 
                for k, v in self.setCurves[nTab].curvesCheckBoxesDict.items(): 
                    if v == curve: k.setChecked(flag)


        self.hPlot[nTab].replot()
        self.tabWidget.setCurrentIndex(oldTab) 
        
        
    def autoScaleSomeAxis(self, nTab, autoScaleBottomX, autoScaleLeftY, autoScaleRightY):
        """
        Autoscale some axis. 
        This function is intended to be called after calling self.update when the programmer wishes to autoscale axis after update.
        @param nTab = Tab index where to autoscale axis.
        @param autoScaleX = Flag to indicate if bottom X axis must be autoscaled (True | False).
        @param autoScaleLeftY = Flag to indicate if left Y axis must be autoscaled (True | False).
        @param autoScaleRightY = Flag to indicate if right Y axis must be autoscaled (True | False).
        """
        zoomerBase = self.autoScaleBase[nTab]
        
        if autoScaleBottomX:
#            self.hPlot[nTab].setAxisAutoScale(Qwt.QwtPlot.xBottom) # There is no need to use Qwt autoscale, because autoscale values have already been stored in self.autoScaleBase[nTab]
            (axisMin,  axisMax) = ( zoomerBase.Xmin,  zoomerBase.Xmax )
            if axisMin != np.inf and axisMax != -np.inf:  self.hPlot[nTab].setAxisScale( Qwt.QwtPlot.xBottom, axisMin,  axisMax )
            self.scalingDlg[nTab].X_groupBox.setChecked(False)
        
        if autoScaleLeftY:
#            self.hPlot[nTab].setAxisAutoScale(Qwt.QwtPlot.yLeft) # There is no need to use Qwt autoscale, because autoscale values have already been stored in self.autoScaleBase[nTab]
            (axisMin,  axisMax) = ( zoomerBase.leftYmin,  zoomerBase.leftYmax )
            if axisMin != np.inf and axisMax != -np.inf:  self.hPlot[nTab].setAxisScale( Qwt.QwtPlot.yLeft, axisMin,  axisMax )
            self.scalingDlg[nTab].leftY_groupBox.setChecked(False)
        
        if autoScaleRightY:
#            self.hPlot[nTab].setAxisAutoScale(Qwt.QwtPlot.yRight) # There is no need to use Qwt autoscale, because autoscale values have already been stored in self.autoScaleBase[nTab]
            (axisMin,  axisMax) = ( zoomerBase.rightYmin,  zoomerBase.rightYmax )
            if axisMin != np.inf and axisMax != -np.inf:  self.hPlot[nTab].setAxisScale( Qwt.QwtPlot.yRight, axisMin,  axisMax ) 
            self.scalingDlg[nTab].rightY_groupBox.setChecked(False)
        
        self.getPrecision(nTab)
        self.hPlot[nTab].replot()

    ##@{
    # @name Functions automatically executed when the user interacts with the GUI 
        
        
    @pyqtSignature("double")
    def on_moveKnob_valueChanged(self, value):
        """
        This function is automatically executed when the user moves the moveKnob.
        This slot must be manually connected to valueChanged, since the QwtKnob widget was not created in QtDesigner.
        @param value = Knob value (double)
        """
        if not self.actionMoveMarker.isChecked(): return
        
        nTab = self.tabWidget.currentIndex()
        
#        # Vectorized version
#        x = np.array(self.markerMove.curve.data().xData())
#        self.markerMove.index = np.argmin(np.abs(x-value))

        # Faster version
        curve = self.markerMove.curve
        N = len(curve.data().xData())
        maxValue = curve.x(N-1) 
        minValue = curve.x(0) 
        self.markerMove.index = int(round( (N-1) * (value - minValue) / (maxValue - minValue) ))
        
        self.markerMove.updatePosition()
        self.hPlot[nTab].replot()
            
    
    def setCurveVisibility(self, value):
        """
        Change curve visibility by using the EditCurvesDlg dialog. 
        self.setCurves.curvesCheckBoxesDict is used to find the QwtPlotCurve corresponding to the self.setCurves checkBox that sent the signal.
        @param value = State of the checkBox that sent the signal.
        """
        on = value != 0
        nTab = self.tabWidget.currentIndex()
        CF = self.setCurves[nTab].curvesCheckBoxesDict[self.sender()]
        CF.setVisible(on)
        
        # Update markers visibility
        for curve in CF.curveList:
            for marker in self.markers[nTab]:
                if marker.curve == curve: marker.setVisible(on)
        
        # Update legend item check state
        CF.legendItem.setChecked(on)
        
        self.hPlot[nTab].replot()


    def setCurveWidth(self,  value):
        """
        Change curve pen width by using the EditCurvesDlg dialog. 
        self.setCurves.curvesCheckBoxesDict is used to find the QwtPlotCurve corresponding to the self.setCurves spinBox that sent the signal.
        @param value = Value of the spinBox that sent the signal.
        """
        nTab = self.tabWidget.currentIndex()
        CF = self.setCurves[nTab].curvesSpinBoxesDict[self.sender()]
        pen = CF.pen   
        pen.setWidth(value)
        CF.setPen(pen)
        self.hPlot[nTab].replot()


    def setCurveColor(self,  checked):
        """
        Change curve pen color by using the EditCurvesDlg dialog. 
        self.setCurves.curvesPushButtonsDict is used to find the QwtPlotCurve corresponding to the self.setCurves pushButton that sent the signal.
        @param checked = Check state of the pushButton that sent the signal.
        """
        nTab = self.tabWidget.currentIndex()
        CF = self.setCurves[nTab].curvesPushButtonsDict[self.sender()]
        pen = CF.pen
        newColor = QColorDialog().getColor(pen.color())
        palette = self.sender().palette()
        palette.setColor(QPalette.Button,  newColor)
        self.sender().setPalette(palette)
        pen.setColor(newColor)
        CF.setPen(pen)
        self.hPlot[nTab].replot()
        
        
    def showCurve(self, item, on):
        """
        Change curve visibility by checking legend items. 
        @param value = State of the legendItem that sent the signal.
        """
        nTab = self.tabWidget.currentIndex()
        item.setVisible(on)
        
        # Update EditCurvesDlg checkBox
        for k, CF in self.setCurves[nTab].curvesCheckBoxesDict.items(): 
            if CF.curveList[0] == item: k.setChecked(on)

        self.hPlot[nTab].replot()
        
        
    def updateManualAxisScaling(self, axis):
        """
        Change scaling of a given axis according to manual scaling control values.
        @param axis = QwtPlot axis (Qwt.QwtPlot.yLeft, Qwt.QwtPlot.yRight, Qwt.QwtPlot.xBottom).
        """
        nTab = self.tabWidget.currentIndex()
        maxScale,  minScale = self.getManualScale(nTab,  axis)
        self.hPlot[nTab].setAxisScale(axis,  minScale,  maxScale)
        self.getPrecision(nTab)
        self.hPlot[nTab].replot()
        
        
    def setManualScaling(self, value, axis):
        """
        Set manaul/auto axis scaling.
        @param value = Value of axis manual scaling group CheckBox (int). 0 -> False (auto), 2 -> True (manual).
        @param axis = QwtPlot axis (Qwt.QwtPlot.yLeft, Qwt.QwtPlot.yRight, Qwt.QwtPlot.xBottom).
        """
        nTab = self.tabWidget.currentIndex()
        
        if value > 0:
            self.updateManualAxisScaling(axis)
        # Do not try to autoscale if value<0 (user uncheck groupBox).
        # This would result in autoscaling when user clicks zoom toolbar icon, without right-click,
        # because on_actionZoom_toggled() sets XXXXgroupBox.setChecked(False)
        # which is connected to setManualScaling(False, axis)
            
        self.hPlot[nTab].replot()
            

    def setAutoScaling(self,  axis, groupBox):
        """ Axis auto scake.
        This function is automatically executed by the GUI when the user presses the 'Auto' buttom of some axis.
        @param axis = QwtPlot axis (Qwt.QwtPlot.yLeft, Qwt.QwtPlot.yRight, Qwt.QwtPlot.xBottom)
        @param groupBox = Manual scaling groupBox, that will be unchecked.
        """   
        nTab = self.tabWidget.currentIndex()
        groupBox.setChecked(False)  # Disable manual scaling
        
        zoomerBase = self.autoScaleBase[nTab]
        if axis == Qwt.QwtPlot.yLeft:       (axisMin,  axisMax) = ( zoomerBase.leftYmin,  zoomerBase.leftYmax )
        if axis == Qwt.QwtPlot.yRight:    (axisMin,  axisMax) = ( zoomerBase.rightYmin,  zoomerBase.rightYmax )
        if axis == Qwt.QwtPlot.xBottom: (axisMin,  axisMax) = ( zoomerBase.Xmin,  zoomerBase.Xmax )

        if axisMin != np.inf and axisMax != -np.inf:  self.hPlot[nTab].setAxisScale( axis, axisMin,  axisMax )        
        self.getPrecision(nTab)
        self.hPlot[nTab].replot()
        
    
    @pyqtSignature("int")
    def on_tabWidget_currentChanged(self,  index):
        """ 
        Disable main window actions actionZoom and actionCurves.
        This function is automatically executed by the GUI when the user changes the current tab page.
        """
        nTab = self.tabWidget.currentIndex()
        
        if nTab >= len(self.hPlot): return  # The function was automatically called when the tab was added and the plots have not been created yet
        
        # Hide previous  setCurvesDlg   
        if self.setCurvesCurrent is not None: self.setCurvesCurrent.hide()
        
        # Change scale Dlg
        if self.scalingDlgCurrent is not None: 
            geom = self.scalingDlgCurrent.geometry()
            self.scalingDlgCurrent.hide()
            self.on_actionScaleAxis_toggled(True)
            self.scalingDlgCurrent.setGeometry(geom)
            
        # Update markerMove Knob limits
        self.updateMoveKnobRange()
        
        self.on_actionZoom_toggled(False)
        self.on_actionNewMarker_toggled(False)
        self.on_actionDeleteMarker_toggled(False)
        self.on_actionMoveMarker_toggled(False)
        self.actionCurves.setChecked(False)
        
        if self.setCurves[nTab] is not None:
            self.on_actionCurves_toggled(False)
            self.actionCurves.setEnabled(True)
        else:
            self.actionCurves.setEnabled(False)
            
    
    @pyqtSignature("bool")
    def on_actionPrint_triggered(self,  checked):
        """ 
        Print [S] parameters plot.
        This function is automatically executed by the GUI when the user triggers the 'Print' action.
        """
        nTab = self.tabWidget.currentIndex()
        
        printer = QPrinter(QPrinter.HighResolution)

        printer.setOrientation(QPrinter.Landscape)
        printer.setColorMode(QPrinter.Color)

        dialog = QPrintDialog(printer)
        if dialog.exec_():
            filter = PrintFilter()
            if (QPrinter.GrayScale == printer.colorMode()):
                filter.setOptions(QwtPlotPrintFilter.PrintAll & ~QwtPlotPrintFilter.PrintBackground | QwtPlotPrintFilter.PrintFrameWithScales)
            self.hPlot[nTab].print_(printer, filter)


    @pyqtSignature("bool")
    def on_actionAbout_triggered(self,  checked):
        """ 
        Display About dialog.
        This function is automatically executed by the GUI when the user triggers the 'About' action.
        The information shown is read from the file docstring.
        """
        st =  __doc__
        st = st.replace('@package',  'Package:')
        st = st.replace('@author',  'Authors:')
        st = st.replace('@date',  'Date:')
        st = st.replace('@version',  'Version:')

        st = u"""<b>%s</b> 
        <p>%s
        <p>Software versions:
        <p>Python %s - Qt %s - PyQt %s - QWT %s on %s %s""" % (applicationName,  st,  platform.python_version(),QT_VERSION_STR, PYQT_VERSION_STR,  Qwt.QWT_VERSION_STR, platform.system(),  platform.release()) 
        
        QMessageBox.about(self, "About %s" % applicationName, st)
        
    
    @pyqtSignature("bool")
    def on_actionPDF_triggered(self,  checked):
        """ 
        Save [S] parameters plot to PDF file.
        This function is automatically executed by the GUI when the user triggers the 'PDF' action.
        """
        nTab = self.tabWidget.currentIndex()
        
        if QT_VERSION > 0x040100:
            fileName = QFileDialog.getSaveFileName(self, 
                            'Export File Name', 'dbPlot.pdf', 'PDF Documents (*.pdf)')

        if not fileName.isEmpty():
            printer = QPrinter()
            printer.setOutputFormat(QPrinter.PdfFormat)
            printer.setOrientation(QPrinter.Landscape)
            printer.setOutputFileName(fileName)

            printer.setCreator('lossyfiltersgui')
            self.hPlot[nTab].print_(printer)
            
    
    @pyqtSignature("bool")
    def on_actionZoom_toggled(self,  checked):
        """ Enable or disable zoom.
        This function is automatically executed by the GUI when the user toggles the 'Zoom' button.
        """
        nTab = self.tabWidget.currentIndex()
        if nTab >= len(self.hPlot): return  # The function was automatically called when the tab was added and the plots have not been created yet
        
        #checked = self.actionZoom.isChecked()
        self.zoomerLeft[nTab].setEnabled(checked)        
        self.zoomerRight[nTab].setEnabled(checked)
        
        if checked:     
            st = u'Drag rectangle to zoom in, center-click to zoom back, shift-center-click to zoom forward, right-click to autoscale all axis'
            self.on_actionScaleAxis_toggled(False)
            self.actionScaleAxis.setChecked(False)
            
            # Disable adding and deleting markers
            self.actionNewMarker.setChecked(False)
            self.actionDeleteMarker.setChecked(False)
            self.actionMoveMarker.setChecked(False)
            
            # Disable manual scaling
            self.scalingDlg[nTab].leftY_groupBox.setChecked(False)
            self.scalingDlg[nTab].rightY_groupBox.setChecked(False)
            self.scalingDlg[nTab].X_groupBox.setChecked(False)
        else:
            self.actionZoom.setChecked(False)
            st = u'Use toolbar buttons to zoom, add or delete markers'
            
        if not self.actionNewMarker.isChecked():    # Do not change status bar when this function is executed by on_actionNewMarker_toggled(True)
            self.statusBar.showMessage(st)
            self.hPlot[nTab].setStatusTip(st)
        
        
    @pyqtSignature("bool")
    def on_actionNewMarker_toggled(self,  checked):
        """ Allow adding markers with mouse click.
        This function is automatically executed by the GUI when the user toggles the 'New Marker' button.
        """    
        nTab = self.tabWidget.currentIndex()
        
        if checked:
            st = u"Left-Click in a curve point to add a marker, Right-Click in background or press 'Del' key to delete last marker "
            
            # Disable zoom and deleting markers
            self.on_actionZoom_toggled(False)
            self.actionDeleteMarker.setChecked(False)
            self.actionMoveMarker.setChecked(False)
            
            self.pickerLeft[nTab].setTrackerPen(QPen(Qt.blue))
            
            symbol = Qwt.QwtSymbol(Qwt.QwtSymbol.Ellipse,QBrush(Qt.yellow), QPen(Qt.red, 2),  QSize(5, 5)  )
            for CF in self.leftYCurves[nTab] + self.rightYCurves[nTab]:
                pen = CF.pen
                self.curveSymbolSize = pen.width()*5 if pen.width() != 0 else 5
                symbol.setPen(pen)
                symbol.setSize(self.curveSymbolSize , self.curveSymbolSize )
                CF.setSymbol(symbol)
            
        else:
            st = u'Use toolbar buttons to zoom, add, move or delete markers'
            self.actionNewMarker.setChecked(False)
            
            self.pickerLeft[nTab].setTrackerPen(QPen(Qt.black))
            
            symbol = Qwt.QwtSymbol(Qwt.QwtSymbol.NoSymbol,QBrush(Qt.yellow), QPen(Qt.red, 2),  QSize(5, 5)  )
            
            for CF in self.leftYCurves[nTab]:
                CF.setSymbol(symbol)
            
            for CF in self.rightYCurves[nTab]:
                CF.setSymbol(symbol)
                
        self.statusBar.showMessage(st)
        self.hPlot[nTab].setStatusTip(st)
        self.hPlot[nTab].replot()
        
        
    @pyqtSignature("bool")
    def on_actionDeleteMarker_toggled(self,  checked):
        """ Delete markers with mouse click.
        This function is automatically executed by the GUI when the user toggles the 'Delete Marker' button.
        """    
        nTab = self.tabWidget.currentIndex()
        
        if checked:
            
            # Disable zoom and adding markers
            self.on_actionZoom_toggled(False)
            self.actionNewMarker.setChecked(False)
            self.actionMoveMarker.setChecked(False)
        
            if len(self.markers[nTab]) == 0:
                QMessageBox.warning(self, "WARNING",  'There are no markers to delete in this Tab.')
                self.on_actionDeleteMarker_toggled(False)
                return
            
            self.pickerLeft[nTab].setTrackerMode(Qwt.QwtPicker.AlwaysOff)
            st = u'Left-click in a marker to delete, Right-Click or Esc key to quit.'
        else:
            self.pickerLeft[nTab].setTrackerMode(Qwt.QwtPicker.AlwaysOn)
            self.pickerLeft[nTab].setTrackerPen(QPen(Qt.black))
            st = u'Use toolbar buttons to zoom, add, move or delete markers'
            self.actionDeleteMarker.setChecked(False)
                
        self.statusBar.showMessage(st)
        self.hPlot[nTab].setStatusTip(st)    
        
        
    @pyqtSignature("bool")
    def on_actionMoveMarker_toggled(self,  checked):
        """ Move marker.
        This function is automatically executed by the GUI when the user toggles the 'Move Marker' button.
        """    
        nTab = self.tabWidget.currentIndex()
        
        if checked:            
            # Disable zoom and adding markers
            self.on_actionZoom_toggled(False)
            self.actionNewMarker.setChecked(False)
            self.actionDeleteMarker.setChecked(False)
        
            if len(self.markers[nTab]) == 0:
                QMessageBox.warning(self, "WARNING",  'There are no markers to move in this Tab.')
                self.on_actionMoveMarker_toggled(False)
                return
            
            self.pickerLeft[nTab].setTrackerMode(Qwt.QwtPicker.AlwaysOff)
            st = u'Left-click in a marker to move, Right-Click or Esc key to quit'
            
            # If there is only one marker, select it automatically for movement
            if len(self.markers[nTab]) == 1: self.hightlightMarker(nTab, self.markers[nTab][0])
            
        else:            
            self.deselectMarker()
            
            self.pickerLeft[nTab].setTrackerMode(Qwt.QwtPicker.AlwaysOn)
            self.pickerLeft[nTab].setTrackerPen(QPen(Qt.black))

            self.moveKnob.setVisible(False)
            self.actionMoveMarker.setChecked(False)
            st = u'Use toolbar buttons to zoom, add, move or delete markers'
                
        self.statusBar.showMessage(st)
        self.hPlot[nTab].setStatusTip(st)
            
            
    @pyqtSignature("bool")
    def on_actionCurves_toggled(self,  checked):
        """ Set curves visibility.
        This function is automatically executed by the GUI when the user toggles the 'Set curves visibility' button.
        """   
        nTab = self.tabWidget.currentIndex()
        if nTab >= len(self.hPlot): return  # The function was automatically called when the tab was added and the plots have not been created yet
        if self.setCurves[nTab] is None: return  # The function was automatically called for a tab page that has no EditCurvesDlg
        
        if checked:
            self.setCurves[nTab].show()
            self.setCurves[nTab].raise_()
            self.setCurves[nTab].activateWindow()
            self.setCurvesCurrent = self.setCurves[nTab]
        else:
            self.setCurves[nTab].hide()
            
            
    @pyqtSignature("bool")
    def on_actionScaleAxis_toggled(self,  checked):
        """ Axis scaling.
        This function is automatically executed by the GUI when the user toggles the 'Axis scale' button.
        """   
        nTab = self.tabWidget.currentIndex()
        if nTab >= len(self.hPlot): return  # The function was automatically called when the tab was added and the plots have not been created yet
        if self.scalingDlg[nTab] is None: return  # The function was automatically called for a tab page that has no EditCurvesDlg
        
        if checked:
            self.scalingDlg[nTab].show()
            self.scalingDlg[nTab].raise_()
            self.scalingDlg[nTab].activateWindow()
            self.scalingDlgCurrent = self.scalingDlg[nTab]        
            self.on_actionZoom_toggled(False)
        else:
            self.scalingDlg[nTab].hide()
            self.scalingDlgCurrent = None
        
    
    ##@}
        
        
class AxisScalingDlg(QDialog,  Ui_dbplot_scaling.Ui_scaling_Dialog):
    """
    GUI dialog to contain axis scaling controls.
        @param parent = Parent widget, in this case is mainWindow. Default None.
    """
    def __init__(self,  parent = None):
        """
        Constructor: Creates PyQt4.QtGui.Qdialog window from QtDesigner.
        """
        super(AxisScalingDlg, self).__init__(parent)
        self.setupUi(self)
        self.mainWindow = parent
        
    def closeEvent(self,  event):
        """
        Reimplementation of the window close function.
        Sets scalingDlgCurrent attribute to None and hide the window.
        """
        nTab = self.mainWindow.tabWidget.currentIndex()
        self.mainWindow.scalingDlg[nTab].hide()
        self.mainWindow.scalingDlgCurrent = None
        self.mainWindow.actionScaleAxis.setChecked(False)
        # Do not close with, just hiding is enough event.accept()


class CurveFamily(object):
    """
    Data structure containing info about properties of a family of curves and the curve list.
    A curve family is a list of curves sharing the same properties.
    The Y data for a curve family is defined in the columns of a 2-D numpy array.
    
    The class attributes and methods will not be documented, because the names are self-explanatory and the code is trivial.
    """
    
    def __init__(self, hPlot, name,  xdata, ydata,  axis, visible, color,  width):
        """
        Class constructor.
        @param hPlot
        @param name
        @param xdata
        @param ydata
        @param axis
        @param visible
        @param color
        @param width
        """
        self.hPlot = hPlot
        self.name = name
        self.axis = axis
        self.visible = visible
        self.Ncurves = 0    # For the moment, it is empty
        self.legend = None  # For the moment, there is no legend
        self.legendItem = None
        
        if width is not None: self.pen = QPen(color, width)
        else:                          self.pen = QPen(color)
        
        self.newCurves(xdata, ydata)
            
        
    def newCurves(self, xdata, ydata):
        """
        Create new list of curves.
        @param xdata        
        @param ydata        
        """
        # Check if there is a family of curves in a 2-D array
        if type(ydata).__name__  == 'ndarray' and ydata.ndim == 2:
            self.Ncurves = ydata.shape[1]
        else:
            self.Ncurves = 1
            
        self.curveList = []
        for m in range(self.Ncurves):
            curve = Qwt.QwtPlotCurve(self.name)
            curve.attach(self.hPlot)
            if self.Ncurves == 1: curve.setData(xdata, ydata)
            else: curve.setData(xdata, ydata[:, m])
            curve.setPen(self.pen)
            curve.setYAxis(self.axis)
            curve.setVisible(self.visible)
            self.curveList.append(curve)
            
        
    def deleteCurves(self):
        """
        Delete all curves.
        """
        for curve in self.curveList:
            curve.detach()
            del curve
        self.curveList = []
        self.Ncurves = 0
    
    
    def updateCurves(self, xdata,  ydata):
        """
        Update all curves with new data.
        @param xdata
        @param ydata
        """
        # Check if there is a family of curves in a 2-D array
        if type(ydata).__name__  == 'ndarray' and ydata.ndim == 2: Ncurves = ydata.shape[1]
        else: Ncurves = 1
        
        # Max and min coordinates for this curve family. Necessary to update zoomerBase in DbPlot.update()
        cfXmin,  cfXmax,  cfYmin, cfYmax = np.inf, -np.inf, np.inf, -np.inf
        
        if Ncurves != self.Ncurves:     # We cannot update the data directly, we have to delete and set the curves again.
            self.deleteCurves()
            self.newCurves(xdata, ydata)
            self.setLegendItems()
            dataUpdated = True
        else:
            dataUpdated = False
            
        for m in range(Ncurves):    # We have to run this loop even if the data is already updated, in order to find min and max coordinates
            if Ncurves == 1: y = ydata
            else:                  y = ydata[:, m]
            if not dataUpdated: self.curveList[m].setData(xdata, y)
            
            if min(xdata) < cfXmin:   cfXmin = min(xdata)
            if max(xdata) > cfXmax: cfXmax = max(xdata)
            if min(y) < cfYmin:   cfYmin = min(y)
            if max(y) > cfYmax: cfYmax = max(y)
                
        return cfXmin,  cfXmax,  cfYmin, cfYmax
        
    
    def setPen(self,  pen):
        """
        Set QPen for all curves.
        @param pen        
        """
        self.pen = pen
        for curve in self.curveList:  curve.setPen(self.pen)
        
        
    def setVisible(self,  visible):
        """
        Set visibility of all curves.
        @param visible = Visibility flag (Bool)
        """
        self.visible = visible
        for curve in self.curveList:  curve.setVisible(self.visible)
        
        
    def setSymbol(self,  symbol):
        """
        Set symbol for all curves.
        @param symbol
        """
        self.symbol = symbol
        for curve in self.curveList:  curve.setSymbol(self.symbol)
        
        
    def setLegendItems(self,  legend = None, updateLegend=False):
        """
        After creating a legend for the QWtPlot, this method will assign the QwtLegendItem of the first curve
        to self.legendItem, and remove the other QwtLegendItem's from the QwtLegend layout.
        @param legend = QwtLegend in QWtPlot. If equal to None, the curves are being updated and the QwtLegend handle is already stored in self.legend .
                        Default None.
        @param updateLegend = Call curve.updateLegend() for the 1st curve. Default False.
        """
        if legend is not None: self.legend = legend
        
        # The legendItem for the curve family is the legendItem of the 1st curve.
        # The legendItem's for the other curves will be removed from the layout and deleted.
        self.legendItem = self.legend.find(self.curveList[0])
        
        for n,  curve in enumerate(self.curveList):
            item = self.legend.find(curve)
            
            if n==0:    # 1st curve
                idenWidth = item.identifierWidth()
                item.setIdentifierWidth(2*idenWidth)
                item.setChecked(self.visible)
                if updateLegend: curve.updateLegend(self.legend) 
            else:   # Remove the legendItem form the layout and delete it
                item.setVisible(False)  # IMPORTANT: It does not work without this statement, although it may seem irrelevant.
                layout = self.legend.contentsWidget().children()[0]
                layout.removeWidget(item)
                del item
                

class EditCurvesDlg(QDialog):
    """
    Dialog to select visible curves.
    """

    def __init__(self,  leftYCurves,  rightYCurves,  parent=None):
        """
        Create dialog to select visible curves.
        @param leftYCurves  = List of QwtPlotCurves in left y-axis.
        @param rightYCurves = List of QwtPlotCurves in right y-axis.
        @param parent       = Parent window (default None).
        """
        super(EditCurvesDlg, self).__init__(parent)
        
        ##
        # Handle to the dbplot main window.
        self.mainWindow = parent
        
        # #
        # Dictionary with curves corresponding to each checkBox widget
        self.curvesCheckBoxesDict = {}
        
        ##
        #Dictionary with curves corresponding to each spinBox widget
        self.curvesSpinBoxesDict = {}        
        
        ##
        #Dictionary with curves corresponding to each pushBotton widget
        self.curvesPushButtonsDict = {}        
        
        nTab = self.mainWindow.tabWidget.currentIndex()
        
        grid = QGridLayout()
        
        if len(leftYCurves) > 0:
            grid.addWidget(QLabel("<b>Left-Y curves</b>"), 0, 0, 1, -1,  Qt.AlignHCenter)
            grid.addWidget(QLabel("Curve"), 1, 0,  Qt.AlignHCenter)
            grid.addWidget(QLabel("Width"), 1, 1,  Qt.AlignHCenter)
            grid.addWidget(QLabel("Color"), 1, 2,  Qt.AlignHCenter)
            
            for n in range(len(leftYCurves)):
                CF = leftYCurves[n]
                checkBox = QCheckBox(CF.name.replace('<sub>', '').replace('</sub>', ''))
                checkBox.setChecked(CF.visible)
                self.curvesCheckBoxesDict[checkBox] = CF
                self.connect(checkBox,  SIGNAL("stateChanged(int)"), self.mainWindow.setCurveVisibility )
                grid.addWidget(checkBox, n+2, 0)
                spinBox = QSpinBox()
                spinBox.setMinimum(0)
                spinBox.setValue(CF.pen.width())
                self.curvesSpinBoxesDict[spinBox] = CF
                self.connect(spinBox, SIGNAL("valueChanged(int)"), self.mainWindow.setCurveWidth )
                grid.addWidget(spinBox,  n+2,  1)
                pushButton = QPushButton()
                palette = pushButton.palette()
                palette.setColor(QPalette.Button,  CF.pen.color())
                pushButton.setPalette(palette)
                self.curvesPushButtonsDict[pushButton] = CF
                self.connect(pushButton, SIGNAL("clicked(bool)"), self.mainWindow.setCurveColor )
                grid.addWidget(pushButton,  n+2,  2)
        
        nLeft = n
        
        if len(rightYCurves) > 0:
            grid.addWidget(QLabel(" "), n+3, 0, 1, -1,  Qt.AlignHCenter)        # Empty row
            grid.addWidget(QLabel("<b>Right-Y curves</b>"), n+4, 0, 1, -1,  Qt.AlignHCenter)
            grid.addWidget(QLabel("Curve"), n+5, 0,  Qt.AlignHCenter)
            grid.addWidget(QLabel("Width"), n+5, 1,  Qt.AlignHCenter)
            grid.addWidget(QLabel("Color"), n+5, 2,  Qt.AlignHCenter)
            
            for n in range(len(rightYCurves)):
                CF = rightYCurves[n]
                Hlayout = QHBoxLayout()
                checkBox = QCheckBox(CF.name.replace('<sub>', '').replace('</sub>', ''))
                checkBox.setChecked(CF.visible)
                self.curvesCheckBoxesDict[checkBox] = CF
                self.connect(checkBox,  SIGNAL("stateChanged(int)"), self.mainWindow.setCurveVisibility )
                grid.addWidget(checkBox, nLeft+6+n, 0)
                spinBox = QSpinBox()
                spinBox.setMinimum(0)
                spinBox.setValue(CF.pen.width())
                self.curvesSpinBoxesDict[spinBox] = CF
                self.connect(spinBox, SIGNAL("valueChanged(int)"), self.mainWindow.setCurveWidth )
                grid.addWidget(spinBox, nLeft+6+n,  1)
                pushButton = QPushButton()
                palette = pushButton.palette()
                palette.setColor(QPalette.Button,  CF.pen.color())
                pushButton.setPalette(palette)
                self.curvesPushButtonsDict[pushButton] = CF
                self.connect(pushButton, SIGNAL("clicked(bool)"), self.mainWindow.setCurveColor )
                grid.addWidget(pushButton, nLeft+6+n,  2)
                
        self.setLayout(grid)
        self.setSizePolicy(QSizePolicy(QSizePolicy.Fixed,  QSizePolicy.Fixed))
        self.setWindowTitle("Curves")
        self.setToolTip("Select visible curves")
        
    def closeEvent(self,  event):
        """
        Reimplementation of the dialog close function.
        Uncheck the toolbar action.
        """
        self.mainWindow.actionCurves.setChecked(False)
        event.accept()
        
        
class MyPicker(Qwt.QwtPlotPicker):
    """
    Class derived from Qwt.QwtPlotPicker to allow reimplementation of Qwt.QwtPlotPicker.trackerText()
    """
    def __init__(self, dbplot,  nTab, *args):
        """
        Derived class constructor. 
        Saves dbplot and nTab as class attributes.
        @param dbplot = DbPlot class instance containing the Tab where this QwtPlotPicker exists.
        @param nTab = Tab widget index containing this QwtPlotPicker.
        """
        super(MyPicker, self).__init__(*args)
        self.dbplot = dbplot
        self.nTab = nTab
        
    def trackerText(self,  point):
        """
        Reimplementation of tracker text function, to customize tracker appereance
        """
        # Check if its necessary to convert from pixel to axis coordinates.
        # This is necessary since this function is overloaded in Qwt
        # QwtText   trackerText (const QPoint &) const 
        # QwtText   trackerText (const QwtDoublePoint &) const
        if type(point).__name__ == "QPoint": point = self.invTransform(point)
        st = '%.*g%s, %.*g%s' % (self.dbplot.precX[self.nTab], point.x(), self.dbplot.Xunits[self.nTab], self.dbplot.precY[self.nTab], point.y(),  self.dbplot.leftYunits[self.nTab])
        return Qwt.QwtText(st)


class CanvasEventFilter(QObject):
    """
    Event filter for the canvas. 
    
    This filter is necessary to pick up events that are not handled by the QwtPlotPicker class, like mouse dragging.
    
    It would be enough (and better practice) to reimplement mousePressEvent() and keyPressEvent() of the QwtPlotCanvas class, 
    but we if we create a derived class from QwtPlotCanvas it is not possible to set an instance of this class to the canvas of a QwtPlot object
    since there is not setPlotCanvas() method in QwtPlot class.
    
    To handle left mouse clicks it is enough to create a mouseClicked() slot and connect to 'selected' signal of QwtPlotPicker class:
    self.connect(pickerLeft, SIGNAL('selected(QwtDoublePoint)'), self.mouseClicked )
    but since we have to implement this filter anyway in order to handle mouse dragging, we will handle also all the other events here.
    
    Reimplementation of mousePressEvent() from QwtPlot does not work, because we need the mouse coordinates obtained from event.pos() to be relative to the 
    QwtPlotCanvas widget, not to the QwtPlot, in order to get the graph coordinates using QwtPlot.invTransform(). 
    
    Implementing this filter is essentially what the QwtPlotPicker class does, but unfortunately it does not support mouse dragging, only mouse selection.
    """
    
    def __init__(self, dbplot, hPlot):
        """
        Class constructor. 
        @param dbplot = DbPlot class instance, necessary to check the value of some dbPlot flags.
        @param hPlot = The parent widget. Must be a Qwt.QwtPlot class instance, since non-processed events will be sent to Qwt.QwtPlot.eventFilter(self, object, event).
        """
        # Initialise parent
        QObject.__init__(self, hPlot)         # This was in pyQwt example
        #super(CanvasEventFilter, self).__init__(*args)     # ESTA NO FUNCIONA (y es aparentemente lo mismo)
        
        self.dbplot = dbplot
        
    def eventFilter(self, object, event):
        """
        The event filter.
        When the event has been processed, for mouse movements the return value is not True, 
        in order to allow the event to be processed also by other widgets like QwtPlotPicker.
        """
            
        if event.type() == QEvent.MouseButtonPress:
            
            if event.button() == Qt.RightButton:
                
                if self.dbplot.actionNewMarker.isChecked(): 
                    if len(self.dbplot.markers[self.dbplot.tabWidget.currentIndex()]) > 0:  # If there are markers, delete the last one.
                        self.dbplot.deleteLastMarker()
                    else:   # Quit
                        self.dbplot.on_actionNewMarker_toggled(False)
                    return True
                    
                if self.dbplot.actionDeleteMarker.isChecked(): 
                    self.dbplot.on_actionDeleteMarker_toggled(False)
                    return True
                    
                if self.dbplot.actionMoveMarker.isChecked(): 
                    if self.dbplot.draggingMode: self.dbplot.deselectMarker()
                    else: self.dbplot.on_actionMoveMarker_toggled(False)
                    return True                
                return True
            
            if event.button() == Qt.LeftButton:
                
                if not self.dbplot.draggingMode:        # Do not process LeftButton clicks when dragging mouse.
                    if     self.dbplot.actionNewMarker.isChecked():       self.dbplot.addMarker(event.pos())
                    elif  self.dbplot.actionDeleteMarker.isChecked():   self.dbplot.deleteMarker(event.pos())
                    elif  self.dbplot.actionMoveMarker.isChecked():     self.dbplot.selectAndHighlightMarker(event.pos())
                # Do not return True in order to allow QwtPlotPicker to process the event
                
#        elif event.type() == QEvent.MouseButtonRelease:
#            pass
        
        elif event.type() == QEvent.MouseMove:
            if self.dbplot.draggingMode and self.dbplot.markerMove is not None: 
                self.dbplot.dragMarker(event.pos())
        
        elif event.type() == QEvent.KeyPress:
            
            if event.key() == Qt.Key_Delete:
                if self.dbplot.actionNewMarker.isChecked(): self.dbplot.deleteLastMarker()
                return True
                
            elif event.key() == Qt.Key_Escape:
                if self.dbplot.actionNewMarker.isChecked(): 
                    self.dbplot.on_actionNewMarker_toggled(False)
                    return True
                if self.dbplot.actionDeleteMarker.isChecked(): 
                    self.dbplot.on_actionDeleteMarker_toggled(False)
                    return True
                if self.dbplot.actionMoveMarker.isChecked(): 
                    if self.dbplot.draggingMode: self.dbplot.deselectMarker()
                    else: self.dbplot.on_actionMoveMarker_toggled(False)
                    return True
                
            elif event.key() == Qt.Key_Right and self.dbplot.actionMoveMarker.isChecked(): 
                self.dbplot.shiftMarker(1)
                return True
                
            elif event.key() == Qt.Key_Left and self.dbplot.actionMoveMarker.isChecked(): 
                self.dbplot.shiftMarker(-1)
                return True
        
        return Qwt.QwtPlot.eventFilter(self, object, event)
        

class  PlotMarker(Qwt.QwtPlotMarker):
    """
    Class for markers.
    """
    
    def __init__(self, dbplot,  axis,  nTab, curve,  index):
        """
        Class constructor. Gets the data from dbplot zoomers base in nTab.
        @param dbplot = DbPlot class instance, to which this marker belongs.
        @param Yaxis = y-axis to use Qwt.QwtPlot.yLeft or Qwt.QwtPlot.yRight.
        @param nTab = Tab widget index.
        @param curve = QwtPlotCurve instance, to which this marker is linked.
        @param index = Index of curve point.
        """
        
        ##
        # DbPlot class instance, to which this marker belongs.
        self.dbplot = dbplot
        
        ##
        # y-axis to use Qwt.QwtPlot.yLeft or Qwt.QwtPlot.yRight.
        self.axis = axis
        
        ##
        # Tab widget index
        self.nTab = nTab
        
        ##
        # QwtPlotCurve instance, to which this marker is linked
        self.curve = curve
        
        ##
        # Index of curve x-data closest to x
        self.index = index
        
        ##
        # Marker x-coordinate
        self.x = curve.x(index)
        
        ##
        # Marker y-coordinate
        self.y = curve.y(index)
        
        ##
        # QwtPlot class instance where this marker is attached to.
        self.hPlot = dbplot.hPlot[nTab]
        
        super(PlotMarker, self).__init__()
        self.setYAxis(axis)
        self.setValue(self.x, self.y)
        self.updateLabel()
        
        ##
        # QwtSymbol class instance to use when the marker state is normal
        self.symbolNormal = Qwt.QwtSymbol(Qwt.QwtSymbol.Cross,QBrush(Qt.black), QPen(Qt.black, 2),  QSize(10, 10)  )
        
        ##
        # QwtSymbol class instance to use when the marker state is selected for move
        self.symbolMove = Qwt.QwtSymbol(Qwt.QwtSymbol.Cross,QBrush(Qt.red), QPen(Qt.red, 3),  QSize(15,  15)  )
        
        ##
        # QwtSymbol class instance to use when the marker state is selected for deletion
        self.symbolDel = Qwt.QwtSymbol(Qwt.QwtSymbol.UTriangle,QBrush(Qt.yellow), QPen(Qt.red, 2),  QSize(10, 10)  )
        
        self.setSymbol(self.symbolNormal)
        
        
    def updateLabel(self):
        """
        Update marker label according to required precision.
        """
        if    self.axis == Qwt.QwtPlot.yLeft:
            units = self.dbplot.leftYunits[self.nTab]
        elif self.axis == Qwt.QwtPlot.yRight:
            units = self.dbplot.rightYunits[self.nTab]
            
        text = Qwt.QwtText( "%.*g%s, %.*g%s" % (self.dbplot.precX[self.nTab],  self.x,  self.dbplot.Xunits[self.nTab],  self.dbplot.precY[self.nTab], self.y, units ) )
#        fn = self.fontInfo().family()        
#        text.setFont(QFont(fn, 12, QFont.Bold))
        self.setLabel(text)
        self.setLabelAlignment(Qt.AlignRight | Qt.AlignBottom)
        
        
    def updatePosition(self):
        """
        Update marker position after a curve update with DbPlot.update().
        """
        self.x = self.curve.x(self.index)
        self.y = self.curve.y(self.index)
        self.setValue(self.x, self.y)
        self.updateLabel()
            

class  AutoScaleZoomerBase(object):
    """
    Class that stores zoomer base.
    It is useul to autoscale to the zoomer base, not using the method setAxisAutoScale, which would scale accounting the masks.
    """
    
    def __init__(self,  leftZoomerBaseRect,  rightZoomerBaseRect):
        """
        Class constructor. Gets the data from dbplot zoomers base in nTab.
        @param leftZoomerBaseRect  = Left zoomer base rectangle (QRectF).
        @param rightZoomerBaseRect = Right zoomer base rectangle (QRectF).
        """
        
        ##
        # Left zoomer base.
        self.leftZoomerBaseRect = leftZoomerBaseRect        
        
        ##
        # Right zoomer base for autoscale,.
        self.rightZoomerBaseRect = rightZoomerBaseRect        
        
        ##
        # Zoomer base for autoscale, Xmin.
        self.Xmin = self.leftZoomerBaseRect.x()
        
        ##
        # Zoomer base for autoscale, Xmax.
        self.Xmax = self.leftZoomerBaseRect.x() + self.leftZoomerBaseRect.width()
        
        ##
        # Zoomer base for autoscale, LeftYmin.
        self.leftYmin = self.leftZoomerBaseRect.y()
        
        ##
        # Zoomer base for autoscale, LeftYmax.
        self.leftYmax = self.leftZoomerBaseRect.y() + self.leftZoomerBaseRect.height()
        
        ##
        # Zoomer base for autoscale, RightYmin.
        self.rightYmin = self.rightZoomerBaseRect.y()
        
        ##
        # Zoomer base for autoscale, RightYmax. 
        self.rightYmax = self.rightZoomerBaseRect.y() + self.rightZoomerBaseRect.height()
        

    def updateZoomerBase(self,  Xmin, Xmax, leftYmin, leftYmax, rightYmin, rightYmax):
        """
        Given a set of max and min values for all axis, computes and returns the zoomer base rectangles.
        The attributes of AutoScaleZoomerBase class instance are updated accordingly.
        @return leftZoomerBaseRect  = Left zoomer base rectangle (QRectF).
        @return rightZoomerBaseRect = Right zoomer base rectangle (QRectF).
        """
        
        self.Xmin = Xmin
        self.Xmax = Xmax
        self.leftYmin = leftYmin
        self.leftYmax = leftYmax
        self.rightYmin = rightYmin
        self.rightYmax = rightYmax
        self.leftZoomerBaseRect    = QRectF(Xmin, leftYmin, Xmax - Xmin, leftYmax - leftYmin )          if max(np.abs( [Xmin, leftYmin, Xmax, leftYmax] ))     < np.inf   else None
        self.rightZoomerBaseRect =  QRectF(Xmin, rightYmin, Xmax - Xmin, rightYmax - rightYmin )   if max(np.abs( [Xmin, rightYmin, Xmax, rightYmax] )) < np.inf   else None
        
        return self.leftZoomerBaseRect,  self.rightZoomerBaseRect


class PrintFilter(Qwt.QwtPlotPrintFilter):
    """
    Filter to transform color of some plot items and fontsize when printing.
    """
    def __init__(self):
        """
        Constructor. The class is derived from Qwt5.QwtPlotPrintFilter.
        """
        Qwt.QwtPlotPrintFilter.__init__(self)

    def color(self, c, item):
        """
        Modifies a color for printing.
        @param c = Color to be modified (QColor).
        @param item = Type of item where the color belongs.
        @return Modified color (QColor).
        """
        if not (self.options() & Qwt.QwtPlotPrintFilter.CanvasBackground):
            if item == Qwt.QwtPlotPrintFilter.MajorGrid:
                return Qt.darkGray
            elif item == Qwt.QwtPlotPrintFilter.MinorGrid:
                return Qt.gray
        if item == Qwt.QwtPlotPrintFilter.CanvasBackground:
            return Qt.white
#        elif item == Qwt.QwtPlotPrintFilter.Title:
#            return Qt.red
#        elif item == Qwt.QwtPlotPrintFilter.AxisScale:
#            return Qt.green
#        elif item == Qwt.QwtPlotPrintFilter.AxisTitle:
#            return Qt.blue
        return c

    def font(self, f, item):
        """
        Modifies a font for printing.
        @param f = Font to be modified (QFont).
        @param item = Type of item where the font belongs.
        @return All fonts are returned unmodified.
        """
        result = QFont(f)
#        result.setPointSize(int(f.pointSize()*1.25))
        return result
        

# Main program for testing purposes #

if __name__ == '__main__':
    # QApplication class instance, containing our application
    app = QApplication(sys.argv)
    ui = DbPlot( [0,  1,  2,  3], [101,  52,  803,  500],  XLabel = 'y-axis' ,  leftYLabel = 'Left axis', leftYNames = ['Left-axis data'],   
                rightYData = [0.3,  -0.5,  0.2,  0.4],  rightYLabel = 'Right axis', rightYNames = ['Right-axis data'], rightYColors = [ Qt.red], 
                Xunits = 'GHz',  leftYunits = 'dBm', 
                windowTitle = 'Example',  tabTitle = 'Single-curve', title = '1 curve (list)', textLabel = 'A two-line\ntext label')
                
    N = 100
    x = np.arange(np.pi/N, np.pi, np.pi / N )
    ly1 = 10*np.log10(np.sin(x))
    ly2 = 10*np.log10(np.abs(np.cos(x)))    
    ly2[ly2 < -10] = -10
    ry1 = 10*np.log10(np.sinh(x))
    ry2 = 10*np.log10(np.cosh(x))
    ui.addTab( 180*x/np.pi,  [ ly1,  ly2],  XLabel = 'x' ,   leftYNames = ['Sin(x)',  '|Cos(x)|'],  leftYLabel = 'Trigonometric values (dB)',
                rightYData = [ ry1,  ry2 ],  rightYLabel = 'Hyperbolic values (dB)', rightYNames = ['Sinh(x)',  'Cosh(x)'], rightYColors = [ Qt.cyan,  Qt.green],
                Xunits = 'rad',  leftYunits = 'dB',  leftWidth = [2,  2], rightWidth = [2, 2], 
                tabTitle = 'Multi-curve', title = '2 curves (numpy arrays) with masks',  leftYVisible = [True,  False],  rightYVisible = [False,  True])
    
    mask =  np.array( [ (20,  -4),  (60,  -2),  (85, -0.5), (90,  -0.4),  (95,  -0.5),  (120,  -2),  (160,  -4) ] )
    ui.addMask(nTab = 1, XData = mask[:, 0],  YData = mask[:, 1],  axis = 'left', name = 'Lower Mask',  color = Qt.blue,  type = 'Lower',  transpar = 32 )
    
    mask = np.array( [ (40,  -2),  (80,  -5), (80,  np.nan),  (100,  np.nan),  (100,  -5),  (140,  -2) ] )
    ui.addMask(nTab = 1, XData = mask[:, 0],  YData = mask[:, 1],  axis = 'left', name = 'Upper Mask',  color = Qt.red,  type = 'Upper',   transpar = 32)
    
    mask = np.array([ (50,  0),  (100,  4),  (150,  8) ])
    ui.addMask(nTab = 1, XData = mask[:, 0],  YData = mask[:, 1],  axis = 'right', name = 'Right lower Mask',  color = Qt.green,  type = 'Lower',   transpar = 64)
    
#    ui.changeCurveVisibility( nTab = 1, leftYVisible = [True,  False], rightYVisible = None,  masksVisible = [None,  True,  False,  ]  )
                
    ui.addTab(  XData = [[0,  1,  2,  3],  [0.5,  1.5,  2.5,  3.5,  4.5]],  leftYData = [  [20,  10,  1,  0],   [3,  5,  -2,  4,  1]  ], leftYNames = ['width=2',  'width=3'],  leftYLabel = 'Left axis',  
                        rightXData = [2.5,  3.5,  4.5,  5.5],  rightYData = [ 25,  35,  45,  55],  rightYNames = 'width=4',  rightYLabel = 'Right-y axis', 
                        leftWidth = [2,  3], rightWidth = 4, 
                        tabTitle = 'Multi-axis', title = 'Example with different x-axis data for each curve, and manual scaling',  Xmin = 1,  Xmax = 5,  leftYmin = 0,  rightYmax = 50)
                        
    ui.addTab(  XData = np.array([0,  1,   2,  3]),  leftYData = [ np.array([5,  10, 1,  0]), np.array([[6,  9,  3,  2],  [6.2, 9.2, 3.2, 2.2],  [6.4, 9.4, 3.4, 2.4]]).T ], leftYNames = ['1-D array Left', '2-D array Left' ],  leftYLabel = 'Left axis',  
                        rightXData = np.array([0.5,  1.5, 2.5,  3.5]),  rightYData = np.array([[ 25,  5,  15,  35], [25.4,  5.4,  15.4,  35.4], [25.8,  5.8,  15.8,  35.8]]).T,  rightYNames = '2-D array Right',  rightYLabel = 'Right-y axis', 
                        tabTitle = 'Numpy arrays', title = 'Example with data in 2-D numpy arrays')
                        
    ui.addTab(  XData = np.array([0,  1,   2,  3]),  leftYData = np.array([[6,  9,  3,  2],  [6.2, 9.2, 3.2, 2.2],  [6.4, 9.4, 3.4, 2.4]]).T, leftYNames = '2-D array Left' ,  leftYLabel = 'Left axis',  
                        tabTitle = 'Left-Y only', title = 'Example with data in left-Y axis only')
                            
    ui.addTab(  XData = [0,  1],  leftYData = [ [1,  2],  [2,  3],  [3,  4],  [4,  5], [5,  6],  [6,  7],  [7,  8],  [8,  9]  ],  
                        tabTitle = 'Multi-color', title = 'Example with many curves of different colors', 
                        leftYVisible = [True,  True,  True, True,  True,  True,  True, True ])
                        
    ui.update(5, XData = [0,  10], leftYData = [ [10.35,  20],  [20,  30],  [30,  40],  [40,  50], [50,  60],  [60,  70],  [70,  80],  [80,  90.14]  ])
    ui.autoScaleSomeAxis(5, autoScaleBottomX = True, autoScaleLeftY = True, autoScaleRightY = False)
    
    ui.addTab( 1.1*180*x/np.pi,  [ 1.1*ly1,  1.1*ly2],  XLabel = 'x' ,   leftYNames = ['Sin(x) 1.1',  '|Cos(x)| 1.1'],  leftYLabel = 'Trigonometric values (dB) 1.1',
                rightYData = [ 1.1*ry1,  1.1*ry2 ],  rightYLabel = 'Hyperbolic values (dB) 1.1', rightYNames = ['Sinh(x) 1.1',  'Cosh(x) 1.1'], rightYColors = [ Qt.cyan,  Qt.green],
                Xunits = 'rad 1.1',  leftYunits = 'dB 1.1',  leftWidth = [2,  2], rightWidth = [2, 2], 
                tabTitle = 'Multi-curve 1.1', title = '2 curves (numpy arrays) with masks 1.1',  leftYVisible = [True,  False],  rightYVisible = [False,  True], 
                replaceTab = 1)
    
    ui.addTab(  XData = [[0,  10,  20,  30,  40],  [5,  15,  25,  35,  45,  55]],  leftYData = [  [200,  100,  10,  0,  50],   [30,  50,  -20,  40,  10, -30]  ], leftYNames = ['width10=2',  'width10=3'],  leftYLabel = 'Left axis 10',  
                        rightXData = [25,  35,  45,  55,  65],  rightYData = [ 2500,  3500,  4500,  5500,  4500],  rightYNames = 'width10=4',  rightYLabel = 'Right-y axis 10', 
                        leftWidth = [2,  3], rightWidth = 4, 
                        tabTitle = 'Multi-axis 10', title = 'Example of replaceTab',  Xmin = 10,  Xmax = 60,  leftYmin = 0,  rightYmax = 5000, 
                        replaceTab = 2)
                        
    ui.addTab(  XData = 10*np.array([0,  1,   2,  3]),  leftYData = [ 10*np.array([5,  10, 1,  0]), 10*np.array([[6,  9,  3,  2],  [6.2, 9.2, 3.2, 2.2],  [6.4, 9.4, 3.4, 2.4]]).T ], leftYNames = ['1-D array Left 10', '2-D array Left 10' ],  leftYLabel = 'Left axis 10',  
                        rightXData = 10*np.array([0.5,  1.5, 2.5,  3.5]),  rightYData = 10*np.array([[ 25,  5,  15,  35], [25.4,  5.4,  15.4,  35.4], [25.8,  5.8,  15.8,  35.8]]).T,  rightYNames = '2-D array Right 10',  rightYLabel = 'Right-y axis 10', 
                        tabTitle = 'Numpy arrays 10', title = 'Example of replace Tab with 2-D numpy arrays', 
                        replaceTab = 3)

    app.exec_()
