Esempio n. 1
0
 def setupPlottingWidgets(self):
     self.plotconfigs = (None, dict(shade=0.35, linewidth=1, color="g"))
     self.rtPlotter = RtPlotter(rangeSelectionCallback=self.plotMz)
     self.rtPlotter.setMinimumSize(300, 100)
     self.mzPlotter = MzPlotter()
     self.mzPlotter.setMinimumSize(300, 100)
     pol = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
     pol.setVerticalStretch(5)
     self.rtPlotter.widget.setSizePolicy(pol)
     self.mzPlotter.widget.setSizePolicy(pol)
Esempio n. 2
0
 def setupPlottingWidgets(self):
     self.plotconfigs = (None, dict(shade=0.35, linewidth=1, color="g") )
     self.rtPlotter = RtPlotter(rangeSelectionCallback=self.plotMz)
     self.rtPlotter.setMinimumSize(300, 100)
     self.mzPlotter = MzPlotter()
     self.mzPlotter.setMinimumSize(300, 100)
     pol = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
     pol.setVerticalStretch(5)
     self.rtPlotter.widget.setSizePolicy(pol)
     self.mzPlotter.widget.setSizePolicy(pol)
Esempio n. 3
0
class TableExplorer(QDialog):
    def __init__(self, tables, offerAbortOption):
        QDialog.__init__(self)

        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setWindowFlags(Qt.Window)

        self.offerAbortOption = offerAbortOption

        self.models = [TableModel(table, self) for table in tables]
        self.model = None
        self.tableView = None

        self.currentRowIdx = -1
        self.hadFeatures = None
        self.wasIntegrated = None

        self.setupWidgets()
        self.setupLayout()
        self.connectSignals()

        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.setSizePolicy(sizePolicy)
        self.setSizeGripEnabled(True)

        self.setupViewForTable(0)

    def setupWidgets(self):
        self.setupMenuBar()
        self.setupTableViews()
        self.setupPlottingWidgets()
        self.chooseSpectrum = QComboBox()
        self.setupIntegrationWidgets()
        if self.offerAbortOption:
            self.setupAcceptButtons()

    def setupMenuBar(self):
        self.menubar = QMenuBar(self)
        menu = self.buildEditMenu()
        self.menubar.addMenu(menu)
        self.chooseTableActions = []
        if len(self.models) > 1:
            menu = self.buildChooseTableMenu()
            self.menubar.addMenu(menu)

    def buildEditMenu(self):
        self.undoAction = QAction("Undo", self)
        self.undoAction.setShortcut(QKeySequence("Ctrl+Z"))
        self.redoAction = QAction("Redo", self)
        self.redoAction.setShortcut(QKeySequence("Ctrl+Y"))
        menu = QMenu("Edit", self.menubar)
        menu.addAction(self.undoAction)
        menu.addAction(self.redoAction)
        return menu

    def setupTableViews(self):
        self.tableViews = []
        for i, model in enumerate(self.models):
            self.tableViews.append(self.setupTableViewFor(model))

    def setupTableViewFor(self, model):
        tableView = QTableView(self)

        @protect_signal_handler
        def handler(evt, view=tableView, model=model, self=self):
            if not view.isSortingEnabled():
                view.setSortingEnabled(True)
                view.resizeColumnsToContents()
                model.emptyActionStack()
                self.updateMenubar()

        tableView.showEvent = handler

        tableView.setModel(model)
        tableView.horizontalHeader().setResizeMode(QHeaderView.Interactive)
        pol = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        tableView.setSizePolicy(pol)
        tableView.setVisible(False)
        # before filling the table, disabling sorting accelerates table
        # construction, sorting is enabled in TableView.showEvent, which is
        # called after construction
        tableView.setSortingEnabled(False)
        return tableView

    def buildChooseTableMenu(self):
        menu = QMenu("Choose Table", self.menubar)
        for i, model in enumerate(self.models):
            action = QAction(" [%d]: %s" % (i, model.getTitle()), self)
            menu.addAction(action)
            self.chooseTableActions.append(action)
        return menu

    def setupPlottingWidgets(self):
        self.plotconfigs = (None, dict(shade=0.35, linewidth=1, color="g"))
        self.rtPlotter = RtPlotter(rangeSelectionCallback=self.plotMz)
        self.rtPlotter.setMinimumSize(300, 100)
        self.mzPlotter = MzPlotter()
        self.mzPlotter.setMinimumSize(300, 100)
        pol = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        pol.setVerticalStretch(5)
        self.rtPlotter.widget.setSizePolicy(pol)
        self.mzPlotter.widget.setSizePolicy(pol)

    def setupIntegrationWidgets(self):
        self.intLabel = QLabel("Integration")
        self.chooseIntMethod = QComboBox()
        import configs
        for name, _ in configs.peakIntegrators:
            self.chooseIntMethod.addItem(name)

        self.choosePostfix = QComboBox()

        self.reintegrateButton = QPushButton()
        self.reintegrateButton.setText("Integrate")

    def setupAcceptButtons(self):
        self.okButton = QPushButton("Ok")
        self.abortButton = QPushButton("Abort")
        self.result = 1  # default for closing

    def setupLayout(self):
        vlayout = QVBoxLayout()
        self.setLayout(vlayout)

        vsplitter = QSplitter()
        vsplitter.setOrientation(Qt.Vertical)
        vsplitter.setOpaqueResize(False)

        vsplitter.addWidget(self.menubar)
        vsplitter.addWidget(self.layoutWidgetsAboveTable())
        vsplitter.addWidget(self.chooseSpectrum)

        for view in self.tableViews:
            vsplitter.addWidget(view)
        vlayout.addWidget(vsplitter)

        if self.offerAbortOption:
            vlayout.addLayout(self.layoutButtons())

    def layoutButtons(self):
        hbox = QHBoxLayout()
        hbox.addWidget(self.abortButton)
        hbox.setAlignment(self.abortButton, Qt.AlignVCenter)
        hbox.addWidget(self.okButton)
        hbox.setAlignment(self.okButton, Qt.AlignVCenter)
        return hbox

    def layoutWidgetsAboveTable(self):
        hsplitter = QSplitter()
        hsplitter.setOpaqueResize(False)
        hsplitter.addWidget(self.rtPlotter.widget)

        integrationLayout = QVBoxLayout()
        integrationLayout.setSpacing(10)
        integrationLayout.setMargin(5)
        integrationLayout.addWidget(self.intLabel)
        integrationLayout.addWidget(self.chooseIntMethod)
        integrationLayout.addWidget(self.choosePostfix)
        integrationLayout.addWidget(self.reintegrateButton)
        integrationLayout.addStretch()
        integrationLayout.setAlignment(self.intLabel, Qt.AlignTop)
        integrationLayout.setAlignment(self.chooseIntMethod, Qt.AlignTop)
        integrationLayout.setAlignment(self.reintegrateButton, Qt.AlignTop)

        self.integrationFrame = QFrame()
        self.integrationFrame.setLayout(integrationLayout)

        hsplitter.addWidget(self.integrationFrame)
        hsplitter.addWidget(self.mzPlotter.widget)
        return hsplitter

    def setupModelDependendLook(self):
        hasFeatures = self.model.hasFeatures()
        isIntegrated = self.model.isIntegrated()
        self.hasFeatures = hasFeatures
        self.isIntegrated = isIntegrated

        self.setWindowTitle(self.model.getTitle())

        self.chooseSpectrum.setVisible(False)
        if hasFeatures != self.hadFeatures:
            self.setPlotVisibility(hasFeatures)
            self.hadFeatures = hasFeatures
            # default: invisible, only activated when row clicked and
            # level >= 2 spectra are available
        if isIntegrated != self.wasIntegrated:
            self.setIntegrationPanelVisiblity(isIntegrated)
            self.wasIntegrated = isIntegrated
        if hasFeatures:
            self.rtPlotter.setEnabled(True)
            self.resetPlots()

    def setPlotVisibility(self, doShow):
        self.rtPlotter.widget.setVisible(doShow)
        self.mzPlotter.widget.setVisible(doShow)

    def resetPlots(self):
        self.rtPlotter.reset()
        self.mzPlotter.reset()

    def setIntegrationPanelVisiblity(self, doShow):
        self.integrationFrame.setVisible(doShow)

    @protect_signal_handler
    def handleClick(self, index, model):
        content = model.data(index)
        if isUrl(content):
            QDesktopServices.openUrl(QUrl(content))

    def connectSignals(self):
        for i, action in enumerate(self.chooseTableActions):

            handler = lambda i=i: self.setupViewForTable(i)
            handler = protect_signal_handler(handler)
            self.menubar.connect(action, SIGNAL("triggered()"), handler)

        for view in self.tableViews:
            vh = view.verticalHeader()
            vh.setContextMenuPolicy(Qt.CustomContextMenu)
            self.connect(vh, SIGNAL("customContextMenuRequested(QPoint)"),\
                         self.openContextMenu)

            self.connect(vh, SIGNAL("sectionClicked(int)"), self.rowClicked)

            model = view.model()
            handler = lambda idx, model=model: self.handleClick(idx, model)
            handler = protect_signal_handler(handler)
            self.connect(view, SIGNAL("clicked(QModelIndex)"), handler)

        self.connect(self.reintegrateButton, SIGNAL("clicked()"),
                     self.doIntegrate)

        self.connect(self.chooseSpectrum, SIGNAL("activated(int)"),
                     self.spectrumChosen)

        if self.offerAbortOption:
            self.connect(self.okButton, SIGNAL("clicked()"), self.ok)
            self.connect(self.abortButton, SIGNAL("clicked()"), self.abort)

    def disconnectModelSignals(self):
        self.disconnect(
            self.model,
            SIGNAL("dataChanged(QModelIndex,QModelIndex,PyQt_PyObject)"),
            self.dataChanged)
        self.menubar.disconnect(self.undoAction, SIGNAL("triggered()"),\
                             protect_signal_handler(self.model.undoLastAction))
        self.menubar.disconnect(self.redoAction, SIGNAL("triggered()"),\
                             protect_signal_handler(self.model.redoLastAction))

    def connectModelSignals(self):
        self.connect(
            self.model,
            SIGNAL("dataChanged(QModelIndex,QModelIndex,PyQt_PyObject)"),
            self.dataChanged)
        self.menubar.connect(self.undoAction, SIGNAL("triggered()"),\
                             protect_signal_handler(self.model.undoLastAction))
        self.menubar.connect(self.redoAction, SIGNAL("triggered()"),\
                             protect_signal_handler(self.model.redoLastAction))

    def updateMenubar(self):
        undoInfo = self.model.infoLastAction()
        redoInfo = self.model.infoRedoAction()
        self.undoAction.setEnabled(undoInfo != None)
        self.redoAction.setEnabled(redoInfo != None)
        if undoInfo:
            self.undoAction.setText("Undo: %s" % undoInfo)
        if redoInfo:
            self.redoAction.setText("Redo: %s" % redoInfo)

    def setupViewForTable(self, i):
        for j, action in enumerate(self.chooseTableActions):
            txt = str(action.text())  # QString -> Python str
            if txt.startswith("*"):
                txt = " " + txt[1:]
                action.setText(txt)
            if i == j:
                action.setText("*" + txt[1:])

        for j in range(len(self.models)):
            self.tableViews[j].setVisible(i == j)

        if self.model is not None:
            self.disconnectModelSignals()
        self.model = self.models[i]
        self.tableView = self.tableViews[i]
        self.setupModelDependendLook()
        if self.isIntegrated:
            self.model.setNonEditable("method", ["area", "rmse", "method",\
                                                 "params"])
        self.choosePostfix.clear()
        mod = self.model
        for p in mod.table.supportedPostfixes(mod.integrationColNames()):
            self.choosePostfix.addItem(repr(p))

        if len(self.choosePostfix) == 1:
            self.choosePostfix.setVisible(False)

        self.connectModelSignals()
        self.updateMenubar()

    @protect_signal_handler
    def dataChanged(self, ix1, ix2, src):
        minr, maxr = sorted((ix1.row(), ix2.row()))
        minc, maxc = sorted((ix1.column(), ix2.column()))
        for r in range(minr, maxr + 1):
            for c in range(minc, maxc + 1):
                idx = self.model.createIndex(r, c)
                self.tableView.update(idx)

        if self.hasFeatures:
            minr, maxr = sorted((ix1.row(), ix2.row()))
            if minr <= self.currentRowIdx <= maxr:
                if isinstance(src, IntegrateAction):
                    self.updatePlots(reset=False)
                else:
                    self.updatePlots(reset=True)

    @protect_signal_handler
    def abort(self):
        self.result = 1
        self.close()

    @protect_signal_handler
    def ok(self):
        self.result = 0
        self.close()

    @protect_signal_handler
    def openContextMenu(self, point):
        idx = self.tableView.verticalHeader().logicalIndexAt(point)
        menu = QMenu()
        cloneAction = menu.addAction("Clone row")
        removeAction = menu.addAction("Delete row")
        undoInfo = self.model.infoLastAction()
        redoInfo = self.model.infoRedoAction()

        if undoInfo is not None:
            undoAction = menu.addAction("Undo %s" % undoInfo)
        if redoInfo is not None:
            redoAction = menu.addAction("Redo %s" % redoInfo)
        appearAt = self.tableView.verticalHeader().mapToGlobal(point)
        choosenAction = menu.exec_(appearAt)
        if choosenAction == removeAction:
            self.model.removeRow(idx)
        elif choosenAction == cloneAction:
            self.model.cloneRow(idx)
        elif undoInfo is not None and choosenAction == undoAction:
            self.model.undoLastAction()
        elif redoInfo is not None and choosenAction == redoAction:
            self.model.redoLastAction()

    @protect_signal_handler
    def doIntegrate(self):
        if self.currentRowIdx < 0:
            return  # no row selected

        # QString -> Python str:
        method = str(self.chooseIntMethod.currentText())
        # Again QString -> Python str.
        # For better readibilty we put single quotes around the postfix
        # entry in the QComboBox which we have to remove now:
        postfix = str(self.choosePostfix.currentText()).strip("'")
        rtmin, rtmax = self.rtPlotter.getRangeSelectionLimits()
        self.model.integrate(postfix, self.currentRowIdx, method, rtmin, rtmax)

    @protect_signal_handler
    def rowClicked(self, rowIdx):
        if not self.hasFeatures:
            return
        self.currentRowIdx = rowIdx
        self.rtPlotter.setEnabled(True)
        self.updatePlots(reset=True)
        if self.hasFeatures:
            self.setupSpectrumChooser()

    def setupSpectrumChooser(self):
        # delete QComboBox:
        while self.chooseSpectrum.count():
            self.chooseSpectrum.removeItem(0)

        # get current spectra
        rowidx = self.currentRowIdx
        postfixes, spectra = self.model.getLevelNSpectra(rowidx, minLevel=2)
        self.currentLevelNSpecs = []

        if not len(spectra):
            self.chooseSpectrum.setVisible(False)
            return

        self.chooseSpectrum.setVisible(True)
        self.chooseSpectrum.addItem("Show only Level 1 spectra")
        for postfix, s in zip(postfixes, spectra):
            if postfix != "":
                txt = postfix + ", "
            else:
                txt = ""
            txt += "rt=%.2fm, level=%d" % (s.rt / 60.0, s.msLevel)
            mzs = [mz for (mz, I) in s.precursors]
            precursors = ", ".join("%.6f" % mz for mz in mzs)
            if precursors:
                txt += ", precursor mzs=[%s]" % precursors
            self.chooseSpectrum.addItem(txt)
            self.currentLevelNSpecs.append(s)

    def updatePlots(self, reset=False):
        rowIdx = self.currentRowIdx
        eics, mzmin, mzmax, rtmin, rtmax, allrts = self.model.getEics(rowIdx)

        curves = eics
        configs = configsForEics(eics)
        if self.isIntegrated:
            smootheds = self.model.getSmoothedEics(rowIdx, allrts)
            if smootheds is not None:
                curves += smootheds
                configs += configsForSmootheds(smootheds)

        if not reset:
            rtmin, rtmax = self.rtPlotter.getRangeSelectionLimits()
            xmin, xmax, ymin, ymax = self.rtPlotter.getLimits()

        self.rtPlotter.plot(curves,
                            configs=configs,
                            titles=None,
                            withmarker=True)

        # allrts are sorted !
        w = rtmax - rtmin
        if w == 0:
            w = 30.0  # seconds
        self.rtPlotter.setRangeSelectionLimits(rtmin, rtmax)
        self.rtPlotter.setXAxisLimits(rtmin - w, rtmax + w)
        self.rtPlotter.replot()
        if not reset:
            self.rtPlotter.setXAxisLimits(xmin, xmax)
            self.rtPlotter.setYAxisLimits(ymin, ymax)
            self.rtPlotter.updateAxes()

        reset = reset and mzmin is not None and mzmax is not None
        limits = (mzmin, mzmax) if reset else None
        self.plotMz(resetLimits=limits)

    @protect_signal_handler
    def spectrumChosen(self, idx):
        if idx == 0:
            self.rtPlotter.setEnabled(True)
            self.chooseIntMethod.setEnabled(True)
            self.reintegrateButton.setEnabled(True)
            self.plotMz()
        else:
            self.rtPlotter.setEnabled(False)
            self.chooseIntMethod.setEnabled(False)
            self.reintegrateButton.setEnabled(False)
            self.mzPlotter.plot([self.currentLevelNSpecs[idx - 1].peaks])
            self.mzPlotter.resetAxes()
            self.mzPlotter.replot()

    def plotMz(self, resetLimits=None):
        """ this one is used from updatePlots and the rangeselectors
            callback """
        rtmin = self.rtPlotter.minRTRangeSelected
        rtmax = self.rtPlotter.maxRTRangeSelected

        # get spectra for current row in given rt-range:
        peakmaps = self.model.getPeakmaps(self.currentRowIdx)

        peaks = [
            pm.getDominatingPeakmap().ms1Peaks(rtmin, rtmax) for pm in peakmaps
        ]

        # plot peaks
        configs = configsForSpectra(peaks)
        postfixes = self.model.table.supportedPostfixes(
            self.model.eicColNames())
        titles = map(repr, postfixes)
        self.mzPlotter.plot(peaks, configs,
                            titles if len(titles) > 1 else None)

        if resetLimits:
            mzmin, mzmax = resetLimits
            Imaxes = []
            for p in peaks:
                imin = p[:, 0].searchsorted(mzmin)
                imax = p[:, 0].searchsorted(mzmax, side='right')
                found = p[imin:imax, 1]
                if len(found):
                    Imaxes.append(found.max())

            if len(Imaxes) == 0:
                Imax = 1.0
            else:
                Imax = max(Imaxes) * 1.2
            self.mzPlotter.setXAxisLimits(mzmin, mzmax)
            self.mzPlotter.reset_y_limits(0, Imax)
        # plot() needs replot() afterwards !
        self.mzPlotter.replot()
Esempio n. 4
0
class TableExplorer(QDialog):

    def __init__(self, tables, offerAbortOption):
        QDialog.__init__(self)

        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setWindowFlags(Qt.Window)

        self.offerAbortOption = offerAbortOption

        self.models = [TableModel(table, self) for table in tables]
        self.model = None
        self.tableView = None

        self.currentRowIdx = -1
        self.hadFeatures = None
        self.wasIntegrated = None

        self.setupWidgets()
        self.setupLayout()
        self.connectSignals()

        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.setSizePolicy(sizePolicy)
        self.setSizeGripEnabled(True)

        self.setupViewForTable(0)

    def setupWidgets(self):
        self.setupMenuBar()
        self.setupTableViews()
        self.setupPlottingWidgets()
        self.chooseSpectrum = QComboBox()
        self.setupIntegrationWidgets()
        if self.offerAbortOption:
            self.setupAcceptButtons()

    def setupMenuBar(self):
        self.menubar = QMenuBar(self)
        menu = self.buildEditMenu()
        self.menubar.addMenu(menu)
        self.chooseTableActions = []
        if len(self.models)>1:
            menu = self.buildChooseTableMenu()
            self.menubar.addMenu(menu)

    def buildEditMenu(self):
        self.undoAction = QAction("Undo", self)
        self.undoAction.setShortcut(QKeySequence("Ctrl+Z"))
        self.redoAction = QAction("Redo", self)
        self.redoAction.setShortcut(QKeySequence("Ctrl+Y"))
        menu = QMenu("Edit", self.menubar)
        menu.addAction(self.undoAction)
        menu.addAction(self.redoAction)
        return menu

    def setupTableViews(self):
        self.tableViews = []
        for i, model in enumerate(self.models):
            self.tableViews.append(self.setupTableViewFor(model))

    def setupTableViewFor(self, model):
        tableView = QTableView(self)

        @protect_signal_handler
        def handler(evt, view=tableView, model=model, self=self):
            if not view.isSortingEnabled():
                view.setSortingEnabled(True)
                view.resizeColumnsToContents()
                model.emptyActionStack()
                self.updateMenubar()
        tableView.showEvent = handler

        tableView.setModel(model)
        tableView.horizontalHeader().setResizeMode(QHeaderView.Interactive)
        pol = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        tableView.setSizePolicy(pol)
        tableView.setVisible(False)
        # before filling the table, disabling sorting accelerates table
        # construction, sorting is enabled in TableView.showEvent, which is
        # called after construction
        tableView.setSortingEnabled(False)
        return tableView

    def buildChooseTableMenu(self):
        menu = QMenu("Choose Table", self.menubar)
        for i, model in enumerate(self.models):
            action = QAction(" [%d]: %s" % (i,model.getTitle()), self)
            menu.addAction(action)
            self.chooseTableActions.append(action)
        return menu

    def setupPlottingWidgets(self):
        self.plotconfigs = (None, dict(shade=0.35, linewidth=1, color="g") )
        self.rtPlotter = RtPlotter(rangeSelectionCallback=self.plotMz)
        self.rtPlotter.setMinimumSize(300, 100)
        self.mzPlotter = MzPlotter()
        self.mzPlotter.setMinimumSize(300, 100)
        pol = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        pol.setVerticalStretch(5)
        self.rtPlotter.widget.setSizePolicy(pol)
        self.mzPlotter.widget.setSizePolicy(pol)

    def setupIntegrationWidgets(self):
        self.intLabel = QLabel("Integration")
        self.chooseIntMethod = QComboBox()
        import configs
        for name, _ in configs.peakIntegrators:
            self.chooseIntMethod.addItem(name)

        self.choosePostfix = QComboBox()

        self.reintegrateButton = QPushButton()
        self.reintegrateButton.setText("Integrate")

    def setupAcceptButtons(self):
        self.okButton = QPushButton("Ok")
        self.abortButton = QPushButton("Abort")
        self.result = 1 # default for closing

    def setupLayout(self):
        vlayout = QVBoxLayout()
        self.setLayout(vlayout)

        vsplitter = QSplitter()
        vsplitter.setOrientation(Qt.Vertical)
        vsplitter.setOpaqueResize(False)

        vsplitter.addWidget(self.menubar)
        vsplitter.addWidget(self.layoutWidgetsAboveTable())
        vsplitter.addWidget(self.chooseSpectrum)

        for view in self.tableViews:
            vsplitter.addWidget(view)
        vlayout.addWidget(vsplitter)

        if self.offerAbortOption:
            vlayout.addLayout(self.layoutButtons())

    def layoutButtons(self):
        hbox = QHBoxLayout()
        hbox.addWidget(self.abortButton)
        hbox.setAlignment(self.abortButton, Qt.AlignVCenter)
        hbox.addWidget(self.okButton)
        hbox.setAlignment(self.okButton, Qt.AlignVCenter)
        return hbox

    def layoutWidgetsAboveTable(self):
        hsplitter = QSplitter()
        hsplitter.setOpaqueResize(False)
        hsplitter.addWidget(self.rtPlotter.widget)

        integrationLayout = QVBoxLayout()
        integrationLayout.setSpacing(10)
        integrationLayout.setMargin(5)
        integrationLayout.addWidget(self.intLabel)
        integrationLayout.addWidget(self.chooseIntMethod)
        integrationLayout.addWidget(self.choosePostfix)
        integrationLayout.addWidget(self.reintegrateButton)
        integrationLayout.addStretch()
        integrationLayout.setAlignment(self.intLabel, Qt.AlignTop)
        integrationLayout.setAlignment(self.chooseIntMethod, Qt.AlignTop)
        integrationLayout.setAlignment(self.reintegrateButton, Qt.AlignTop)

        self.integrationFrame = QFrame()
        self.integrationFrame.setLayout(integrationLayout)

        hsplitter.addWidget(self.integrationFrame)
        hsplitter.addWidget(self.mzPlotter.widget)
        return hsplitter

    def setupModelDependendLook(self):
        hasFeatures = self.model.hasFeatures()
        isIntegrated = self.model.isIntegrated()
        self.hasFeatures = hasFeatures
        self.isIntegrated = isIntegrated

        self.setWindowTitle(self.model.getTitle())

        self.chooseSpectrum.setVisible(False)
        if hasFeatures != self.hadFeatures:
            self.setPlotVisibility(hasFeatures)
            self.hadFeatures = hasFeatures
            # default: invisible, only activated when row clicked and
            # level >= 2 spectra are available
        if isIntegrated != self.wasIntegrated:
            self.setIntegrationPanelVisiblity(isIntegrated)
            self.wasIntegrated = isIntegrated
        if hasFeatures:
            self.rtPlotter.setEnabled(True)
            self.resetPlots()

    def setPlotVisibility(self, doShow):
        self.rtPlotter.widget.setVisible(doShow)
        self.mzPlotter.widget.setVisible(doShow)

    def resetPlots(self):
        self.rtPlotter.reset()
        self.mzPlotter.reset()

    def setIntegrationPanelVisiblity(self, doShow):
        self.integrationFrame.setVisible(doShow)

    @protect_signal_handler
    def handleClick(self, index, model):
        content = model.data(index)
        if isUrl(content):
            QDesktopServices.openUrl(QUrl(content))

    def connectSignals(self):
        for i, action in enumerate(self.chooseTableActions):

            handler = lambda i=i: self.setupViewForTable(i)
            handler = protect_signal_handler(handler)
            self.menubar.connect(action, SIGNAL("triggered()"), handler)

        for view in self.tableViews:
            vh = view.verticalHeader()
            vh.setContextMenuPolicy(Qt.CustomContextMenu)
            self.connect(vh, SIGNAL("customContextMenuRequested(QPoint)"),\
                         self.openContextMenu)

            self.connect(vh, SIGNAL("sectionClicked(int)"), self.rowClicked)

            model = view.model()
            handler = lambda idx, model=model: self.handleClick(idx, model)
            handler = protect_signal_handler(handler)
            self.connect(view, SIGNAL("clicked(QModelIndex)"), handler)

        self.connect(self.reintegrateButton, SIGNAL("clicked()"),
                     self.doIntegrate)

        self.connect(self.chooseSpectrum, SIGNAL("activated(int)"),
                     self.spectrumChosen)

        if self.offerAbortOption:
            self.connect(self.okButton, SIGNAL("clicked()"), self.ok)
            self.connect(self.abortButton, SIGNAL("clicked()"), self.abort)

    def disconnectModelSignals(self):
        self.disconnect(self.model,
                   SIGNAL("dataChanged(QModelIndex,QModelIndex,PyQt_PyObject)"),
                   self.dataChanged)
        self.menubar.disconnect(self.undoAction, SIGNAL("triggered()"),\
                             protect_signal_handler(self.model.undoLastAction))
        self.menubar.disconnect(self.redoAction, SIGNAL("triggered()"),\
                             protect_signal_handler(self.model.redoLastAction))

    def connectModelSignals(self):
        self.connect(self.model,
                   SIGNAL("dataChanged(QModelIndex,QModelIndex,PyQt_PyObject)"),
                   self.dataChanged)
        self.menubar.connect(self.undoAction, SIGNAL("triggered()"),\
                             protect_signal_handler(self.model.undoLastAction))
        self.menubar.connect(self.redoAction, SIGNAL("triggered()"),\
                             protect_signal_handler(self.model.redoLastAction))

    def updateMenubar(self):
        undoInfo = self.model.infoLastAction()
        redoInfo = self.model.infoRedoAction()
        self.undoAction.setEnabled(undoInfo != None)
        self.redoAction.setEnabled(redoInfo != None)
        if undoInfo:
            self.undoAction.setText("Undo: %s" % undoInfo)
        if redoInfo:
            self.redoAction.setText("Redo: %s" % redoInfo)

    def setupViewForTable(self, i):
        for j, action in enumerate(self.chooseTableActions):
            txt = str(action.text()) # QString -> Python str
            if txt.startswith("*"):
                txt = " "+txt[1:]
                action.setText(txt)
            if i==j:
                action.setText("*"+txt[1:])

        for j in range(len(self.models)):
            self.tableViews[j].setVisible(i==j)

        if self.model is not None:
            self.disconnectModelSignals()
        self.model = self.models[i]
        self.tableView = self.tableViews[i]
        self.setupModelDependendLook()
        if self.isIntegrated:
            self.model.setNonEditable("method", ["area", "rmse", "method",\
                                                 "params"])
        self.choosePostfix.clear()
        mod = self.model
        for p in mod.table.supportedPostfixes(mod.integrationColNames()):
            self.choosePostfix.addItem(repr(p))

        if len(self.choosePostfix) == 1:
            self.choosePostfix.setVisible(False)

        self.connectModelSignals()
        self.updateMenubar()

    @protect_signal_handler
    def dataChanged(self, ix1, ix2, src):
        minr, maxr = sorted((ix1.row(), ix2.row()))
        minc, maxc = sorted((ix1.column(), ix2.column()))
        for r in range(minr, maxr+1):
            for c in range(minc, maxc+1):
                idx = self.model.createIndex(r, c)
                self.tableView.update(idx)

        if self.hasFeatures:
            minr, maxr = sorted((ix1.row(), ix2.row()))
            if minr <= self.currentRowIdx <= maxr:
                if isinstance(src, IntegrateAction):
                    self.updatePlots(reset=False)
                else:
                    self.updatePlots(reset=True)

    @protect_signal_handler
    def abort(self):
        self.result = 1
        self.close()

    @protect_signal_handler
    def ok(self):
        self.result = 0
        self.close()


    @protect_signal_handler
    def openContextMenu(self, point):
        idx = self.tableView.verticalHeader().logicalIndexAt(point)
        menu = QMenu()
        cloneAction = menu.addAction("Clone row")
        removeAction = menu.addAction("Delete row")
        undoInfo = self.model.infoLastAction()
        redoInfo = self.model.infoRedoAction()

        if undoInfo is not None:
            undoAction = menu.addAction("Undo %s" % undoInfo)
        if redoInfo is not None:
            redoAction = menu.addAction("Redo %s" % redoInfo)
        appearAt = self.tableView.verticalHeader().mapToGlobal(point)
        choosenAction = menu.exec_(appearAt)
        if choosenAction == removeAction:
            self.model.removeRow(idx)
        elif choosenAction == cloneAction:
            self.model.cloneRow(idx)
        elif undoInfo is not None and choosenAction == undoAction:
            self.model.undoLastAction()
        elif redoInfo is not None and choosenAction == redoAction:
            self.model.redoLastAction()

    @protect_signal_handler
    def doIntegrate(self):
        if self.currentRowIdx < 0:
            return # no row selected

        # QString -> Python str:
        method = str(self.chooseIntMethod.currentText())
        # Again QString -> Python str.
        # For better readibilty we put single quotes around the postfix
        # entry in the QComboBox which we have to remove now:
        postfix = str(self.choosePostfix.currentText()).strip("'")
        rtmin, rtmax = self.rtPlotter.getRangeSelectionLimits()
        self.model.integrate(postfix, self.currentRowIdx, method, rtmin, rtmax)

    @protect_signal_handler
    def rowClicked(self, rowIdx):
        if not self.hasFeatures:
            return
        self.currentRowIdx = rowIdx
        self.rtPlotter.setEnabled(True)
        self.updatePlots(reset=True)
        if self.hasFeatures:
            self.setupSpectrumChooser()


    def setupSpectrumChooser(self):
        # delete QComboBox:
        while self.chooseSpectrum.count():
            self.chooseSpectrum.removeItem(0)

        # get current spectra
        rowidx = self.currentRowIdx
        postfixes, spectra = self.model.getLevelNSpectra(rowidx, minLevel=2)
        self.currentLevelNSpecs = []

        if not len(spectra):
            self.chooseSpectrum.setVisible(False)
            return

        self.chooseSpectrum.setVisible(True)
        self.chooseSpectrum.addItem("Show only Level 1 spectra")
        for postfix, s in zip(postfixes, spectra):
            if postfix != "":
                txt = postfix+", "
            else:
                txt = ""
            txt += "rt=%.2fm, level=%d" % (s.rt/60.0, s.msLevel)
            mzs = [ mz for (mz, I) in s.precursors ]
            precursors = ", ".join("%.6f" % mz for mz in mzs)
            if precursors:
                txt += ", precursor mzs=[%s]" % precursors
            self.chooseSpectrum.addItem(txt)
            self.currentLevelNSpecs.append(s)

    def updatePlots(self, reset=False):
        rowIdx = self.currentRowIdx
        eics, mzmin, mzmax, rtmin, rtmax, allrts = self.model.getEics(rowIdx)

        curves = eics
        configs = configsForEics(eics)
        if self.isIntegrated:
            smootheds = self.model.getSmoothedEics(rowIdx, allrts)
            if smootheds is not None:
                curves += smootheds
                configs += configsForSmootheds(smootheds)

        if not reset:
            rtmin, rtmax = self.rtPlotter.getRangeSelectionLimits()
            xmin, xmax, ymin, ymax = self.rtPlotter.getLimits()

        self.rtPlotter.plot(curves, configs=configs, titles=None,
                            withmarker=True)

        # allrts are sorted !
        w = rtmax - rtmin
        if w == 0:
            w = 30.0 # seconds
        self.rtPlotter.setRangeSelectionLimits(rtmin, rtmax)
        self.rtPlotter.setXAxisLimits(rtmin -w, rtmax + w)
        self.rtPlotter.replot()
        if not reset:
            self.rtPlotter.setXAxisLimits(xmin, xmax)
            self.rtPlotter.setYAxisLimits(ymin, ymax)
            self.rtPlotter.updateAxes()

        reset = reset and mzmin is not None and mzmax is not None
        limits = (mzmin, mzmax) if reset else None
        self.plotMz(resetLimits=limits)

    @protect_signal_handler
    def spectrumChosen(self, idx):
        if idx==0:
            self.rtPlotter.setEnabled(True)
            self.chooseIntMethod.setEnabled(True)
            self.reintegrateButton.setEnabled(True)
            self.plotMz()
        else:
            self.rtPlotter.setEnabled(False)
            self.chooseIntMethod.setEnabled(False)
            self.reintegrateButton.setEnabled(False)
            self.mzPlotter.plot([self.currentLevelNSpecs[idx-1].peaks])
            self.mzPlotter.resetAxes()
            self.mzPlotter.replot()

    def plotMz(self, resetLimits=None):
        """ this one is used from updatePlots and the rangeselectors
            callback """
        rtmin=self.rtPlotter.minRTRangeSelected
        rtmax=self.rtPlotter.maxRTRangeSelected

        # get spectra for current row in given rt-range:
        peakmaps = self.model.getPeakmaps(self.currentRowIdx)

        peaks = [pm.getDominatingPeakmap().ms1Peaks(rtmin, rtmax) for pm in peakmaps]

        # plot peaks
        configs = configsForSpectra(peaks)
        postfixes = self.model.table.supportedPostfixes(self.model.eicColNames())
        titles = map(repr, postfixes)
        self.mzPlotter.plot(peaks, configs, titles if len(titles)>1 else None)

        if resetLimits:
            mzmin, mzmax = resetLimits
            Imaxes = []
            for p  in peaks:
                imin = p[:,0].searchsorted(mzmin)
                imax = p[:,0].searchsorted(mzmax, side='right')
                found = p[imin:imax,1]
                if len(found):
                    Imaxes.append(found.max())

            if len(Imaxes) == 0:
                Imax = 1.0
            else:
                Imax = max(Imaxes) * 1.2
            self.mzPlotter.setXAxisLimits(mzmin, mzmax)
            self.mzPlotter.reset_y_limits(0, Imax)
        # plot() needs replot() afterwards !
        self.mzPlotter.replot()