コード例 #1
0
class xBanWindow(QMainWindow):
    """The main window of xban

    The main window serves three major purposes:
    - statusbar (sand the save button)
    - scrollable area
    """
    def __init__(self, base_path, file, file_config, parent=None):
        super().__init__(parent)

        board = BanBoard(file, file_config)
        board_area = QScrollArea()
        board_area.setWidget(board)
        board_area.setWidgetResizable(True)
        self.setCentralWidget(board_area)

        self.stbar = QStatusBar()

        # add a save button at the right bottom corner
        save_btn = BanButton(
            "save",
            objectName="appBtn_save",
            toolTip="save xban file",
            shortcut="Ctrl+S",
        )

        shadow = QGraphicsDropShadowEffect(self,
                                           blurRadius=10,
                                           offset=5,
                                           color=QColor("lightgrey"))
        save_btn.setGraphicsEffect(shadow)
        save_btn.pressed.connect(board.save_board)

        self.stbar.addPermanentWidget(save_btn)
        self.setStatusBar(self.stbar)
        log_handler = QLogHandler(self)
        root_logger = logging.getLogger()
        root_logger.addHandler(log_handler)
        log_handler.signal.log_msg.connect(
            partial(self.stbar.showMessage, timeout=1500))
        self.stbar.showMessage(f"Initiate {file}", 1500)
        self.show()

    def closeEvent(self, event):
        """Auto save when close"""

        self.centralWidget().widget().save_board()
        super().closeEvent(event)
コード例 #2
0
    def __init__(self, app, appname, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.hide()
        #self.setMinimumSize(640, 480)
        self.setFixedSize(self.geometry().width(), self.geometry().height())
        self.setWindowIcon(QIcon('icon.ico'))
        self.setWindowTitle(appname)

        # Create menu bar
        menu_bar = QMenuBar()

        help_menu = menu_bar.addAction('&Help')
        about_menu = menu_bar.addAction('A&bout')
        exit_menu = menu_bar.addAction('&Exit')

        self.setMenuBar(menu_bar)

        # Make interface layouts
        window = QWidget()
        layout = QVBoxLayout()

        top_section = QVBoxLayout()

        buttons = QGridLayout()
        middle_section = QHBoxLayout()

        label_section = QHBoxLayout()
        range_section = QGridLayout()
        clarity_section = QHBoxLayout()
        plot_section = QHBoxLayout()
        bottom_section = QVBoxLayout()

        status_layout = QHBoxLayout()

        # Create widgets and items
        figure = plt.figure()
        canvas = FigureCanvas(figure)

        label = QLabel(f'''Welcome to {appname}!
Plot any equation of the form y = f(x).
Use the options below to plot your own equation!''')

        help_message = QMessageBox()
        help_message.setTextFormat(Qt.RichText)
        help_message.setText(f'''<h3>Help</h3>
{appname} lets you plot any equation of the form y = f(x).
<br/>
Enter the function f(x), specify the range of x, and click Plot!
<br/><br/>
Operators : <code>+, -, *, /</code><br/>
Variable : <code>x</code><br/>
Functions : <code>sin, cos, tan</code><br/>
<code>pi</code> : π<br/>
<code>e</code> : Exponential e<br/>
<code>c</code> : Speed of Light<br/>''')
        help_message.setStandardButtons(QMessageBox.Ok)
        help_message.setWindowTitle(f'{appname} - Help')
        self.help = help_message

        help_button = QPushButton('Help...')
        help_button.clicked.connect(self.help.exec_)

        about_message = QMessageBox()
        about_message.setWindowTitle(f'{appname} - About')
        about_message.setTextFormat(Qt.RichText)
        about_message.setText(f'''<h3>About</h3>
{appname} is created in PySide2 (Qt), using \
the Matplotlib and Equation PyPI modules for plotting and parsing expressions respectively.
<br/><br/>
Created by <a href="http://paramsid.com">Param Siddharth</a>.''')
        about_message.setStandardButtons(QMessageBox.Ok)
        self.about = about_message

        about_button = QPushButton('About...')
        about_button.clicked.connect(self.about.exec_)

        expr_label = QLabel('f(x) =')
        expr_input = QLineEdit()

        range_label1 = QLabel('Minimum (x):')
        range_min = QLineEdit()
        range_label2 = QLabel('Maximum (x):')
        range_max = QLineEdit()

        clarity_label = QLabel('Clarity:')
        clarity_spinbox = QSpinBox()
        clarity_spinbox.setRange(1, 10000)
        clarity_spinbox.setValue(100)

        plot_button = QPushButton('Plot')
        plot_button.setMaximumWidth(200)

        status = QStatusBar()
        status_text = QLabel('')
        status_text.setStyleSheet('color: #999999;')

        attribution = QLabel(
            'Made with <span style="color: red;">❤</span> by <a href="http://paramsid.com">Param</a>'
        )
        attribution.setTextFormat(Qt.RichText)
        attribution.setStyleSheet('color: #555555; font-size: 20px;')
        attribution.setAlignment(Qt.AlignRight | Qt.AlignVCenter)

        help_menu.triggered.connect(self.help.exec_)
        about_menu.triggered.connect(self.about.exec_)
        exit_menu.triggered.connect(self.close)

        # Configure backend
        backmath.configure(canvas=canvas,
                           figure=figure,
                           btn=plot_button,
                           text=expr_input,
                           limits=(range_min, range_max),
                           status=status_text,
                           range_text=(range_min, range_max),
                           clarity=clarity_spinbox)

        # Finalize and display
        top_section.addWidget(canvas)

        buttons.addWidget(help_button, 0, 0, 1, 1)
        buttons.addWidget(about_button, 0, 1, 1, 1)

        middle_section.addWidget(label)
        middle_section.addLayout(buttons)

        label_section.addWidget(expr_label)
        label_section.addWidget(expr_input)

        equally_spaced = QSizePolicy(QSizePolicy.Preferred,
                                     QSizePolicy.Preferred)
        equally_spaced.setHorizontalStretch(1)

        range_label1.setSizePolicy(equally_spaced)
        range_min.setSizePolicy(equally_spaced)
        range_label2.setSizePolicy(equally_spaced)
        range_max.setSizePolicy(equally_spaced)

        range_label1.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        range_label2.setAlignment(Qt.AlignRight | Qt.AlignVCenter)

        range_section.addWidget(range_label1, 0, 0, 1, 1)
        range_section.addWidget(range_min, 0, 1, 1, 1)
        range_section.addWidget(range_label2, 0, 2, 1, 1)
        range_section.addWidget(range_max, 0, 3, 1, 1)

        clarity_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)

        clarity_section.addWidget(clarity_label)
        clarity_section.addWidget(clarity_spinbox)

        plot_section.addWidget(plot_button)

        status.addWidget(status_text)
        status.addPermanentWidget(attribution)

        status_layout.addWidget(status)

        bottom_section.addLayout(label_section)
        bottom_section.addLayout(range_section)
        bottom_section.addLayout(clarity_section)
        bottom_section.addLayout(plot_section)

        layout.addLayout(top_section)
        layout.addLayout(middle_section)
        layout.addLayout(bottom_section)
        layout.addLayout(status_layout)

        window.setLayout(layout)
        self.setCentralWidget(window)
        self.show()

        status_text.setText('READY ')
コード例 #3
0
ファイル: view.py プロジェクト: VasudevDevulapally/biopeaks
class View(QMainWindow):
    def __init__(self, model, controller):
        super().__init__()

        self._model = model
        self._controller = controller
        self.segmentcursor = False
        self.togglecolors = {"#1f77b4": "m", "m": "#1f77b4"}

        #################################################################
        # define GUI layout and connect input widgets to external slots #
        #################################################################

        self.setWindowTitle("biopeaks")
        self.setGeometry(50, 50, 1750, 750)
        self.setWindowIcon(QIcon(":/python_icon.png"))

        # figure0 for signal
        self.figure0 = Figure()
        self.canvas0 = FigureCanvas(self.figure0)
        # Enforce minimum height, otherwise resizing with self.splitter causes
        # mpl to throw an error because figure is resized to height 0. The
        # widget can still be fully collapsed with self.splitter-
        self.canvas0.setMinimumHeight(1)  # in pixels
        self.ax00 = self.figure0.add_subplot(1, 1, 1)
        self.ax00.set_frame_on(False)
        self.figure0.subplots_adjust(left=0.04, right=0.98, bottom=0.25)
        self.line00 = None
        self.scat = None
        self.segmentspan = None

        # figure1 for marker
        self.figure1 = Figure()
        self.canvas1 = FigureCanvas(self.figure1)
        self.canvas1.setMinimumHeight(1)
        self.ax10 = self.figure1.add_subplot(1, 1, 1, sharex=self.ax00)
        self.ax10.get_xaxis().set_visible(False)
        self.ax10.set_frame_on(False)
        self.figure1.subplots_adjust(left=0.04, right=0.98)
        self.line10 = None

        # figure2 for statistics
        self.figure2 = Figure()
        self.canvas2 = FigureCanvas(self.figure2)
        self.canvas2.setMinimumHeight(1)
        self.ax20 = self.figure2.add_subplot(3, 1, 1, sharex=self.ax00)
        self.ax20.get_xaxis().set_visible(False)
        self.ax20.set_frame_on(False)
        self.line20 = None
        self.ax21 = self.figure2.add_subplot(3, 1, 2, sharex=self.ax00)
        self.ax21.get_xaxis().set_visible(False)
        self.ax21.set_frame_on(False)
        self.line21 = None
        self.ax22 = self.figure2.add_subplot(3, 1, 3, sharex=self.ax00)
        self.ax22.get_xaxis().set_visible(False)
        self.ax22.set_frame_on(False)
        self.line22 = None
        self.figure2.subplots_adjust(left=0.04, right=0.98)

        # navigation bar
        self.navitools = CustomNavigationToolbar(self.canvas0, self)

        # peak editing
        self.editcheckbox = QCheckBox("editable", self)
        self.editcheckbox.stateChanged.connect(self._model.set_peakseditable)

        # peak saving batch
        self.savecheckbox = QCheckBox("save during batch processing", self)
        self.savecheckbox.stateChanged.connect(self._model.set_savebatchpeaks)

        # peak auto-correction batch
        self.correctcheckbox = QCheckBox("correct during batch processing",
                                         self)
        self.correctcheckbox.stateChanged.connect(
            self._model.set_correctbatchpeaks)

        # selecting stats for saving
        self.periodcheckbox = QCheckBox("period", self)
        self.periodcheckbox.stateChanged.connect(
            lambda: self.select_stats("period"))
        self.ratecheckbox = QCheckBox("rate", self)
        self.ratecheckbox.stateChanged.connect(
            lambda: self.select_stats("rate"))
        self.tidalampcheckbox = QCheckBox("tidal amplitude", self)
        self.tidalampcheckbox.stateChanged.connect(
            lambda: self.select_stats("tidalamp"))

        # channel selection
        self.sigchanmenulabel = QLabel("biosignal")
        self.sigchanmenu = QComboBox(self)
        self.sigchanmenu.addItem("A1")
        self.sigchanmenu.addItem("A2")
        self.sigchanmenu.addItem("A3")
        self.sigchanmenu.addItem("A4")
        self.sigchanmenu.addItem("A5")
        self.sigchanmenu.addItem("A6")
        self.sigchanmenu.currentTextChanged.connect(self._model.set_signalchan)
        # initialize with default value
        self._model.set_signalchan(self.sigchanmenu.currentText())

        self.markerchanmenulabel = QLabel("marker")
        self.markerchanmenu = QComboBox(self)
        self.markerchanmenu.addItem("none")
        self.markerchanmenu.addItem("I1")
        self.markerchanmenu.addItem("I2")
        self.markerchanmenu.addItem("A1")
        self.markerchanmenu.addItem("A2")
        self.markerchanmenu.addItem("A3")
        self.markerchanmenu.addItem("A4")
        self.markerchanmenu.addItem("A5")
        self.markerchanmenu.addItem("A6")
        self.markerchanmenu.currentTextChanged.connect(
            self._model.set_markerchan)
        # initialize with default value
        self._model.set_markerchan(self.markerchanmenu.currentText())

        # processing mode (batch or single file)
        self.batchmenulabel = QLabel("mode")
        self.batchmenu = QComboBox(self)
        self.batchmenu.addItem("single file")
        self.batchmenu.addItem("multiple files")
        self.batchmenu.currentTextChanged.connect(self._model.set_batchmode)
        self.batchmenu.currentTextChanged.connect(self.toggle_options)
        # initialize with default value
        self._model.set_batchmode(self.batchmenu.currentText())
        self.toggle_options(self.batchmenu.currentText())

        # modality selection
        self.modmenulabel = QLabel("modality")
        self.modmenu = QComboBox(self)
        self.modmenu.addItem("ECG")
        self.modmenu.addItem("PPG")
        self.modmenu.addItem("RESP")
        self.modmenu.currentTextChanged.connect(self._model.set_modality)
        self.modmenu.currentTextChanged.connect(self.toggle_options)
        # initialize with default value
        self._model.set_modality(self.modmenu.currentText())
        self.toggle_options(self.modmenu.currentText())

        # segment selection; this widget can be openend / set visible from
        # the menu and closed from within itself (see mapping of segmentermap);
        # it provides utilities to select a segment from the signal
        self.segmentermap = QSignalMapper(self)
        self.segmenter = QDockWidget("select a segment", self)
        # disable closing such that widget can only be closed by confirming
        # selection or custom button
        self.segmenter.setFeatures(QDockWidget.NoDockWidgetFeatures)
        # Limit number of decimals to four.
        regex = QRegExp("[0-9]*\.?[0-9]{4}")
        validator = QRegExpValidator(regex)

        self.startlabel = QLabel("start")
        self.startedit = QLineEdit()
        self.startedit.setValidator(validator)

        self.endlabel = QLabel("end")
        self.endedit = QLineEdit()
        self.endedit.setValidator(validator)

        segmentfromcursor = QAction(QIcon(":/mouse_icon.png"),
                                    "select with mouse", self)
        segmentfromcursor.triggered.connect(self.enable_segmentedit)
        self.startedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition)
        self.endedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition)

        self.previewedit = QPushButton("preview segment")
        lambdafn = lambda: self._model.set_segment(
            [self.startedit.text(), self.endedit.text()])
        self.previewedit.clicked.connect(lambdafn)

        self.confirmedit = QPushButton("confirm segment")
        self.confirmedit.clicked.connect(self._controller.segment_signal)
        self.confirmedit.clicked.connect(self.segmentermap.map)
        self.segmentermap.setMapping(self.confirmedit, 0)

        self.abortedit = QPushButton("abort segmentation")
        self.abortedit.clicked.connect(self.segmentermap.map)
        # reset the segment to None
        self.segmentermap.setMapping(self.abortedit, 2)

        self.segmenterlayout = QFormLayout()
        self.segmenterlayout.addRow(self.startlabel, self.startedit)
        self.segmenterlayout.addRow(self.endlabel, self.endedit)
        self.segmenterlayout.addRow(self.previewedit)
        self.segmenterlayout.addRow(self.confirmedit)
        self.segmenterlayout.addRow(self.abortedit)
        self.segmenterwidget = QWidget()
        self.segmenterwidget.setLayout(self.segmenterlayout)
        self.segmenter.setWidget(self.segmenterwidget)

        self.segmenter.setVisible(False)
        self.segmenter.setAllowedAreas(Qt.RightDockWidgetArea)
        self.addDockWidget(Qt.RightDockWidgetArea, self.segmenter)

        # Set up dialog to gather user input for custom files.

        regex = QRegExp("[1-9][0-9]")
        validator = QRegExpValidator(regex)

        self.signallabel = QLabel("biosignal column")
        self.signaledit = QLineEdit()
        self.signaledit.setValidator(validator)

        self.markerlabel = QLabel("marker column")
        self.markeredit = QLineEdit()
        self.markeredit.setValidator(validator)

        regex = QRegExp("[0-9]{2}")
        validator = QRegExpValidator(regex)

        self.headerrowslabel = QLabel("number of header rows")
        self.headerrowsedit = QLineEdit()
        self.headerrowsedit.setValidator(validator)

        regex = QRegExp("[0-9]{5}")
        validator = QRegExpValidator(regex)

        self.sfreqlabel = QLabel("sampling rate")
        self.sfreqedit = QLineEdit()
        self.sfreqedit.setValidator(validator)

        self.separatorlabel = QLabel("column separator")
        self.separatormenu = QComboBox(self)
        self.separatormenu.addItem("comma")
        self.separatormenu.addItem("tab")
        self.separatormenu.addItem("colon")
        self.separatormenu.addItem("space")

        self.continuecustomfile = QPushButton("continue loading file")
        self.continuecustomfile.clicked.connect(self.set_customheader)

        self.customfiledialog = QDialog()
        self.customfiledialog.setWindowTitle("custom file info")
        self.customfiledialog.setWindowIcon(QIcon(":/file_icon.png"))
        self.customfiledialog.setWindowFlags(
            Qt.WindowCloseButtonHint
        )  # remove help button by only setting close button
        self.customfilelayout = QFormLayout()
        self.customfilelayout.addRow(self.signallabel, self.signaledit)
        self.customfilelayout.addRow(self.markerlabel, self.markeredit)
        self.customfilelayout.addRow(self.separatorlabel, self.separatormenu)
        self.customfilelayout.addRow(self.headerrowslabel, self.headerrowsedit)
        self.customfilelayout.addRow(self.sfreqlabel, self.sfreqedit)
        self.customfilelayout.addRow(self.continuecustomfile)
        self.customfiledialog.setLayout(self.customfilelayout)

        # set up menubar
        menubar = self.menuBar()

        # signal menu
        signalmenu = menubar.addMenu("biosignal")

        openSignal = signalmenu.addMenu("load")
        openEDF = QAction("EDF", self)
        openEDF.triggered.connect(lambda: self._model.set_filetype("EDF"))
        openEDF.triggered.connect(self._controller.get_fpaths)
        openSignal.addAction(openEDF)
        openOpenSignals = QAction("OpenSignals", self)
        openOpenSignals.triggered.connect(
            lambda: self._model.set_filetype("OpenSignals"))
        openOpenSignals.triggered.connect(self._controller.get_fpaths)
        openSignal.addAction(openOpenSignals)
        openCustom = QAction("Custom", self)
        openCustom.triggered.connect(
            lambda: self._model.set_filetype("Custom"))
        openCustom.triggered.connect(lambda: self.customfiledialog.exec_())
        openSignal.addAction(openCustom)

        segmentSignal = QAction("select segment", self)
        segmentSignal.triggered.connect(self.segmentermap.map)
        self.segmentermap.setMapping(segmentSignal, 1)
        signalmenu.addAction(segmentSignal)

        self.segmentermap.mapped.connect(self.toggle_segmenter)

        saveSignal = QAction("save", self)
        saveSignal.triggered.connect(self._controller.get_wpathsignal)
        signalmenu.addAction(saveSignal)

        # peak menu
        peakmenu = menubar.addMenu("peaks")

        findPeaks = QAction("find", self)
        findPeaks.triggered.connect(self._controller.find_peaks)
        peakmenu.addAction(findPeaks)

        autocorrectPeaks = QAction("autocorrect", self)
        autocorrectPeaks.triggered.connect(self._controller.autocorrect_peaks)
        peakmenu.addAction(autocorrectPeaks)

        savePeaks = QAction("save", self)
        savePeaks.triggered.connect(self._controller.get_wpathpeaks)
        peakmenu.addAction(savePeaks)

        loadPeaks = QAction("load", self)
        loadPeaks.triggered.connect(self._controller.get_rpathpeaks)
        peakmenu.addAction(loadPeaks)

        # stats menu
        statsmenu = menubar.addMenu("statistics")

        calculateStats = QAction("calculate", self)
        calculateStats.triggered.connect(self._controller.calculate_stats)
        statsmenu.addAction(calculateStats)

        saveStats = QAction("save", self)
        saveStats.triggered.connect(self._controller.get_wpathstats)
        statsmenu.addAction(saveStats)

        # set up status bar to display error messages and current file path
        self.statusBar = QStatusBar()
        self.setStatusBar(self.statusBar)
        self.progressBar = QProgressBar(self)
        self.progressBar.setRange(0, 1)
        self.statusBar.addPermanentWidget(self.progressBar)
        self.currentFile = QLabel()
        self.statusBar.addPermanentWidget(self.currentFile)

        # set up the central widget containing the plot and navigationtoolbar
        self.centwidget = QWidget()
        self.setCentralWidget(self.centwidget)

        # connect canvas0 to keyboard and mouse input for peak editing;
        # only widgets (e.g. canvas) that currently have focus capture
        # keyboard input: "You must enable keyboard focus for a widget if
        # it processes keyboard events."
        self.canvas0.setFocusPolicy(Qt.ClickFocus)
        self.canvas0.setFocus()
        self.canvas0.mpl_connect("key_press_event",
                                 self._controller.edit_peaks)
        self.canvas0.mpl_connect("button_press_event", self.get_xcursor)

        # arrange the three figure canvases in splitter object
        self.splitter = QSplitter(Qt.Vertical)
        # setting opaque resizing to false is important, since resizing gets
        # very slow otherwise once axes are populated
        self.splitter.setOpaqueResize(False)
        self.splitter.addWidget(self.canvas0)
        self.splitter.addWidget(self.canvas1)
        self.splitter.addWidget(self.canvas2)
        self.splitter.setChildrenCollapsible(False)

        # define GUI layout
        self.vlayout0 = QVBoxLayout(self.centwidget)
        self.vlayout1 = QVBoxLayout()
        self.vlayoutA = QFormLayout()
        self.vlayoutB = QFormLayout()
        self.vlayoutC = QVBoxLayout()
        self.vlayoutD = QVBoxLayout()
        self.hlayout0 = QHBoxLayout()

        self.optionsgroupA = QGroupBox("processing options")
        self.vlayoutA.addRow(self.modmenulabel, self.modmenu)
        self.vlayoutA.addRow(self.batchmenulabel, self.batchmenu)
        self.optionsgroupA.setLayout(self.vlayoutA)

        self.optionsgroupB = QGroupBox("channels")
        self.vlayoutB.addRow(self.sigchanmenulabel, self.sigchanmenu)
        self.vlayoutB.addRow(self.markerchanmenulabel, self.markerchanmenu)
        self.optionsgroupB.setLayout(self.vlayoutB)

        self.optionsgroupC = QGroupBox("peaks")
        self.vlayoutC.addWidget(self.editcheckbox)
        self.vlayoutC.addWidget(self.savecheckbox)
        self.vlayoutC.addWidget(self.correctcheckbox)
        self.optionsgroupC.setLayout(self.vlayoutC)

        self.optionsgroupD = QGroupBox("select statistics for saving")
        self.vlayoutD.addWidget(self.periodcheckbox)
        self.vlayoutD.addWidget(self.ratecheckbox)
        self.vlayoutD.addWidget(self.tidalampcheckbox)
        self.optionsgroupD.setLayout(self.vlayoutD)

        self.vlayout1.addWidget(self.optionsgroupA)
        self.vlayout1.addWidget(self.optionsgroupB)
        self.vlayout1.addWidget(self.optionsgroupC)
        self.vlayout1.addWidget(self.optionsgroupD)
        self.optionsgroupwidget = QWidget()
        self.optionsgroupwidget.setLayout(self.vlayout1)
        self.optionsgroup = QDockWidget("configurations", self)
        self.optionsgroup.setAllowedAreas(Qt.LeftDockWidgetArea)
        self.toggleoptionsgroup = self.optionsgroup.toggleViewAction()
        self.toggleoptionsgroup.setText("show/hide configurations")
        menubar.addAction(self.toggleoptionsgroup)
        self.optionsgroup.setWidget(self.optionsgroupwidget)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.optionsgroup)

        self.vlayout0.addWidget(self.splitter)

        self.hlayout0.addWidget(self.navitools)
        self.vlayout0.addLayout(self.hlayout0)

        ##############################################
        # connect output widgets to external signals #
        ##############################################
        self._model.signal_changed.connect(self.plot_signal)
        self._model.marker_changed.connect(self.plot_marker)
        self._model.peaks_changed.connect(self.plot_peaks)
        self._model.period_changed.connect(self.plot_period)
        self._model.rate_changed.connect(self.plot_rate)
        self._model.tidalamp_changed.connect(self.plot_tidalamp)
        self._model.path_changed.connect(self.display_path)
        self._model.segment_changed.connect(self.plot_segment)
        self._model.status_changed.connect(self.display_status)
        self._model.progress_changed.connect(self.display_progress)
        self._model.model_reset.connect(self.reset_plot)

    ###########
    # methods #
    ###########

    def plot_signal(self, value):
        self.ax00.clear()
        self.ax00.relim()
        # reset navitools history
        self.navitools.update()
        self.line00 = self.ax00.plot(self._model.sec, value, zorder=1)
        self.ax00.set_xlabel("seconds", fontsize="large", fontweight="heavy")
        self.canvas0.draw()
#        print("plot_signal listening")
#        print(self.ax0.collections, self.ax0.patches, self.ax0.artists)

    def plot_peaks(self, value):
        # self.scat is listed in ax.collections
        if self.ax00.collections:
            self.ax00.collections[0].remove()
        self.scat = self.ax00.scatter(self._model.sec[value],
                                      self._model.signal[value],
                                      c="m",
                                      zorder=2)
        self.canvas0.draw()
#        print("plot_peaks listening")
#        print(self.ax0.collections, self.ax0.patches, self.ax0.artists)

    def plot_segment(self, value):
        # If an invalid signal has been selected reset the segmenter interface.
        if value is None:
            self.toggle_segmenter(1)
            return
        if self.ax00.patches:  # self.segementspan is listed in ax.patches
            self.ax00.patches[0].remove()
        self.segmentspan = self.ax00.axvspan(value[0],
                                             value[1],
                                             color="m",
                                             alpha=0.25)
        self.canvas0.draw()
        self.confirmedit.setEnabled(True)
#        print(self.ax0.collections, self.ax0.patches, self.ax0.artists)

    def plot_marker(self, value):
        self.ax10.clear()
        self.ax10.relim()
        self.line10 = self.ax10.plot(value[0], value[1])
        self.canvas1.draw()
#        print("plot_marker listening")

    def plot_period(self, value):
        self.ax20.clear()
        self.ax20.relim()
        self.navitools.home()
        if self._model.savestats["period"]:
            self.line20 = self.ax20.plot(self._model.sec, value, c="m")
        else:
            self.line20 = self.ax20.plot(self._model.sec, value)
        self.ax20.set_ylim(bottom=min(value), top=max(value))
        self.ax20.set_title("period", pad=0, fontweight="heavy")
        self.ax20.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()
#        print("plot_period listening")

    def plot_rate(self, value):
        self.ax21.clear()
        self.ax21.relim()
        self.navitools.home()
        if self._model.savestats["rate"]:
            self.line21 = self.ax21.plot(self._model.sec, value, c="m")
        else:
            self.line21 = self.ax21.plot(self._model.sec, value)
        self.ax21.set_ylim(bottom=min(value), top=max(value))
        self.ax21.set_title("rate", pad=0, fontweight="heavy")
        self.ax21.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()
#        print("plot_rate listening")

    def plot_tidalamp(self, value):
        self.ax22.clear()
        self.ax22.relim()
        self.navitools.home()
        if self._model.savestats["tidalamp"]:
            self.line22 = self.ax22.plot(self._model.sec, value, c="m")
        else:
            self.line22 = self.ax22.plot(self._model.sec, value)
        self.ax22.set_ylim(bottom=min(value), top=max(value))
        self.ax22.set_title("amplitude", pad=0, fontweight="heavy")
        self.ax22.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()
#        print("plot_tidalamp listening")

    def display_path(self, value):
        self.currentFile.setText(value)

    def display_status(self, status):
        # display status until new status is set
        self.statusBar.showMessage(status)

    def display_progress(self, value):
        # if value is 0, the progressbar indicates a busy state
        self.progressBar.setRange(0, value)

    def toggle_segmenter(self, value):
        if not self._model.loaded:
            return
        # Open segmenter when called from signalmenu or clear segmenter
        # upon selection of invalid segment.
        if value == 1:
            self.segmenter.setVisible(True)
            self.confirmedit.setEnabled(False)
            self.startedit.clear()
            self.endedit.clear()
            if self.ax00.patches:
                self.ax00.patches[0].remove()
                self.canvas0.draw()
        # Close segmenter after segment has been confirmed.
        elif value == 0:
            self.segmenter.setVisible(False)
            if self.ax00.patches:
                self.ax00.patches[0].remove()
                self.canvas0.draw()
        # Close segmenter after segmentation has been aborted (reset
        # segment).
        elif value == 2:
            self._model.set_segment([0,
                                     0])  # This will reset the model to None
            self.segmenter.setVisible(False)
            if self.ax00.patches:
                self.ax00.patches[0].remove()
                self.canvas0.draw()

    def enable_segmentedit(self):
        # disable peak editing to avoid interference
        self.editcheckbox.setChecked(False)
        if self.startedit.hasFocus():
            self.segmentcursor = "start"
        elif self.endedit.hasFocus():
            self.segmentcursor = "end"

    def set_customheader(self):
        """Populate the customheader with inputs from the customfiledialog"""

        # Check if one of the mandatory fields is missing.
        mandatoryfields = self.signaledit.text() and self.headerrowsedit.text(
        ) and self.sfreqedit.text()

        if not mandatoryfields:
            self._model.status = (
                "Please provide values for 'biosignal column'"
                ", 'number of header rows' and 'sampling"
                " rate'.")
            return

        seps = {"comma": ",", "tab": "\t", "colon": ":", "space": " "}
        self._model.customheader = dict.fromkeys(
            self._model.customheader, None
        )  # reset header here since it cannot be reset in controller.get_fpaths()

        self._model.customheader["signalidx"] = int(self.signaledit.text())
        self._model.customheader["skiprows"] = int(self.headerrowsedit.text())
        self._model.customheader["sfreq"] = int(self.sfreqedit.text())
        self._model.customheader["separator"] = seps[
            self.separatormenu.currentText()]
        if self.markeredit.text():  # not mandatory
            self._model.customheader["markeridx"] = int(self.markeredit.text())

        self.customfiledialog.done(QDialog.Accepted)  # close the dialog window
        self._controller.get_fpaths()  # move on to file selection

    def get_xcursor(self, event):
        # event.button 1 corresponds to left mouse button
        if event.button != 1:
            return
        # limit number of decimal places to two
        if self.segmentcursor == "start":
            self.startedit.selectAll()
            self.startedit.insert("{:.2f}".format(event.xdata))
        elif self.segmentcursor == "end":
            self.endedit.selectAll()
            self.endedit.insert("{:.2f}".format(event.xdata))
        # disable segment cursor again after value has been set
        self.segmentcursor = False

    def select_stats(self, event):
        """
        select or deselect statistics to be saved; toggle boolean with xor
        operator ^=, toggle color with dictionary
        """
        self._model.savestats[event] ^= True
        line = None
        if event == "period":
            if self.line20:
                line = self.line20[0]
        elif event == "rate":
            if self.line21:
                line = self.line21[0]
        elif event == "tidalamp":
            if self.line22:
                line = self.line22[0]
        if line:
            line.set_color(self.togglecolors[line.get_color()])
        self.canvas2.draw()

    def toggle_options(self, event):
        if event in ["ECG", "PPG"]:
            self.tidalampcheckbox.setEnabled(False)
            self.tidalampcheckbox.setChecked(False)
            self.ax22.set_visible(False)
            self.canvas2.draw()
        elif event == "RESP":
            self.tidalampcheckbox.setEnabled(True)
            self.ax22.set_visible(True)
            self.canvas2.draw()
        elif event == "multiple files":
            self.editcheckbox.setEnabled(False)
            self.editcheckbox.setChecked(False)
            self.savecheckbox.setEnabled(True)
            self.correctcheckbox.setEnabled(True)
            self.markerchanmenu.setEnabled(False)
        elif event == "single file":
            self.editcheckbox.setEnabled(True)
            self.markerchanmenu.setEnabled(True)
            self.savecheckbox.setEnabled(False)
            self.savecheckbox.setChecked(False)
            self.correctcheckbox.setEnabled(False)
            self.correctcheckbox.setChecked(False)

    def reset_plot(self):
        self.ax00.clear()
        self.ax00.relim()
        self.line00 = None
        self.scat = None
        self.segmentspan = None
        self.ax10.clear()
        self.ax10.relim()
        self.line10 = None
        self.ax20.clear()
        self.ax20.relim()
        self.line20 = None
        self.ax21.clear()
        self.ax21.relim()
        self.line21 = None
        self.ax22.clear()
        self.ax22.relim()
        self.line22 = None
        self.canvas0.draw()
        self.canvas1.draw()
        self.canvas2.draw()
        self.navitools.update()
        self.currentFile.clear()
コード例 #4
0
ファイル: main.py プロジェクト: akai10tsuki/mkvbatchmultiplex
    def _initMenu(self):  # pylint: disable=too-many-statements

        menuBar = QMenuBar()
        self.menuItems = []

        # File SubMenu
        self.fileMenu = QMenuWidget(Text.txt0020)
        exitIcon = self.style().standardIcon(QStyle.SP_DialogCloseButton)

        # Preferences
        actPreferences = QActionWidget(
            Text.txt0050,
            self,
            shortcut=Text.txt0026,
            statusTip=Text.txt0051,
        )
        actPreferences.triggered.connect(self.setPreferences.getPreferences)

        # Exit application
        actExit = QActionWidget(
            exitIcon,
            Text.txt0021,
            self,
            shortcut=Text.txt0022,
            statusTip=Text.txt0023,
        )
        actExit.triggered.connect(self.close)

        # Abort
        actAbort = QActionWidget(Text.txt0024, self, statusTip=Text.txt0025)
        actAbort.triggered.connect(abort)

        # Add actions to SubMenu
        self.fileMenu.addAction(actPreferences)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(actExit)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(actAbort)
        menuBar.addMenu(self.fileMenu)
        self.menuItems.append(self.fileMenu)
        self.menuItems.append(actPreferences)
        self.menuItems.append(actExit)
        self.menuItems.append(actAbort)

        # Help Menu
        actHelpContents = QActionWidget(Text.txt0061, self, textSuffix="...")
        actHelpContents.triggered.connect(lambda: _help(self.appDirectory, 0))
        actHelpUsing = QActionWidget(Text.txt0062, self)
        actHelpUsing.triggered.connect(lambda: _help(self.appDirectory, 1))
        actAbout = QActionWidget(Text.txt0063, self)
        actAbout.triggered.connect(self.about)
        actAboutQt = QActionWidget(Text.txt0064, self)
        actAboutQt.triggered.connect(self.aboutQt)
        self.helpMenu = QMenuWidget(Text.txt0060)
        self.helpMenu.addAction(actHelpContents)
        self.helpMenu.addAction(actHelpUsing)
        self.helpMenu.addSeparator()
        self.helpMenu.addAction(actAbout)
        self.helpMenu.addAction(actAboutQt)
        menuBar.addMenu(self.helpMenu)
        self.menuItems.append(self.helpMenu)
        self.menuItems.append(actHelpContents)
        self.menuItems.append(actHelpUsing)
        self.menuItems.append(actAbout)
        self.menuItems.append(actAboutQt)

        # Init status var
        statusBar = QStatusBar()
        statusBar.addPermanentWidget(VerticalLine())
        statusBar.addPermanentWidget(self.jobsLabel)
        statusBar.addPermanentWidget(VerticalLine())
        statusBar.addPermanentWidget(self.progressBar)
        statusBar.addPermanentWidget(self.progressSpin)

        self.setMenuBar(menuBar)
        self.setStatusBar(statusBar)
コード例 #5
0
ファイル: WTreeEdit.py プロジェクト: EOMYS-Public/pyleecan
class WTreeEdit(QWidget):
    """TreeEdit widget is to show and edit all of the pyleecan objects data."""

    # Signals
    dataChanged = Signal()

    def __init__(self, obj, *args, **kwargs):
        QWidget.__init__(self, *args, **kwargs)

        self.class_dict = ClassInfo().get_dict()
        self.treeDict = None  # helper to track changes
        self.obj = obj  # the object
        self.is_save_needed = False

        self.model = TreeEditModel(obj)

        self.setupUi()

        # === Signals ===
        self.selectionModel.selectionChanged.connect(self.onSelectionChanged)
        self.treeView.collapsed.connect(self.onItemCollapse)
        self.treeView.expanded.connect(self.onItemExpand)
        self.treeView.customContextMenuRequested.connect(self.openContextMenu)
        self.model.dataChanged.connect(self.onDataChanged)
        self.dataChanged.connect(self.setSaveNeeded)

        # === Finalize ===
        # set 'root' the selected item and resize columns
        self.treeView.setCurrentIndex(self.treeView.model().index(0, 0))
        self.treeView.resizeColumnToContents(0)

    def setupUi(self):
        """Setup the UI"""
        # === Widgets ===
        # TreeView
        self.treeView = QTreeView()
        # self.treeView.rootNode = model.invisibleRootItem()
        self.treeView.setModel(self.model)
        self.treeView.setAlternatingRowColors(False)

        # self.treeView.setColumnWidth(0, 150)
        self.treeView.setMinimumWidth(100)

        self.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.selectionModel = self.treeView.selectionModel()

        self.statusBar = QStatusBar()
        self.statusBar.setSizeGripEnabled(False)
        self.statusBar.setSizePolicy(QSizePolicy.Expanding,
                                     QSizePolicy.Maximum)
        self.statusBar.setStyleSheet(
            "QStatusBar {border: 1px solid rgb(200, 200, 200)}")
        self.saveLabel = QLabel("unsaved")
        self.saveLabel.setVisible(False)
        self.statusBar.addPermanentWidget(self.saveLabel)

        # Splitters
        self.leftSplitter = QSplitter()
        self.leftSplitter.setStretchFactor(0, 0)
        self.leftSplitter.setStretchFactor(1, 1)

        # === Layout ===
        # Horizontal Div.
        self.hLayout = QVBoxLayout()
        self.hLayout.setContentsMargins(0, 0, 0, 0)
        self.hLayout.setSpacing(0)

        # add widgets to layout
        self.hLayout.addWidget(self.leftSplitter)
        self.hLayout.addWidget(self.statusBar)

        # add widgets
        self.leftSplitter.addWidget(self.treeView)

        self.setLayout(self.hLayout)

    def update(self, obj):
        """Check if object has changed and update tree in case."""
        if not obj is self.obj:
            self.obj = obj
            self.model = TreeEditModel(obj)
            self.treeView.setModel(self.model)
            self.model.dataChanged.connect(self.onDataChanged)
            self.selectionModel = self.treeView.selectionModel()
            self.selectionModel.selectionChanged.connect(
                self.onSelectionChanged)
            self.treeView.setCurrentIndex(self.treeView.model().index(0, 0))
            self.setSaveNeeded(True)

    def setSaveNeeded(self, state=True):
        self.is_save_needed = state
        self.saveLabel.setVisible(state)

    def openContextMenu(self, point):
        """Generate and open context the menu at the given point position."""
        index = self.treeView.indexAt(point)
        pos = QtGui.QCursor.pos()

        if not index.isValid():
            return

        # get the data
        item = self.model.item(index)
        obj_info = self.model.get_obj_info(item)

        # init the menu
        menu = TreeEditContextMenu(obj_dict=obj_info, parent=self)
        menu.exec_(pos)

        self.onSelectionChanged(self.selectionModel.selection())

    def onItemCollapse(self, index):
        """Slot for item collapsed"""
        # dynamic resize
        for ii in range(3):
            self.treeView.resizeColumnToContents(ii)

    def onItemExpand(self, index):
        """Slot for item expand"""
        # dynamic resize
        for ii in range(3):
            self.treeView.resizeColumnToContents(ii)

    def onDataChanged(self, first=None, last=None):
        """Slot for changed data"""
        self.dataChanged.emit()
        self.onSelectionChanged(self.selectionModel.selection())

    def onSelectionChanged(self, itemSelection):
        """Slot for changed item selection"""
        # get the index
        if itemSelection.indexes():
            index = itemSelection.indexes()[0]
        else:
            index = self.treeView.model().index(0, 0)
            self.treeView.setCurrentIndex(index)
            return

        # get the data
        item = self.model.item(index)
        obj = item.object()
        typ = type(obj).__name__
        obj_info = self.model.get_obj_info(item)
        ref_typ = obj_info["ref_typ"] if obj_info else None

        # set statusbar information on class typ
        msg = f"{typ} (Ref: {ref_typ})" if ref_typ else f"{typ}"
        self.statusBar.showMessage(msg)

        # --- choose the respective widget by class type ---
        # numpy array -> table editor
        if typ == "ndarray":
            widget = WTableData(obj, editable=True)
            widget.dataChanged.connect(self.dataChanged.emit)

        elif typ == "MeshSolution":
            widget = WMeshSolution(obj)  # only a view (not editable)

        # list (no pyleecan type, non empty) -> table editor
        # TODO add another widget for lists of non 'primitive' types (e.g. DataND)
        elif isinstance(obj, list) and not self.isListType(ref_typ) and obj:
            widget = WTableData(obj, editable=True)
            widget.dataChanged.connect(self.dataChanged.emit)

        # generic editor
        else:
            # widget = SimpleInputWidget().generate(obj)
            widget = WTableParameterEdit(obj)
            widget.dataChanged.connect(self.dataChanged.emit)

        # show the widget
        if self.leftSplitter.widget(1) is None:
            self.leftSplitter.addWidget(widget)
        else:
            self.leftSplitter.replaceWidget(1, widget)
            widget.setParent(
                self.leftSplitter)  # workaround for PySide2 replace bug
            widget.show()
        pass

    def isListType(self, typ):
        if not typ:
            return False
        return typ[0] == "[" and typ[-1] == "]" and typ[1:-1] in self.class_dict

    def isDictType(self, typ):
        if not typ:
            return False
        return typ[0] == "{" and typ[-1] == "}" and typ[1:-1] in self.class_dict
コード例 #6
0
ファイル: ui_fe14_map_editor.py プロジェクト: thane98/paragon
class Ui_FE14MapEditor(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.toolbar = QToolBar()
        self.toggle_coordinate_type_action = QAction("Toggle Coordinate Type")
        self.refresh_action = QAction("Refresh")
        self.refresh_action.setShortcut(QKeySequence("Ctrl+R"))
        self.copy_spawn_action = QAction("Copy Spawn")
        self.copy_spawn_action.setShortcut(QKeySequence("Ctrl+C"))
        self.paste_spawn_action = QAction("Paste Spawn")
        self.paste_spawn_action.setShortcut(QKeySequence("Ctrl+V"))
        self.add_spawn_action = QAction("Add Spawn")
        self.delete_spawn_action = QAction("Delete Spawn")
        self.add_group_action = QAction("Add Group")
        self.delete_group_action = QAction("Delete Group")
        self.add_tile_action = QAction("Add Tile")
        self.toggle_mode_action = QAction("Toggle Mode")
        self.undo_action = QAction("Undo")
        self.undo_action.setShortcut(QKeySequence("Ctrl+Z"))
        self.redo_action = QAction("Redo")
        self.redo_action.setShortcut(QKeySequence("Ctrl+Shift+Z"))
        self.toolbar.addActions(
            [self.toggle_coordinate_type_action, self.refresh_action])
        self.toolbar.addSeparator()
        self.toolbar.addActions([
            self.copy_spawn_action, self.paste_spawn_action,
            self.add_spawn_action, self.delete_spawn_action,
            self.add_group_action, self.delete_group_action
        ])
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.add_tile_action)
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.toggle_mode_action)
        self.toolbar.addSeparator()
        self.toolbar.addActions([self.undo_action, self.redo_action])
        self.addToolBar(self.toolbar)

        self.model_view = QTreeView()
        self.model_view.setHeaderHidden(True)
        self.grid = FE14MapGrid()
        self.grid_scroll = QScrollArea()
        self.grid_scroll.setWidgetResizable(True)
        self.grid_scroll.setWidget(self.grid)
        self.tile_list = QListView()
        self.terrain_pane = FE14TerrainEditorPane()
        self.spawn_pane = FE14SpawnEditorPane()

        self.model_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.model_view_context_menu = QMenu()
        self.model_view_context_menu.addActions(
            [self.toggle_coordinate_type_action, self.refresh_action])
        self.model_view_context_menu.addSeparator()
        self.model_view_context_menu.addActions([
            self.copy_spawn_action, self.paste_spawn_action,
            self.add_spawn_action, self.delete_spawn_action,
            self.add_group_action, self.delete_group_action
        ])
        self.model_view_context_menu.addSeparator()
        self.model_view_context_menu.addAction(self.add_tile_action)
        self.model_view_context_menu.addSeparator()
        self.model_view_context_menu.addAction(self.toggle_mode_action)
        self.model_view_context_menu.addSeparator()
        self.model_view_context_menu.addActions(
            [self.undo_action, self.redo_action])

        self.status_bar = QStatusBar()
        self.coordinate_type_label = QLabel()
        self.status_bar.addPermanentWidget(self.coordinate_type_label)
        self.setStatusBar(self.status_bar)

        self.main_widget = QSplitter()
        self.main_widget.setOrientation(QtCore.Qt.Horizontal)
        self.main_widget.addWidget(self.model_view)
        self.main_widget.addWidget(self.grid_scroll)
        self.main_widget.addWidget(self.spawn_pane)
        self.main_widget.addWidget(self.terrain_pane)
        self.setCentralWidget(self.main_widget)
コード例 #7
0
class TabContent(QWidget):
    NewTabRequested = Signal(WebSearch)
    TitleRequested = Signal((str, str), ())

    def __init__(self, index, source_model, memory_database, parent=None):
        super().__init__(parent)

        self._database_table = ItemsTable(memory_database,
                                          "tab" + str(index),
                                          drop_on_del=True)
        # self._database_table = ItemsTable(Database("./test.db"), "items", drop_on_del=False)
        self._database_table.Clear()

        self._last_search = None

        self._fetcher = Fetcher()
        self._fetcher.FetchingStarted.connect(self._HandleFetchingStarted)
        self._fetcher.BatchReady.connect(self._database_table.AddData)
        self._fetcher.FetchingFinished.connect(self._HandleFetchingCompleted)
        self._fetcher.FetchingStopped.connect(self._HandleFetchingStopped)
        self._fetcher.FetchingError.connect(self._HandleFetchingError)

        self._source_panel = SourcePanel()
        self._source_panel.setModel(source_model)
        self._source_panel.WebSourceSelected.connect(
            self._HandleWebSourceSelected)
        self._source_panel.LocalSourceSelected.connect(
            self._HandleLocalSourceSelected)

        self._search_bar = SearchBar()
        self._search_bar.QueryLaunched.connect(self._HandleQueryLaunched)
        self._search_bar.StopPressed.connect(self.StopFetching)

        self._splitter = TableItemSplitter()
        self._splitter.table_view.NewTabRequested.connect(self.NewTabRequested)

        self._filter_bar = FilterBar()
        self._filter_bar.TextChanged.connect(self._splitter.table_model.Filter)

        self._status_bar = QStatusBar()
        self._splitter.table_view.StatusUpdated.connect(
            self._HandleStatusUpdated)
        self._search_status_bar = SearchStatus()
        self._status_bar.addPermanentWidget(self._search_status_bar)
        self._fetcher.BatchProgress.connect(
            self._search_status_bar.SetProgress)

        self._active_source = None
        self._source_panel.SelectSource(INSPIRE_SOURCE)

        self._SetupUI()

        self.setFocusProxy(self._search_bar)

    def _SetupUI(self):
        main_layout = QVBoxLayout()
        search_filter_widget = QWidget()
        search_filter_layout = QHBoxLayout(search_filter_widget)
        central_widget = QWidget()
        central_layout = QVBoxLayout(central_widget)

        panel_splitter = QSplitter(Qt.Horizontal)
        panel_splitter.setHandleWidth(4)
        panel_splitter.addWidget(self._source_panel)
        panel_splitter.addWidget(central_widget)
        panel_splitter.setStretchFactor(0, 0)
        panel_splitter.setStretchFactor(1, 1)
        panel_splitter.moveSplitter(LEFT_PANEL_WIDTH, 1)
        panel_splitter.setSizePolicy(QSizePolicy.Expanding,
                                     QSizePolicy.Expanding)

        search_filter_layout.addWidget(self._search_bar)
        search_filter_layout.addWidget(self._filter_bar)
        search_filter_layout.setContentsMargins(0, 0, 0, 0)

        central_layout.addWidget(search_filter_widget)
        central_layout.addWidget(self._splitter)
        central_layout.addWidget(self._status_bar)
        central_layout.setContentsMargins(0, 0, 0, 0)

        main_layout.addWidget(panel_splitter)
        self.setLayout(main_layout)

    def RunSearch(self, search):
        self._source_panel.SelectSource(search.source)
        self._search_bar.LaunchQuery(search.query)

    def StopFetching(self):
        self._fetcher.Stop()

    def _HandleWebSourceSelected(self, source):
        if self._last_search is None:
            self._splitter.table_view.SetShowCitations(source.has_cites)

        if isinstance(self._active_source, WebSource):
            self._active_source = source
            return

        self._active_source = source
        self._search_bar.SetQueryEditEnabled(True)
        self._filter_bar.clear()

        if self._last_search is None:
            self.TitleRequested[()].emit()
        else:
            self._search_bar.SetQuery(self._last_search.query)
            self.TitleRequested.emit(self._last_search.source.icon,
                                     self._last_search.title)
            self._splitter.table_view.SetShowCitations(
                self._last_search.source.has_cites)
        self._search_status_bar.text.show()

        self._splitter.SetTable(self._database_table)

    def _HandleLocalSourceSelected(self, source, tags):
        if not isinstance(self._active_source, LocalSource):
            self.StopFetching()
            self._search_bar.Clear()
            self._search_bar.SetQueryEditEnabled(False)
            self._splitter.table_view.SetShowCitations(False)

        self._active_source = source
        title = source.name + (": " + tags[0].name if tags != [] else "")
        self.TitleRequested.emit(icons.DATABASE, title)
        self._filter_bar.clear()
        self._search_status_bar.text.hide()
        self._splitter.SetLocalSource(source, tags)

    def _HandleQueryLaunched(self, query):
        if not isinstance(self._active_source, WebSource):
            return

        self._database_table.Clear()
        self._filter_bar.clear()

        search = self._active_source.CreateSearch(query)
        self._last_search = search
        self.TitleRequested.emit(search.source.icon, search.title)
        self._splitter.table_view.SetShowCitations(
            self._active_source.has_cites)
        self._fetcher.Fetch(search.source.plugin, search.query)

    def _HandleFetchingStarted(self):
        self._search_bar.SetStopEnabled(True)
        self._search_status_bar.text.clear()
        self._search_status_bar.ShowProgress()

    def _HandleFetchingCompleted(self, records_number):
        self._HandleFetchingEnded("Fetching completed: " +
                                  str(records_number) + " records found")

    def _HandleFetchingStopped(self):
        self._HandleFetchingEnded("Fetching stopped")

    def _HandleFetchingError(self, error):
        self._HandleFetchingEnded("Fetching error: " + error)

    def _HandleFetchingEnded(self, message):
        self._search_bar.SetStopEnabled(False)
        self._search_status_bar.HideProgress()
        self._search_status_bar.text.setText(message)

    def _HandleStatusUpdated(self, total, selected):
        self._status_bar.showMessage(
            str(total) + " item" + ("" if total == 1 else "s") +
            (", " + str(selected) + " selected" if total > 0 else ""))
コード例 #8
0
ファイル: view.py プロジェクト: marshb/biopeaks
class View(QMainWindow):
    """View component of the MVC application.

    Presents the state of the application as well as the available means of
    interaction. Receives updates about the state from the Model and informs
    Controller about user interactions.

    """
    def __init__(self, model, controller):
        """Define GUI elements and their layout.

        Parameters
        ----------
        model : QObject
            Model component of the MVC application.
        controller : QObject
            Controller component of the MVC application.
        """
        super().__init__()

        self._model = model
        self._controller = controller
        self.segmentcursor = False
        self.togglecolors = {"#1f77b4": "m", "m": "#1f77b4"}

        self.setWindowTitle("biopeaks")
        self.setGeometry(50, 50, 1750, 750)
        self.setWindowIcon(QIcon(":/python_icon.png"))

        # Figure for biosignal.
        self.figure0 = Figure()
        self.canvas0 = FigureCanvas(self.figure0)
        # Enforce minimum height, otherwise resizing with self.splitter causes
        # mpl to throw an error because figure is resized to height 0. The
        # widget can still be fully collapsed with self.splitter.
        self.canvas0.setMinimumHeight(1)  # in pixels
        self.ax00 = self.figure0.add_subplot(1, 1, 1)
        self.ax00.set_frame_on(False)
        self.figure0.subplots_adjust(left=0.04, right=0.98, bottom=0.25)
        self.line00 = None
        self.scat = None
        self.segmentspan = None

        # Figure for marker.
        self.figure1 = Figure()
        self.canvas1 = FigureCanvas(self.figure1)
        self.canvas1.setMinimumHeight(1)
        self.ax10 = self.figure1.add_subplot(1, 1, 1, sharex=self.ax00)
        self.ax10.get_xaxis().set_visible(False)
        self.ax10.set_frame_on(False)
        self.figure1.subplots_adjust(left=0.04, right=0.98)
        self.line10 = None

        # Figure for statistics.
        self.figure2 = Figure()
        self.canvas2 = FigureCanvas(self.figure2)
        self.canvas2.setMinimumHeight(1)
        self.ax20 = self.figure2.add_subplot(3, 1, 1, sharex=self.ax00)
        self.ax20.get_xaxis().set_visible(False)
        self.ax20.set_frame_on(False)
        self.line20 = None
        self.ax21 = self.figure2.add_subplot(3, 1, 2, sharex=self.ax00)
        self.ax21.get_xaxis().set_visible(False)
        self.ax21.set_frame_on(False)
        self.line21 = None
        self.ax22 = self.figure2.add_subplot(3, 1, 3, sharex=self.ax00)
        self.ax22.get_xaxis().set_visible(False)
        self.ax22.set_frame_on(False)
        self.line22 = None
        self.figure2.subplots_adjust(left=0.04, right=0.98)

        self.navitools = CustomNavigationToolbar(self.canvas0, self)

        # Peak editing.
        self.editcheckbox = QCheckBox("editable", self)
        self.editcheckbox.stateChanged.connect(self._model.set_peakseditable)

        # Peak saving during batch processing.
        self.savecheckbox = QCheckBox("save during batch processing", self)
        self.savecheckbox.stateChanged.connect(self._model.set_savebatchpeaks)

        # Peak auto-correction during batch processing.
        self.correctcheckbox = QCheckBox("correct during batch processing",
                                         self)
        self.correctcheckbox.stateChanged.connect(
            self._model.set_correctbatchpeaks)

        # Selection of stats for saving.
        self.periodcheckbox = QCheckBox("period", self)
        self.periodcheckbox.stateChanged.connect(
            lambda: self.select_stats("period"))
        self.ratecheckbox = QCheckBox("rate", self)
        self.ratecheckbox.stateChanged.connect(
            lambda: self.select_stats("rate"))
        self.tidalampcheckbox = QCheckBox("tidal amplitude", self)
        self.tidalampcheckbox.stateChanged.connect(
            lambda: self.select_stats("tidalamp"))

        # Channel selection.
        self.sigchanmenulabel = QLabel("biosignal")
        self.sigchanmenu = QComboBox(self)
        self.sigchanmenu.addItem("A1")
        self.sigchanmenu.addItem("A2")
        self.sigchanmenu.addItem("A3")
        self.sigchanmenu.addItem("A4")
        self.sigchanmenu.addItem("A5")
        self.sigchanmenu.addItem("A6")
        self.sigchanmenu.currentTextChanged.connect(self._model.set_signalchan)
        self._model.set_signalchan(
            self.sigchanmenu.currentText())  # initialize with default value

        self.markerchanmenulabel = QLabel("marker")
        self.markerchanmenu = QComboBox(self)
        self.markerchanmenu.addItem("none")
        self.markerchanmenu.addItem("I1")
        self.markerchanmenu.addItem("I2")
        self.markerchanmenu.addItem("A1")
        self.markerchanmenu.addItem("A2")
        self.markerchanmenu.addItem("A3")
        self.markerchanmenu.addItem("A4")
        self.markerchanmenu.addItem("A5")
        self.markerchanmenu.addItem("A6")
        self.markerchanmenu.currentTextChanged.connect(
            self._model.set_markerchan)
        self._model.set_markerchan(self.markerchanmenu.currentText())

        # Processing mode.
        self.batchmenulabel = QLabel("mode")
        self.batchmenu = QComboBox(self)
        self.batchmenu.addItem("single file")
        self.batchmenu.addItem("multiple files")
        self.batchmenu.currentTextChanged.connect(self._model.set_batchmode)
        self.batchmenu.currentTextChanged.connect(self.toggle_options)
        self._model.set_batchmode(self.batchmenu.currentText())
        self.toggle_options(self.batchmenu.currentText())

        # Modality selection.
        self.modmenulabel = QLabel("modality")
        self.modmenu = QComboBox(self)
        self.modmenu.addItem("ECG")
        self.modmenu.addItem("PPG")
        self.modmenu.addItem("RESP")
        self.modmenu.currentTextChanged.connect(self._model.set_modality)
        self.modmenu.currentTextChanged.connect(self.toggle_options)
        self._model.set_modality(self.modmenu.currentText())
        self.toggle_options(self.modmenu.currentText())

        # Segment selection. This widget can be openend / set visible from
        # the menu and closed from within itself (see mapping of segmentermap).
        self.segmentermap = QSignalMapper(self)
        self.segmenter = QDockWidget("select a segment", self)
        self.segmenter.setFeatures(
            QDockWidget.NoDockWidgetFeatures
        )  # disable closing such that widget can only be closed by confirming selection or custom button
        regex = QRegExp(
            "[0-9]*\.?[0-9]{4}")  # Limit number of decimals to four

        validator = QRegExpValidator(regex)

        self.startlabel = QLabel("start")
        self.startedit = QLineEdit()
        self.startedit.setValidator(validator)

        self.endlabel = QLabel("end")
        self.endedit = QLineEdit()
        self.endedit.setValidator(validator)

        segmentfromcursor = QAction(QIcon(":/mouse_icon.png"),
                                    "select with mouse", self)
        segmentfromcursor.triggered.connect(self.enable_segmentedit)
        self.startedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition)
        self.endedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition)

        self.previewedit = QPushButton("preview segment")
        lambdafn = lambda: self._model.set_segment(
            [self.startedit.text(), self.endedit.text()])
        self.previewedit.clicked.connect(lambdafn)

        self.confirmedit = QPushButton("confirm segment")
        self.confirmedit.clicked.connect(self._controller.segment_dataset)
        self.confirmedit.clicked.connect(self.segmentermap.map)
        self.segmentermap.setMapping(self.confirmedit, 0)

        self.abortedit = QPushButton("abort segmentation")
        self.abortedit.clicked.connect(self.segmentermap.map)
        self.segmentermap.setMapping(self.abortedit,
                                     2)  # resets the segment to None

        self.segmenterlayout = QFormLayout()
        self.segmenterlayout.addRow(self.startlabel, self.startedit)
        self.segmenterlayout.addRow(self.endlabel, self.endedit)
        self.segmenterlayout.addRow(self.previewedit)
        self.segmenterlayout.addRow(self.confirmedit)
        self.segmenterlayout.addRow(self.abortedit)
        self.segmenterwidget = QWidget()
        self.segmenterwidget.setLayout(self.segmenterlayout)
        self.segmenter.setWidget(self.segmenterwidget)

        self.segmenter.setVisible(False)
        self.segmenter.setAllowedAreas(Qt.RightDockWidgetArea)
        self.addDockWidget(Qt.RightDockWidgetArea, self.segmenter)

        # Custom file dialog.
        regex = QRegExp("[1-9][0-9]")
        validator = QRegExpValidator(regex)

        self.signallabel = QLabel("biosignal column")
        self.signaledit = QLineEdit()
        self.signaledit.setValidator(validator)

        self.markerlabel = QLabel("marker column")
        self.markeredit = QLineEdit()
        self.markeredit.setValidator(validator)

        regex = QRegExp("[0-9]{2}")
        validator = QRegExpValidator(regex)

        self.headerrowslabel = QLabel("number of header rows")
        self.headerrowsedit = QLineEdit()
        self.headerrowsedit.setValidator(validator)

        regex = QRegExp("[0-9]{5}")
        validator = QRegExpValidator(regex)

        self.sfreqlabel = QLabel("sampling rate")
        self.sfreqedit = QLineEdit()
        self.sfreqedit.setValidator(validator)

        self.separatorlabel = QLabel("column separator")
        self.separatormenu = QComboBox(self)
        self.separatormenu.addItem("comma")
        self.separatormenu.addItem("tab")
        self.separatormenu.addItem("colon")
        self.separatormenu.addItem("space")

        self.continuecustomfile = QPushButton("continue loading file")
        self.continuecustomfile.clicked.connect(self.set_customheader)

        self.customfiledialog = QDialog()
        self.customfiledialog.setWindowTitle("custom file info")
        self.customfiledialog.setWindowIcon(QIcon(":/file_icon.png"))
        self.customfiledialog.setWindowFlags(
            Qt.WindowCloseButtonHint
        )  # remove help button by only setting close button
        self.customfilelayout = QFormLayout()
        self.customfilelayout.addRow(self.signallabel, self.signaledit)
        self.customfilelayout.addRow(self.markerlabel, self.markeredit)
        self.customfilelayout.addRow(self.separatorlabel, self.separatormenu)
        self.customfilelayout.addRow(self.headerrowslabel, self.headerrowsedit)
        self.customfilelayout.addRow(self.sfreqlabel, self.sfreqedit)
        self.customfilelayout.addRow(self.continuecustomfile)
        self.customfiledialog.setLayout(self.customfilelayout)

        # Layout.
        menubar = self.menuBar()

        signalmenu = menubar.addMenu("biosignal")

        openSignal = signalmenu.addMenu("load")
        openEDF = QAction("EDF", self)
        openEDF.triggered.connect(lambda: self._model.set_filetype("EDF"))
        openEDF.triggered.connect(self._controller.load_channels)
        openSignal.addAction(openEDF)
        openOpenSignals = QAction("OpenSignals", self)
        openOpenSignals.triggered.connect(
            lambda: self._model.set_filetype("OpenSignals"))
        openOpenSignals.triggered.connect(self._controller.load_channels)
        openSignal.addAction(openOpenSignals)
        openCustom = QAction("Custom", self)
        openCustom.triggered.connect(
            lambda: self._model.set_filetype("Custom"))
        openCustom.triggered.connect(lambda: self.customfiledialog.exec_())
        openSignal.addAction(openCustom)

        segmentSignal = QAction("select segment", self)
        segmentSignal.triggered.connect(self.segmentermap.map)
        self.segmentermap.setMapping(segmentSignal, 1)
        signalmenu.addAction(segmentSignal)

        self.segmentermap.mapped.connect(self.toggle_segmenter)

        saveSignal = QAction("save", self)
        saveSignal.triggered.connect(self._controller.save_channels)
        signalmenu.addAction(saveSignal)

        peakmenu = menubar.addMenu("peaks")

        findPeaks = QAction("find", self)
        findPeaks.triggered.connect(self._controller.find_peaks)
        peakmenu.addAction(findPeaks)

        autocorrectPeaks = QAction("autocorrect", self)
        autocorrectPeaks.triggered.connect(self._controller.autocorrect_peaks)
        peakmenu.addAction(autocorrectPeaks)

        savePeaks = QAction("save", self)
        savePeaks.triggered.connect(self._controller.save_peaks)
        peakmenu.addAction(savePeaks)

        loadPeaks = QAction("load", self)
        loadPeaks.triggered.connect(self._controller.load_peaks)
        peakmenu.addAction(loadPeaks)

        statsmenu = menubar.addMenu("statistics")

        calculateStats = QAction("calculate", self)
        calculateStats.triggered.connect(self._controller.calculate_stats)
        statsmenu.addAction(calculateStats)

        saveStats = QAction("save", self)
        saveStats.triggered.connect(self._controller.save_stats)
        statsmenu.addAction(saveStats)

        self.statusBar = QStatusBar()
        self.setStatusBar(self.statusBar)
        self.progressBar = QProgressBar(self)
        self.progressBar.setRange(0, 1)
        self.statusBar.addPermanentWidget(self.progressBar)
        self.currentFile = QLabel()
        self.statusBar.addPermanentWidget(self.currentFile)

        self.centwidget = QWidget()  # contains figures and navigationtoolbar
        self.setCentralWidget(self.centwidget)

        self.canvas0.setFocusPolicy(
            Qt.ClickFocus
        )  # only widgets (e.g. canvas) that currently have focus capture keyboard input
        self.canvas0.setFocus()
        self.canvas0.mpl_connect(
            "key_press_event", self._controller.edit_peaks
        )  # connect canvas to keyboard input for peak editing
        self.canvas0.mpl_connect(
            "button_press_event",
            self.get_xcursor)  # connect canvas to mouse input for peak editing

        self.splitter = QSplitter(
            Qt.Vertical
        )  # arrange the three figure canvases in splitter object
        self.splitter.setOpaqueResize(
            False)  # resizing gets very slow otherwise once axes are populated
        self.splitter.addWidget(self.canvas0)
        self.splitter.addWidget(self.canvas1)
        self.splitter.addWidget(self.canvas2)
        self.splitter.setChildrenCollapsible(False)

        self.vlayout0 = QVBoxLayout(self.centwidget)
        self.vlayout1 = QVBoxLayout()
        self.vlayoutA = QFormLayout()
        self.vlayoutB = QFormLayout()
        self.vlayoutC = QVBoxLayout()
        self.vlayoutD = QVBoxLayout()
        self.hlayout0 = QHBoxLayout()

        self.optionsgroupA = QGroupBox("processing options")
        self.vlayoutA.addRow(self.modmenulabel, self.modmenu)
        self.vlayoutA.addRow(self.batchmenulabel, self.batchmenu)
        self.optionsgroupA.setLayout(self.vlayoutA)

        self.optionsgroupB = QGroupBox("channels")
        self.vlayoutB.addRow(self.sigchanmenulabel, self.sigchanmenu)
        self.vlayoutB.addRow(self.markerchanmenulabel, self.markerchanmenu)
        self.optionsgroupB.setLayout(self.vlayoutB)

        self.optionsgroupC = QGroupBox("peaks")
        self.vlayoutC.addWidget(self.editcheckbox)
        self.vlayoutC.addWidget(self.savecheckbox)
        self.vlayoutC.addWidget(self.correctcheckbox)
        self.optionsgroupC.setLayout(self.vlayoutC)

        self.optionsgroupD = QGroupBox("select statistics for saving")
        self.vlayoutD.addWidget(self.periodcheckbox)
        self.vlayoutD.addWidget(self.ratecheckbox)
        self.vlayoutD.addWidget(self.tidalampcheckbox)
        self.optionsgroupD.setLayout(self.vlayoutD)

        self.vlayout1.addWidget(self.optionsgroupA)
        self.vlayout1.addWidget(self.optionsgroupB)
        self.vlayout1.addWidget(self.optionsgroupC)
        self.vlayout1.addWidget(self.optionsgroupD)
        self.optionsgroupwidget = QWidget()
        self.optionsgroupwidget.setLayout(self.vlayout1)
        self.optionsgroup = QDockWidget("configurations", self)
        self.optionsgroup.setAllowedAreas(Qt.LeftDockWidgetArea)
        self.toggleoptionsgroup = self.optionsgroup.toggleViewAction()
        self.toggleoptionsgroup.setText("show/hide configurations")
        menubar.addAction(self.toggleoptionsgroup)
        self.optionsgroup.setWidget(self.optionsgroupwidget)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.optionsgroup)

        self.vlayout0.addWidget(self.splitter)

        self.hlayout0.addWidget(self.navitools)
        self.vlayout0.addLayout(self.hlayout0)

        # Subscribe to updates from the Model.
        self._model.signal_changed.connect(self.plot_signal)
        self._model.marker_changed.connect(self.plot_marker)
        self._model.peaks_changed.connect(self.plot_peaks)
        self._model.period_changed.connect(self.plot_period)
        self._model.rate_changed.connect(self.plot_rate)
        self._model.tidalamp_changed.connect(self.plot_tidalamp)
        self._model.path_changed.connect(self.display_path)
        self._model.segment_changed.connect(self.plot_segment)
        self._model.status_changed.connect(self.display_status)
        self._model.progress_changed.connect(self.display_progress)
        self._model.model_reset.connect(self.reset_plot)

    def plot_signal(self, signal):
        """Plot the biosignal.

        Receives updates in signal from Model.

        Parameters
        ----------
        signal : ndarray of float
            Vector representing the biosignal.

        See Also
        --------
        model.Model.signal
        """
        self.ax00.clear()
        self.ax00.relim()
        self.navitools.update()  # reset navitools history
        self.line00 = self.ax00.plot(self._model.sec, signal, zorder=1)
        self.ax00.set_xlabel("seconds", fontsize="large", fontweight="heavy")
        self.canvas0.draw()

    def plot_peaks(self, peaks):
        """Plot the extrema.

        Receives updates in peaks from Model.

        Parameters
        ----------
        peaks : ndarray of int
            Vector representing the extrema.

        See Also
        --------
        model.Model.peaks
        """
        if self.ax00.collections:  # self.scat is listed in ax.collections
            self.ax00.collections[0].remove()
        self.scat = self.ax00.scatter(self._model.sec[peaks],
                                      self._model.signal[peaks],
                                      c="m",
                                      zorder=2)
        self.canvas0.draw()

    def plot_segment(self, segment):
        """Show preview of segment.

        Receives updates in segment from Model.

        Parameters
        ----------
        segment : list of float
            The start and end of the segment in seconds.

        See Also
        --------
        model.Model.segment
        """
        if segment is None:  # if an invalid segment has been selected reset the segmenter interface
            self.toggle_segmenter(1)
            return
        if self.ax00.patches:  # self.segementspan is listed in ax.patches
            self.ax00.patches[0].remove()
        self.segmentspan = self.ax00.axvspan(segment[0],
                                             segment[1],
                                             color="m",
                                             alpha=0.25)
        self.canvas0.draw()
        self.confirmedit.setEnabled(True)

    def plot_marker(self, marker):
        """Plot the marker channel.

        Receives updates in marker from Model.

        Parameters
        ----------
        marker : list of ndarray
            Seconds element is vector representing the marker channel and first
            element is a vector representing the seconds associated with each
            sample in the marker channel.

        See Also
        --------
        model.Model.marker
        """
        self.ax10.clear()
        self.ax10.relim()
        self.line10 = self.ax10.plot(marker[0], marker[1])
        self.canvas1.draw()

    def plot_period(self, period):
        """Plot instantaneous period.

        Receives updates in period from Model.

        Parameters
        ----------
        period : ndarray of float
            Vector representing the instantaneous period.

        See Also
        --------
        model.Model.periodintp
        """
        self.ax20.clear()
        self.ax20.relim()
        self.navitools.home()
        if self._model.savestats["period"]:
            self.line20 = self.ax20.plot(self._model.sec, period, c="m")
        else:
            self.line20 = self.ax20.plot(self._model.sec, period)
        self.ax20.set_ylim(bottom=min(period), top=max(period))
        self.ax20.set_title("period", pad=0, fontweight="heavy")
        self.ax20.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()

    def plot_rate(self, rate):
        """Plot instantaneous rate.

        Receives updates in rate from Model.

        Parameters
        ----------
        rate : ndarray of float
            Vector representing the instantaneous rate.

        See Also
        --------
        model.Model.rateintp
        """
        self.ax21.clear()
        self.ax21.relim()
        self.navitools.home()
        if self._model.savestats["rate"]:
            self.line21 = self.ax21.plot(self._model.sec, rate, c="m")
        else:
            self.line21 = self.ax21.plot(self._model.sec, rate)
        self.ax21.set_ylim(bottom=min(rate), top=max(rate))
        self.ax21.set_title("rate", pad=0, fontweight="heavy")
        self.ax21.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()

    def plot_tidalamp(self, tidalamp):
        """Plot instantaneous tidal amplitude.

        Receives updates in tidal amplitude from Model.

        Parameters
        ----------
        tidalamp : ndarray of float
            Vector representing the instantaneous tidal amplitude.

        See Also
        --------
        model.Model.tidalampintp
        """
        self.ax22.clear()
        self.ax22.relim()
        self.navitools.home()
        if self._model.savestats["tidalamp"]:
            self.line22 = self.ax22.plot(self._model.sec, tidalamp, c="m")
        else:
            self.line22 = self.ax22.plot(self._model.sec, tidalamp)
        self.ax22.set_ylim(bottom=min(tidalamp), top=max(tidalamp))
        self.ax22.set_title("amplitude", pad=0, fontweight="heavy")
        self.ax22.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()

    def display_path(self, path):
        """Display the path to the current dataset.

        Receives update in path from Model.

        Parameters
        ----------
        path : str
            The path to the file containing the current dataset.

        See Also
        --------
        model.Model.rpathsignal
        """
        self.currentFile.setText(path)

    def display_status(self, status):
        """Display a status message.

        Receives updates in status message from Model.

        Parameters
        ----------
        status : str
            A status message.

        See Also
        --------
        model.Model.status
        """
        self.statusBar.showMessage(status)

    def display_progress(self, progress):
        """Display task progress.

        Receives updates in progress from Model.

        Parameters
        ----------
        progress : int
            Integer indicating the current task progress.

        See Also
        --------
        model.Model.progress, controller.Worker, controller.threaded
        """
        self.progressBar.setRange(
            0, progress)  # indicates busy state if progress is 0

    def toggle_segmenter(self, visibility_state):
        """Toggle visibility of segmenter widget.

        Parameters
        ----------
        visibility_state : int
            Update in state of the segmenter widget's visibility.
        """
        if not self._model.loaded:
            return
        if visibility_state == 1:  # open segmenter when called from signalmenu or clear segmenter upon selection of invalid segment
            self.segmenter.setVisible(True)
            self.confirmedit.setEnabled(False)
            self.startedit.clear()
            self.endedit.clear()
        elif visibility_state == 0:  # close segmenter after segment has been confirmed
            self.segmenter.setVisible(False)
        elif visibility_state == 2:  # close segmenter after segmentation has been aborted (reset segment)
            self._model.set_segment([0, 0])
            self.segmenter.setVisible(False)
        if self.ax00.patches:
            self.ax00.patches[0].remove()
            self.canvas0.draw()

    def enable_segmentedit(self):
        """Associate cursor position with a specific segmenter text field.

        Regulate if cursor position is associated with editing the start or
        end of a segment.
        """
        self.editcheckbox.setChecked(
            False)  # disable peak editing to avoid interference
        if self.startedit.hasFocus():
            self.segmentcursor = "start"
        elif self.endedit.hasFocus():
            self.segmentcursor = "end"

    def get_xcursor(self, mouse_event):
        """Retrieve input to segmenter text fields from cursor position.

        Retrieve the start or end of a segment in seconds from the current
        cursor position.

        Parameters
        ----------
        mouse_event : MouseEvent
            Event containing information about the current cursor position
            in data coordinates.

        See Also
        --------
        matplotlib.backend_bases.MouseEvent
        """
        if mouse_event.button != 1:  # 1 = left mouse button
            return
        if self.segmentcursor == "start":
            self.startedit.selectAll()
            self.startedit.insert("{:.2f}".format(
                mouse_event.xdata))  # limit number of decimal places to two
        elif self.segmentcursor == "end":
            self.endedit.selectAll()
            self.endedit.insert("{:.2f}".format(mouse_event.xdata))
        self.segmentcursor = False  # disable segment cursor again after value has been set

    def set_customheader(self):
        """Populate the customheader with inputs from the customfiledialog."""
        mandatoryfields = self.signaledit.text() and self.headerrowsedit.text(
        ) and self.sfreqedit.text(
        )  # check if one of the mandatory fields is missing

        if not mandatoryfields:
            self._model.status = (
                "Please provide values for 'biosignal column'"
                ", 'number of header rows' and 'sampling"
                " rate'.")
            return

        seps = {"comma": ",", "tab": "\t", "colon": ":", "space": " "}
        self._model.customheader = dict.fromkeys(
            self._model.customheader, None
        )  # reset header here since it cannot be reset in controller.load_chanels

        self._model.customheader["signalidx"] = int(self.signaledit.text())
        self._model.customheader["skiprows"] = int(self.headerrowsedit.text())
        self._model.customheader["sfreq"] = int(self.sfreqedit.text())
        self._model.customheader["separator"] = seps[
            self.separatormenu.currentText()]
        if self.markeredit.text():  # not mandatory
            self._model.customheader["markeridx"] = int(self.markeredit.text())

        self.customfiledialog.done(QDialog.Accepted)  # close the dialog window
        self._controller.load_channels()  # move on to file selection

    def select_stats(self, statistic):
        """Select statistics to be saved.

        Parameters
        ----------
        statistic : str
            The selected statistic.
        """
        self._model.savestats[
            statistic] ^= True  # toggle boolean with xor operator
        line = None
        if statistic == "period":
            if self.line20:
                line = self.line20[0]
        elif statistic == "rate":
            if self.line21:
                line = self.line21[0]
        elif statistic == "tidalamp":
            if self.line22:
                line = self.line22[0]
        if line:
            line.set_color(self.togglecolors[line.get_color()])
        self.canvas2.draw()

    def toggle_options(self, state):
        """Toggle availability of configuration options.

        Based on current state.

        Parameters
        ----------
        state : str
            The aspect of the current state to which the availability of
            configuration options needs to be adapted.
        """
        if state in ["ECG", "PPG"]:
            self.tidalampcheckbox.setEnabled(False)
            self.tidalampcheckbox.setChecked(False)
            self.ax22.set_visible(False)
            self.canvas2.draw()
        elif state == "RESP":
            self.tidalampcheckbox.setEnabled(True)
            self.ax22.set_visible(True)
            self.canvas2.draw()
        elif state == "multiple files":
            self.editcheckbox.setEnabled(False)
            self.editcheckbox.setChecked(False)
            self.savecheckbox.setEnabled(True)
            self.correctcheckbox.setEnabled(True)
            self.markerchanmenu.setEnabled(False)
        elif state == "single file":
            self.editcheckbox.setEnabled(True)
            self.markerchanmenu.setEnabled(True)
            self.savecheckbox.setEnabled(False)
            self.savecheckbox.setChecked(False)
            self.correctcheckbox.setEnabled(False)
            self.correctcheckbox.setChecked(False)

    def reset_plot(self):
        """Reset plot elements associated with the current dataset."""
        self.ax00.clear()
        self.ax00.relim()
        self.line00 = None
        self.scat = None
        self.segmentspan = None
        self.ax10.clear()
        self.ax10.relim()
        self.line10 = None
        self.ax20.clear()
        self.ax20.relim()
        self.line20 = None
        self.ax21.clear()
        self.ax21.relim()
        self.line21 = None
        self.ax22.clear()
        self.ax22.relim()
        self.line22 = None
        self.canvas0.draw()
        self.canvas1.draw()
        self.canvas2.draw()
        self.navitools.update()
        self.currentFile.clear()