class ClickToFlashWhitelistDialog(QDialog, Ui_ClickToFlashWhitelistDialog):
    """
    Class implementing a dialog to manage the ClickToFlash whitelist.
    """
    def __init__(self, whitelist, parent=None):
        """
        Constructor
        
        @param whitelist list of whitelisted hosts (list of string)
        @param parent reference to the parent widget (QWidget)
        """
        super(ClickToFlashWhitelistDialog, self).__init__(parent)
        self.setupUi(self)
        
        self.iconLabel.setPixmap(UI.PixmapCache.getPixmap("flashBlock48.png"))
        
        self.__model = QStringListModel(whitelist[:], self)
        self.__model.sort(0)
        self.__proxyModel = QSortFilterProxyModel(self)
        self.__proxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.__proxyModel.setSourceModel(self.__model)
        self.whitelist.setModel(self.__proxyModel)
        
        self.searchEdit.textChanged.connect(
            self.__proxyModel.setFilterFixedString)
        
        self.removeButton.clicked.connect(self.whitelist.removeSelected)
        self.removeAllButton.clicked.connect(self.whitelist.removeAll)
    
    @pyqtSlot()
    def on_addButton_clicked(self):
        """
        Private slot to add an entry to the whitelist.
        """
        host, ok = QInputDialog.getText(
            self,
            self.tr("ClickToFlash Whitelist"),
            self.tr("Enter host name to add to whitelist:"),
            QLineEdit.Normal)
        if ok and host != "" and host not in self.__model.stringList():
            self.__model.insertRow(self.__model.rowCount())
            self.__model.setData(
                self.__model.index(self.__model.rowCount() - 1), host)
            self.__model.sort(0)
    
    def getWhitelist(self):
        """
        Public method to get the whitelisted hosts.
        
        @return list of whitelisted hosts (list of string)
        """
        return self.__model.stringList()
class E5ErrorMessageFilterDialog(QDialog, Ui_E5ErrorMessageFilterDialog):
    """
    Class implementing a dialog to manage the list of messages to be ignored.
    """
    def __init__(self, messageFilters, parent=None):
        """
        Constructor
        
        @param messageFilters list of message filters to be edited
            (list of strings)
        @param parent reference to the parent widget (QWidget)
        """
        super(E5ErrorMessageFilterDialog, self).__init__(parent)
        self.setupUi(self)
        
        self.__model = QStringListModel(messageFilters, self)
        self.__model.sort(0)
        self.__proxyModel = QSortFilterProxyModel(self)
        self.__proxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.__proxyModel.setSourceModel(self.__model)
        self.filterList.setModel(self.__proxyModel)
        
        self.searchEdit.textChanged.connect(
            self.__proxyModel.setFilterFixedString)
        
        self.removeButton.clicked.connect(self.filterList.removeSelected)
        self.removeAllButton.clicked.connect(self.filterList.removeAll)
    
    @pyqtSlot()
    def on_addButton_clicked(self):
        """
        Private slot to add an entry to the list.
        """
        filter, ok = QInputDialog.getText(
            self,
            self.tr("Error Messages Filter"),
            self.tr("Enter message filter to add to the list:"),
            QLineEdit.Normal)
        if ok and filter != "" and filter not in self.__model.stringList():
            self.__model.insertRow(self.__model.rowCount())
            self.__model.setData(
                self.__model.index(self.__model.rowCount() - 1), filter)
            self.__model.sort(0)
    
    def getFilters(self):
        """
        Public method to get the list of message filters.
        
        @return error message filters (list of strings)
        """
        return self.__model.stringList()[:]
示例#3
0
class NoCacheHostsDialog(QDialog, Ui_NoCacheHostsDialog):
    """
    Class implementing a dialog to manage the list of hosts not to be cached.
    """
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        """
        super(NoCacheHostsDialog, self).__init__(parent)
        self.setupUi(self)
        
        self.__model = QStringListModel(
            Preferences.getHelp("NoCacheHosts"), self)
        self.__model.sort(0)
        self.__proxyModel = QSortFilterProxyModel(self)
        self.__proxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.__proxyModel.setSourceModel(self.__model)
        self.noCacheList.setModel(self.__proxyModel)
        
        self.searchEdit.textChanged.connect(
            self.__proxyModel.setFilterFixedString)
        
        self.removeButton.clicked.connect(self.noCacheList.removeSelected)
        self.removeAllButton.clicked.connect(self.noCacheList.removeAll)
    
    @pyqtSlot()
    def on_addButton_clicked(self):
        """
        Private slot to add an entry to the list.
        """
        host, ok = QInputDialog.getText(
            self,
            self.tr("Not Cached Hosts"),
            self.tr("Enter host name to add to the list:"),
            QLineEdit.Normal)
        if ok and host != "" and host not in self.__model.stringList():
            self.__model.insertRow(self.__model.rowCount())
            self.__model.setData(
                self.__model.index(self.__model.rowCount() - 1), host)
            self.__model.sort(0)
    
    def accept(self):
        """
        Public method to accept the dialog data.
        """
        Preferences.setHelp("NoCacheHosts", self.__model.stringList())
        
        super(NoCacheHostsDialog, self).accept()
示例#4
0
class MainWindow(QMainWindow):
    """MNELAB main window."""
    def __init__(self, model):
        """Initialize MNELAB main window.

        Parameters
        ----------
        model : mnelab.model.Model instance
            The main window needs to connect to a model containing all data
            sets. This decouples the GUI from the data (model/view).
        """
        super().__init__()

        self.model = model  # data model
        self.setWindowTitle("MNELAB")

        # restore settings
        settings = read_settings()
        self.recent = settings["recent"]  # list of recent files
        if settings["geometry"]:
            self.restoreGeometry(settings["geometry"])
        else:
            self.setGeometry(300, 300, 1000, 750)  # default window size
            self.move(QApplication.desktop().screen().rect().center() -
                      self.rect().center())  # center window
        if settings["state"]:
            self.restoreState(settings["state"])

        # initialize menus
        file_menu = self.menuBar().addMenu("&File")
        file_menu.addAction(
            "&Open...",
            lambda: self.open_file(model.load, "Open raw", SUPPORTED_FORMATS),
            QKeySequence.Open)
        self.recent_menu = file_menu.addMenu("Open recent")
        self.recent_menu.aboutToShow.connect(self._update_recent_menu)
        self.recent_menu.triggered.connect(self._load_recent)
        if not self.recent:
            self.recent_menu.setEnabled(False)
        self.close_file_action = file_menu.addAction("&Close",
                                                     self.model.remove_data,
                                                     QKeySequence.Close)
        self.close_all_action = file_menu.addAction("Close all",
                                                    self.close_all)
        file_menu.addSeparator()
        self.import_bad_action = file_menu.addAction(
            "Import bad channels...", lambda: self.import_file(
                model.import_bads, "Import bad channels", "*.csv"))
        self.import_events_action = file_menu.addAction(
            "Import events...", lambda: self.import_file(
                model.import_events, "Import events", "*.csv"))
        self.import_anno_action = file_menu.addAction(
            "Import annotations...", lambda: self.import_file(
                model.import_annotations, "Import annotations", "*.csv"))
        self.import_ica_action = file_menu.addAction(
            "Import &ICA...", lambda: self.open_file(
                model.import_ica, "Import ICA", "*.fif *.fif.gz"))
        file_menu.addSeparator()
        self.export_raw_action = file_menu.addAction(
            "Export &raw...",
            lambda: self.export_file(model.export_raw, "Export raw", "*.fif"))
        self.export_bad_action = file_menu.addAction(
            "Export &bad channels...", lambda: self.export_file(
                model.export_bads, "Export bad channels", "*.csv"))
        self.export_events_action = file_menu.addAction(
            "Export &events...", lambda: self.export_file(
                model.export_events, "Export events", "*.csv"))
        self.export_anno_action = file_menu.addAction(
            "Export &annotations...", lambda: self.export_file(
                model.export_annotations, "Export annotations", "*.csv"))
        self.export_ica_action = file_menu.addAction(
            "Export ICA...", lambda: self.export_file(
                model.export_ica, "Export ICA", "*.fif *.fif.gz"))
        file_menu.addSeparator()
        file_menu.addAction("&Quit", self.close, QKeySequence.Quit)

        edit_menu = self.menuBar().addMenu("&Edit")
        self.pick_chans_action = edit_menu.addAction("Pick &channels...",
                                                     self.pick_channels)
        self.chan_props_action = edit_menu.addAction("Channel &properties...",
                                                     self.channel_properties)
        self.set_montage_action = edit_menu.addAction("Set &montage...",
                                                      self.set_montage)
        edit_menu.addSeparator()
        self.setref_action = edit_menu.addAction("&Set reference...",
                                                 self.set_reference)
        edit_menu.addSeparator()
        self.events_action = edit_menu.addAction("Events...", self.edit_events)

        plot_menu = self.menuBar().addMenu("&Plot")
        self.plot_raw_action = plot_menu.addAction("&Raw data", self.plot_raw)
        self.plot_psd_action = plot_menu.addAction(
            "&Power spectral "
            "density...", self.plot_psd)
        self.plot_montage_action = plot_menu.addAction("Current &montage",
                                                       self.plot_montage)
        plot_menu.addSeparator()
        self.plot_ica_components_action = plot_menu.addAction(
            "ICA components...", self.plot_ica_components)

        tools_menu = self.menuBar().addMenu("&Tools")
        self.filter_action = tools_menu.addAction("&Filter data...",
                                                  self.filter_data)
        self.find_events_action = tools_menu.addAction("Find &events...",
                                                       self.find_events)
        self.run_ica_action = tools_menu.addAction("Run &ICA...", self.run_ica)

        view_menu = self.menuBar().addMenu("&View")
        statusbar_action = view_menu.addAction("Statusbar",
                                               self._toggle_statusbar)
        statusbar_action.setCheckable(True)

        help_menu = self.menuBar().addMenu("&Help")
        help_menu.addAction("&About", self.show_about)
        help_menu.addAction("About &Qt", self.show_about_qt)

        # set up data model for sidebar (list of open files)
        self.names = QStringListModel()
        self.names.dataChanged.connect(self._update_names)
        splitter = QSplitter()
        self.sidebar = QListView()
        self.sidebar.setFrameStyle(QFrame.NoFrame)
        self.sidebar.setFocusPolicy(Qt.NoFocus)
        self.sidebar.setModel(self.names)
        self.sidebar.clicked.connect(self._update_data)
        splitter.addWidget(self.sidebar)
        self.infowidget = InfoWidget()
        splitter.addWidget(self.infowidget)
        width = splitter.size().width()
        splitter.setSizes((width * 0.3, width * 0.7))
        self.setCentralWidget(splitter)

        self.status_label = QLabel()
        self.statusBar().addPermanentWidget(self.status_label)
        if settings["statusbar"]:
            self.statusBar().show()
            statusbar_action.setChecked(True)
        else:
            self.statusBar().hide()
            statusbar_action.setChecked(False)

        self.setAcceptDrops(True)
        self.data_changed()

    def data_changed(self):
        # update sidebar
        self.names.setStringList(self.model.names)
        self.sidebar.setCurrentIndex(self.names.index(self.model.index))

        # update info widget
        if self.model.data:
            self.infowidget.set_values(self.model.get_info())
        else:
            self.infowidget.clear()

        # update status bar
        if self.model.data:
            mb = self.model.nbytes / 1024**2
            self.status_label.setText("Total Memory: {:.2f} MB".format(mb))
        else:
            self.status_label.clear()

        # toggle actions
        if len(self.model) == 0:  # disable if no data sets are currently open
            enabled = False
        else:
            enabled = True
        self.close_file_action.setEnabled(enabled)
        self.close_all_action.setEnabled(enabled)
        self.export_raw_action.setEnabled(enabled)
        if self.model.data:
            bads = bool(self.model.current["raw"].info["bads"])
            self.export_bad_action.setEnabled(enabled and bads)
            events = self.model.current["events"] is not None
            self.export_events_action.setEnabled(enabled and events)
            annot = self.model.current["raw"].annotations is not None
            self.export_anno_action.setEnabled(enabled and annot)
            montage = bool(self.model.current["montage"])
            self.plot_montage_action.setEnabled(enabled and montage)
            ica = bool(self.model.current["ica"])
            self.export_ica_action.setEnabled(enabled and ica)
            self.plot_ica_components_action.setEnabled(enabled and ica
                                                       and montage)
            self.events_action.setEnabled(enabled and events)
        else:
            self.export_bad_action.setEnabled(enabled)
            self.export_events_action.setEnabled(enabled)
            self.export_anno_action.setEnabled(enabled)
            self.plot_montage_action.setEnabled(enabled)
            self.export_ica_action.setEnabled(enabled)
            self.plot_ica_components_action.setEnabled(enabled)
            self.events_action.setEnabled(enabled)
        self.import_bad_action.setEnabled(enabled)
        self.import_events_action.setEnabled(enabled)
        self.import_anno_action.setEnabled(enabled)
        self.pick_chans_action.setEnabled(enabled)
        self.chan_props_action.setEnabled(enabled)
        self.set_montage_action.setEnabled(enabled)
        self.plot_raw_action.setEnabled(enabled)
        self.plot_psd_action.setEnabled(enabled)
        self.filter_action.setEnabled(enabled)
        self.setref_action.setEnabled(enabled)
        self.find_events_action.setEnabled(enabled)
        self.run_ica_action.setEnabled(enabled)
        self.import_ica_action.setEnabled(enabled)

        # add to recent files
        if len(self.model) > 0:
            self._add_recent(self.model.current["fname"])

    def open_file(self, f, text, ffilter):
        """Open file."""
        fname = QFileDialog.getOpenFileName(self, text, filter=ffilter)[0]
        if fname:
            f(fname)

    def export_file(self, f, text, ffilter):
        """Export to file."""
        fname = QFileDialog.getSaveFileName(self, text, filter=ffilter)[0]
        if fname:
            f(fname)

    def import_file(self, f, text, ffilter):
        """Import file."""
        fname = QFileDialog.getOpenFileName(self, text, filter=ffilter)[0]
        if fname:
            try:
                f(fname)
            except LabelsNotFoundError as e:
                QMessageBox.critical(self, "Channel labels not found", str(e))
            except InvalidAnnotationsError as e:
                QMessageBox.critical(self, "Invalid annotations", str(e))

    def close_all(self):
        """Close all currently open data sets."""
        msg = QMessageBox.question(self, "Close all data sets",
                                   "Close all data sets?")
        if msg == QMessageBox.Yes:
            while len(self.model) > 0:
                self.model.remove_data()

    def pick_channels(self):
        """Pick channels in current data set."""
        channels = self.model.current["raw"].info["ch_names"]
        dialog = PickChannelsDialog(self, channels, selected=channels)
        if dialog.exec_():
            picks = [item.data(0) for item in dialog.channels.selectedItems()]
            drops = set(channels) - set(picks)
            if drops:
                self.auto_duplicate()
                self.model.drop_channels(drops)
                self.model.history.append(f"raw.drop({drops})")

    def channel_properties(self):
        """Show channel properties dialog."""
        info = self.model.current["raw"].info
        dialog = ChannelPropertiesDialog(self, info)
        if dialog.exec_():
            dialog.model.sort(0)
            bads = []
            renamed = {}
            types = {}
            for i in range(dialog.model.rowCount()):
                new_label = dialog.model.item(i, 1).data(Qt.DisplayRole)
                old_label = info["ch_names"][i]
                if new_label != old_label:
                    renamed[old_label] = new_label
                new_type = dialog.model.item(i, 2).data(Qt.DisplayRole).lower()
                old_type = channel_type(info, i).lower()
                if new_type != old_type:
                    types[new_label] = new_type
                if dialog.model.item(i, 3).checkState() == Qt.Checked:
                    bads.append(info["ch_names"][i])
            self.model.set_channel_properties(bads, renamed, types)

    def set_montage(self):
        """Set montage."""
        montages = mne.channels.get_builtin_montages()
        # TODO: currently it is not possible to remove an existing montage
        dialog = MontageDialog(self,
                               montages,
                               selected=self.model.current["montage"])
        if dialog.exec_():
            name = dialog.montages.selectedItems()[0].data(0)
            montage = mne.channels.read_montage(name)
            ch_names = self.model.current["raw"].info["ch_names"]
            # check if at least one channel name matches a name in the montage
            if set(ch_names) & set(montage.ch_names):
                self.model.set_montage(name)
            else:
                QMessageBox.critical(
                    self, "No matching channel names",
                    "Channel names defined in the montage do "
                    "not match any channel name in the data.")

    def edit_events(self):
        pos = self.model.current["events"][:, 0].tolist()
        desc = self.model.current["events"][:, 2].tolist()
        dialog = EventsDialog(self, pos, desc)
        if dialog.exec_():
            pass

    def plot_raw(self):
        """Plot raw data."""
        events = self.model.current["events"]
        nchan = self.model.current["raw"].info["nchan"]
        fig = self.model.current["raw"].plot(events=events,
                                             n_channels=nchan,
                                             title=self.model.current["name"],
                                             show=False)
        self.model.history.append("raw.plot(n_channels={})".format(nchan))
        win = fig.canvas.manager.window
        win.setWindowTitle("Raw data")
        win.findChild(QStatusBar).hide()
        win.installEventFilter(self)  # detect if the figure is closed

        # prevent closing the window with the escape key
        try:
            key_events = fig.canvas.callbacks.callbacks["key_press_event"][8]
        except KeyError:
            pass
        else:  # this requires MNE >=0.15
            key_events.func.keywords["params"]["close_key"] = None

        fig.show()

    def plot_psd(self):
        """Plot power spectral density (PSD)."""
        fig = self.model.current["raw"].plot_psd(average=False,
                                                 spatial_colors=False,
                                                 show=False)
        win = fig.canvas.manager.window
        win.setWindowTitle("Power spectral density")
        fig.show()

    def plot_montage(self):
        """Plot current montage."""
        fig = self.model.current["raw"].plot_sensors(show_names=True,
                                                     show=False)
        win = fig.canvas.manager.window
        win.setWindowTitle("Montage")
        win.findChild(QStatusBar).hide()
        win.findChild(QToolBar).hide()
        fig.show()

    def plot_ica_components(self):
        self.model.current["ica"].plot_components()

    def run_ica(self):
        """Run ICA calculation."""
        try:
            import picard
        except ImportError:
            have_picard = False
        else:
            have_picard = True

        dialog = RunICADialog(self, self.model.current["raw"].info["nchan"],
                              have_picard)

        if dialog.exec_():
            calc = CalcDialog(self, "Calculating ICA", "Calculating ICA.")
            method = dialog.method.currentText()
            exclude_bad_segments = dialog.exclude_bad_segments.isChecked()
            ica = mne.preprocessing.ICA(method=dialog.methods[method])
            pool = mp.Pool(1)
            kwds = {"reject_by_annotation": exclude_bad_segments}
            res = pool.apply_async(func=ica.fit,
                                   args=(self.model.current["raw"], ),
                                   kwds=kwds,
                                   callback=lambda x: calc.accept())
            if not calc.exec_():
                pool.terminate()
            else:
                self.model.current["ica"] = res.get(timeout=1)
                self.data_changed()

    def filter_data(self):
        """Filter data."""
        dialog = FilterDialog(self)
        if dialog.exec_():
            self.auto_duplicate()
            self.model.filter(dialog.low, dialog.high)

    def find_events(self):
        dialog = FindEventsDialog(self)
        if dialog.exec_():
            consecutive = dialog.consecutive.isChecked()
            initial_event = dialog.initial_event.isChecked()
            uint_cast = dialog.uint_cast.isChecked()
            min_dur = dialog.min_dur
            shortest_event = dialog.shortest_event
            self.model.find_events(consecutive=consecutive,
                                   initial_event=initial_event,
                                   uint_cast=uint_cast,
                                   min_duration=min_dur,
                                   shortest_event=shortest_event)

    def set_reference(self):
        """Set reference."""
        dialog = ReferenceDialog(self)
        if dialog.exec_():
            self.auto_duplicate()
            if dialog.average.isChecked():
                self.model.set_reference("average")
            else:
                ref = [c.strip() for c in dialog.channellist.text().split(",")]
                self.model.set_reference(ref)

    def show_about(self):
        """Show About dialog."""
        msg = f"""<p style="font-weight: bold">MNELAB {__version__}</p>
        <p style="font-weight: normal">
        <a href="https://github.com/cbrnr/mnelab">MNELAB</a> - a graphical user
        interface for
        <a href="https://github.com/mne-tools/mne-python">MNE</a>.</p>
        <p style="font-weight: normal">
        This program uses MNE version {mne.__version__}.</p>
        <p style="font-weight: normal">
        Licensed under the BSD 3-clause license.</p>
        <p style="font-weight: normal">
        Copyright 2017-2018 by Clemens Brunner.</p>"""
        QMessageBox.about(self, "About MNELAB", msg)

    def show_about_qt(self):
        """Show About Qt dialog."""
        QMessageBox.aboutQt(self, "About Qt")

    def auto_duplicate(self):
        # if current data is stored in a file create a new data set
        if self.model.current["fname"]:
            self.model.duplicate_data()
        # otherwise ask the user
        else:
            msg = QMessageBox.question(self, "Overwrite existing data set",
                                       "Overwrite existing data set?")
            if msg == QMessageBox.No:  # create new data set
                self.model.duplicate_data()

    def _add_recent(self, fname):
        """Add a file to recent file list.

        Parameters
        ----------
        fname : str
            File name.
        """
        if fname in self.recent:  # avoid duplicates
            self.recent.remove(fname)
        self.recent.insert(0, fname)
        while len(self.recent) > MAX_RECENT:  # prune list
            self.recent.pop()
        write_settings(recent=self.recent)
        if not self.recent_menu.isEnabled():
            self.recent_menu.setEnabled(True)

    def _remove_recent(self, fname):
        """Remove file from recent file list.

        Parameters
        ----------
        fname : str
            File name.
        """
        if fname in self.recent:
            self.recent.remove(fname)
            write_settings(recent=self.recent)
            if not self.recent:
                self.recent_menu.setEnabled(False)

    @pyqtSlot(QModelIndex)
    def _update_data(self, selected):
        """Update index and information based on the state of the sidebar.

        Parameters
        ----------
        selected : QModelIndex
            Index of the selected row.
        """
        if selected.row() != self.model.index:
            self.model.index = selected.row()
            self.data_changed()

    @pyqtSlot(QModelIndex, QModelIndex)
    def _update_names(self, start, stop):
        """Update names in DataSets after changes in sidebar."""
        for index in range(start.row(), stop.row() + 1):
            self.model.data[index]["name"] = self.names.stringList()[index]

    @pyqtSlot()
    def _update_recent_menu(self):
        self.recent_menu.clear()
        for recent in self.recent:
            self.recent_menu.addAction(recent)

    @pyqtSlot(QAction)
    def _load_recent(self, action):
        self.model.load(action.text())

    @pyqtSlot()
    def _toggle_statusbar(self):
        if self.statusBar().isHidden():
            self.statusBar().show()
        else:
            self.statusBar().hide()
        write_settings(statusbar=not self.statusBar().isHidden())

    @pyqtSlot(QDropEvent)
    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.acceptProposedAction()

    @pyqtSlot(QDropEvent)
    def dropEvent(self, event):
        mime = event.mimeData()
        if mime.hasUrls():
            urls = mime.urls()
            for url in urls:
                self.load_file(url.toLocalFile())

    @pyqtSlot(QEvent)
    def closeEvent(self, event):
        """Close application.

        Parameters
        ----------
        event : QEvent
            Close event.
        """
        write_settings(geometry=self.saveGeometry(), state=self.saveState())
        if self.model.history:
            print("\nCommand History")
            print("===============")
            print("\n".join(self.model.history))
        QApplication.quit()

    def eventFilter(self, source, event):
        # currently the only source is the raw plot window
        if event.type() == QEvent.Close:
            self.data_changed()
        return QObject.eventFilter(self, source, event)
示例#5
0
class MainWindow(QMainWindow):
    """MNELAB main window."""
    def __init__(self, model):
        """Initialize MNELAB main window.
        Parameters
        ----------
        model : mnelab.model.Model instance
            The main window needs to connect to a model containing all data
            sets. This decouples the GUI from the data (model/view).
        """
        super().__init__()

        self.model = model  # data model
        self.setWindowTitle("MNELAB")

        # restore settings
        settings = read_settings()
        self.recent = settings["recent"]  # list of recent files
        if settings["geometry"]:
            self.restoreGeometry(settings["geometry"])
        else:
            self.setGeometry(300, 300, 1000, 750)  # default window size
            self.move(QApplication.desktop().screen().rect().center() -
                      self.rect().center())  # center window
        if settings["state"]:
            self.restoreState(settings["state"])

        self.actions = {}  # contains all actions

        # initialize menus
        file_menu = self.menuBar().addMenu("&File")
        self.actions["open_file"] = file_menu.addAction(
            "&Open...",
            lambda: self.open_file(model.load, "Open raw", SUPPORTED_FORMATS),
            QKeySequence.Open)
        self.recent_menu = file_menu.addMenu("Open recent")
        self.recent_menu.aboutToShow.connect(self._update_recent_menu)
        self.recent_menu.triggered.connect(self._load_recent)
        if not self.recent:
            self.recent_menu.setEnabled(False)
        self.actions["close_file"] = file_menu.addAction(
            "&Close",
            self.model.remove_data,
            QKeySequence.Close)
        self.actions["close_all"] = file_menu.addAction(
            "Close all",
            self.close_all)
        file_menu.addSeparator()
        self.actions["import_bads"] = file_menu.addAction(
            "Import bad channels...",
            lambda: self.import_file(model.import_bads, "Import bad channels",
                                     "*.csv *.txt"))
        self.actions["import_events"] = file_menu.addAction(
            "Import events...",
            lambda: self.import_file(model.import_events, "Import events",
                                     "*.csv *.mrk"))
        self.actions["import_annotations"] = file_menu.addAction(
            "Import annotations...",
            lambda: self.import_file(model.import_annotations,
                                     "Import annotations", "*.csv *.mrk"))
        self.actions["import_ica"] = file_menu.addAction(
            "Import &ICA...",
            lambda: self.open_file(model.import_ica, "Import ICA",
                                   "*.fif *.fif.gz"))
        file_menu.addSeparator()
        self.actions["export_data"] = file_menu.addAction(
            "Export data...",
            lambda: self.export_file(model.export_data, "Export",
                                     SUPPORTED_EXPORT_FORMATS))
        self.actions["export_bads"] = file_menu.addAction(
            "Export &bad channels...",
            lambda: self.export_file(model.export_bads, "Export bad channels",
                                     "*.csv"))
        self.actions["export_events"] = file_menu.addAction(
            "Export &events...",
            lambda: self.export_file(model.export_events, "Export events",
                                     "*.csv"))
        self.actions["export_annotations"] = file_menu.addAction(
            "Export &annotations...",
            lambda: self.export_file(model.export_annotations,
                                     "Export annotations", "*.csv"))
        self.actions["export_ica"] = file_menu.addAction(
            "Export ICA...",
            lambda: self.export_file(model.export_ica,
                                     "Export ICA", "*.fif *.fif.gz"))
        self.actions["export_psd"] = file_menu.addAction(
            "Export Power Spectrum Density...",
            lambda: self.export_file(model.export_psd,
                                     "Export Power Spectrum Density", "*.hdf"))
        self.actions["export_tfr"] = file_menu.addAction(
            "Export Time-Frequency...",
            lambda: self.export_file(model.export_tfr,
                                     "Export Time-Frequency", "*.hdf"))
        file_menu.addSeparator()
        self.actions["quit"] = file_menu.addAction(
            "&Quit", self.close, QKeySequence.Quit)

        edit_menu = self.menuBar().addMenu("&Edit")
        self.actions["pick_chans"] = edit_menu.addAction(
            "Pick &channels...",
            self.pick_channels)
        self.actions["chan_props"] = edit_menu.addAction(
            "Edit channel &properties...",
            self.channel_properties)
        self.actions["set_montage"] = edit_menu.addAction(
            "Edit &montage...", self.set_montage)
        self.actions["events"] = edit_menu.addAction(
            "Edit &events...", self.edit_events)

        plot_menu = self.menuBar().addMenu("&Plot")
        self.actions["plot_raw"] = plot_menu.addAction(
            "Plot &Data...", self.plot_raw)
        self.actions["plot_image"] = plot_menu.addAction(
            "Plot data as &Image...", self.plot_image)
        self.actions["plot_states"] = plot_menu.addAction(
            "Plot &States...", self.plot_states)
        self.actions["plot_topomaps"] = plot_menu.addAction(
            "Plot &Topomaps...", self.plot_topomaps)
        self.actions["plot_montage"] = plot_menu.addAction(
            "Plot Current &montage", self.plot_montage)

        tools_menu = self.menuBar().addMenu("&Preprocessing")
        self.actions["filter"] = tools_menu.addAction(
            "&Filter data...", self.filter_data)
        self.actions["resample"] = tools_menu.addAction(
            "&Downsample...", self.resample)

        self.actions["interpolate_bads"] = tools_menu.addAction(
            "Interpolate bad channels...", self.interpolate_bads)
        self.actions["set_ref"] = tools_menu.addAction(
            "&Set reference...", self.set_reference)

        ica_menu = self.menuBar().addMenu("&ICA")
        self.actions["run_ica"] = ica_menu.addAction(
            "Run &ICA...", self.run_ica)
        ica_menu.addSeparator()
        self.actions["plot_ica_components"] = ica_menu.addAction(
            "Plot ICA &components...",
            self.plot_ica_components_with_timeseries)
        self.actions["plot_ica_sources"] = ica_menu.addAction(
            "Plot &ICA sources...", self.plot_ica_sources)
        self.actions["plot_correlation_matrix"] = ica_menu.addAction(
            "Plot correlation &matrix...", self.plot_correlation_matrix)
        self.actions["plot_overlay"] = ica_menu.addAction(
            "Plot overlay...", self.plot_ica_overlay)
        ica_menu.addSeparator()
        self.actions["apply_ica"] = ica_menu.addAction(
            "Apply &ICA...", self.apply_ica)

        freq_menu = self.menuBar().addMenu("&Frequencies")
        self.actions["plot_psd"] = freq_menu.addAction(
            "Compute &Power spectral density...", self.plot_psd)
        self.actions["plot_tfr"] = freq_menu.addAction(
            "Compute &Time-Frequency...", self.plot_tfr)
        freq_menu.addSeparator()
        self.actions["open_tfr"] = freq_menu.addAction(
            "&Open Time-Frequency file...", self.open_tfr)
        self.actions["open_psd"] = freq_menu.addAction(
            "&Open Power Spectrum Density file...",
            self.open_psd)

        events_menu = self.menuBar().addMenu("&Events")
        self.actions["plot_events"] = events_menu.addAction(
            "&Plot events...", self.plot_events)
        events_menu.addSeparator()
        self.actions["find_events"] = events_menu.addAction(
            "Find &events...", self.find_events)
        self.actions["add_events"] = events_menu.addAction(
            "Setup events as annotation...", self.add_events)

        epochs_menu = self.menuBar().addMenu("Epochs")
        self.actions["epoch_data"] = epochs_menu.addAction(
            "Cut data into epochs...", self.epoch_data)
        self.actions["evoke_data"] = epochs_menu.addAction(
            "Average epochs...", self.evoke_data)

        batch_menu = self.menuBar().addMenu("&Batch")
        self.actions["open_batch"] = batch_menu.addAction(
            "Open &Batch processing window", self.open_batch)

        view_menu = self.menuBar().addMenu("&View")
        self.actions["statusbar"] = view_menu.addAction(
            "Statusbar", self._toggle_statusbar)
        self.actions["statusbar"].setCheckable(True)

        help_menu = self.menuBar().addMenu("&Help")
        self.actions["about"] = help_menu.addAction("&About", self.show_about)
        self.actions["about_qt"] = help_menu.addAction(
            "About &Qt", self.show_about_qt)

        # actions that are always enabled
        self.always_enabled = ["open_file", "about", "about_qt", "quit",
                               "statusbar", "open_batch", "open_tfr",
                               "open_psd"]

        # set up data model for sidebar (list of open files)
        self.names = QStringListModel()
        self.names.dataChanged.connect(self._update_names)
        splitter = QSplitter()
        self.sidebar = QListView()
        self.sidebar.setFrameStyle(QFrame.NoFrame)
        self.sidebar.setFocusPolicy(Qt.NoFocus)
        self.sidebar.setModel(self.names)
        self.sidebar.clicked.connect(self._update_data)
        splitter.addWidget(self.sidebar)
        self.infowidget = InfoWidget()
        splitter.addWidget(self.infowidget)
        width = splitter.size().width()
        splitter.setSizes((width * 0.3, width * 0.7))
        self.setCentralWidget(splitter)

        self.status_label = QLabel()
        self.statusBar().addPermanentWidget(self.status_label)
        if settings["statusbar"]:
            self.statusBar().show()
            self.actions["statusbar"].setChecked(True)
        else:
            self.statusBar().hide()
            self.actions["statusbar"].setChecked(False)

        self.setAcceptDrops(True)
        self.data_changed()

    def data_changed(self):
        # update sidebar
        self.names.setStringList(self.model.names)
        self.sidebar.setCurrentIndex(self.names.index(self.model.index))

        # update info widget
        if self.model.data:
            self.infowidget.set_values(self.model.get_info())
        else:
            self.infowidget.clear()

        # update status bar
        if self.model.data:
            mb = self.model.nbytes / 1024 ** 2
            self.status_label.setText("Total Memory: {:.2f} MB".format(mb))
        else:
            self.status_label.clear()

        # toggle actions
        if len(self.model) == 0:  # disable if no data sets are currently open
            enabled = False
        else:
            enabled = True

        for name, action in self.actions.items():  # toggle
            if name not in self.always_enabled:
                action.setEnabled(enabled)

        if self.model.data:  # toggle if specific conditions are met
            if self.model.current["raw"]:
                raw = True
                evoked = False
                bads = bool(self.model.current["raw"].info["bads"])
                annot = self.model.current["raw"].annotations is not None
                events = self.model.current["events"] is not None
                self.actions["import_annotations"].setEnabled(True)
                self.actions["import_events"].setEnabled(True)
                self.actions["evoke_data"].setEnabled(False)
                self.actions["plot_image"].setEnabled(False)
                self.actions["plot_tfr"].setEnabled(False)
            else:
                raw = False
                annot = False
                events = False
                self.actions["find_events"].setEnabled(False)
                self.actions["import_annotations"].setEnabled(False)
                self.actions["import_events"].setEnabled(False)
                self.actions["plot_image"].setEnabled(True)
                if self.model.current["epochs"]:
                    evoked = False
                    bads = bool(self.model.current["epochs"].info["bads"])
                    self.actions["evoke_data"].setEnabled(True)
                else:
                    evoked = True
                    bads = bool(self.model.current["evoked"].info["bads"])
                    self.actions["evoke_data"].setEnabled(False)
            self.actions["export_bads"].setEnabled(enabled and bads)
            self.actions["export_events"].setEnabled(enabled and events)
            self.actions["export_annotations"].setEnabled(enabled and annot)
            montage = bool(self.model.current["montage"])
            self.actions["run_ica"].setEnabled(enabled and montage
                                               and not evoked)
            self.actions["plot_montage"].setEnabled(enabled and montage)
            self.actions["interpolate_bads"].setEnabled(enabled and montage)
            ica = bool(self.model.current["ica"])
            self.actions["export_ica"].setEnabled(enabled and ica)
            self.actions["plot_events"].setEnabled(raw and events)
            self.actions["plot_ica_components"].setEnabled(enabled and ica
                                                           and montage)
            self.actions["plot_ica_sources"].setEnabled(enabled and ica
                                                        and montage)
            self.actions["plot_correlation_matrix"].setEnabled(
                enabled and ica and montage)
            self.actions["plot_overlay"].setEnabled(
                enabled and ica and montage)
            self.actions["run_ica"].setEnabled(montage and not evoked)
            self.actions["apply_ica"].setEnabled(enabled and ica
                                                 and montage)
            self.actions["events"].setEnabled(enabled and events)
            self.actions["epoch_data"].setEnabled(enabled and events)
            self.actions["add_events"].setEnabled(enabled and events)
            self.actions["plot_states"].setEnabled(montage and evoked)
            self.actions["plot_topomaps"].setEnabled(montage and evoked)
            self.actions["export_tfr"].setEnabled(
                self.model.current["tfr"] is not None)
            self.actions["export_psd"].setEnabled(
                self.model.current["psd"] is not None)

        # add to recent files
        if len(self.model) > 0:
            self._add_recent(self.model.current["fname"])

    def open_file(self, f, text, ffilter):
        """Open file."""
        fname = QFileDialog.getOpenFileName(self, text, filter=ffilter)[0]
        if fname:
            f(fname)

    def export_file(self, f, text, ffilter):
        """Export to file."""
        # BUG on windows fname = QFileDialog.getSaveFileName(self,
        # text, filter=ffilter)[0]
        fname = QFileDialog.getSaveFileName(self, text, filter=ffilter)[0]
        if fname:
            f(fname)

    def import_file(self, f, text, ffilter):
        """Import file."""
        fname = QFileDialog.getOpenFileName(self, text, filter=ffilter)[0]
        if fname:
            try:
                f(fname)
            except LabelsNotFoundError as e:
                QMessageBox.critical(self, "Channel labels not found", str(e))
            except InvalidAnnotationsError as e:
                QMessageBox.critical(self, "Invalid annotations", str(e))

    def close_all(self):
        """Close all currently open data sets."""
        msg = QMessageBox.question(self, "Close all data sets",
                                   "Close all data sets?")
        if msg == QMessageBox.Yes:
            while len(self.model) > 0:
                self.model.remove_data()

    def pick_channels(self):
        """Pick channels in current data set."""
        if self.model.current["raw"]:
            channels = self.model.current["raw"].info["ch_names"]
        elif self.model.current["epochs"]:
            channels = self.model.current["epochs"].info["ch_names"]
        else:
            channels = self.model.current["evoked"].info["ch_names"]
        dialog = PickChannelsDialog(self, channels, selected=channels)
        if dialog.exec_():
            picks = [item.data(0) for item in dialog.channels.selectedItems()]
            drops = set(channels) - set(picks)
            if drops:
                self.auto_duplicate()
                self.model.drop_channels(drops)
                self.model.history.append(f"data.drop({drops})")

    def channel_properties(self):
        """Show channel properties dialog."""
        if self.model.current["raw"]:
            info = self.model.current["raw"].info
        elif self.model.current["epochs"]:
            info = self.model.current["epochs"].info
        else:
            info = self.model.current["evoked"].info
        dialog = ChannelPropertiesDialog(self, info)
        if dialog.exec_():
            dialog.model.sort(0)
            bads = []
            renamed = {}
            types = {}
            for i in range(dialog.model.rowCount()):
                new_label = dialog.model.item(i, 1).data(Qt.DisplayRole)
                old_label = info["ch_names"][i]
                if new_label != old_label:
                    renamed[old_label] = new_label
                new_type = dialog.model.item(i, 2).data(Qt.DisplayRole).lower()
                types[new_label] = new_type
                if dialog.model.item(i, 3).checkState() == Qt.Checked:
                    bads.append(info["ch_names"][i])
            self.model.set_channel_properties(bads, renamed, types)

    def set_montage(self):
        """Set montage."""
        montages = mne.channels.get_builtin_montages()
        # TODO: currently it is not possible to remove an existing montage
        dialog = MontageDialog(self, montages,
                               selected=self.model.current["montage"])
        if dialog.exec_():
            if dialog.montage_path == '':
                name = dialog.montages.selectedItems()[0].data(0)
                montage = mne.channels.read_montage(name)
                self.model.history.append("montage = mne.channels."
                                          + ("read_montage({})").format(name))
            else:
                from .utils.montage import xyz_to_montage
                montage = xyz_to_montage(dialog.montage_path)
                self.model.history.append("montage = xyz_to_montage({})"
                                          .format(dialog.montage_path))
            if self.model.current["raw"]:
                ch_names = self.model.current["raw"].info["ch_names"]
            elif self.model.current["epochs"]:
                ch_names = self.model.current["epochs"].info["ch_names"]
            elif self.model.current["evoked"]:
                ch_names = self.model.current["evoked"].info["ch_names"]
            # check if at least one channel name matches a name in the montage
            if set(ch_names) & set(montage.ch_names):
                self.model.set_montage(montage)
            else:
                QMessageBox.critical(self, "No matching channel names",
                                     "Channel names defined in the montage do "
                                     "not match any channel name in the data.")

    def edit_events(self):
        pos = self.model.current["events"][:, 0].tolist()
        desc = self.model.current["events"][:, 2].tolist()
        dialog = EventsDialog(self, pos, desc)
        if dialog.exec_():
            pass

    def plot_raw(self):
        """Plot data."""
        events = self.model.current["events"]
        if self.model.current["raw"]:
            nchan = self.model.current["raw"].info["nchan"]
            fig = self.model.current["raw"].plot(
                events=events, title=self.model.current["name"],
                scalings="auto", show=False)
            self.model.history.append("raw.plot(n_channels={})".format(nchan))
        elif self.model.current["epochs"]:
            nchan = self.model.current["epochs"].info["nchan"]
            fig = self.model.current["epochs"].plot(
                title=self.model.current["name"],
                scalings="auto", show=False)
            self.model.history.append(
                "epochs.plot(n_channels={})".format(nchan))
        elif self.model.current["evoked"]:
            nchan = self.model.current["evoked"].info["nchan"]
            fig = self.model.current["evoked"].plot(show=False, gfp=True,
                                                    spatial_colors=True,
                                                    selectable=False)
            self.model.history.append(
                "epochs.plot(n_channels={})".format(nchan))
        win = fig.canvas.manager.window
        win.setWindowTitle(self.model.current["name"])
        win.findChild(QStatusBar).hide()
        win.installEventFilter(self)  # detect if the figure is closed

        # prevent closing the window with the escape key
        try:
            key_events = fig.canvas.callbacks.callbacks["key_press_event"][8]
        except KeyError:
            pass
        else:  # this requires MNE >=0.15
            # This line causes bug... I don't know why exactly
            # AttributeError: '_StrongRef' object has no attribute 'func'
            #
            # key_events.func.keywords["params"]["close_key"] = None
            pass

        fig.show()

    def plot_image(self):
        if self.model.current["epochs"]:
            try:
                epochs = self.model.current["epochs"]
                dialog = NavEpochsDialog(None, epochs)
                dialog.setWindowModality(Qt.WindowModal)
                dialog.setWindowTitle(self.model.current["name"])
                dialog.exec()
            except Exception as e:
                print(e)
        elif self.model.current["evoked"]:
            fig = self.model.current["evoked"].plot_image(show=False)
            self.model.history.append("evoked.plot_image()")
            win = fig.canvas.manager.window
            win.findChild(QStatusBar).hide()
            win.setWindowTitle(self.model.current["name"])
            win.installEventFilter(self)  # detect if the figure is closed
            fig.show()

    def plot_states(self):
        if self.model.current["evoked"]:
            dialog = EvokedStatesDialog(None, self.model.current["evoked"])
            dialog.setWindowModality(Qt.NonModal)
            dialog.setWindowTitle(self.model.current["name"])
            dialog.show()

    def plot_topomaps(self):
        if self.model.current["evoked"]:
            dialog = EvokedTopoDialog(None, self.model.current["evoked"])
            dialog.setWindowModality(Qt.NonModal)
            dialog.setWindowTitle(self.model.current["name"])
            dialog.show()

    def plot_events(self):
        events = self.model.current["events"]
        fig = mne.viz.plot_events(events, show=False)
        win = fig.canvas.manager.window
        win.setWindowModality(Qt.WindowModal)
        win.setWindowTitle(self.model.current["name"])
        win.findChild(QStatusBar).hide()
        win.findChild(QToolBar).hide()
        fig.show()

    def plot_psd(self):
        """Plot power spectral density (PSD)."""
        if self.model.current["raw"]:
            raw = self.model.current["raw"]
            dialog = PSDDialog(None, raw)
            dialog.setWindowModality(Qt.WindowModal)
            dialog.setWindowTitle('PSD of ' + self.model.current["name"])
            dialog.exec_()
        elif self.model.current["epochs"]:
            epochs = self.model.current["epochs"]
            dialog = PSDDialog(None, epochs)
            dialog.setWindowModality(Qt.WindowModal)
            dialog.setWindowTitle('PSD of ' + self.model.current["name"])
            dialog.exec_()
        elif self.model.current["evoked"]:
            evoked = self.model.current["evoked"]
            dialog = PSDDialog(None, evoked)
            dialog.setWindowModality(Qt.WindowModal)
            dialog.setWindowTitle('PSD of ' + self.model.current["name"])
            dialog.exec_()

        try:
            psd = dialog.psd
        except Exception as e:
            psd = None
        if psd is not None:
            self.model.current["psd"] = psd
            self.data_changed()

    def open_psd(self):
        fname = QFileDialog.getOpenFileName(self, "Open TFR",
                                            filter="*.h5 *.hdf")[0]
        try:
            psd = EpochsPSD().init_from_hdf(fname)
            win = EpochsPSDWindow(psd, parent=None)
            win.setWindowTitle(fname)
            win.exec()
        except Exception as e:
            print(e)
            try:
                psd = RawPSD().init_from_hdf(fname)
                win = RawPSDWindow(psd, parent=None)
                win.setWindowModality(Qt.WindowModal)
                win.setWindowTitle(fname)
                win.exec()
            except Exception:
                pass

    def plot_tfr(self):
        """Plot Time-Frequency."""
        if self.model.current["epochs"]:
            data = self.model.current["epochs"]
        elif self.model.current["evoked"]:
            data = self.model.current["evoked"]
        dialog = TimeFreqDialog(None, data)
        dialog.setWindowModality(Qt.WindowModal)
        dialog.setWindowTitle('TFR of ' + self.model.current["name"])
        dialog.exec_()

        try:
            tfr = dialog.avgTFR
            self.data_changed()
        except Exception as e:
            tfr = None
        if tfr is not None:
            self.model.current["tfr"] = tfr
            self.data_changed()

    def open_tfr(self):
        try:
            fname = QFileDialog.getOpenFileName(self, "Open TFR",
                                                filter="*.h5 *.hdf")[0]
            avgTFR = AvgEpochsTFR().init_from_hdf(fname)
            win = AvgTFRWindow(avgTFR, parent=None)
            win.setWindowModality(Qt.WindowModal)
            win.setWindowTitle(fname)
            win.exec()
        except Exception as e:
            print(e)

    def plot_montage(self):
        """Plot current montage."""
        if self.model.current["raw"]:
            data = self.model.current["raw"]
        elif self.model.current["epochs"]:
            data = self.model.current["epochs"]
        elif self.model.current["evoked"]:
            data = self.model.current["evoked"]
        chans = Counter([mne.io.pick.channel_type(data.info, i)
                         for i in range(data.info["nchan"])])
        fig = plt.figure()
        types = []
        for type in chans.keys():
            if type in ['eeg', 'mag', 'grad']:
                types.append(type)

        for i, type in enumerate(types):
            ax = fig.add_subplot(1, len(types), i + 1)
            ax.set_title(type + '({} channels)'.format(chans[type]))
            data.plot_sensors(show_names=True, show=False,
                              ch_type=type, axes=ax, title='')
        win = fig.canvas.manager.window
        win.resize(len(types) * 600, 600)
        win.setWindowTitle(self.model.current["name"])
        win.findChild(QStatusBar).hide()
        win.findChild(QToolBar).hide()
        fig.show()

    def plot_ica_components_with_timeseries(self):
        if self.model.current["raw"]:
            try:
                fig = plot_ica_components_with_timeseries(
                                             self.model.current["ica"],
                                             inst=self.model.current["raw"])
            except Exception as e:
                QMessageBox.critical(self, "Unexpected error ", str(e))

        elif self.model.current["epochs"]:
            try:
                fig = plot_ica_components_with_timeseries(
                            self.model.current["ica"],
                            inst=self.model.current["epochs"])
            except Exception as e:
                try:
                    fig = self.model.current["ica"].plot_ica_components(
                                            inst=self.model.current["epochs"])
                except Exception as e:
                    QMessageBox.critical(self, "Unexpected error ", str(e))

    def plot_ica_sources(self):
        if self.model.current["raw"]:
            fig = (self.model.current["ica"]
                   .plot_sources(inst=self.model.current["raw"]))
        elif self.model.current["epochs"]:
            fig = (self.model.current["ica"]
                   .plot_sources(inst=self.model.current["epochs"]))
        win = fig.canvas.manager.window
        win.setWindowTitle("ICA Sources")
        win.findChild(QStatusBar).hide()
        win.installEventFilter(self)  # detect if the figure is closed

    def plot_correlation_matrix(self):
        if self.model.current["raw"]:
            try:
                plot_cormat(self.model.current["raw"],
                            self.model.current["ica"])
            except ValueError as e:
                QMessageBox.critical(
                 self, "Can't compute correlation with template ", str(e))
            except Exception as e:
                QMessageBox.critical(
                 self, "Unexpected error ", str(e))
        elif self.model.current["epochs"]:
            try:
                plot_cormat(self.model.current["epochs"],
                            self.model.current["ica"])
            except ValueError as e:
                QMessageBox.critical(
                    self, "Can't compute correlation with template ", str(e))
            except Exception as e:
                QMessageBox.critical(
                    self, "Unexpected error ", str(e))

    def plot_ica_overlay(self):
        if self.model.current["raw"]:
            plot_overlay(self.model.current["ica"],
                         self.model.current["raw"])
        elif self.model.current["epochs"]:
            plot_overlay(self.model.current["ica"],
                         self.model.current["epochs"])
        return()

    def run_ica(self):
        """Run ICA calculation."""
        try:
            import picard
            import mne.preprocessing.ICA
        except ImportError:
            have_picard = False
            import mne
        else:
            have_picard = True

        try:
            import sklearn  # required for FastICA
        except ImportError:
            have_sklearn = False
        else:
            have_sklearn = True
        if self.model.current["raw"]:
            data = self.model.current["raw"]
            inst_type = "raw"
        elif self.model.current["epochs"]:
            data = self.model.current["epochs"]
            inst_type = "epochs"
        nchan = len(mne.pick_types(data.info,
                                   meg=True, eeg=True, exclude='bads'))
        dialog = RunICADialog(self, nchan, have_picard, have_sklearn)

        if dialog.exec_():
            calc = CalcDialog(self, "Calculating ICA", "Calculating ICA.")
            method = dialog.method.currentText()
            exclude_bad_segments = dialog.exclude_bad_segments.isChecked()
            decim = int(dialog.decim.text())
            n_components = int(dialog.n_components.text())
            max_pca_components = int(dialog.max_pca_components.text())
            n_pca_components = int(dialog.pca_components.text())
            random_state = int(dialog.random_seed.text())
            max_iter = int(dialog.max_iter.text())
            ica = mne.preprocessing.ICA(
                method=dialog.methods[method],
                n_components=n_components,
                max_pca_components=max_pca_components,
                n_pca_components=n_pca_components,
                random_state=random_state,
                max_iter=max_iter)

            pool = mp.Pool(1)
            kwds = {"reject_by_annotation": exclude_bad_segments,
                    "decim": decim}
            res = pool.apply_async(func=ica.fit,
                                   args=(data,),
                                   kwds=kwds, callback=lambda x: calc.accept())
            if not calc.exec_():
                pool.terminate()
            else:
                self.auto_duplicate()
                self.model.current["ica"] = res.get(timeout=1)
                self.model.history.append(
                    "ica=ICA("
                    + ("method={} ,").format(dialog.methods[method])
                    + ("n_components={}, ").format(n_components)
                    + ("max_pca_components={}, ").format(max_pca_components)
                    + ("n_pca_components={}, ").format(n_pca_components)
                    + ("random_state={}, ").format(random_state)
                    + ("max_iter={})").format(max_iter))
                self.model.history.append(
                    "ica.fit("
                    + ("inst={}, ").format(inst_type)
                    + ("decim={}, ").format(decim)
                    + ("reject_by_annotation={})"
                       .format(exclude_bad_segments)))
                self.data_changed()

    def apply_ica(self):
        """Set reference."""
        self.auto_duplicate()
        self.model.apply_ica()

    def resample(self):
        """Resample data."""
        dialog = ResampleDialog(self)
        if dialog.exec_():
            sfreq = dialog.sfreq
            if sfreq is not None:
                self.auto_duplicate()
                if self.model.current['raw']:
                    self.model.current['raw'].resample(sfreq)
                elif self.model.current['epochs']:
                    self.model.current['epochs'].resample(sfreq)
                elif self.model.current['evoked']:
                    self.model.current['evoked'].resample(sfreq)
                self.model.current["name"] += " (resampled)"
                self.data_changed()

    def filter_data(self):
        """Filter data."""
        if self.model.current['raw']:
            israw = True
        else:
            israw = False
        dialog = FilterDialog(self, israw)
        if dialog.exec_():
            if dialog.low or dialog.high or dialog.notch_freqs:
                self.auto_duplicate()
                self.model.filter(dialog.low, dialog.high, dialog.notch_freqs)

    def find_events(self):
        info = self.model.current["raw"].info

        # use first stim channel as default in dialog
        default_stim = 0
        for i in range(info["nchan"]):
            if mne.io.pick.channel_type(info, i) == "stim":
                default_stim = i
                break
        dialog = FindEventsDialog(self, info["ch_names"], default_stim)
        if dialog.exec_():
            stim_channel = dialog.stimchan.currentText()
            consecutive = dialog.consecutive.isChecked()
            initial_event = dialog.initial_event.isChecked()
            uint_cast = dialog.uint_cast.isChecked()
            min_dur = dialog.minduredit.value()
            shortest_event = dialog.shortesteventedit.value()
            self.model.find_events(stim_channel=stim_channel,
                                   consecutive=consecutive,
                                   initial_event=initial_event,
                                   uint_cast=uint_cast,
                                   min_duration=min_dur,
                                   shortest_event=shortest_event)

    def interpolate_bads(self):
        """Interpolate bad channels."""
        self.auto_duplicate()
        self.model.interpolate_bads()

    def add_events(self):
        """Setup the events in the data as a STIM channel."""
        self.auto_duplicate()
        self.model.add_events()

    def epoch_data(self):
        """Cut raw data into epochs."""
        dialog = EpochingDialog(self, self.model.current["events"],
                                self.model.current["raw"])
        if dialog.exec_():
            selected = [int(item.text()) for item
                        in dialog.labels.selectedItems()]
            try:
                tmin = float(dialog.tmin.text())
                tmax = float(dialog.tmax.text())
            except ValueError as e:
                show_error('Unable to compute epochs...', info=str(e))
            else:
                if dialog.baseline.isChecked():
                    try:
                        a = float(float(dialog.a.text()))
                    except ValueError:
                        a = None

                    try:
                        b = float(float(dialog.b.text()))
                    except ValueError:
                        b = None

                    baseline = (a, b)
                else:
                    baseline = None

                self.auto_duplicate()
                self.model.epoch_data(selected, tmin, tmax, baseline)

    def evoke_data(self):
        """Compute the mean of epochs."""
        self.auto_duplicate()
        self.model.evoke_data()

    def set_reference(self):
        """Set reference."""
        dialog = ReferenceDialog(self)
        if dialog.exec_():
            self.auto_duplicate()
            if dialog.average.isChecked():
                self.model.set_reference("average")
            else:
                ref = [c.strip() for c in dialog.channellist.text().split(",")]
                self.model.set_reference(ref)

    def open_batch(self):
        """Open batch processing dialog."""
        dialog = BatchDialog(self)
        dialog.exec_()

    def show_about(self):
        """Show About dialog."""
        msg_box = QMessageBox(self)
        text = ("<h3>MNELAB</h3>"
                "<nobr><p>MNELAB is a graphical user interface for MNE.</p>"
                "</nobr>")
        msg_box.setText(text)

        mnelab_url = "github.com/cbrnr/mnelab"
        mne_url = "github.com/mne-tools/mne-python"

        text = (f'<nobr><p>This program uses MNE version {mne.__version__} '
                f'(Python {".".join(str(k) for k in version_info[:3])}).</p>'
                f'</nobr>'
                f'<nobr><p>MNELAB repository: '
                f'<a href=https://{mnelab_url}>{mnelab_url}</a></p></nobr>'
                f'<nobr><p>MNE repository: '
                f'<a href=https://{mne_url}>{mne_url}</a></p></nobr>'
                f'<p>Licensed under the BSD 3-clause license.</p>'
                f'<p>Copyright 2017-2019 by Clemens Brunner.</p>')
        msg_box.setInformativeText(text)
        msg_box.exec_()

    def show_about_qt(self):
        """Show About Qt dialog."""
        QMessageBox.aboutQt(self, "About Qt")

    def auto_duplicate(self):
        # if current data is stored in a file create a new data set
        if self.model.current["fname"]:
            self.model.duplicate_data()
        # otherwise ask the user
        else:
            msg = QMessageBox.question(self, "Overwrite existing data set",
                                       "Overwrite existing data set?")
            if msg == QMessageBox.No:  # create new data set
                self.model.duplicate_data()

    def _add_recent(self, fname):
        """Add a file to recent file list.

        Parameters
        ----------
        fname : str
            File name.
        """
        if fname in self.recent:  # avoid duplicates
            self.recent.remove(fname)
        self.recent.insert(0, fname)
        while len(self.recent) > MAX_RECENT:  # prune list
            self.recent.pop()
        write_settings(recent=self.recent)
        if not self.recent_menu.isEnabled():
            self.recent_menu.setEnabled(True)

    def _remove_recent(self, fname):
        """Remove file from recent file list.

        Parameters
        ----------
        fname : str
            File name.
        """
        if fname in self.recent:
            self.recent.remove(fname)
            write_settings(recent=self.recent)
            if not self.recent:
                self.recent_menu.setEnabled(False)

    @pyqtSlot(QModelIndex)
    def _update_data(self, selected):
        """Update index and information based on the state of the sidebar.

        Parameters
        ----------
        selected : QModelIndex
            Index of the selected row.
        """
        if selected.row() != self.model.index:
            self.model.index = selected.row()
            self.data_changed()

    @pyqtSlot(QModelIndex, QModelIndex)
    def _update_names(self, start, stop):
        """Update names in DataSets after changes in sidebar."""
        for index in range(start.row(), stop.row() + 1):
            self.model.data[index]["name"] = self.names.stringList()[index]

    @pyqtSlot()
    def _update_recent_menu(self):
        self.recent_menu.clear()
        for recent in self.recent:
            self.recent_menu.addAction(recent)

    @pyqtSlot(QAction)
    def _load_recent(self, action):
        self.model.load(action.text())

    @pyqtSlot()
    def _toggle_statusbar(self):
        if self.statusBar().isHidden():
            self.statusBar().show()
        else:
            self.statusBar().hide()
        write_settings(statusbar=not self.statusBar().isHidden())

    @pyqtSlot(QDropEvent)
    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.acceptProposedAction()

    @pyqtSlot(QDropEvent)
    def dropEvent(self, event):
        mime = event.mimeData()
        if mime.hasUrls():
            urls = mime.urls()
            for url in urls:
                self.model.load(url.toLocalFile())

    @pyqtSlot(QEvent)
    def closeEvent(self, event):
        """Close application.

        Parameters
        ----------
        event : QEvent
            Close event.
        """
        write_settings(geometry=self.saveGeometry(), state=self.saveState())
        if self.model.history:
            print("\nCommand History")
            print("===============")
            print("\n".join(self.model.history))
        QApplication.quit()

    def eventFilter(self, source, event):
        # currently the only source is the raw plot window
        if event.type() == QEvent.Close:
            self.data_changed()
        return QObject.eventFilter(self, source, event)
class QmyWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)  # 调用父类构造函数
        self.ui = Ui_Form()  # 创建UI对象
        self.ui.setupUi(self)  # 构造UI

        self.__provinces = [
            "北京", "上海", "天津", "河北", "山东", "四川", "重庆", "广东", "河南"
        ]
        self.model = QStringListModel(self)
        self.model.setStringList(self.__provinces)
        self.ui.listView.setModel(self.model)
        self.ui.listView.setEditTriggers(QAbstractItemView.DoubleClicked
                                         | QAbstractItemView.SelectedClicked)

        # # ===================自定义功能函数

        # # ===================事件处理函数

        # # ===================由connectSlotsByName()自动关联的槽函数
        # 添加项
        self.ui.btnList_Append.clicked.connect(self.do_btnlist_append_clicked)
        # 插入项
        self.ui.btnList_Insert.clicked.connect(self.do_btnlist_insert_clicked)
        # 删除当前项
        self.ui.btnList_Delete.clicked.connect(self.do_btnlist_delete_clicked)
        # 清除列表
        self.ui.btnList_Clear.clicked.connect(self.do_btnlist_clear_clicked)
        # 显示数据模型的内容
        self.ui.btnText_Display.clicked.connect(
            self.do_btntext_display_clicked)
        # 点击listview时show行列信息
        self.ui.listView.clicked.connect(self.do_listview_clicked)

        # # ===================自定义槽函数

    # 添加项
    def do_btnlist_append_clicked(self):
        lastRow = self.model.rowCount()
        self.model.insertRow(lastRow)  # 在尾部插入一空行
        index = self.model.index(lastRow, 0)  # 获取最后一行的模型索引
        self.model.setData(index, "new item", Qt.DisplayRole)  # 设置显示文字
        self.ui.listView.setCurrentIndex(index)  # 设置当前选中的行

    # 插入项
    def do_btnlist_insert_clicked(self):
        index = self.ui.listView.currentIndex()  # 当前模型索引
        self.model.insertRow(index.row())
        self.model.setData(index, "inserted item", Qt.DisplayRole)
        self.ui.listView.setCurrentIndex(index)  # 设置当前选中的行

    # 删除当前项
    def do_btnlist_delete_clicked(self):
        index = self.ui.listView.currentIndex()
        self.model.removeRow(index.row())  # 删除当前行

    # 清除列表
    def do_btnlist_clear_clicked(self):
        count = self.model.rowCount()
        self.model.removeRows(0, count)

    # 显示数据模型的内容
    def do_btntext_display_clicked(self):
        strList = self.model.stringList()  # 列表类型
        self.ui.plainTextEdit.clear()
        for strLine in strList:
            self.ui.plainTextEdit.appendPlainText(strLine)

    # 显示选中的行列信息
    def do_listview_clicked(self, index):
        self.ui.label.setText("当前项index: row=%d, column=%d" %
                              (index.row(), index.column()))
示例#7
0
class HelpLanguagesDialog(QDialog, Ui_HelpLanguagesDialog):
    """
    Class implementing a dialog to configure the preferred languages.
    """
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        """
        super(HelpLanguagesDialog, self).__init__(parent)
        self.setupUi(self)

        self.__model = QStringListModel()
        self.languagesList.setModel(self.__model)
        self.languagesList.selectionModel().currentChanged.connect(
            self.__currentChanged)

        languages = Preferences.toList(
            Preferences.Prefs.settings.value("Help/AcceptLanguages",
                                             self.defaultAcceptLanguages()))
        self.__model.setStringList(languages)

        allLanguages = []
        for index in range(QLocale.C + 1, QLocale.LastLanguage + 1):
            allLanguages += self.expand(QLocale.Language(index))
        self.__allLanguagesModel = QStringListModel()
        self.__allLanguagesModel.setStringList(allLanguages)
        self.addCombo.setModel(self.__allLanguagesModel)

    def __currentChanged(self, current, previous):
        """
        Private slot to handle a change of the current selection.
        
        @param current index of the currently selected item (QModelIndex)
        @param previous index of the previously selected item (QModelIndex)
        """
        self.removeButton.setEnabled(current.isValid())
        row = current.row()
        self.upButton.setEnabled(row > 0)
        self.downButton.setEnabled(row != -1
                                   and row < self.__model.rowCount() - 1)

    @pyqtSlot()
    def on_upButton_clicked(self):
        """
        Private slot to move a language up.
        """
        currentRow = self.languagesList.currentIndex().row()
        data = self.languagesList.currentIndex().data()
        self.__model.removeRow(currentRow)
        self.__model.insertRow(currentRow - 1)
        self.__model.setData(self.__model.index(currentRow - 1), data)
        self.languagesList.setCurrentIndex(self.__model.index(currentRow - 1))

    @pyqtSlot()
    def on_downButton_clicked(self):
        """
        Private slot to move a language down.
        """
        currentRow = self.languagesList.currentIndex().row()
        data = self.languagesList.currentIndex().data()
        self.__model.removeRow(currentRow)
        self.__model.insertRow(currentRow + 1)
        self.__model.setData(self.__model.index(currentRow + 1), data)
        self.languagesList.setCurrentIndex(self.__model.index(currentRow + 1))

    @pyqtSlot()
    def on_removeButton_clicked(self):
        """
        Private slot to remove a language from the list of acceptable
        languages.
        """
        currentRow = self.languagesList.currentIndex().row()
        self.__model.removeRow(currentRow)

    @pyqtSlot()
    def on_addButton_clicked(self):
        """
        Private slot to add a language to the list of acceptable languages.
        """
        language = self.addCombo.currentText()
        if language in self.__model.stringList():
            return

        self.__model.insertRow(self.__model.rowCount())
        self.__model.setData(self.__model.index(self.__model.rowCount() - 1),
                             language)
        self.languagesList.setCurrentIndex(
            self.__model.index(self.__model.rowCount() - 1))

    def accept(self):
        """
        Public method to accept the data entered.
        """
        result = self.__model.stringList()
        if result == self.defaultAcceptLanguages():
            Preferences.Prefs.settings.remove("Help/AcceptLanguages")
        else:
            Preferences.Prefs.settings.setValue("Help/AcceptLanguages", result)
        super(HelpLanguagesDialog, self).accept()

    @classmethod
    def httpString(cls, languages):
        """
        Class method to convert a list of acceptable languages into a
        byte array.
       
        The byte array can be sent along with the Accept-Language http header
        (see RFC 2616).
        
        @param languages list of acceptable languages (list of strings)
        @return converted list (QByteArray)
        """
        processed = []
        qvalue = 1.0
        for language in languages:
            leftBracket = language.find('[')
            rightBracket = language.find(']')
            tag = language[leftBracket + 1:rightBracket]
            if not processed:
                processed.append(tag)
            else:
                processed.append("{0};q={1:.1f}".format(tag, qvalue))
            if qvalue > 0.1:
                qvalue -= 0.1

        return QByteArray(", ".join(processed))

    @classmethod
    def defaultAcceptLanguages(cls):
        """
        Class method to get the list of default accept languages.
        
        @return list of acceptable languages (list of strings)
        """
        language = QLocale.system().name()
        if not language:
            return []
        else:
            return cls.expand(QLocale(language).language())

    @classmethod
    def expand(cls, language):
        """
        Class method to expand a language enum to a readable languages
        list.
        
        @param language language number (QLocale.Language)
        @return list of expanded language names (list of strings)
        """
        allLanguages = []
        countries = [
            l.country() for l in QLocale.matchingLocales(
                language, QLocale.AnyScript, QLocale.AnyCountry)
        ]
        languageString = "{0} [{1}]"\
            .format(QLocale.languageToString(language),
                    QLocale(language).name().split('_')[0])
        allLanguages.append(languageString)
        for country in countries:
            languageString = "{0}/{1} [{2}]"\
                .format(QLocale.languageToString(language),
                        QLocale.countryToString(country),
                        '-'.join(QLocale(language, country).name()
                                 .split('_')).lower())
            if languageString not in allLanguages:
                allLanguages.append(languageString)

        return allLanguages
class QIndex_gasAnalysis(QDialog):
    username = ''
    person_id = ''
    url = ''
    gas = []

    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_Index_gasAnalysis()
        self.ui.setupUi(self)

        self.model = QStringListModel(self)
        self.ui.listView.setModel(self.model)
        self.ui.listView.setEditTriggers(QAbstractItemView.NoEditTriggers)

    def setInfo(self, username, person_id, url):
        self.username = username
        self.person_id = person_id
        self.url = url

        #开始查询该用户的所有气体文件,用list变量gas保存
        list_gasInfo = requests.get(self.url + '/person/getGasInfo/' +
                                    str(self.person_id))
        self.gas = eval(list_gasInfo.text)

        #开始将数据添加进list中
        for i in self.gas:
            self.addItem(i["save_time"])

    def addItem(self, name):  #向list添加数据
        #在尾部插入一空行
        lastRow = self.model.rowCount()
        self.model.insertRow(lastRow)
        #给该空行设置显示
        index = self.model.index(lastRow, 0)
        self.model.setData(index, name, Qt.DisplayRole)
        #选中该行
        self.ui.listView.setCurrentIndex(index)

    @pyqtSlot(bool)
    def on_OKButton_clicked(self):
        if self.model.rowCount() == 0:  #如果当前列表框没有数据,则不做出响应
            pass
        else:
            #(1)选中的气体数据
            gasData = self.gas[self.ui.listView.currentIndex().row()]
            #(2)选择哪些分类器
            classifierlist = []
            if self.ui.LogisticBox.isChecked():
                classifierlist.append('Logistic')
            if self.ui.SVCBox.isChecked():
                classifierlist.append('SVC')
            if self.ui.Linear_SVCBox.isChecked():
                classifierlist.append('LinearSVC')
            if self.ui.PerceptronBox.isChecked():
                classifierlist.append('Perceptron')
            if self.ui.gaussianBox.isChecked():
                classifierlist.append('Gaussian')
            if self.ui.kNNBox.isChecked():
                classifierlist.append('Knn')
            if self.ui.ForestBox.isChecked():
                classifierlist.append('Forest')
            #(3)给此条分析记录命名
            name = self.ui.nameEdit.text()

            #(4)需要对哪种类别进行分析
            statusType = self.ui.typeBox.currentText()

            #数据获取完毕后需要确认数据完整性
            if len(classifierlist) == 0:  #如果一个分类器都没有选
                QMessageBox.information(self, "错误", "请至少选择一个分类器")
            elif name == '':  #如果没有命名
                QMessageBox.information(self, "错误", "请对此条分析进行命名")
            else:
                #确认完毕后,可以开始进行分析
                self.gasAnalysisStart(gasData, classifierlist, name,
                                      statusType)

    def gasAnalysisStart(self, gasData, classifierList, name, statusType):
        #首先需要将最大值和最小值与初始值做相减处理,同时进行拼接
        gas_max, gas_min, gas_ori, gas_extrTime = eval(
            gasData["gas_max"]), eval(gasData["gas_min"]), eval(
                gasData["gas_ori"]), eval(gasData["gas_extrTime"])
        for i in range(len(gas_ori)):
            gas_max[i], gas_min[
                i] = gas_max[i] - gas_ori[i], gas_min[i] - gas_ori[i]
        X_test = gas_max + gas_min + gas_extrTime
        X_test = np.array(X_test)

        #对数据进行标准化处理,需要加载事先保存好的标准化模型
        sc = joblib.load(os.getcwd() + '\classifier\sc')
        X_test = sc.transform(X_test.reshape(
            1, -1))  #这个地方由于标准化仅支持二维Array,所以需要reshape一下

        #这个地方需要根据不同的选择,做不同种类的数据分析
        if statusType == '呼气者性别':

            #加载分类器,根据需求依次进行训练
            statusData = {
                'status_type': statusType,
                'status_Logistic': 'None',
                'status_SVC': 'None',
                'status_LinearSVC': 'None',
                'status_Gaussian': 'None',
                'status_Knn': 'None',
                'status_Forest': 'None',
                'status_Perceptron': 'None',
                'save_name': name,
                'person_id': self.person_id,
                'is_delete': 'False',
                'gas_save_time': gasData['save_time']
            }  #初始化data
            for i in classifierList:
                classifier = joblib.load(os.getcwd() +
                                         '\classifier\classifier_' + i + '.m')
                Y_test = classifier.predict(X_test).tolist()
                statusData['status_' + i] = Y_test[0]
            #向云端服务器发送存储请求
            save_status = requests.post(self.url + '/person/getStatusInfo/' +
                                        str(self.person_id),
                                        data=statusData)
            #print(save_status.text)

            #保存成功后弹出窗口,并关闭此页面
            QMessageBox.information(self, "保存成功", "请在'查看历史分析记录'中查看")

        self.accept()
示例#9
0
class PipPage(ConfigurationPageBase, Ui_PipPage):
    """
    Class implementing the configuration page.
    """
    def __init__(self, plugin):
        """
        Constructor
        
        @param plugin reference to the plugin object
        """
        super(PipPage, self).__init__()
        self.setupUi(self)
        self.setObjectName("PipPage")
        
        self.__plugin = plugin
        
        self.__model = QStringListModel(self)
        self.__proxyModel = QSortFilterProxyModel(self)
        self.__proxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.__proxyModel.setSourceModel(self.__model)
        self.stringList.setModel(self.__proxyModel)
        
        self.removeButton.clicked.connect(self.stringList.removeSelected)
        self.removeAllButton.clicked.connect(self.stringList.removeAll)
        
        self.indexLabel.setText(self.tr(
            '<b>Note:</b> Leave empty to use the default index URL ('
            '<a href="{0}">{0}</a>).')
            .format(DefaultIndexUrl))
        
        # set initial values
        self.__model.setStringList(
            self.__plugin.getPreferences("PipExecutables"))
        self.__model.sort(0)
        self.indexEdit.setText(self.__plugin.getPreferences("PipSearchIndex"))
    
    @pyqtSlot()
    def on_addButton_clicked(self):
        """
        Private slot used to add an executable to the list.
        """
        executable = E5FileDialog.getOpenFileName(
            self,
            self.tr("Add pip executable"),
            os.path.expanduser("~"),
            "")
        executable = Utilities.toNativeSeparators(executable)
        if executable != "" and executable not in self.__model.stringList():
            self.__model.insertRow(self.__model.rowCount())
            self.__model.setData(
                self.__model.index(self.__model.rowCount() - 1), executable)
            self.__model.sort(0)
    
    @pyqtSlot()
    def on_defaultListButton_clicked(self):
        """
        Private slot to load the default list of pip executables.
        """
        self.stringList.removeAll()
        defaults = self.__plugin.getDefaultPipExecutables()
        for executable in defaults:
            self.__model.insertRow(self.__model.rowCount())
            self.__model.setData(
                self.__model.index(self.__model.rowCount() - 1), executable)
        self.__model.sort(0)
    
    def save(self):
        """
        Public slot to save the pip configuration.
        """
        executables = self.__model.stringList()[:]
        self.__plugin.setPreferences(
            "PipExecutables", executables)
        self.__plugin.setPreferences(
            "PipSearchIndex", self.indexEdit.text().strip())
        if executables:
            if self.__plugin.getPreferences("CurrentPipExecutable") \
                    not in executables:
                self.__plugin.setPreferences(
                    "CurrentPipExecutable", "")
        else:
            # reset the current executable if none have been configured
            self.__plugin.setPreferences(
                "CurrentPipExecutable", "")
示例#10
0
class QIndex_gasList(QDialog):
    username = ''
    person_id = ''
    url = ''
    gas = []

    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_Index_gasList()
        self.ui.setupUi(self)

        self.model = QStringListModel(self)
        self.ui.listView.setModel(self.model)
        self.ui.listView.setEditTriggers(QAbstractItemView.NoEditTriggers)
        #self.ui.listView.setEditTriggers(QAbstractItemView.DoubleClicked | QAbstractItemView.SelectedClicked)

        self.__createChart()

    def setInfo(self, username, person_id, url):
        self.username = username
        self.person_id = person_id
        self.url = url

        #开始查询该用户的所有气体文件,用list变量gas保存
        list_gasInfo = requests.get(self.url + '/person/getGasInfo/' +
                                    str(self.person_id))
        self.gas = eval(list_gasInfo.text)

        #开始将数据添加进list中
        for i in self.gas:
            self.addItem(i["save_time"])

    def addItem(self, name):  #向list添加数据
        #在尾部插入一空行
        lastRow = self.model.rowCount()
        self.model.insertRow(lastRow)
        #给该空行设置显示
        index = self.model.index(lastRow, 0)
        self.model.setData(index, name, Qt.DisplayRole)
        #选中该行
        self.ui.listView.setCurrentIndex(index)

    def on_listView_clicked(self, index):  #如果某条数据信息被点击,则向绘图框发送信息
        self.showGas(self.gas[self.ui.listView.currentIndex().row()])
        self.__prepareData()

    def showGas(self, gasData):  #接收gas数据,并在页面右侧进行显示
        #首先向五个Edit设置数据
        sensor = self.ui.sensorBox.currentIndex()
        self.ui.timeEdit.setText(gasData["gas_time"])
        self.ui.maxEdit.setText(str(eval(gasData["gas_max"])[sensor]))
        self.ui.minEdit.setText(str(eval(gasData["gas_min"])[sensor]))
        self.ui.oriEdit.setText(str(eval(gasData["gas_ori"])[sensor]))
        self.ui.extrEdit.setText(str(eval(gasData["gas_extrTime"])[sensor]))

    def on_sensorBox_currentIndexChanged(self):
        gasData = self.gas[self.ui.listView.currentIndex().row()]
        sensor = self.ui.sensorBox.currentIndex()
        self.ui.timeEdit.setText(gasData["gas_time"])
        self.ui.maxEdit.setText(str(eval(gasData["gas_max"])[sensor]))
        self.ui.minEdit.setText(str(eval(gasData["gas_min"])[sensor]))
        self.ui.oriEdit.setText(str(eval(gasData["gas_ori"])[sensor]))
        self.ui.extrEdit.setText(str(eval(gasData["gas_extrTime"])[sensor]))

    def __createChart(self):
        self.__chart = QChart()
        self.ui.chartView.setChart(self.__chart)
        self.__chart.legend().setVisible(False)  #隐藏图例
        self.__chart.setMargins(QMargins(0, 0, 0, 0))  #把间距设置到最小

        #初始化线条数组
        series = [QLineSeries() for _ in range(15)]
        color = [
            '#FF88C2', '#FF8888', '#FFA488', '#FFBB66', '#FFDD55', '#FFFF77',
            '#DDFF77', '#BBFF66', '#66FF66', '#77FFCC', '#77FFEE', '#66FFFF',
            '#77DDFF', '#99BBFF', '#9999FF'
        ]

        #设置线条颜色形状
        pen = QPen()
        pen.setStyle(Qt.SolidLine)
        pen.setWidth(2)
        for i in range(15):
            pen.setColor(QColor(color[i]))
            series[i].setPen(pen)

        #向表格添加线条
        for i in range(15):
            self.__chart.addSeries(series[i])

        #设置坐标轴
        axisX = QValueAxis()
        #self.__curAxis=axisX       #当前坐标轴
        axisX.setRange(0, 200)  #设置坐标轴范围
        axisX.setLabelFormat("%d")  #标签格式
        axisX.setTickCount(5)  #主分隔个数
        axisX.setMinorTickCount(4)
        axisX.setGridLineVisible(True)
        axisX.setMinorGridLineVisible(False)

        axisY = QValueAxis()
        axisY.setRange(0, 1024)
        axisY.setLabelFormat("%d")  #标签格式
        axisY.setTickCount(5)
        axisY.setMinorTickCount(4)
        axisY.setGridLineVisible(True)
        axisY.setMinorGridLineVisible(False)

        self.__chart.addAxis(axisX, Qt.AlignBottom)  #坐标轴添加到图表,并指定方向
        self.__chart.addAxis(axisY, Qt.AlignLeft)

        for i in range(15):
            series[i].attachAxis(axisX)
            series[i].attachAxis(axisY)

    def __prepareData(self):  ##为序列设置数据点
        chart = self.ui.chartView.chart()  #获取chartView中的QChart对象
        series = [QLineSeries() for _ in range(15)]
        for i in range(15):
            series[i] = chart.series()[i]
            series[i].clear()

        gasData = self.gas[self.ui.listView.currentIndex().row()]

        sensors = ['' for _ in range(15)]
        for i in range(15):
            sensors[i] = eval(gasData['gas_sensors' + str(i + 1)])
            for j in range(len(sensors[i])):
                series[i].append(j, sensors[i][j])

    @pyqtSlot(bool)
    def on_refreshButton_clicked(self):  #刷新列表
        #先清空所有的list
        self.model.removeRows(0, self.model.rowCount())

        #开始查询该用户的所有气体文件,用list变量gas保存
        list_gasInfo = requests.get(self.url + '/person/getGasInfo/' +
                                    str(self.person_id))
        self.gas = eval(list_gasInfo.text)

        #开始将数据添加进list中
        for i in self.gas:
            self.addItem(i["save_time"])

    @pyqtSlot(bool)
    def on_deleteButton_clicked(self):  #删除某条气体数据
        #如果此时listview没有数据,则跳过
        if self.model.rowCount() == 0:
            pass
        else:
            #弹出确认删除的窗口
            result = QMessageBox.question(
                self, '删除', '是否要删除' +
                self.gas[self.ui.listView.currentIndex().row()]["save_time"],
                QMessageBox.Yes | QMessageBox.Cancel, QMessageBox.NoButton)
            if result == QMessageBox.Yes:
                delete_gasInfo = requests.get(
                    self.url + '/person/deleteGas/' + str(self.gas[
                        self.ui.listView.currentIndex().row()]["gas_id"]))
                #这个地方的删除实际上没有删除数据库中的表,是把gas的is_delete写为了True,显示的时候不做显示

                #先清空所有的list
                self.model.removeRows(0, self.model.rowCount())

                #开始查询该用户的所有气体文件,用list变量gas保存
                list_gasInfo = requests.get(self.url + '/person/getGasInfo/' +
                                            str(self.person_id))
                self.gas = eval(list_gasInfo.text)

                #开始将数据添加进list中
                for i in self.gas:
                    self.addItem(i["save_time"])
class MainApp(QWidget):
    STRANGER_DANGER = 350
    IMAGE_SIZE = (100, 100)

    stranger_color = (179, 20, 20)
    recognized_color = (59, 235, 62)

    def __init__(self, fps=30, parent=None):
        # type: (int, Optional[QWidget]) -> None
        super().__init__(parent=parent)

        self.pkg_path = path.dirname(path.dirname(path.abspath(__file__)))
        self.training_data_dir = path.join(self.pkg_path, 'train')
        self.models_dir = path.join(self.pkg_path, 'models')
        self.model_fname = 'fisherfaces.p'

        try:
            self.model = data_provider.load_model(
                path.join(self.models_dir, self.model_fname))
        except AssertionError:
            self.model = None

        self.existing_labels = QStringListModel(self.get_existing_labels())

        self.fps = fps
        self.video_size = QSize(640, 480)

        self.gray_image = None
        self.detected_faces = []

        # Setup the UI
        self.main_layout = QHBoxLayout()
        self.setLayout(self.main_layout)

        self.control_layout = QVBoxLayout()
        self.control_layout.setSpacing(8)
        self.main_layout.addItem(self.control_layout)

        # Setup the existing label view
        self.labels_view = QListView(parent=self)
        self.labels_view.setModel(self.existing_labels)
        self.labels_view.setSelectionMode(QListView.SingleSelection)
        self.labels_view.setItemDelegate(CapitalizeDelegate(self))
        self.control_layout.addWidget(self.labels_view)

        self.new_label_txt = QLineEdit(self)
        self.new_label_txt.returnPressed.connect(self.add_new_label)
        self.new_label_txt.returnPressed.connect(self.new_label_txt.clear)
        self.control_layout.addWidget(self.new_label_txt)

        self.add_button = QPushButton('Add Label', self)
        self.add_button.clicked.connect(self.add_new_label)
        self.control_layout.addWidget(self.add_button)

        # Setup the training area
        train_box = QGroupBox('Train', self)
        train_box_layout = QVBoxLayout()
        train_box.setLayout(train_box_layout)
        self.control_layout.addWidget(train_box)
        self.train_btn = QPushButton('Train', self)
        self.train_btn.clicked.connect(self.train)
        train_box_layout.addWidget(self.train_btn)

        self.control_layout.addStretch(0)

        # Add take picture shortcut
        self.take_picture_btn = QPushButton('Take picture', self)
        self.take_picture_btn.clicked.connect(self.take_picture)
        self.control_layout.addWidget(self.take_picture_btn)
        shortcut = QShortcut(QKeySequence('Space'), self, self.take_picture)
        shortcut.setWhatsThis('Take picture and add to training data.')

        # Add quit shortcut
        shortcut = QShortcut(QKeySequence('Esc'), self, self.close)
        shortcut.setWhatsThis('Quit')

        # Setup the main camera area
        self.image_label = QLabel(self)
        self.image_label.setFixedSize(self.video_size)
        self.main_layout.addWidget(self.image_label)

        # Setup the camera
        self.capture = cv2.VideoCapture(0)
        self.capture.set(cv2.CAP_PROP_FRAME_WIDTH, self.video_size.width())
        self.capture.set(cv2.CAP_PROP_FRAME_HEIGHT, self.video_size.height())

        self.timer = QTimer()
        self.timer.timeout.connect(self.display_video_stream)
        self.timer.start(int(1000 / self.fps))

    def classify_face(self, image):
        if self.model is None:
            return

        label_idx, distances = self.model.predict(image.ravel(), True)
        label_idx, distance = label_idx[0], distances[0][label_idx]

        labels = self.existing_labels.stringList()
        return labels[label_idx], distance

    def get_training_data(self):
        """Read the images from disk into an n*(w*h) matrix."""
        return data_provider.get_image_data_from_directory(
            self.training_data_dir)

    def train(self):
        X, y, mapping = self.get_training_data()
        # Inspect scree plot to determine appropriate number of PCA components
        classifier = PCALDAClassifier(
            n_components=2, pca_components=200, metric='euclidean',
        ).fit(X, y)

        # Replace the existing running model
        self.model = classifier

        # Save the classifier to file
        data_provider.save_model(
            classifier, path.join(self.models_dir, self.model_fname))

    def add_new_label(self):
        new_label = self.new_label_txt.text()
        new_label = new_label.lower()

        # Prevent empty entries
        if len(new_label) < 3:
            return

        string_list = self.existing_labels.stringList()

        if new_label not in string_list:
            string_list.append(new_label)
            string_list.sort()
            self.existing_labels.setStringList(string_list)

            # Automatically select the added label
            selection_model = self.labels_view.selectionModel()
            index = self.existing_labels.index(string_list.index(new_label))
            selection_model.setCurrentIndex(index, QItemSelectionModel.Select)

    def display_video_stream(self):
        """Read frame from camera and repaint QLabel widget."""
        _, frame = self.capture.read()
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        frame = cv2.flip(frame, 1)

        # Use the Viola-Jones face detector to detect faces to classify
        face_cascade = cv2.CascadeClassifier(path.join(
            self.pkg_path, 'resources', 'haarcascade_frontalface_default.xml'))
        self.gray_image = gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        self.detected_faces = face_cascade.detectMultiScale(gray, 1.3, 5)
        for x, y, w, h in self.detected_faces:
            # Label the detected face as per the model
            face = gray[y:y + h, x:x + w]
            face = cv2.resize(face, self.IMAGE_SIZE)

            result = self.classify_face(face)
            # If a model is loaded, we can predict
            if result:
                predicted, distance = self.classify_face(face)

                if distance > self.STRANGER_DANGER:
                    predicted = 'Stranger danger!'
                    color = self.stranger_color
                else:
                    predicted = predicted.capitalize()
                    color = self.recognized_color

                # Draw a rectangle around the detected face
                cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2)
                text = '%s (%.1f)' % (predicted, distance)
                cv2.putText(frame, text, (x, y + h + 15),
                            cv2.FONT_HERSHEY_TRIPLEX, 0.5, color)
            else:
                # Draw a rectangle around the detected face
                cv2.rectangle(frame, (x, y), (x + w, y + h),
                              self.stranger_color, 2)
                cv2.putText(frame, 'Stranger danger!', (x, y + h + 15),
                            cv2.FONT_HERSHEY_TRIPLEX, 0.5, self.stranger_color)

        # Display the image in the image area
        image = QImage(frame, frame.shape[1], frame.shape[0],
                       frame.strides[0], QImage.Format_RGB888)
        self.image_label.setPixmap(QPixmap.fromImage(image))

    @contextmanager
    def stop_camera_feed(self):
        """Temporarly stop the feed and face detection."""
        try:
            self.timer.stop()
            yield
        finally:
            self.timer.start(int(1000 / self.fps))

    def take_picture(self):
        # Notify the user there were no faces detected
        if self.detected_faces is None or len(self.detected_faces) < 1:
            return
            raise NoFacesError()

        if len(self.detected_faces) > 1:
            return
            raise MultipleFacesError()

        with self.stop_camera_feed():
            x, y, w, h = self.detected_faces[0]

            face = self.gray_image[y:y + h, x:x + w]
            face = cv2.resize(face, self.IMAGE_SIZE)
            denoised_image = cv2.fastNlMeansDenoising(face)

            if not self.selected_label:
                return

            self.save_image(denoised_image, self.selected_label)

    @property
    def selected_label(self):
        index = self.labels_view.selectedIndexes()
        if len(index) < 1:
            return None

        label = self.existing_labels.data(index[0], Qt.DisplayRole)

        return label

    def get_existing_labels(self):
        """Get a list of the currently existing labels"""
        return data_provider.get_folder_names(self.training_data_dir)

    def save_image(self, image: np.ndarray, label: str) -> None:
        """Save an image to disk in the appropriate directory."""
        if not path.exists(self.training_data_dir):
            mkdir(self.training_data_dir)

        label_path = path.join(self.training_data_dir, label)
        if not path.exists(label_path):
            mkdir(label_path)

        existing_files = listdir(label_path)
        existing_files = map(lambda p: path.splitext(p)[0], existing_files)
        existing_files = list(map(int, existing_files))
        last_fname = sorted(existing_files)[-1] if len(existing_files) else 0

        fname = path.join(label_path, '%03d.png' % (last_fname + 1))
        cv2.imwrite(fname, image)
示例#12
0
class MainWindow(QMainWindow):

    def __init__(self, parent=None, data_set = ""):
        super(MainWindow, self).__init__(parent)

        self.current_sum = 0
        self.emg = []
        self.imu = []
        self.myo = None
        self.check_timer = PTimer(self, self.check_sample)

        self.read_data_set(data_set)

        self.initUI()

    def read_data_set(self, data_set):
        with open(data_set) as f:
            self.project = json.load(f)

    def initUI(self):
        self.setWindowTitle(self.project["name"])

        self.setGeometry(240, 120, WIDTH, HEIGHT)
        self.setFixedSize(QSize(WIDTH, HEIGHT))

        files_group = QGroupBox(self)
        files_group.setGeometry(20, 10, 590, 300)
        files_group.setTitle("Files")
        files_group.setStyleSheet(ll_ss)

        act_group = QGroupBox(self)
        act_group.setGeometry(630, 10, 170, 300)
        act_group.setTitle("Actions")
        act_group.setStyleSheet(ll_ss)

        log_group = QGroupBox(self)
        log_group.setGeometry(20, 320, 430, 190)
        log_group.setTitle("Log")
        log_group.setStyleSheet(ll_ss)

        device_group = QGroupBox(self)
        device_group.setGeometry(470, 320, 160, 190)
        device_group.setTitle("Device")
        device_group.setStyleSheet(ll_ss)

        status_group = QGroupBox(self)
        status_group.setGeometry(650, 320, 150, 190)
        status_group.setTitle("Status")
        status_group.setStyleSheet(ll_ss)

        self.wheres = QComboBox(act_group)
        self.gestures = QComboBox(act_group)

        self.wheres.setGeometry(10, 30, 150, 25)

        for w in self.project["sets"]:
            self.wheres.addItem(w)
        self.wheres.currentIndexChanged.connect(self.refresh_list)

        self.gestures.setGeometry(10, 70, 150, 25)

        for g in self.project["gestures"]:
            self.gestures.addItem(g)
        self.gestures.currentIndexChanged.connect(self.refresh_list)

        lbl = QLabel(act_group)
        lbl.setText("EMG(Hz):")
        lbl.move(18, 111)

        self.emg_freq = QSpinBox(act_group)
        self.emg_freq.setMinimum(1)
        self.emg_freq.setMaximum(200)
        self.emg_freq.setValue(self.project["emg_freq"])
        self.emg_freq.move(act_group.width() - 65, 110)

        lbl = QLabel(act_group)
        lbl.setText("IMU(Hz):")
        lbl.move(18, 146)

        self.imu_freq = QSpinBox(act_group)
        self.imu_freq.setMinimum(1)
        self.imu_freq.setMaximum(50)
        self.imu_freq.setValue(self.project["emg_freq"])
        self.imu_freq.setGeometry(act_group.width() - 65, 145, 49, 20)

        lbl = QLabel(act_group)
        lbl.setText("Time(ms):")
        lbl.move(18, 181)

        self.dur = QSpinBox(act_group)
        self.dur.setMinimum(1)
        self.dur.setSingleStep(10)
        self.dur.setMaximum(5000)
        self.dur.setValue(self.project["duration"])
        self.dur.setGeometry(act_group.width() - 71, 181, 55, 20)

        self.imu_check = QCheckBox(act_group)
        self.imu_check.setText("Include IMU")
        self.imu_check.setChecked(self.project["imu_check"] == 1)
        self.imu_check.move(16, 215)

        self.startbtn = QPushButton(act_group)
        self.startbtn.setText("Start")
        self.startbtn.setStyleSheet("QPushButton::disabled{background-color: rgb(245, 245, 245);color: rgb(140, 140, 140); border: none; border-radius:5px;} QPushButton::enabled{background-color: rgb(0, 99, 225);color: white; border: none; border-radius:5px;}")
        self.startbtn.setGeometry(15, 250, 140, 22)
        self.startbtn.clicked.connect(self.start)
        self.startbtn.setEnabled(False)

        self.log_txt = QPlainTextEdit(log_group)
        self.log_txt.setStyleSheet(ll_ss_txt)
        self.log_txt.setGeometry(15, 25, log_group.width() - 30, log_group.height() - 40)
        self.log_txt.textChanged.connect(self.scroll_log_view)
        self.log_txt.setReadOnly(True)
        self.add_log("Application Started")

        self.dev_name = QLabel(device_group)
        self.dev_name.setText("Name: <unknown>")
        self.dev_name.setMaximumWidth(device_group.width() - 30)
        self.dev_name.move(15, 25)

        self.dev_batt = QLabel(device_group)
        self.dev_batt.setText("Battery: <unknown>")
        self.dev_batt.setMaximumWidth(device_group.width() - 30)
        self.dev_batt.move(15, 55)

        dev_con = QLabel(device_group)
        dev_con.setText("Connected: ")
        dev_con.setMaximumWidth(device_group.width() - 30)
        dev_con.move(15, 85)

        self.dev_con_color = QFrame(device_group)
        self.dev_con_color.setStyleSheet("background-color:red;border-radius:10px;")
        self.dev_con_color.setGeometry(device_group.width() - 15 - 20, 83, 20, 20)

        self.conbtn = QPushButton(device_group)
        self.conbtn.setText("Connect")
        self.conbtn.setEnabled(True)
        self.conbtn.setGeometry(10, 110, device_group.width() - 20, 25)
        self.conbtn.clicked.connect(self.connection)

        self.discbtn = QPushButton(device_group)
        self.discbtn.setText("Disconnect")
        self.discbtn.setGeometry(10, 145, device_group.width() - 20, 25)
        self.discbtn.setEnabled(False)
        self.discbtn.clicked.connect(self.disconnection)

        reclbl = QLabel(status_group)
        reclbl.setText("Recording: ")
        reclbl.setMaximumWidth(status_group.width() - 30)
        reclbl.move(15, 25)

        self.rec_con_color = QFrame(status_group)
        self.rec_con_color.setStyleSheet("background-color:red;border-radius:10px;")
        self.rec_con_color.setGeometry(status_group.width() - 15 - 20, 23, 20, 20)

        self.rec_proglbl = QLabel(status_group)
        self.rec_proglbl.setText("Progress: 0%")
        self.rec_proglbl.setMaximumWidth(status_group.width() - 30)
        self.rec_proglbl.setGeometry(15, 55, status_group.width() - 30, 25)

        self.rec_prog = QProgressBar(status_group)
        self.rec_prog.setMaximum(100)
        self.rec_prog.setGeometry(15, 90, status_group.width() - 30, 10)

        self.fileslbl = QLabel(status_group)
        self.fileslbl.setText("Files: ")
        self.fileslbl.setMaximumWidth(status_group.width() - 30)
        self.fileslbl.setGeometry(15, 110, status_group.width() - 30, 25)

        self.listview = PListView(files_group, ll_ss_txt, self.project)
        self.listview.setGeometry(15, 25, files_group.width() - 30, files_group.height() - 40)
        self.listview.setSelectionMode(QAbstractItemView.ExtendedSelection)

        self.listview.doubleClicked.connect(self.view_data)
        self.listview.contextMenuShowed.connect(self.view_data)
        self.listview.contextMenuDifferenced.connect(self.show_diff_data)
        self.listview.contextMenuAveraged.connect(self.show_ave_data)
        self.listview.contextMenuShowedFinder.connect(self.show_finder)
        self.listview.contextMenuMoved.connect(self.move_data)
        self.listview.contextMenuDeleted.connect(self.delete_data)
        self.model = QStringListModel()
        self.listview.setModel(self.model)

        self.list_data_for_set_and_gesture(self.project["sets"][0], self.project["gestures"][0])

        self.add_log("Loading dataset...")
        total = 0
        for set in self.project["sets"]:
            for gesture in self.project["gestures"]:
                path = self.project["location"] + "/" + set + "/" + gesture
                sum = 0
                for f in os.listdir(path):
                    if not f.startswith("."):
                        sum = sum + 1
                total = total + sum
                self.add_log("Found " + str(sum) + " files at " + set + "/" + gesture, False)
        self.add_log("Found " + str(total) + " files")

    def connection(self):
        if not self.myo:
            self.myo = MyoManager(sender=self)

        if not self.myo.connected:
            self.myo.connect()

    def disconnection(self):
        if self.myo:
            if self.myo.connected:
                self.myo.disconnect()

    def view_data(self):
        for index in self.listview.selectedIndexes():
            path = self.project["location"] + "/" + self.project["sets"][self.wheres.currentIndex()] + "/" + self.project["gestures"][self.gestures.currentIndex()] + "/" + index.data() + ".json"
            w = ViewDataWindow(self, [path])
            w.show()

    def show_diff_data(self):
        paths = []
        for index in self.listview.selectedIndexes():
            paths.append(self.project["location"] + "/" + self.project["sets"][self.wheres.currentIndex()] + "/" + self.project["gestures"][self.gestures.currentIndex()] + "/" + index.data() + ".json")
        w = ViewDataWindow(self, paths, 'diff')
        w.show()

    def show_ave_data(self):
        paths = []
        for index in self.listview.selectedIndexes():
            paths.append(self.project["location"] + "/" + self.project["sets"][self.wheres.currentIndex()] + "/" + self.project["gestures"][self.gestures.currentIndex()] + "/" + index.data() + ".json")
        w = ViewDataWindow(self, paths, 'ave')
        w.show()

    def show_finder(self):
        args = ["open", "-R"]
        for index in self.listview.selectedIndexes():
            args.append(self.project["location"] + "/" + self.project["sets"][self.wheres.currentIndex()] + "/" + self.project["gestures"][self.gestures.currentIndex()] + "/" + index.data() + ".json")
        call(args)

    def move_data(self, action:QAction):
        tmp = {}
        for index in self.listview.selectedIndexes():
            tmp[str(index.row())] = index.data()

        for row, data in sorted(tmp.items(), reverse=True):
            self.current_sum = self.current_sum - 1
            where = action.parent().title()
            gesture = action.text()
            src = self.project["location"] + "/" + self.project["sets"][self.wheres.currentIndex()] + "/" + self.project["gestures"][self.gestures.currentIndex()] + "/" + data + ".json"
            dest = self.project["location"] + "/" + where + "/" + gesture + "/" + data + ".json"
            shutil.copy(src, dest)
            os.remove(src)
            self.model.removeRow(int(row))
            
        self.fileslbl.setText("Files: " + str(self.current_sum))

    def delete_data(self):
        result = QMessageBox.question(
            self, 'Yes', 'Are you sure you want to delete this file',
            QMessageBox.Yes | QMessageBox.No, defaultButton=QMessageBox.Yes)
        if result == QMessageBox.Yes:
            tmp = {}
            for index in self.listview.selectedIndexes():
                tmp[str(index.row())] = index.data()

            for row, data in sorted(tmp.items(), reverse=True):
                self.current_sum = self.current_sum - 1
                path = self.project["location"] + "/" + self.project["sets"][self.wheres.currentIndex()] + "/" + self.project["gestures"][self.gestures.currentIndex()] + "/" + data + ".json"
                os.remove(path)
                self.model.removeRow(int(row))

            self.fileslbl.setText("Files: " + str(self.current_sum))

    def scroll_log_view(self):
        self.log_txt.verticalScrollBar().setValue(self.log_txt.document().size().height())

    def list_data_for_set_and_gesture(self, set, gesture):
        self.model.setStringList({})
        path = self.project["location"] + "/" + set + "/" + gesture
        os.chdir(path)
        files = filter(os.path.isfile, os.listdir(path))
        files = [os.path.join(path, f) for f in files]  # add path to each file
        files.sort(key=lambda x: os.path.getmtime(x))
        self.current_sum = 0
        for file in files:
            f = os.path.basename(file).replace(".json", "")
            if f.startswith("."):
                continue
            self.current_sum = self.current_sum + 1
            self.model.insertRow(self.model.rowCount())
            index = self.model.index(self.model.rowCount() - 1)
            self.model.setData(index, f)

        self.fileslbl.setText("Files: " + str(self.current_sum))

    def add_log(self, str, date = True):
        if date:
            #self.log_txt.insertHtml("<p>[" + datetime.datetime.now().strftime("%H:%M:%S") + "]: " + str + "<br></p>")
            self.log_txt.appendPlainText("[" + datetime.datetime.now().strftime("%H:%M:%S") + "]: " + str)
        else:
            #self.log_txt.insertHtml("<p>" + str + "<br></p>")
            self.log_txt.appendPlainText(str)
        #self.log_txt.repaint()

    def refresh_list(self):
        set = self.project["sets"][self.wheres.currentIndex()]
        gesture = self.project["gestures"][self.gestures.currentIndex()]
        self.list_data_for_set_and_gesture(set, gesture)

    def start(self):
        if self.startbtn.isEnabled():
            self.listview.clearFocus()
            # index = QModelIndex()
            # self.listview.setCurrentIndex(index)
            self.startbtn.setEnabled(False)
            self.start_sampling()

    def callback(self, dict):
        type = dict["type"]
        data = dict["data"]
        if type == EventType.connected:
            self.dev_con_color.setStyleSheet("background-color:green;border-radius:10px;")
            self.dev_name.setText("Name: " + repr(data["name"]))
            self.dev_batt.setText("Battery: <unknown>")
            self.conbtn.setEnabled(False)
            self.discbtn.setEnabled(True)
            self.startbtn.setEnabled(True)
        elif type == EventType.battery_level:
            self.dev_batt.setText("Battery: " + str(data["battery"]) + "%")
        elif type == EventType.disconnected:
            self.dev_con_color.setStyleSheet("background-color:red;border-radius:10px;")
            self.dev_name.setText("Name: <unknown>")
            self.dev_batt.setText("Battery: <unknown>")
            self.conbtn.setEnabled(True)
            self.discbtn.setEnabled(False)
            self.startbtn.setEnabled(False)

        self.repaint()

    def start_sampling(self):
        self.add_log("Started sampling data")
        self.rec_con_color.setStyleSheet("background-color:green;border-radius:10px;")

        self.rec_con_color.repaint()

        self.emg.clear()
        self.imu.clear()

        duration = self.dur.value() / 1000

        emg_timer = PTimer(self, self.sample_emg)
        emg_timer.setTickCount(int(duration * self.emg_freq.value()))
        emg_timer.start(1000 / self.emg_freq.value())


        if self.imu_check.isChecked():
            imu_timer = PTimer(self, self.sample_imu)
            imu_timer.setTickCount(int(duration * self.imu_freq.value()))
            imu_timer.start(1000 / self.imu_freq.value())

        self.check_timer.start(100)

    def check_sample(self):
        duration = self.dur.value() / 1000
        emg_ended = len(self.emg) == duration * self.emg_freq.value()
        imu_ended = (len(self.imu) == duration * self.imu_freq.value())
        if emg_ended and (not self.imu_check.isChecked() or (self.imu_check.isChecked() and imu_ended)):
            self.sample_ended()
            self.check_timer.stop()

    def sample_emg(self):
        self.emg.append(self.myo.listener.emg)
        perc = int(len(self.emg) * 100 / (self.dur.value() / 1000 * self.emg_freq.value()))
        self.rec_proglbl.setText("Progress: " + str(perc) + "%")
        self.rec_prog.setValue(perc)

    def sample_imu(self):
        self.imu.append(self.myo.listener.data)

    def sample_ended(self):
        self.rec_con_color.setStyleSheet("background-color:red;border-radius:10px;")

        self.rec_con_color.repaint()

        self.add_log("Sampled ended")
        self.add_log("Saving data")

        name = str(uuid.uuid4())
        path = self.project["location"] + "/" + self.project["sets"][self.wheres.currentIndex()] + "/" + self.project["gestures"][self.gestures.currentIndex()] + "/" + name + ".json"

        data = {
            "date" : datetime.datetime.now().strftime("%d/%m/%y/%H:%M:%S"),
            "duration" : self.dur.value(),
            "emg" : {
                "frequency" : self.emg_freq.value(),
                "data" : self.emg
            }
        }

        if self.imu_check.isChecked():
            data["imu"] = {
                "frequency": self.imu_freq.value(),
                "data": self.imu
            }

        with open(path, 'w') as outfile:
            json.dump(data, outfile)

        self.add_log("Data saved")

        self.model.insertRow(self.model.rowCount())
        index = self.model.index(self.model.rowCount() - 1)
        self.model.setData(index, name)

        self.startbtn.setEnabled(True)

        self.current_sum = self.current_sum + 1
        self.fileslbl.setText("Files: " + str(self.current_sum))

    def keyPressEvent(self, event):
        if event.key() == 16777216:
            self.disconnection()
        elif event.key() == 16777220:
            if not self.listview.hasFocus() or len(self.listview.selectedIndexes()) == 0:
                if self.myo:
                    if self.myo.connected:
                        self.start()

    def closeEvent(self, QCloseEvent):
        self.parent().show()
        self.disconnection()
示例#13
0
class HelpLanguagesDialog(QDialog, Ui_HelpLanguagesDialog):
    """
    Class implementing a dialog to configure the preferred languages.
    """
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        """
        super(HelpLanguagesDialog, self).__init__(parent)
        self.setupUi(self)
        
        self.__model = QStringListModel()
        self.languagesList.setModel(self.__model)
        self.languagesList.selectionModel().currentChanged.connect(
            self.__currentChanged)
        
        languages = Preferences.toList(Preferences.Prefs.settings.value(
            "Help/AcceptLanguages", self.defaultAcceptLanguages()))
        self.__model.setStringList(languages)
        
        allLanguages = []
        for index in range(QLocale.C + 1, QLocale.LastLanguage + 1):
            allLanguages += self.expand(QLocale.Language(index))
        self.__allLanguagesModel = QStringListModel()
        self.__allLanguagesModel.setStringList(allLanguages)
        self.addCombo.setModel(self.__allLanguagesModel)
    
    def __currentChanged(self, current, previous):
        """
        Private slot to handle a change of the current selection.
        
        @param current index of the currently selected item (QModelIndex)
        @param previous index of the previously selected item (QModelIndex)
        """
        self.removeButton.setEnabled(current.isValid())
        row = current.row()
        self.upButton.setEnabled(row > 0)
        self.downButton.setEnabled(
            row != -1 and row < self.__model.rowCount() - 1)

    @pyqtSlot()
    def on_upButton_clicked(self):
        """
        Private slot to move a language up.
        """
        currentRow = self.languagesList.currentIndex().row()
        data = self.languagesList.currentIndex().data()
        self.__model.removeRow(currentRow)
        self.__model.insertRow(currentRow - 1)
        self.__model.setData(self.__model.index(currentRow - 1), data)
        self.languagesList.setCurrentIndex(self.__model.index(currentRow - 1))
    
    @pyqtSlot()
    def on_downButton_clicked(self):
        """
        Private slot to move a language down.
        """
        currentRow = self.languagesList.currentIndex().row()
        data = self.languagesList.currentIndex().data()
        self.__model.removeRow(currentRow)
        self.__model.insertRow(currentRow + 1)
        self.__model.setData(self.__model.index(currentRow + 1), data)
        self.languagesList.setCurrentIndex(self.__model.index(currentRow + 1))
    
    @pyqtSlot()
    def on_removeButton_clicked(self):
        """
        Private slot to remove a language from the list of acceptable
        languages.
        """
        currentRow = self.languagesList.currentIndex().row()
        self.__model.removeRow(currentRow)
    
    @pyqtSlot()
    def on_addButton_clicked(self):
        """
        Private slot to add a language to the list of acceptable languages.
        """
        language = self.addCombo.currentText()
        if language in self.__model.stringList():
            return
        
        self.__model.insertRow(self.__model.rowCount())
        self.__model.setData(self.__model.index(self.__model.rowCount() - 1),
                             language)
        self.languagesList.setCurrentIndex(
            self.__model.index(self.__model.rowCount() - 1))
    
    def accept(self):
        """
        Public method to accept the data entered.
        """
        result = self.__model.stringList()
        if result == self.defaultAcceptLanguages():
            Preferences.Prefs.settings.remove("Help/AcceptLanguages")
        else:
            Preferences.Prefs.settings.setValue("Help/AcceptLanguages", result)
        super(HelpLanguagesDialog, self).accept()
    
    @classmethod
    def httpString(cls, languages):
        """
        Class method to convert a list of acceptable languages into a
        byte array.
       
        The byte array can be sent along with the Accept-Language http header
        (see RFC 2616).
        
        @param languages list of acceptable languages (list of strings)
        @return converted list (QByteArray)
        """
        processed = []
        qvalue = 1.0
        for language in languages:
            leftBracket = language.find('[')
            rightBracket = language.find(']')
            tag = language[leftBracket + 1:rightBracket]
            if not processed:
                processed.append(tag)
            else:
                processed.append("{0};q={1:.1f}".format(tag, qvalue))
            if qvalue > 0.1:
                qvalue -= 0.1
        
        return QByteArray(", ".join(processed))
    
    @classmethod
    def defaultAcceptLanguages(cls):
        """
        Class method to get the list of default accept languages.
        
        @return list of acceptable languages (list of strings)
        """
        language = QLocale.system().name()
        if not language:
            return []
        else:
            return cls.expand(QLocale(language).language())
    
    @classmethod
    def expand(cls, language):
        """
        Class method to expand a language enum to a readable languages
        list.
        
        @param language language number (QLocale.Language)
        @return list of expanded language names (list of strings)
        """
        allLanguages = []
        countries = [l.country() for l in QLocale.matchingLocales(
            language, QLocale.AnyScript, QLocale.AnyCountry)]
        languageString = "{0} [{1}]"\
            .format(QLocale.languageToString(language),
                    QLocale(language).name().split('_')[0])
        allLanguages.append(languageString)
        for country in countries:
            languageString = "{0}/{1} [{2}]"\
                .format(QLocale.languageToString(language),
                        QLocale.countryToString(country),
                        '-'.join(QLocale(language, country).name()
                                 .split('_')).lower())
            if languageString not in allLanguages:
                allLanguages.append(languageString)
        
        return allLanguages
示例#14
0
class MainWindow(QMainWindow):
    labelsChanged = pyqtSignal(list, name='labelsChanged')

    def __init__(self):
        QMainWindow.__init__(self)

        self.view = QGraphicsView()
        self.scene = LabelingScene()

        self.view.setScene(self.scene)
        self.setCentralWidget(self.view)

        self.previousImageIdx = 0
        self.currentImageIdx = 0

        self.setupToolBar()
        self.setupDockWidgets()
        self.setupStatusBar()

        self.showMaximized()

        self.scene.labelsChanged.connect(self.updateLabels)
        self.labelsChanged.connect(self.scene.setLabels)

        self.scene.nextImage.connect(self.nextImage)
        self.scene.previousImage.connect(self.previousImage)
        self.scene.copyLabelsFromPrevious.connect(
            self.copyLabelsFromPreviousImage)

    def setupToolBar(self):
        self.toolbar = QToolBar()

        self.toolbar.addAction(QIcon.fromTheme("document-new"),
                               "Create a new label file", self.newFile)
        self.toolbar.addAction(QIcon.fromTheme("document-open"),
                               "Open a label file", self.openFile)
        saveAction = self.toolbar.addAction(QIcon.fromTheme("document-save"),
                                            "Save", self.saveFile)
        saveAction.setShortcut(QKeySequence(QKeySequence.Save))

        self.toolbar.addSeparator()
        self.toolbar.addAction(QIcon.fromTheme("insert-image"),
                               "Add images to dataset", self.addImages)
        self.toolbar.addSeparator()

        self.toolbar.addAction(QIcon.fromTheme("go-previous"), "Next image",
                               self.nextImage)
        self.toolbar.addAction(QIcon.fromTheme("edit-copy"),
                               "Copy labels from previous image",
                               self.copyLabelsFromPreviousImage)
        self.toolbar.addAction(QIcon.fromTheme("go-next"), "Previous image",
                               self.previousImage)

        self.addToolBar(self.toolbar)

    def setupDockWidgets(self):
        self.fileListWidget = QListView()
        self.leftDockWidget = QDockWidget()
        self.leftDockWidget.setWidget(self.fileListWidget)

        self.fileListModel = QStringListModel(self.fileListWidget)
        self.fileListWidget.setModel(self.fileListModel)
        self.fileListWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)

        self.fileListWidget.doubleClicked.connect(
            lambda index: self.loadImageAtIndex(index.row()))

        self.addDockWidget(Qt.LeftDockWidgetArea, self.leftDockWidget)

    def setupStatusBar(self):
        self.statusBar = QStatusBar()
        self.setStatusBar(self.statusBar)

        self.currentImageLabel = QLabel()
        self.statusBar.addPermanentWidget(self.currentImageLabel)

        self.scene.mouseMoved.connect(lambda p: self.statusBar.showMessage(
            "Mouse at scene pos {}, {}".format(p.x(), p.y())))

    #Here we assume that the image files are stored in the same folder as the labels file.
    #This is usual practice, maybe in the future we could put images and labels in the same file (HDF5)
    def newFile(self):
        imageFileNames = self.normalizeFileNames(
            QFileDialog.getOpenFileNames(
                caption="Select image files to label")[0])
        self.labelsFileName = QFileDialog.getSaveFileName(
            caption="Select labels file to save")[0]
        self.labelsFolder = QFileInfo(self.labelsFileName).absolutePath()

        print("Creating new labels file with {} images to be labeled".format(
            len(imageFileNames)))

        self.labeledImages = []

        for imageFileName in imageFileNames:
            self.labeledImages.append(LabeledImage(imageFileName, []))

        self.loadImageAtIndex(0)
        self.previousImageIdx = 0
        self.fileListModel.setStringList(imageFileNames)

    def normalizeFileNames(self, fileNames):
        return [QFileInfo(f).fileName() for f in fileNames]

    def loadImageAtIndex(self, index):
        if (index < 0) or (index >= len(self.labeledImages)):
            return

        self.previousImageIdx = self.currentImageIdx
        self.currentImageIdx = index
        self.scene.displayLabeledImage(
            self.labeledImages[self.currentImageIdx], self.labelsFolder)

        self.fileListWidget.setCurrentIndex(
            self.fileListModel.index(self.currentImageIdx, 0))

        msg = "{} ({}/{})".format(
            self.labeledImages[self.currentImageIdx].fileName,
            self.currentImageIdx + 1, len(self.labeledImages))

        self.currentImageLabel.setText(msg)

    def updateLabels(self, newLabels):
        self.labeledImages[self.currentImageIdx].labels = newLabels

    def openFile(self):
        labelsFileName = QFileDialog.getOpenFileName(
            caption="Select labels file to load")[0]

        if labelsFileName == "" or len(labelsFileName) == 0:
            return

        self.labelsFileName = labelsFileName
        self.labelsFolder = QFileInfo(self.labelsFileName).absolutePath()
        self.labeledImages = readXML(self.labelsFileName)

        self.fileListModel.setStringList(
            [s.fileName for s in self.labeledImages])

        self.loadImageAtIndex(0)
        self.previousImageIdx = 0

        self.scene.labelsCache = self.allLabelNames()

    def saveFile(self):
        writeXML(self.labelsFileName, self.labeledImages)
        self.statusBar.showMessage("Saved!")

    def nextImage(self):
        self.loadImageAtIndex(self.currentImageIdx + 1)

    def previousImage(self):
        self.loadImageAtIndex(self.currentImageIdx - 1)

    def copyLabelsFromPreviousImage(self):
        self.labeledImages[self.currentImageIdx].labels = self.labeledImages[
            self.previousImageIdx].labels

        self.labelsChanged.emit(
            self.labeledImages[self.currentImageIdx].labels)

    def allLabelNames(self):
        labelNames = []

        for labeledImage in self.labeledImages:
            for label in labeledImage.labels:
                if label.classLabel not in labelNames:
                    labelNames.append(label.classLabel)

        return labelNames

    def addImages(self):
        imageFileNames = QFileDialog.getOpenFileNames(
            caption="Select image files to label")

        if not imageFileNames[0] or len(imageFileNames[0]) == 0:
            return

        imageFileNames = imageFileNames[0]
        labelsDir = QFileInfo(self.labelsFileName).absoluteDir()
        originDir = QFileInfo(imageFileNames[0]).absoluteDir()

        #Copy image files to the labels folder
        if originDir.absolutePath() != labelsDir.absolutePath():
            progDialog = QProgressDialog(
                "Copying image files to the labels folder", "Cancel", 0,
                len(imageFileNames), self)

        i = 0
        for imageFileName in imageFileNames:
            progDialog.setValue(i)

            oldName = QFileInfo(imageFileName).fileName()
            newPath = labelsDir.absoluteFilePath(oldName)

            print("Copying {} to {}".format(imageFileName, newPath))

            ok = QFile.copy(imageFileName, newPath)

            QApplication.processEvents()

            if not ok:
                print("Error copying {} to {}".format(imageFileName, newPath))

            i += 1

        progDialog.setValue(len(imageFileNames))
        progDialog.close()

        currentImageFileNames = [s.fileName for s in self.labeledImages]

        newImgIdx = len(self.labeledImages)

        for imageFileName in imageFileNames:
            normalizedFileName = QFileInfo(imageFileName).fileName()

            if normalizedFileName in currentImageFileNames:
                print("File {} already in dataset, skipping".format(
                    normalizedFileName))
                continue

            self.labeledImages.append(LabeledImage(normalizedFileName, []))

        self.fileListModel.setStringList(
            [s.fileName for s in self.labeledImages])
        self.loadImageAtIndex(newImgIdx)

        print("Added {} images to dataset".format(len(imageFileNames)))
        print("New length of labeledImages array {}".format(
            len(self.labeledImages)))
示例#15
0
class MyMainView(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        # python2 中的写法,等同于   super().__init__(parent)
        super(MyMainView, self).__init__(parent)
        super().setupUi(self)
        # self.setupUi(self)
        # 获取当前工作路径
        self.path = os.getcwd()
        self.slm = QStringListModel()
        self.qlist = []
        self.slm.setStringList(self.qlist)
        self.musicListView.setModel(self.slm)
        self.searchBtn.setStyleSheet(
            '''QPushButton{background:#F7667;border-radius:5px;}''')

        self.searchBtn.clicked.connect(self.search_music)

        self.musicListView.clicked.connect(self.clickItem)
        self.musicListView.setContextMenuPolicy(Qt.CustomContextMenu)
        # listview 右键点击事件
        self.musicListView.customContextMenuRequested[QPoint].connect(
            self.listWidgetContext)

        # 选择存储文件夹
        self.selectFolder.triggered.connect(self.selectFolderAction)

        # 选择歌曲源

        self.sourceBox.addItems(['网抑云', 'QQ', '虾米', '酷狗', '百度'])

    def selectFolderAction(self):
        directory = QFileDialog.getExistingDirectory(self, "选择文件夹", "./")
        self.path = directory
        # 当窗口非继承QtWidgets.QDialog时,self可替换成 None
        print(directory)

    def listWidgetContext(self):
        popMenu = QMenu(self.musicListView)
        popMenu.addAction("下载", self.downloadMusic)
        popMenu.addAction("查看", self.getInfo)
        popMenu.exec_(QCursor.pos())

    def getInfo(self):
        QMessageBox.information(
            self, 'ListWidgets',
            "你选择了{}".format(self.musicListView.currentIndex().row()))

    def clickItem(self, qModelIndex):
        QMessageBox.information(
            self, 'ListWidgets',
            "你选择了{}".format(self.slm.stringList()[qModelIndex.row()]))

    def search_music(self):
        '''创建线程'''
        # strip 去除字符串的前后空格
        search_content = self.musicEdit.text().strip()
        if not search_content:
            self.showMessage("请输入搜索内容!!!")
        else:
            self.back_task = BackTaskThread(search_content,
                                            self.sourceBox.currentIndex())
            # 连接信号
            self.back_task.update_music_list.connect(self.update_music_list)
            # 开始线程
            self.back_task.start()

    def update_music_list(self, music_list):
        '''根据返回结果更新UI界面'''
        self.music_list = music_list
        self.slm.removeRows(0, self.slm.rowCount())
        for music in music_list:
            row = self.slm.rowCount()
            self.slm.insertRow(row)

            self.slm.setData(self.slm.index(row), music.name)
        #
    def downloadMusic(self):
        # 获取现在选择的列表中的第几个
        row = self.musicListView.currentIndex().row()
        print(row)
        music = self.music_list[row]
        self.download_music_task = DownloadMusicThread(self.path, music.name,
                                                       music.id, music.source)
        # self.download_music_task.download_music.connect(lambda : self.showMessage("下载完成!!!"))
        self.download_music_task.download_music.connect(self.updateProgressBar)
        self.download_music_task.start()

    def showMessage(self, message):
        QMessageBox.information(self, 'ListWidgets', message)

    def updateProgressBar(self, progress):
        if progress == -1:
            '''无法下载删除这个条目'''
            row = self.musicListView.currentIndex().row()
            # 删除该元素
            self.music_list.remove(self.music_list[row])
            self.slm.removeRow(row)
            button = QMessageBox.warning(
                self, "Warning", "恢复出厂设置将导致用户数据丢失,是否继续操作?",
                QMessageBox.Reset | QMessageBox.Help | QMessageBox.Cancel,
                QMessageBox.Reset)
        else:
            self.progressBar.setValue(progress)
示例#16
0
class QIndex_statusInfo(QDialog):
    username = ''
    person_id = ''
    url = ''
    status = []
    def __init__(self, parent = None):
        super().__init__(parent)
        self.ui = Ui_Index_statusInfo()
        self.ui.setupUi(self)

        self.model=QStringListModel(self)
        self.ui.listView.setModel(self.model)
        self.ui.listView.setEditTriggers(QAbstractItemView.NoEditTriggers)
    
    def setInfo(self, username, person_id, url):
        self.username = username
        self.person_id = person_id
        self.url = url

        #开始查询该用户的所有气体文件,用list变量gas保存
        list_statusInfo = requests.get(self.url + '/person/getStatusInfo/' + str(self.person_id))
        self.status = eval(list_statusInfo.text)

        #开始将数据添加进list中
        for i in self.status:
            self.addItem(i["save_name"])
    
    def addItem(self, name): #向list添加数据
        #在尾部插入一空行 
        lastRow = self.model.rowCount()
        self.model.insertRow(lastRow)
        #给该空行设置显示
        index = self.model.index(lastRow, 0)
        self.model.setData(index, name, Qt.DisplayRole)
        #选中该行
        self.ui.listView.setCurrentIndex(index)
    
    def on_listView_clicked(self,index): #如果某条数据信息被点击,则向绘图框发送信息
        index = index.row()
        status = self.status[index]
        self.ui.saveTimeEdit.setText(status['gas_save_time'])
        self.ui.preTypeEdit.setText(status['status_type'])
        self.ui.preLogisticEdit.setText(status['status_Logistic'])
        self.ui.preSVCEdit.setText(status['status_SVC'])
        self.ui.preLinearSVCEdit.setText(status['status_LinearSVC'])
        self.ui.prePerceptronEdit.setText(status['status_Perceptron'])
        self.ui.preKNNEdit.setText(status['status_Knn'])
        self.ui.preForestEdit.setText(status['status_Forest'])
        self.ui.preGaussianEdit.setText(status['status_Gaussian'])
        self.ui.preVoteEdit.setText(self.vote(status))

    def vote(self, status):
        res = []
        if status['status_Logistic'] != 'None':
            res.append(status['status_Logistic'])
        if status['status_SVC'] != 'None':
            res.append(status['status_SVC'])
        if status['status_LinearSVC'] != 'None':
            res.append(status['status_LinearSVC'])
        if status['status_Perceptron'] != 'None':
            res.append(status['status_Perceptron'])
        if status['status_Knn'] != 'None':
            res.append(status['status_Knn'])
        if status['status_Forest'] != 'None':
            res.append(status['status_Forest'])
        if status['status_Gaussian'] != 'None':
            res.append(status['status_Gaussian'])
        return Counter(res).most_common(1)[0][0]
    
    @pyqtSlot(bool)
    def on_refreshButton_clicked(self):
        #先清空所有的list
        self.model.removeRows(0, self.model.rowCount())

        #开始查询该用户的所有气体文件,用list变量status保存
        list_statusInfo = requests.get(self.url + '/person/getStatusInfo/' + str(self.person_id))
        self.status = eval(list_statusInfo.text)

        #开始将数据添加进list中
        for i in self.status:
            self.addItem(i["save_name"])


    @pyqtSlot(bool)
    def on_deleteButton_clicked(self):
        #如果此时listview没有数据,则跳过
        if self.model.rowCount() == 0:
            pass
        else:
            #弹出确认删除的窗口
            result = QMessageBox.question(self, '删除', '是否要删除' + self.status[self.ui.listView.currentIndex().row()]["save_name"], QMessageBox.Yes|QMessageBox.Cancel, QMessageBox.NoButton)
            if result == QMessageBox.Yes:
                delete_statusInfo = requests.get(self.url + '/person/deleteStatus/' + str(self.status[self.ui.listView.currentIndex().row()]["status_id"]))
                #这个地方的删除实际上没有删除数据库中的表,是把status的is_delete写为了True,显示的时候不做显示

                #先清空所有的list
                self.model.removeRows(0, self.model.rowCount())

                #开始查询该用户的所有气体文件,用list变量status保存
                list_statusInfo = requests.get(self.url + '/person/getStatusInfo/' + str(self.person_id))
                self.status = eval(list_statusInfo.text)

                #开始将数据添加进list中
                for i in self.status:
                    self.addItem(i["save_name"])
示例#17
0
class SoundpacksTab(QTabWidget):
    def __init__(self):
        super(SoundpacksTab, self).__init__()

        self.tab_disabled = False

        self.qnam = QNetworkAccessManager()

        self.http_reply = None
        self.download_http_reply = None
        self.current_repo_info = None

        self.soundpacks = []
        self.soundpacks_model = None

        self.installing_new_soundpack = False
        self.downloading_new_soundpack = False
        self.extracting_new_soundpack = False

        self.close_after_install = False

        self.game_dir = None
        self.soundpacks_dir = None

        layout = QVBoxLayout()

        top_part = QWidget()
        tp_layout = QHBoxLayout()
        tp_layout.setContentsMargins(0, 0, 0, 0)
        self.tp_layout = tp_layout

        installed_gb = QGroupBox()
        tp_layout.addWidget(installed_gb)
        self.installed_gb = installed_gb

        installed_gb_layout = QVBoxLayout()
        installed_gb.setLayout(installed_gb_layout)
        self.installed_gb_layout = installed_gb_layout

        installed_lv = QListView()
        installed_lv.clicked.connect(self.installed_clicked)
        installed_lv.setEditTriggers(QAbstractItemView.NoEditTriggers)
        installed_gb_layout.addWidget(installed_lv)
        self.installed_lv = installed_lv

        installed_buttons = QWidget()
        ib_layout = QHBoxLayout()
        installed_buttons.setLayout(ib_layout)
        ib_layout.setContentsMargins(0, 0, 0, 0)
        self.ib_layout = ib_layout
        self.installed_buttons = installed_buttons
        installed_gb_layout.addWidget(installed_buttons)

        disable_existing_button = QPushButton()
        disable_existing_button.clicked.connect(self.disable_existing)
        disable_existing_button.setEnabled(False)
        ib_layout.addWidget(disable_existing_button)
        self.disable_existing_button = disable_existing_button

        delete_existing_button = QPushButton()
        delete_existing_button.clicked.connect(self.delete_existing)
        delete_existing_button.setEnabled(False)
        ib_layout.addWidget(delete_existing_button)
        self.delete_existing_button = delete_existing_button

        repository_gb = QGroupBox()
        tp_layout.addWidget(repository_gb)
        self.repository_gb = repository_gb

        repository_gb_layout = QVBoxLayout()
        repository_gb.setLayout(repository_gb_layout)
        self.repository_gb_layout = repository_gb_layout

        repository_lv = QListView()
        repository_lv.clicked.connect(self.repository_clicked)
        repository_lv.setEditTriggers(QAbstractItemView.NoEditTriggers)
        repository_gb_layout.addWidget(repository_lv)
        self.repository_lv = repository_lv

        suggest_new_label = QLabel()
        suggest_new_label.setOpenExternalLinks(True)
        repository_gb_layout.addWidget(suggest_new_label)
        self.suggest_new_label = suggest_new_label

        install_new_button = QPushButton()
        install_new_button.clicked.connect(self.install_new)
        install_new_button.setEnabled(False)
        repository_gb_layout.addWidget(install_new_button)
        self.install_new_button = install_new_button

        top_part.setLayout(tp_layout)
        layout.addWidget(top_part)
        self.top_part = top_part

        details_gb = QGroupBox()
        layout.addWidget(details_gb)
        self.details_gb = details_gb

        details_gb_layout = QGridLayout()

        viewname_label = QLabel()
        details_gb_layout.addWidget(viewname_label, 0, 0, Qt.AlignRight)
        self.viewname_label = viewname_label

        viewname_le = QLineEdit()
        viewname_le.setReadOnly(True)
        details_gb_layout.addWidget(viewname_le, 0, 1)
        self.viewname_le = viewname_le

        name_label = QLabel()
        details_gb_layout.addWidget(name_label, 1, 0, Qt.AlignRight)
        self.name_label = name_label

        name_le = QLineEdit()
        name_le.setReadOnly(True)
        details_gb_layout.addWidget(name_le, 1, 1)
        self.name_le = name_le

        path_label = QLabel()
        details_gb_layout.addWidget(path_label, 2, 0, Qt.AlignRight)
        self.path_label = path_label

        path_le = QLineEdit()
        path_le.setReadOnly(True)
        details_gb_layout.addWidget(path_le, 2, 1)
        self.path_le = path_le

        size_label = QLabel()
        details_gb_layout.addWidget(size_label, 3, 0, Qt.AlignRight)
        self.size_label = size_label

        size_le = QLineEdit()
        size_le.setReadOnly(True)
        details_gb_layout.addWidget(size_le, 3, 1)
        self.size_le = size_le

        homepage_label = QLabel()
        details_gb_layout.addWidget(homepage_label, 4, 0, Qt.AlignRight)
        self.homepage_label = homepage_label

        homepage_tb = QTextBrowser()
        homepage_tb.setReadOnly(True)
        homepage_tb.setOpenExternalLinks(True)
        homepage_tb.setMaximumHeight(23)
        homepage_tb.setLineWrapMode(QTextEdit.NoWrap)
        homepage_tb.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        details_gb_layout.addWidget(homepage_tb, 4, 1)
        self.homepage_tb = homepage_tb

        details_gb.setLayout(details_gb_layout)
        self.details_gb_layout = details_gb_layout

        self.setLayout(layout)

        self.load_repository()
        self.set_text()

    def set_text(self):
        self.installed_gb.setTitle(_('Installed'))
        self.disable_existing_button.setText(_('Disable'))
        self.delete_existing_button.setText(_('Delete'))
        self.repository_gb.setTitle(_('Repository'))
        suggest_url = cons.NEW_ISSUE_URL + '?' + urlencode({
            'title':
            _('Add this new soundpack to the repository'),
            'body':
            _('''* Name: [Enter the name of the soundpack]
* Url: [Enter the Url where we can find the soundpack]
* Author: [Enter the name of the author]
* Homepage: [Enter the Url of the author website or where the soundpack was published]
* Soundpack not found in version: {version}
''').format(version=version)
        })
        self.suggest_new_label.setText(
            _('<a href="{url}">Suggest a new soundpack '
              'on GitHub</a>').format(url=suggest_url))
        self.install_new_button.setText(_('Install this soundpack'))
        self.details_gb.setTitle(_('Details'))
        self.viewname_label.setText(_('View name:'))
        self.name_label.setText(_('Name:'))

        selection_model = self.repository_lv.selectionModel()
        if selection_model is not None and selection_model.hasSelection():
            self.path_label.setText(_('Url:'))
        else:
            self.path_label.setText(_('Path:'))

        self.size_label.setText(_('Size:'))
        self.homepage_label.setText(_('Home page:'))

    def get_main_window(self):
        return self.parentWidget().parentWidget().parentWidget()

    def get_main_tab(self):
        return self.parentWidget().parentWidget().main_tab

    def get_mods_tab(self):
        return self.get_main_tab().get_mods_tab()

    def get_settings_tab(self):
        return self.get_main_tab().get_settings_tab()

    def get_backups_tab(self):
        return self.get_main_tab().get_backups_tab()

    def disable_tab(self):
        self.tab_disabled = True

        self.disable_existing_button.setEnabled(False)
        self.delete_existing_button.setEnabled(False)

        self.install_new_button.setEnabled(False)

        installed_selection = self.installed_lv.selectionModel()
        if installed_selection is not None:
            installed_selection.clearSelection()

        repository_selection = self.repository_lv.selectionModel()
        if repository_selection is not None:
            repository_selection.clearSelection()

    def enable_tab(self):
        self.tab_disabled = False

        installed_selection = self.installed_lv.selectionModel()
        if installed_selection is None:
            installed_selected = False
        else:
            installed_selected = installed_selection.hasSelection()

        self.disable_existing_button.setEnabled(installed_selected)
        self.delete_existing_button.setEnabled(installed_selected)

        repository_selection = self.repository_lv.selectionModel()
        if repository_selection is None:
            repository_selected = False
        else:
            repository_selected = repository_selection.hasSelection()

        self.install_new_button.setEnabled(repository_selected)

    def load_repository(self):
        self.repo_soundpacks = []

        self.install_new_button.setEnabled(False)

        self.repo_soundpacks_model = QStringListModel()
        self.repository_lv.setModel(self.repo_soundpacks_model)
        self.repository_lv.selectionModel().currentChanged.connect(
            self.repository_selection)

        json_file = get_data_path('soundpacks.json')

        if os.path.isfile(json_file):
            with open(json_file, 'r', encoding='utf8') as f:
                try:
                    values = json.load(f)
                    if isinstance(values, list):
                        values.sort(key=lambda x: x['name'])
                        self.repo_soundpacks = values

                        self.repo_soundpacks_model.insertRows(
                            self.repo_soundpacks_model.rowCount(),
                            len(self.repo_soundpacks))
                        for index, soundpack_info in enumerate(
                                self.repo_soundpacks):
                            self.repo_soundpacks_model.setData(
                                self.repo_soundpacks_model.index(index),
                                soundpack_info['viewname'])
                except ValueError:
                    pass

    def install_new(self):
        if not self.installing_new_soundpack:
            selection_model = self.repository_lv.selectionModel()
            if selection_model is None or not selection_model.hasSelection():
                return

            selected = selection_model.currentIndex()
            selected_info = self.repo_soundpacks[selected.row()]

            # Is it already installed?
            for soundpack in self.soundpacks:
                if soundpack['NAME'] == selected_info['name']:
                    confirm_msgbox = QMessageBox()
                    confirm_msgbox.setWindowTitle(
                        _('Soundpack already present'))
                    confirm_msgbox.setText(
                        _('It seems this soundpack is '
                          'already installed. The launcher will not overwrite '
                          'the soundpack if it has the same directory name. You '
                          'might want to delete the soundpack first if you want '
                          'to update it. Also, there can only be a single '
                          'soundpack with the same name value available in the '
                          'game.'))
                    confirm_msgbox.setInformativeText(
                        _('Are you sure you want '
                          'to install the {view} soundpack?').format(
                              view=selected_info['viewname']))
                    confirm_msgbox.addButton(_('Install the soundpack'),
                                             QMessageBox.YesRole)
                    confirm_msgbox.addButton(_('Do not install again'),
                                             QMessageBox.NoRole)
                    confirm_msgbox.setIcon(QMessageBox.Warning)

                    if confirm_msgbox.exec() == 1:
                        return
                    break

            self.install_type = selected_info['type']

            if selected_info['type'] == 'direct_download':
                if self.http_reply is not None and self.http_reply.isRunning():
                    self.http_reply_aborted = True
                    self.http_reply.abort()

                self.installing_new_soundpack = True
                self.download_aborted = False

                download_dir = tempfile.mkdtemp(prefix=cons.TEMP_PREFIX)

                download_url = selected_info['url']

                url = QUrl(download_url)
                file_info = QFileInfo(url.path())
                file_name = file_info.fileName()

                self.downloaded_file = os.path.join(download_dir, file_name)
                self.downloading_file = open(self.downloaded_file, 'wb')

                main_window = self.get_main_window()

                status_bar = main_window.statusBar()
                status_bar.clearMessage()

                status_bar.busy += 1

                downloading_label = QLabel()
                downloading_label.setText(
                    _('Downloading: {0}').format(selected_info['url']))
                status_bar.addWidget(downloading_label, 100)
                self.downloading_label = downloading_label

                dowloading_speed_label = QLabel()
                status_bar.addWidget(dowloading_speed_label)
                self.dowloading_speed_label = dowloading_speed_label

                downloading_size_label = QLabel()
                status_bar.addWidget(downloading_size_label)
                self.downloading_size_label = downloading_size_label

                progress_bar = QProgressBar()
                status_bar.addWidget(progress_bar)
                self.downloading_progress_bar = progress_bar
                progress_bar.setMinimum(0)

                self.download_last_read = datetime.utcnow()
                self.download_last_bytes_read = 0
                self.download_speed_count = 0

                self.downloading_new_soundpack = True

                request = QNetworkRequest(QUrl(url))
                request.setRawHeader(b'User-Agent', cons.FAKE_USER_AGENT)

                self.download_http_reply = self.qnam.get(request)
                self.download_http_reply.finished.connect(
                    self.download_http_finished)
                self.download_http_reply.readyRead.connect(
                    self.download_http_ready_read)
                self.download_http_reply.downloadProgress.connect(
                    self.download_dl_progress)

                self.install_new_button.setText(
                    _('Cancel soundpack '
                      'installation'))
                self.installed_lv.setEnabled(False)
                self.repository_lv.setEnabled(False)

                self.get_main_tab().disable_tab()
                self.get_mods_tab().disable_tab()
                self.get_settings_tab().disable_tab()
                self.get_backups_tab().disable_tab()
            elif selected_info['type'] == 'browser_download':
                bd_dialog = BrowserDownloadDialog(
                    'soundpack', selected_info['url'],
                    selected_info.get('expected_filename', None))
                bd_dialog.exec()

                if bd_dialog.downloaded_path is not None:
                    self.installing_new_soundpack = True
                    self.downloaded_file = bd_dialog.downloaded_path

                    self.install_new_button.setText(
                        _('Cancel soundpack '
                          'installation'))
                    self.installed_lv.setEnabled(False)
                    self.repository_lv.setEnabled(False)

                    self.get_main_tab().disable_tab()
                    self.get_mods_tab().disable_tab()
                    self.get_settings_tab().disable_tab()
                    self.get_backups_tab().disable_tab()

                    main_window = self.get_main_window()
                    status_bar = main_window.statusBar()

                    # Test downloaded file
                    status_bar.showMessage(
                        _('Testing downloaded file archive'))

                    if self.downloaded_file.lower().endswith('.7z'):
                        try:
                            with open(self.downloaded_file, 'rb') as f:
                                archive = Archive7z(f)
                        except FormatError:
                            status_bar.clearMessage()
                            status_bar.showMessage(
                                _('Selected file is a '
                                  'bad archive file'))

                            self.finish_install_new_soundpack()
                            return
                        except NoPasswordGivenError:
                            status_bar.clearMessage()
                            status_bar.showMessage(
                                _('Selected file is a '
                                  'password protected archive file'))

                            self.finish_install_new_soundpack()
                            return
                    else:
                        archive_exception = None
                        if self.downloaded_file.lower().endswith('.zip'):
                            archive_class = zipfile.ZipFile
                            archive_exception = zipfile.BadZipFile
                            test_method = 'testzip'
                        elif self.downloaded_file.lower().endswith('.rar'):
                            archive_class = rarfile.RarFile
                            archive_exception = rarfile.Error
                            test_method = 'testrar'
                        else:
                            extension = os.path.splitext(
                                self.downloaded_file)[1]
                            status_bar.clearMessage()
                            status_bar.showMessage(
                                _('Unknown downloaded archive format '
                                  '({extension})').format(extension=extension))

                            self.finish_install_new_soundpack()
                            return

                        try:
                            with archive_class(self.downloaded_file) as z:
                                test = getattr(z, test_method)
                                if test() is not None:
                                    status_bar.clearMessage()
                                    status_bar.showMessage(
                                        _('Downloaded archive is invalid'))

                                    self.finish_install_new_soundpack()
                                    return
                        except archive_exception:
                            status_bar.clearMessage()
                            status_bar.showMessage(
                                _('Selected file is a '
                                  'bad archive file'))

                            self.finish_install_new_soundpack()
                            return

                    status_bar.clearMessage()
                    self.extract_new_soundpack()

        else:
            main_window = self.get_main_window()
            status_bar = main_window.statusBar()

            # Cancel installation
            if self.downloading_new_soundpack:
                self.download_aborted = True
                self.download_http_reply.abort()
            elif self.extracting_new_soundpack:
                self.extracting_timer.stop()

                status_bar.removeWidget(self.extracting_label)
                status_bar.removeWidget(self.extracting_progress_bar)

                status_bar.busy -= 1

                self.extracting_new_soundpack = False

                self.extracting_zipfile.close()

                download_dir = os.path.dirname(self.downloaded_file)
                delete_path(download_dir)

                if os.path.isdir(self.extract_dir):
                    delete_path(self.extract_dir)

            status_bar.showMessage(_('Soundpack installation cancelled'))

            self.finish_install_new_soundpack()

    def download_http_finished(self):
        self.downloading_file.close()

        main_window = self.get_main_window()

        status_bar = main_window.statusBar()
        status_bar.removeWidget(self.downloading_label)
        status_bar.removeWidget(self.dowloading_speed_label)
        status_bar.removeWidget(self.downloading_size_label)
        status_bar.removeWidget(self.downloading_progress_bar)

        status_bar.busy -= 1

        if self.download_aborted:
            download_dir = os.path.dirname(self.downloaded_file)
            delete_path(download_dir)

            self.downloading_new_soundpack = False
        else:
            redirect = self.download_http_reply.attribute(
                QNetworkRequest.RedirectionTargetAttribute)
            if redirect is not None:
                download_dir = os.path.dirname(self.downloaded_file)
                delete_path(download_dir)
                os.makedirs(download_dir)

                self.downloading_file = open(self.downloaded_file, 'wb')

                status_bar.busy += 1

                redirected_url = urljoin(
                    self.download_http_reply.request().url().toString(),
                    redirect.toString())

                downloading_label = QLabel()
                downloading_label.setText(
                    _('Downloading: {0}').format(redirected_url))
                status_bar.addWidget(downloading_label, 100)
                self.downloading_label = downloading_label

                dowloading_speed_label = QLabel()
                status_bar.addWidget(dowloading_speed_label)
                self.dowloading_speed_label = dowloading_speed_label

                downloading_size_label = QLabel()
                status_bar.addWidget(downloading_size_label)
                self.downloading_size_label = downloading_size_label

                progress_bar = QProgressBar()
                status_bar.addWidget(progress_bar)
                self.downloading_progress_bar = progress_bar
                progress_bar.setMinimum(0)

                self.download_last_read = datetime.utcnow()
                self.download_last_bytes_read = 0
                self.download_speed_count = 0

                progress_bar.setValue(0)

                request = QNetworkRequest(QUrl(redirected_url))
                request.setRawHeader(b'User-Agent', cons.FAKE_USER_AGENT)

                self.download_http_reply = self.qnam.get(request)
                self.download_http_reply.finished.connect(
                    self.download_http_finished)
                self.download_http_reply.readyRead.connect(
                    self.download_http_ready_read)
                self.download_http_reply.downloadProgress.connect(
                    self.download_dl_progress)
            else:
                # Test downloaded file
                status_bar.showMessage(_('Testing downloaded file archive'))

                if self.downloaded_file.lower().endswith('.7z'):
                    try:
                        with open(self.downloaded_file, 'rb') as f:
                            archive = Archive7z(f)
                    except FormatError:
                        status_bar.clearMessage()
                        status_bar.showMessage(
                            _('Selected file is a '
                              'bad archive file'))

                        self.finish_install_new_soundpack()
                        return
                    except NoPasswordGivenError:
                        status_bar.clearMessage()
                        status_bar.showMessage(
                            _('Selected file is a '
                              'password protected archive file'))

                        self.finish_install_new_soundpack()
                        return
                else:
                    archive_exception = None
                    if self.downloaded_file.lower().endswith('.zip'):
                        archive_class = zipfile.ZipFile
                        archive_exception = zipfile.BadZipFile
                        test_method = 'testzip'
                    elif self.downloaded_file.lower().endswith('.rar'):
                        archive_class = rarfile.RarFile
                        archive_exception = rarfile.Error
                        test_method = 'testrar'
                    else:
                        extension = os.path.splitext(self.downloaded_file)[1]
                        status_bar.clearMessage()
                        status_bar.showMessage(
                            _('Unknown downloaded archive format ({extension})'
                              ).format(extension=extension))

                        self.finish_install_new_soundpack()
                        return

                    try:
                        with archive_class(self.downloaded_file) as z:
                            test = getattr(z, test_method)
                            if test() is not None:
                                status_bar.clearMessage()
                                status_bar.showMessage(
                                    _('Downloaded archive is invalid'))

                                self.finish_install_new_soundpack()
                                return
                    except archive_exception:
                        status_bar.clearMessage()
                        status_bar.showMessage(
                            _('Selected file is a '
                              'bad archive file'))

                        self.finish_install_new_soundpack()
                        return

                status_bar.clearMessage()
                self.downloading_new_soundpack = False
                self.extract_new_soundpack()

    def finish_install_new_soundpack(self):
        self.installing_new_soundpack = False

        self.installed_lv.setEnabled(True)
        self.repository_lv.setEnabled(True)

        self.install_new_button.setText(_('Install this soundpack'))

        self.get_main_tab().enable_tab()
        self.get_mods_tab().enable_tab()
        self.get_settings_tab().enable_tab()
        self.get_backups_tab().enable_tab()

        if self.close_after_install:
            self.get_main_window().close()

    def download_http_ready_read(self):
        self.downloading_file.write(self.download_http_reply.readAll())

    def download_dl_progress(self, bytes_read, total_bytes):
        self.downloading_progress_bar.setMaximum(total_bytes)
        self.downloading_progress_bar.setValue(bytes_read)

        self.download_speed_count += 1

        self.downloading_size_label.setText(
            '{bytes_read}/{total_bytes}'.format(
                bytes_read=sizeof_fmt(bytes_read),
                total_bytes=sizeof_fmt(total_bytes)))

        if self.download_speed_count % 5 == 0:
            delta_bytes = bytes_read - self.download_last_bytes_read
            delta_time = datetime.utcnow() - self.download_last_read

            bytes_secs = delta_bytes / delta_time.total_seconds()
            self.dowloading_speed_label.setText(
                _('{bytes_sec}/s').format(bytes_sec=sizeof_fmt(bytes_secs)))

            self.download_last_bytes_read = bytes_read
            self.download_last_read = datetime.utcnow()

    def extract_new_soundpack(self):
        self.extracting_new_soundpack = True

        if self.downloaded_file.lower().endswith('.7z'):
            self.extracting_zipfile = open(self.downloaded_file, 'rb')
            self.extracting_archive = Archive7z(self.extracting_zipfile)

            self.extracting_infolist = self.extracting_archive.getmembers()
        else:
            if self.downloaded_file.lower().endswith('.zip'):
                archive_class = zipfile.ZipFile
            elif self.downloaded_file.lower().endswith('.rar'):
                archive_class = rarfile.RarFile

            z = archive_class(self.downloaded_file)
            self.extracting_zipfile = z

            self.extracting_infolist = z.infolist()

        self.extract_dir = os.path.join(self.game_dir, 'newsoundpack')
        while os.path.exists(self.extract_dir):
            self.extract_dir = os.path.join(
                self.game_dir,
                'newsoundpack-{0}'.format('%08x' % random.randrange(16**8)))
        os.makedirs(self.extract_dir)

        self.extracting_index = 0

        main_window = self.get_main_window()
        status_bar = main_window.statusBar()

        status_bar.busy += 1

        extracting_label = QLabel()
        status_bar.addWidget(extracting_label, 100)
        self.extracting_label = extracting_label

        progress_bar = QProgressBar()
        status_bar.addWidget(progress_bar)
        self.extracting_progress_bar = progress_bar

        timer = QTimer(self)
        self.extracting_timer = timer

        progress_bar.setRange(0, len(self.extracting_infolist))

        def timeout():
            self.extracting_progress_bar.setValue(self.extracting_index)

            if self.extracting_index == len(self.extracting_infolist):
                self.extracting_timer.stop()

                main_window = self.get_main_window()
                status_bar = main_window.statusBar()

                status_bar.removeWidget(self.extracting_label)
                status_bar.removeWidget(self.extracting_progress_bar)

                status_bar.busy -= 1

                self.extracting_new_soundpack = False

                self.extracting_zipfile.close()
                self.extracting_zipfile = None

                if self.downloaded_file.lower().endswith('.7z'):
                    self.extracting_archive = None

                if self.install_type == 'direct_download':
                    download_dir = os.path.dirname(self.downloaded_file)
                    delete_path(download_dir)

                self.move_new_soundpack()

            else:
                extracting_element = self.extracting_infolist[
                    self.extracting_index]

                self.extracting_label.setText(
                    _('Extracting {0}').format(extracting_element.filename))

                if self.downloaded_file.lower().endswith('.7z'):
                    destination = os.path.join(
                        self.extract_dir,
                        *extracting_element.filename.split('/'))
                    dest_dir = os.path.dirname(destination)
                    if not os.path.isdir(dest_dir):
                        os.makedirs(dest_dir)
                    with open(destination, 'wb') as f:
                        f.write(extracting_element.read())
                else:
                    self.extracting_zipfile.extract(extracting_element,
                                                    self.extract_dir)

                self.extracting_index += 1

        timer.timeout.connect(timeout)
        timer.start(0)

    def move_new_soundpack(self):
        # Find the soundpack in the self.extract_dir
        # Move the soundpack from that location into self.soundpacks_dir

        self.moving_new_soundpack = True

        main_window = self.get_main_window()
        status_bar = main_window.statusBar()

        status_bar.showMessage(_('Finding the soundpack'))

        next_scans = deque()
        current_scan = scandir(self.extract_dir)

        soundpack_dir = None

        while True:
            try:
                entry = next(current_scan)
                if entry.is_dir():
                    next_scans.append(entry.path)
                elif entry.is_file():
                    dirname, basename = os.path.split(entry.path)
                    if basename == 'soundpack.txt':
                        soundpack_dir = dirname
                        entry = None
                        break
            except StopIteration:
                if len(next_scans) > 0:
                    current_scan = scandir(next_scans.popleft())
                else:
                    break

        for item in current_scan:
            pass

        if soundpack_dir is None:
            status_bar.showMessage(
                _('Soundpack installation cancelled - There '
                  'is no soundpack in the downloaded archive'))
            delete_path(self.extract_dir)
            self.moving_new_soundpack = False

            self.finish_install_new_soundpack()
        else:
            soundpack_dir_name = os.path.basename(soundpack_dir)
            target_dir = os.path.join(self.soundpacks_dir, soundpack_dir_name)
            if os.path.exists(target_dir):
                status_bar.showMessage(
                    _('Soundpack installation cancelled - '
                      'There is already a {basename} directory in '
                      '{soundpacks_dir}').format(
                          basename=soundpack_dir_name,
                          soundpacks_dir=self.soundpacks_dir))
            else:
                shutil.move(soundpack_dir, self.soundpacks_dir)
                status_bar.showMessage(_('Soundpack installation completed'))

            delete_path(self.extract_dir)
            self.moving_new_soundpack = False

            self.game_dir_changed(self.game_dir)
            self.finish_install_new_soundpack()

    def disable_existing(self):
        selection_model = self.installed_lv.selectionModel()
        if selection_model is None or not selection_model.hasSelection():
            return

        selected = selection_model.currentIndex()
        selected_info = self.soundpacks[selected.row()]

        if selected_info['enabled']:
            config_file = os.path.join(selected_info['path'], 'soundpack.txt')
            new_config_file = os.path.join(selected_info['path'],
                                           'soundpack.txt.disabled')
            try:
                shutil.move(config_file, new_config_file)
                selected_info['enabled'] = False
                self.soundpacks_model.setData(
                    selected, selected_info['VIEW'] + _(' (Disabled)'))
                self.disable_existing_button.setText(_('Enable'))
            except OSError as e:
                main_window = self.get_main_window()
                status_bar = main_window.statusBar()

                status_bar.showMessage(str(e))
        else:
            config_file = os.path.join(selected_info['path'],
                                       'soundpack.txt.disabled')
            new_config_file = os.path.join(selected_info['path'],
                                           'soundpack.txt')
            try:
                shutil.move(config_file, new_config_file)
                selected_info['enabled'] = True
                self.soundpacks_model.setData(selected, selected_info['VIEW'])
                self.disable_existing_button.setText(_('Disable'))
            except OSError as e:
                main_window = self.get_main_window()
                status_bar = main_window.statusBar()

                status_bar.showMessage(str(e))

    def delete_existing(self):
        selection_model = self.installed_lv.selectionModel()
        if selection_model is None or not selection_model.hasSelection():
            return

        selected = selection_model.currentIndex()
        selected_info = self.soundpacks[selected.row()]

        confirm_msgbox = QMessageBox()
        confirm_msgbox.setWindowTitle(_('Delete soundpack'))
        confirm_msgbox.setText(
            _('This will delete the soundpack directory. It '
              'cannot be undone.'))
        confirm_msgbox.setInformativeText(
            _('Are you sure you want to '
              'delete the {view} soundpack?').format(
                  view=selected_info['VIEW']))
        confirm_msgbox.addButton(_('Delete the soundpack'),
                                 QMessageBox.YesRole)
        confirm_msgbox.addButton(_('I want to keep the soundpack'),
                                 QMessageBox.NoRole)
        confirm_msgbox.setIcon(QMessageBox.Warning)

        if confirm_msgbox.exec() == 0:
            main_window = self.get_main_window()
            status_bar = main_window.statusBar()

            if not delete_path(selected_info['path']):
                status_bar.showMessage(_('Soundpack deletion cancelled'))
            else:
                self.soundpacks_model.removeRows(selected.row(), 1)
                self.soundpacks.remove(selected_info)

                status_bar.showMessage(_('Soundpack deleted'))

    def installed_selection(self, selected, previous):
        self.installed_clicked()

    def installed_clicked(self):
        selection_model = self.installed_lv.selectionModel()
        if selection_model is not None and selection_model.hasSelection():
            selected = selection_model.currentIndex()
            selected_info = self.soundpacks[selected.row()]

            self.viewname_le.setText(selected_info['VIEW'])
            self.name_le.setText(selected_info['NAME'])
            self.path_label.setText(_('Path:'))
            self.path_le.setText(selected_info['path'])
            self.size_le.setText(sizeof_fmt(selected_info['size']))
            self.homepage_tb.setText('')

            if selected_info['enabled']:
                self.disable_existing_button.setText(_('Disable'))
            else:
                self.disable_existing_button.setText(_('Enable'))

        if not self.tab_disabled:
            self.disable_existing_button.setEnabled(True)
            self.delete_existing_button.setEnabled(True)

        self.install_new_button.setEnabled(False)

        repository_selection = self.repository_lv.selectionModel()
        if repository_selection is not None:
            repository_selection.clearSelection()

    def repository_selection(self, selected, previous):
        self.repository_clicked()

    def repository_clicked(self):
        selection_model = self.repository_lv.selectionModel()
        if selection_model is not None and selection_model.hasSelection():
            selected = selection_model.currentIndex()
            selected_info = self.repo_soundpacks[selected.row()]

            self.viewname_le.setText(selected_info['viewname'])
            self.name_le.setText(selected_info['name'])

            if selected_info['type'] == 'direct_download':
                self.path_label.setText(_('Url:'))
                self.path_le.setText(selected_info['url'])
                self.homepage_tb.setText('<a href="{url}">{url}</a>'.format(
                    url=html.escape(selected_info['homepage'])))
                if 'size' not in selected_info:
                    if not (self.current_repo_info is not None
                            and self.http_reply is not None
                            and self.http_reply.isRunning()
                            and self.current_repo_info is selected_info):
                        if (self.http_reply is not None
                                and self.http_reply.isRunning()):
                            self.http_reply_aborted = True
                            self.http_reply.abort()

                        self.http_reply_aborted = False
                        self.size_le.setText(_('Getting remote size'))
                        self.current_repo_info = selected_info

                        request = QNetworkRequest(QUrl(selected_info['url']))
                        request.setRawHeader(b'User-Agent',
                                             cons.FAKE_USER_AGENT)

                        self.http_reply = self.qnam.head(request)
                        self.http_reply.finished.connect(
                            self.size_query_finished)
                else:
                    self.size_le.setText(sizeof_fmt(selected_info['size']))
            elif selected_info['type'] == 'browser_download':
                self.path_label.setText(_('Url:'))
                self.path_le.setText(selected_info['url'])
                self.homepage_tb.setText('<a href="{url}">{url}</a>'.format(
                    url=html.escape(selected_info['homepage'])))
                if 'size' in selected_info:
                    self.size_le.setText(sizeof_fmt(selected_info['size']))
                else:
                    self.size_le.setText(_('Unknown'))

        if (self.soundpacks_dir is not None
                and os.path.isdir(self.soundpacks_dir)
                and not self.tab_disabled):
            self.install_new_button.setEnabled(True)
        self.disable_existing_button.setEnabled(False)
        self.delete_existing_button.setEnabled(False)

        installed_selection = self.installed_lv.selectionModel()
        if installed_selection is not None:
            installed_selection.clearSelection()

    def size_query_finished(self):
        if (not self.http_reply_aborted and self.http_reply.attribute(
                QNetworkRequest.HttpStatusCodeAttribute) == 200
                and self.http_reply.hasRawHeader(b'Content-Length')):

            content_length = int(self.http_reply.rawHeader(b'Content-Length'))
            self.current_repo_info['size'] = content_length

            selection_model = self.repository_lv.selectionModel()
            if selection_model is not None and selection_model.hasSelection():
                selected = selection_model.currentIndex()
                selected_info = self.repo_soundpacks[selected.row()]

                if selected_info is self.current_repo_info:
                    self.size_le.setText(sizeof_fmt(content_length))
        else:
            selection_model = self.repository_lv.selectionModel()
            if selection_model is not None and selection_model.hasSelection():
                selected = selection_model.currentIndex()
                selected_info = self.repo_soundpacks[selected.row()]

                if selected_info is self.current_repo_info:
                    self.size_le.setText(_('Unknown'))

    def config_info(self, config_file):
        val = {}
        try:
            with open(config_file, 'r', encoding='latin1') as f:
                for line in f:
                    if line.startswith('NAME'):
                        space_index = line.find(' ')
                        name = line[space_index:].strip().replace(',', '')
                        val['NAME'] = name
                    elif line.startswith('VIEW'):
                        space_index = line.find(' ')
                        view = line[space_index:].strip()
                        val['VIEW'] = view

                    if 'NAME' in val and 'VIEW' in val:
                        break
        except FileNotFoundError:
            return val
        return val

    def scan_size(self, soundpack_info):
        next_scans = deque()
        current_scan = scandir(soundpack_info['path'])

        total_size = 0

        while True:
            try:
                entry = next(current_scan)
                if entry.is_dir():
                    next_scans.append(entry.path)
                elif entry.is_file():
                    total_size += entry.stat().st_size
            except StopIteration:
                if len(next_scans) > 0:
                    current_scan = scandir(next_scans.popleft())
                else:
                    break

        return total_size

    def add_soundpack(self, soundpack_info):
        index = self.soundpacks_model.rowCount()
        self.soundpacks_model.insertRows(self.soundpacks_model.rowCount(), 1)
        disabled_text = ''
        if not soundpack_info['enabled']:
            disabled_text = _(' (Disabled)')
        self.soundpacks_model.setData(self.soundpacks_model.index(index),
                                      soundpack_info['VIEW'] + disabled_text)

    def clear_soundpacks(self):
        self.game_dir = None
        self.soundpacks = []

        self.disable_existing_button.setEnabled(False)
        self.delete_existing_button.setEnabled(False)
        self.install_new_button.setEnabled(False)

        if self.soundpacks_model is not None:
            self.soundpacks_model.setStringList([])
        self.soundpacks_model = None

        repository_selection = self.repository_lv.selectionModel()
        if repository_selection is not None:
            repository_selection.clearSelection()

        self.viewname_le.setText('')
        self.name_le.setText('')
        self.path_le.setText('')
        self.size_le.setText('')
        self.homepage_tb.setText('')

    def game_dir_changed(self, new_dir):
        self.game_dir = new_dir
        self.soundpacks = []

        self.disable_existing_button.setEnabled(False)
        self.delete_existing_button.setEnabled(False)

        self.soundpacks_model = QStringListModel()
        self.installed_lv.setModel(self.soundpacks_model)
        self.installed_lv.selectionModel().currentChanged.connect(
            self.installed_selection)

        repository_selection = self.repository_lv.selectionModel()
        if repository_selection is not None:
            repository_selection.clearSelection()
        self.install_new_button.setEnabled(False)

        self.viewname_le.setText('')
        self.name_le.setText('')
        self.path_le.setText('')
        self.size_le.setText('')
        self.homepage_tb.setText('')

        soundpacks_dir = os.path.join(new_dir, 'data', 'sound')
        if os.path.isdir(soundpacks_dir):
            self.soundpacks_dir = soundpacks_dir

            dir_scan = scandir(soundpacks_dir)

            while True:
                try:
                    entry = next(dir_scan)
                    if entry.is_dir():
                        soundpack_path = entry.path
                        config_file = os.path.join(soundpack_path,
                                                   'soundpack.txt')
                        if os.path.isfile(config_file):
                            info = self.config_info(config_file)
                            if 'NAME' in info and 'VIEW' in info:
                                soundpack_info = {
                                    'path': soundpack_path,
                                    'enabled': True
                                }
                                soundpack_info.update(info)

                                self.soundpacks.append(soundpack_info)
                                soundpack_info['size'] = (
                                    self.scan_size(soundpack_info))
                                self.add_soundpack(soundpack_info)
                                continue
                        disabled_config_file = os.path.join(
                            soundpack_path, 'soundpack.txt.disabled')
                        if os.path.isfile(disabled_config_file):
                            info = self.config_info(disabled_config_file)
                            if 'NAME' in info and 'VIEW' in info:
                                soundpack_info = {
                                    'path': soundpack_path,
                                    'enabled': False
                                }
                                soundpack_info.update(info)

                                self.soundpacks.append(soundpack_info)
                                soundpack_info['size'] = (
                                    self.scan_size(soundpack_info))
                                self.add_soundpack(soundpack_info)

                except StopIteration:
                    break
        else:
            self.soundpacks_dir = None
示例#18
0
class MainWindow(QMainWindow):
    """MNELAB main window.
    """
    def __init__(self):
        super().__init__()

        # restore settings
        settings = self._read_settings()
        self.recent = settings["recent"]  # list of recent files
        if settings["geometry"]:
            self.restoreGeometry(settings["geometry"])
        else:
            self.setGeometry(300, 300, 1000, 750)  # default window size
            self.move(QApplication.desktop().screen().rect().center() -
                      self.rect().center())  # center window
        if settings["state"]:
            self.restoreState(settings["state"])

        self.setWindowTitle("MNELAB")

        # initialize menus
        menubar = self.menuBar()

        file_menu = menubar.addMenu("&File")
        file_menu.addAction("&Open...", self.open_file, QKeySequence.Open)
        self.recent_menu = file_menu.addMenu("Open recent")
        self.recent_menu.aboutToShow.connect(self._update_recent_menu)
        self.recent_menu.triggered.connect(self._load_recent)
        if not self.recent:
            self.recent_menu.setEnabled(False)
        self.close_file_action = file_menu.addAction("&Close", self.close_file,
                                                     QKeySequence.Close)
        self.close_all_action = file_menu.addAction("Close all",
                                                    self.close_all)
        file_menu.addSeparator()
        self.import_bad_action = file_menu.addAction("Import bad channels...",
                                                     self.import_bads)
        self.import_anno_action = file_menu.addAction("Import annotations...",
                                                      self.import_annotations)
        file_menu.addSeparator()
        self.export_raw_action = file_menu.addAction("Export &raw...",
                                                     self.export_raw)
        self.export_bad_action = file_menu.addAction("Export &bad channels...",
                                                     self.export_bads)
        self.export_anno_action = file_menu.addAction("Export &annotations...",
                                                      self.export_annotations)
        self.export_events_action = file_menu.addAction("Export &events...",
                                                        self.export_events)
        file_menu.addSeparator()
        file_menu.addAction("&Quit", self.close, QKeySequence.Quit)

        edit_menu = menubar.addMenu("&Edit")
        self.pick_chans_action = edit_menu.addAction("Pick &channels...",
                                                     self.pick_channels)
        self.chan_props_action = edit_menu.addAction("Channel &properties...",
                                                     self.channel_properties)
        self.set_montage_action = edit_menu.addAction("Set &montage...",
                                                      self.set_montage)
        edit_menu.addSeparator()
        self.setref_action = edit_menu.addAction("&Set reference...",
                                                 self.set_reference)

        plot_menu = menubar.addMenu("&Plot")
        self.plot_raw_action = plot_menu.addAction("&Raw data", self.plot_raw)
        self.plot_psd_action = plot_menu.addAction("&Power spectral "
                                                   "density...", self.plot_psd)
        self.plot_montage_action = plot_menu.addAction("Current &montage",
                                                       self.plot_montage)

        tools_menu = menubar.addMenu("&Tools")
        self.filter_action = tools_menu.addAction("&Filter data...",
                                                  self.filter_data)
        self.find_events_action = tools_menu.addAction("Find &events...",
                                                       self.find_events)
        self.run_ica_action = tools_menu.addAction("Run &ICA...")
        self.import_ica_action = tools_menu.addAction("&Load ICA...",
                                                      self.load_ica)

        view_menu = menubar.addMenu("&View")
        statusbar_action = view_menu.addAction("Statusbar",
                                               self._toggle_statusbar)
        statusbar_action.setCheckable(True)

        help_menu = menubar.addMenu("&Help")
        help_menu.addAction("&About", self.show_about)
        help_menu.addAction("About &Qt", self.show_about_qt)

        # set up data model for sidebar (list of open files)
        self.names = QStringListModel()
        self.names.dataChanged.connect(self._update_names)
        splitter = QSplitter()
        self.sidebar = QListView()
        self.sidebar.setFrameStyle(QFrame.NoFrame)
        self.sidebar.setFocusPolicy(Qt.NoFocus)
        self.sidebar.setModel(self.names)
        self.sidebar.clicked.connect(self._update_data)
        splitter.addWidget(self.sidebar)
        self.infowidget = InfoWidget()
        splitter.addWidget(self.infowidget)
        width = splitter.size().width()
        splitter.setSizes((width * 0.3, width * 0.7))
        self.setCentralWidget(splitter)

        self.status_label = QLabel()
        self.statusBar().addPermanentWidget(self.status_label)
        if settings["statusbar"]:
            self.statusBar().show()
            statusbar_action.setChecked(True)
        else:
            self.statusBar().hide()
            statusbar_action.setChecked(False)

        self.setAcceptDrops(True)

        self._toggle_actions(False)
        self.show()

    def open_file(self):
        """Show open file dialog.
        """
        fname = QFileDialog.getOpenFileName(self, "Open file",
                                            filter=SUPPORTED_FORMATS)[0]
        if fname:
            self.load_file(fname)

    def load_file(self, fname):
        """Load file.

        Parameters
        ----------
        fname : str
            File name.
        """
        if not exists(fname):
            QMessageBox.critical(self, "File not found",
                                 "{} does not exist.".format(fname))
            self._remove_recent(fname)
            return
        name, ext = splitext(split(fname)[-1])
        ftype = ext[1:].upper()
        if ext not in SUPPORTED_FORMATS:
            raise ValueError("File format {} is not supported.".format(ftype))

        if ext in [".edf", ".bdf"]:
            raw = mne.io.read_raw_edf(fname, stim_channel=-1, preload=True)
            history.append("raw = mne.io.read_raw_edf('{}', "
                           "stim_channel=-1, preload=True)".format(fname))
        elif ext in [".fif"]:
            raw = mne.io.read_raw_fif(fname, preload=True)
            history.append("raw = mne.io.read_raw_fif('{}',"
                           "preload=True)".format(fname))
        elif ext in [".vhdr"]:
            raw = mne.io.read_raw_brainvision(fname, preload=True)
            history.append("raw = mne.io.read_raw_brainvision('{}', "
                           "preload=True)".format(fname))

        data.insert_data(DataSet(name=name, fname=fname, ftype=ftype, raw=raw))
        self.find_events()
        self._update_sidebar(data.names, data.index)
        self._update_infowidget()
        self._update_statusbar()
        self._add_recent(fname)
        self._toggle_actions()

    def export_raw(self):
        """Export raw to FIF file.
        """
        fname = QFileDialog.getSaveFileName(self, "Export raw",
                                            filter="*.fif")[0]
        if fname:
            name, ext = splitext(split(fname)[-1])
            ext = ext if ext else ".fif"  # automatically add extension
            fname = join(split(fname)[0], name + ext)
            data.current.raw.save(fname)

    def export_bads(self):
        """Export bad channels info to a CSV file.
        """
        fname = QFileDialog.getSaveFileName(self, "Export bad channels",
                                            filter="*.csv")[0]
        if fname:
            name, ext = splitext(split(fname)[-1])
            ext = ext if ext else ".csv"  # automatically add extension
            fname = join(split(fname)[0], name + ext)
            with open(fname, "w") as f:
                f.write(",".join(data.current.raw.info["bads"]))

    def import_bads(self):
        """Import bad channels info from a CSV file.
        """
        fname = QFileDialog.getOpenFileName(self, "Import bad channels",
                                            filter="*.csv")[0]
        if fname:
            with open(fname) as f:
                bads = f.read().replace(" ", "").split(",")
                if set(bads) - set(data.current.raw.info["ch_names"]):
                    QMessageBox.critical(self, "Channel labels not found",
                                         "Some channel labels from the file "
                                         "are not present in the data.")
                else:
                    data.current.raw.info["bads"] = bads
                    data.data[data.index].raw.info["bads"] = bads

    def export_events(self):
        """Export events to a CSV file.

        The resulting CSV file has two columns. The first column contains the
        position (in samples), whereas the second column contains the type of
        the events. The first line is a header containing the column names.
        """
        fname = QFileDialog.getSaveFileName(self, "Export events",
                                            filter="*.csv")[0]
        if fname:
            name, ext = splitext(split(fname)[-1])
            ext = ext if ext else ".csv"  # automatically add extension
            fname = join(split(fname)[0], name + ext)
            np.savetxt(fname, data.current.events[:, [0, 2]], fmt="%d",
                       delimiter=",", header="pos,type", comments="")

    def export_annotations(self):
        """Export annotations to a CSV file.

        The resulting CSV file has three columns. The first column contains the
        annotation type, the second column contains the onset (in s), and the
        third column contains the duration (in s). The first line is a header
        containing the column names.
        """
        fname = QFileDialog.getSaveFileName(self, "Export annotations",
                                            filter="*.csv")[0]
        if fname:
            name, ext = splitext(split(fname)[-1])
            ext = ext if ext else ".csv"  # automatically add extension
            fname = join(split(fname)[0], name + ext)
            anns = data.current.raw.annotations
            with open(fname, "w") as f:
                f.write("type,onset,duration\n")
                for a in zip(anns.description, anns.onset, anns.duration):
                    f.write(",".join([a[0], str(a[1]), str(a[2])]))
                    f.write("\n")

    def import_annotations(self):
        fname = QFileDialog.getOpenFileName(self, "Import annotations",
                                            filter="*.csv")[0]
        if fname:
            descs, onsets, durations = [], [], []
            fs = data.current.raw.info["sfreq"]
            with open(fname) as f:
                f.readline()  # skip header
                for line in f:
                    ann = line.split(",")
                    onset = float(ann[1].strip())
                    duration = float(ann[2].strip())
                    if onset > data.current.raw.n_times / fs:
                        QMessageBox.critical(self, "Invalid annotations",
                                             "One or more annotations are "
                                             "outside of the data range.")
                        return
                    descs.append(ann[0].strip())
                    onsets.append(onset)
                    durations.append(duration)
            annotations = mne.Annotations(onsets, durations, descs)
            data.raw.annotations = annotations
            data.data[data.index].raw.annotations = annotations
            self._update_infowidget()

    def close_file(self):
        """Close current file.
        """
        data.remove_data()
        self._update_sidebar(data.names, data.index)
        self._update_infowidget()
        self._update_statusbar()

        if not data:
            self._toggle_actions(False)

    def close_all(self):
        """Close all currently open data sets.
        """
        msg = QMessageBox.question(self, "Close all data sets",
                                   "Close all data sets?")
        if msg == QMessageBox.Yes:
            while data:
                self.close_file()

    def get_info(self):
        """Get basic information on current file.

        Returns
        -------
        info : dict
            Dictionary with information on current file.
        """
        raw = data.current.raw
        fname = data.current.fname
        ftype = data.current.ftype
        reference = data.current.reference
        events = data.current.events
        montage = data.current.montage

        if raw.info["bads"]:
            nbads = len(raw.info["bads"])
            nchan = "{} ({} bad)".format(raw.info["nchan"], nbads)
        else:
            nchan = raw.info["nchan"]
        chans = Counter([channel_type(raw.info, i)
                         for i in range(raw.info["nchan"])])

        if events is not None:
            nevents = events.shape[0]
            unique = [str(e) for e in sorted(set(events[:, 2]))]
            if len(unique) > 20:  # do not show all events
                first = ", ".join(unique[:10])
                last = ", ".join(unique[-10:])
                events = "{} ({})".format(nevents, first + ", ..., " + last)
            else:
                events = "{} ({})".format(nevents, ", ".join(unique))
        else:
            events = "-"

        if isinstance(reference, list):
            reference = ",".join(reference)

        if raw.annotations is not None:
            annots = len(raw.annotations.description)
        else:
            annots = "-"

        return {"File name": fname if fname else "-",
                "File type": ftype if ftype else "-",
                "Number of channels": nchan,
                "Channels": ", ".join(
                    [" ".join([str(v), k.upper()]) for k, v in chans.items()]),
                "Samples": raw.n_times,
                "Sampling frequency": str(raw.info["sfreq"]) + " Hz",
                "Length": str(raw.n_times / raw.info["sfreq"]) + " s",
                "Events": events,
                "Annotations": annots,
                "Reference": reference if reference else "-",
                "Montage": montage if montage is not None else "-",
                "Size in memory": "{:.2f} MB".format(
                    raw._data.nbytes / 1024 ** 2),
                "Size on disk": "-" if not fname else "{:.2f} MB".format(
                    getsize(fname) / 1024 ** 2)}

    def pick_channels(self):
        """Pick channels in current data set.
        """
        channels = data.current.raw.info["ch_names"]
        dialog = PickChannelsDialog(self, channels, selected=channels)
        if dialog.exec_():
            picks = [item.data(0) for item in dialog.channels.selectedItems()]
            drops = set(channels) - set(picks)
            tmp = data.current.raw.drop_channels(drops)
            name = data.current.name + " (channels dropped)"
            new = DataSet(raw=tmp, name=name, events=data.current.events)
            history.append("raw.drop({})".format(drops))
            self._update_datasets(new)

    def channel_properties(self):
        info = data.current.raw.info
        dialog = ChannelPropertiesDialog(self, info)
        if dialog.exec_():
            dialog.model.sort(0)
            bads = []
            renamed = {}
            types = {}
            for i in range(dialog.model.rowCount()):
                new_label = dialog.model.item(i, 1).data(Qt.DisplayRole)
                old_label = info["ch_names"][i]
                if new_label != old_label:
                    renamed[old_label] = new_label
                new_type = dialog.model.item(i, 2).data(Qt.DisplayRole).lower()
                old_type = channel_type(info, i).lower()
                if new_type != old_type:
                    types[new_label] = new_type
                if dialog.model.item(i, 3).checkState() == Qt.Checked:
                    bads.append(info["ch_names"][i])
            info["bads"] = bads
            data.data[data.index].raw.info["bads"] = bads
            if renamed:
                mne.rename_channels(info, renamed)
                mne.rename_channels(data.data[data.index].raw.info, renamed)
            if types:
                data.current.raw.set_channel_types(types)
                data.data[data.index].raw.set_channel_types(types)
            self._update_infowidget()
            self._toggle_actions(True)

    def set_montage(self):
        """Set montage.
        """
        path = join(mne.__path__[0], "channels", "data", "montages")
        supported = (".elc", ".txt", ".csd", ".sfp", ".elp", ".hpts", ".loc",
                     ".locs", ".eloc", ".bvef")
        files = [splitext(f) for f in listdir(path)]
        montages = sorted([f for f, ext in files if ext in supported],
                          key=str.lower)
        # TODO: currently it is not possible to remove an existing montage
        dialog = MontageDialog(self, montages,
                               selected=data.current.montage)
        if dialog.exec_():
            name = dialog.montages.selectedItems()[0].data(0)
            montage = mne.channels.read_montage(name)

            ch_names = data.current.raw.info["ch_names"]
            # check if at least one channel name matches a name in the montage
            if set(ch_names) & set(montage.ch_names):
                data.current.montage = name
                data.data[data.index].montage = name
                data.current.raw.set_montage(montage)
                data.data[data.index].raw.set_montage(montage)
                self._update_infowidget()
                self._toggle_actions()
            else:
                QMessageBox.critical(self, "No matching channel names",
                                     "Channel names defined in the montage do "
                                     "not match any channel name in the data.")

    def plot_raw(self):
        """Plot raw data.
        """
        events = data.current.events
        nchan = data.current.raw.info["nchan"]
        fig = data.current.raw.plot(events=events, n_channels=nchan,
                                        title=data.current.name,
                                        show=False)
        history.append("raw.plot(n_channels={})".format(nchan))
        win = fig.canvas.manager.window
        win.setWindowTitle("Raw data")
        win.findChild(QStatusBar).hide()
        win.installEventFilter(self)  # detect if the figure is closed

        # prevent closing the window with the escape key
        try:
            key_events = fig.canvas.callbacks.callbacks["key_press_event"][8]
        except KeyError:
            pass
        else:  # this requires MNE >=0.15
            key_events.func.keywords["params"]["close_key"] = None

        fig.show()

    def plot_psd(self):
        """Plot power spectral density (PSD).
        """
        fig = data.current.raw.plot_psd(average=False,
                                            spatial_colors=False, show=False)
        win = fig.canvas.manager.window
        win.setWindowTitle("Power spectral density")
        fig.show()

    def plot_montage(self):
        """Plot montage.
        """
        montage = mne.channels.read_montage(data.current.montage)
        fig = montage.plot(show_names=True, show=False)
        win = fig.canvas.manager.window
        win.setWindowTitle("Montage")
        win.findChild(QStatusBar).hide()
        win.findChild(QToolBar).hide()
        fig.show()

    def load_ica(self):
        """Load ICA solution from a file.
        """
        fname = QFileDialog.getOpenFileName(self, "Load ICA",
                                            filter="*.fif *.fif.gz")
        if fname[0]:
            self.state.ica = mne.preprocessing.read_ica(fname[0])

    def find_events(self):
        events = mne.find_events(data.current.raw, consecutive=False)
        if events.shape[0] > 0:  # if events were found
            data.current.events = events
            data.data[data.index].events = events
            self._update_infowidget()

    def filter_data(self):
        """Filter data.
        """
        dialog = FilterDialog(self)

        if dialog.exec_():
            low, high = dialog.low, dialog.high
            tmp = filter_data(data.current.raw._data,
                              data.current.raw.info["sfreq"],
                              l_freq=low, h_freq=high, fir_design="firwin")
            name = data.current.name + " ({}-{} Hz)".format(low, high)
            new = DataSet(raw=mne.io.RawArray(tmp, data.current.raw.info),
                          name=name, events=data.current.events)
            history.append("raw.filter({}, {})".format(low, high))
            self._update_datasets(new)

    def set_reference(self):
        """Set reference.
        """
        dialog = ReferenceDialog(self)
        if dialog.exec_():
            if dialog.average.isChecked():
                tmp, _ = mne.set_eeg_reference(data.current.raw, None)
                tmp.apply_proj()
                name = data.current.name + " (average ref)"
                new = DataSet(raw=tmp, name=name, reference="average",
                              events=data.current.events)
            else:
                ref = [c.strip() for c in dialog.channellist.text().split(",")]
                refstr = ",".join(ref)
                if set(ref) - set(data.current.raw.info["ch_names"]):
                    # add new reference channel(s) to data
                    try:
                        tmp = mne.add_reference_channels(data.current.raw,
                                                         ref)
                    except RuntimeError:
                        QMessageBox.critical(self, "Cannot add new channels",
                                             "Cannot add new channels to "
                                             "average referenced data.")
                        return
                else:
                    # re-reference to existing channel(s)
                    tmp, _ = mne.set_eeg_reference(data.current.raw, ref)
                name = data.current.name + " (ref {})".format(refstr)
                new = DataSet(raw=tmp, name=name, reference=refstr,
                              events=data.current.events)
            self._update_datasets(new)

    def show_about(self):
        """Show About dialog.
        """
        msg = """<b>MNELAB {}</b><br/><br/>
        <a href="https://github.com/cbrnr/mnelab">MNELAB</a> - a graphical user
        interface for
        <a href="https://github.com/mne-tools/mne-python">MNE</a>.<br/><br/>
        This program uses MNE version {}.<br/><br/>
        Licensed under the BSD 3-clause license.<br/>
        Copyright 2017 by Clemens Brunner.""".format(__version__,
                                                     mne.__version__)
        QMessageBox.about(self, "About MNELAB", msg)

    def show_about_qt(self):
        """Show About Qt dialog.
        """
        QMessageBox.aboutQt(self, "About Qt")

    def _update_datasets(self, dataset):
        # if current data is stored in a file create a new data set
        if data.current.fname:
            data.insert_data(dataset)
        # otherwise ask if the current data set should be overwritten or if a
        # new data set should be created
        else:
            msg = QMessageBox.question(self, "Overwrite existing data set",
                                       "Overwrite existing data set?")
            if msg == QMessageBox.No:  # create new data set
                data.insert_data(dataset)
            else:  # overwrite existing data set
                data.update_data(dataset)
        self._update_sidebar(data.names, data.index)
        self._update_infowidget()
        self._update_statusbar()

    def _update_sidebar(self, names, index):
        """Update (overwrite) sidebar with names and current index.
        """
        self.names.setStringList(names)
        self.sidebar.setCurrentIndex(self.names.index(index))

    def _update_infowidget(self):
        if data:
            self.infowidget.set_values(self.get_info())
        else:
            self.infowidget.clear()

    def _update_statusbar(self):
        if data:
            mb = data.nbytes / 1024 ** 2
            self.status_label.setText("Total Memory: {:.2f} MB".format(mb))
        else:
            self.status_label.clear()

    def _toggle_actions(self, enabled=True):
        """Toggle actions.

        Parameters
        ----------
        enabled : bool
            Specifies whether actions are enabled (True) or disabled (False).
        """
        self.close_file_action.setEnabled(enabled)
        self.close_all_action.setEnabled(enabled)
        self.export_raw_action.setEnabled(enabled)
        if data.data:
            bads = bool(data.current.raw.info["bads"])
            self.export_bad_action.setEnabled(enabled and bads)
            events = data.current.events is not None
            self.export_events_action.setEnabled(enabled and events)
            annot = data.current.raw.annotations is not None
            self.export_anno_action.setEnabled(enabled and annot)
            montage = bool(data.current.montage)
            self.plot_montage_action.setEnabled(enabled and montage)
        else:
            self.export_bad_action.setEnabled(enabled)
            self.export_events_action.setEnabled(enabled)
            self.export_anno_action.setEnabled(enabled)
            self.plot_montage_action.setEnabled(enabled)
        self.import_bad_action.setEnabled(enabled)
        self.import_anno_action.setEnabled(enabled)
        self.pick_chans_action.setEnabled(enabled)
        self.chan_props_action.setEnabled(enabled)
        self.set_montage_action.setEnabled(enabled)
        self.plot_raw_action.setEnabled(enabled)
        self.plot_psd_action.setEnabled(enabled)
        self.filter_action.setEnabled(enabled)
        self.setref_action.setEnabled(enabled)
        self.find_events_action.setEnabled(enabled)
        self.run_ica_action.setEnabled(enabled)
        self.import_ica_action.setEnabled(enabled)

    def _add_recent(self, fname):
        """Add a file to recent file list.

        Parameters
        ----------
        fname : str
            File name.
        """
        if fname in self.recent:  # avoid duplicates
            self.recent.remove(fname)
        self.recent.insert(0, fname)
        while len(self.recent) > MAX_RECENT:  # prune list
            self.recent.pop()
        self._write_settings()
        if not self.recent_menu.isEnabled():
            self.recent_menu.setEnabled(True)

    def _remove_recent(self, fname):
        """Remove file from recent file list.

        Parameters
        ----------
        fname : str
            File name.
        """
        if fname in self.recent:
            self.recent.remove(fname)
            self._write_settings()
            if not self.recent:
                self.recent_menu.setEnabled(False)

    def _write_settings(self):
        """Write application settings.
        """
        settings = QSettings()
        settings.setValue("recent", self.recent)
        settings.setValue("statusbar", not self.statusBar().isHidden())
        settings.setValue("geometry", self.saveGeometry())
        settings.setValue("state", self.saveState())

    def _read_settings(self):
        """Read application settings.

        Returns
        -------
        settings : dict
            The restored settings values are returned in a dictionary for
            further processing.
        """
        settings = QSettings()

        recent = settings.value("recent")
        if not recent:
            recent = []  # default is empty list

        statusbar = settings.value("statusbar")
        if statusbar is None:  # default is True
            statusbar = True

        geometry = settings.value("geometry")
        state = settings.value("state")

        return {"recent": recent, "statusbar": statusbar, "geometry": geometry,
                "state": state}

    @pyqtSlot(QModelIndex)
    def _update_data(self, selected):
        """Update index and information based on the state of the sidebar.

        Parameters
        ----------
        selected : QModelIndex
            Index of the selected row.
        """
        if selected.row() != data.index:
            data.index = selected.row()
            data.update_current()
            self._update_infowidget()

    @pyqtSlot(QModelIndex, QModelIndex)
    def _update_names(self, start, stop):
        """Update names in DataSets after changes in sidebar.
        """
        for index in range(start.row(), stop.row() + 1):
            data.data[index].name = self.names.stringList()[index]
        if data.index in range(start.row(), stop.row() + 1):
            data.current.name = data.names[data.index]

    @pyqtSlot()
    def _update_recent_menu(self):
        self.recent_menu.clear()
        for recent in self.recent:
            self.recent_menu.addAction(recent)

    @pyqtSlot(QAction)
    def _load_recent(self, action):
        self.load_file(action.text())

    @pyqtSlot()
    def _toggle_statusbar(self):
        if self.statusBar().isHidden():
            self.statusBar().show()
        else:
            self.statusBar().hide()
        self._write_settings()

    @pyqtSlot(QDropEvent)
    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.acceptProposedAction()

    @pyqtSlot(QDropEvent)
    def dropEvent(self, event):
        mime = event.mimeData()
        if mime.hasUrls():
            urls = mime.urls()
            for url in urls:
                self.load_file(url.toLocalFile())

    @pyqtSlot(QEvent)
    def closeEvent(self, event):
        """Close application.

        Parameters
        ----------
        event : QEvent
            Close event.
        """
        self._write_settings()
        if history:
            print("\nCommand History")
            print("===============")
            print("\n".join(history))
        QApplication.quit()

    def eventFilter(self, source, event):
        # currently the only source is the raw plot window
        if event.type() == QEvent.Close:
            self._update_infowidget()
            self._toggle_actions()
        return QObject.eventFilter(self, source, event)
示例#19
0
class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setupUi(self)
        self.setAttribute(Qt.WA_QuitOnClose)

        geometry = Settings().retrieve_geometry("main")
        if geometry:
            self.restoreGeometry(geometry)
        geometry = Settings().retrieve_geometry("localPanel")
        if geometry:
            self.localFilesTreeView.header().restoreState(geometry)

        self._connection_scanner = ConnectionScanner()
        self._connection = None
        self._root_dir = Settings().root_dir
        self._mcu_files_model = None
        self._terminal = Terminal()
        self._terminal_dialog = None
        self._code_editor = None
        self._flash_dialog = None
        self._settings_dialog = None
        self._about_dialog = None
        self._preset_password = None

        self.actionNavigate.triggered.connect(self.navigate_directory)
        self.actionTerminal.triggered.connect(self.open_terminal)
        self.actionCode_Editor.triggered.connect(self.open_code_editor)
        self.actionUpload.triggered.connect(self.upload_transfer_scripts)
        self.actionFlash.triggered.connect(self.open_flash_dialog)
        self.actionSettings.triggered.connect(self.open_settings_dialog)
        self.actionAbout.triggered.connect(self.open_about_dialog)

        self.lastSelectedConnection = None
        self.connectionComboBox.currentIndexChanged.connect(self.connection_changed)
        self.refreshButton.clicked.connect(self.refresh_ports)

        # Populate baud speed combo box and select default
        self.baudComboBox.clear()
        for speed in BaudOptions.speeds:
            self.baudComboBox.addItem(str(speed))
        self.baudComboBox.setCurrentIndex(BaudOptions.speeds.index(115200))

        self.presetButton.clicked.connect(self.show_presets)
        self.connectButton.clicked.connect(self.connect_pressed)

        self.update_file_tree()

        self.listButton.clicked.connect(self.list_mcu_files)
        self.mcuFilesListView.clicked.connect(self.mcu_file_selection_changed)
        self.mcuFilesListView.doubleClicked.connect(self.read_mcu_file)
        self.executeButton.clicked.connect(self.execute_mcu_code)
        self.removeButton.clicked.connect(self.remove_file)
        self.localPathEdit.setText(self._root_dir)

        local_selection_model = self.localFilesTreeView.selectionModel()
        local_selection_model.selectionChanged.connect(self.local_file_selection_changed)
        self.localFilesTreeView.doubleClicked.connect(self.open_local_file)

        self.compileButton.clicked.connect(self.compile_files)
        self.update_compile_button()
        self.autoTransferCheckBox.setChecked(Settings().auto_transfer)

        self.transferToMcuButton.clicked.connect(self.transfer_to_mcu)
        self.transferToPcButton.clicked.connect(self.transfer_to_pc)

        self.disconnected()

    def closeEvent(self, event):
        Settings().root_dir = self._root_dir
        Settings().auto_transfer = self.autoTransferCheckBox.isChecked()
        Settings().update_geometry("main", self.saveGeometry())
        Settings().update_geometry("localPanel", self.localFilesTreeView.header().saveState())
        Settings().save()
        if self._connection is not None and self._connection.is_connected():
            self.end_connection()
        if self._terminal_dialog:
            self._terminal_dialog.close()
        if self._code_editor:
            self._code_editor.close()
        event.accept()

    def connection_changed(self):
        connection = self._connection_scanner.port_list[self.connectionComboBox.currentIndex()]
        self.connectionStackedWidget.setCurrentIndex(1 if connection == "wifi" else 0)
        self.lastSelectedConnection = connection

    def refresh_ports(self):
        # Cache value of last selected connection because it might change when manipulating combobox
        last_selected_connection = self.lastSelectedConnection

        self._connection_scanner.scan_connections(with_wifi=True)
        self.connectionComboBox.clear()

        # Test if there are any available ports
        if self._connection_scanner.port_list:
            selected_port_idx = -1
            pref_port = Settings().preferred_port

            # Populate port combo box and get index of preferred port if available
            for i, port in enumerate(self._connection_scanner.port_list):
                self.connectionComboBox.addItem(port)
                if pref_port and port.upper() == pref_port.upper():
                    selected_port_idx = i

            # Override preferred port if user made selection and this port is still available
            if last_selected_connection and last_selected_connection in self._connection_scanner.port_list:
                selected_port_idx = self._connection_scanner.port_list.index(last_selected_connection)
            # Set current port
            self.connectionComboBox.setCurrentIndex(selected_port_idx if selected_port_idx >= 0 else 0)
            self.connectButton.setEnabled(True)
        else:
            self.connectButton.setEnabled(False)

    def set_status(self, status):
        if status == "Connected":
            self.statusLabel.setStyleSheet("QLabel { background-color : none; color : green; font : bold;}")
        elif status == "Disconnected":
            self.statusLabel.setStyleSheet("QLabel { background-color : none; color : red; }")
        elif status == "Connecting...":
            self.statusLabel.setStyleSheet("QLabel { background-color : none; color : blue; }")
        elif status == "Error":
            self.statusLabel.setStyleSheet("QLabel { background-color : red; color : white; }")
        elif status == "Password":
            self.statusLabel.setStyleSheet("QLabel { background-color : red; color : white; }")
            status = "Wrong Password"
        elif status == "Host":
            self.statusLabel.setStyleSheet("QLabel { background-color : red; color : white; }")
            status = "Invalid IP or domain"
        else:
            self.statusLabel.setStyleSheet("QLabel { background-color : red; color : white; }")
        self.statusLabel.setText(status)
        QApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

    def update_compile_button(self):
        self.compileButton.setEnabled(bool(Settings().mpy_cross_path) and
                                      len(self.get_local_file_selection()) > 0)

    def disconnected(self):
        self.connectButton.setText("Connect")
        self.set_status("Disconnected")
        self.listButton.setEnabled(False)
        self.connectionComboBox.setEnabled(True)
        self.baudComboBox.setEnabled(True)
        self.refreshButton.setEnabled(True)
        self.mcuFilesListView.setEnabled(False)
        self.executeButton.setEnabled(False)
        self.removeButton.setEnabled(False)
        self.actionTerminal.setEnabled(False)
        self.actionUpload.setEnabled(False)
        self.transferToMcuButton.setEnabled(False)
        self.transferToPcButton.setEnabled(False)
        # Clear terminal on disconnect
        self._terminal.clear()
        if self._terminal_dialog:
            self._terminal_dialog.close()
        if self._code_editor:
            self._code_editor.disconnected()
        self.refresh_ports()

    def connected(self):
        self.connectButton.setText("Disconnect")
        self.set_status("Connected")
        self.listButton.setEnabled(True)
        self.connectionComboBox.setEnabled(False)
        self.baudComboBox.setEnabled(False)
        self.refreshButton.setEnabled(False)
        self.mcuFilesListView.setEnabled(True)
        self.actionTerminal.setEnabled(True)
        if isinstance(self._connection, SerialConnection):
            self.actionUpload.setEnabled(True)
        self.transferToMcuButton.setEnabled(True)
        if self._code_editor:
            self._code_editor.connected(self._connection)
        self.list_mcu_files()

    def navigate_directory(self):
        dialog = QFileDialog()
        dialog.setDirectory(self._root_dir)
        dialog.setFileMode(QFileDialog.Directory)
        dialog.setOption(QFileDialog.ShowDirsOnly)
        dialog.exec()
        path = dialog.selectedFiles()
        if path and path[0]:
            self._root_dir = path[0]
            self.localPathEdit.setText(self._root_dir)
            self.update_file_tree()

    def update_file_tree(self):
        model = QFileSystemModel()
        model.setRootPath(self._root_dir)
        self.localFilesTreeView.setModel(model)
        local_selection_model = self.localFilesTreeView.selectionModel()
        local_selection_model.selectionChanged.connect(self.local_file_selection_changed)
        self.localFilesTreeView.setRootIndex(model.index(self._root_dir))

    def serial_mcu_connection_valid(self):
        try:
            self._connection.list_files()
            return True
        except OperationError:
            return False

    def list_mcu_files(self):
        file_list = []
        try:
            file_list = self._connection.list_files()
        except OperationError:
            QMessageBox().critical(self, "Operation failed", "Could not list files.", QMessageBox.Ok)
            return

        self._mcu_files_model = QStringListModel()

        for file in file_list:
            idx = self._mcu_files_model.rowCount()
            self._mcu_files_model.insertRow(idx)
            self._mcu_files_model.setData(self._mcu_files_model.index(idx), file)

        self.mcuFilesListView.setModel(self._mcu_files_model)
        self.mcu_file_selection_changed()

    def execute_mcu_code(self):
        idx = self.mcuFilesListView.currentIndex()
        assert isinstance(idx, QModelIndex)
        model = self.mcuFilesListView.model()
        assert isinstance(model, QStringListModel)
        file_name = model.data(idx, Qt.EditRole)
        self._connection.run_file(file_name)

    def remove_file(self):
        idx = self.mcuFilesListView.currentIndex()
        assert isinstance(idx, QModelIndex)
        model = self.mcuFilesListView.model()
        assert isinstance(model, QStringListModel)
        file_name = model.data(idx, Qt.EditRole)
        try:
            self._connection.remove_file(file_name)
        except OperationError:
            QMessageBox().critical(self, "Operation failed", "Could not remove the file.", QMessageBox.Ok)
            return
        self.list_mcu_files()

    def ask_for_password(self, title, label="Password"):
        if self._preset_password is not None:
            return self._preset_password

        input_dlg = QInputDialog(parent=self, flags=Qt.Dialog)
        input_dlg.setTextEchoMode(QLineEdit.Password)
        input_dlg.setWindowTitle(title)
        input_dlg.setLabelText(label)
        input_dlg.resize(500, 100)
        input_dlg.exec()
        return input_dlg.textValue()

    def start_connection(self):
        self.set_status("Connecting...")

        connection = self._connection_scanner.port_list[self.connectionComboBox.currentIndex()]

        if connection == "wifi":
            host = self.addressLineEdit.text()
            port = self.portSpinBox.value()

            try:
                self._connection = WifiConnection(host, port, self._terminal, self.ask_for_password)
            except ConnectionError:
                # Do nothing, _connection will be None and code
                # at the end of function will handle this
                pass
            except PasswordException:
                self.set_status("Password")
                return
            except HostnameResolutionError:
                self.set_status("Host")
                return
            except NewPasswordException:
                QMessageBox().information(self, "Password set",
                                          "WebREPL password was not previously configured, so it was set to "
                                          "\"passw\" (without quotes). "
                                          "You can change it in port_config.py (will require reboot to take effect). "
                                          "Caution: Passwords longer than 9 characters will be truncated.\n\n"
                                          "Continue by connecting again.", QMessageBox.Ok)
                return
        else:
            baud_rate = BaudOptions.speeds[self.baudComboBox.currentIndex()]
            self._connection = SerialConnection(connection, baud_rate, self._terminal,
                                                self.serialResetCheckBox.isChecked())
            if self._connection.is_connected():
                if not self.serial_mcu_connection_valid():
                    self._connection.disconnect()
                    self._connection = None
            else:
                # serial connection didn't work, so likely the unplugged the serial device and COM value is stale
                self.refresh_ports()

        if self._connection is not None and self._connection.is_connected():
            self.connected()
            if isinstance(self._connection, SerialConnection):
                if Settings().use_transfer_scripts and not self._connection.check_transfer_scripts_version():
                    QMessageBox.warning(self,
                                        "Transfer scripts problem",
                                        "Transfer scripts for UART are either"
                                        " missing or have wrong version.\nPlease use 'File->Init transfer files' to"
                                        " fix this issue.")
        else:
            self._connection = None
            self.set_status("Error")
            self.refresh_ports()

    def end_connection(self):
        self._connection.disconnect()
        self._connection = None

        self.disconnected()

    def show_presets(self):
        dialog = WiFiPresetDialog()
        dialog.accepted.connect(lambda: self.use_preset(dialog.selected_ip,
                                                        dialog.selected_port,
                                                        dialog.selected_password))
        dialog.exec()

    def use_preset(self, ip, port, password):
        self.addressLineEdit.setText(ip)
        self.portSpinBox.setValue(port)
        self._preset_password = password

    def connect_pressed(self):
        if self._connection is not None and self._connection.is_connected():
            self.end_connection()
        else:
            self.start_connection()

    def run_file(self):
        content = self.codeEdit.toPlainText()
        self._connection.send_block(content)

    def open_local_file(self, idx):
        assert isinstance(idx, QModelIndex)
        model = self.localFilesTreeView.model()
        assert isinstance(model, QFileSystemModel)

        if model.isDir(idx):
            return

        local_path = model.filePath(idx)
        remote_path = local_path.rsplit("/", 1)[1]

        if Settings().external_editor_path:
            self.open_external_editor(local_path)
        else:
            if FileInfo.is_file_binary(local_path):
                QMessageBox.information(self, "Binary file detected", "Editor doesn't support binary files.")
                return
            with open(local_path) as f:
                text = "".join(f.readlines())
                self.open_code_editor()
                self._code_editor.set_code(local_path, remote_path, text)

    def mcu_file_selection_changed(self):
        idx = self.mcuFilesListView.currentIndex()
        assert isinstance(idx, QModelIndex)
        if idx.row() >= 0:
            self.executeButton.setEnabled(True)
            self.removeButton.setEnabled(True)
            self.transferToPcButton.setEnabled(True)
        else:
            self.executeButton.setEnabled(False)
            self.removeButton.setEnabled(False)
            self.transferToPcButton.setEnabled(False)

    def get_local_file_selection(self):
        """Returns absolute paths for selected local files"""
        indices = self.localFilesTreeView.selectedIndexes()
        model = self.localFilesTreeView.model()
        assert isinstance(model, QFileSystemModel)

        def filter_indices(x):
            return x.column() == 0 and not model.isDir(x)

        # Filter out all but first column (file name) and
        # don't include directories
        indices = [x for x in indices if filter_indices(x)]

        # Return absolute paths
        return [model.filePath(idx) for idx in indices]

    def local_file_selection_changed(self):
        self.update_compile_button()
        local_file_paths = self.get_local_file_selection()
        if len(local_file_paths) == 1:
            self.remoteNameEdit.setText(local_file_paths[0].rsplit("/", 1)[1])
        else:
            self.remoteNameEdit.setText("")

    def compile_files(self):
        local_file_paths = self.get_local_file_selection()
        compiled_file_paths = []

        for local_path in local_file_paths:
            split = os.path.splitext(local_path)
            if split[1] == ".mpy":
                title = "COMPILE WARNING!! " + os.path.basename(local_path)
                QMessageBox.warning(self, title, "Can't compile .mpy files, already bytecode")
                continue
            mpy_path = split[0] + ".mpy"

            try:
                os.remove(mpy_path)
                # Force view to update itself so that it sees file removed
                self.localFilesTreeView.repaint()
                QApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
            except OSError:
                pass

            try:
                with subprocess.Popen([Settings().mpy_cross_path, os.path.basename(local_path)],
                                      cwd=os.path.dirname(local_path),
                                      stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
                    proc.wait()  # Wait for process to finish
                    out = proc.stderr.read()
                    if out:
                        QMessageBox.warning(self, "Compilation error", out.decode("utf-8"))
                        continue

            except OSError:
                QMessageBox.warning(self, "Compilation error", "Failed to run mpy-cross")
                continue

            compiled_file_paths += [mpy_path]

        # Force view to update so that it sees compiled files added
        self.localFilesTreeView.repaint()
        QApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

        # Force view to update last time so that it resorts the content
        # Without this, the new file will be placed at the bottom of the view no matter what
        header = self.localFilesTreeView.header()
        column = header.sortIndicatorSection()
        order = header.sortIndicatorOrder()
        # This is necessary so that view actually sorts anything again
        self.localFilesTreeView.sortByColumn(-1, Qt.AscendingOrder)
        QApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
        self.localFilesTreeView.sortByColumn(column, order)

        selection_model = self.localFilesTreeView.selectionModel()
        if compiled_file_paths:
            assert isinstance(selection_model, QItemSelectionModel)
            selection_model.clearSelection()

        for mpy_path in compiled_file_paths:
            idx = self.localFilesTreeView.model().index(mpy_path)
            selection_model.select(idx, QItemSelectionModel.Select | QItemSelectionModel.Rows)

        if (self.autoTransferCheckBox.isChecked() and self._connection and self._connection.is_connected()
            and compiled_file_paths):
            self.transfer_to_mcu()

    def finished_read_mcu_file(self, file_name, transfer):
        assert isinstance(transfer, FileTransfer)
        result = transfer.read_result

        if result.binary_data:
            try:
                text = result.binary_data.decode("utf-8", "strict")
            except UnicodeDecodeError:
                QMessageBox.information(self, "Binary file detected", "Editor doesn't support binary files, "
                                                                      "but these can still be transferred.")
                return
        else:
            text = "! Failed to read file !"

        self.open_code_editor()
        self._code_editor.set_code(None, file_name, text)

    def read_mcu_file(self, idx):
        assert isinstance(idx, QModelIndex)
        model = self.mcuFilesListView.model()
        assert isinstance(model, QStringListModel)
        file_name = model.data(idx, Qt.EditRole)

        progress_dlg = FileTransferDialog(FileTransferDialog.DOWNLOAD)
        progress_dlg.finished.connect(lambda: self.finished_read_mcu_file(file_name, progress_dlg.transfer))
        progress_dlg.show()
        self._connection.read_file(file_name, progress_dlg.transfer)

    def upload_transfer_scripts(self):
        progress_dlg = FileTransferDialog(FileTransferDialog.UPLOAD)
        progress_dlg.finished.connect(self.list_mcu_files)
        progress_dlg.show()
        self._connection.upload_transfer_files(progress_dlg.transfer)

    def transfer_to_mcu(self):
        local_file_paths = self.get_local_file_selection()

        progress_dlg = FileTransferDialog(FileTransferDialog.UPLOAD)
        progress_dlg.finished.connect(self.list_mcu_files)
        progress_dlg.show()

        # Handle single file transfer
        if len(local_file_paths) == 1:
            local_path = local_file_paths[0]
            remote_path = self.remoteNameEdit.text()
            with open(local_path, "rb") as f:
                content = f.read()
            self._connection.write_file(remote_path, content, progress_dlg.transfer)
            return

        # Batch file transfer
        progress_dlg.enable_cancel()
        progress_dlg.transfer.set_file_count(len(local_file_paths))
        self._connection.write_files(local_file_paths, progress_dlg.transfer)

    def finished_transfer_to_pc(self, file_path, transfer):
        if not transfer.read_result.binary_data:
            return

        try:
            with open(file_path, "wb") as file:
                file.write(transfer.read_result.binary_data)
        except IOError:
            QMessageBox.critical(self, "Save operation failed", "Couldn't save the file. Check path and permissions.")

    def transfer_to_pc(self):
        idx = self.mcuFilesListView.currentIndex()
        assert isinstance(idx, QModelIndex)
        model = self.mcuFilesListView.model()
        assert isinstance(model, QStringListModel)
        remote_path = model.data(idx, Qt.EditRole)
        local_path = self.localPathEdit.text() + "/" + remote_path

        progress_dlg = FileTransferDialog(FileTransferDialog.DOWNLOAD)
        progress_dlg.finished.connect(lambda: self.finished_transfer_to_pc(local_path, progress_dlg.transfer))
        progress_dlg.show()
        self._connection.read_file(remote_path, progress_dlg.transfer)

    def open_terminal(self):
        if self._terminal_dialog is not None:
            return
        self._terminal_dialog = TerminalDialog(self, self._connection, self._terminal)
        self._terminal_dialog.finished.connect(self.close_terminal)
        self._terminal_dialog.show()

    def close_terminal(self):
        self._terminal_dialog = None

    def open_external_editor(self, file_path):
        ext_path = Settings().external_editor_path
        ext_args = []
        if Settings().external_editor_args:
            def wildcard_replace(s):
                s = s.replace("%f", file_path)
                return s

            ext_args = [wildcard_replace(x.strip()) for x in Settings().external_editor_args.split(";")]

        subprocess.Popen([ext_path] + ext_args)

    def open_code_editor(self):
        if self._code_editor is not None:
            return

        self._code_editor = CodeEditDialog(self, self._connection)
        self._code_editor.mcu_file_saved.connect(self.list_mcu_files)
        self._code_editor.finished.connect(self.close_code_editor)
        self._code_editor.show()

    def close_code_editor(self):
        self._code_editor = None

    def open_flash_dialog(self):
        if self._connection is not None and self._connection.is_connected():
            self.end_connection()

        self._flash_dialog = FlashDialog(self)
        self._flash_dialog.finished.connect(self.close_flash_dialog)
        self._flash_dialog.show()

    def close_flash_dialog(self):
        self._flash_dialog = None

    def open_settings_dialog(self):
        if self._settings_dialog is not None:
            return
        self._settings_dialog = SettingsDialog(self)
        self._settings_dialog.finished.connect(self.close_settings_dialog)
        self._settings_dialog.show()

    def close_settings_dialog(self):
        self._settings_dialog = None
        # Update compile button as mpy-cross path might have been set
        self.update_compile_button()

    def open_about_dialog(self):
        if self._about_dialog is not None:
            return
        self._settings_dialog = AboutDialog(self)
        self._settings_dialog.finished.connect(self.close_about_dialog)
        self._settings_dialog.show()

    def close_about_dialog(self):
        self._about_dialog = None
示例#20
0
class WiFiPresetDialog(QDialog, Ui_WiFiPresetDialog):
    def __init__(self):
        super(WiFiPresetDialog, self).__init__(None, Qt.WindowCloseButtonHint)
        self.setupUi(self)

        self.selected_ip = None
        self.selected_port = None
        self.selected_password = None

        self.presetsListView.doubleClicked.connect(self.select_preset)
        self.addButton.clicked.connect(self.add_preset)
        self.removeButton.clicked.connect(self.remove_preset)

        self.model = QStringListModel()
        self.presetsListView.setModel(self.model)
        self.update_preset_list()

    def update_preset_list(self):
        self.model.removeRows(0, self.model.rowCount())
        for preset in Settings().wifi_presets:
            idx = self.model.rowCount()
            self.model.insertRow(idx)
            name, ip, port, _ = preset
            text = "{}\nIP: {}    Port: {}".format(name, ip, port)
            self.model.setData(self.model.index(idx), text)

    def closeEvent(self, event):
        self.reject()
        event.accept()

    def add_preset(self):
        name = self.nameLineEdit.text()
        ip = self.ipLineEdit.text()
        port = self.portSpinBox.value()
        password = self.passwordLineEdit.text()
        # Make sure password is non if empty
        if not password:
            password = None

        if not name:
            QMessageBox().warning(self, "Missing name", "Fill the name of preset", QMessageBox.Ok)
            return

        if not IpHelper.is_valid_ipv4(ip):
            QMessageBox().warning(self, "Invalid IP", "The IP address has invalid format", QMessageBox.Ok)
            return

        Settings().wifi_presets.append((name, ip, port, password))
        self.update_preset_list()

    def remove_preset(self):
        idx = self.presetsListView.currentIndex()
        assert isinstance(idx, QModelIndex)
        if idx.row() < 0:
            return

        Settings().wifi_presets.remove(Settings().wifi_presets[idx.row()])
        self.update_preset_list()

    def select_preset(self):
        idx = self.presetsListView.currentIndex()
        assert isinstance(idx, QModelIndex)
        if idx.row() < 0:
            return

        _, self.selected_ip, self.selected_port, self.selected_password = Settings().wifi_presets[idx.row()]
        self.accept()
示例#21
0
class QmyWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)  #调用父类构造函数,创建窗体
        self.ui = Ui_Widget()  #创建UI对象
        self.ui.setupUi(self)  #构造UI界面

        self.__provinces = [
            "北京", "上海", "天津", "河北", "山东", "四川", "重庆", "广东", "河南"
        ]
        self.model = QStringListModel(self)
        self.model.setStringList(self.__provinces)
        self.ui.listView.setModel(self.model)
        ##        trig=(QAbstractItemView.DoubleClicked |QAbstractItemView.SelectedClicked)
        ##        self.ui.listView.setEditTriggers(trig)

        self.ui.listView.setEditTriggers(QAbstractItemView.DoubleClicked
                                         | QAbstractItemView.SelectedClicked)

##  =================自定义功能函数=================================

##  ==========由connectSlotsByName() 自动连接的槽函数===============

    @pyqtSlot()  ##重设模型数据内容
    def on_btnList_Reset_clicked(self):
        self.model.setStringList(self.__provinces)

    @pyqtSlot()  ##添加项
    def on_btnList_Append_clicked(self):
        lastRow = self.model.rowCount()
        self.model.insertRow(lastRow)  #在尾部插入一空行
        index = self.model.index(lastRow, 0)  #获取最后一行的ModelIndex
        self.model.setData(index, "new item", Qt.DisplayRole)  #设置显示文字
        self.ui.listView.setCurrentIndex(index)  #设置当前选中的行

    @pyqtSlot()  ##插入项
    def on_btnList_Insert_clicked(self):
        index = self.ui.listView.currentIndex()  #当前 modelIndex
        self.model.insertRow(index.row())
        self.model.setData(index, "inserted item", Qt.DisplayRole)  #设置显示文字
        ##        self.model.setData(index,Qt.AlignRight, Qt.TextAlignmentRole) #设置对齐方式,不起作用
        self.ui.listView.setCurrentIndex(index)  #设置当前选中的行

    @pyqtSlot()  ##删除当前项
    def on_btnList_Delete_clicked(self):
        index = self.ui.listView.currentIndex()  #获取当前 modelIndex
        self.model.removeRow(index.row())
        #删除当前行

    @pyqtSlot()  ##清空列表
    def on_btnList_Clear_clicked(self):
        count = self.model.rowCount()
        self.model.removeRows(0, count)

    @pyqtSlot()  ##清空文本
    def on_btnText_Clear_clicked(self):
        self.ui.plainTextEdit.clear()

    @pyqtSlot()  ##显示数据模型的内容
    def on_btnText_Display_clicked(self):
        strList = self.model.stringList()  #列表类型
        self.ui.plainTextEdit.clear()
        for strLine in strList:
            self.ui.plainTextEdit.appendPlainText(strLine)

    def on_listView_clicked(self, index):
        self.ui.LabInfo.setText("当前项index: row=%d, column=%d" %
                                (index.row(), index.column()))
class PythonNavigator(QWidget):
	def __init__(self):
		super().__init__()
		self.resize(1200, 600)                                 # (1600, 1200)
		self.setWindowTitle('Python Module Navigation')
		self.setWindowIcon(QIcon('python.ico'))
		self.module_object = None
		self.model = None

		self.layout = QVBoxLayout()

		_path = 'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python37\\lib'
#		_path = 'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python37\\lib\\site-packages'
#-		_path = 'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python37\\lib\\site-packages\\PyQt5'
		available_modules = sorted(
            tuple(
                module_item.name for module_item in pkg.iter_modules() \
                    if module_item.module_finder.path == _path \
                    and module_item.ispkg
            )
        )

		# combobox widget
		self.comboModules = QComboBox()
        
		self.comboModules.addItems(available_modules)
#		self.comboModules.addItems(('QtWidgets', 'QtCore', 'QtGui'))   # ???
        
		self.comboModules.currentIndexChanged.connect(self.updateModuleList)
		self.layout.addWidget(self.comboModules)

		self.search = QLineEdit()
		self.search.textChanged.connect(self.filter_items)
		self.layout.addWidget(self.search)

		layoutLabels = QHBoxLayout()
		self.layout.addLayout(layoutLabels)

		self.labelSelectedClass = QLabel('Selected Class: ')
		self.labelSelectedMemeber= QLabel('Selected Memeber: ')
        
		layoutLabels.addWidget(self.labelSelectedClass)
		layoutLabels.addWidget(self.labelSelectedMemeber)

		layoutListWidgets = QHBoxLayout()
		self.layout.addLayout(layoutListWidgets)

		self.listWidgetClasses = QListWidget()

		self.listWidgetClasses.verticalScrollBar().setStyleSheet('width: 25px')            # 35px
		self.listWidgetClasses.itemSelectionChanged.connect(self.updateClassList)
		self.listWidgetClasses.doubleClicked.connect(lambda : self.displayHelper('class'))
		layoutListWidgets.addWidget(self.listWidgetClasses)

		self.listWidgetMemebers = QListWidget()
		self.listWidgetMemebers.verticalScrollBar().setStyleSheet('width: 25px')            #  35
		self.listWidgetMemebers.itemSelectionChanged.connect(self.updateMemeberlabel)
		self.listWidgetMemebers.doubleClicked.connect(lambda : self.displayHelper('member'))
		layoutListWidgets.addWidget(self.listWidgetMemebers)

		layoutStatus = QHBoxLayout()
		self.status = QLabel()
		appVersion = QLabel('v1.2')
		layoutStatus.addWidget(self.status)
		layoutStatus.addWidget(appVersion, alignment=Qt.AlignRight)
		self.layout.addLayout(layoutStatus)
		self.setLayout(self.layout)

		self.updateModuleList()

	def displayHelper(self, by_type: str):
		if by_type == 'class':
			class_name = self.listWidgetClasses.currentItem().text()
			obj = getattr(self.module_object, class_name)
		elif by_type == 'member':
			class_name = self.listWidgetClasses.currentItem().text()
			memeber_name = self.listWidgetMemebers.currentItem().text()
			obj = getattr(getattr(self.module_object, class_name), memeber_name)
		else:
			self.status.setText('No information available')
			return

		self.help = HelperText(obj)
		self.help.show()

	def updateMemeberlabel(self):
		try:
			member_name = self.listWidgetMemebers.currentItem().text()
			self.labelSelectedMemeber.setText('Selected Memeber: {0}'.format(member_name))
		except Exception as e:
			self.status.setText(str(e))

	def updateClassList(self):
		self.listWidgetMemebers.clear()

		class_name = self.listWidgetClasses.currentItem().text()

		try:
			obj = getattr(self.module_object, class_name)			
		except AttributeError as e:
			self.status.setText(str(e))
			return 

		self.listWidgetMemebers.addItems(dir(obj))
		self.status.clear()

		try:
			self.labelSelectedClass.setText('Selected Class: {0}'.format(class_name))
		except Excepion as e:
			self.status.setText(str(e))
		
	def updateModuleList(self):
		module_name = self.comboModules.currentText()
		self.module_object = sys.modules.get(module_name)
		self.reset_fields()

		if self.module_object is None:
			self.status.setText('Information is not available')
			return

		module_dir = dir(self.module_object)

		self.model = QStringListModel()
		self.model.setStringList(module_dir)

		self.listWidgetClasses.addItems(module_dir)

		self.status.clear()

	def reset_fields(self):
		self.listWidgetClasses.clear()
		self.listWidgetMemebers.clear()
		self.labelSelectedClass.setText('Selected Class: ')
		self.labelSelectedMemeber.setText('Selected Memeber: ')

	def filter_items(self):
		filtered_text = str(self.search.text()).lower()

		if self.model:
			for row in range(self.model.rowCount()):
				if filtered_text in str(self.model.index(row).data()).lower():
					self.listWidgetClasses.setRowHidden(row, False)
				else:
					self.listWidgetClasses.setRowHidden(row, True)
示例#23
0
class MainWindow(QMainWindow):
    """MNELAB main window.
    """
    def __init__(self):
        super().__init__()

        self.datasets = DataSets()
        self._max_recent = 6  # maximum number of recent files
        self.history = []  # command history

        settings = self._read_settings()
        self.recent = settings["recent"] if settings["recent"] else []

        self.setGeometry(300, 300, 800, 600)
        self.setWindowTitle("MNELAB")

        menubar = self.menuBar()

        file_menu = menubar.addMenu("&File")
        file_menu.addAction("&Open...", self.open_file, QKeySequence.Open)
        self.recent_menu = file_menu.addMenu("Open Recent")
        self.recent_menu.aboutToShow.connect(self._update_recent_menu)
        self.recent_menu.triggered.connect(self._load_recent)
        if not self.recent:
            self.recent_menu.setEnabled(False)
        self.close_file_action = file_menu.addAction("&Close", self.close_file,
                                                     QKeySequence.Close)
        file_menu.addSeparator()
        file_menu.addAction("&Quit", self.close, QKeySequence.Quit)

        plot_menu = menubar.addMenu("&Plot")
        self.plot_raw_action = plot_menu.addAction("&Raw data", self.plot_raw)

        tools_menu = menubar.addMenu("&Tools")
        self.filter_action = tools_menu.addAction("&Filter data...",
                                                  self.filter_data)
        self.run_ica_action = tools_menu.addAction("&Run ICA...")
        self.import_ica_action = tools_menu.addAction("&Load ICA...",
                                                      self.load_ica)

        view_menu = menubar.addMenu("&View")
        view_menu.addAction("Show/hide statusbar", self._toggle_statusbar)

        help_menu = menubar.addMenu("&Help")
        help_menu.addAction("&About", self.show_about)
        help_menu.addAction("About &Qt", self.show_about_qt)

        self.names = QStringListModel()
        splitter = QSplitter()
        self.sidebar = QListView()
        self.sidebar.setFocusPolicy(0)
        self.sidebar.setFrameStyle(0)
        self.sidebar.setModel(self.names)
        self.sidebar.clicked.connect(self._update_data)
        splitter.addWidget(self.sidebar)
        self.infowidget = InfoWidget()
        splitter.addWidget(self.infowidget)
        width = splitter.size().width()
        splitter.setSizes((width * 0.25, width * 0.75))
        self.setCentralWidget(splitter)

        self.status_label = QLabel()
        self.statusBar().addPermanentWidget(self.status_label)
        if settings["statusbar"]:
            self.statusBar().show()
        else:
            self.statusBar().hide()

        self._toggle_actions(False)
        self.show()

    def open_file(self):
        """Open file.
        """
        fname = QFileDialog.getOpenFileName(self, "Open file",
                                            filter="*.bdf *.edf")[0]
        if fname:
            self.load_file(fname)

    def load_file(self, fname):
        raw = mne.io.read_raw_edf(fname, stim_channel=None, preload=True)
        name, _ = splitext(split(fname)[-1])
        self.history.append("raw = mne.io.read_raw_edf('{}', "
                            "stim_channel=None, preload=True)".format(fname))
        self.datasets.insert_data(DataSet(name=name, fname=fname, raw=raw))
        self._update_sidebar()
        self._update_main()
        self._add_recent(fname)
        self._update_statusbar()
        self._toggle_actions()

    def close_file(self):
        """Close current file.
        """
        self.datasets.remove_data()
        self._update_sidebar()
        self._update_main()
        self._update_statusbar()

        if not self.datasets:
            self.infowidget.clear()
            self._toggle_actions(False)
            self.status_label.clear()

    def get_info(self):
        """Get basic information on current file.
        """
        raw = self.datasets.current.raw
        fname = self.datasets.current.fname

        nchan = raw.info["nchan"]
        chans = Counter([channel_type(raw.info, i) for i in range(nchan)])

        return {"File name": fname if fname else "-",
                "Number of channels": raw.info["nchan"],
                "Channels": ", ".join(
                    [" ".join([str(v), k.upper()]) for k, v in chans.items()]),
                "Samples": raw.n_times,
                "Sampling frequency": str(raw.info["sfreq"]) + " Hz",
                "Length": str(raw.n_times / raw.info["sfreq"]) + " s",
                "Size in memory": "{:.2f} MB".format(
                    raw._data.nbytes / 1024 ** 2),
                "Size on disk": "-" if not fname else "{:.2f} MB".format(
                    getsize(fname) / 1024 ** 2)}

    def plot_raw(self):
        """Plot raw data.
        """
        events = self.datasets.current.events
        self.datasets.current.raw.plot(events=events)

    def load_ica(self):
        """Load ICA solution from a file.
        """
        fname = QFileDialog.getOpenFileName(self, "Load ICA",
                                            filter="*.fif *.fif.gz")
        if fname[0]:
            self.state.ica = mne.preprocessing.read_ica(fname[0])

    def filter_data(self):
        dialog = FilterDialog()

        if dialog.exec_():
            low, high = dialog.low, dialog.high
            self.datasets.current.raw.filter(low, high)
            self.history.append("raw.filter({}, {})".format(low, high))
            if QMessageBox.question(self, "Add new data set",
                                    "Store the current signals in a new data "
                                    "set?") == QMessageBox.Yes:
                new = DataSet(name="NEW", fname="",
                              raw=self.datasets.current.raw)
                self.datasets.insert_data(new)
                self._update_sidebar()
                self._update_main()
                self._update_statusbar()

    def show_about(self):
        """Show About dialog.
        """
        QMessageBox.about(self, "About MNELAB",
                          "Licensed under the BSD 3-clause license.\n"
                          "Copyright 2017 by Clemens Brunner.")

    def show_about_qt(self):
        """Show About Qt dialog.
        """
        QMessageBox.aboutQt(self, "About Qt")

    def _update_sidebar(self):
        self.names.setStringList(self.datasets.names)
        self.sidebar.setCurrentIndex(self.names.index(self.datasets.index))

    def _update_main(self):
        if self.datasets:
            self.infowidget.set_values(self.get_info())
        else:
            self.infowidget.clear()

    def _update_statusbar(self):
        if self.datasets:
            mb = self.datasets.nbytes / 1024 ** 2
            self.status_label.setText("Total Memory: {:.2f} MB".format(mb))
        else:
            self.status_label.clear()

    def _toggle_actions(self, enabled=True):
        """Toggle actions.
        """
        self.close_file_action.setEnabled(enabled)
        self.plot_raw_action.setEnabled(enabled)
        self.filter_action.setEnabled(enabled)
        self.run_ica_action.setEnabled(enabled)
        self.import_ica_action.setEnabled(enabled)

    def _add_recent(self, fname):
        if fname in self.recent:  # avoid duplicates
            self.recent.remove(fname)
        self.recent.insert(0, fname)
        while len(self.recent) > self._max_recent:  # prune list
            self.recent.pop()
        self._write_settings()
        if not self.recent_menu.isEnabled():
            self.recent_menu.setEnabled(True)

    def _write_settings(self):
        settings = QSettings()
        if self.recent:
            settings.setValue("recent", self.recent)
        settings.setValue("statusbar", not self.statusBar().isHidden())

    def _read_settings(self):
        settings = QSettings()
        recent = settings.value("recent")
        statusbar = settings.value("statusbar")
        if (statusbar is None) or (statusbar == "true"):
            statusbar = True
        else:
            statusbar = False
        return {"recent": recent, "statusbar": statusbar}

    @pyqtSlot(QModelIndex)
    def _update_data(self, selected):
        """Update index and information based on the state of the sidebar.
        """
        if selected.row() != self.datasets.index:
            self.datasets.index = selected.row()
            self.datasets.update_current()
            self._update_main()

    @pyqtSlot()
    def _update_recent_menu(self):
        self.recent_menu.clear()
        for recent in self.recent:
            self.recent_menu.addAction(recent)

    @pyqtSlot(QAction)
    def _load_recent(self, action):
        self.load_file(action.text())

    @pyqtSlot()
    def _toggle_statusbar(self):
        if self.statusBar().isHidden():
            self.statusBar().show()
        else:
            self.statusBar().hide()
        self._write_settings()

    def closeEvent(self, event):
        print("\nCommand History")
        print("===============")
        print("\n".join(self.history))
        event.accept()
示例#24
0
class QmyWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)  #调用父类构造函数,创建窗体
        self.ui = Ui_Widget()  #创建Ui对象
        self.ui.setupUi(self)  #构造UI

        self.__provinces = [
            '北京', '上海', '天津', '河北', '山东', '四川', '重庆', '广东', '河南'
        ]

        self.model = QStringListModel(self)
        self.model.setStringList(self.__provinces)

        self.ui.listView.setModel(self.model)
        #设置QListView的项是否可以编辑,以及如何进入编辑状态
        self.ui.listView.setEditTriggers(
            QAbstractItemView.DoubleClicked
            | QAbstractItemView.SelectedClicked)  #设置编辑模式

    ##==========自定义功能函数==========

    ##==========事件处理函数===========

    ##==========由connectSlotsByName()自动关联的槽函数====
    @pyqtSlot()
    def on_btnAdd_clicked(self):
        self.row = self.model.rowCount()  #行数
        print(self.row)
        self.model.insertRow(self.row)  #在行尾加入一个空行,没有文字
        index = self.model.index(self.row, 0)  #0是列,获取最后一行的modelIndex
        print(index)
        self.model.setData(index, "new item", Qt.DisplayRole)  #添加名称,设置项的角色
        self.ui.listView.setCurrentIndex(index)  #选择当前行

    @pyqtSlot()
    def on_btnInsert_clicked(self):
        '''
		插入行
		:return:
		'''
        index = self.ui.listView.currentIndex()
        self.model.insertRow(index.row())
        self.model.setData(index, "insert item", Qt.DisplayRole)
        self.ui.listView.setCurrentIndex(index)

    @pyqtSlot()
    def on_btnDel_clicked(self):
        '''
		删除行
		:return:
		'''
        index = self.ui.listView.currentIndex()
        self.model.removeRow(index.row())

    @pyqtSlot()
    def on_btnClear_clicked(self):
        '''
		清除列表
		removeRows()从行号row开始删除count(几)行
		:return:
		'''
        count = self.model.rowCount()
        self.model.removeRows(0, count)

    @pyqtSlot()
    def on_btnStringList_clicked(self):
        strlist = self.model.stringList()
        self.ui.plainTextEdit.clear()
        for strline in strlist:
            self.ui.plainTextEdit.appendPlainText(strline)

    def on_listView_clicked(self, index):
        self.ui.label.setText("当前项 index: row = %d, column = %d" %
                              (index.row(), index.column()))
示例#25
0
class E5StringListEditWidget(QWidget, Ui_E5StringListEditWidget):
    """
    Class implementing a dialog to edit a list of strings.
    """
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        """
        super(E5StringListEditWidget, self).__init__(parent)
        self.setupUi(self)
        
        self.__model = QStringListModel(self)
        self.__proxyModel = QSortFilterProxyModel(self)
        self.__proxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.__proxyModel.setSourceModel(self.__model)
        self.stringList.setModel(self.__proxyModel)
        
        self.searchEdit.textChanged.connect(
            self.__proxyModel.setFilterFixedString)
        
        self.removeButton.clicked.connect(self.stringList.removeSelected)
        self.removeAllButton.clicked.connect(self.stringList.removeAll)
    
    def setList(self, stringList):
        """
        Public method to set the list of strings to be edited.
        
        @param stringList list of strings to be edited (list of string)
        """
        self.__model.setStringList(stringList)
        self.__model.sort(0)
    
    def getList(self):
        """
        Public method to get the edited list of strings.
        
        @return edited list of string (list of string)
        """
        return self.__model.stringList()[:]
    
    def setListWhatsThis(self, txt):
        """
        Public method to set a what's that help text for the string list.
        
        @param txt help text to be set (string)
        """
        self.stringList.setWhatsThis(txt)
    
    @pyqtSlot()
    def on_addButton_clicked(self):
        """
        Private slot to add an entry to the list.
        """
        entry, ok = QInputDialog.getText(
            self,
            self.tr("Add Entry"),
            self.tr("Enter the entry to add to the list:"),
            QLineEdit.Normal)
        if ok and entry != "" and entry not in self.__model.stringList():
            self.__model.insertRow(self.__model.rowCount())
            self.__model.setData(
                self.__model.index(self.__model.rowCount() - 1), entry)
            self.__model.sort(0)
示例#26
0
class E5StringListEditWidget(QWidget, Ui_E5StringListEditWidget):
    """
    Class implementing a dialog to edit a list of strings.
    """
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        """
        super(E5StringListEditWidget, self).__init__(parent)
        self.setupUi(self)
        
        self.__model = QStringListModel(self)
        self.__proxyModel = QSortFilterProxyModel(self)
        self.__proxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.__proxyModel.setSourceModel(self.__model)
        self.stringList.setModel(self.__proxyModel)
        
        self.searchEdit.textChanged.connect(
            self.__proxyModel.setFilterFixedString)
        
        self.removeButton.clicked.connect(self.stringList.removeSelected)
        self.removeAllButton.clicked.connect(self.stringList.removeAll)
    
    def setList(self, stringList):
        """
        Public method to set the list of strings to be edited.
        
        @param stringList list of strings to be edited (list of string)
        """
        self.__model.setStringList(stringList)
        self.__model.sort(0)
    
    def getList(self):
        """
        Public method to get the edited list of strings.
        
        @return edited list of string (list of string)
        """
        return self.__model.stringList()[:]
    
    def setListWhatsThis(self, txt):
        """
        Public method to set a what's that help text for the string list.
        
        @param txt help text to be set (string)
        """
        self.stringList.setWhatsThis(txt)
    
    @pyqtSlot()
    def on_addButton_clicked(self):
        """
        Private slot to add an entry to the list.
        """
        entry, ok = QInputDialog.getText(
            self,
            self.tr("Add Entry"),
            self.tr("Enter the entry to add to the list:"),
            QLineEdit.Normal)
        if ok and entry != "" and entry not in self.__model.stringList():
            self.__model.insertRow(self.__model.rowCount())
            self.__model.setData(
                self.__model.index(self.__model.rowCount() - 1), entry)
            self.__model.sort(0)
示例#27
0
class MainWindow(QMainWindow):
    """MNELAB main window.
    """
    def __init__(self):
        super().__init__()

        self.MAX_RECENT = 6  # maximum number of recent files
        self.SUPPORTED_FORMATS = "*.bdf *.edf"

        self.all = DataSets()  # contains currently loaded data sets
        self.history = []  # command history

        settings = self._read_settings()
        self.recent = settings["recent"]  # list of recent files

        if settings["geometry"]:
            self.restoreGeometry(settings["geometry"])
        else:
            self.setGeometry(300, 300, 1000, 750)  # default window size
            self.move(QApplication.desktop().screen().rect().center() -
                      self.rect().center())  # center window
        if settings["state"]:
            self.restoreState(settings["state"])

        self.setWindowTitle("MNELAB")

        menubar = self.menuBar()

        file_menu = menubar.addMenu("&File")
        file_menu.addAction("&Open...", self.open_file, QKeySequence.Open)
        self.recent_menu = file_menu.addMenu("Open recent")
        self.recent_menu.aboutToShow.connect(self._update_recent_menu)
        self.recent_menu.triggered.connect(self._load_recent)
        if not self.recent:
            self.recent_menu.setEnabled(False)
        self.close_file_action = file_menu.addAction("&Close", self.close_file,
                                                     QKeySequence.Close)
        self.close_all_action = file_menu.addAction("Close all",
                                                    self.close_all)
        file_menu.addSeparator()
        self.import_bad_action = file_menu.addAction("Import bad channels...",
                                                     self.import_bads)
        self.export_bad_action = file_menu.addAction("Export &bad channels...",
                                                     self.export_bads)
        file_menu.addSeparator()
        file_menu.addAction("&Quit", self.close, QKeySequence.Quit)

        edit_menu = menubar.addMenu("&Edit")
        self.pick_chans_action = edit_menu.addAction("Pick &channels...",
                                                     self.pick_channels)
        self.set_bads_action = edit_menu.addAction("&Bad channels...",
                                                   self.set_bads)
        edit_menu.addSeparator()
        self.setref_action = edit_menu.addAction("&Set reference...",
                                                 self.set_reference)

        plot_menu = menubar.addMenu("&Plot")
        self.plot_raw_action = plot_menu.addAction("&Raw data", self.plot_raw)
        self.plot_psd_action = plot_menu.addAction("&Power spectral "
                                                   "density...", self.plot_psd)

        tools_menu = menubar.addMenu("&Tools")
        self.filter_action = tools_menu.addAction("&Filter data...",
                                                  self.filter_data)
        self.find_events_action = tools_menu.addAction("Find &events...",
                                                       self.find_events)
        self.run_ica_action = tools_menu.addAction("Run &ICA...")
        self.import_ica_action = tools_menu.addAction("&Load ICA...",
                                                      self.load_ica)

        view_menu = menubar.addMenu("&View")
        statusbar_action = view_menu.addAction("Statusbar",
                                               self._toggle_statusbar)
        statusbar_action.setCheckable(True)

        help_menu = menubar.addMenu("&Help")
        help_menu.addAction("&About", self.show_about)
        help_menu.addAction("About &Qt", self.show_about_qt)

        self.names = QStringListModel()
        self.names.dataChanged.connect(self._update_names)
        splitter = QSplitter()
        self.sidebar = QListView()
        self.sidebar.setFrameStyle(QFrame.NoFrame)
        self.sidebar.setFocusPolicy(Qt.NoFocus)
        self.sidebar.setModel(self.names)
        self.sidebar.clicked.connect(self._update_data)
        splitter.addWidget(self.sidebar)
        self.infowidget = InfoWidget()
        splitter.addWidget(self.infowidget)
        width = splitter.size().width()
        splitter.setSizes((width * 0.3, width * 0.7))
        self.setCentralWidget(splitter)

        self.status_label = QLabel()
        self.statusBar().addPermanentWidget(self.status_label)
        if settings["statusbar"]:
            self.statusBar().show()
            statusbar_action.setChecked(True)
        else:
            self.statusBar().hide()
            statusbar_action.setChecked(False)

        self.setAcceptDrops(True)

        self._toggle_actions(False)
        self.show()

    def open_file(self):
        """Show open file dialog.
        """
        fname = QFileDialog.getOpenFileName(self, "Open file",
                                            filter=self.SUPPORTED_FORMATS)[0]
        if fname:
            self.load_file(fname)

    def load_file(self, fname):
        """Load file.

        Parameters
        ----------
        fname : str
            File name.
        """
        # TODO: check if fname exists
        raw = mne.io.read_raw_edf(fname, stim_channel=-1, preload=True)
        name, ext = splitext(split(fname)[-1])
        self.history.append("raw = mne.io.read_raw_edf('{}', "
                            "stim_channel=None, preload=True)".format(fname))
        self.all.insert_data(DataSet(name=name, fname=fname,
                                     ftype=ext[1:].upper(), raw=raw))
        self.find_events()
        self._update_sidebar(self.all.names, self.all.index)
        self._update_infowidget()
        self._update_statusbar()
        self._add_recent(fname)
        self._toggle_actions()

    def export_bads(self):
        """Export bad channels info to a CSV file.
        """
        fname = QFileDialog.getSaveFileName(self, "Export bad channels",
                                            filter="*.csv")[0]
        if fname:
            name, ext = splitext(split(fname)[-1])
            ext = ext if ext else ".csv"  # automatically add extension
            fname = join(split(fname)[0], name + ext)
            with open(fname, "w") as f:
                f.write(",".join(self.all.current.raw.info["bads"]))

    def import_bads(self):
        """Import bad channels info from a CSV file.
        """
        fname = QFileDialog.getOpenFileName(self, "Import bad channels",
                                            filter="*.csv")[0]
        if fname:
            with open(fname) as f:
                bads = f.read().replace(" ", "").split(",")
                if set(bads) - set(self.all.current.raw.info["ch_names"]):
                    QMessageBox.critical(self, "Channel labels not found",
                                         "Some channel labels from the file "
                                         "are not present in the data.")
                else:
                    self.all.current.raw.info["bads"] = bads
                    self.all.data[self.all.index].raw.info["bads"] = bads

    def close_file(self):
        """Close current file.
        """
        self.all.remove_data()
        self._update_sidebar(self.all.names, self.all.index)
        self._update_infowidget()
        self._update_statusbar()

        if not self.all:
            self._toggle_actions(False)

    def close_all(self):
        """Close all currently open data sets.
        """
        msg = QMessageBox.question(self, "Close all data sets",
                                   "Close all data sets?")
        if msg == QMessageBox.Yes:
            while self.all:
                self.close_file()

    def get_info(self):
        """Get basic information on current file.

        Returns
        -------
        info : dict
            Dictionary with information on current file.
        """
        raw = self.all.current.raw
        fname = self.all.current.fname
        ftype = self.all.current.ftype
        reference = self.all.current.reference
        events = self.all.current.events

        nchan = raw.info["nchan"]
        chans = Counter([channel_type(raw.info, i) for i in range(nchan)])

        if events is not None:
            nevents = events.shape[0]
            unique = [str(e) for e in set(events[:, 2])]
            events = "{} ({})".format(nevents, ", ".join(unique))
        else:
            events = "-"

        if isinstance(reference, list):
            reference = ",".join(reference)

        if raw.annotations is not None:
            annots = len(raw.annotations.description)
        else:
            annots = "-"

        return {"File name": fname if fname else "-",
                "File type": ftype if ftype else "-",
                "Number of channels": nchan,
                "Channels": ", ".join(
                    [" ".join([str(v), k.upper()]) for k, v in chans.items()]),
                "Samples": raw.n_times,
                "Sampling frequency": str(raw.info["sfreq"]) + " Hz",
                "Length": str(raw.n_times / raw.info["sfreq"]) + " s",
                "Events": events,
                "Annotations": annots,
                "Reference": reference if reference else "-",
                "Size in memory": "{:.2f} MB".format(
                    raw._data.nbytes / 1024 ** 2),
                "Size on disk": "-" if not fname else "{:.2f} MB".format(
                    getsize(fname) / 1024 ** 2)}

    def pick_channels(self):
        """Pick channels in current data set.
        """
        channels = self.all.current.raw.info["ch_names"]
        dialog = PickChannelsDialog(self, channels)
        if dialog.exec_():
            picks = [item.data(0) for item in dialog.channels.selectedItems()]
            drops = set(channels) - set(picks)
            tmp = self.all.current.raw.drop_channels(drops)
            name = self.all.current.name + " (channels dropped)"
            new = DataSet(raw=tmp, name=name, events=self.all.current.events)
            self.history.append("raw.drop({})".format(drops))
            self._update_datasets(new)

    def set_bads(self):
        """Set bad channels.
        """
        channels = self.all.current.raw.info["ch_names"]
        selected = self.all.current.raw.info["bads"]
        dialog = PickChannelsDialog(self, channels, selected, "Bad channels")
        if dialog.exec_():
            bads = [item.data(0) for item in dialog.channels.selectedItems()]
            self.all.current.raw.info["bads"] = bads
            self.all.data[self.all.index].raw.info["bads"] = bads
            self._toggle_actions(True)

    def plot_raw(self):
        """Plot raw data.
        """
        events = self.all.current.events
        nchan = self.all.current.raw.info["nchan"]
        fig = self.all.current.raw.plot(events=events, n_channels=nchan,
                                        title=self.all.current.name,
                                        show=False)
        self.history.append("raw.plot(n_channels={})".format(nchan))
        win = fig.canvas.manager.window
        win.setWindowTitle("Raw data")
        win.findChild(QStatusBar).hide()
        win.installEventFilter(self)  # detect if the figure is closed

        # prevent closing the window with the escape key
        try:
            key_events = fig.canvas.callbacks.callbacks["key_press_event"][8]
        except KeyError:
            pass
        else:  # this requires MNE >=0.15
            key_events.func.keywords["params"]["close_key"] = None

        fig.show()

    def plot_psd(self):
        """Plot power spectral density (PSD).
        """
        fig = self.all.current.raw.plot_psd(average=False,
                                            spatial_colors=False, show=False)
        win = fig.canvas.manager.window
        win.setWindowTitle("Power spectral density")
        fig.show()

    def load_ica(self):
        """Load ICA solution from a file.
        """
        fname = QFileDialog.getOpenFileName(self, "Load ICA",
                                            filter="*.fif *.fif.gz")
        if fname[0]:
            self.state.ica = mne.preprocessing.read_ica(fname[0])

    def find_events(self):
        events = mne.find_events(self.all.current.raw, consecutive=False)
        if events.shape[0] > 0:  # if events were found
            self.all.current.events = events
            self.all.data[self.all.index].events = events
            self._update_infowidget()

    def filter_data(self):
        """Filter data.
        """
        dialog = FilterDialog(self)

        if dialog.exec_():
            low, high = dialog.low, dialog.high
            tmp = filter_data(self.all.current.raw._data,
                              self.all.current.raw.info["sfreq"],
                              l_freq=low, h_freq=high)
            name = self.all.current.name + " ({}-{} Hz)".format(low, high)
            new = DataSet(raw=mne.io.RawArray(tmp, self.all.current.raw.info),
                          name=name, events=self.all.current.events)
            self.history.append("raw.filter({}, {})".format(low, high))
            self._update_datasets(new)

    def set_reference(self):
        """Set reference.
        """
        dialog = ReferenceDialog(self)
        if dialog.exec_():
            if dialog.average.isChecked():
                tmp, _ = mne.set_eeg_reference(self.all.current.raw, None)
                tmp.apply_proj()
                name = self.all.current.name + " (average ref)"
                new = DataSet(raw=tmp, name=name, reference="average",
                              events=self.all.current.events)
            else:
                ref = [c.strip() for c in dialog.channellist.text().split(",")]
                refstr = ",".join(ref)
                if set(ref) - set(self.all.current.raw.info["ch_names"]):
                    # add new reference channel(s) to data
                    try:
                        tmp = mne.add_reference_channels(self.all.current.raw,
                                                         ref)
                    except RuntimeError:
                        QMessageBox.critical(self, "Cannot add new channels",
                                             "Cannot add new channels to "
                                             "average referenced data.")
                        return
                else:
                    # re-reference to existing channel(s)
                    tmp, _ = mne.set_eeg_reference(self.all.current.raw, ref)
                name = self.all.current.name + " (ref {})".format(refstr)
                new = DataSet(raw=tmp, name=name, reference=refstr,
                              events=self.all.current.events)
            self._update_datasets(new)

    def show_about(self):
        """Show About dialog.
        """
        msg = """<b>MNELAB {}</b><br/><br/>
        <a href="https://github.com/cbrnr/mnelab">MNELAB</a> - a graphical user
        interface for
        <a href="https://github.com/mne-tools/mne-python">MNE</a>.<br/><br/>
        This program uses MNE version {}.<br/><br/>
        Licensed under the BSD 3-clause license.<br/>
        Copyright 2017 by Clemens Brunner.""".format(__version__,
                                                     mne.__version__)
        QMessageBox.about(self, "About MNELAB", msg)

    def show_about_qt(self):
        """Show About Qt dialog.
        """
        QMessageBox.aboutQt(self, "About Qt")

    def _update_datasets(self, dataset):
        # if current data is stored in a file create a new data set
        if self.all.current.fname:
            self.all.insert_data(dataset)
        # otherwise ask if the current data set should be overwritten or if a
        # new data set should be created
        else:
            msg = QMessageBox.question(self, "Overwrite existing data set",
                                       "Overwrite existing data set?")
            if msg == QMessageBox.No:  # create new data set
                self.all.insert_data(dataset)
            else:  # overwrite existing data set
                self.all.update_data(dataset)
        self._update_sidebar(self.all.names, self.all.index)
        self._update_infowidget()
        self._update_statusbar()

    def _update_sidebar(self, names, index):
        """Update (overwrite) sidebar with names and current index.
        """
        self.names.setStringList(names)
        self.sidebar.setCurrentIndex(self.names.index(index))

    def _update_infowidget(self):
        if self.all:
            self.infowidget.set_values(self.get_info())
        else:
            self.infowidget.clear()

    def _update_statusbar(self):
        if self.all:
            mb = self.all.nbytes / 1024 ** 2
            self.status_label.setText("Total Memory: {:.2f} MB".format(mb))
        else:
            self.status_label.clear()

    def _toggle_actions(self, enabled=True):
        """Toggle actions.

        Parameters
        ----------
        enabled : bool
            Specifies whether actions are enabled (True) or disabled (False).
        """
        self.close_file_action.setEnabled(enabled)
        self.close_all_action.setEnabled(enabled)
        if self.all.data:
            bads = bool(self.all.current.raw.info["bads"])
            self.export_bad_action.setEnabled(enabled and bads)
        else:
            self.export_bad_action.setEnabled(enabled)
        self.import_bad_action.setEnabled(enabled)
        self.pick_chans_action.setEnabled(enabled)
        self.set_bads_action.setEnabled(enabled)
        self.plot_raw_action.setEnabled(enabled)
        self.plot_psd_action.setEnabled(enabled)
        self.filter_action.setEnabled(enabled)
        self.setref_action.setEnabled(enabled)
        self.find_events_action.setEnabled(enabled)
        self.run_ica_action.setEnabled(enabled)
        self.import_ica_action.setEnabled(enabled)

    def _add_recent(self, fname):
        """Add a file to recent file list.

        Parameters
        ----------
        fname : str
            File name.
        """
        if fname in self.recent:  # avoid duplicates
            self.recent.remove(fname)
        self.recent.insert(0, fname)
        while len(self.recent) > self.MAX_RECENT:  # prune list
            self.recent.pop()
        self._write_settings()
        if not self.recent_menu.isEnabled():
            self.recent_menu.setEnabled(True)

    def _write_settings(self):
        """Write application settings.
        """
        settings = QSettings()
        if self.recent:
            settings.setValue("recent", self.recent)
        settings.setValue("statusbar", not self.statusBar().isHidden())
        settings.setValue("geometry", self.saveGeometry())
        settings.setValue("state", self.saveState())

    def _read_settings(self):
        """Read application settings.

        Returns
        -------
        settings : dict
            The restored settings values are returned in a dictionary for
            further processing.
        """
        settings = QSettings()

        recent = settings.value("recent")
        if not recent:
            recent = []  # default is empty list

        statusbar = settings.value("statusbar")
        if statusbar is None:  # default is True
            statusbar = True

        geometry = settings.value("geometry")

        state = settings.value("state")

        return {"recent": recent, "statusbar": statusbar, "geometry": geometry,
                "state": state}

    @pyqtSlot(QModelIndex)
    def _update_data(self, selected):
        """Update index and information based on the state of the sidebar.

        Parameters
        ----------
        selected : QModelIndex
            Index of the selected row.
        """
        if selected.row() != self.all.index:
            self.all.index = selected.row()
            self.all.update_current()
            self._update_infowidget()

    @pyqtSlot(QModelIndex, QModelIndex)
    def _update_names(self, start, stop):
        """Update names in DataSets after changes in sidebar.
        """
        for index in range(start.row(), stop.row() + 1):
            self.all.data[index].name = self.names.stringList()[index]
        if self.all.index in range(start.row(), stop.row() + 1):
            self.all.current.name = self.all.names[self.all.index]

    @pyqtSlot()
    def _update_recent_menu(self):
        self.recent_menu.clear()
        for recent in self.recent:
            self.recent_menu.addAction(recent)

    @pyqtSlot(QAction)
    def _load_recent(self, action):
        self.load_file(action.text())

    @pyqtSlot()
    def _toggle_statusbar(self):
        if self.statusBar().isHidden():
            self.statusBar().show()
        else:
            self.statusBar().hide()
        self._write_settings()

    @pyqtSlot(QDropEvent)
    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.acceptProposedAction()

    @pyqtSlot(QDropEvent)
    def dropEvent(self, event):
        mime = event.mimeData()
        if mime.hasUrls():
            urls = mime.urls()
            for url in urls:
                self.load_file(url.toLocalFile())

    @pyqtSlot(QEvent)
    def closeEvent(self, event):
        """Close application.

        Parameters
        ----------
        event : QEvent
            Close event.
        """
        self._write_settings()
        if self.history:
            print("\nCommand History")
            print("===============")
            print("\n".join(self.history))
        QApplication.quit()

    def eventFilter(self, source, event):
        # currently the only source is the raw plot window
        if event.type() == QEvent.Close:
            self._update_infowidget()
        return QObject.eventFilter(self, source, event)
示例#28
0
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        Ui_MainWindow.__init__(self)
        self.setupUi(self)
        self.appModel = AppModel()
        self.listViewModel = QStringListModel(self)
        self.info.setEditTriggers(QAbstractItemView.NoEditTriggers)

        self.initTable()
        self.initListSites()

        self.setConnections()

    def initListSites(self):
        for site in Sites:
            self.listViewModel.insertRow(self.listViewModel.rowCount())
            index = self.listViewModel.index(self.listViewModel.rowCount() - 1)
            self.listViewModel.setData(index, site.name)

        self.listSites.setModel(self.listViewModel)

    def initTable(self):
        self.info.setColumnCount(6)
        self.info.setHorizontalHeaderLabels([
            'Site', 'Author', 'Header', 'Url', 'Count comments', 'Count likes'
        ])
        self.info.resizeColumnsToContents()

    def setConnections(self):
        self.actionExit.triggered.connect(QCoreApplication.instance().quit)
        self.actionAbout.triggered.connect(self.about)
        self.addNewArticleBtn.clicked.connect(self.parseSite)
        self.appModel.changeArticlesSignal.connect(self.onChangedArticled)

    def about(self):
        QMessageBox.about(self, "About", "Ametov Pavel\n8-T3O-302B-16")

    def error(self, message: str):
        box = QMessageBox()
        box.setWindowTitle("Error")
        box.setText(message)
        box.exec()

    def parseSite(self):
        try:
            index = self.listSites.currentIndex().row()
            numberArticle = self.numberArticle.text()

            if index == -1:
                raise Exception('Select site')

            try:
                numberArticle = int(numberArticle)
            except Exception as err:
                raise Exception("Incorrect value of article")

            site = Sites(index)

            self.appModel.parseSite(site, numberArticle)
        except Exception as err:
            self.error(err.args[0])

    def onChangedArticled(self):
        self.info.clear()
        self.initTable()

        articles = self.appModel.articlesModels

        self.info.setRowCount(len(articles))

        for index in range(len(articles)):

            article = articles[index]
            self.info.setItem(index, 0, QTableWidgetItem(article.site.name))
            self.info.setItem(index, 1, QTableWidgetItem(article.author))
            self.info.setItem(index, 2, QTableWidgetItem(article.header))
            self.info.setItem(index, 3, QTableWidgetItem(article.url))
            self.info.setItem(index, 4,
                              QTableWidgetItem(str(article.countComments)))
            self.info.setItem(index, 5,
                              QTableWidgetItem(str(article.countLikes)))

        self.info.resizeColumnsToContents()