コード例 #1
0
class PangoFileWidget(PangoDockWidget):
    def __init__(self, title, parent=None):
        super().__init__(title, parent)
        self.setFixedWidth(160)

        self.file_model = QFileSystemModel()
        self.file_model.setFilter(QDir.Files | QDir.NoDotAndDotDot)
        self.file_model.setNameFilters(["*.jpg", "*.png"])
        self.file_model.setNameFilterDisables(False)
        self.th_provider = ThumbnailProvider()
        self.file_model.setIconProvider(self.th_provider)

        self.file_view = QListView()
        self.file_view.setModel(self.file_model)
        self.file_view.setViewMode(QListView.IconMode)
        self.file_view.setFlow(QListView.LeftToRight)
        self.file_view.setIconSize(QSize(150, 150))
        
        self.setWidget(self.file_view)

    def select_next_image(self):
        c_idx = self.file_view.currentIndex()
        idx = c_idx.siblingAtRow(c_idx.row()+1)
        if idx.row() != -1:
            self.file_view.setCurrentIndex(idx)

    def select_prev_image(self):
        c_idx = self.file_view.currentIndex()
        idx = c_idx.siblingAtRow(c_idx.row()-1)
        if idx.row() != -1:
            self.file_view.setCurrentIndex(idx)
コード例 #2
0
ファイル: main.py プロジェクト: petersohn/photo-organizer
 def _select_next(self, view: W.QListView, sorted_rows: List[int]) -> None:
     if not sorted_rows:
         return
     next_row = sorted_rows[0] - len(sorted_rows) + 1
     row_count = view.model().rowCount()
     if next_row >= row_count:
         next_row = row_count - 1
     view.setCurrentIndex(view.model().index(next_row, 0))
コード例 #3
0
class Player(QWidget):

    fullScreenChanged = pyqtSignal(bool)

    def __init__(self, playlist, parent=None, add_button = None):
        super(Player, self).__init__(parent)
        self.add_button = add_button

        self.colorDialog = None
        self.trackInfo = ""
        self.statusInfo = ""
        self.duration = 0

        self.player = QMediaPlayer()
        self.playlist = QMediaPlaylist()
        self.player.setPlaylist(self.playlist)

        self.player.durationChanged.connect(self.durationChanged)
        self.player.positionChanged.connect(self.positionChanged)
        self.player.metaDataChanged.connect(self.metaDataChanged)
        self.playlist.currentIndexChanged.connect(self.playlistPositionChanged)
        self.player.mediaStatusChanged.connect(self.statusChanged)
        self.player.bufferStatusChanged.connect(self.bufferingProgress)
        self.player.error.connect(self.displayErrorMessage)

        self.videoWidget = VideoWidget()
        self.player.setVideoOutput(self.videoWidget)

        self.playlistModel = PlaylistModel()
        self.playlistModel.setPlaylist(self.playlist)

        self.playlistView = QListView()
        self.playlistView.setModel(self.playlistModel)
        self.playlistView.setCurrentIndex(
                self.playlistModel.index(self.playlist.currentIndex(), 0))

        self.playlistView.activated.connect(self.jump)

        self.slider = QSlider(Qt.Horizontal)
        self.slider.setRange(0, self.player.duration() / 1000)

        self.labelDuration = QLabel()
        self.slider.sliderMoved.connect(self.seek)

        openButton = QPushButton("Open Audio/Video File", clicked=self.open)

        controls = PlayerControls()
        controls.setState(self.player.state())
        controls.setVolume(self.player.volume())
        controls.setMuted(controls.isMuted())

        controls.play.connect(self.player.play)
        controls.pause.connect(self.player.pause)
        controls.stop.connect(self.player.stop)
        controls.next.connect(self.playlist.next)
        controls.previous.connect(self.previousClicked)
        controls.changeVolume.connect(self.player.setVolume)
        controls.changeMuting.connect(self.player.setMuted)
        controls.changeRate.connect(self.player.setPlaybackRate)
        controls.stop.connect(self.videoWidget.update)

        self.player.stateChanged.connect(controls.setState)
        self.player.volumeChanged.connect(controls.setVolume)
        self.player.mutedChanged.connect(controls.setMuted)


        displayLayout = QHBoxLayout()
        displayLayout.addWidget(self.videoWidget, 2)
        displayLayout.addWidget(self.playlistView)

        controlLayout = QHBoxLayout()
        controlLayout.setContentsMargins(0, 0, 0, 0)
        controlLayout.addWidget(openButton)
        # button to add decoder
        if add_button:
            add_decoder_btn = QPushButton("Decode Keystrokes of Selected Media")
            add_decoder_btn.clicked.connect(add_button)            
            controlLayout.addWidget(add_decoder_btn)
        # controlLayout.addStretch(1)
        controlLayout.addWidget(controls)

        layout = QVBoxLayout()
        layout.addLayout(displayLayout)
        hLayout = QHBoxLayout()
        hLayout.addWidget(self.slider)
        hLayout.addWidget(self.labelDuration)
        layout.addLayout(hLayout)
        layout.addLayout(controlLayout)


        self.setLayout(layout)

        if not self.player.isAvailable():
            QMessageBox.warning(self, "Service not available",
                    "The QMediaPlayer object does not have a valid service.\n"
                    "Please check the media service plugins are installed.")

            controls.setEnabled(False)
            self.playlistView.setEnabled(False)
            openButton.setEnabled(False)

        self.metaDataChanged()

        self.addToPlaylist(playlist)

    def get_current_file(self):
        inds = self.playlistView.selectedIndexes()
        if len(inds) == 1:
            index = inds[0]
            location = self.playlistModel.m_playlist.media(index.row()).canonicalUrl()
            return location.path()
            
        

    def open(self):
        fileNames, _ = QFileDialog.getOpenFileNames(self, "Open Files")
        self.addToPlaylist(fileNames)

    def addToPlaylist(self, fileNames):
        for name in fileNames:
            fileInfo = QFileInfo(name)
            if fileInfo.exists():
                url = QUrl.fromLocalFile(fileInfo.absoluteFilePath())
                if fileInfo.suffix().lower() == 'm3u':
                    self.playlist.load(url)
                else:
                    self.playlist.addMedia(QMediaContent(url))
            else:
                url = QUrl(name)
                if url.isValid():
                    self.playlist.addMedia(QMediaContent(url))

    def durationChanged(self, duration):
        duration /= 1000

        self.duration = duration
        self.slider.setMaximum(duration)

    def positionChanged(self, progress):
        progress /= 1000

        if not self.slider.isSliderDown():
            self.slider.setValue(progress)

        self.updateDurationInfo(progress)

    def metaDataChanged(self):
        if self.player.isMetaDataAvailable():
            self.setTrackInfo("%s - %s" % (
                    self.player.metaData(QMediaMetaData.AlbumArtist),
                    self.player.metaData(QMediaMetaData.Title)))

    def previousClicked(self):
        # Go to the previous track if we are within the first 5 seconds of
        # playback.  Otherwise, seek to the beginning.
        if self.player.position() <= 5000:
            self.playlist.previous()
        else:
            self.player.setPosition(0)

    def jump(self, index):
        if index.isValid():
            self.playlist.setCurrentIndex(index.row())
            self.player.play()

    def playlistPositionChanged(self, position):
        self.playlistView.setCurrentIndex(
                self.playlistModel.index(position, 0))

    def seek(self, seconds):
        self.player.setPosition(seconds * 1000)

    def statusChanged(self, status):
        self.handleCursor(status)

        if status == QMediaPlayer.LoadingMedia:
            self.setStatusInfo("Loading...")
        elif status == QMediaPlayer.StalledMedia:
            self.setStatusInfo("Media Stalled")
        elif status == QMediaPlayer.EndOfMedia:
            QApplication.alert(self)
        elif status == QMediaPlayer.InvalidMedia:
            self.displayErrorMessage()
        else:
            self.setStatusInfo("")

    def handleCursor(self, status):
        if status in (QMediaPlayer.LoadingMedia, QMediaPlayer.BufferingMedia, QMediaPlayer.StalledMedia):
            self.setCursor(Qt.BusyCursor)
        else:
            self.unsetCursor()

    def bufferingProgress(self, progress):
        self.setStatusInfo("Buffering %d%" % progress)

    def setTrackInfo(self, info):
        self.trackInfo = info

        if self.statusInfo != "":
            self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo))
        else:
            self.setWindowTitle(self.trackInfo)

    def setStatusInfo(self, info):
        self.statusInfo = info

        if self.statusInfo != "":
            self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo))
        else:
            self.setWindowTitle(self.trackInfo)

    def displayErrorMessage(self):
        self.setStatusInfo(self.player.errorString())

    def updateDurationInfo(self, currentInfo):
        duration = self.duration
        if currentInfo or duration:
            currentTime = QTime((currentInfo/3600)%60, (currentInfo/60)%60,
                    currentInfo%60, (currentInfo*1000)%1000)
            totalTime = QTime((duration/3600)%60, (duration/60)%60,
                    duration%60, (duration*1000)%1000);

            format = 'hh:mm:ss' if duration > 3600 else 'mm:ss'
            tStr = currentTime.toString(format) + " / " + totalTime.toString(format)
        else:
            tStr = ""

        self.labelDuration.setText(tStr)
コード例 #4
0
class Player(QWidget):

    fullScreenChanged = pyqtSignal(bool)

    def __init__(self, playlist, parent=None):
        super(Player, self).__init__(parent)
        self.setStyleSheet(stylesheet(self))
        self.colorDialog = None
        self.trackInfo = ""
        self.statusInfo = ""
        self.duration = 0

        self.url = ""
        self. settings = QSettings("QAudioPlayer", "QAudioPlayer")

        self.player = QMediaPlayer()
        self.playlist = QMediaPlaylist()
        self.player.setPlaylist(self.playlist)

        self.player.durationChanged.connect(self.durationChanged)
        self.player.positionChanged.connect(self.positionChanged)
        self.player.metaDataChanged.connect(self.metaDataChanged)
        self.playlist.currentIndexChanged.connect(self.playlistPositionChanged)
        self.player.mediaStatusChanged.connect(self.statusChanged)
        self.player.bufferStatusChanged.connect(self.bufferingProgress)
        self.player.error.connect(self.displayErrorMessage)

        self.playlistModel = PlaylistModel()
        self.playlistModel.setPlaylist(self.playlist)

        self.playlistView = QListView()
#        self.playlistView.setSpacing(1)
        self.playlistView.setStyleSheet(stylesheet(self))
        self.playlistView.setModel(self.playlistModel)
        self.playlistView.setCurrentIndex(
                self.playlistModel.index(self.playlist.currentIndex(), 0))

        self.playlistView.activated.connect(self.jump)

        self.slider = QSlider(Qt.Horizontal)
        self.slider.setRange(0, self.player.duration() / 1000)
        self.slider.setStyleSheet(stylesheet(self))

        self.labelDuration = QLabel()
        self.slider.sliderMoved.connect(self.seek)

        openButton = QPushButton("Open", clicked=self.open)
        openButton.setIcon(openButton.style().standardIcon(QStyle.SP_DialogOpenButton))

        clearButton = QPushButton("", clicked=self.clearList)
        clearButton.setFixedWidth(36)
        clearButton.setIcon(QIcon.fromTheme("edit-delete"))
        clearButton.setToolTip("clear List")

        controls = PlayerControls()
        controls.setState(self.player.state())
        controls.setVolume(self.player.volume())
        controls.setMuted(controls.isMuted())

        controls.play.connect(self.player.play)
        controls.pause.connect(self.player.pause)
        controls.stop.connect(self.player.stop)
        controls.next.connect(self.playlist.next)
        controls.previous.connect(self.previousClicked)
        controls.changeVolume.connect(self.player.setVolume)
        controls.changeMuting.connect(self.player.setMuted)
        controls.changeRate.connect(self.player.setPlaybackRate)

        self.player.stateChanged.connect(controls.setState)
        self.player.volumeChanged.connect(controls.setVolume)
        self.player.mutedChanged.connect(controls.setMuted)

        displayLayout = QHBoxLayout()
        displayLayout.addWidget(self.playlistView)

        controlLayout = QHBoxLayout()
        controlLayout.setContentsMargins(0, 0, 0, 0)
        controlLayout.addWidget(openButton)
        controlLayout.addWidget(clearButton)
#        controlLayout.addStretch(1)
        controlLayout.addWidget(controls)
#        controlLayout.addStretch(1)

        layout = QVBoxLayout()
        layout.addLayout(displayLayout)
        hLayout = QHBoxLayout()
        hLayout.addWidget(self.slider)
        hLayout.addWidget(self.labelDuration)
        layout.addLayout(hLayout)
        layout.addLayout(controlLayout)

        self.statusBar = QStatusBar()
        vlayout = QVBoxLayout()
        vlayout.addWidget(self.statusBar)
        layout.addLayout(vlayout)
        self.statusBar.showMessage("Welcome")
        self.setWindowTitle("QAudioPlayer")
        self.setMinimumSize(300, 200)
#        self.setBackgroundRole(QPalette.Window)
        self.setContentsMargins(0,0,0,0)
        self.setLayout(layout)
        self.readSettings()

        if not self.player.isAvailable():
            QMessageBox.warning(self, "Service not available",
                    "The QMediaPlayer object does not have a valid service.\n"
                    "Please check the media service plugins are installed.")

            controls.setEnabled(False)
            self.playlistView.setEnabled(False)
            openButton.setEnabled(False)
            self.colorButton.setEnabled(False)
            self.fullScreenButton.setEnabled(False)

        self.metaDataChanged()

        self.addToPlaylist(playlist)

    def readSettings(self):
        if self.settings.contains("url"):
            self.url = self.settings.value("url")
            self.addToPlaylist(self.url)

    def writeSettings(self):
        self.settings.setValue("url", self.url)

    def closeEvent(self, event):
        print("writing settings")
        self.writeSettings()
        print("goodbye ...")
        event.accept()

    def open(self):
        fileNames, _ = QFileDialog.getOpenFileNames(self, "Open Files", "/home", "Audio Files *.mp3 *.m4a *.ogg *.wav *.m3u")
        if fileNames:
            self.url = fileNames
            self.addToPlaylist(fileNames)
            print("added Files to playlist")

    def openOnStart(self, name):
        fileInfo = QFileInfo(name)
        if fileInfo.exists():
            url = QUrl.fromLocalFile(fileInfo.absoluteFilePath())
            if fileInfo.suffix().lower() == 'm3u':
                self.playlist.load(url)
            else:
                self.playlist.addMedia(QMediaContent(url))
        else:
            url = QUrl(name)
            if url.isValid():
                self.playlist.addMedia(QMediaContent(url))
        print("added Files to playlist")

    def clearList(self):
        self.playlist.clear()

    def addToPlaylist(self, fileNames):
        for name in fileNames:
            fileInfo = QFileInfo(name)
            if fileInfo.exists():
                url = QUrl.fromLocalFile(fileInfo.absoluteFilePath())
                if fileInfo.suffix().lower() == 'm3u':
                    self.playlist.load(url)
                else:
                    self.playlist.addMedia(QMediaContent(url))
            else:
                url = QUrl(name)
                if url.isValid():
                    self.playlist.addMedia(QMediaContent(url))

    def durationChanged(self, duration):
        duration /= 1000

        self.duration = duration
        self.slider.setMaximum(duration)

    def positionChanged(self, progress):
        progress /= 1000

        if not self.slider.isSliderDown():
            self.slider.setValue(progress)

        self.updateDurationInfo(progress)

    def metaDataChanged(self):
        if self.player.isMetaDataAvailable():
            self.setTrackInfo("%s - %s" % (
                    self.player.metaData(QMediaMetaData.AlbumArtist),
                    self.player.metaData(QMediaMetaData.Title)))

    def previousClicked(self):
        # Go to the previous track if we are within the first 5 seconds of
        # playback.  Otherwise, seek to the beginning.
        if self.player.position() <= 5000:
            self.playlist.previous()
        else:
            self.player.setPosition(0)

    def jump(self, index):
        if index.isValid():
            self.playlist.setCurrentIndex(index.row())
            self.player.play()

    def playlistPositionChanged(self, position):
        self.playlistView.setCurrentIndex(
                self.playlistModel.index(position, 0))

    def seek(self, seconds):
        self.player.setPosition(seconds * 1000)

    def statusChanged(self, status):
        self.handleCursor(status)

        if status == QMediaPlayer.LoadingMedia:
            self.setStatusInfo("Loading...")
        elif status == QMediaPlayer.StalledMedia:
            self.setStatusInfo("Media Stalled")
        elif status == QMediaPlayer.EndOfMedia:
            QApplication.alert(self)
        elif status == QMediaPlayer.InvalidMedia:
            self.displayErrorMessage()
        else:
            self.setStatusInfo("")

    def handleCursor(self, status):
        if status in (QMediaPlayer.LoadingMedia, QMediaPlayer.BufferingMedia, QMediaPlayer.StalledMedia):
            self.setCursor(Qt.BusyCursor)
        else:
            self.unsetCursor()

    def bufferingProgress(self, progress):
        self.setStatusInfo("Buffering %d%" % progress)

    def setTrackInfo(self, info):
        self.trackInfo = info

        if self.statusInfo != "":
             self.statusBar.showMessage("%s | %s" % (self.trackInfo, self.statusInfo))
        else:
            self.statusBar.showMessage(self.trackInfo)

    def setStatusInfo(self, info):
        self.statusInfo = info

        if self.statusInfo != "":
            self.statusBar.showMessage("%s | %s" % (self.trackInfo, self.statusInfo))
        else:
            self.statusBar.showMessage(self.trackInfo)

    def displayErrorMessage(self):
        self.setStatusInfo(self.player.errorString())

    def updateDurationInfo(self, currentInfo):
        duration = self.duration
        if currentInfo or duration:
            currentTime = QTime((currentInfo/3600)%60, (currentInfo/60)%60,
                    currentInfo%60, (currentInfo*1000)%1000)
            totalTime = QTime((duration/3600)%60, (duration/60)%60,
                    duration%60, (duration*1000)%1000);

            format = 'hh:mm:ss' if duration > 3600 else 'mm:ss'
            tStr = currentTime.toString(format) + " / " + totalTime.toString(format)
        else:
            tStr = ""

        self.labelDuration.setText(tStr)
コード例 #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__()

        mp.set_start_method("spawn", force=True)  # required for Linux/macOS

        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")
        icon = QIcon(":/open_file.svg")
        self.actions["open_file"] = file_menu.addAction(
            icon, "&Open...", self.open_data, 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()
        icon = QIcon(":/meta_info.svg")
        self.actions["meta_info"] = file_menu.addAction(
            icon, "Show information...", self.meta_info)
        file_menu.addSeparator()
        self.actions["import_bads"] = file_menu.addAction(
            "Import bad channels...", lambda: self.import_file(
                model.import_bads, "Import bad channels", "*.csv"))
        self.actions["import_events"] = file_menu.addAction(
            "Import events...", lambda: self.import_file(
                model.import_events, "Import events", "*.csv"))
        self.actions["import_annotations"] = file_menu.addAction(
            "Import annotations...", lambda: self.import_file(
                model.import_annotations, "Import annotations", "*.csv"))
        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.export_menu = file_menu.addMenu("Export data")
        for name, ext in EXPORT_FORMATS.items():
            self.actions["export_data_" + ext] = self.export_menu.addAction(
                f"{name} ({ext[1:].upper()})...",
                partial(self.export_file, model.export_data, "Export data",
                        ext))
        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"))
        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)
        icon = QIcon(":/chan_props.svg")
        self.actions["chan_props"] = edit_menu.addAction(
            icon, "Channel &properties...", self.channel_properties)
        self.actions["set_montage"] = edit_menu.addAction(
            "Set &montage...", self.set_montage)
        edit_menu.addSeparator()
        self.actions["set_ref"] = edit_menu.addAction("&Set reference...",
                                                      self.set_reference)
        edit_menu.addSeparator()
        self.actions["annotations"] = edit_menu.addAction(
            "Annotations...", self.edit_annotations)
        self.actions["events"] = edit_menu.addAction("Events...",
                                                     self.edit_events)

        edit_menu.addSeparator()
        self.actions["crop"] = edit_menu.addAction("&Crop data...", self.crop)

        plot_menu = self.menuBar().addMenu("&Plot")
        icon = QIcon(":/plot_data.svg")
        self.actions["plot_data"] = plot_menu.addAction(
            icon, "&Data...", self.plot_data)
        icon = QIcon(":/plot_psd.svg")
        self.actions["plot_psd"] = plot_menu.addAction(
            icon, "&Power spectral density...", self.plot_psd)
        icon = QIcon(":/plot_montage.svg")
        self.actions["plot_montage"] = plot_menu.addAction(
            icon, "&Montage...", self.plot_montage)
        plot_menu.addSeparator()
        self.actions["plot_ica_components"] = plot_menu.addAction(
            "ICA &components...", self.plot_ica_components)
        self.actions["plot_ica_sources"] = plot_menu.addAction(
            "ICA &sources...", self.plot_ica_sources)

        tools_menu = self.menuBar().addMenu("&Tools")
        icon = QIcon(":/filter.svg")
        self.actions["filter"] = tools_menu.addAction(icon, "&Filter data...",
                                                      self.filter_data)
        icon = QIcon(":/find_events.svg")
        self.actions["find_events"] = tools_menu.addAction(
            icon, "Find &events...", self.find_events)
        self.actions["events_from_annotations"] = tools_menu.addAction(
            "Create events from annotations", self.events_from_annotations)
        tools_menu.addSeparator()
        icon = QIcon(":/run_ica.svg")
        self.actions["run_ica"] = tools_menu.addAction(icon, "Run &ICA...",
                                                       self.run_ica)
        self.actions["apply_ica"] = tools_menu.addAction(
            "Apply &ICA", self.apply_ica)
        tools_menu.addSeparator()
        self.actions["interpolate_bads"] = tools_menu.addAction(
            "Interpolate bad channels...", self.interpolate_bads)
        tools_menu.addSeparator()
        icon = QIcon(":/epoch_data.svg")
        self.actions["epoch_data"] = tools_menu.addAction(
            icon, "Create Epochs...", self.epoch_data)

        view_menu = self.menuBar().addMenu("&View")
        self.actions["history"] = view_menu.addAction("&History...",
                                                      self.show_history)
        self.actions["toolbar"] = view_menu.addAction("&Toolbar",
                                                      self._toggle_toolbar)
        self.actions["toolbar"].setCheckable(True)
        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", "toolbar", "statusbar"
        ]

        # set up toolbar
        self.toolbar = self.addToolBar("toolbar")
        self.toolbar.setObjectName("toolbar")
        self.toolbar.addAction(self.actions["open_file"])
        self.toolbar.addAction(self.actions["meta_info"])
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.actions["chan_props"])
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.actions["plot_data"])
        self.toolbar.addAction(self.actions["plot_psd"])
        self.toolbar.addAction(self.actions["plot_montage"])
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.actions["filter"])
        self.toolbar.addAction(self.actions["find_events"])
        self.toolbar.addAction(self.actions["epoch_data"])
        self.toolbar.addAction(self.actions["run_ica"])

        self.setUnifiedTitleAndToolBarOnMac(True)
        if settings["toolbar"]:
            self.toolbar.show()
            self.actions["toolbar"].setChecked(True)
        else:
            self.toolbar.hide()
            self.actions["toolbar"].setChecked(False)

        # 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((int(width * 0.3), int(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
            bads = bool(self.model.current["data"].info["bads"])
            self.actions["export_bads"].setEnabled(enabled and bads)
            events = self.model.current["events"] is not None
            self.actions["export_events"].setEnabled(enabled and events)
            if self.model.current["dtype"] == "raw":
                annot = bool(self.model.current["data"].annotations)
            else:
                annot = False
            self.actions["export_annotations"].setEnabled(enabled and annot)
            self.actions["annotations"].setEnabled(enabled and annot)
            montage = bool(self.model.current["montage"])
            self.actions["plot_montage"].setEnabled(enabled and montage)
            ica = bool(self.model.current["ica"])
            self.actions["apply_ica"].setEnabled(enabled and ica)
            self.actions["export_ica"].setEnabled(enabled and ica)
            self.actions["plot_ica_components"].setEnabled(enabled and ica
                                                           and montage)
            self.actions["plot_ica_sources"].setEnabled(enabled and ica)
            self.actions["interpolate_bads"].setEnabled(enabled and montage
                                                        and bads)
            self.actions["events"].setEnabled(enabled and events)
            self.actions["events_from_annotations"].setEnabled(enabled
                                                               and annot)
            self.actions["find_events"].setEnabled(
                enabled and self.model.current["dtype"] == "raw")
            self.actions["epoch_data"].setEnabled(
                enabled and events and self.model.current["dtype"] == "raw")
            self.actions["crop"].setEnabled(
                enabled and self.model.current["dtype"] == "raw")
            self.actions["meta_info"].setEnabled(
                enabled
                and self.model.current["ftype"] == "Extensible Data Format")
        # add to recent files
        if len(self.model) > 0:
            self._add_recent(self.model.current["fname"])

    def open_data(self, fname=None):
        """Open raw file."""
        if fname is None:
            fname = QFileDialog.getOpenFileName(self, "Open raw",
                                                filter="*")[0]
        if fname:
            if not (isfile(fname) or isdir(fname)):
                self._remove_recent(fname)
                QMessageBox.critical(self, "File does not exist",
                                     f"File {fname} does not exist anymore.")
                return

            name, ext, ftype = split_fname(fname, IMPORT_FORMATS)

            if ext in [".xdf", ".xdfz", ".xdf.gz"]:
                streams = parse_chunks(parse_xdf(fname))
                rows, disabled = [], []
                for idx, s in enumerate(streams):
                    rows.append([
                        s["stream_id"], s["name"], s["type"],
                        s["channel_count"], s["channel_format"],
                        s["nominal_srate"]
                    ])
                    is_marker = (s["nominal_srate"] == 0
                                 or s["channel_format"] == "string")
                    if is_marker:  # disable marker streams
                        disabled.append(idx)

                enabled = list(set(range(len(rows))) - set(disabled))
                if enabled:
                    selected = enabled[0]
                else:
                    selected = None
                dialog = XDFStreamsDialog(self,
                                          rows,
                                          selected=selected,
                                          disabled=disabled)
                if dialog.exec_():
                    row = dialog.view.selectionModel().selectedRows()[0].row()
                    stream_id = dialog.model.data(dialog.model.index(row, 0))
                    self.model.load(fname, stream_id=stream_id)
            else:  # all other file formats
                try:
                    self.model.load(fname)
                except FileNotFoundError as e:
                    QMessageBox.critical(self, "File not found", str(e))
                except UnknownFileTypeError as e:
                    QMessageBox.critical(self, "Unknown file type", str(e))

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

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

    def import_file(self, f, text, ffilter):
        """Import file."""
        fname = QFileDialog.getOpenFileName(self, text, filter="*")[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 meta_info(self):
        xml = get_xml(self.model.current["fname"])
        dialog = MetaInfoDialog(self, xml)
        dialog.exec_()

    def pick_channels(self):
        """Pick channels in current data set."""
        channels = self.model.current["data"].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["data"].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.make_standard_montage(name)
            ch_names = self.model.current["data"].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_annotations(self):
        fs = self.model.current["data"].info["sfreq"]
        pos = self.model.current["data"].annotations.onset
        pos = (pos * fs).astype(int).tolist()
        dur = self.model.current["data"].annotations.duration
        dur = (dur * fs).astype(int).tolist()
        desc = self.model.current["data"].annotations.description.tolist()
        dialog = AnnotationsDialog(self, pos, dur, desc)
        if dialog.exec_():
            rows = dialog.table.rowCount()
            onset, duration, description = [], [], []
            for i in range(rows):
                data = dialog.table.item(i, 0).data(Qt.DisplayRole)
                onset.append(float(data) / fs)
                data = dialog.table.item(i, 1).data(Qt.DisplayRole)
                duration.append(float(data) / fs)
                data = dialog.table.item(i, 2).data(Qt.DisplayRole)
                description.append(data)
            self.model.set_annotations(onset, duration, description)

    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_():
            rows = dialog.table.rowCount()
            events = np.zeros((rows, 3), dtype=int)
            for i in range(rows):
                pos = int(dialog.table.item(i, 0).data(Qt.DisplayRole))
                desc = int(dialog.table.item(i, 1).data(Qt.DisplayRole))
                events[i] = pos, 0, desc
            self.model.set_events(events)

    def crop(self):
        """Filter data."""
        fs = self.model.current["data"].info["sfreq"]
        length = self.model.current["data"].n_times / fs
        dialog = CropDialog(self, 0, length)
        if dialog.exec_():
            self.auto_duplicate()
            if dialog.start is None:
                dialog.start = 0
            self.model.crop(dialog.start, dialog.stop)

    def plot_data(self):
        """Plot data."""
        # self.bad is needed to update history if bad channels are selected in
        # the interactive plot window (see also self.eventFilter)
        self.bads = self.model.current["data"].info["bads"]
        events = self.model.current["events"]
        nchan = self.model.current["data"].info["nchan"]
        fig = self.model.current["data"].plot(events=events,
                                              n_channels=nchan,
                                              title=self.model.current["name"],
                                              scalings="auto",
                                              show=False)
        self.model.history.append("data.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:
            fig._mne_params["close_key"] = None
        except AttributeError:  # does not exist in older MNE versions
            pass

        fig.show()

    def plot_psd(self):
        """Plot power spectral density (PSD)."""
        kwds = {"show": False}
        if self.model.current["type"] == "raw":
            kwds.update({"average": False, "spatial_colors": False})
        fig = self.model.current["data"].plot_psd(**kwds)
        win = fig.canvas.manager.window
        win.setWindowTitle("Power spectral density")
        fig.show()

    def plot_montage(self):
        """Plot current montage."""
        fig = self.model.current["data"].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(
            inst=self.model.current["data"])

    def plot_ica_sources(self):
        self.model.current["ica"].plot_sources(inst=self.model.current["data"])

    def run_ica(self):
        """Run ICA calculation."""
        dialog = RunICADialog(self, self.model.current["data"].info["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()
            fit_params = {}

            if not dialog.extended.isHidden():
                fit_params["extended"] = dialog.extended.isChecked()

            if not dialog.ortho.isHidden():
                fit_params["ortho"] = dialog.ortho.isChecked()

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

    def apply_ica(self):
        """Apply current fitted ICA."""
        self.auto_duplicate()
        self.model.apply_ica()

    def interpolate_bads(self):
        """Interpolate bad channels"""
        dialog = InterpolateBadsDialog(self)
        if dialog.exec_():
            duplicated = self.auto_duplicate()
            try:
                self.model.interpolate_bads(dialog.reset_bads, dialog.mode,
                                            dialog.origin)
            except ValueError as e:
                if duplicated:  # undo
                    self.model.remove_data()
                    self.model.index -= 1
                    self.data_changed()
                msgbox = ErrorMessageBox(self,
                                         "Could not interpolate bad channels",
                                         str(e), traceback.format_exc())
                msgbox.show()

    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):
        info = self.model.current["data"].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 events_from_annotations(self):
        self.model.events_from_annotations()

    def epoch_data(self):
        """Epoch raw data."""
        dialog = EpochDialog(self, self.model.current["events"])
        if dialog.exec_():
            events = [
                int(item.text()) for item in dialog.events.selectedItems()
            ]
            tmin = dialog.tmin.value()
            tmax = dialog.tmax.value()

            if dialog.baseline.isChecked():
                baseline = dialog.a.value(), dialog.b.value()
            else:
                baseline = None

            duplicated = self.auto_duplicate()
            try:
                self.model.epoch_data(events, tmin, tmax, baseline)
            except ValueError as e:
                if duplicated:  # undo
                    self.model.remove_data()
                    self.model.index -= 1
                    self.data_changed()
                msgbox = ErrorMessageBox(self, "Could not create epochs",
                                         str(e), traceback.format_exc())
                msgbox.show()

    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_history(self):
        """Show history."""
        dialog = HistoryDialog(self, "\n".join(self.model.history))
        dialog.exec_()

    def show_about(self):
        """Show About dialog."""
        msg_box = QMessageBox(self)
        text = (f"<img src=':/mnelab_logo.png'>"
                f"<p>MNELAB {__version__}</p>")
        msg_box.setText(text)

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

        pkgs = []
        for key, value in have.items():
            if value:
                pkgs.append(f"{key}&nbsp;({value})")
            else:
                pkgs.append(f"{key}&nbsp;(not installed)")

        text = (f'<nobr><p>This program uses Python '
                f'{".".join(str(k) for k in version_info[:3])} and the '
                f'following packages:</p></nobr>'
                f'<p>{", ".join(pkgs)}</p>'
                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-2020 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):
        """Automatically duplicate current data set.

        If the current data set is stored in a file (i.e. was loaded directly
        from a file), a new data set is automatically created. If the current
        data set is not stored in a file (i.e. was created by operations in
        MNELAB), a dialog box asks the user if the current data set should be
        overwritten or duplicated.

        Returns
        -------
        duplicated : bool
            True if the current data set was automatically duplicated, False if
            the current data set was overwritten.
        """
        # if current data is stored in a file create a new data set
        if self.model.current["fname"]:
            self.model.duplicate_data()
            return True
        # 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()
                return True
        return False

    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.open_data(fname=action.text())

    @pyqtSlot()
    def _toggle_toolbar(self):
        if self.toolbar.isHidden():
            self.toolbar.show()
        else:
            self.toolbar.hide()
        write_settings(toolbar=not self.toolbar.isHidden())

    @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:
                try:
                    self.open_data(url.toLocalFile())
                except FileNotFoundError as e:
                    QMessageBox.critical(self, "File not found", str(e))

    @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()
            bads = self.model.current["data"].info["bads"]
            if self.bads != bads:
                self.model.history.append(f"data.info['bads'] = {bads}")
        return QObject.eventFilter(self, source, event)
コード例 #6
0
class Player(QWidget):

    fullScreenChanged = pyqtSignal(bool)

    def __init__(self, playlist, parent=None):
        super(Player, self).__init__(parent)

        self.colorDialog = None
        self.trackInfo = ""
        self.statusInfo = ""
        self.duration = 0

        self.player = QMediaPlayer()
        self.playlist = QMediaPlaylist()
        self.player.setPlaylist(self.playlist)

        self.player.durationChanged.connect(self.durationChanged)
        self.player.positionChanged.connect(self.positionChanged)
        self.player.metaDataChanged.connect(self.metaDataChanged)
        self.playlist.currentIndexChanged.connect(self.playlistPositionChanged)
        self.player.mediaStatusChanged.connect(self.statusChanged)
        self.player.bufferStatusChanged.connect(self.bufferingProgress)
        self.player.videoAvailableChanged.connect(self.videoAvailableChanged)
        self.player.error.connect(self.displayErrorMessage)

        self.videoWidget = VideoWidget()
        self.player.setVideoOutput(self.videoWidget)

        self.playlistModel = PlaylistModel()
        self.playlistModel.setPlaylist(self.playlist)

        self.playlistView = QListView()
        self.playlistView.setModel(self.playlistModel)
        self.playlistView.setCurrentIndex(
            self.playlistModel.index(self.playlist.currentIndex(), 0))

        self.playlistView.activated.connect(self.jump)

        self.slider = QSlider(Qt.Horizontal)
        self.slider.setRange(0, self.player.duration() / 1000)

        self.labelDuration = QLabel()
        self.slider.sliderMoved.connect(self.seek)

        #        self.labelHistogram = QLabel()
        #        self.labelHistogram.setText("Histogram:")
        #        self.histogram = HistogramWidget()
        #        histogramLayout = QHBoxLayout()
        #        histogramLayout.addWidget(self.labelHistogram)
        #        histogramLayout.addWidget(self.histogram, 1)

        self.probe = QVideoProbe()
        #        self.probe.videoFrameProbed.connect(self.histogram.processFrame)
        self.probe.setSource(self.player)

        openButton = QPushButton("打开", clicked=self.open)

        controls = PlayerControls()
        controls.setState(self.player.state())
        controls.setVolume(self.player.volume())
        controls.setMuted(controls.isMuted())

        controls.play.connect(self.player.play)
        controls.pause.connect(self.player.pause)
        controls.stop.connect(self.player.stop)
        controls.next.connect(self.playlist.next)
        controls.previous.connect(self.previousClicked)
        controls.changeVolume.connect(self.player.setVolume)
        controls.changeMuting.connect(self.player.setMuted)
        controls.changeRate.connect(self.player.setPlaybackRate)
        controls.stop.connect(self.videoWidget.update)

        self.player.stateChanged.connect(controls.setState)
        self.player.volumeChanged.connect(controls.setVolume)
        self.player.mutedChanged.connect(controls.setMuted)

        self.fullScreenButton = QPushButton("全屏")
        self.fullScreenButton.setCheckable(True)

        self.colorButton = QPushButton("颜色选项")
        self.colorButton.setEnabled(False)
        self.colorButton.clicked.connect(self.showColorDialog)

        displayLayout = QHBoxLayout()
        displayLayout.addWidget(self.videoWidget, 2)
        displayLayout.addWidget(self.playlistView)

        controlLayout = QHBoxLayout()
        controlLayout.setContentsMargins(0, 0, 0, 0)
        controlLayout.addWidget(openButton)
        controlLayout.addStretch(1)
        controlLayout.addWidget(controls)
        controlLayout.addStretch(1)
        controlLayout.addWidget(self.fullScreenButton)
        controlLayout.addWidget(self.colorButton)

        layout = QVBoxLayout()
        layout.addLayout(displayLayout)
        hLayout = QHBoxLayout()
        hLayout.addWidget(self.slider)
        hLayout.addWidget(self.labelDuration)
        layout.addLayout(hLayout)
        layout.addLayout(controlLayout)
        #        layout.addLayout(histogramLayout)

        self.setLayout(layout)

        if not self.player.isAvailable():
            QMessageBox.warning(
                self, "Service not available",
                "The QMediaPlayer object does not have a valid service.\n"
                "Please check the media service plugins are installed.")

            controls.setEnabled(False)
            self.playlistView.setEnabled(False)
            openButton.setEnabled(False)
            self.colorButton.setEnabled(False)
            self.fullScreenButton.setEnabled(False)

        self.metaDataChanged()

        self.addToPlaylist(playlist)

    def open(self):
        fileNames, _ = QFileDialog.getOpenFileNames(self, "Open Files")
        self.addToPlaylist(fileNames)

    def addToPlaylist(self, fileNames):
        for name in fileNames:
            fileInfo = QFileInfo(name)
            if fileInfo.exists():
                url = QUrl.fromLocalFile(fileInfo.absoluteFilePath())
                if fileInfo.suffix().lower() == 'm3u':
                    self.playlist.load(url)
                else:
                    self.playlist.addMedia(QMediaContent(url))
            else:
                url = QUrl(name)
                if url.isValid():
                    self.playlist.addMedia(QMediaContent(url))

    def durationChanged(self, duration):
        duration /= 1000

        self.duration = duration
        self.slider.setMaximum(duration)

    def positionChanged(self, progress):
        progress /= 1000

        if not self.slider.isSliderDown():
            self.slider.setValue(progress)

        self.updateDurationInfo(progress)

    def metaDataChanged(self):
        if self.player.isMetaDataAvailable():
            self.setTrackInfo(
                "%s - %s" % (self.player.metaData(QMediaMetaData.AlbumArtist),
                             self.player.metaData(QMediaMetaData.Title)))

    def previousClicked(self):
        # Go to the previous track if we are within the first 5 seconds of
        # playback.  Otherwise, seek to the beginning.
        if self.player.position() <= 5000:
            self.playlist.previous()
        else:
            self.player.setPosition(0)

    def jump(self, index):
        if index.isValid():
            self.playlist.setCurrentIndex(index.row())
            self.player.play()

    def playlistPositionChanged(self, position):
        self.playlistView.setCurrentIndex(self.playlistModel.index(
            position, 0))

    def seek(self, seconds):
        self.player.setPosition(seconds * 1000)

    def statusChanged(self, status):
        self.handleCursor(status)

        if status == QMediaPlayer.LoadingMedia:
            self.setStatusInfo("Loading...")
        elif status == QMediaPlayer.StalledMedia:
            self.setStatusInfo("Media Stalled")
        elif status == QMediaPlayer.EndOfMedia:
            QApplication.alert(self)
        elif status == QMediaPlayer.InvalidMedia:
            self.displayErrorMessage()
        else:
            self.setStatusInfo("")

    def handleCursor(self, status):
        if status in (QMediaPlayer.LoadingMedia, QMediaPlayer.BufferingMedia,
                      QMediaPlayer.StalledMedia):
            self.setCursor(Qt.BusyCursor)
        else:
            self.unsetCursor()

    def bufferingProgress(self, progress):
        self.setStatusInfo("Buffering %d%" % progress)

    def videoAvailableChanged(self, available):
        if available:
            self.fullScreenButton.clicked.connect(
                self.videoWidget.setFullScreen)
            self.videoWidget.fullScreenChanged.connect(
                self.fullScreenButton.setChecked)

            if self.fullScreenButton.isChecked():
                self.videoWidget.setFullScreen(True)
        else:
            self.fullScreenButton.clicked.disconnect(
                self.videoWidget.setFullScreen)
            self.videoWidget.fullScreenChanged.disconnect(
                self.fullScreenButton.setChecked)

            self.videoWidget.setFullScreen(False)

        self.colorButton.setEnabled(available)

    def setTrackInfo(self, info):
        self.trackInfo = info

        if self.statusInfo != "":
            self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo))
        else:
            self.setWindowTitle(self.trackInfo)

    def setStatusInfo(self, info):
        self.statusInfo = info

        if self.statusInfo != "":
            self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo))
        else:
            self.setWindowTitle(self.trackInfo)

    def displayErrorMessage(self):
        self.setStatusInfo(self.player.errorString())

    def updateDurationInfo(self, currentInfo):
        duration = self.duration
        if currentInfo or duration:
            currentTime = QTime((currentInfo / 3600) % 60,
                                (currentInfo / 60) % 60, currentInfo % 60,
                                (currentInfo * 1000) % 1000)
            totalTime = QTime((duration / 3600) % 60, (duration / 60) % 60,
                              duration % 60, (duration * 1000) % 1000)

            format = 'hh:mm:ss' if duration > 3600 else 'mm:ss'
            tStr = currentTime.toString(format) + " / " + totalTime.toString(
                format)
        else:
            tStr = ""

        self.labelDuration.setText(tStr)

    def showColorDialog(self):
        if self.colorDialog is None:
            brightnessSlider = QSlider(Qt.Horizontal)
            brightnessSlider.setRange(-100, 100)
            brightnessSlider.setValue(self.videoWidget.brightness())
            brightnessSlider.sliderMoved.connect(
                self.videoWidget.setBrightness)
            self.videoWidget.brightnessChanged.connect(
                brightnessSlider.setValue)

            contrastSlider = QSlider(Qt.Horizontal)
            contrastSlider.setRange(-100, 100)
            contrastSlider.setValue(self.videoWidget.contrast())
            contrastSlider.sliderMoved.connect(self.videoWidget.setContrast)
            self.videoWidget.contrastChanged.connect(contrastSlider.setValue)

            hueSlider = QSlider(Qt.Horizontal)
            hueSlider.setRange(-100, 100)
            hueSlider.setValue(self.videoWidget.hue())
            hueSlider.sliderMoved.connect(self.videoWidget.setHue)
            self.videoWidget.hueChanged.connect(hueSlider.setValue)

            saturationSlider = QSlider(Qt.Horizontal)
            saturationSlider.setRange(-100, 100)
            saturationSlider.setValue(self.videoWidget.saturation())
            saturationSlider.sliderMoved.connect(
                self.videoWidget.setSaturation)
            self.videoWidget.saturationChanged.connect(
                saturationSlider.setValue)

            layout = QFormLayout()
            layout.addRow("亮度", brightnessSlider)
            layout.addRow("对比度", contrastSlider)
            layout.addRow("色调", hueSlider)
            layout.addRow("饱和度", saturationSlider)

            button = QPushButton("关闭")
            layout.addRow(button)

            self.colorDialog = QDialog(self)
            self.colorDialog.setWindowTitle("颜色选项")
            self.colorDialog.setLayout(layout)

            button.clicked.connect(self.colorDialog.close)

        self.colorDialog.show()
コード例 #7
0
class IntracranialElectrodeLocator(QMainWindow):
    """Locate electrode contacts using a coregistered MRI and CT."""

    def __init__(self, info, trans, aligned_ct, subject=None,
                 subjects_dir=None, groups=None, verbose=None):
        """GUI for locating intracranial electrodes.

        .. note:: Images will be displayed using orientation information
                  obtained from the image header. Images will be resampled to
                  dimensions [256, 256, 256] for display.
        """
        # initialize QMainWindow class
        super(IntracranialElectrodeLocator, self).__init__()

        if not info.ch_names:
            raise ValueError('No channels found in `info` to locate')

        # store info for modification
        self._info = info
        self._verbose = verbose

        # load imaging data
        self._subject_dir = _check_subject_dir(subject, subjects_dir)
        self._load_image_data(aligned_ct)

        self._ch_alpha = 0.5
        self._radius = int(_CH_PLOT_SIZE // 100)  # starting 1/200 of image
        # initialize channel data
        self._ch_index = 0
        # load data, apply trans
        self._head_mri_t = _get_trans(trans, 'head', 'mri')[0]
        self._mri_head_t = invert_transform(self._head_mri_t)
        # load channels, convert from m to mm
        self._chs = {name: apply_trans(self._head_mri_t, ch['loc'][:3]) * 1000
                     for name, ch in zip(info.ch_names, info['chs'])}
        self._ch_names = list(self._chs.keys())
        # set current position
        if np.isnan(self._chs[self._ch_names[self._ch_index]]).any():
            self._ras = np.array([0., 0., 0.])
        else:
            self._ras = self._chs[self._ch_names[self._ch_index]].copy()
        self._current_slice = apply_trans(
            self._ras_vox_t, self._ras).round().astype(int)
        self._group_channels(groups)

        # GUI design

        # Main plots: make one plot for each view; sagittal, coronal, axial
        plt_grid = QGridLayout()
        plts = [_make_slice_plot(), _make_slice_plot(), _make_slice_plot()]
        self._figs = [plts[0][1], plts[1][1], plts[2][1]]
        plt_grid.addWidget(plts[0][0], 0, 0)
        plt_grid.addWidget(plts[1][0], 0, 1)
        plt_grid.addWidget(plts[2][0], 1, 0)
        self._renderer = _get_renderer(
            name='IEEG Locator', size=(400, 400), bgcolor='w')
        # TODO: should eventually make sure the renderer here is actually
        # some PyVista(Qt) variant, not mayavi, otherwise the following
        # call will fail (hopefully it's rare that people who want to use this
        # have also set their MNE_3D_BACKEND=mayavi and/or don't have a working
        # pyvistaqt setup; also hopefully the refactoring to use the
        # Qt/notebook abstraction will make this easier, too):
        plt_grid.addWidget(self._renderer.plotter)

        # Channel selector
        self._ch_list = QListView()
        self._ch_list.setSelectionMode(Qt.QAbstractItemView.SingleSelection)
        self._ch_list.setMinimumWidth(150)
        self._set_ch_names()

        # Plots
        self._plot_images()

        # Menus
        button_hbox = self._get_button_bar()
        slider_hbox = self._get_slider_bar()
        bottom_hbox = self._get_bottom_bar()

        # Put everything together
        plot_ch_hbox = QHBoxLayout()
        plot_ch_hbox.addLayout(plt_grid)
        plot_ch_hbox.addWidget(self._ch_list)

        main_vbox = QVBoxLayout()
        main_vbox.addLayout(button_hbox)
        main_vbox.addLayout(slider_hbox)
        main_vbox.addLayout(plot_ch_hbox)
        main_vbox.addLayout(bottom_hbox)

        central_widget = QWidget()
        central_widget.setLayout(main_vbox)
        self.setCentralWidget(central_widget)

        # ready for user
        self._move_cursors_to_pos()
        self._ch_list.setFocus()  # always focus on list

    def _load_image_data(self, ct):
        """Get MRI and CT data to display and transforms to/from vox/RAS."""
        self._mri_data, self._vox_ras_t = _load_image(
            op.join(self._subject_dir, 'mri', 'brain.mgz'),
            'MRI Image', verbose=self._verbose)
        self._ras_vox_t = np.linalg.inv(self._vox_ras_t)

        self._voxel_sizes = np.array(self._mri_data.shape)
        self._img_ranges = [[0, self._voxel_sizes[1], 0, self._voxel_sizes[2]],
                            [0, self._voxel_sizes[0], 0, self._voxel_sizes[2]],
                            [0, self._voxel_sizes[0], 0, self._voxel_sizes[1]]]

        # ready ct
        self._ct_data, vox_ras_t = _load_image(ct, 'CT', verbose=self._verbose)
        if self._mri_data.shape != self._ct_data.shape or \
                not np.allclose(self._vox_ras_t, vox_ras_t, rtol=1e-6):
            raise ValueError('CT is not aligned to MRI, got '
                             f'CT shape={self._ct_data.shape}, '
                             f'MRI shape={self._mri_data.shape}, '
                             f'CT affine={vox_ras_t} and '
                             f'MRI affine={self._vox_ras_t}')

        if op.exists(op.join(self._subject_dir, 'surf', 'lh.seghead')):
            self._head = _read_mri_surface(
                op.join(self._subject_dir, 'surf', 'lh.seghead'))
            assert _frame_to_str[self._head['coord_frame']] == 'mri'
        else:
            warn('`seghead` not found, skipping head plot, see '
                 ':ref:`mne.bem.make_scalp_surfaces` to add the head')
            self._head = None
        if op.exists(op.join(self._subject_dir, 'surf', 'lh.pial')):
            self._lh = _read_mri_surface(
                op.join(self._subject_dir, 'surf', 'lh.pial'))
            assert _frame_to_str[self._lh['coord_frame']] == 'mri'
            self._rh = _read_mri_surface(
                op.join(self._subject_dir, 'surf', 'rh.pial'))
            assert _frame_to_str[self._rh['coord_frame']] == 'mri'
        else:
            warn('`pial` surface not found, skipping adding to 3D '
                 'plot. This indicates the Freesurfer recon-all '
                 'has been modified and these files have been deleted.')
            self._lh = self._rh = None

    def _make_ch_image(self, axis):
        """Make a plot to display the channel locations."""
        # Make channel data higher resolution so it looks better.
        ch_image = np.zeros((_CH_PLOT_SIZE, _CH_PLOT_SIZE)) * np.nan
        vx, vy, vz = self._voxel_sizes

        def color_ch_radius(ch_image, xf, yf, group, radius):
            # Take the fraction across each dimension of the RAS
            # coordinates converted to xyz and put a circle in that
            # position in this larger resolution image
            ex, ey = np.round(np.array([xf, yf]) * _CH_PLOT_SIZE).astype(int)
            for i in range(-radius, radius + 1):
                for j in range(-radius, radius + 1):
                    if (i**2 + j**2)**0.5 < radius:
                        # negative y because y axis is inverted
                        ch_image[-(ey + i), ex + j] = group
            return ch_image

        for name, ras in self._chs.items():
            # move from middle-centered (half coords positive, half negative)
            # to bottom-left corner centered (all coords positive).
            if np.isnan(ras).any():
                continue
            xyz = apply_trans(self._ras_vox_t, ras)
            # check if closest to that voxel
            dist = np.linalg.norm(xyz - self._current_slice)
            if dist < self._radius:
                x, y, z = xyz
                group = self._groups[name]
                r = self._radius - np.round(abs(dist)).astype(int)
                if axis == 0:
                    ch_image = color_ch_radius(
                        ch_image, y / vy, z / vz, group, r)
                elif axis == 1:
                    ch_image = color_ch_radius(
                        ch_image, x / vx, z / vx, group, r)
                elif axis == 2:
                    ch_image = color_ch_radius(
                        ch_image, x / vx, y / vy, group, r)
        return ch_image

    @verbose
    def _save_ch_coords(self, info=None, verbose=None):
        """Save the location of the electrode contacts."""
        logger.info('Saving channel positions to `info`')
        if info is None:
            info = self._info
        for name, ch in zip(info.ch_names, info['chs']):
            ch['loc'][:3] = apply_trans(
                self._mri_head_t, self._chs[name] / 1000)  # mm->m

    def _plot_images(self):
        """Use the MRI and CT to make plots."""
        # Plot sagittal (0), coronal (1) or axial (2) view
        self._images = dict(ct=list(), chs=list(),
                            cursor=list(), cursor2=list())
        ct_min, ct_max = np.nanmin(self._ct_data), np.nanmax(self._ct_data)
        text_kwargs = dict(fontsize=3, color='#66CCEE', family='monospace',
                           weight='bold', ha='center', va='center')
        xyz = apply_trans(self._ras_vox_t, self._ras)
        for axis in range(3):
            ct_data = np.take(self._ct_data, self._current_slice[axis],
                              axis=axis).T
            self._images['ct'].append(self._figs[axis].axes[0].imshow(
                ct_data, cmap='gray', aspect='auto',
                vmin=ct_min, vmax=ct_max))
            self._images['chs'].append(
                self._figs[axis].axes[0].imshow(
                    self._make_ch_image(axis), aspect='auto',
                    extent=self._img_ranges[axis],
                    cmap=_CMAP, alpha=self._ch_alpha, vmin=0, vmax=_N_COLORS))
            self._images['cursor'].append(
                self._figs[axis].axes[0].plot(
                    (xyz[axis], xyz[axis]), (0, self._voxel_sizes[axis]),
                    color=[0, 1, 0], linewidth=1, alpha=0.5)[0])
            self._images['cursor2'].append(
                self._figs[axis].axes[0].plot(
                    (0, self._voxel_sizes[axis]), (xyz[axis], xyz[axis]),
                    color=[0, 1, 0], linewidth=1, alpha=0.5)[0])
            # label axes
            self._figs[axis].text(0.5, 0.05, _IMG_LABELS[axis][0],
                                  **text_kwargs)
            self._figs[axis].text(0.05, 0.5, _IMG_LABELS[axis][1],
                                  **text_kwargs)
            self._figs[axis].axes[0].axis(self._img_ranges[axis])
            self._figs[axis].canvas.mpl_connect(
                'scroll_event', self._on_scroll)
            self._figs[axis].canvas.mpl_connect(
                'button_release_event', partial(self._on_click, axis))
        # add head and brain in mm (convert from m)
        if self._head is not None:
            self._renderer.mesh(
                *self._head['rr'].T * 1000, triangles=self._head['tris'],
                color='gray', opacity=0.2, reset_camera=False, render=False)
        if self._lh is not None and self._rh is not None:
            self._renderer.mesh(
                *self._lh['rr'].T * 1000, triangles=self._lh['tris'],
                color='white', opacity=0.2, reset_camera=False, render=False)
            self._renderer.mesh(
                *self._rh['rr'].T * 1000, triangles=self._rh['tris'],
                color='white', opacity=0.2, reset_camera=False, render=False)
        self._3d_chs = dict()
        self._plot_3d_ch_pos()
        self._renderer.set_camera(azimuth=90, elevation=90, distance=300,
                                  focalpoint=tuple(self._ras))
        # update plots
        self._draw()
        self._renderer._update()

    def _scale_radius(self):
        """Scale the radius to mm."""
        shape = np.mean(self._ct_data.shape)  # this is Freesurfer shape (256)
        scale = np.diag(self._ras_vox_t)[:3].mean()
        return scale * self._radius * (shape / _CH_PLOT_SIZE)

    def _update_camera(self, render=False):
        """Update the camera position."""
        self._renderer.set_camera(
            # needs fix, distance moves when focal point updates
            distance=self._renderer.plotter.camera.distance * 0.9,
            focalpoint=tuple(self._ras),
            reset_camera=False)

    def _plot_3d_ch(self, name, render=False):
        """Plot a single 3D channel."""
        if name in self._3d_chs:
            self._renderer.plotter.remove_actor(self._3d_chs.pop(name))
        if not any(np.isnan(self._chs[name])):
            radius = self._scale_radius()
            self._3d_chs[name] = self._renderer.sphere(
                tuple(self._chs[name]), scale=radius * 3,
                color=_UNIQUE_COLORS[self._groups[name] % _N_COLORS],
                opacity=self._ch_alpha)[0]
        if render:
            self._renderer._update()

    def _plot_3d_ch_pos(self, render=False):
        for name in self._chs:
            self._plot_3d_ch(name)
        if render:
            self._renderer._update()

    def _get_button_bar(self):
        """Make a bar with buttons for user interactions."""
        hbox = QHBoxLayout()

        help_button = QPushButton('Help')
        help_button.released.connect(self._show_help)
        hbox.addWidget(help_button)

        hbox.addStretch(8)

        hbox.addWidget(QLabel('Snap to Center'))
        self._snap_button = QPushButton('Off')
        self._snap_button.setMaximumWidth(25)  # not too big
        hbox.addWidget(self._snap_button)
        self._snap_button.released.connect(self._toggle_snap)
        self._toggle_snap()  # turn on to start

        hbox.addStretch(1)

        self._toggle_brain_button = QPushButton('Show Brain')
        self._toggle_brain_button.released.connect(self._toggle_show_brain)
        hbox.addWidget(self._toggle_brain_button)

        hbox.addStretch(1)

        mark_button = QPushButton('Mark')
        hbox.addWidget(mark_button)
        mark_button.released.connect(self._mark_ch)

        remove_button = QPushButton('Remove')
        hbox.addWidget(remove_button)
        remove_button.released.connect(self._remove_ch)

        self._group_selector = ComboBox()
        group_model = self._group_selector.model()

        for i in range(_N_COLORS):
            self._group_selector.addItem(' ')
            color = QtGui.QColor()
            color.setRgb(*(255 * np.array(_UNIQUE_COLORS[i % _N_COLORS])
                           ).round().astype(int))
            brush = QtGui.QBrush(color)
            brush.setStyle(QtCore.Qt.SolidPattern)
            group_model.setData(group_model.index(i, 0),
                                brush, QtCore.Qt.BackgroundRole)
        self._group_selector.clicked.connect(self._select_group)
        self._group_selector.currentIndexChanged.connect(
            self._select_group)
        hbox.addWidget(self._group_selector)

        # update background color for current selection
        self._update_group()

        return hbox

    def _get_slider_bar(self):
        """Make a bar with sliders on it."""

        def make_label(name):
            label = QLabel(name)
            label.setAlignment(QtCore.Qt.AlignCenter)
            return label

        def make_slider(smin, smax, sval, sfun=None):
            slider = QSlider(QtCore.Qt.Horizontal)
            slider.setMinimum(int(round(smin)))
            slider.setMaximum(int(round(smax)))
            slider.setValue(int(round(sval)))
            slider.setTracking(False)  # only update on release
            if sfun is not None:
                slider.valueChanged.connect(sfun)
            slider.keyPressEvent = self._key_press_event
            return slider

        slider_hbox = QHBoxLayout()

        ch_vbox = QVBoxLayout()
        ch_vbox.addWidget(make_label('ch alpha'))
        ch_vbox.addWidget(make_label('ch radius'))
        slider_hbox.addLayout(ch_vbox)

        ch_slider_vbox = QVBoxLayout()
        self._alpha_slider = make_slider(0, 100, self._ch_alpha * 100,
                                         self._update_ch_alpha)
        ch_plot_max = _CH_PLOT_SIZE // 50  # max 1 / 50 of plot size
        ch_slider_vbox.addWidget(self._alpha_slider)
        self._radius_slider = make_slider(0, ch_plot_max, self._radius,
                                          self._update_radius)
        ch_slider_vbox.addWidget(self._radius_slider)
        slider_hbox.addLayout(ch_slider_vbox)

        ct_vbox = QVBoxLayout()
        ct_vbox.addWidget(make_label('CT min'))
        ct_vbox.addWidget(make_label('CT max'))
        slider_hbox.addLayout(ct_vbox)

        ct_slider_vbox = QVBoxLayout()
        ct_min = int(round(np.nanmin(self._ct_data)))
        ct_max = int(round(np.nanmax(self._ct_data)))
        self._ct_min_slider = make_slider(
            ct_min, ct_max, ct_min, self._update_ct_scale)
        ct_slider_vbox.addWidget(self._ct_min_slider)
        self._ct_max_slider = make_slider(
            ct_min, ct_max, ct_max, self._update_ct_scale)
        ct_slider_vbox.addWidget(self._ct_max_slider)
        slider_hbox.addLayout(ct_slider_vbox)
        return slider_hbox

    def _get_bottom_bar(self):
        """Make a bar at the bottom with information in it."""
        hbox = QHBoxLayout()

        hbox.addStretch(10)

        self._intensity_label = QLabel('')  # update later
        hbox.addWidget(self._intensity_label)

        RAS_label = QLabel('RAS =')
        self._RAS_textbox = QPlainTextEdit('')  # update later
        self._RAS_textbox.setMaximumHeight(25)
        self._RAS_textbox.setMaximumWidth(200)
        self._RAS_textbox.focusOutEvent = self._update_RAS
        self._RAS_textbox.textChanged.connect(self._check_update_RAS)
        hbox.addWidget(RAS_label)
        hbox.addWidget(self._RAS_textbox)
        self._update_moved()  # update text now
        return hbox

    def _group_channels(self, groups):
        """Automatically find a group based on the name of the channel."""
        if groups is not None:
            for name in self._ch_names:
                if name not in groups:
                    raise ValueError(f'{name} not found in ``groups``')
                _validate_type(groups[name], (float, int), f'groups[{name}]')
            self.groups = groups
        else:
            i = 0
            self._groups = dict()
            base_names = dict()
            for name in self._ch_names:
                # strip all numbers from the name
                base_name = ''.join([letter for letter in name if
                                     not letter.isdigit() and letter != ' '])
                if base_name in base_names:
                    # look up group number by base name
                    self._groups[name] = base_names[base_name]
                else:
                    self._groups[name] = i
                    base_names[base_name] = i
                    i += 1

    def _set_ch_names(self):
        """Add the channel names to the selector."""
        self._ch_list_model = QtGui.QStandardItemModel(self._ch_list)
        for name in self._ch_names:
            self._ch_list_model.appendRow(QtGui.QStandardItem(name))
            self._color_list_item(name=name)
        self._ch_list.setModel(self._ch_list_model)
        self._ch_list.clicked.connect(self._go_to_ch)
        self._ch_list.setCurrentIndex(
            self._ch_list_model.index(self._ch_index, 0))
        self._ch_list.keyPressEvent = self._key_press_event

    def _select_group(self):
        """Change the group label to the selection."""
        group = self._group_selector.currentIndex()
        self._groups[self._ch_names[self._ch_index]] = group
        # color differently if found already
        self._color_list_item(self._ch_names[self._ch_index])
        self._update_group()

    def _update_group(self):
        """Set background for closed group menu."""
        group = self._group_selector.currentIndex()
        rgb = (255 * np.array(_UNIQUE_COLORS[group % _N_COLORS])
               ).round().astype(int)
        self._group_selector.setStyleSheet(
            'background-color: rgb({:d},{:d},{:d})'.format(*rgb))
        self._group_selector.update()

    def _on_scroll(self, event):
        """Process mouse scroll wheel event to zoom."""
        self._zoom(event.step, draw=True)

    def _zoom(self, sign=1, draw=False):
        """Zoom in on the image."""
        delta = _ZOOM_STEP_SIZE * sign
        for axis, fig in enumerate(self._figs):
            xmid = self._images['cursor'][axis].get_xdata()[0]
            ymid = self._images['cursor2'][axis].get_ydata()[0]
            xmin, xmax = fig.axes[0].get_xlim()
            ymin, ymax = fig.axes[0].get_ylim()
            xwidth = (xmax - xmin) / 2 - delta
            ywidth = (ymax - ymin) / 2 - delta
            if xwidth <= 0 or ywidth <= 0:
                return
            fig.axes[0].set_xlim(xmid - xwidth, xmid + xwidth)
            fig.axes[0].set_ylim(ymid - ywidth, ymid + ywidth)
            self._images['cursor'][axis].set_ydata([ymin, ymax])
            self._images['cursor2'][axis].set_xdata([xmin, xmax])
            if draw:
                self._figs[axis].canvas.draw()

    def _update_ch_selection(self):
        """Update which channel is selected."""
        name = self._ch_names[self._ch_index]
        self._ch_list.setCurrentIndex(
            self._ch_list_model.index(self._ch_index, 0))
        self._group_selector.setCurrentIndex(self._groups[name])
        self._update_group()
        if not np.isnan(self._chs[name]).any():
            self._ras[:] = self._chs[name]
            self._move_cursors_to_pos()
            self._update_camera(render=True)
            self._draw()

    def _go_to_ch(self, index):
        """Change current channel to the item selected."""
        self._ch_index = index.row()
        self._update_ch_selection()

    @pyqtSlot()
    def _next_ch(self):
        """Increment the current channel selection index."""
        self._ch_index = (self._ch_index + 1) % len(self._ch_names)
        self._update_ch_selection()

    @pyqtSlot()
    def _update_RAS(self, event):
        """Interpret user input to the RAS textbox."""
        text = self._RAS_textbox.toPlainText().replace('\n', '')
        ras = text.split(',')
        if len(ras) != 3:
            ras = text.split(' ')  # spaces also okay as in freesurfer
        ras = [var.lstrip().rstrip() for var in ras]

        if len(ras) != 3:
            self._update_moved()  # resets RAS label
            return
        all_float = all([all([dig.isdigit() or dig in ('-', '.')
                              for dig in var]) for var in ras])
        if not all_float:
            self._update_moved()  # resets RAS label
            return

        ras = np.array([float(var) for var in ras])
        xyz = apply_trans(self._ras_vox_t, ras)
        wrong_size = any([var < 0 or var > n for var, n in
                          zip(xyz, self._voxel_sizes)])
        if wrong_size:
            self._update_moved()  # resets RAS label
            return

        # valid RAS position, update and move
        self._ras = ras
        self._move_cursors_to_pos()

    @pyqtSlot()
    def _check_update_RAS(self):
        """Check whether the RAS textbox is done being edited."""
        if '\n' in self._RAS_textbox.toPlainText():
            self._update_RAS(event=None)
            self._ch_list.setFocus()  # remove focus from text edit

    def _color_list_item(self, name=None):
        """Color the item in the view list for easy id of marked channels."""
        name = self._ch_names[self._ch_index] if name is None else name
        color = QtGui.QColor('white')
        if not np.isnan(self._chs[name]).any():
            group = self._groups[name]
            color.setRgb(*[int(c * 255) for c in
                           _UNIQUE_COLORS[int(group) % _N_COLORS]])
        brush = QtGui.QBrush(color)
        brush.setStyle(QtCore.Qt.SolidPattern)
        self._ch_list_model.setData(
            self._ch_list_model.index(self._ch_names.index(name), 0),
            brush, QtCore.Qt.BackgroundRole)
        # color text black
        color = QtGui.QColor('black')
        brush = QtGui.QBrush(color)
        brush.setStyle(QtCore.Qt.SolidPattern)
        self._ch_list_model.setData(
            self._ch_list_model.index(self._ch_names.index(name), 0),
            brush, QtCore.Qt.ForegroundRole)

    @pyqtSlot()
    def _toggle_snap(self):
        """Toggle snapping the contact location to the center of mass."""
        if self._snap_button.text() == 'Off':
            self._snap_button.setText('On')
            self._snap_button.setStyleSheet("background-color: green")
        else:  # text == 'On', turn off
            self._snap_button.setText('Off')
            self._snap_button.setStyleSheet("background-color: red")

    @pyqtSlot()
    def _mark_ch(self):
        """Mark the current channel as being located at the crosshair."""
        name = self._ch_names[self._ch_index]
        if self._snap_button.text() == 'Off':
            self._chs[name][:] = self._ras
        else:
            coord = apply_trans(self._ras_vox_t, self._ras.copy())
            shape = np.mean(self._mri_data.shape)  # Freesurfer shape (256)
            voxels_max = int(
                4 / 3 * np.pi * (shape * self._radius / _CH_PLOT_SIZE)**3)
            neighbors = _voxel_neighbors(
                coord, self._ct_data, thresh=0.5,
                voxels_max=voxels_max, use_relative=True)
            self._chs[name][:] = apply_trans(  # to surface RAS
                self._vox_ras_t, np.array(list(neighbors)).mean(axis=0))
        self._color_list_item()
        self._update_ch_images(draw=True)
        self._plot_3d_ch(name, render=True)
        self._save_ch_coords()
        self._next_ch()
        self._ch_list.setFocus()

    @pyqtSlot()
    def _remove_ch(self):
        """Remove the location data for the current channel."""
        name = self._ch_names[self._ch_index]
        self._chs[name] *= np.nan
        self._color_list_item()
        self._save_ch_coords()
        self._update_ch_images(draw=True)
        self._plot_3d_ch(name, render=True)
        self._next_ch()
        self._ch_list.setFocus()

    def _draw(self, axis=None):
        """Update the figures with a draw call."""
        for axis in (range(3) if axis is None else [axis]):
            self._figs[axis].canvas.draw()

    def _update_ch_images(self, axis=None, draw=False):
        """Update the channel image(s)."""
        for axis in range(3) if axis is None else [axis]:
            self._images['chs'][axis].set_data(
                self._make_ch_image(axis))
            if draw:
                self._draw(axis)

    def _update_ct_images(self, axis=None, draw=False):
        """Update the CT image(s)."""
        for axis in range(3) if axis is None else [axis]:
            ct_data = np.take(self._ct_data, self._current_slice[axis],
                              axis=axis).T
            # Threshold the CT so only bright objects (electrodes) are visible
            ct_data[ct_data < self._ct_min_slider.value()] = np.nan
            ct_data[ct_data > self._ct_max_slider.value()] = np.nan
            self._images['ct'][axis].set_data(ct_data)
            if draw:
                self._draw(axis)

    def _update_mri_images(self, axis=None, draw=False):
        """Update the CT image(s)."""
        if 'mri' in self._images:
            for axis in range(3) if axis is None else [axis]:
                self._images['mri'][axis].set_data(
                    np.take(self._mri_data, self._current_slice[axis],
                            axis=axis).T)
                if draw:
                    self._draw(axis)

    def _update_images(self, axis=None, draw=True):
        """Update CT and channel images when general changes happen."""
        self._update_ct_images(axis=axis)
        self._update_ch_images(axis=axis)
        self._update_mri_images(axis=axis)
        if draw:
            self._draw(axis)

    def _update_ct_scale(self):
        """Update CT min slider value."""
        new_min = self._ct_min_slider.value()
        new_max = self._ct_max_slider.value()
        # handle inversions
        self._ct_min_slider.setValue(min([new_min, new_max]))
        self._ct_max_slider.setValue(max([new_min, new_max]))
        self._update_ct_images(draw=True)

    def _update_radius(self):
        """Update channel plot radius."""
        self._radius = np.round(self._radius_slider.value()).astype(int)
        self._update_ch_images(draw=True)
        self._plot_3d_ch_pos(render=True)
        self._ch_list.setFocus()  # remove focus from 3d plotter

    def _update_ch_alpha(self):
        """Update channel plot alpha."""
        self._ch_alpha = self._alpha_slider.value() / 100
        for axis in range(3):
            self._images['chs'][axis].set_alpha(self._ch_alpha)
        self._draw()
        self._plot_3d_ch_pos(render=True)
        self._ch_list.setFocus()  # remove focus from 3d plotter

    def _get_click_pos(self, axis, x, y):
        """Get which axis was clicked and where."""
        fx, fy = self._figs[axis].transFigure.inverted().transform((x, y))
        xmin, xmax = self._figs[axis].axes[0].get_xlim()
        ymin, ymax = self._figs[axis].axes[0].get_ylim()
        return (fx * (xmax - xmin) + xmin, fy * (ymax - ymin) + ymin)

    def _move_cursors_to_pos(self):
        """Move the cursors to a position."""
        x, y, z = apply_trans(self._ras_vox_t, self._ras)
        self._current_slice = np.array([x, y, z]).round().astype(int)
        self._move_cursor_to(0, x=y, y=z)
        self._move_cursor_to(1, x=x, y=z)
        self._move_cursor_to(2, x=x, y=y)
        self._zoom(0)  # doesn't actually zoom just resets view to center
        self._update_images(draw=True)
        self._update_moved()

    def _move_cursor_to(self, axis, x, y):
        """Move the cursors to a position for a given subplot."""
        self._images['cursor2'][axis].set_ydata([y, y])
        self._images['cursor'][axis].set_xdata([x, x])

    def _show_help(self):
        """Show the help menu."""
        QMessageBox.information(
            self, 'Help',
            "Help:\n'm': mark channel location\n"
            "'r': remove channel location\n"
            "'b': toggle viewing of brain in T1\n"
            "'+'/'-': zoom\nleft/right arrow: left/right\n"
            "up/down arrow: superior/inferior\n"
            "page up/page down arrow: anterior/posterior")

    def _toggle_show_brain(self):
        """Toggle whether the brain/MRI is being shown."""
        if 'mri' in self._images:
            for img in self._images['mri']:
                img.remove()
            self._images.pop('mri')
            self._toggle_brain_button.setText('Show Brain')
        else:
            self._images['mri'] = list()
            for axis in range(3):
                mri_data = np.take(self._mri_data,
                                   self._current_slice[axis], axis=axis).T
                self._images['mri'].append(self._figs[axis].axes[0].imshow(
                    mri_data, cmap='hot', aspect='auto', alpha=0.25))
            self._toggle_brain_button.setText('Hide Brain')
        self._draw()

    def _key_press_event(self, event):
        """Execute functions when the user presses a key."""
        if event.key() == 'escape':
            self.close()

        if event.text() == 'h':
            self._show_help()

        if event.text() == 'm':
            self._mark_ch()

        if event.text() == 'r':
            self._remove_ch()

        if event.text() == 'b':
            self._toggle_show_brain()

        if event.text() in ('=', '+', '-'):
            self._zoom(sign=-2 * (event.text() == '-') + 1, draw=True)

        # Changing slices
        if event.key() in (QtCore.Qt.Key_Up, QtCore.Qt.Key_Down,
                           QtCore.Qt.Key_Left, QtCore.Qt.Key_Right,
                           QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown):
            if event.key() in (QtCore.Qt.Key_Up, QtCore.Qt.Key_Down):
                self._ras[2] += 2 * (event.key() == QtCore.Qt.Key_Up) - 1
            elif event.key() in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Right):
                self._ras[0] += 2 * (event.key() == QtCore.Qt.Key_Right) - 1
            elif event.key() in (QtCore.Qt.Key_PageUp,
                                 QtCore.Qt.Key_PageDown):
                self._ras[1] += 2 * (event.key() == QtCore.Qt.Key_PageUp) - 1
            self._move_cursors_to_pos()

    def _on_click(self, axis, event):
        """Move to view on MRI and CT on click."""
        # Transform coordinates to figure coordinates
        pos = self._get_click_pos(axis, event.x, event.y)
        logger.info(f'Clicked axis {axis} at pos {pos}')

        if axis is not None and pos is not None:
            xyz = apply_trans(self._ras_vox_t, self._ras)
            if axis == 0:
                xyz[[1, 2]] = pos
            elif axis == 1:
                xyz[[0, 2]] = pos
            elif axis == 2:
                xyz[[0, 1]] = pos
            self._ras = apply_trans(self._vox_ras_t, xyz)
            self._move_cursors_to_pos()

    def _update_moved(self):
        """Update when cursor position changes."""
        self._RAS_textbox.setPlainText('{:.2f}, {:.2f}, {:.2f}'.format(
            *self._ras))
        self._intensity_label.setText('intensity = {:.2f}'.format(
            self._ct_data[tuple(self._current_slice)]))
コード例 #8
0
    def __init__(self, *args, **kwargs):
        super(KwWorkFontDialog, self).__init__(*args, **kwargs)

        #fontIds = []
        #for font_fname in free_font_path_generator():
        #    fontIds = QFontDatabase.addApplicationFont(font_fname)
        #QFontDatabase.removeAllApplicationFonts()

        fontFreeFilterChecker = QCheckBox(u'Вільні')
        fontNoFreeFilterChecker = QCheckBox(u'Невільні')

        fontRating5FilterChecker = QCheckBox(u'Найкращі')
        fontRating4FilterChecker = QCheckBox(u'Добрі')
        fontRating3FilterChecker = QCheckBox(u'Задовільні')

        fontUseStrict_FilterChecker = QCheckBox(u'Діловодство, наука')
        fontUseWriting_FilterChecker = QCheckBox(u'Рукопис')
        fontUseFantasy_FilterChecker = QCheckBox(u'Декорації, реклама')

        fontFamilyListView = QListView()
        fontFamilyListView.setMinimumWidth(250)
        #fontFamilyModel = QStringListModel([str(fontDatabase.sty)])

        fontStyleListView = QListView()
        fontStyleListView.setFixedWidth(150)

        fontSizeListView = QListView()
        fontSizeListView.setFixedWidth(60)
        fontSizeModel = QStringListModel([
            str(v) for v in (6, 7, 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24,
                             26, 28, 36, 48, 72)
        ])
        fontSizeListView.setModel(fontSizeModel)
        fontSizeListView.setEditTriggers(QAbstractItemView.NoEditTriggers)

        fontSampleWidget = QLabel(u'ІіЇїРр')
        fontSampleWidget.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)
        fontSampleWidget.setStyleSheet(r'''QLabel {
          font: normal normal 72pt "Consolas";
          padding: 3px;
          qproperty-alignment: AlignCenter;
          color: black;
          background-color: white;
          }''')

        sampleGroup = QGroupBox(u'Зразок:')
        sampleLayout = QHBoxLayout(sampleGroup)
        sampleLayout.addWidget(fontSampleWidget)

        fontFilterLayout = QHBoxLayout()

        fontAccessFilterGroup = QGroupBox(u'Доступність:')
        fontAccessFilterLayout = QVBoxLayout(fontAccessFilterGroup)
        fontAccessFilterLayout.addWidget(fontFreeFilterChecker)
        fontAccessFilterLayout.addWidget(fontNoFreeFilterChecker)
        fontAccessFilterLayout.addStretch()
        fontFilterLayout.addWidget(fontAccessFilterGroup)

        fontUseFilterGroup = QGroupBox(u'Призначення:')
        fontUseFilterLayout = QVBoxLayout(fontUseFilterGroup)
        fontUseFilterLayout.addWidget(fontUseStrict_FilterChecker)
        fontUseFilterLayout.addWidget(fontUseWriting_FilterChecker)
        fontUseFilterLayout.addWidget(fontUseFantasy_FilterChecker)
        fontFilterLayout.addWidget(fontUseFilterGroup)

        fontRatingFilterGroup = QGroupBox(u'Оцінка:')
        fontRatingFilterLayout = QVBoxLayout(fontRatingFilterGroup)
        fontRatingFilterLayout.addWidget(fontRating5FilterChecker)
        fontRatingFilterLayout.addWidget(fontRating4FilterChecker)
        fontRatingFilterLayout.addWidget(fontRating3FilterChecker)
        fontFilterLayout.addWidget(fontRatingFilterGroup)

        fontLayout = QGridLayout()

        familyLayout = QVBoxLayout()
        familyLayout.setSpacing(3)
        familyLayout.addWidget(QLabel(u'Шрифт:'))
        familyLayout.addWidget(fontFamilyListView)
        fontLayout.addLayout(familyLayout, 0, 0)

        styleLayout = QVBoxLayout()
        styleLayout.setSpacing(3)
        styleLayout.addWidget(QLabel(u'Стиль:'))
        styleLayout.addWidget(fontStyleListView)
        fontLayout.addLayout(styleLayout, 0, 1)

        sizeLayout = QVBoxLayout()
        sizeLayout.setSpacing(3)
        sizeLayout.addWidget(QLabel(u'Розмір:'))
        sizeLayout.addWidget(fontSizeListView)
        fontLayout.addLayout(sizeLayout, 0, 2)

        fontLayout.addWidget(sampleGroup, 1, 0, 1, 3)

        bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)

        layout = QVBoxLayout(self)
        testButton = QPushButton(u'Тестова: відкрити шрифт')
        testButton.clicked.connect(lambda: QFontDialog.getFont())
        layout.addWidget(testButton)
        layout.addLayout(fontFilterLayout)
        layout.addLayout(fontLayout)
        layout.addSpacing(5)
        layout.addWidget(bbox)

        self.setWindowTitle(u'Вибір шрифта')
        self.setWindowFlags(self.windowFlags()
                            ^ Qt.WindowContextHelpButtonHint)

        # Налаштування.
        fontMaxPointSize = fontSizeListView.font()
        # print(QFontDatabase.families())
        fontMaxPointSize.setPointSize(72)
        fm = QFontMetrics(fontMaxPointSize)
        fontSampleWidget.setMinimumHeight(
            fm.size(Qt.TextSingleLine
                    | Qt.TextShowMnemonic, u'Їр').height())

        fontSizeListView.setCurrentIndex(
            fontSizeModel.match(fontSizeModel.index(0, 0), Qt.DisplayRole,
                                fontSizeListView.font().pointSize())[0])

        fontFreeFilterChecker.setChecked(True)

        fontUseStrict_FilterChecker.setChecked(True)

        fontRating5FilterChecker.setChecked(True)
        fontRating4FilterChecker.setChecked(True)
コード例 #9
0
class MusicPlayer(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.options = self.parent().options

        self.ffmpeg = self.options['paths']['ffmpeg_bin']
        self.thumbnails = self.options['paths']['thumbnails']
        self.thumb_width = self.options['thumbnail']['width']
        self.thumb_height = self.options['thumbnail']['height']

        self.jumping = False
        self.blocked = False
        self.durations = {}
        self.playback_value = 0

        self.text = ["None", "Repeat", "Random"]
        self.playlist_list = []
        self.values = [
            QMediaPlaylist.Loop, QMediaPlaylist.CurrentItemInLoop,
            QMediaPlaylist.Random
        ]

        # Thumbnail widget
        self.image_label = QLabel()

        # Control widgets
        self.playButton = QToolButton(clicked=self.play)
        self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))

        self.stopButton = QToolButton(clicked=self.stop)
        self.stopButton.setIcon(self.style().standardIcon(QStyle.SP_MediaStop))
        self.stopButton.setEnabled(False)

        self.playbackButton = QToolButton(clicked=self.playback_mode)
        self.playbackButton.setText(self.text[0])

        self.nextButton = QToolButton(clicked=self.next_song)
        self.nextButton.setIcon(self.style().standardIcon(
            QStyle.SP_MediaSkipForward))

        self.previousButton = QToolButton(clicked=self.previous_song)
        self.previousButton.setIcon(self.style().standardIcon(
            QStyle.SP_MediaSkipBackward))

        self.muteButton = QToolButton(clicked=self.mute_clicked)
        self.muteButton.setIcon(self.style().standardIcon(
            QStyle.SP_MediaVolume))

        self.volumeSlider = QSlider(Qt.Horizontal,
                                    sliderMoved=self.change_volume)
        self.volumeSlider.setRange(0, 100)
        self.volumeSlider.setPageStep(1)
        self.volumeSlider.setValue(50)

        # Player and playlist setup

        self.player = QMediaPlayer()
        self.player.setVolume(50)
        self.player.stateChanged.connect(self.waveform)

        self.playlist = QMediaPlaylist()
        self.playlist.setPlaybackMode(self.values[0])
        self.playlist.setCurrentIndex(1)

        self.player.setPlaylist(self.playlist)

        self.playlistModel = PlaylistModel()
        self.playlistModel.setPlaylist(self.playlist)

        self.playlistView = QListView()
        self.playlistView.setModel(self.playlistModel)
        self.playlistView.setCurrentIndex(
            self.playlistModel.index(self.playlist.currentIndex(), 0))
        self.playlistView.activated.connect(self.jump)
        self.playlistView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.playlistView.customContextMenuRequested.connect(
            self.list_view_menu)
        self.playlist.currentIndexChanged.connect(
            lambda position: self.change_thumbnail(position))

        song_search = QLineEdit()
        song_search.textChanged.connect(self.search)
        song_search.setClearButtonEnabled(True)

        # Playlist
        self.playlist_name = QComboBox()
        self.playlist_name.currentTextChanged.connect(self.switch_playlist)
        self.refresh_lists()

        self.up_button = QToolButton(clicked=self.move_up)
        self.up_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowUp))
        self.down_button = QToolButton(clicked=self.move_down)
        self.down_button.setIcon(self.style().standardIcon(
            QStyle.SP_ArrowDown))

        # Sound wave widget

        self.wave_graphic = WaveGraphic(self)
        #self.wave_graphic.hide()

        # Testing slider again
        self.slider = QSlider(Qt.Horizontal)
        self.slider.setRange(0, self.player.duration() / 1000)
        self.slider.sliderMoved.connect(self.seek)
        self.player.durationChanged.connect(self.durationChanged)
        self.player.positionChanged.connect(self.positionChanged)

        # Shortcuts
        QShortcut(QKeySequence(Qt.CTRL + Qt.Key_F), self, song_search.setFocus)

        # Layouts setup

        playlist_layout = QHBoxLayout()
        playlist_layout.addWidget(self.playlist_name)
        playlist_layout.addWidget(self.up_button)
        playlist_layout.addWidget(self.down_button)

        control_layout = QHBoxLayout()
        control_layout.setContentsMargins(0, 0, 0, 0)
        control_layout.addWidget(self.stopButton)
        control_layout.addWidget(self.previousButton)
        control_layout.addWidget(self.playButton)
        control_layout.addWidget(self.nextButton)
        control_layout.addWidget(self.muteButton)
        control_layout.addWidget(self.volumeSlider)
        control_layout.addWidget(self.playbackButton)

        display_layout = QVBoxLayout()
        display_layout.addWidget(song_search)
        display_layout.addWidget(self.playlistView)
        display_layout.addLayout(playlist_layout)

        music_layout = QVBoxLayout()
        music_layout.addWidget(self.image_label)
        music_layout.addWidget(self.slider)
        music_layout.addLayout(control_layout)

        main_layout = QHBoxLayout()
        main_layout.addLayout(music_layout)
        main_layout.addLayout(display_layout)

        main_2_layout = QVBoxLayout()
        main_2_layout.addLayout(main_layout)
        main_2_layout.addWidget(self.wave_graphic)

        self.setLayout(main_2_layout)

    def waveform(self, status):
        if status == QMediaPlayer.PlayingState:
            self.wave_graphic.start()
        elif status == QMediaPlayer.PausedState:
            self.wave_graphic.pause()
        else:
            self.wave_graphic.stop()

    def list_view_menu(self, point):
        menu = QMenu("Menu", self)
        recommend_action = QAction('&Recommend Songs', self)
        menu.addAction(recommend_action)

        # TODO: [FEATURE] add rename song
        recommend_action.triggered.connect(
            lambda: self.parent().call_download_manager(self.get_links()))

        rename_action = QAction('&Rename', self)
        menu.addAction(rename_action)

        # TODO: [FEATURE] add rename song
        rename_action.triggered.connect(lambda: print("rename"))

        # Show the context menu.
        menu.exec_(self.playlistView.mapToGlobal(point))

    def get_links(self):
        title = self.playlistView.selectedIndexes()[0].data()
        link = getYoutubeURLFromSearch(title)
        if len(link) > 1:
            return youtube_recommendations(link)

    def move_up(self):
        index = self.playlistView.currentIndex().row()
        if index - 1 >= 0:
            item, above = self.playlist.media(index), self.playlist.media(
                index - 1)
            self.playlist.removeMedia(index)
            self.playlist.removeMedia(index - 1)
            self.playlist.insertMedia(index - 1, item)
            self.playlist.insertMedia(index, above)
            self.blocked = True
            self.playlistView.setCurrentIndex(
                self.playlistModel.index(index - 1, 0))
            self.blocked = False
            self.stop()

    def move_down(self):
        index = self.playlistView.currentIndex().row()
        if index + 1 <= self.playlistModel.rowCount():
            item, below = self.playlist.media(index), self.playlist.media(
                index + 1)
            self.playlist.removeMedia(index + 1)
            self.playlist.removeMedia(index)
            self.playlist.insertMedia(index, below)
            self.playlist.insertMedia(index + 1, item)
            self.blocked = True
            self.playlistView.setCurrentIndex(
                self.playlistModel.index(index + 1, 0))
            self.blocked = False
            self.stop()

    def search(self, part_of_song):
        for index in range(self.playlistModel.rowCount()):
            item = self.playlistModel.data(self.playlistModel.index(
                index, 0)).lower()
            self.playlistView.setRowHidden(index,
                                           part_of_song.lower() not in item)

    def change_thumbnail(self, position=0):

        self.playlistView.setCurrentIndex(self.playlistModel.index(
            position, 0))

        song = self.playlistView.selectedIndexes()[0].data()
        if self.wave_graphic.is_song_cached(song):
            self.wave_graphic.load_waves(song)
        else:
            wc_ = WaveConverter(song, self.wave_graphic.set_wav)
            wc_.convert()

        if self.playlistView.currentIndex().data() is None or self.blocked:
            return

        max_ratio = 0
        img = None

        for item in listdir(self.thumbnails):
            if item.endswith('.jpg'):
                ratio = similar(
                    item,
                    self.playlistView.currentIndex().data().replace(
                        '.mp3', '.jpg'))
                if ratio > max_ratio:
                    max_ratio = ratio
                    img = item

        if img:
            p = QPixmap(self.thumbnails + img)
            self.image_label.setPixmap(
                p.scaled(self.thumb_width, self.thumb_height,
                         Qt.KeepAspectRatio))

    def switch_playlist(self, current_text):
        self.playlist.clear()
        if current_text == "No Playlist":
            self.refresh()
        else:
            if read_playlist(current_text):
                songs = read_playlist(current_text).split('\n')
                for song in songs:
                    self.playlist.addMedia(
                        QMediaContent(QUrl.fromLocalFile(song)))

    def refresh(self):
        # Change it so it will go to same song.
        if self.playlist_name.currentText() != "No Playlist":
            return

        paths = fetch_options()['paths']['music_path'].split(';')

        current_songs = [
            self.playlistModel.data(self.playlistModel.index(row, 0))
            for row in range(self.playlistModel.rowCount())
        ]

        for path in paths:
            if not path:
                continue
            for item in listdir(path):
                if isfile(join(path, item)) and item.endswith(".mp3") and (
                        item not in current_songs):
                    self.playlist.addMedia(
                        QMediaContent(QUrl.fromLocalFile(join(path, item))))

    def refresh_lists(self):
        path = fetch_options()['paths']['playlist']
        self.playlist_name.clear()
        self.playlist_list = ["No Playlist"]
        self.playlist_name.addItem("No Playlist")
        for item in listdir(path):
            if isfile(join(path, item)) and item.endswith(".lst"):
                self.playlist_list.append(item.split('.')[0])
                self.playlist_name.addItem(item.split('.')[0])

    def playback_mode(self):
        # Normal -> Loop -> Random
        def up_value(value, max_value=3):
            if value + 1 == max_value:
                return 0
            return value + 1

        self.playback_value = up_value(self.playback_value)
        self.playlist.setPlaybackMode(self.values[self.playback_value])
        self.playbackButton.setText(self.text[self.playback_value])

    def jump(self, index):
        if index.isValid() and not self.blocked:
            self.playlist.setCurrentIndex(index.row())
            self.jumping = True
            self.play()

    def play(self):
        if self.blocked:
            return
        if self.player.state() != QMediaPlayer.PlayingState or self.jumping:
            self.player.play()
            self.playButton.setIcon(self.style().standardIcon(
                QStyle.SP_MediaPause))
            self.jumping = False
        else:
            self.player.pause()
            self.playButton.setIcon(self.style().standardIcon(
                QStyle.SP_MediaPlay))
        self.stopButton.setEnabled(True)

    def change_volume(self, value):
        self.player.setVolume(value)

    def stop(self):
        if self.player.state() != QMediaPlayer.StoppedState:
            self.player.stop()
        self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
        self.stopButton.setEnabled(False)

    def next_song(self):
        self.playlist.next()
        self.playlistView.setCurrentIndex(
            self.playlistModel.index(self.playlist.currentIndex(), 0))

    def previous_song(self):
        self.playlist.previous()
        self.playlistView.setCurrentIndex(
            self.playlistModel.index(self.playlist.currentIndex(), 0))

    def mute_clicked(self):
        self.player.setMuted(not self.player.isMuted())
        self.muteButton.setIcon(self.style().standardIcon(
            QStyle.SP_MediaVolume if not self.player.isMuted() else QStyle.
            SP_MediaVolumeMuted))

    def durationChanged(self, duration):
        duration /= 1000
        self.slider.setMaximum(duration)

    def positionChanged(self, progress):
        progress /= 1000

        if not self.slider.isSliderDown():
            self.slider.setValue(progress)

    def seek(self, seconds):
        self.player.setPosition(seconds * 1000)
コード例 #10
0
class LocationCompleterView(QWidget):
    def __init__(self):
        super().__init__(None)
        self._view = None  # QListView
        self._delegate = None  # LocationCompleterDelegate
        self._searchEnginesLayout = None  # QHBoxLayout
        self._resizeHeight = -1
        self._resizeTimer = None  # QTimer
        self._forceResize = True

        self.setAttribute(Qt.WA_ShowWithoutActivating)
        self.setAttribute(Qt.WA_X11NetWmWindowTypeCombo)

        if gVar.app.platformName() == 'xcb':
            self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint
                                | Qt.BypassWindowManagerHint)
        else:
            self.setWindowFlags(Qt.Popup)

        layout = QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)

        self._view = QListView(self)
        layout.addWidget(self._view)

        self._view.setUniformItemSizes(True)
        self._view.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self._view.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
        self._view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self._view.setSelectionBehavior(QAbstractItemView.SelectRows)
        self._view.setSelectionMode(QAbstractItemView.SingleSelection)

        self._view.setMouseTracking(True)
        gVar.app.installEventFilter(self)

        self._delegate = LocationCompleterDelegate(self)
        self._view.setItemDelegate(self._delegate)

        searchFrame = QFrame(self)
        searchFrame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        searchLayout = QHBoxLayout(searchFrame)
        searchLayout.setContentsMargins(10, 4, 4, 4)

        searchSettingsButton = ToolButton(self)
        searchSettingsButton.setIcon(IconProvider.settingsIcon())
        searchSettingsButton.setToolTip(_('Manage Search Engines'))
        searchSettingsButton.setAutoRaise(True)
        searchSettingsButton.setIconSize(QSize(16, 16))
        searchSettingsButton.clicked.connect(self.searchEnginesDialogRequested)

        searchLabel = QLabel(_('Search with:'))
        self._searchEnginesLayout = QHBoxLayout()

        self._setupSearchEngines()
        gVar.app.searchEnginesManager().enginesChanged.connect(
            self._setupSearchEngines)

        searchLayout.addWidget(searchLabel)
        searchLayout.addLayout(self._searchEnginesLayout)
        searchLayout.addStretch()
        searchLayout.addWidget(searchSettingsButton)

        layout.addWidget(searchFrame)

    def model(self):
        '''
        @return: QAbstractItemModel
        '''
        return self._view.model()

    def setModel(self, model):
        '''
        @param model QAbstractItemModel
        '''
        self._view.setModel(model)

    def selectionModel(self):
        '''
        @return: QItemSelectionModel
        '''
        return self._view.selectionModel()

    def currentIndex(self):
        '''
        @return: QModelIndex
        '''
        return self._view.currentIndex()

    def setCurrentIndex(self, index):
        '''
        @param index QModelIndex
        '''
        self._view.setCurrentIndex(index)

    def adjustSize(self):
        maxItemsCount = 12
        newHeight = self._view.sizeHintForRow(0) * min(maxItemsCount,
                                                       self.model().rowCount())

        if not self._resizeTimer:
            self._resizeTimer = QTimer(self)
            self._resizeTimer.setInterval(200)

            def func():
                if self._resizeHeight > 0:
                    self._view.setFixedHeight(self._resizeHeight)
                    self.setFixedHeight(self.sizeHint().height())
                self._resizeHeight = -1

            self._resizeTimer.timeout.connect(func)

        if not self._forceResize:
            if newHeight == self._resizeHeight:
                return
            elif newHeight == self._view.height():
                self._resizeHeight = -1
                return
            elif newHeight < self._view.height():
                self._resizeHeight = newHeight
                self._resizeTimer.start()
                return

        self._resizeHeight = -1
        self._forceResize = False
        self._view.setFixedHeight(newHeight)
        self.setFixedHeight(self.sizeHint().height())

    # override
    def eventFilter(self, obj, event):  # noqa C901
        '''
        @param obj QObject
        @param event QEvent
        @return: bool
        '''
        # Event filter based on QCompleter::eventFilter from qcompleter.cpp
        if obj == self or obj == self._view or not self.isVisible():
            return False

        evtType = event.type()
        if obj == self._view.viewport():
            if evtType == QEvent.MouseButtonRelease:
                # QMouseEvent
                e = event
                index = self._view.indexAt(e.pos())
                if not index.isValid():
                    return False

                # Qt::MouseButton
                button = e.button()
                # Qt::KeyboardModifiers
                modifiers = e.modifiers()

                if button == Qt.LeftButton and modifiers == Qt.NoModifier:
                    self.indexActivated.emit(index)
                    return True

                if button == Qt.MiddleButton or (button == Qt.LeftButton
                                                 and modifiers
                                                 == Qt.ControlModifier):
                    self.indexCtrlActivated.emit(index)
                    return True

                if button == Qt.LeftButton and modifiers == Qt.ShiftModifier:
                    self.indexShiftActivated.emit(index)
                    return True

            return False

        if evtType == QEvent.KeyPress:
            # QKeyEvent
            keyEvent = event
            evtKey = keyEvent.key()
            modifiers = keyEvent.modifiers()
            index = self._view.currentIndex()
            item = self.model().index(0, 0)
            if item.data(LocationCompleterModel.VisitSearchItemRole):
                visitSearchIndex = item
            else:
                visitSearchIndex = QModelIndex()

            if (evtKey == Qt.Key_Up or evtKey == Qt.Key_Down) and \
                    self._view.currentIndex() != index:
                self._view.setCurrentIndex(index)  # TODO: ?

            if evtKey in (Qt.Key_Return, Qt.Key_Enter):
                if index.isValid():
                    if modifiers == Qt.NoModifier or modifiers == Qt.KeypadModifier:
                        self.indexActivated.emit(index)
                        return True

                    if modifiers == Qt.ControlModifier:
                        self.indexCtrlActivated.emit(index)
                        return True

                    if modifiers == Qt.ShiftModifier:
                        self.indexShiftActivated.emit(index)
                        return True

            elif evtKey == Qt.Key_End:
                if modifiers & Qt.ControlModifier:
                    self._view.setCurrentIndex(self.model().index(
                        self.model().rowCount() - 1, 0))
                    return True
                else:
                    self.close()

            elif evtKey == Qt.Key_Home:
                if modifiers & Qt.ControlModifier:
                    self._view.setCurrentIndex(self.model().index(0, 0))
                    self._view.scrollToTop()
                    return True
                else:
                    self.close()

            elif evtKey == Qt.Key_Escape:
                self.close()
                return True

            elif evtKey == Qt.Key_F4:
                if modifiers == Qt.AltModifier:
                    self.close()
                    return False

            elif evtKey in (Qt.Key_Tab, Qt.Key_Backtab):
                if modifiers != Qt.NoModifier and modifiers != Qt.ShiftModifier:
                    return False
                isBack = evtKey == Qt.Key_Backtab
                if evtKey == Qt.Key_Tab and modifiers == Qt.ShiftModifier:
                    isBack = True
                ev = QKeyEvent(QKeyEvent.KeyPress, isBack and Qt.Key_Up
                               or Qt.Key_Down, Qt.NoModifier)
                QApplication.sendEvent(self.focusProxy(), ev)
                return True

            elif evtKey in (Qt.Key_Up, Qt.Key_PageUp):
                if modifiers != Qt.NoModifier:
                    return False
                step = evtKey == Qt.Key_PageUp and 5 or 1
                if not index.isValid() or index == visitSearchIndex:
                    rowCount = self.model().rowCount()
                    lastIndex = self.model().index(rowCount - 1, 0)
                    self._view.setCurrentIndex(lastIndex)
                elif index.row() == 0:
                    self._view.setCurrentIndex(QModelIndex())
                else:
                    row = max(0, index.row() - step)
                    self._view.setCurrentIndex(self.model().index(row, 0))
                return True

            elif evtKey in (Qt.Key_Down, Qt.Key_PageDown):
                if modifiers != Qt.NoModifier:
                    return False
                step = evtKey == Qt.Key_PageDown and 5 or 1
                if not index.isValid():
                    firstIndex = self.model().index(0, 0)
                    self._view.setCurrentIndex(firstIndex)
                elif index != visitSearchIndex and index.row(
                ) == self.model().rowCount() - 1:
                    self._view.setCurrentIndex(visitSearchIndex)
                    self._view.scrollToTop()
                else:
                    row = min(self.model().rowCount() - 1, index.row() + step)
                    self._view.setCurrentIndex(self.model().index(row, 0))
                return True

            elif evtKey == Qt.Key_Delete:
                if index != visitSearchIndex and self._view.viewport().rect(
                ).contains(self._view.visualRect(index)):
                    self.indexDeleteRequested.emit(index)
                    return True

            elif evtKey == Qt.Key_Shift:
                self._delegate.setForceVisitItem(True)
                self._view.viewport().update()

            # end of switch evtKey

            if self.focusProxy():
                self.focusProxy().event(keyEvent)

            return True

        elif evtType == QEvent.KeyRelease:
            if event.key() == Qt.Key_Shift:
                self._delegate.setForceVisitItem(False)
                self._view.viewport().update()
                return True

        elif evtType in (QEvent.Wheel, QEvent.MouseButtonPress):
            if not self.underMouse():
                self.close()
                return False

        elif evtType == QEvent.FocusOut:
            # QFocusEvent
            focusEvent = event
            reason = focusEvent.reason()
            if reason != Qt.PopupFocusReason and reason != Qt.MouseFocusReason:
                self.close()

        elif evtType in (QEvent.Move, QEvent.Resize):
            w = obj
            if isinstance(w, QWidget) and w.isWindow() and self.focusProxy(
            ) and w == self.focusProxy().window():
                self.close()

        # end of switch evtType
        return False

    # Q_SIGNALS
    closed = pyqtSignal()
    searchEnginesDialogRequested = pyqtSignal()
    loadRequested = pyqtSignal(LoadRequest)

    indexActivated = pyqtSignal(QModelIndex)
    indexCtrlActivated = pyqtSignal(QModelIndex)
    indexShiftActivated = pyqtSignal(QModelIndex)
    indexDeleteRequested = pyqtSignal(QModelIndex)

    # public Q_SLOTS:
    def close(self):
        self.hide()
        self._view.verticalScrollBar().setValue(0)
        self._delegate.setForceVisitItem(False)
        self._forceResize = True

        self.closed.emit()

    # private:
    def _setupSearchEngines(self):
        for idx in (range(self._searchEnginesLayout.count())):
            item = self._searchEnginesLayout.takeAt(0)
            item.deleteLater()

        engines = gVar.app.searchEnginesManager().allEngines()
        for engine in engines:
            button = ToolButton(self)
            button.setIcon(engine.icon)
            button.setToolTip(engine.name)
            button.setAutoRaise(True)
            button.setIconSize(QSize(16, 16))

            def func():
                text = self.model().index(0, 0).data(
                    LocationCompleterModel.SearchStringRole)
                self.loadRequested.emit(
                    gVar.app.searchEngineManager().searchResult(engine, text))

            button.clicked.connect(func)
            self._searchEnginesLayout.addWidget(button)
コード例 #11
0
ファイル: decompiler_gui.py プロジェクト: xTVaser/jak-project
class ObjectFileView(QDialog):
    def __init__(self, name):
        super().__init__()
        self.setWindowTitle(name)
        with open(
                os.path.join(get_jak_path(), "decompiler_out",
                             "{}_asm.json".format(name))) as f:
            self.asm_data = json.loads(f.read())

        main_layout = QVBoxLayout()
        monospaced_font = get_monospaced_font()
        self.header_label = QLabel()

        main_layout.addWidget(self.header_label)

        function_splitter = QSplitter()
        function_splitter.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))

        self.function_list = QTreeView()
        self.function_list_model = QStandardItemModel()
        self.functions_by_name = dict()
        root = self.function_list_model.invisibleRootItem()
        seg_roots = []

        for i in range(3):
            seg_entry = QStandardItem(segment_id_to_name(i))
            seg_entry.setFont(monospaced_font)
            seg_entry.setEditable(False)
            root.appendRow(seg_entry)
            seg_roots.append(seg_entry)

        for f in self.asm_data["functions"]:
            function_entry = QStandardItem(f["name"])
            function_entry.setFont(monospaced_font)
            function_entry.setEditable(False)
            seg_roots[f["segment"]].appendRow(function_entry)
            self.functions_by_name[f["name"]] = f

        self.header_label.setText(
            "Object File {} Functions ({} total):".format(
                name, len(self.asm_data["functions"])))

        self.function_list.setModel(self.function_list_model)
        self.function_list.clicked.connect(self.display_function)
        function_splitter.addWidget(self.function_list)

        layout = QVBoxLayout()

        self.function_header_label = QLabel("No function selected")
        self.function_header_label.setFont(monospaced_font)
        self.header_label.setSizePolicy(
            QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
        layout.addWidget(self.function_header_label)

        self.op_asm_split_view = QSplitter()
        self.op_asm_split_view.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))

        self.basic_op_pane = QListView()
        self.basic_op_pane.clicked.connect(self.basic_op_clicked)
        #layout.addWidget(self.basic_op_pane)
        self.op_asm_split_view.addWidget(self.basic_op_pane)

        self.asm_pane = QListView()
        self.op_asm_split_view.addWidget(self.asm_pane)

        layout.addWidget(self.op_asm_split_view)

        self.asm_display = QPlainTextEdit()
        self.asm_display.setMaximumHeight(80)
        layout.addWidget(self.asm_display)

        self.warnings_label = QLabel()
        layout.addWidget(self.warnings_label)

        widget = QWidget()
        widget.setLayout(layout)
        function_splitter.addWidget(widget)
        main_layout.addWidget(function_splitter)

        # add it to the window!
        self.setLayout(main_layout)

    def display_function(self, item):
        name = item.data()
        monospaced_font = get_monospaced_font()
        func = self.functions_by_name[name]
        basic_op_model = QStandardItemModel()
        basic_op_root = basic_op_model.invisibleRootItem()
        asm_model = QStandardItemModel()
        asm_root = asm_model.invisibleRootItem()

        self.basic_id_to_asm = []
        self.current_function = name
        op_idx = 0
        basic_idx = 0
        for op in func["asm"]:
            if "label" in op:
                asm_item = QStandardItem(op["label"] + "\n    " + op["asm_op"])
            else:
                asm_item = QStandardItem("    " + op["asm_op"])
            asm_item.setFont(monospaced_font)
            asm_item.setEditable(False)
            asm_root.appendRow(asm_item)

            if "basic_op" in op:
                if "label" in op:
                    basic_item = QStandardItem(op["label"] + "\n    " +
                                               op["basic_op"])
                else:
                    basic_item = QStandardItem("    " + op["basic_op"])
                basic_item.setFont(monospaced_font)
                basic_item.setEditable(False)
                basic_op_root.appendRow(basic_item)
                self.basic_id_to_asm.append(op_idx)
                basic_idx = basic_idx + 1
            op_idx = op_idx + 1
        self.basic_id_to_asm.append(op_idx)
        self.basic_op_pane.setModel(basic_op_model)
        self.asm_pane.setModel(asm_model)
        self.warnings_label.setText(func["warnings"])
        self.asm_display.setPlainText("")
        self.function_header_label.setText(
            "{}, type: {}\nfunc: {} obj: {}".format(name, func["type"],
                                                    func["name"],
                                                    func["parent_object"]))

    def basic_op_clicked(self, item):
        text = ""
        added_reg = 0
        asm_idx = self.basic_id_to_asm[item.row()]

        asm_op = self.functions_by_name[self.current_function]["asm"][asm_idx]
        if "type_map" in asm_op:
            for reg, type_name in asm_op["type_map"].items():
                text += "{}: {} ".format(reg, type_name)
                added_reg += 1
                if added_reg >= 4:
                    text += "\n"
                    added_reg = 0
            text += "\n"

        for i in range(asm_idx, self.basic_id_to_asm[item.row() + 1]):
            text += self.functions_by_name[
                self.current_function]["asm"][i]["asm_op"] + "\n"
        op = self.functions_by_name[self.current_function]["asm"][asm_idx]
        if "referenced_string" in op:
            text += op["referenced_string"]
        self.asm_display.setPlainText(text)
        self.asm_display.setFont(get_monospaced_font())
        self.asm_pane.setCurrentIndex(self.asm_pane.model().index(asm_idx, 0))
コード例 #12
0
ファイル: playerview.py プロジェクト: b0ck/musictool
class Player(QWidget):

    fullScreenChanged = pyqtSignal(bool)
    videoWidget = None
    colorDialog = None
    videoWidget = None
    trackInfo = ""
    statusInfo = ""
    duration = 0

    def __init__(self, playlist, parent=None):
        """

        :param playlist:
        :param parent:
        """

        super(Player, self).__init__(parent)

        self._init_audio_player()
        self._init_video_player()
        self._init_addtional_controls()
        control_layout, controls, open_button = self._init_player_controls_layout(
        )
        self._init_playlist_view()
        self._init_layout(control_layout=control_layout)
        self.check_for_player_service(controls=controls,
                                      open_button=open_button)
        self.meta_data_changed()
        self.add_to_playlist(playlist)

    def check_for_player_service(self, controls, open_button):
        """

        :param controls:
        :param open_button:
        :return:
        """

        if not self.player.isAvailable():
            QMessageBox.warning(
                self, "Service not available",
                "The QMediaPlayer object does not have a valid service.\n"
                "Please check the media service plugins are installed.")

            controls.setEnabled(False)
            self.playlistView.setEnabled(False)
            open_button.setEnabled(False)
            self.colorButton.setEnabled(False)
            self.fullScreenButton.setEnabled(False)

    def _init_layout(self, control_layout):
        """

        :param control_layout:
        :return:
        """

        display_layout = QHBoxLayout()
        if self.videoWidget:
            display_layout.addWidget(self.videoWidget, 2)
        if self.playlistView:
            display_layout.addWidget(self.playlistView)

        layout = QVBoxLayout()
        layout.addLayout(display_layout)
        if self.slider or self.labelDuration:
            h_layout = QHBoxLayout()
            if self.slider:
                h_layout.addWidget(self.slider)
            if self.labelDuration:
                h_layout.addWidget(self.labelDuration)
            layout.addLayout(h_layout)

        if control_layout:
            layout.addLayout(control_layout)

        layout.addLayout(self._get_histogram_layout())

        self.setLayout(layout)

    def _init_playlist_view(self):
        """

        :return:
        """

        self.playlistView = QListView()
        self.playlistView.setModel(self.playlistModel)
        self.playlistView.setCurrentIndex(
            self.playlistModel.index(self.playlist.currentIndex(), 0))
        self.playlistView.activated.connect(self.jump)

    def _init_addtional_controls(self):
        """

        :return:
        """

        self.slider = QSlider(Qt.Horizontal)
        self.slider.setRange(0, self.player.duration() / 1000)

        self.labelDuration = QLabel()
        self.slider.sliderMoved.connect(self.seek)

        self.fullScreenButton = QPushButton("FullScreen")
        self.fullScreenButton.setCheckable(True)

        self.colorButton = QPushButton("Color Options...")
        self.colorButton.setEnabled(False)
        self.colorButton.clicked.connect(self.show_color_dialog)

    def _init_player_controls_layout(self):
        """

        :return:
        """

        openButton = QPushButton("Open", clicked=self.open)

        controls = PlayerControls()
        controls.set_state(self.player.state())
        controls.set_volume(self.player.volume())
        controls.set_muted(controls.is_muted())

        controls.play.connect(self.player.play)
        controls.pause.connect(self.player.pause)
        controls.stop.connect(self.player.stop)
        controls.next.connect(self.playlist.next)
        controls.previous.connect(self.previous_clicked)
        controls.changeVolume.connect(self.player.setVolume)
        controls.changeMuting.connect(self.player.setMuted)
        controls.changeRate.connect(self.player.setPlaybackRate)

        if self.videoWidget:
            controls.stop.connect(self.videoWidget.update)

        self.player.stateChanged.connect(controls.set_state)
        self.player.volumeChanged.connect(controls.set_volume)
        self.player.mutedChanged.connect(controls.set_muted)

        controlLayout = QHBoxLayout()
        controlLayout.setContentsMargins(0, 0, 0, 0)
        controlLayout.addWidget(openButton)
        controlLayout.addStretch(1)
        controlLayout.addWidget(controls)
        controlLayout.addStretch(1)
        if self.fullScreenButton:
            controlLayout.addWidget(self.fullScreenButton)
        if self.colorButton:
            controlLayout.addWidget(self.colorButton)

        return controlLayout, controls, openButton

    def _init_audio_player(self):
        """

        :return:
        """

        self.playlist = QMediaPlaylist()
        self.player = QMediaPlayer()
        self.player.setPlaylist(self.playlist)

        self.player.durationChanged.connect(self.duration_changed)
        self.player.positionChanged.connect(self.position_changed)
        self.player.metaDataChanged.connect(self.meta_data_changed)
        self.playlist.currentIndexChanged.connect(
            self.playlist_position_changed)
        self.player.mediaStatusChanged.connect(self.status_changed)
        self.player.bufferStatusChanged.connect(self.buffering_progress)
        self.player.videoAvailableChanged.connect(self.video_available_changed)
        self.player.error.connect(self.display_error_message)

        self.playlistModel = PlaylistModel()
        self.playlistModel.set_playlist(self.playlist)

    def _init_video_player(self):
        """

        :return:
        """
        self.histogram = HistogramWidget()

        self.videoWidget = VideoWidget()
        self.player.setVideoOutput(self.videoWidget)

        self.probe = QVideoProbe()
        self.probe.videoFrameProbed.connect(self.histogram.process_frame)
        self.probe.setSource(self.player)

    def _get_histogram_layout(self):
        """

        :return:
        """
        self.labelHistogram = QLabel()
        self.labelHistogram.setText("Histogram:")

        histogramLayout = QHBoxLayout()
        histogramLayout.addWidget(self.labelHistogram)
        if self.histogram:
            histogramLayout.addWidget(self.histogram, 1)

        return histogramLayout

    def open(self):
        """

        :return:
        """

        file_names, _ = QFileDialog.getOpenFileNames(self, "Open Files")
        self.add_to_playlist(file_names)

    def add_to_playlist(self, file_names):
        """

        :param file_names:
        :return:
        """

        for name in file_names:
            file_info = QFileInfo(name)
            if file_info.exists():
                url = QUrl.fromLocalFile(file_info.absoluteFilePath())
                if file_info.suffix().lower() == 'm3u':
                    self.playlist.load(url)

                else:
                    self.playlist.addMedia(QMediaContent(url))

            else:
                url = QUrl(name)
                if url.isValid():
                    self.playlist.addMedia(QMediaContent(url))

    def duration_changed(self, duration):
        """

        :param duration:
        :return:
        """

        duration /= 1000

        self.duration = duration
        self.slider.setMaximum(duration)

    def position_changed(self, progress):
        """

        :param progress:
        :return:
        """

        progress /= 1000

        if not self.slider.isSliderDown():
            self.slider.setValue(progress)

        self.update_duration_info(progress)

    def meta_data_changed(self):
        """

        :return:
        """

        if self.player.isMetaDataAvailable():
            self.set_track_info(
                "%s - %s" % (self.player.metaData(QMediaMetaData.AlbumArtist),
                             self.player.metaData(QMediaMetaData.Title)))

    def previous_clicked(self):
        """

        :return:
        """

        if self.player.position() <= 5000:
            self.playlist.previous()
        else:
            self.player.setPosition(0)

    def jump(self, index):
        """

        :param index:
        :return:
        """

        if index.isValid():
            self.playlist.setCurrentIndex(index.row())
            self.player.play()

    def playlist_position_changed(self, position):
        """

        :param position:
        :return:
        """

        self.playlistView.setCurrentIndex(self.playlistModel.index(
            position, 0))

    def seek(self, seconds):
        """

        :param seconds:
        :return:
        """
        self.player.setPosition(seconds * 1000)

    def status_changed(self, status):
        """

        :param status:
        :return:
        """

        self.handle_cursor(status)

        if status == QMediaPlayer.LoadingMedia:
            self.set_status_info("Loading...")

        elif status == QMediaPlayer.StalledMedia:
            self.set_status_info("Media Stalled")

        elif status == QMediaPlayer.EndOfMedia:
            QApplication.alert(self)

        elif status == QMediaPlayer.InvalidMedia:
            self.display_error_message()

        else:
            self.set_status_info("")

    def handle_cursor(self, status):
        """

        :param status:
        :return:
        """

        if status in (QMediaPlayer.LoadingMedia, QMediaPlayer.BufferingMedia,
                      QMediaPlayer.StalledMedia):
            self.setCursor(Qt.BusyCursor)

        else:
            self.unsetCursor()

    def buffering_progress(self, progress):
        """

        :param progress:
        :return:
        """

        self.set_status_info("Buffering %d%" % progress)

    def video_available_changed(self, available):
        """

        :param available:
        :return:
        """

        if available:
            self.fullScreenButton.clicked.connect(
                self.videoWidget.setFullScreen)
            self.videoWidget.fullScreenChanged.connect(
                self.fullScreenButton.setChecked)

            if self.fullScreenButton.isChecked():
                self.videoWidget.setFullScreen(True)
        else:
            self.fullScreenButton.clicked.disconnect(
                self.videoWidget.setFullScreen)
            self.videoWidget.fullScreenChanged.disconnect(
                self.fullScreenButton.setChecked)

            self.videoWidget.setFullScreen(False)

        self.colorButton.setEnabled(available)

    def set_track_info(self, info):
        """

        :param info:
        :return:
        """
        self.trackInfo = info

        if self.statusInfo != "":
            self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo))
        else:
            self.setWindowTitle(self.trackInfo)

    def set_status_info(self, info):
        """

        :param info:
        :return:
        """

        self.statusInfo = info

        if self.statusInfo != "":
            self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo))
        else:
            self.setWindowTitle(self.trackInfo)

    def display_error_message(self):
        """

        :return:
        """

        self.set_status_info(self.player.errorString())

    def update_duration_info(self, current_info):
        """

        :param current_info:
        :return:
        """
        result = ""
        duration = self.duration

        if current_info or duration:
            current_time = QTime((current_info / 3600) % 60,
                                 (current_info / 60) % 60, current_info % 60,
                                 (current_info * 1000) % 1000)
            total_time = QTime((duration / 3600) % 60, (duration / 60) % 60,
                               duration % 60, (duration * 1000) % 1000)
            if duration > 3600:
                format = 'hh:mm:ss'
            else:
                format = 'mm:ss'

            result = current_time.toString(
                format) + " / " + total_time.toString(format)

        self.labelDuration.setText(result)

    def show_color_dialog(self):
        """

        :return:
        """

        if self.colorDialog is None:
            brightness_slider = QSlider(Qt.Horizontal)
            brightness_slider.setRange(-100, 100)
            brightness_slider.setValue(self.videoWidget.brightness())
            brightness_slider.sliderMoved.connect(
                self.videoWidget.setBrightness)
            self.videoWidget.brightnessChanged.connect(
                brightness_slider.setValue)

            contrast_slider = QSlider(Qt.Horizontal)
            contrast_slider.setRange(-100, 100)
            contrast_slider.setValue(self.videoWidget.contrast())
            contrast_slider.sliderMoved.connect(self.videoWidget.setContrast)
            self.videoWidget.contrastChanged.connect(contrast_slider.setValue)

            hue_slider = QSlider(Qt.Horizontal)
            hue_slider.setRange(-100, 100)
            hue_slider.setValue(self.videoWidget.hue())
            hue_slider.sliderMoved.connect(self.videoWidget.setHue)
            self.videoWidget.hueChanged.connect(hue_slider.setValue)

            saturation_slider = QSlider(Qt.Horizontal)
            saturation_slider.setRange(-100, 100)
            saturation_slider.setValue(self.videoWidget.saturation())
            saturation_slider.sliderMoved.connect(
                self.videoWidget.setSaturation)
            self.videoWidget.saturationChanged.connect(
                saturation_slider.setValue)

            layout = QFormLayout()
            layout.addRow("Brightness", brightness_slider)
            layout.addRow("Contrast", contrast_slider)
            layout.addRow("Hue", hue_slider)
            layout.addRow("Saturation", saturation_slider)

            button = QPushButton("Close")
            layout.addRow(button)

            self.colorDialog = QDialog(self)
            self.colorDialog.setWindowTitle("Color Options")
            self.colorDialog.setLayout(layout)

            button.clicked.connect(self.colorDialog.close)

        self.colorDialog.show()
コード例 #13
0
ファイル: code.py プロジェクト: Suproq/ylmediapleer
class Player(QWidget):

    fullScreenChanged = pyqtSignal(bool)

    def __init__(self, playlist, parent=None):
        super(Player, self).__init__(parent)

        self.colorDialog = None
        self.trackInfo = ""
        self.statusInfo = ""
        self.duration = 0

        self.player = QMediaPlayer()
        self.playlist = QMediaPlaylist()
        self.player.setPlaylist(self.playlist)

        self.player.durationChanged.connect(self.durationChanged)
        self.player.positionChanged.connect(self.positionChanged)
        self.player.metaDataChanged.connect(self.metaDataChanged)
        self.playlist.currentIndexChanged.connect(self.playlistPositionChanged)
        self.player.mediaStatusChanged.connect(self.statusChanged)
        self.player.bufferStatusChanged.connect(self.bufferingProgress)
        self.player.videoAvailableChanged.connect(self.videoAvailableChanged)
        self.player.error.connect(self.displayErrorMessage)

        self.videoWidget = VideoWidget()
        self.player.setVideoOutput(self.videoWidget)

        self.playlistModel = PlaylistModel()
        self.playlistModel.setPlaylist(self.playlist)

        self.playlistView = QListView()
        self.playlistView.setModel(self.playlistModel)
        self.playlistView.setCurrentIndex(
                self.playlistModel.index(self.playlist.currentIndex(), 0))

        self.playlistView.activated.connect(self.jump)

        self.slider = QSlider(Qt.Horizontal)
        self.slider.setRange(0, self.player.duration() / 1000)

        self.labelDuration = QLabel()
        self.slider.sliderMoved.connect(self.seek)


        openButton = QPushButton("Открыть файл", clicked=self.open)

        controls = PlayerControls()
        controls.setState(self.player.state())
        controls.setVolume(self.player.volume())

        controls.play.connect(self.player.play)
        controls.pause.connect(self.player.pause)
        controls.stop.connect(self.player.stop)
        controls.next.connect(self.playlist.next)
        controls.previous.connect(self.previousClicked)
        controls.changeVolume.connect(self.player.setVolume)
        controls.changeRate.connect(self.player.setPlaybackRate)
        controls.stop.connect(self.videoWidget.update)

        self.player.stateChanged.connect(controls.setState)
        self.player.volumeChanged.connect(controls.setVolume)

        self.fullScreenButton = QPushButton("Полный экран")
        self.fullScreenButton.setCheckable(True)

        displayLayout = QHBoxLayout()
        displayLayout.addWidget(self.videoWidget, 2)
        displayLayout.addWidget(self.playlistView)

        controlLayout = QHBoxLayout()
        controlLayout.setContentsMargins(0, 0, 0, 0)
        controlLayout.addWidget(openButton)
        controlLayout.addStretch(1)
        controlLayout.addWidget(controls)
        controlLayout.addStretch(1)
        controlLayout.addWidget(self.fullScreenButton)

        layout = QVBoxLayout()
        layout.addLayout(displayLayout)
        hLayout = QHBoxLayout()
        hLayout.addWidget(self.slider)
        hLayout.addWidget(self.labelDuration)
        layout.addLayout(hLayout)
        layout.addLayout(controlLayout)

        self.setLayout(layout)

        self.metaDataChanged()

        self.addToPlaylist(playlist)

    def open(self):
        fileNames, _ = QFileDialog.getOpenFileNames(self, "Выбрать файл")
        self.addToPlaylist(fileNames)

    def addToPlaylist(self, fileNames):
        for name in fileNames:
            fileInfo = QFileInfo(name)
            if fileInfo.exists():
                url = QUrl.fromLocalFile(fileInfo.absoluteFilePath())
                if fileInfo.suffix().lower() == 'm3u':
                    self.playlist.load(url)
                else:
                    self.playlist.addMedia(QMediaContent(url))
            else:
                url = QUrl(name)
                if url.isValid():
                    self.playlist.addMedia(QMediaContent(url))

    def durationChanged(self, duration):
        duration /= 1000

        self.duration = duration
        self.slider.setMaximum(duration)

    def positionChanged(self, progress):
        progress /= 1000

        if not self.slider.isSliderDown():
            self.slider.setValue(progress)

        self.updateDurationInfo(progress)

    def metaDataChanged(self):
        if self.player.isMetaDataAvailable():
            self.setTrackInfo("%s - %s" % (
                    self.player.metaData(QMediaMetaData.AlbumArtist),
                    self.player.metaData(QMediaMetaData.Title)))

    def previousClicked(self):
        # Go to the previous track if we are within the first 5 seconds of
        # playback.  Otherwise, seek to the beginning.
        if self.player.position() <= 5000:
            self.playlist.previous()
        else:
            self.player.setPosition(0)

    def jump(self, index):
        if index.isValid():
            self.playlist.setCurrentIndex(index.row())
            self.player.play()

    def playlistPositionChanged(self, position):
        self.playlistView.setCurrentIndex(
                self.playlistModel.index(position, 0))

    def seek(self, seconds):
        self.player.setPosition(seconds * 1000)

    def statusChanged(self, status):
        self.handleCursor(status)

        if status == QMediaPlayer.LoadingMedia:
            self.setStatusInfo("Загрузка...")
        elif status == QMediaPlayer.StalledMedia:
            self.setStatusInfo("Видео стоп")
        elif status == QMediaPlayer.EndOfMedia:
            QApplication.alert(self)
        elif status == QMediaPlayer.InvalidMedia:
            self.displayErrorMessage()
        else:
            self.setStatusInfo("")

    def handleCursor(self, status):
        if status in (QMediaPlayer.LoadingMedia, QMediaPlayer.BufferingMedia, QMediaPlayer.StalledMedia):
            self.setCursor(Qt.BusyCursor)
        else:
            self.unsetCursor()

    def bufferingProgress(self, progress):
        self.setStatusInfo("Буферизация %d%" % progress)

    def videoAvailableChanged(self, available):
        if available:
            self.fullScreenButton.clicked.connect(
                    self.videoWidget.setFullScreen)
            self.videoWidget.fullScreenChanged.connect(
                    self.fullScreenButton.setChecked)

            if self.fullScreenButton.isChecked():
                self.videoWidget.setFullScreen(True)
        else:
            self.fullScreenButton.clicked.disconnect(
                    self.videoWidget.setFullScreen)
            self.videoWidget.fullScreenChanged.disconnect(
                    self.fullScreenButton.setChecked)

            self.videoWidget.setFullScreen(False)


    def setTrackInfo(self, info):
        self.trackInfo = info

        if self.statusInfo != "":
            self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo))
        else:
            self.setWindowTitle(self.trackInfo)

    def setStatusInfo(self, info):
        self.statusInfo = info

        if self.statusInfo != "":
            self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo))
        else:
            self.setWindowTitle(self.trackInfo)

    def displayErrorMessage(self):
        self.setStatusInfo(self.player.errorString())

    def updateDurationInfo(self, currentInfo):
        duration = self.duration
        if currentInfo or duration:
            currentTime = QTime((currentInfo/3600)%60, (currentInfo/60)%60,
                    currentInfo%60, (currentInfo*1000)%1000)
            totalTime = QTime((duration/3600)%60, (duration/60)%60,
                    duration%60, (duration*1000)%1000);

            format = 'hh:mm:ss' if duration > 3600 else 'mm:ss'
            tStr = currentTime.toString(format) + " / " + totalTime.toString(format)
        else:
            tStr = ""

        self.labelDuration.setText(tStr)
コード例 #14
0
class DesktopIconWidget(QFrame):
    def __init__(self, parent):
        QFrame.__init__(self, parent)
        self.setFrameStyle(QFrame.Box | QFrame.Sunken)
        self.setStyleSheet("QListView{background:transparent;}")

        self.listView = QListView(self)
        self.setLayout(QHBoxLayout())
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().addWidget(self.listView)

        self.listView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.listView.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.listView.setMovement(QListView.Snap)
        self.listView.setFlow(QListView.LeftToRight)
        self.listView.setResizeMode(QListView.Adjust)
        self.listView.setGridSize(
            QSize(self.logicalDpiX() / 96 * 70,
                  self.logicalDpiY() / 96 * 70))
        self.listView.setViewMode(QListView.IconMode)

        self.quickDesktopModel = QuickDesktopModel(
            self.window().platform.databaseFile)
        self.listView.setModel(self.quickDesktopModel)
        self.createActions()
        self.makeConnections()

    def createActions(self):
        self.actionCreateComputer = QAction(self.tr("我的电脑(&C)"), self)
        self.actionCreateDocuments = QAction(self.tr("我的文档(&D)"), self)
        self.actionCreateMusic = QAction(self.tr("我的音乐(&M)"), self)
        self.actionCreatePictures = QAction(self.tr("我的图片(&P)"), self)
        self.actionCreateShortcut = QAction(self.tr("创建快捷方式(&C)"), self)
        self.actionCreateShortcut.setIcon(QIcon(":/images/new.png"))
        self.actionCreateBookmark = QAction(self.tr("创建网络链接(&B)"), self)
        self.actionCreateBookmark.setIcon(QIcon(":/images/insert-link.png"))
        self.actionRemoveShortcut = QAction(self.tr("删除快捷方式(&R)"), self)
        self.actionRemoveShortcut.setIcon(QIcon(":/images/delete.png"))
        self.actionRenameShortcut = QAction(self.tr("重命名(&N)"), self)
        self.actionRenameShortcut.setIcon(QIcon(":/images/edit-rename.png"))
        self.actionEditShortcut = QAction(self.tr("编辑快捷方式(&E)"), self)
        self.actionEditShortcut.setIcon(QIcon(":/images/edit.png"))

    def makeConnections(self):
        self.listView.customContextMenuRequested.connect(
            self.onQuickDesktopContextMenuRequest)
        self.listView.activated.connect(self.runQuickDesktopShortcut)

        self.actionCreateComputer.triggered.connect(
            self.createComputerShortcut)
        self.actionCreateDocuments.triggered.connect(
            self.createDocumentsShortcut)
        self.actionCreateMusic.triggered.connect(self.createMusicShortcut)
        self.actionCreatePictures.triggered.connect(
            self.createPicturesShortcut)
        self.actionCreateShortcut.triggered.connect(self.createShortcut)
        self.actionCreateBookmark.triggered.connect(self.createBookmark)
        self.actionEditShortcut.triggered.connect(self.editShortcut)
        self.actionRemoveShortcut.triggered.connect(self.removeShortcut)
        self.actionRenameShortcut.triggered.connect(self.renameShortcut)

    def onQuickDesktopContextMenuRequest(self, pos):
        index = self.listView.indexAt(pos)
        self.listView.setCurrentIndex(index)
        menu = QMenu()
        menu.addAction(self.actionCreateShortcut)
        menu.addAction(self.actionCreateBookmark)
        menu2 = menu.addMenu(self.tr("创建特殊快捷方式(&S)"))
        if os.name == "nt":
            menu2.addAction(self.actionCreateComputer)
        menu2.addAction(self.actionCreateDocuments)
        menu2.addAction(self.actionCreatePictures)
        menu2.addAction(self.actionCreateMusic)
        if index.isValid():
            menu.addAction(self.actionRemoveShortcut)
            if not self.quickDesktopModel.isSpecialShortcut(index):
                menu.addAction(self.actionEditShortcut)
            menu.addAction(self.actionRenameShortcut)
        try:
            getattr(menu, "exec")(QCursor.pos())
        except AttributeError:
            getattr(menu, "exec_")(QCursor.pos())

    def createShortcut(self):
        d = ShortcutDialog(self)
        if self.window().runDialog(d.create) == QDialog.Accepted:
            shortcut = d.getResult()
            shortcut["id"] = str(uuid.uuid4())
            self.quickDesktopModel.addShortcut(shortcut)
        d.deleteLater()

    def createBookmark(self):
        d = BookmarkDialog(self)
        if self.window().runDialog(d.create) == QDialog.Accepted:
            shortcut = {
                "id": str(uuid.uuid4()),
                "icon": "",
                "openwith": "",
                "dir": "",
            }
            shortcut.update(d.getResult())
            self.quickDesktopModel.addShortcut(shortcut)
        d.deleteLater()

    def createComputerShortcut(self):
        shortcut = {
            "id": str(uuid.uuid4()),
            "name": self.tr("我的电脑"),
            "path": COMPUTER_PATH,
            "icon": "",
            "dir": "",
            "openwith": "",
        }
        self.quickDesktopModel.addShortcut(shortcut)

    def createDocumentsShortcut(self):
        shortcut = {
            "id": str(uuid.uuid4()),
            "name": self.tr("我的文档"),
            "path": DOCUMENTS_PATH,
            "icon": "",
            "dir": "",
            "openwith": "",
        }
        self.quickDesktopModel.addShortcut(shortcut)

    def createPicturesShortcut(self):
        shortcut = {
            "id": str(uuid.uuid4()),
            "name": self.tr("图片收藏"),
            "path": PICTURES_PATH,
            "icon": "",
            "dir": "",
            "openwith": "",
        }
        self.quickDesktopModel.addShortcut(shortcut)

    def createMusicShortcut(self):
        shortcut = {
            "id": str(uuid.uuid4()),
            "name": self.tr("我的音乐"),
            "path": MUSIC_PATH,
            "icon": "",
            "dir": "",
            "openwith": "",
        }
        self.quickDesktopModel.addShortcut(shortcut)

    def renameShortcut(self):
        self.listView.edit(self.listView.currentIndex())

    def removeShortcut(self):
        self.quickDesktopModel.removeShortcut(self.listView.currentIndex())

    def editShortcut(self):
        index = self.listView.currentIndex()
        if not index.isValid():
            return
        shortcut = self.quickDesktopModel.shortcutAt(index)
        url = QUrl.fromUserInput(shortcut["path"])
        if not url.isValid():
            return
        if url.scheme() == "special":
            QMessageBox.information(self, self.tr("编辑快捷方式"),
                                    self.tr("不能编辑特殊图标。"))
            return
        elif url.scheme() == "file":
            d = ShortcutDialog(self)
        else:
            d = BookmarkDialog(self)
        if self.window().runDialog(d.edit, shortcut) == QDialog.Accepted:
            shortcut.update(d.getResult())
            self.quickDesktopModel.updateShortcut(shortcut, index)
        d.deleteLater()

    def runQuickDesktopShortcut(self):
        index = self.listView.currentIndex()
        if not index.isValid():
            return
        if not self.quickDesktopModel.runShortcut(index):
            QMessageBox.information(self, self.tr("快捷面板"), \
                    self.tr("不能运行快捷方式。请检查文件是否存在或者程序是否正确。"))
        else:
            self.window().close()
コード例 #15
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)
コード例 #16
0
class IntracranialElectrodeLocator(QMainWindow):
    """Locate electrode contacts using a coregistered MRI and CT."""

    _xy_idx = (
        (1, 2),
        (0, 2),
        (0, 1),
    )

    def __init__(self,
                 info,
                 trans,
                 aligned_ct,
                 subject=None,
                 subjects_dir=None,
                 groups=None,
                 verbose=None):
        """GUI for locating intracranial electrodes.

        .. note:: Images will be displayed using orientation information
                  obtained from the image header. Images will be resampled to
                  dimensions [256, 256, 256] for display.
        """
        # initialize QMainWindow class
        super(IntracranialElectrodeLocator, self).__init__()

        if not info.ch_names:
            raise ValueError('No channels found in `info` to locate')

        # store info for modification
        self._info = info
        self._seeg_idx = pick_types(self._info, meg=False, seeg=True)
        self._verbose = verbose

        # channel plotting default parameters
        self._ch_alpha = 0.5
        self._radius = int(_CH_PLOT_SIZE // 100)  # starting 1/100 of image

        # load imaging data
        self._subject_dir = op.join(subjects_dir, subject)
        self._load_image_data(aligned_ct)

        # initialize channel data
        self._ch_index = 0
        # load data, apply trans
        self._head_mri_t = _get_trans(trans, 'head', 'mri')[0]
        self._mri_head_t = invert_transform(self._head_mri_t)
        # load channels, convert from m to mm
        self._chs = {
            name: apply_trans(self._head_mri_t, ch['loc'][:3]) * 1000
            for name, ch in zip(info.ch_names, info['chs'])
        }
        self._ch_names = list(self._chs.keys())
        # set current position
        if np.isnan(self._chs[self._ch_names[self._ch_index]]).any():
            ras = [0., 0., 0.]
        else:
            ras = self._chs[self._ch_names[self._ch_index]]
        self._set_ras(ras, update_plots=False)
        self._group_channels(groups)

        # GUI design

        # Main plots: make one plot for each view; sagittal, coronal, axial
        plt_grid = QGridLayout()
        plts = [_make_slice_plot(), _make_slice_plot(), _make_slice_plot()]
        self._figs = [plts[0][1], plts[1][1], plts[2][1]]
        plt_grid.addWidget(plts[0][0], 0, 0)
        plt_grid.addWidget(plts[1][0], 0, 1)
        plt_grid.addWidget(plts[2][0], 1, 0)
        self._renderer = _get_renderer(name='IEEG Locator',
                                       size=(400, 400),
                                       bgcolor='w')
        plt_grid.addWidget(self._renderer.plotter)

        # Channel selector
        self._ch_list = QListView()
        self._ch_list.setSelectionMode(Qt.QAbstractItemView.SingleSelection)
        max_ch_name_len = max([len(name) for name in self._chs])
        self._ch_list.setMinimumWidth(max_ch_name_len * _CH_MENU_WIDTH)
        self._ch_list.setMaximumWidth(max_ch_name_len * _CH_MENU_WIDTH)
        self._set_ch_names()

        # Plots
        self._plot_images()

        # Menus
        button_hbox = self._get_button_bar()
        slider_hbox = self._get_slider_bar()
        bottom_hbox = self._get_bottom_bar()

        # Add lines
        self._lines = dict()
        self._lines_2D = dict()
        for group in set(self._groups.values()):
            self._update_lines(group)

        # Put everything together
        plot_ch_hbox = QHBoxLayout()
        plot_ch_hbox.addLayout(plt_grid)
        plot_ch_hbox.addWidget(self._ch_list)

        main_vbox = QVBoxLayout()
        main_vbox.addLayout(button_hbox)
        main_vbox.addLayout(slider_hbox)
        main_vbox.addLayout(plot_ch_hbox)
        main_vbox.addLayout(bottom_hbox)

        central_widget = QWidget()
        central_widget.setLayout(main_vbox)
        self.setCentralWidget(central_widget)

        # ready for user
        self._move_cursors_to_pos()
        self._ch_list.setFocus()  # always focus on list

    def _load_image_data(self, ct):
        """Get MRI and CT data to display and transforms to/from vox/RAS."""
        # allows recon-all not to be finished (T1 made in a few minutes)
        mri_img = 'brain' if op.isfile(
            op.join(self._subject_dir, 'mri', 'brain.mgz')) else 'T1'
        self._mri_data, self._vox_ras_t = _load_image(op.join(
            self._subject_dir, 'mri', f'{mri_img}.mgz'),
                                                      'MRI Image',
                                                      verbose=self._verbose)
        self._ras_vox_t = np.linalg.inv(self._vox_ras_t)

        self._voxel_sizes = np.array(self._mri_data.shape)
        # We need our extents to land the centers of each pixel on the voxel
        # number. This code assumes 1mm isotropic...
        img_delta = 0.5
        self._img_extents = list([
            -img_delta, self._voxel_sizes[idx[0]] -
            img_delta, -img_delta, self._voxel_sizes[idx[1]] - img_delta
        ] for idx in self._xy_idx)
        ch_deltas = list(img_delta * (self._voxel_sizes[ii] / _CH_PLOT_SIZE)
                         for ii in range(3))
        self._ch_extents = list([
            -ch_delta, self._voxel_sizes[idx[0]] -
            ch_delta, -ch_delta, self._voxel_sizes[idx[1]] - ch_delta
        ] for idx, ch_delta in zip(self._xy_idx, ch_deltas))

        # ready ct
        self._ct_data, vox_ras_t = _load_image(ct, 'CT', verbose=self._verbose)
        if self._mri_data.shape != self._ct_data.shape or \
                not np.allclose(self._vox_ras_t, vox_ras_t, rtol=1e-6):
            raise ValueError('CT is not aligned to MRI, got '
                             f'CT shape={self._ct_data.shape}, '
                             f'MRI shape={self._mri_data.shape}, '
                             f'CT affine={vox_ras_t} and '
                             f'MRI affine={self._vox_ras_t}')
        self._ct_maxima = None  # don't compute until turned on

        if op.exists(op.join(self._subject_dir, 'surf', 'lh.seghead')):
            self._head = _read_mri_surface(
                op.join(self._subject_dir, 'surf', 'lh.seghead'))
            assert _frame_to_str[self._head['coord_frame']] == 'mri'
        else:
            warn('`seghead` not found, using marching cubes on CT for '
                 'head plot, use :ref:`mne.bem.make_scalp_surfaces` '
                 'to add the scalp surface instead of skull from the CT')
            self._head = None
        if op.exists(op.join(self._subject_dir, 'surf', 'lh.pial')):
            self._lh = _read_mri_surface(
                op.join(self._subject_dir, 'surf', 'lh.pial'))
            assert _frame_to_str[self._lh['coord_frame']] == 'mri'
            self._rh = _read_mri_surface(
                op.join(self._subject_dir, 'surf', 'rh.pial'))
            assert _frame_to_str[self._rh['coord_frame']] == 'mri'
        else:
            warn('`pial` surface not found, skipping adding to 3D '
                 'plot. This indicates the Freesurfer recon-all '
                 'has not finished or has been modified and '
                 'these files have been deleted.')
            self._lh = self._rh = None

    def _make_ch_image(self, axis, proj=False):
        """Make a plot to display the channel locations."""
        # Make channel data higher resolution so it looks better.
        ch_image = np.zeros((_CH_PLOT_SIZE, _CH_PLOT_SIZE)) * np.nan
        vxyz = self._voxel_sizes

        def color_ch_radius(ch_image, xf, yf, group, radius):
            # Take the fraction across each dimension of the RAS
            # coordinates converted to xyz and put a circle in that
            # position in this larger resolution image
            ex, ey = np.round(np.array([xf, yf]) * _CH_PLOT_SIZE).astype(int)
            ii = np.arange(-radius, radius + 1)
            ii_sq = ii * ii
            idx = np.where(ii_sq + ii_sq[:, np.newaxis] < radius * radius)
            # negative y because y axis is inverted
            ch_image[-(ey + ii[idx[1]]), ex + ii[idx[0]]] = group
            return ch_image

        for name, ras in self._chs.items():
            # move from middle-centered (half coords positive, half negative)
            # to bottom-left corner centered (all coords positive).
            if np.isnan(ras).any():
                continue
            xyz = apply_trans(self._ras_vox_t, ras)
            # check if closest to that voxel
            dist = np.linalg.norm(xyz - self._current_slice)
            if proj or dist < self._radius:
                group = self._groups[name]
                r = self._radius if proj else \
                    self._radius - np.round(abs(dist)).astype(int)
                xf, yf = (xyz / vxyz)[list(self._xy_idx[axis])]
                ch_image = color_ch_radius(ch_image, xf, yf, group, r)
        return ch_image

    @verbose
    def _save_ch_coords(self, info=None, verbose=None):
        """Save the location of the electrode contacts."""
        logger.info('Saving channel positions to `info`')
        if info is None:
            info = self._info
        with info._unlock():
            for name, ch in zip(info.ch_names, info['chs']):
                ch['loc'][:3] = apply_trans(self._mri_head_t,
                                            self._chs[name] / 1000)  # mm->m

    def _plot_images(self):
        """Use the MRI and CT to make plots."""
        # Plot sagittal (0), coronal (1) or axial (2) view
        self._images = dict(ct=list(),
                            chs=list(),
                            ct_bounds=list(),
                            cursor_v=list(),
                            cursor_h=list())
        ct_min, ct_max = np.nanmin(self._ct_data), np.nanmax(self._ct_data)
        text_kwargs = dict(fontsize='medium',
                           weight='bold',
                           color='#66CCEE',
                           family='monospace',
                           ha='center',
                           va='center',
                           path_effects=[
                               patheffects.withStroke(linewidth=4,
                                                      foreground="k",
                                                      alpha=0.75)
                           ])
        xyz = apply_trans(self._ras_vox_t, self._ras)
        for axis in range(3):
            plot_x_idx, plot_y_idx = self._xy_idx[axis]
            fig = self._figs[axis]
            ax = fig.axes[0]
            ct_data = np.take(self._ct_data,
                              self._current_slice[axis],
                              axis=axis).T
            self._images['ct'].append(
                ax.imshow(ct_data,
                          cmap='gray',
                          aspect='auto',
                          zorder=1,
                          vmin=ct_min,
                          vmax=ct_max))
            img_extent = self._img_extents[axis]  # x0, x1, y0, y1
            w, h = np.diff(np.array(img_extent).reshape(2, 2), axis=1)[:, 0]
            self._images['ct_bounds'].append(
                Rectangle(img_extent[::2],
                          w,
                          h,
                          edgecolor='w',
                          facecolor='none',
                          alpha=0.25,
                          lw=0.5,
                          zorder=1.5))
            ax.add_patch(self._images['ct_bounds'][-1])
            self._images['chs'].append(
                ax.imshow(self._make_ch_image(axis),
                          aspect='auto',
                          extent=self._ch_extents[axis],
                          zorder=3,
                          cmap=_CMAP,
                          alpha=self._ch_alpha,
                          vmin=0,
                          vmax=_N_COLORS))
            v_x = (xyz[plot_x_idx], ) * 2
            v_y = img_extent[2:4]
            self._images['cursor_v'].append(
                ax.plot(v_x,
                        v_y,
                        color='lime',
                        linewidth=0.5,
                        alpha=0.5,
                        zorder=8)[0])
            h_y = (xyz[plot_y_idx], ) * 2
            h_x = img_extent[0:2]
            self._images['cursor_h'].append(
                ax.plot(h_x,
                        h_y,
                        color='lime',
                        linewidth=0.5,
                        alpha=0.5,
                        zorder=8)[0])
            # label axes
            self._figs[axis].text(0.5, 0.05, _IMG_LABELS[axis][0],
                                  **text_kwargs)
            self._figs[axis].text(0.05, 0.5, _IMG_LABELS[axis][1],
                                  **text_kwargs)
            self._figs[axis].axes[0].axis(img_extent)
            self._figs[axis].canvas.mpl_connect('scroll_event',
                                                self._on_scroll)
            self._figs[axis].canvas.mpl_connect(
                'button_release_event', partial(self._on_click, axis=axis))
        # add head and brain in mm (convert from m)
        if self._head is None:
            logger.info('Using marching cubes on CT for the '
                        '3D visualization panel')
            rr, tris = _marching_cubes(
                np.where(self._ct_data < np.quantile(self._ct_data, 0.95), 0,
                         1), [1])[0]
            rr = apply_trans(self._vox_ras_t, rr)
            self._renderer.mesh(*rr.T,
                                triangles=tris,
                                color='gray',
                                opacity=0.2,
                                reset_camera=False,
                                render=False)
        else:
            self._renderer.mesh(*self._head['rr'].T * 1000,
                                triangles=self._head['tris'],
                                color='gray',
                                opacity=0.2,
                                reset_camera=False,
                                render=False)
        if self._lh is not None and self._rh is not None:
            self._renderer.mesh(*self._lh['rr'].T * 1000,
                                triangles=self._lh['tris'],
                                color='white',
                                opacity=0.2,
                                reset_camera=False,
                                render=False)
            self._renderer.mesh(*self._rh['rr'].T * 1000,
                                triangles=self._rh['tris'],
                                color='white',
                                opacity=0.2,
                                reset_camera=False,
                                render=False)
        self._3d_chs = dict()
        for name in self._chs:
            self._plot_3d_ch(name)
        self._renderer.set_camera(azimuth=90,
                                  elevation=90,
                                  distance=300,
                                  focalpoint=tuple(self._ras))
        # update plots
        self._draw()
        self._renderer._update()

    def _update_camera(self, render=False):
        """Update the camera position."""
        self._renderer.set_camera(
            # needs fix, distance moves when focal point updates
            distance=self._renderer.plotter.camera.distance * 0.9,
            focalpoint=tuple(self._ras),
            reset_camera=False)

    def _plot_3d_ch(self, name, render=False):
        """Plot a single 3D channel."""
        if name in self._3d_chs:
            self._renderer.plotter.remove_actor(self._3d_chs.pop(name),
                                                render=False)
        if not any(np.isnan(self._chs[name])):
            self._3d_chs[name] = self._renderer.sphere(
                tuple(self._chs[name]),
                scale=1,
                color=_CMAP(self._groups[name])[:3],
                opacity=self._ch_alpha)[0]
            # The actor scale is managed differently than the glyph scale
            # in order not to recreate objects, we use the actor scale
            self._3d_chs[name].SetOrigin(self._chs[name])
            self._3d_chs[name].SetScale(self._radius * _RADIUS_SCALAR)
        if render:
            self._renderer._update()

    def _get_button_bar(self):
        """Make a bar with buttons for user interactions."""
        hbox = QHBoxLayout()

        help_button = QPushButton('Help')
        help_button.released.connect(self._show_help)
        hbox.addWidget(help_button)

        hbox.addStretch(8)

        hbox.addWidget(QLabel('Snap to Center'))
        self._snap_button = QPushButton('Off')
        self._snap_button.setMaximumWidth(25)  # not too big
        hbox.addWidget(self._snap_button)
        self._snap_button.released.connect(self._toggle_snap)
        self._toggle_snap()  # turn on to start

        hbox.addStretch(1)

        self._toggle_brain_button = QPushButton('Show Brain')
        self._toggle_brain_button.released.connect(self._toggle_show_brain)
        hbox.addWidget(self._toggle_brain_button)

        hbox.addStretch(1)

        mark_button = QPushButton('Mark')
        hbox.addWidget(mark_button)
        mark_button.released.connect(self._mark_ch)

        remove_button = QPushButton('Remove')
        hbox.addWidget(remove_button)
        remove_button.released.connect(self._remove_ch)

        self._group_selector = ComboBox()
        group_model = self._group_selector.model()

        for i in range(_N_COLORS):
            self._group_selector.addItem(' ')
            color = QtGui.QColor()
            color.setRgb(*(255 * np.array(_CMAP(i))).round().astype(int))
            brush = QtGui.QBrush(color)
            brush.setStyle(QtCore.Qt.SolidPattern)
            group_model.setData(group_model.index(i, 0), brush,
                                QtCore.Qt.BackgroundRole)
        self._group_selector.clicked.connect(self._select_group)
        self._group_selector.currentIndexChanged.connect(self._select_group)
        hbox.addWidget(self._group_selector)

        # update background color for current selection
        self._update_group()

        return hbox

    def _get_slider_bar(self):
        """Make a bar with sliders on it."""
        def make_label(name):
            label = QLabel(name)
            label.setAlignment(QtCore.Qt.AlignCenter)
            return label

        def make_slider(smin, smax, sval, sfun=None):
            slider = QSlider(QtCore.Qt.Horizontal)
            slider.setMinimum(int(round(smin)))
            slider.setMaximum(int(round(smax)))
            slider.setValue(int(round(sval)))
            slider.setTracking(False)  # only update on release
            if sfun is not None:
                slider.valueChanged.connect(sfun)
            slider.keyPressEvent = self._key_press_event
            return slider

        slider_hbox = QHBoxLayout()

        ch_vbox = QVBoxLayout()
        ch_vbox.addWidget(make_label('ch alpha'))
        ch_vbox.addWidget(make_label('ch radius'))
        slider_hbox.addLayout(ch_vbox)

        ch_slider_vbox = QVBoxLayout()
        self._alpha_slider = make_slider(0, 100, self._ch_alpha * 100,
                                         self._update_ch_alpha)
        ch_plot_max = _CH_PLOT_SIZE // 50  # max 1 / 50 of plot size
        ch_slider_vbox.addWidget(self._alpha_slider)
        self._radius_slider = make_slider(0, ch_plot_max, self._radius,
                                          self._update_radius)
        ch_slider_vbox.addWidget(self._radius_slider)
        slider_hbox.addLayout(ch_slider_vbox)

        ct_vbox = QVBoxLayout()
        ct_vbox.addWidget(make_label('CT min'))
        ct_vbox.addWidget(make_label('CT max'))
        slider_hbox.addLayout(ct_vbox)

        ct_slider_vbox = QVBoxLayout()
        ct_min = int(round(np.nanmin(self._ct_data)))
        ct_max = int(round(np.nanmax(self._ct_data)))
        self._ct_min_slider = make_slider(ct_min, ct_max, ct_min,
                                          self._update_ct_scale)
        ct_slider_vbox.addWidget(self._ct_min_slider)
        self._ct_max_slider = make_slider(ct_min, ct_max, ct_max,
                                          self._update_ct_scale)
        ct_slider_vbox.addWidget(self._ct_max_slider)
        slider_hbox.addLayout(ct_slider_vbox)
        return slider_hbox

    def _get_bottom_bar(self):
        """Make a bar at the bottom with information in it."""
        hbox = QHBoxLayout()

        hbox.addStretch(3)

        self._toggle_show_mip_button = QPushButton('Show Max Intensity Proj')
        self._toggle_show_mip_button.released.connect(self._toggle_show_mip)
        hbox.addWidget(self._toggle_show_mip_button)

        self._toggle_show_max_button = QPushButton('Show Maxima')
        self._toggle_show_max_button.released.connect(self._toggle_show_max)
        hbox.addWidget(self._toggle_show_max_button)

        self._intensity_label = QLabel('')  # update later
        hbox.addWidget(self._intensity_label)

        VOX_label = QLabel('VOX =')
        self._VOX_textbox = QPlainTextEdit('')  # update later
        self._VOX_textbox.setMaximumHeight(25)
        self._VOX_textbox.setMaximumWidth(125)
        self._VOX_textbox.focusOutEvent = self._update_VOX
        self._VOX_textbox.textChanged.connect(self._check_update_VOX)
        hbox.addWidget(VOX_label)
        hbox.addWidget(self._VOX_textbox)

        RAS_label = QLabel('RAS =')
        self._RAS_textbox = QPlainTextEdit('')  # update later
        self._RAS_textbox.setMaximumHeight(25)
        self._RAS_textbox.setMaximumWidth(200)
        self._RAS_textbox.focusOutEvent = self._update_RAS
        self._RAS_textbox.textChanged.connect(self._check_update_RAS)
        hbox.addWidget(RAS_label)
        hbox.addWidget(self._RAS_textbox)
        self._update_moved()  # update text now
        return hbox

    def _group_channels(self, groups):
        """Automatically find a group based on the name of the channel."""
        if groups is not None:
            for name in self._ch_names:
                if name not in groups:
                    raise ValueError(f'{name} not found in ``groups``')
                _validate_type(groups[name], (float, int), f'groups[{name}]')
            self.groups = groups
        else:
            i = 0
            self._groups = dict()
            base_names = dict()
            for name in self._ch_names:
                # strip all numbers from the name
                base_name = ''.join([
                    letter for letter in name
                    if not letter.isdigit() and letter != ' '
                ])
                if base_name in base_names:
                    # look up group number by base name
                    self._groups[name] = base_names[base_name]
                else:
                    self._groups[name] = i
                    base_names[base_name] = i
                    i += 1

    def _update_lines(self, group, only_2D=False):
        """Draw lines that connect the points in a group."""
        if group in self._lines_2D:  # remove existing 2D lines first
            for line in self._lines_2D[group]:
                line.remove()
            self._lines_2D.pop(group)
        if only_2D:  # if not in projection, don't add 2D lines
            if self._toggle_show_mip_button.text() == \
                    'Show Max Intensity Proj':
                return
        elif group in self._lines:  # if updating 3D, remove first
            self._renderer.plotter.remove_actor(self._lines[group],
                                                render=False)
        pos = np.array([
            self._chs[ch] for i, ch in enumerate(self._ch_names)
            if self._groups[ch] == group and i in self._seeg_idx
            and not np.isnan(self._chs[ch]).any()
        ])
        if len(pos) < 2:  # not enough points for line
            return
        # first, the insertion will be the point farthest from the origin
        # brains are a longer posterior-anterior, scale for this (80%)
        insert_idx = np.argmax(
            np.linalg.norm(pos * np.array([1, 0.8, 1]), axis=1))
        # second, find the farthest point from the insertion
        target_idx = np.argmax(np.linalg.norm(pos[insert_idx] - pos, axis=1))
        # third, make a unit vector and to add to the insertion for the bolt
        elec_v = pos[insert_idx] - pos[target_idx]
        elec_v /= np.linalg.norm(elec_v)
        if not only_2D:
            self._lines[group] = self._renderer.tube(
                [pos[target_idx]], [pos[insert_idx] + elec_v * _BOLT_SCALAR],
                radius=self._radius * _TUBE_SCALAR,
                color=_CMAP(group)[:3])[0]
        if self._toggle_show_mip_button.text() == 'Hide Max Intensity Proj':
            # add 2D lines on each slice plot if in max intensity projection
            target_vox = apply_trans(self._ras_vox_t, pos[target_idx])
            insert_vox = apply_trans(self._ras_vox_t,
                                     pos[insert_idx] + elec_v * _BOLT_SCALAR)
            lines_2D = list()
            for axis in range(3):
                x, y = self._xy_idx[axis]
                lines_2D.append(self._figs[axis].axes[0].plot(
                    [target_vox[x], insert_vox[x]],
                    [target_vox[y], insert_vox[y]],
                    color=_CMAP(group),
                    linewidth=0.25,
                    zorder=7)[0])
            self._lines_2D[group] = lines_2D

    def _set_ch_names(self):
        """Add the channel names to the selector."""
        self._ch_list_model = QtGui.QStandardItemModel(self._ch_list)
        for name in self._ch_names:
            self._ch_list_model.appendRow(QtGui.QStandardItem(name))
            self._color_list_item(name=name)
        self._ch_list.setModel(self._ch_list_model)
        self._ch_list.clicked.connect(self._go_to_ch)
        self._ch_list.setCurrentIndex(
            self._ch_list_model.index(self._ch_index, 0))
        self._ch_list.keyPressEvent = self._key_press_event

    def _select_group(self):
        """Change the group label to the selection."""
        group = self._group_selector.currentIndex()
        self._groups[self._ch_names[self._ch_index]] = group
        # color differently if found already
        self._color_list_item(self._ch_names[self._ch_index])
        self._update_group()

    def _update_group(self):
        """Set background for closed group menu."""
        group = self._group_selector.currentIndex()
        rgb = (255 * np.array(_CMAP(group))).round().astype(int)
        self._group_selector.setStyleSheet(
            'background-color: rgb({:d},{:d},{:d})'.format(*rgb))
        self._group_selector.update()

    def _on_scroll(self, event):
        """Process mouse scroll wheel event to zoom."""
        self._zoom(event.step, draw=True)

    def _zoom(self, sign=1, draw=False):
        """Zoom in on the image."""
        delta = _ZOOM_STEP_SIZE * sign
        for axis, fig in enumerate(self._figs):
            xmid = self._images['cursor_v'][axis].get_xdata()[0]
            ymid = self._images['cursor_h'][axis].get_ydata()[0]
            xmin, xmax = fig.axes[0].get_xlim()
            ymin, ymax = fig.axes[0].get_ylim()
            xwidth = (xmax - xmin) / 2 - delta
            ywidth = (ymax - ymin) / 2 - delta
            if xwidth <= 0 or ywidth <= 0:
                return
            fig.axes[0].set_xlim(xmid - xwidth, xmid + xwidth)
            fig.axes[0].set_ylim(ymid - ywidth, ymid + ywidth)
            if draw:
                self._figs[axis].canvas.draw()

    def _update_ch_selection(self):
        """Update which channel is selected."""
        name = self._ch_names[self._ch_index]
        self._ch_list.setCurrentIndex(
            self._ch_list_model.index(self._ch_index, 0))
        self._group_selector.setCurrentIndex(self._groups[name])
        self._update_group()
        if not np.isnan(self._chs[name]).any():
            self._set_ras(self._chs[name])
            self._update_camera(render=True)
            self._draw()

    def _go_to_ch(self, index):
        """Change current channel to the item selected."""
        self._ch_index = index.row()
        self._update_ch_selection()

    @pyqtSlot()
    def _next_ch(self):
        """Increment the current channel selection index."""
        self._ch_index = (self._ch_index + 1) % len(self._ch_names)
        self._update_ch_selection()

    @pyqtSlot()
    def _update_RAS(self, event):
        """Interpret user input to the RAS textbox."""
        text = self._RAS_textbox.toPlainText()
        ras = self._convert_text(text, 'ras')
        if ras is not None:
            self._set_ras(ras)

    @pyqtSlot()
    def _update_VOX(self, event):
        """Interpret user input to the RAS textbox."""
        text = self._VOX_textbox.toPlainText()
        ras = self._convert_text(text, 'vox')
        if ras is not None:
            self._set_ras(ras)

    def _convert_text(self, text, text_kind):
        text = text.replace('\n', '')
        vals = text.split(',')
        if len(vals) != 3:
            vals = text.split(' ')  # spaces also okay as in freesurfer
        vals = [var.lstrip().rstrip() for var in vals]
        try:
            vals = np.array([float(var) for var in vals]).reshape(3)
        except Exception:
            self._update_moved()  # resets RAS label
            return
        if text_kind == 'vox':
            vox = vals
            ras = apply_trans(self._vox_ras_t, vox)
        else:
            assert text_kind == 'ras'
            ras = vals
            vox = apply_trans(self._ras_vox_t, ras)
        wrong_size = any(var < 0 or var > n - 1
                         for var, n in zip(vox, self._voxel_sizes))
        if wrong_size:
            self._update_moved()  # resets RAS label
            return
        return ras

    @property
    def _ras(self):
        return self._ras_safe

    def _set_ras(self, ras, update_plots=True):
        ras = np.asarray(ras, dtype=float)
        assert ras.shape == (3, )
        msg = ', '.join(f'{x:0.2f}' for x in ras)
        logger.debug(f'Trying RAS:  ({msg}) mm')
        # clip to valid
        vox = apply_trans(self._ras_vox_t, ras)
        vox = np.array([
            np.clip(d, 0, self._voxel_sizes[ii] - 1)
            for ii, d in enumerate(vox)
        ])
        # transform back, make write-only
        self._ras_safe = apply_trans(self._vox_ras_t, vox)
        self._ras_safe.flags['WRITEABLE'] = False
        msg = ', '.join(f'{x:0.2f}' for x in self._ras_safe)
        logger.debug(f'Setting RAS: ({msg}) mm')
        if update_plots:
            self._move_cursors_to_pos()

    @property
    def _vox(self):
        return apply_trans(self._ras_vox_t, self._ras)

    @property
    def _current_slice(self):
        return self._vox.round().astype(int)

    @pyqtSlot()
    def _check_update_RAS(self):
        """Check whether the RAS textbox is done being edited."""
        if '\n' in self._RAS_textbox.toPlainText():
            self._update_RAS(event=None)
            self._ch_list.setFocus()  # remove focus from text edit

    @pyqtSlot()
    def _check_update_VOX(self):
        """Check whether the VOX textbox is done being edited."""
        if '\n' in self._VOX_textbox.toPlainText():
            self._update_VOX(event=None)
            self._ch_list.setFocus()  # remove focus from text edit

    def _color_list_item(self, name=None):
        """Color the item in the view list for easy id of marked channels."""
        name = self._ch_names[self._ch_index] if name is None else name
        color = QtGui.QColor('white')
        if not np.isnan(self._chs[name]).any():
            group = self._groups[name]
            color.setRgb(*[int(c * 255) for c in _CMAP(group)])
        brush = QtGui.QBrush(color)
        brush.setStyle(QtCore.Qt.SolidPattern)
        self._ch_list_model.setData(
            self._ch_list_model.index(self._ch_names.index(name), 0), brush,
            QtCore.Qt.BackgroundRole)
        # color text black
        color = QtGui.QColor('black')
        brush = QtGui.QBrush(color)
        brush.setStyle(QtCore.Qt.SolidPattern)
        self._ch_list_model.setData(
            self._ch_list_model.index(self._ch_names.index(name), 0), brush,
            QtCore.Qt.ForegroundRole)

    @pyqtSlot()
    def _toggle_snap(self):
        """Toggle snapping the contact location to the center of mass."""
        if self._snap_button.text() == 'Off':
            self._snap_button.setText('On')
            self._snap_button.setStyleSheet("background-color: green")
        else:  # text == 'On', turn off
            self._snap_button.setText('Off')
            self._snap_button.setStyleSheet("background-color: red")

    @pyqtSlot()
    def _mark_ch(self):
        """Mark the current channel as being located at the crosshair."""
        name = self._ch_names[self._ch_index]
        if self._snap_button.text() == 'Off':
            self._chs[name][:] = self._ras
        else:
            shape = np.mean(self._mri_data.shape)  # Freesurfer shape (256)
            voxels_max = int(4 / 3 * np.pi *
                             (shape * self._radius / _CH_PLOT_SIZE)**3)
            neighbors = _voxel_neighbors(self._vox,
                                         self._ct_data,
                                         thresh=0.5,
                                         voxels_max=voxels_max,
                                         use_relative=True)
            self._chs[name][:] = apply_trans(  # to surface RAS
                self._vox_ras_t,
                np.array(list(neighbors)).mean(axis=0))
        self._color_list_item()
        self._update_lines(self._groups[name])
        self._update_ch_images(draw=True)
        self._plot_3d_ch(name, render=True)
        self._save_ch_coords()
        self._next_ch()
        self._ch_list.setFocus()

    @pyqtSlot()
    def _remove_ch(self):
        """Remove the location data for the current channel."""
        name = self._ch_names[self._ch_index]
        self._chs[name] *= np.nan
        self._color_list_item()
        self._save_ch_coords()
        self._update_lines(self._groups[name])
        self._update_ch_images(draw=True)
        self._plot_3d_ch(name, render=True)
        self._next_ch()
        self._ch_list.setFocus()

    def _draw(self, axis=None):
        """Update the figures with a draw call."""
        for axis in (range(3) if axis is None else [axis]):
            self._figs[axis].canvas.draw()

    def _update_ch_images(self, axis=None, draw=False):
        """Update the channel image(s)."""
        for axis in range(3) if axis is None else [axis]:
            self._images['chs'][axis].set_data(self._make_ch_image(axis))
            if self._toggle_show_mip_button.text() == \
                    'Hide Max Intensity Proj':
                self._images['mip_chs'][axis].set_data(
                    self._make_ch_image(axis, proj=True))
            if draw:
                self._draw(axis)

    def _update_ct_images(self, axis=None, draw=False):
        """Update the CT image(s)."""
        for axis in range(3) if axis is None else [axis]:
            ct_data = np.take(self._ct_data,
                              self._current_slice[axis],
                              axis=axis).T
            # Threshold the CT so only bright objects (electrodes) are visible
            ct_data[ct_data < self._ct_min_slider.value()] = np.nan
            ct_data[ct_data > self._ct_max_slider.value()] = np.nan
            self._images['ct'][axis].set_data(ct_data)
            if 'local_max' in self._images:
                ct_max_data = np.take(self._ct_maxima,
                                      self._current_slice[axis],
                                      axis=axis).T
                self._images['local_max'][axis].set_data(ct_max_data)
            if draw:
                self._draw(axis)

    def _update_mri_images(self, axis=None, draw=False):
        """Update the CT image(s)."""
        if 'mri' in self._images:
            for axis in range(3) if axis is None else [axis]:
                self._images['mri'][axis].set_data(
                    np.take(self._mri_data,
                            self._current_slice[axis],
                            axis=axis).T)
                if draw:
                    self._draw(axis)

    def _update_images(self, axis=None, draw=True):
        """Update CT and channel images when general changes happen."""
        self._update_ct_images(axis=axis)
        self._update_ch_images(axis=axis)
        self._update_mri_images(axis=axis)
        if draw:
            self._draw(axis)

    def _update_ct_scale(self):
        """Update CT min slider value."""
        new_min = self._ct_min_slider.value()
        new_max = self._ct_max_slider.value()
        # handle inversions
        self._ct_min_slider.setValue(min([new_min, new_max]))
        self._ct_max_slider.setValue(max([new_min, new_max]))
        self._update_ct_images(draw=True)

    def _update_radius(self):
        """Update channel plot radius."""
        self._radius = np.round(self._radius_slider.value()).astype(int)
        if self._toggle_show_max_button.text() == 'Hide Maxima':
            self._update_ct_maxima()
            self._update_ct_images()
        else:
            self._ct_maxima = None  # signals ct max is out-of-date
        self._update_ch_images(draw=True)
        for name, actor in self._3d_chs.items():
            if not np.isnan(self._chs[name]).any():
                actor.SetOrigin(self._chs[name])
                actor.SetScale(self._radius * _RADIUS_SCALAR)
        self._renderer._update()
        self._ch_list.setFocus()  # remove focus from 3d plotter

    def _update_ch_alpha(self):
        """Update channel plot alpha."""
        self._ch_alpha = self._alpha_slider.value() / 100
        for axis in range(3):
            self._images['chs'][axis].set_alpha(self._ch_alpha)
        self._draw()
        for actor in self._3d_chs.values():
            actor.GetProperty().SetOpacity(self._ch_alpha)
        self._renderer._update()
        self._ch_list.setFocus()  # remove focus from 3d plotter

    def _move_cursors_to_pos(self):
        """Move the cursors to a position."""
        for axis in range(3):
            x, y = self._vox[list(self._xy_idx[axis])]
            self._images['cursor_v'][axis].set_xdata([x, x])
            self._images['cursor_h'][axis].set_ydata([y, y])
        self._zoom(0)  # doesn't actually zoom just resets view to center
        self._update_images(draw=True)
        self._update_moved()

    def _show_help(self):
        """Show the help menu."""
        QMessageBox.information(
            self, 'Help', "Help:\n'm': mark channel location\n"
            "'r': remove channel location\n"
            "'b': toggle viewing of brain in T1\n"
            "'+'/'-': zoom\nleft/right arrow: left/right\n"
            "up/down arrow: superior/inferior\n"
            "left angle bracket/right angle bracket: anterior/posterior")

    def _update_ct_maxima(self):
        """Compute the maximum voxels based on the current radius."""
        self._ct_maxima = maximum_filter(self._ct_data,
                                         (self._radius, ) * 3) == self._ct_data
        self._ct_maxima[self._ct_data <= np.median(self._ct_data)] = \
            False
        self._ct_maxima = np.where(self._ct_maxima, 1, np.nan)  # transparent

    def _toggle_show_mip(self):
        """Toggle whether the maximum-intensity projection is shown."""
        if self._toggle_show_mip_button.text() == 'Show Max Intensity Proj':
            self._toggle_show_mip_button.setText('Hide Max Intensity Proj')
            self._images['mip'] = list()
            self._images['mip_chs'] = list()
            ct_min, ct_max = np.nanmin(self._ct_data), np.nanmax(self._ct_data)
            for axis in range(3):
                ct_mip_data = np.max(self._ct_data, axis=axis).T
                self._images['mip'].append(self._figs[axis].axes[0].imshow(
                    ct_mip_data,
                    cmap='gray',
                    aspect='auto',
                    vmin=ct_min,
                    vmax=ct_max,
                    zorder=5))
                # add circles for each channel
                xs, ys, colors = list(), list(), list()
                for name, ras in self._chs.items():
                    xyz = self._vox
                    xs.append(xyz[self._xy_idx[axis][0]])
                    ys.append(xyz[self._xy_idx[axis][1]])
                    colors.append(_CMAP(self._groups[name]))
                self._images['mip_chs'].append(self._figs[axis].axes[0].imshow(
                    self._make_ch_image(axis, proj=True),
                    aspect='auto',
                    extent=self._ch_extents[axis],
                    zorder=6,
                    cmap=_CMAP,
                    alpha=1,
                    vmin=0,
                    vmax=_N_COLORS))
            for group in set(self._groups.values()):
                self._update_lines(group, only_2D=True)
        else:
            for img in self._images['mip'] + self._images['mip_chs']:
                img.remove()
            self._images.pop('mip')
            self._images.pop('mip_chs')
            self._toggle_show_mip_button.setText('Show Max Intensity Proj')
            for group in set(self._groups.values()):  # remove lines
                self._update_lines(group, only_2D=True)
        self._draw()

    def _toggle_show_max(self):
        """Toggle whether to color local maxima differently."""
        if self._toggle_show_max_button.text() == 'Show Maxima':
            self._toggle_show_max_button.setText('Hide Maxima')
            # happens on initiation or if the radius is changed with it off
            if self._ct_maxima is None:  # otherwise don't recompute
                self._update_ct_maxima()
            self._images['local_max'] = list()
            for axis in range(3):
                ct_max_data = np.take(self._ct_maxima,
                                      self._current_slice[axis],
                                      axis=axis).T
                self._images['local_max'].append(
                    self._figs[axis].axes[0].imshow(ct_max_data,
                                                    cmap='autumn',
                                                    aspect='auto',
                                                    vmin=0,
                                                    vmax=1,
                                                    zorder=4))
        else:
            for img in self._images['local_max']:
                img.remove()
            self._images.pop('local_max')
            self._toggle_show_max_button.setText('Show Maxima')
        self._draw()

    def _toggle_show_brain(self):
        """Toggle whether the brain/MRI is being shown."""
        if 'mri' in self._images:
            for img in self._images['mri']:
                img.remove()
            self._images.pop('mri')
            self._toggle_brain_button.setText('Show Brain')
        else:
            self._images['mri'] = list()
            for axis in range(3):
                mri_data = np.take(self._mri_data,
                                   self._current_slice[axis],
                                   axis=axis).T
                self._images['mri'].append(self._figs[axis].axes[0].imshow(
                    mri_data, cmap='hot', aspect='auto', alpha=0.25, zorder=2))
            self._toggle_brain_button.setText('Hide Brain')
        self._draw()

    def _key_press_event(self, event):
        """Execute functions when the user presses a key."""
        if event.key() == 'escape':
            self.close()

        if event.text() == 'h':
            self._show_help()

        if event.text() == 'm':
            self._mark_ch()

        if event.text() == 'r':
            self._remove_ch()

        if event.text() == 'b':
            self._toggle_show_brain()

        if event.text() in ('=', '+', '-'):
            self._zoom(sign=-2 * (event.text() == '-') + 1, draw=True)

        # Changing slices
        if event.key() in (QtCore.Qt.Key_Up, QtCore.Qt.Key_Down,
                           QtCore.Qt.Key_Left, QtCore.Qt.Key_Right,
                           QtCore.Qt.Key_Comma, QtCore.Qt.Key_Period,
                           QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown):
            ras = np.array(self._ras)
            if event.key() in (QtCore.Qt.Key_Up, QtCore.Qt.Key_Down):
                ras[2] += 2 * (event.key() == QtCore.Qt.Key_Up) - 1
            elif event.key() in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Right):
                ras[0] += 2 * (event.key() == QtCore.Qt.Key_Right) - 1
            else:
                ras[1] += 2 * (event.key() == QtCore.Qt.Key_PageUp
                               or event.key() == QtCore.Qt.Key_Period) - 1
            self._set_ras(ras)

    def _on_click(self, event, axis):
        """Move to view on MRI and CT on click."""
        if event.inaxes is self._figs[axis].axes[0]:
            # Data coordinates are voxel coordinates
            pos = (event.xdata, event.ydata)
            logger.info(f'Clicked {"XYZ"[axis]} ({axis}) axis at pos {pos}')
            xyz = self._vox
            xyz[list(self._xy_idx[axis])] = pos
            logger.debug(f'Using voxel  {list(xyz)}')
            ras = apply_trans(self._vox_ras_t, xyz)
            self._set_ras(ras)

    def _update_moved(self):
        """Update when cursor position changes."""
        self._RAS_textbox.setPlainText(
            '{:.2f}, {:.2f}, {:.2f}'.format(*self._ras))
        self._VOX_textbox.setPlainText(
            '{:3d}, {:3d}, {:3d}'.format(*self._current_slice))
        self._intensity_label.setText('intensity = {:.2f}'.format(
            self._ct_data[tuple(self._current_slice)]))

    @safe_event
    def closeEvent(self, event):
        """Clean up upon closing the window."""
        self._renderer.plotter.close()
        self.close()
コード例 #17
0
class Player(QWidget):
 
    fullScreenChanged = pyqtSignal(bool)
 
    def __init__(self, playlist, parent=None):
        super(Player, self).__init__(parent)
 
        self.colorDialog = None
        self.trackInfo = ""
        self.statusInfo = ""
        self.duration = 0
 
        self.player = QMediaPlayer()
        self.playlist = QMediaPlaylist()
        self.player.setPlaylist(self.playlist)
 
        self.player.durationChanged.connect(self.durationChanged)
        self.player.positionChanged.connect(self.positionChanged)
        self.player.metaDataChanged.connect(self.metaDataChanged)
        self.playlist.currentIndexChanged.connect(self.playlistPositionChanged)
        self.player.mediaStatusChanged.connect(self.statusChanged)
        self.player.bufferStatusChanged.connect(self.bufferingProgress)
        self.player.videoAvailableChanged.connect(self.videoAvailableChanged)
        self.player.error.connect(self.displayErrorMessage)
 
        self.videoWidget = VideoWidget()
        self.player.setVideoOutput(self.videoWidget)
 
        self.playlistModel = PlaylistModel()
        self.playlistModel.setPlaylist(self.playlist)
 
        self.playlistView = QListView()
        self.playlistView.setModel(self.playlistModel)
        self.playlistView.setCurrentIndex(
                self.playlistModel.index(self.playlist.currentIndex(), 0))
 
        self.playlistView.activated.connect(self.jump)

        self.script_box = QPlainTextEdit()
        self.segmentList = QTreeWidget()
        self.segmentList.setSortingEnabled(True)
        #self.segmentList.setColumnCount(5)
        self.segmentList.setColumnCount(4)
        #self.segmentList.setHeaderLabels(['Product','Start','Label','Tool','Behavior'])
        self.segmentList.setHeaderLabels(['Start segment', 'End segment', 'Label', 'Event'])

        '''
        self.productTextInput = QLineEdit()
        self.startTextInput = QLineEdit()
        self.labelTextInput = QLineEdit()
        self.toolTextInput = QLineEdit()
        self.behaviorTextInput = QLineEdit()
        '''
        self.startTextInput = QLineEdit()
        self.endTextInput = QLineEdit()
        self.labelTextInput = QLineEdit()
        self.contentTextInput = QLineEdit()

        self.addBtn = QPushButton("Add")
        self.addBtn.clicked.connect(self.addSegment)

        self.saveBtn = QPushButton("Save")
        self.saveBtn.clicked.connect(self.saveSegments)

        self.slider = QSlider(Qt.Horizontal)
        self.slider.setRange(0, self.player.duration() / 1000)
 
        self.labelDuration = QLabel()
        self.slider.sliderMoved.connect(self.seek)
 
        self.labelHistogram = QLabel()
        self.labelHistogram.setText("Histogram:")
        self.histogram = HistogramWidget()
        histogramLayout = QHBoxLayout()
        histogramLayout.addWidget(self.labelHistogram)
        histogramLayout.addWidget(self.histogram, 1)
 
        self.probe = QVideoProbe()
        self.probe.videoFrameProbed.connect(self.histogram.processFrame)
        self.probe.setSource(self.player)
 
        openButton = QPushButton("Open", clicked=self.open)
        if os.path.isdir(VIDEO_DIR):
            self.open_folder(VIDEO_DIR)

        controls = PlayerControls()
        controls.setState(self.player.state())
        controls.setVolume(self.player.volume())
        controls.setMuted(controls.isMuted())
 
        controls.play.connect(self.player.play)
        controls.pause.connect(self.player.pause)
        controls.stop.connect(self.player.stop)
        controls.next.connect(self.playlist.next)
        controls.previous.connect(self.previousClicked)
        controls.changeVolume.connect(self.player.setVolume)
        controls.changeMuting.connect(self.player.setMuted)
        controls.changeRate.connect(self.player.setPlaybackRate)
        controls.stop.connect(self.videoWidget.update)
 
        self.player.stateChanged.connect(controls.setState)
        self.player.volumeChanged.connect(controls.setVolume)
        self.player.mutedChanged.connect(controls.setMuted)

        #self.segmentButton = QPushButton("Segment")
        #self.segmentButton.clicked.connect(self.createNewSegment)
        self.startSegmentButton = QPushButton("Start Segment")
        self.startSegmentButton.clicked.connect(self.createNewStartSegment)
        # self.segmentButton.setCheckable(True)

        self.endSegmentButton = QPushButton("End Segment")
        self.endSegmentButton.clicked.connect(self.createNewEndSegment)

        #self.fullScreenButton = QPushButton("FullScreen")
        #self.fullScreenButton.setCheckable(True)
 
        self.colorButton = QPushButton("Color Options...")
        self.colorButton.setEnabled(False)
        self.colorButton.clicked.connect(self.showColorDialog)
 
        displayLayout = QHBoxLayout()
        # videoLayout = QVBoxLayout()
        # videoLayout.addWidget(self.videoWidget)
        # videoLayout.addWidget(self.script_box)
        
        displayLayout.addWidget(self.videoWidget, 3)

        editLayout = QVBoxLayout()
        editLayout.addWidget(self.playlistView, 2)
        #editLayout.addWidget(self.script_box, 4)
        editLayout.addWidget(self.segmentList, 3)
        segmentInputLayout = QHBoxLayout()
        '''
        segmentInputLayout.addWidget(self.productTextInput)
        segmentInputLayout.addWidget(self.startTextInput)
        segmentInputLayout.addWidget(self.labelTextInput)
        segmentInputLayout.addWidget(self.toolTextInput)
        segmentInputLayout.addWidget(self.behaviorTextInput)
        '''
        segmentInputLayout.addWidget(self.startTextInput)
        segmentInputLayout.addWidget(self.endTextInput)
        segmentInputLayout.addWidget(self.labelTextInput)
        segmentInputLayout.addWidget(self.contentTextInput)

        editLayout.addLayout(segmentInputLayout,1)

        displayLayout.addLayout(editLayout, 2)
 
        controlLayout = QHBoxLayout()
        controlLayout.setContentsMargins(0, 0, 0, 0)
        controlLayout.addWidget(openButton)
        controlLayout.addStretch(1)
        controlLayout.addWidget(controls)
        controlLayout.addStretch(1)
        #controlLayout.addWidget(self.segmentButton)
        controlLayout.addWidget(self.startSegmentButton)
        controlLayout.addWidget(self.endSegmentButton)
        controlLayout.addWidget(self.addBtn)
        controlLayout.addWidget(self.saveBtn)
        #controlLayout.addWidget(self.fullScreenButton)
        # controlLayout.addWidget(self.colorButton)
 
        layout = QVBoxLayout()
        layout.addLayout(displayLayout, 2)
        hLayout = QHBoxLayout()
        hLayout.addWidget(self.slider)
        hLayout.addWidget(self.labelDuration)
        layout.addLayout(hLayout)
        layout.addLayout(controlLayout)
        # layout.addLayout(histogramLayout)
 
        self.setLayout(layout)
 
        if not self.player.isAvailable():
            QMessageBox.warning(self, "Service not available",
                    "The QMediaPlayer object does not have a valid service.\n"
                    "Please check the media service plugins are installed.")
 
            controls.setEnabled(False)
            self.playlistView.setEnabled(False)
            openButton.setEnabled(False)
            self.colorButton.setEnabled(False)
            #self.fullScreenButton.setEnabled(False)
 
        self.metaDataChanged()
 
        self.addToPlaylist(playlist)
    
    def open(self):
        fileNames, _ = QFileDialog.getOpenFileNames(self, "Open Files")
        self.addToPlaylist(fileNames)

    def open_folder(self, folder_path):
        fileNames = [folder_path+x for x in os.listdir(folder_path) if x.endswith('.mp4')]
        self.addToPlaylist(fileNames)
 
    def addToPlaylist(self, fileNames):
        for name in fileNames:
            fileInfo = QFileInfo(name)
            if fileInfo.exists():
                url = QUrl.fromLocalFile(fileInfo.absoluteFilePath())
                if fileInfo.suffix().lower() == 'm3u':
                    self.playlist.load(url)
                else:
                    self.playlist.addMedia(QMediaContent(url))
            else:
                url = QUrl(name)
                if url.isValid():
                    self.playlist.addMedia(QMediaContent(url))

    def addSegment(self):
        item = TreeWidgetItem(self.segmentList)
        '''
        item.setText(0, self.productTextInput.text())
        item.setText(1, self.startTextInput.text())
        item.setText(2, self.labelTextInput.text())
        item.setText(3, self.toolTextInput.text())
        item.setText(4, self.behaviorTextInput.text())
        '''

        item.setText(0, self.startTextInput.text())
        item.setText(1, self.endTextInput.text())
        item.setText(2, self.labelTextInput.text())
        item.setText(3, self.contentTextInput.text())

        item.setFlags(item.flags() | Qt.ItemIsEditable)
        self.segmentList.addTopLevelItem(item)
        self.segmentList.sortByColumn(0, Qt.AscendingOrder)

        self.clear_input_boxes()
        self.player.play()
    
    def saveSegments(self):
        itemCnt = self.segmentList.topLevelItemCount()
        colCnt = self.segmentList.columnCount()
        save_dict = {'segments':[]}

        for i in range(itemCnt):
            item = self.segmentList.topLevelItem(i)
            temp_data = []
            for j in range(colCnt):
                temp_data.append(item.text(j))
            #temp_dict = {'product': temp_data[0], 'start': temp_data[1], 'label': temp_data[2], 'tool': temp_data[3], 'behavior': temp_data[4]}

            if len(temp_data[0]) > 0 and len(temp_data[1]) > 0 and (':' in temp_data[0]) and (':' in temp_data[1]):

                start_interval_seconds = 0
                j = 0
                while j < len(temp_data[0].split(':')):
                    start_interval_seconds += (int(temp_data[0].split(':')[- 1 - j]) * (60 ** j))
                    j += 1

                end_interval_seconds = 0
                j = 0
                while j < len(temp_data[1].split(':')):
                    end_interval_seconds += (int(temp_data[1].split(':')[- 1 - j]) * (60 ** j))
                    j += 1

            else:
                start_interval_seconds = ''
                end_interval_seconds = ''

            temp_dict = {'start_segment': start_interval_seconds, 'end_segment': end_interval_seconds, 'label': temp_data[2], 'event': temp_data[3]}
            save_dict['segments'].append(temp_dict)
        
        import json
        file_name = self.playlist.currentMedia().canonicalUrl().fileName()
        with open(SEGMENT_DIR+file_name.replace('.mp4','.json'),'w') as file:
            json.dump(save_dict, file)

    def durationChanged(self, duration):
        duration /= 1000
 
        self.duration = duration
        self.slider.setMaximum(duration)
 
    def positionChanged(self, progress):
        progress /= 1000

        if not self.slider.isSliderDown():
            self.slider.setValue(progress)
 
        self.updateDurationInfo(progress)
 
    def metaDataChanged(self):
        if self.player.isMetaDataAvailable():
            self.setTrackInfo("%s - %s" % (
                    self.player.metaData(QMediaMetaData.AlbumArtist),
                    self.player.metaData(QMediaMetaData.Title)))
 
    def previousClicked(self):
        # Go to the previous track if we are within the first 5 seconds of
        # playback.  Otherwise, seek to the beginning.
        if self.player.position() <= 5000:
            self.playlist.previous()
        else:
            self.player.setPosition(0)
    
    def clear_input_boxes(self):
        '''
        self.productTextInput.clear()
        self.startTextInput.clear()
        self.labelTextInput.clear()
        self.toolTextInput.clear()
        self.behaviorTextInput.clear()
        '''
        self.startTextInput.clear()
        self.endTextInput.clear()
        self.labelTextInput.clear()
        self.contentTextInput.clear()


    def jump(self, index):
        if index.isValid():
            self.playlist.setCurrentIndex(index.row())
            self.player.play()
            file_name = self.playlist.currentMedia().canonicalUrl().fileName()
            '''
            script_file_name = file_name.replace('.mp4','.txt')
            if os.path.isfile(SCRIPT_DIR+script_file_name):
                text=open(SCRIPT_DIR+script_file_name).read()
                self.script_box.setPlainText(text)
            '''

            segment_file_path = SEGMENT_DIR + file_name.replace('.mp4','.json')
            json_dict = self.open_json(segment_file_path)
            self.clear_input_boxes()
            self.segmentList.clear()
            for segment in json_dict["segments"]:
                item = TreeWidgetItem(self.segmentList)
                '''
                item.setText(0, segment['product'])
                item.setText(1, str(segment['start']))
                item.setText(2, segment['label'])
                item.setText(3, segment['tool'])
                item.setText(4, segment['behavior'])
                '''
                item.setText(0, segment['start_segment'])
                item.setText(1, segment['end_segment'])
                item.setText(2, segment['label'])
                item.setText(3, segment['content'])

                item.setFlags(item.flags() | Qt.ItemIsEditable)
                self.segmentList.addTopLevelItem(item)
                
            
            
            # print([str(x.text()) for x in self.segmentList.currentItem()])

    def open_json(self, file_path):
        import json
        try:
            with open(file_path, 'r') as file:
                json_dict = json.loads(file.read())
        except:
            json_dict = {"segments":[]}
            # json_dict = {"segments":[{"product":"Sorry","start":"File not found.","label":"","tool":"","behavior":""}]}
        return json_dict
    
    def playlistPositionChanged(self, position):
        self.playlistView.setCurrentIndex(
                self.playlistModel.index(position, 0))
 
    def seek(self, seconds):
        self.player.setPosition(seconds * 1000)
 
    def statusChanged(self, status):
        self.handleCursor(status)
 
        if status == QMediaPlayer.LoadingMedia:
            self.setStatusInfo("Loading...")
        elif status == QMediaPlayer.StalledMedia:
            self.setStatusInfo("Media Stalled")
        elif status == QMediaPlayer.EndOfMedia:
            QApplication.alert(self)
        elif status == QMediaPlayer.InvalidMedia:
            self.displayErrorMessage()
        else:
            self.setStatusInfo("")
 
    def handleCursor(self, status):
        if status in (QMediaPlayer.LoadingMedia, QMediaPlayer.BufferingMedia, QMediaPlayer.StalledMedia):
            self.setCursor(Qt.BusyCursor)
        else:
            self.unsetCursor()
 
    def bufferingProgress(self, progress):
        self.setStatusInfo("Buffering %d%" % progress)
 
    def videoAvailableChanged(self, available):
        '''
        if available:
            self.fullScreenButton.clicked.connect(
                    self.videoWidget.setFullScreen)
            self.videoWidget.fullScreenChanged.connect(
                    self.fullScreenButton.setChecked)
 
            if self.fullScreenButton.isChecked():
                self.videoWidget.setFullScreen(True)
        else:
            self.fullScreenButton.clicked.disconnect(
                    self.videoWidget.setFullScreen)
            self.videoWidget.fullScreenChanged.disconnect(
                    self.fullScreenButton.setChecked)
 
            self.videoWidget.setFullScreen(False)

        '''
        self.colorButton.setEnabled(available)
 
    def setTrackInfo(self, info):
        self.trackInfo = info
 
        if self.statusInfo != "":
            self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo))
        else:
            self.setWindowTitle(self.trackInfo)
 
    def setStatusInfo(self, info):
        self.statusInfo = info
 
        if self.statusInfo != "":
            self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo))
        else:
            self.setWindowTitle(self.trackInfo)
 
    def displayErrorMessage(self):
        self.setStatusInfo(self.player.errorString())
 
    def updateDurationInfo(self, currentInfo):
        duration = self.duration
        if currentInfo or duration:
            currentTime = QTime((currentInfo/3600)%60, (currentInfo/60)%60,
                    currentInfo%60, (currentInfo*1000)%1000)
            totalTime = QTime((duration/3600)%60, (duration/60)%60,
                    duration%60, (duration*1000)%1000);
 
            format = 'hh:mm:ss' if duration > 3600 else 'mm:ss'
            tStr = currentTime.toString(format) + " / " + totalTime.toString(format)
        else:
            tStr = ""
 
        self.labelDuration.setText(tStr)
    '''
    def createNewSegment(self):
        self.startTextInput.setText(str(int(self.player.position()/1000)))
    '''

    def createNewStartSegment(self):
        seconds = int(self.player.position()/1000)
        self.startTextInput.setText("{:02d}".format(math.floor(seconds / 3600)) + ':' + "{:02d}".format(
            math.floor((seconds / 60)) - math.floor(seconds / 3600) * 60) + ':' + "{:02d}".format(seconds % 60))

    def createNewEndSegment(self):
        seconds = int(self.player.position() / 1000)
        self.endTextInput.setText("{:02d}".format(math.floor(seconds / 3600)) + ':' + "{:02d}".format(
            math.floor((seconds / 60)) - math.floor(seconds / 3600) * 60) + ':' + "{:02d}".format(seconds % 60))
        self.player.pause()

    def showColorDialog(self):
        if self.colorDialog is None:
            brightnessSlider = QSlider(Qt.Horizontal)
            brightnessSlider.setRange(-100, 100)
            brightnessSlider.setValue(self.videoWidget.brightness())
            brightnessSlider.sliderMoved.connect(
                    self.videoWidget.setBrightness)
            self.videoWidget.brightnessChanged.connect(
                    brightnessSlider.setValue)
 
            contrastSlider = QSlider(Qt.Horizontal)
            contrastSlider.setRange(-100, 100)
            contrastSlider.setValue(self.videoWidget.contrast())
            contrastSlider.sliderMoved.connect(self.videoWidget.setContrast)
            self.videoWidget.contrastChanged.connect(contrastSlider.setValue)
 
            hueSlider = QSlider(Qt.Horizontal)
            hueSlider.setRange(-100, 100)
            hueSlider.setValue(self.videoWidget.hue())
            hueSlider.sliderMoved.connect(self.videoWidget.setHue)
            self.videoWidget.hueChanged.connect(hueSlider.setValue)
 
            saturationSlider = QSlider(Qt.Horizontal)
            saturationSlider.setRange(-100, 100)
            saturationSlider.setValue(self.videoWidget.saturation())
            saturationSlider.sliderMoved.connect(
                    self.videoWidget.setSaturation)
            self.videoWidget.saturationChanged.connect(
                    saturationSlider.setValue)
 
            layout = QFormLayout()
            layout.addRow("Brightness", brightnessSlider)
            layout.addRow("Contrast", contrastSlider)
            layout.addRow("Hue", hueSlider)
            layout.addRow("Saturation", saturationSlider)
 
            button = QPushButton("Close")
            layout.addRow(button)
 
            self.colorDialog = QDialog(self)
            self.colorDialog.setWindowTitle("Color Options")
            self.colorDialog.setLayout(layout)
 
            button.clicked.connect(self.colorDialog.close)
 
        self.colorDialog.show()
コード例 #18
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()
コード例 #19
0
ファイル: app.py プロジェクト: XYlearn/Code-in-HIT
class App(QMainWindow):
    def __init__(self):
        super().__init__()
        # self.gfont = FontProperties(fname='assets/PingFang.ttc')
        self.accuser = Accuser()
        self.dbconn = DBConn()
        self.scheduler = Scheduler(self.dbconn)
        self.scheduler_status = False
        self.automaton = ReAutomaton()
        self.analyzer = anal.Analyzer(self.dbconn)
        self.init_ui()

    def closeEvent(self, a0):
        self.scheduler.browser.quit()
        return super().closeEvent(a0)

    def init_ui(self):
        self.setWindowTitle("Weibo Monitor")
        self.setGeometry(30, 30, 1024, 720)
        self.init_components()
        self.show()

    def init_components(self):
        self.gwidget = QTabWidget()
        self.setCentralWidget(self.gwidget)
        self.glayout = QHBoxLayout()
        self.crawl_widget = QWidget()
        self.ana_widget = QWidget()
        self.init_crawl_widget()
        self.init_ana_widget()
        self.gwidget.addTab(self.crawl_widget, "Crawl")
        self.gwidget.addTab(self.ana_widget, "Analyze")
        self.init_triggers()
        self.running = False

    def init_crawl_widget(self):
        self.llayout = QVBoxLayout()
        self.crawl_widget.setLayout(self.llayout)
        self.log_label = QLabel("Log")
        self.log_list = QTableView()
        self.log_model = QStandardItemModel()
        self.log_model.setColumnCount(3)
        self.log_model.setHorizontalHeaderLabels(["Status", "Time", "Message"])
        self.log_list.setModel(self.log_model)
        self.log_list.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.log_list.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.ResizeToContents)
        self.log_list.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.ResizeToContents)
        self.log_list.horizontalHeader().setSectionResizeMode(
            2, QHeaderView.ResizeToContents)
        log_widget = QWidget()
        log_layout = QVBoxLayout()
        log_layout.addWidget(self.log_label)
        log_layout.addWidget(self.log_list)
        log_widget.setLayout(log_layout)
        self.rule_label = QLabel("Rule")
        self.rule_edit = QTextEdit()
        rule_widget = QWidget()
        rule_layout = QVBoxLayout()
        rule_layout.addWidget(self.rule_label)
        rule_layout.addWidget(self.rule_edit)
        rule_widget.setLayout(rule_layout)
        self.crawl_button = QPushButton('Crawl')
        self.llayout.addWidget(log_widget)
        self.llayout.addWidget(rule_widget)
        self.llayout.addWidget(self.crawl_button)
        self.llayout.setStretch(0, 15)
        self.llayout.setStretch(1, 6)
        self.llayout.setStretch(2, 1)

    def init_ana_widget(self):
        self.rlayout = QHBoxLayout()
        self.ana_widget.setLayout(self.rlayout)
        self.fig = Figure()
        self.canvas = FigureCanvas(self.fig)
        self.canvas_label = QLabel("Result")
        canvas_widget = QWidget()
        canvas_layout = QVBoxLayout()
        canvas_widget.setLayout(canvas_layout)
        canvas_layout.addWidget(self.canvas_label)
        canvas_layout.addWidget(self.canvas)
        canvas_layout.setStretch(0, 1)
        canvas_layout.setStretch(1, 20)

        self.tracklist_panel = QWidget()
        self.tracklist_layout = QVBoxLayout()
        self.tracklist_panel.setLayout(self.tracklist_layout)
        self.init_topic_tracklist()
        self.init_weibo_tracklist()
        self.tracklist_layout.addWidget(self.topic_tracklist_widget)
        self.tracklist_layout.addWidget(self.weibo_tracklist_widget)
        self.rlayout.addWidget(self.tracklist_panel)
        self.rlayout.addWidget(canvas_widget)
        cfg = self.scheduler.load_config()
        for topic_name in cfg['topic_tracklist']:
            self.topic_tracklist_model.appendRow(QStandardItem(topic_name))
        for weibo_id in cfg["weibo_id_tracklist"]:
            self.weibo_tracklist_model.appendRow(QStandardItem(str(weibo_id)))

    def init_topic_tracklist(self):
        self.topic_tracklist = QListView()
        self.topic_tracklist_label = QLabel("Topic Track List")
        self.topic_tracklist_model = QStandardItemModel()
        self.topic_tracklist.setModel(self.topic_tracklist_model)
        topic_tracklist_buttons = QWidget()
        topic_tracklist_buttons_layout = QHBoxLayout()
        topic_tracklist_buttons_layout.setSpacing(0)
        topic_tracklist_buttons_layout.setContentsMargins(0, 0, 0, 0)
        topic_tracklist_buttons.setLayout(topic_tracklist_buttons_layout)
        self.topic_tracklist_add_button = QPushButton("+")
        self.topic_tracklist_del_button = QPushButton("-")
        self.topic_tracklist_conf_button = QPushButton("confirm")
        self.topic_analyze_button = QPushButton("analyze")
        topic_tracklist_buttons_layout.addWidget(
            self.topic_tracklist_add_button)
        topic_tracklist_buttons_layout.addWidget(
            self.topic_tracklist_del_button)
        topic_tracklist_buttons_layout.addStretch(20)
        topic_tracklist_buttons_layout.addWidget(
            self.topic_tracklist_conf_button)
        topic_tracklist_buttons_layout.addWidget(self.topic_analyze_button)
        topic_tracklist_buttons_layout.setStretch(0, 6)
        topic_tracklist_buttons_layout.setStretch(1, 6)
        topic_tracklist_buttons_layout.setStretch(2, 6)
        topic_tracklist_buttons_layout.setStretch(3, 6)
        self.topic_tracklist_widget = QWidget()
        topic_tracklist_layout = QVBoxLayout()
        self.topic_tracklist_widget.setLayout(topic_tracklist_layout)
        topic_tracklist_layout.addWidget(self.topic_tracklist_label)
        topic_tracklist_layout.addWidget(self.topic_tracklist)
        topic_tracklist_layout.addWidget(topic_tracklist_buttons)

    def init_weibo_tracklist(self):
        self.weibo_tracklist = QListView()
        self.weibo_tracklist_label = QLabel("Weibo Track List")
        self.weibo_tracklist_model = QStandardItemModel()
        self.weibo_tracklist.setModel(self.weibo_tracklist_model)
        weibo_tracklist_buttons = QWidget()
        weibo_tracklist_buttons_layout = QHBoxLayout()
        weibo_tracklist_buttons_layout.setSpacing(0)
        weibo_tracklist_buttons_layout.setContentsMargins(0, 0, 0, 0)
        weibo_tracklist_buttons.setLayout(weibo_tracklist_buttons_layout)
        self.weibo_tracklist_add_button = QPushButton("+")
        self.weibo_tracklist_del_button = QPushButton("-")
        self.weibo_tracklist_conf_button = QPushButton("confirm")
        self.weibo_analyze_button = QPushButton("analyze")
        weibo_tracklist_buttons_layout.addWidget(
            self.weibo_tracklist_add_button)
        weibo_tracklist_buttons_layout.addWidget(
            self.weibo_tracklist_del_button)
        weibo_tracklist_buttons_layout.addStretch(20)
        weibo_tracklist_buttons_layout.addWidget(
            self.weibo_tracklist_conf_button)
        weibo_tracklist_buttons_layout.addWidget(self.weibo_analyze_button)
        weibo_tracklist_buttons_layout.setStretch(0, 6)
        weibo_tracklist_buttons_layout.setStretch(1, 6)
        weibo_tracklist_buttons_layout.setStretch(2, 6)
        weibo_tracklist_buttons_layout.setStretch(3, 6)

        self.weibo_sentiment_checkbox = QCheckBox("Comment Sentimnet")
        weibo_analyze_options = QWidget()
        weibo_analyze_options_layout = QHBoxLayout()
        weibo_analyze_options.setLayout(weibo_analyze_options_layout)
        weibo_analyze_options_layout.addWidget(self.weibo_sentiment_checkbox)

        self.weibo_tracklist_widget = QWidget()
        weibo_tracklist_layout = QVBoxLayout()
        self.weibo_tracklist_widget.setLayout(weibo_tracklist_layout)
        weibo_tracklist_layout.addWidget(self.weibo_tracklist_label)
        weibo_tracklist_layout.addWidget(self.weibo_tracklist)
        weibo_tracklist_layout.addWidget(weibo_tracklist_buttons)
        weibo_tracklist_layout.addWidget(weibo_analyze_options)

    def init_triggers(self):
        self.crawl_button.clicked.connect(self.crawl_button_clicked)
        self.topic_tracklist_add_button.clicked.connect(
            self.topic_add_button_clicked)
        self.topic_tracklist_del_button.clicked.connect(
            self.topic_del_button_clicked)
        self.topic_tracklist_conf_button.clicked.connect(
            self.topic_conf_button_clicked)
        self.topic_analyze_button.clicked.connect(
            self.topic_analyze_button_clicked)
        self.weibo_tracklist_add_button.clicked.connect(
            self.weibo_add_button_clicked)
        self.weibo_tracklist_del_button.clicked.connect(
            self.weibo_del_button_clicked)
        self.weibo_tracklist_conf_button.clicked.connect(
            self.weibo_conf_button_clicked)
        self.weibo_analyze_button.clicked.connect(
            self.weibo_analyze_button_clicked)

    @pyqtSlot()
    def crawl_button_clicked(self):
        rule_text = self.rule_edit.toPlainText().strip()
        if rule_text:
            try:
                rule = eval(rule_text)
                assert isinstance(rule, ReRule)
            except Exception as ex:
                QMessageBox.information(self, "Invalid Rule Syntax",
                                        '\n'.join(ex.args))
                return
            self.automaton.set_rule(rule)
        if self.scheduler_status:
            self.log("Restart Crawlers", "INFO")
        else:
            self.scheduler_status = True
            self.crawl_button.setText("Restart Crawler")
            self.log("Start Crawlers", "INFO")
        self.scheduler.terminate()
        self.scheduler.wait()
        self.scheduler.set_weibo_handler(self.handle_weibo)
        self.scheduler.set_topic_handler(self.handle_topic)
        self.scheduler.start()

    @pyqtSlot()
    def topic_analyze_button_clicked(self):
        indexes = self.topic_tracklist.selectedIndexes()
        if not indexes:
            return
        index = indexes[0]
        topic_name = self.topic_tracklist_model.item(index.row()).text()

        if not topic_name:
            return
        topic = self.analyzer.get_topic(topic_name)
        if not topic:
            return
        topic_heat = self.analyzer.get_topic_heat(topic)
        self.fig.clear()
        ax = self.fig.add_subplot(111)
        times = list(map(lambda x: x[0], topic_heat))
        heats = list(map(lambda x: x[1], topic_heat))
        ax.plot(times, heats)
        # ax.set_title(topic_name, fontproperties=self.gfont)
        self.fig.autofmt_xdate()
        self.canvas.draw()

    @pyqtSlot()
    def weibo_analyze_button_clicked(self):
        indexes = self.weibo_tracklist.selectedIndexes()
        if not indexes:
            return
        index = indexes[0]
        weibo_id = self.weibo_tracklist_model.item(index.row()).text()
        if not weibo_id:
            return
        weibo_id = int(weibo_id)

        weibo = self.analyzer.get_weibo(weibo_id)
        if not weibo:
            return
        weibo_heat = self.analyzer.get_weibo_heat(weibo)
        self.fig.clear()
        ax = self.fig.add_subplot(111)
        times = list(map(lambda x: x[0], weibo_heat))
        heats = list(map(lambda x: x[1], weibo_heat))
        ax.plot(times, heats)
        topics = self.analyzer.get_weibo_topics(weibo_id)
        sentiment = self.analyzer.get_sentiment(weibo['text'])
        # ax.set_title('#'.join(topics) + "--Sentiment: {:.3f}".format(sentiment), fontproperties=self.gfont)
        self.fig.autofmt_xdate()
        self.canvas.draw()

    @pyqtSlot()
    def topic_add_button_clicked(self):
        item = QStandardItem("")
        self.topic_tracklist_model.appendRow(item)
        self.topic_tracklist.setCurrentIndex(item.index())

    @pyqtSlot()
    def topic_del_button_clicked(self):
        index = self.topic_tracklist.selectedIndexes()[0]
        self.topic_tracklist_model.removeRow(index.row())
        if self.topic_tracklist_model.rowCount():
            self.topic_tracklist.setCurrentIndex(
                self.topic_tracklist_model.index(0, 0))

    @pyqtSlot()
    def topic_conf_button_clicked(self):
        topics = [
            self.topic_tracklist_model.item(idx).text()
            for idx in range(self.topic_tracklist_model.rowCount())
        ]
        topic_tracklist = ','.join(topics)
        self.scheduler.config.set("TopicSpider", "topic_tracklist",
                                  topic_tracklist)
        with open("config.ini", "w") as fp:
            self.scheduler.config.write(fp)

    @pyqtSlot()
    def weibo_add_button_clicked(self):
        item = QStandardItem("")
        self.weibo_tracklist_model.appendRow(item)
        self.weibo_tracklist.setCurrentIndex(item.index())

    @pyqtSlot()
    def weibo_del_button_clicked(self):
        index = self.weibo_tracklist.selectedIndexes()[0]
        self.weibo_tracklist_model.removeRow(index.row())
        if self.weibo_tracklist_model.rowCount():
            self.weibo_tracklist.setCurrentIndex(
                self.weibo_tracklist_model.index(0, 0))

    @pyqtSlot()
    def weibo_conf_button_clicked(self):
        weibo_ids = [
            self.weibo_tracklist_model.item(idx).text()
            for idx in range(self.weibo_tracklist_model.rowCount())
        ]
        weibo_tracklist = ','.join(weibo_ids)
        self.scheduler.config.set("WeiboSpider", "weibo_id_tracklist",
                                  weibo_tracklist)
        with open("config.ini", "w") as fp:
            self.scheduler.config.write(fp)

    def handle_weibo(self, weibo):
        self.dbconn.process_weibo(weibo)
        if self.automaton.match(weibo.text):
            msg = "Matched Weibo {}: {}".format(weibo.weibo_id,
                                                repr(weibo.text))
            self.log(msg, "WARNING")
            self.handle_matched_weibo(weibo)
        else:
            msg = "Process Weibo {}".format(weibo.weibo_id)
            self.log(msg, "NORMAL")

    def handle_matched_weibo(self, weibo):
        reason = '匹配敏感关键字规则\n' + self.rule_edit.toPlainText()
        if self.accuser.accuse_weibo(weibo, reason):
            msg = "Accuse weibo {}".format(weibo.weibo_id)
            self.log(msg, "INFO")

    def handle_topic(self, topic):
        self.dbconn.process_topic(topic)
        msg = "Process Topic {}".format(repr(topic.name))
        self.log(msg, "NORMAL")

    def log(self, msg, status_str="INFO"):
        time_str = datetime.now().strftime("%Y-%m-%d-%H:%M:%S")
        self.log_model.appendRow([
            QStandardItem(status_str),
            QStandardItem(time_str),
            QStandardItem(msg)
        ])
コード例 #20
0
class MatchViewGroup(QGroupBox):
    def __init__(self, ui_template):
        super(MatchViewGroup, self).__init__()
        """
        Import settings
        """
        self.ui_template = ui_template
        self.settings = import_settings()

        self.current_match = Match()

        self.setTitle('Match View')
        """
        Layout
        """
        self.grid_layout = QGridLayout()
        self.setLayout(self.grid_layout)
        self.grid_layout.setColumnStretch(1, 1)
        self.grid_layout.setColumnStretch(2, 1)
        self.grid_layout.setColumnStretch(3, 1)
        self.grid_layout.setRowStretch(1, 1)
        """
        Rounds list
        """
        self.rounds_list = QListView()
        self.rounds_list_model = QStandardItemModel()
        self.rounds_list.setModel(self.rounds_list_model)
        self.rounds_list.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.grid_layout.addWidget(self.rounds_list, 0, 0, 2, 1)
        """
        End game state info
        """
        self.end_game_state_info_group = QGroupBox()
        self.end_game_state_info_layout = QVBoxLayout()
        self.end_game_state_info_group.setLayout(
            self.end_game_state_info_layout)
        self.end_game_state_info_group.setTitle('Results')
        self.grid_layout.addWidget(self.end_game_state_info_group, 2, 0, 2, 1)
        # Results
        self.match_seed_label = QLabel()
        self.end_game_state_info_layout.addWidget(self.match_seed_label)
        self.match_winner_label = QLabel()
        self.end_game_state_info_layout.addWidget(self.match_winner_label)
        self.player_A_results_label = QLabel()
        self.end_game_state_info_layout.addWidget(self.player_A_results_label)
        self.player_B_results_label = QLabel()
        self.end_game_state_info_layout.addWidget(self.player_B_results_label)
        self.referee_messages_label = QLabel()
        self.referee_messages_label.setText('Referee Messages')
        self.end_game_state_info_layout.addWidget(self.referee_messages_label)
        self.referee_messages_text = QTextBrowser()
        self.end_game_state_info_layout.addWidget(self.referee_messages_text)

        # Map view
        self.map_view = QGraphicsView()
        self.map_scene = QGraphicsScene()
        self.map_view.setScene(self.map_scene)
        self.grid_layout.addWidget(self.map_view, 1, 1, 2, 2)

        # Add groups and field labels to the match view
        self.groups = {}
        self.populate_fields(self.ui_template, self.grid_layout)

    def populate_fields(self, template, layout, i=0):
        groups = template['groups']
        for group_ in groups:
            group = QGroupBox()
            group.setTitle(group_['title'])
            layout_ = QGridLayout()
            layout_.setSpacing(0)
            group.setLayout(layout_)
            self.groups[group_['name']] = group
            coordinates = [i, 0]
            span = [1, 1]
            if 'row' in group_ and 'col' in group_:
                coordinates = [group_['row'], group_['col']]
            if 'colSpan' in group_ or 'rowSpan' in group_:
                span = [group_.get('rowSpan', 1), group_.get('colSpan', 1)]
            layout.addWidget(group, *coordinates, *span)
            group.fields = {}
            if 'fields' in group_:
                for index, field_ in enumerate(group_['fields']):
                    if field_['name'] == 'worm':
                        field = QLabel()
                        field.setPixmap(QPixmap(field_['resource']))
                        layout_.addWidget(field, index - 1, 0)
                    else:
                        field_label = QLabel()
                        field_label.setText(field_['label'])
                        field = QLabel()
                        if field_['label'] != '':
                            group.fields[field_['name']] = field
                            layout_.addWidget(field_label, index, 0)
                            layout_.addWidget(field, index, 1)
                        else:
                            group.fields[field_['name']] = field
                            layout_.addWidget(field, index, 1)
            if 'groups' in group_:
                self.populate_fields(group_, layout_, index + 1)

    def match_selected(self, *args):
        match = args[0].indexes()[0].data()
        match_logs_directory = self.settings.value('match_logs_directory')
        directory = os.path.join(match_logs_directory, match)
        self.map_scene.clear()
        if os.path.isdir(directory):
            self.load_match(directory)

    def round_selected(self, *args):
        index_array = args[0].indexes()
        if len(index_array):
            index = index_array[0]
            self.current_match.set_current_round(index.data())
            self.update_fields()
            self.draw_map()

    def draw_map(self):
        cells = self.current_match.current_round.state['map']
        self.map_scene.clear()
        self.map_scene.setSceneRect(
            0, 0, self.current_match.state['mapSize'] * PIXEL_SIZE,
            self.current_match.state['mapSize'] * PIXEL_SIZE)
        for row in cells:
            for cell in row:
                pic = QPixmap(IMAGE[cell['type']])
                item = self.map_scene.addPixmap(
                    pic.scaled(PIXEL_SIZE, PIXEL_SIZE))
                item.setPos(cell['x'] * PIXEL_SIZE, cell['y'] * PIXEL_SIZE)
                if 'occupier' in cell:
                    if cell['occupier']['playerId'] == 1:
                        pic = QPixmap(':resources/icons/map/wormA.png')
                    else:
                        pic = QPixmap(':resources/icons/map/wormB.png')
                    item = self.map_scene.addPixmap(
                        pic.scaled(PIXEL_SIZE, PIXEL_SIZE))
                    item.setPos(cell['x'] * PIXEL_SIZE, cell['y'] * PIXEL_SIZE)
                    health_label = QLabel()
                    font = QFont()
                    font.setBold(True)
                    health_label.setFont(font)
                    health_label.setText(
                        str(cell['occupier']['id']) + ' - ' +
                        str(cell['occupier']['health']))
                    item = self.map_scene.addWidget(health_label)
                    item.setPos((cell['x'] - 0.5) * PIXEL_SIZE,
                                (cell['y'] - 1) * PIXEL_SIZE)
                if 'powerup' in cell:
                    pic = QPixmap(IMAGE[cell['powerup']['type']])
                    item = self.map_scene.addPixmap(
                        pic.scaled(PIXEL_SIZE, PIXEL_SIZE))
                    item.setPos(cell['x'] * PIXEL_SIZE, cell['y'] * PIXEL_SIZE)

    def load_match(self, directory):
        self.current_match = Match(directory)
        self.list_rounds()
        self.update_end_game_state()
        self.rounds_list.setCurrentIndex(self.rounds_list_model.index(0, 0))

    def update_end_game_state(self):
        results = self.current_match.end_game_state
        self.match_seed_label.setText(results['seed'])
        self.match_winner_label.setText(results['winner'])
        self.player_A_results_label.setText(results['playerA'])
        self.player_B_results_label.setText(results['playerB'])
        self.referee_messages_text.setText(results['referee'])

    def list_rounds(self):
        match_directory = self.current_match.match_directory
        model = self.rounds_list_model
        model.clear()
        rounds = [
            round_ for round_ in os.listdir(match_directory) if
            'Round' in round_ and os.path.isdir(os.path.join(match_directory))
        ]
        for round_ in rounds:
            item = QStandardItem(round_)
            model.appendRow(item)

    def update_fields(self):
        for group_key, group in self.groups.items():
            if group_key == 'match_info_group':
                state = self.current_match.state
                self.update_labels(group, state)
            if 'player_A_move_info' in group_key:
                temp = [
                    value for key, value in
                    self.current_match.current_round.player_command.items()
                    if 'A -' in key
                ]
                self.update_labels(group, temp[0])
            if 'player_B_move_info' in group_key:
                temp = [
                    value for key, value in
                    self.current_match.current_round.player_command.items()
                    if 'B -' in key
                ]
                self.update_labels(group, temp[0])
            if group_key == 'player_A_info_group':
                temp = [
                    value for key, value in
                    self.current_match.current_round.state.items()
                    if 'A - ' in key
                ]
                self.update_labels(group, temp[0])
            if group_key == 'player_B_info_group':
                temp = [
                    value for key, value in
                    self.current_match.current_round.state.items()
                    if 'B - ' in key
                ]
                self.update_labels(group, temp[0])
            if group_key == 'player_A_worms_info_group':
                players = [player for player in self.current_match.players]
                self.populate_worms_info(group, players[0])
            if group_key == 'player_B_worms_info_group':
                self.populate_worms_info(group, players[1])

    @staticmethod
    def update_labels(group, state):
        for field_key, field in group.fields.items():
            field.setText(str(state[field_key]))
            if field_key == 'colour':
                field.setIcon()

    def populate_worms_info(self, group, player):
        # Clears the layout before re-adding worms info
        for i in reversed(range(group.layout().count())):
            group.layout().itemAt(i).widget().setParent(None)
        worms = self.current_match.current_round.state[player]['worms']
        for worm in worms:
            wormGroup = QGroupBox()
            wormGroup_layout = QGridLayout()
            wormGroup_layout.setSpacing(0)
            wormGroup.setLayout(wormGroup_layout)
            group.layout().addWidget(wormGroup)
            for index, (key, value) in enumerate(worm.items()):
                label_ = QLabel()
                label_.setText(key + ': ')
                label = QLabel()
                label.setText(str(value))
                wormGroup_layout.addWidget(label_, index, 0)
                wormGroup_layout.addWidget(label, index, 1)
コード例 #21
0
ファイル: desktop_icon.py プロジェクト: hgoldfish/quickpanel
class DesktopIconWidget(QFrame):
    def __init__(self, parent):
        QFrame.__init__(self, parent)
        self.setFrameStyle(QFrame.Box | QFrame.Sunken)
        self.setStyleSheet("QListView{background:transparent;}")

        self.listView = QListView(self)
        self.setLayout(QHBoxLayout())
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().addWidget(self.listView)

        self.listView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.listView.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.listView.setMovement(QListView.Snap)
        self.listView.setFlow(QListView.LeftToRight)
        self.listView.setResizeMode(QListView.Adjust)
        self.listView.setGridSize(QSize(self.logicalDpiX() / 96 * 70,
                                        self.logicalDpiY() / 96 * 70))
        self.listView.setViewMode(QListView.IconMode)

        self.quickDesktopModel = QuickDesktopModel(self.window().platform.databaseFile)
        self.listView.setModel(self.quickDesktopModel)
        self.createActions()
        self.makeConnections()

    def createActions(self):
        self.actionCreateComputer = QAction(self.tr("我的电脑(&C)"), self)
        self.actionCreateDocuments = QAction(self.tr("我的文档(&D)"), self)
        self.actionCreateMusic = QAction(self.tr("我的音乐(&M)"), self)
        self.actionCreatePictures = QAction(self.tr("我的图片(&P)"), self)
        self.actionCreateShortcut = QAction(self.tr("创建快捷方式(&C)"), self)
        self.actionCreateShortcut.setIcon(QIcon(":/images/new.png"))
        self.actionCreateBookmark = QAction(self.tr("创建网络链接(&B)"), self)
        self.actionCreateBookmark.setIcon(QIcon(":/images/insert-link.png"))
        self.actionRemoveShortcut = QAction(self.tr("删除快捷方式(&R)"), self)
        self.actionRemoveShortcut.setIcon(QIcon(":/images/delete.png"))
        self.actionRenameShortcut = QAction(self.tr("重命名(&N)"), self)
        self.actionRenameShortcut.setIcon(QIcon(":/images/edit-rename.png"))
        self.actionEditShortcut = QAction(self.tr("编辑快捷方式(&E)"), self)
        self.actionEditShortcut.setIcon(QIcon(":/images/edit.png"))

    def makeConnections(self):
        self.listView.customContextMenuRequested.connect(self.onQuickDesktopContextMenuRequest)
        self.listView.activated.connect(self.runQuickDesktopShortcut)

        self.actionCreateComputer.triggered.connect(self.createComputerShortcut)
        self.actionCreateDocuments.triggered.connect(self.createDocumentsShortcut)
        self.actionCreateMusic.triggered.connect(self.createMusicShortcut)
        self.actionCreatePictures.triggered.connect(self.createPicturesShortcut)
        self.actionCreateShortcut.triggered.connect(self.createShortcut)
        self.actionCreateBookmark.triggered.connect(self.createBookmark)
        self.actionEditShortcut.triggered.connect(self.editShortcut)
        self.actionRemoveShortcut.triggered.connect(self.removeShortcut)
        self.actionRenameShortcut.triggered.connect(self.renameShortcut)

    def onQuickDesktopContextMenuRequest(self, pos):
        index = self.listView.indexAt(pos)
        self.listView.setCurrentIndex(index)
        menu = QMenu()
        menu.addAction(self.actionCreateShortcut)
        menu.addAction(self.actionCreateBookmark)
        menu2 = menu.addMenu(self.tr("创建特殊快捷方式(&S)"))
        if os.name == "nt":
            menu2.addAction(self.actionCreateComputer)
        menu2.addAction(self.actionCreateDocuments)
        menu2.addAction(self.actionCreatePictures)
        menu2.addAction(self.actionCreateMusic)
        if index.isValid():
            menu.addAction(self.actionRemoveShortcut)
            if not self.quickDesktopModel.isSpecialShortcut(index):
                menu.addAction(self.actionEditShortcut)
            menu.addAction(self.actionRenameShortcut)
        try:
            getattr(menu, "exec")(QCursor.pos())
        except AttributeError:
            getattr(menu, "exec_")(QCursor.pos())

    def createShortcut(self):
        d = ShortcutDialog(self)
        if self.window().runDialog(d.create) == QDialog.Accepted:
            shortcut = d.getResult()
            shortcut["id"] = str(uuid.uuid4())
            self.quickDesktopModel.addShortcut(shortcut)
        d.deleteLater()

    def createBookmark(self):
        d = BookmarkDialog(self)
        if self.window().runDialog(d.create) == QDialog.Accepted:
            shortcut = {
                    "id": str(uuid.uuid4()),
                    "icon": "",
                    "openwith": "",
                    "dir": "",
            }
            shortcut.update(d.getResult())
            self.quickDesktopModel.addShortcut(shortcut)
        d.deleteLater()

    def createComputerShortcut(self):
        shortcut = {
                "id": str(uuid.uuid4()),
                "name": self.tr("我的电脑"),
                "path": COMPUTER_PATH,
                "icon": "",
                "dir": "",
                "openwith": "",
        }
        self.quickDesktopModel.addShortcut(shortcut)

    def createDocumentsShortcut(self):
        shortcut = {
                "id": str(uuid.uuid4()),
                "name": self.tr("我的文档"),
                "path": DOCUMENTS_PATH,
                "icon": "",
                "dir": "",
                "openwith": "",
        }
        self.quickDesktopModel.addShortcut(shortcut)

    def createPicturesShortcut(self):
        shortcut = {
                "id": str(uuid.uuid4()),
                "name": self.tr("图片收藏"),
                "path": PICTURES_PATH,
                "icon": "",
                "dir": "",
                "openwith": "",
        }
        self.quickDesktopModel.addShortcut(shortcut)

    def createMusicShortcut(self):
        shortcut = {
                "id": str(uuid.uuid4()),
                "name": self.tr("我的音乐"),
                "path": MUSIC_PATH,
                "icon": "",
                "dir": "",
                "openwith": "",
        }
        self.quickDesktopModel.addShortcut(shortcut)

    def renameShortcut(self):
        self.listView.edit(self.listView.currentIndex())

    def removeShortcut(self):
        self.quickDesktopModel.removeShortcut(self.listView.currentIndex())

    def editShortcut(self):
        index = self.listView.currentIndex()
        if not index.isValid():
            return
        shortcut = self.quickDesktopModel.shortcutAt(index)
        url = QUrl.fromUserInput(shortcut["path"])
        if not url.isValid():
            return
        if url.scheme() == "special":
            QMessageBox.information(self, self.tr("编辑快捷方式"), self.tr("不能编辑特殊图标。"))
            return
        elif url.scheme() == "file":
            d = ShortcutDialog(self)
        else:
            d = BookmarkDialog(self)
        if self.window().runDialog(d.edit, shortcut) == QDialog.Accepted:
            shortcut.update(d.getResult())
            self.quickDesktopModel.updateShortcut(shortcut, index)
        d.deleteLater()

    def runQuickDesktopShortcut(self):
        index = self.listView.currentIndex()
        if not index.isValid():
            return
        if not self.quickDesktopModel.runShortcut(index):
            QMessageBox.information(self, self.tr("快捷面板"), \
                    self.tr("不能运行快捷方式。请检查文件是否存在或者程序是否正确。"))
        else:
            self.window().close()
コード例 #22
0
ファイル: mnelab.py プロジェクト: chrismullins/mnelab
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)
コード例 #23
0
ファイル: client_gui2.py プロジェクト: vsurguch/client
class AppWindow(QMainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self.setGeometry(100, 100, 550, 400)
        self.setUpMenu()
        self.setUpLayout()
        self.checkBoxConnect.stateChanged.connect(self.connect_disconnect)
        self.addButton.clicked.connect(self.add_contact)
        self.deleteButton.clicked.connect(self.delete_contact)
        self.sendButton.clicked.connect(self.send_message)
        self.contactsList.clicked.connect(self.select_contact)
        # self.messageField.drop.connect(self.add_file_to_message)

        # model -controller - view
        self.contact_list_model = StandardItemModelContacts(self)
        self.messages_list_model = StandardItemModelMessages(self)
        self.model = ClientModel(self.contact_list_model,
                                 self.messages_list_model)
        self.contactsList.setModel(self.contact_list_model)
        self.messagesList.setModel(self.messages_list_model)
        self.controller = ClientController(self.model)

        # signal-slot
        self.controller.auth_recieved.connect(self.authentification_recieved)
        self.controller.gotPersonal.connect(self.new_personal_message)

        # not connected
        self.connected = False
        # nobody selected
        self.currently_selected = ''

    def setUpMenu(self):
        fileMenu = self.menuBar().addMenu('File')
        quitAct = QAction('Quit', self)
        quitAct.triggered.connect(self.doQuit)
        fileMenu.addAction(quitAct)
        sendMenu = self.menuBar().addMenu('Send')
        sendFile = QAction('Send File', self)
        sendFile.triggered.connect(self.doSendFile)
        sendMenu.addAction(sendFile)

    def setUpLayout(self):
        hBoxLayout = QHBoxLayout()
        mainFrame = QFrame()
        mainFrame.setLayout(hBoxLayout)

        self.contact = QLineEdit()
        self.contact.setFont(QFont('Calibri', 14))
        self.contact.setPlaceholderText('Add Contact Here')
        self.contact.setMinimumHeight(30)
        self.contact.setMaximumHeight(30)

        self.addButton = QPushButton('+')
        self.addButton.setMinimumHeight(30)
        self.addButton.setMaximumHeight(30)
        self.addButton.setMinimumWidth(30)
        self.addButton.setMaximumWidth(30)

        self.deleteButton = QPushButton('-')
        self.deleteButton.setMinimumHeight(30)
        self.deleteButton.setMaximumHeight(30)
        self.deleteButton.setMinimumWidth(30)
        self.deleteButton.setMaximumWidth(30)

        self.contactsList = QListView()
        self.contactsList.setFrameShape(QFrame.Box)

        spacer = QFrame()
        spacer.setMaximumWidth(5)

        self.nameLabel = QLabel('Username')
        self.nameLabel.setMinimumHeight(30)
        self.nameLabel.setMaximumHeight(30)
        self.nameLabel.setFont(QFont('Calibri', 14))
        self.checkBoxConnect = QCheckBox()
        self.checkBoxConnect.setText('Connect')
        self.checkBoxConnect.setChecked(False)
        self.checkBoxConnect.setMaximumHeight(30)
        self.checkBoxConnect.setMinimumHeight(30)
        self.checkBoxConnect.setMaximumWidth(70)
        self.checkBoxConnect.setMinimumWidth(70)

        self.messagesList = QListView()
        self.messagesList.setIconSize(QSize(30, 30))
        self.messagesList.setFrameShape(QFrame.Box)
        self.messageField = MessageField()
        self.messageField.setMinimumHeight(50)
        self.messageField.setMaximumHeight(50)
        self.sendButton = QPushButton()
        self.sendButton.setText('>>')
        self.sendButton.setMinimumHeight(50)
        self.sendButton.setMaximumHeight(50)
        self.sendButton.setMinimumWidth(50)
        self.sendButton.setMaximumWidth(50)

        contBox = QGridLayout()
        contBox.addWidget(self.contact, 0, 0)
        contBox.addWidget(self.addButton, 0, 1)
        contBox.addWidget(self.deleteButton, 0, 2)
        contBox.addWidget(self.contactsList, 1, 0, 2, 3)
        contBox.addWidget(spacer, 0, 3, 3, 1)
        contBox.addWidget(self.nameLabel, 0, 4)
        contBox.addWidget(self.checkBoxConnect, 0, 5, 1, 2)
        contBox.addWidget(self.messagesList, 1, 4, 1, 3)
        contBox.addWidget(self.messageField, 2, 4, 1, 2)
        contBox.addWidget(self.sendButton, 2, 6)
        contBox.setColumnStretch(0, 3)
        contBox.setColumnStretch(4, 6)
        groupBoxCont = QFrame()
        groupBoxCont.setLayout(contBox)

        self.setCentralWidget(groupBoxCont)

    def doQuit(self):
        self.close()

    def doSendFile(self):
        filename = QFileDialog.getOpenFileName(self, 'Open file', '/home')[0]
        self.messageField.file = filename
        self.messageField.appendPlainText(filename)

    def connect_disconnect(self):
        self.checkBoxConnect.setEnabled(False)
        if self.checkBoxConnect.isChecked():
            self.authentification()
        else:
            self.user_disconnect()
            self.checkBoxConnect.setEnabled(True)

    # Log in  dialog
    def authentification(self):
        #read config file
        d = conf.read_conf()
        #show Authorization dialog
        auth_dialog = AuthDialog(self.model,
                                 d['username'],
                                 d['hostname'],
                                 d['port'],
                                 parent=self)
        auth_dialog.accepted.connect(self.auth_dialog_accepted)
        auth_dialog.rejected.connect(self.auth_dialog_rejected)
        auth_dialog.show()

    def auth_dialog_accepted(self):
        self.checkBoxConnect.setEnabled(True)
        self.controller.run_client()

    def auth_dialog_rejected(self):
        self.checkBoxConnect.setChecked(False)
        self.checkBoxConnect.setEnabled(True)

    @pyqtSlot(bool)
    def authentification_recieved(self, ok):
        if ok:
            self.connected = True
            self.checkBoxConnect.setText('Connected')
            self.nameLabel.setText('{}:'.format(self.model.username))
            self.model.contactlist.clear()
            self.model.personal_messages = []
            self.model.server_messages = []
            self.model.sent_messages = []
            self.model.open_db()
            self.controller.ask_contact_list()
        else:
            self.checkBoxConnect.setChecked(False)

    def user_disconnect(self):
        if self.connected:
            conf.save_conf(username=self.model.username,
                           hostname=self.model.host,
                           port=str(self.model.port))
            self.model.clear_data()
            self.controller.user_disconnect()
            self.contact.setText('')
            self.nameLabel.setText('')
            self.checkBoxConnect.setText('Connect')
            self.connected = False
            self.currently_selected = ''

    @pyqtSlot(str)
    def get_contact_list(self):
        pass

    def add_contact(self):
        name = self.contact.text()
        if name != '':
            self.controller.add_contact(name)
            self.contact.setText('')

    def delete_contact(self):
        name = self.contact.text()
        if name != '':
            self.controller.delete_contact(name)
            self.contact.setText('')

    @pyqtSlot(QMimeData)
    def add_file_to_message(self, mime):
        self.messageField.setPlainText(mime.text())

    def send_message(self):
        sel_list = self.contactsList.selectionModel().selectedIndexes()
        if len(sel_list) > 0:
            contact = self.model.contactlist.contacts[sel_list[0].row()]
            if self.messageField.file != '':
                self.controller.send_file(contact.name, self.messageField.file)
                self.messageField.file = ''
            else:
                self.controller.personal_message(
                    contact.name, self.messageField.toPlainText())
            self.messageField.clear()
            self.model.update_messages_for_contact(contact.name)
        else:
            pass

    @pyqtSlot(QModelIndex)
    def select_contact(self, modelindex):
        item = self.contact_list_model.data(modelindex, role=Qt.DisplayRole)
        contact_name = self.model.contactlist.contacts_name[modelindex.row()]
        self.contact.setText(contact_name)
        self.show_messages_for_contact(contact_name)
        self.contactsList.setCurrentIndex(modelindex)
        self.currently_selected = contact_name

    @pyqtSlot(str)
    def new_personal_message(self, sender):
        self.model.new_message_increase(sender, True)
        # sel_list = self.listViewContacts.selectionModel().selectedIndexes()
        if sender == self.currently_selected:
            self.show_messages_for_contact(self.currently_selected)

    @pyqtSlot(str)
    def show_messages_for_contact(self, contact_name):
        self.model.update_messages_for_contact(contact_name)
        self.model.new_message_increase(contact_name, False)
        self.messagesList.scrollToBottom()
コード例 #24
0
class MainWindow(QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()

        self.top: int = 300
        self.left: int = 300
        self.width: int = 200
        self.height: int = 300

        self.setWindowTitle("Albums - List View Example")
        self.setGeometry(self.top, self.left, self.width, self.height)

        self.dbm: DbManager = None
        self.dao: AlbumDao = None

        self.list_view: QListView = None
        self.list_model: AlbumListModel = None
        self.list_selection_model: QItemSelectionModel = None

        self.init_db()
        self.init_ui()

    def init_db(self):
        """
        Initialises DB connection
        :return:
        """
        self.dbm = DbManager()
        self.dbm.create()
        self.dao = AlbumDao(dbm=self.dbm)
        if not self.dao.init():
            print("Failed to initialise DAO for Albums")
        else:
            print("DAO initialised for Albums")

    def init_ui(self):
        """
        Initialises UI components
        :return:
        """
        self.list_view = QListView()
        self.list_model = AlbumListModel(dao=self.dao)
        self.list_model.dataChanged.connect(self.on_data_changed)
        self.list_selection_model = QItemSelectionModel(self.list_model)
        self.list_view.setModel(self.list_model)
        self.list_view.setSelectionModel(self.list_selection_model)

        btn_add = QPushButton('Add', self)
        btn_add.setToolTip('Add Album')

        btn_edit = QPushButton('Edit', self)
        btn_edit.setToolTip('Edit Album')

        btn_delete = QPushButton('Delete', self)
        btn_delete.setToolTip('Delete Album')

        btn_add.clicked.connect(self.create_album)
        btn_edit.clicked.connect(self.edit_album)
        btn_delete.clicked.connect(self.delete_album)

        list_layout = QHBoxLayout()
        list_layout.addStretch(1)
        list_layout.addWidget(self.list_view)

        buttons_layout = QHBoxLayout()
        buttons_layout.addStretch(1)
        buttons_layout.addWidget(btn_add)
        buttons_layout.addWidget(btn_edit)
        buttons_layout.addWidget(btn_delete)

        main_layout = QVBoxLayout()
        main_layout.addLayout(list_layout)
        main_layout.addLayout(buttons_layout)

        self.setLayout(main_layout)
        self.center()
        self.show()

    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def create_album(self):
        input_name, ok_clicked = QInputDialog.getText(self,
                                                      "Create a new Album",
                                                      "Choose a name",
                                                      QLineEdit.Normal, "")
        if ok_clicked:
            album = Album(name=input_name)
            created_index = self.list_model.add_album(album)
            self.list_view.setCurrentIndex(created_index)

    def edit_album(self):
        if len(self.list_selection_model.selectedIndexes()) > 0:
            current_index = self.list_selection_model.currentIndex()
            old_name = self.list_model.data(current_index,
                                            self.list_model.name_role)
            input_name, ok_clicked = QInputDialog.getText(
                self, "Album's name", "Change Album name", QLineEdit.Normal,
                old_name)
            if ok_clicked:
                self.list_model.setData(current_index, input_name,
                                        self.list_model.name_role)

    def delete_album(self):
        if len(self.list_selection_model.selectedIndexes()) > 0:
            current_index = self.list_selection_model.currentIndex()
            row = current_index.row()
            self.list_model.removeRow(row)

            # Try to select the previous album
            previous_index = self.list_model.index(row - 1, 0)
            if previous_index.isValid():
                self.list_selection_model.setCurrentIndex(
                    previous_index, QItemSelectionModel.SelectCurrent)
            else:
                # Try to select the next album
                next_index = self.list_model.index(row, 0)
                if next_index.isValid():
                    self.list_selection_model.setCurrentIndex(
                        next_index, QItemSelectionModel.SelectCurrent)

    def on_data_changed(self, index_1: QModelIndex, index_2: QModelIndex):
        print('Data has been updated')
コード例 #25
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)
コード例 #26
0
class ListManager(QDialog):
    def __init__(self):
        super().__init__()
        self.setWindowFlags(Qt.WindowCloseButtonHint | Qt.WindowTitleHint)
        self.setWindowTitle('List Manager')
        self.setFixedSize(400, 300)
        self.ListViewer = QListView()
        self.ListViewer.clicked.connect(self.ClickList)
        self.ListViewer.doubleClicked.connect(self.Ok)
        self.OkButton = QPushButton('OK')
        self.OkButton.clicked.connect(self.Ok)
        self.CancelButton = QPushButton('Cancel')
        self.CancelButton.clicked.connect(self.Cancel)
        sortorderlable = QLabel('Sort Order')
        self.SortOrder = [QRadioButton("Asc"), QRadioButton("Desc")]
        self.OrderGroup = QButtonGroup()
        sortalglable = QLabel('Sort Algorithm')
        self.SortAlg = [QRadioButton("Path"), QRadioButton("Natural")]
        self.AlgGroup = QButtonGroup()

        hbox1 = QHBoxLayout()
        hbox2 = QHBoxLayout()
        vbox1 = QVBoxLayout()
        vbox2 = QVBoxLayout()
        vbox3 = QVBoxLayout()
        mainbox = QVBoxLayout()

        vbox1.addWidget(sortorderlable)
        for i in range(len(self.SortOrder)):
            vbox1.addWidget(self.SortOrder[i])
            self.OrderGroup.addButton(self.SortOrder[i], i)
            self.SortOrder[i].clicked.connect(self.ClickRadioButton)
        vbox2.addWidget(sortalglable)
        for i in range(len(self.SortAlg)):
            vbox2.addWidget(self.SortAlg[i])
            self.AlgGroup.addButton(self.SortAlg[i], i)
            self.SortAlg[i].clicked.connect(self.ClickRadioButton)

        vbox3.addLayout(vbox1)
        vbox3.addLayout(vbox2)
        vbox3.addStretch(1)
        hbox1.addWidget(self.ListViewer)
        hbox1.addLayout(vbox3)
        hbox2.addStretch(1)
        hbox2.addWidget(self.OkButton)
        hbox2.addWidget(self.CancelButton)
        mainbox.addLayout(hbox1)
        mainbox.addLayout(hbox2)
        self.setLayout(mainbox)
        self.list = []
        self.path = ''

    def showEvent(self, event):
        self.center()
        self.LoadListToView()

    def closeEvent(self, event):
        self.Cancel()

    def center(self):
        frameGm = self.frameGeometry()
        screen = QApplication.desktop().screenNumber(
            QApplication.desktop().cursor().pos())
        centerPoint = QApplication.desktop().screenGeometry(screen).center()
        frameGm.moveCenter(centerPoint)
        self.move(frameGm.topLeft())

    def LoadListToView(self):
        if self.list:
            self.index = self.list.index(self.path)
            self.Model = QStandardItemModel(self.ListViewer)
            for file in self.list:
                item = QStandardItem(file)
                item.setEditable(False)
                self.Model.appendRow(item)
            self.ListViewer.setModel(self.Model)
            self.ListViewer.setCurrentIndex(self.Model.index(self.index, 0))

    def ClickList(self, index):
        self.path = self.Model.data(index)
        self.index = self.list.index(self.path)

    def ClickRadioButton(self):
        if self.list:
            self.list = self.Sort(self.list, self.OrderGroup.checkedId(),
                                  self.AlgGroup.checkedId())
            self.LoadListToView()

    def Ok(self):
        self.accept()

    def Cancel(self):
        self.reject()

    def Sort(self, list, sortid, algid):
        if list:
            if sortid == 0:
                isreverse = False
            else:
                isreverse = True

            if algid == 0:
                sortalg = ns.PATH
            else:
                sortalg = 0

            return natsorted(list, alg=sortalg, reverse=isreverse)
        else:
            return list
コード例 #27
0
class GMDir:
    EXT_WHITELIST = '.pdf'

    @property
    def path(self):
        return self._path

    @property
    def documents(self):
        return self._documents

    @property
    def name(self):
        return self._name

    @property
    def tab_index(self):
        return self._tab_index

    @property
    def closed(self):
        return self._closed

    @property
    def open_count(self):
        return self._open_count

    @property
    def unsaved_count(self):
        return self._unsaved_count

    def __init__(self, gm, path):
        assert isinstance(gm, GalleryMark)
        self._gm = gm
        self._path = path
        self._name = os.path.basename(path)
        self._documents = []
        self._open_count = 0
        self._unsaved_count = 0

        self._tab_index = None
        self._tab_widget = None

        self._listview = QListView()
        self._listview.setViewMode(QListView.ListMode)
        self._listview.setSelectionBehavior(QListView.SelectRows)
        self._listview.activated.connect(self.onListViewItemActivated)

        self._active_document = None

        self._closed = False
        self.refresh(init=True)

    def refresh(self, init=False):
        to_remove = []
        for doc in self._documents:
            if not os.path.exists(doc.path):
                to_remove.append(doc)

        for r in to_remove:
            self._documents.remove(r)

        for item in os.listdir(self._path):
            full_path = os.path.join(self._path, item)

            if os.path.isfile(full_path):
                if item.lower().endswith(self.EXT_WHITELIST):
                    if init:
                        self._documents.append(GMDoc(full_path))
                    else:
                        found = False
                        for doc in self._documents:
                            if doc.path == full_path:
                                found = True

                        if not found:
                            self.documents.append(GMDoc(full_path))

        if self._active_document is None and len(self._documents) > 0:
            self.setActiveDocument(self._documents[0])

        self.updateState()

        if self._tab_widget is not None:
            self._tab_widget.setTabText(self._tab_index, self.getTabName())

    def onListViewItemActivated(self, model_index):
        doc = model_index.data(Qt.UserRole)
        self._gm.openDocument(doc)
        self.updateState()

    def updateState(self):
        self._open_count = 0
        self._unsaved_count = 0

        model = QStandardItemModel()

        for doc in self._documents:
            item_text = doc.name
            changed = doc.changed()

            if changed:
                item_text = '{}*'.format(item_text)
                self._unsaved_count += 1

            if self._active_document is not None and doc == self._active_document:
                item_text = '> {}'.format(item_text)
                self._open_count += 1
            elif doc.is_open:
                item_text = '+ {}'.format(item_text)
                self._open_count += 1

            item = QStandardItem(item_text)
            item.setData(doc, Qt.UserRole)
            item.setEditable(False)
            model.appendRow(item)

        self._listview.setModel(model)

    def getDocumentByIndex(self, index):
        if 0 <= index < len(self._documents):
            mi = self._listview.model().index(index, 0)
            self._listview.setCurrentIndex(mi)
            return mi.data(Qt.UserRole)
        return None

    def setActiveDocument(self, doc):
        self._active_document = doc
        self.updateState()

    def getPreviousDocument(self):
        previous = None

        if self._active_document is not None:
            for i, doc in enumerate(self._documents):
                if doc == self._active_document:
                    pi = i - 1

                    if pi >= 0:
                        previous = self._documents[pi]

        return previous

    def getNextDocument(self):
        next = None

        if self._active_document is not None:
            for i, doc in enumerate(self._documents):
                if doc == self._active_document:
                    ni = i + 1

                    if ni < len(self._documents):
                        next = self._documents[ni]

        return next

    def getTabName(self):
        return '{} ({})'.format(self._name, len(self._documents))

    def setupTab(self, tab_widget):
        assert isinstance(tab_widget, QTabWidget)

        container = QWidget()
        layout = QVBoxLayout()

        self.updateState()

        layout.addWidget(self._listview)
        container.setLayout(layout)

        tab_widget.addTab(container, self.getTabName())
        self._tab_index = tab_widget.indexOf(container)
        self._tab_widget = tab_widget

    def close(self, active_doc=None):
        for doc in self._documents:
            if active_doc is not None and doc == active_doc:
                continue
            if doc.is_open:
                if not doc.close():
                    return False
        self._closed = True
        return True
コード例 #28
0
ファイル: pwdmgr.py プロジェクト: Freyjawion/pycomics
class PwdManager(QDialog):
    def __init__(self):
        super().__init__()
        self.setWindowFlags(Qt.WindowCloseButtonHint | Qt.WindowTitleHint)
        self.setWindowTitle('Password Manager')
        self.setFixedSize(400, 300)
        self.PwdViewer = QListView()
        self.PwdViewer.clicked.connect(self.ClickPwd)
        self.AddButton = QPushButton('Add')
        self.AddButton.clicked.connect(self.AddPwd)
        self.DelButton = QPushButton('Delete')
        self.DelButton.clicked.connect(self.DelPwd)
        self.PwdBox = QLineEdit()
        self.PwdBox.textEdited.connect(self.PwdChanged)

        vbox = QVBoxLayout()
        vbox.addWidget(self.AddButton)
        vbox.addWidget(self.DelButton)
        vbox.addStretch(1)
        hbox = QHBoxLayout()
        hbox.addWidget(self.PwdViewer)
        hbox.addLayout(vbox)
        MainBox = QVBoxLayout()
        MainBox.addWidget(self.PwdBox)
        MainBox.addLayout(hbox)
        self.setLayout(MainBox)

    def showEvent(self, event):
        self.center()
        self.LoadPwdToList()
        if self.Model.rowCount() == 0:
            self.AddPwd()
        self.IsChanged = False

    def closeEvent(self, event):
        self.RemoveEmpty()
        pwdf = open('password.pwd', 'w')
        for index in range(self.Model.rowCount()):
            pwdf.write(self.Model.data(self.Model.index(index, 0)) + '\n')
        pwdf.close()
        if self.IsChanged:
            self.done(1)
        else:
            self.done(0)

    def center(self):
        frameGm = self.frameGeometry()
        screen = QApplication.desktop().screenNumber(
            QApplication.desktop().cursor().pos())
        centerPoint = QApplication.desktop().screenGeometry(screen).center()
        frameGm.moveCenter(centerPoint)
        self.move(frameGm.topLeft())

    def LoadPwdToList(self):
        if not os.path.exists(r'password.pwd'):
            open("password.pwd", "wb").close()
        pwdf = open('password.pwd', 'r')
        pwdlist = pwdf.read().splitlines()
        pwdf.close()
        self.Model = QStandardItemModel(self.PwdViewer)
        for pwd in pwdlist:
            item = QStandardItem(pwd)
            item.setEditable(False)
            self.Model.appendRow(item)
        self.PwdViewer.setModel(self.Model)
        self.PwdViewer.setCurrentIndex(self.Model.index(0, 0))
        self.GetPwd(self.PwdViewer.currentIndex())

    def ClickPwd(self, index):
        self.RemoveEmpty()
        if self.Model.rowCount() == 0:
            self.AddPwd()
        self.GetPwd(index)

    def GetPwd(self, index):
        self.PwdBox.setText(self.Model.data(index))
        self.PwdBox.setFocus()

    def PwdChanged(self):
        self.IsChanged = True
        self.Model.setData(self.PwdViewer.currentIndex(), self.PwdBox.text())
        if self.PwdBox.text() == '':
            self.DelPwd()

    def DelPwd(self):
        self.Model.removeRow(self.PwdViewer.currentIndex().row())
        self.GetPwd(self.PwdViewer.currentIndex())
        if self.Model.rowCount() == 0:
            self.AddPwd()

    def AddPwd(self):
        item = QStandardItem()
        item.setEditable(False)
        self.Model.appendRow(item)
        self.PwdViewer.setCurrentIndex(
            self.Model.index(self.Model.rowCount() - 1, 0))
        self.GetPwd(self.PwdViewer.currentIndex())

    def RemoveEmpty(self):
        for item in self.Model.findItems('', Qt.MatchFixedString):
            self.Model.removeRow(item.row())
コード例 #29
0
ファイル: main.py プロジェクト: vikas-vm/mediaProQT
class MainWindow(QWidget):

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

        self.playlistView = QListView()
        self.switch_status = 2
        self.video_widget = QVideoWidget()
        self.playlist = QMediaPlaylist()
        self.model = PlaylistModel(self.playlist)
        self.titleBar = TitleBar(self)
        self.currentTimeLabel = QLabel()
        self.timeSlider = QSlider()
        self.totalTimeLabel = QLabel()
        self.mediaPlayer = QMediaPlayer()
        self.open_btn = QPushButton('Open File')
        self.play_btn = QPushButton()
        self.prev_btn = QPushButton()
        self.stop_btn = QPushButton()
        self.next_btn = QPushButton()
        self.switch_media_widgets_btn = QPushButton()
        self.pseudo_label = QLabel()

        self.vol_label = QLabel()
        self.volume_slider = Slider(Qt.Horizontal)
        self.gui()
        self.set_children_focus_policy(Qt.NoFocus)

    def gui(self):
        self.currentTimeLabel.setMinimumSize(QSize(80, 0))
        self.currentTimeLabel.setAlignment(Qt.AlignRight | Qt.AlignTrailing | Qt.AlignVCenter)

        self.timeSlider.setOrientation(Qt.Horizontal)
        self.totalTimeLabel.setMinimumSize(QSize(80, 0))
        self.totalTimeLabel.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter)

        self.playlistView.setAcceptDrops(True)
        self.playlistView.setProperty("showDropIndicator", True)
        self.playlistView.setDragDropMode(QAbstractItemView.DropOnly)
        self.playlistView.setAlternatingRowColors(True)
        self.playlistView.setUniformItemSizes(True)

        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setWindowTitle('Media Player')
        self.titleBar.label.setText('Media Player')
        self.setWindowIcon(QIcon('icon_png/media_player.png'))

        self.setGeometry(600, 200, 850, 600)
        self.timeSlider.setRange(0, 0)
        self.play_btn.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
        self.prev_btn.setIcon(self.style().standardIcon(QStyle.SP_MediaSkipBackward))
        self.next_btn.setIcon(self.style().standardIcon(QStyle.SP_MediaSkipForward))
        self.stop_btn.setIcon(self.style().standardIcon(QStyle.SP_MediaStop))
        self.switch_media_widgets_btn.setIcon(self.style().standardIcon(QStyle.SP_FileDialogDetailedView))
        self.vol_label.setText("")
        self.vol_label.setPixmap(QPixmap("icon_png/speaker-volume.png"))
        self.currentTimeLabel.setText("00:00")
        self.totalTimeLabel.setText("00:00")
        self.volume_slider.setValue(self.mediaPlayer.volume())
        self.mediaPlayer.setVideoOutput(self.video_widget)
        self.mediaPlayer.setPlaylist(self.playlist)
        self.playlistView.setModel(self.model)
        self.video_widget.hide()

        sizegrip = QSizeGrip(self)

        self.setAcceptDrops(True)

        inner_h_box = QHBoxLayout()
        inner_h_box.addWidget(self.prev_btn)
        inner_h_box.addWidget(self.stop_btn)
        inner_h_box.addWidget(self.next_btn)

        vol_h_box = QHBoxLayout()
        vol_h_box.addWidget(self.vol_label, 0)
        vol_h_box.addWidget(self.volume_slider, 1)

        h_box = QHBoxLayout()
        h_box.addWidget(self.open_btn)
        h_box.addWidget(self.play_btn, 0)
        h_box.addLayout(inner_h_box, 0)
        h_box.addWidget(self.switch_media_widgets_btn, 0)
        h_box.addWidget(self.pseudo_label, 1)
        h_box.addLayout(vol_h_box, 0)
        h_box.addWidget(sizegrip, 0, Qt.AlignBottom | Qt.AlignRight)

        video_slider_h_box = QHBoxLayout()
        video_slider_h_box.addWidget(self.currentTimeLabel)
        video_slider_h_box.addWidget(self.timeSlider)
        video_slider_h_box.addWidget(self.totalTimeLabel)

        v_box = QVBoxLayout()
        v_box.addWidget(self.titleBar, 0)
        v_box.addWidget(self.video_widget, 1)
        v_box.addWidget(self.playlistView, 1)
        v_box.addLayout(video_slider_h_box, 0)
        v_box.addLayout(h_box, 0)

        inner_h_box.setContentsMargins(20, 0, 10, 0)
        vol_h_box.setContentsMargins(0, 0, 20, 0)
        h_box.setContentsMargins(20, 0, 0, 0)
        v_box.setContentsMargins(0, 0, 0, 0)
        video_slider_h_box.setSpacing(10)
        h_box.setSpacing(0)
        v_box.setSpacing(0)

        self.setLayout(v_box)
        self.enabler()

        # connections
        self.open_btn.clicked.connect(self.open_file)
        self.play_btn.clicked.connect(self.play_media)
        self.stop_btn.clicked.connect(self.stop_media)

        self.prev_btn.pressed.connect(self.play_prev)
        self.next_btn.pressed.connect(self.play_next)
        self.switch_media_widgets_btn.pressed.connect(self.switch_media)

        self.playlist.currentIndexChanged.connect(self.playlist_position_changed)
        selection_model = self.playlistView.selectionModel()
        selection_model.selectionChanged.connect(self.playlist_selection_changed)

        self.mediaPlayer.durationChanged.connect(self.update_duration)
        self.mediaPlayer.positionChanged.connect(self.update_position)
        self.timeSlider.valueChanged.connect(self.mediaPlayer.setPosition)

        self.mediaPlayer.stateChanged.connect(self.media_state)

        self.mediaPlayer.volumeChanged.connect(self.volume_changed)
        self.volume_slider.valueChanged.connect(self.set_volume)

    def set_children_focus_policy(self, policy):
        def recursive_set_child_focus_policy(parent_q_widget):
            for childQWidget in parent_q_widget.findChildren(QWidget):
                childQWidget.setFocusPolicy(policy)
                recursive_set_child_focus_policy(childQWidget)

        recursive_set_child_focus_policy(self)

    def enabler(self, state=False):
        if state is False:
            self.play_btn.setEnabled(False)
            self.prev_btn.setEnabled(False)
            self.stop_btn.setEnabled(False)
            self.next_btn.setEnabled(False)
        else:
            self.play_btn.setEnabled(True)
            self.stop_btn.setEnabled(True)
            self.prev_btn.setEnabled(True)
            self.next_btn.setEnabled(True)

    def switch_media(self):
        if self.switch_status == 0:
            self.video_widget.hide()
            self.playlistView.show()
            self.switch_status = 1
            self.switch_media_widgets_btn.setEnabled(True)
        elif self.switch_status == 1:
            self.video_widget.show()
            self.playlistView.hide()
            self.switch_status = 0
            self.switch_media_widgets_btn.setEnabled(True)
        else:
            self.video_widget.hide()
            self.playlistView.show()
            self.switch_media_widgets_btn.setEnabled(False)

    def play_media(self):
        if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
            self.mediaPlayer.pause()
        else:
            self.mediaPlayer.play()
        self.ui_handler()

    def ui_handler(self):
        if not self.playlist.isEmpty():
            self.enabler(True)
        file_path = QFileInfo(self.mediaPlayer.currentMedia().canonicalUrl().toString()).fileName()
        ext = os.path.splitext(file_path)[-1].lower()
        audio_ext = ['.flac', '.mp3']
        video_ext = ['.mp4', '.m4a', '.mov', '.flv', 'avi', '3gp', '.mkv', '.wmv']

        if ext in audio_ext:
            self.switch_status = 2
            self.switch_media()
            if self.isFullScreen():
                self.fullscreen()
        elif ext in video_ext:
            self.switch_status = 1
            self.switch_media()
        self.setWindowTitle(file_path + ' - Media Player')
        self.titleBar.label.setText(file_path + ' - Media Player')

    def stop_media(self):
        if self.mediaPlayer.state() != QMediaPlayer.StoppedState:
            self.mediaPlayer.stop()
            self.setWindowTitle('Media Player')
            self.titleBar.label.setText('Media Player')

    def fullscreen(self):
        if self.switch_status == 2 or self.isFullScreen():
            self.titleBar.show()
            self.timeSlider.show()
            self.currentTimeLabel.show()
            self.totalTimeLabel.show()
            self.volume_slider.show()
            self.open_btn.show()
            self.play_btn.show()
            self.prev_btn.show()
            self.stop_btn.show()
            self.next_btn.show()
            self.switch_media_widgets_btn.show()
            self.pseudo_label.show()
            self.vol_label.show()
            self.showNormal()
        else:
            self.titleBar.hide()
            self.timeSlider.hide()
            self.currentTimeLabel.hide()
            self.totalTimeLabel.hide()
            self.volume_slider.hide()
            self.open_btn.hide()
            self.play_btn.hide()
            self.prev_btn.hide()
            self.stop_btn.hide()
            self.next_btn.hide()
            self.switch_media_widgets_btn.hide()
            self.pseudo_label.hide()
            self.vol_label.hide()
            self.showFullScreen()

    def mouseDoubleClickEvent(self, event: QMouseEvent):
        event.accept()
        if event.button() == Qt.LeftButton:
            self.fullscreen()

    def media_state(self):

        os_sleep = WindowsInhibitor()
        if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
            self.play_btn.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
            if os.name == 'nt':
                os_sleep = WindowsInhibitor()
                os_sleep.inhibit()
        else:
            self.play_btn.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
            if os_sleep:
                os_sleep.uninhibit()

    def play_next(self):
        self.playlist.next()

    def media_seek(self, seek):
        if not self.playlist.isEmpty():
            player = self.mediaPlayer
            if (player.duration() - seek) > player.position():
                player.setPosition(player.position() + seek)

    def play_prev(self):
        self.playlist.previous()

    def dragEnterEvent(self, e):
        if e.mimeData().hasUrls():
            e.acceptProposedAction()

    def dropEvent(self, e):
        for url in e.mimeData().urls():
            ext = os.path.splitext(url.fileName())[-1].lower()
            allowed_ext = ['.flac', '.mp3', '.mp4', '.m4a', '.mov', '.flv', 'avi', '3gp', '.mkv', '.wmv']
            if ext in allowed_ext:
                self.playlist.addMedia(
                    QMediaContent(url)
                )

        self.model.layoutChanged.emit()

        if self.mediaPlayer.state() != QMediaPlayer.PlayingState:
            i = self.playlist.mediaCount() - len(e.mimeData().urls())
            self.playlist.setCurrentIndex(i)
            if not self.playlist.isEmpty():
                self.play_media()

    def open_file(self):
        filter_files = "Media (*.mp3 *.mp4 *.mkv);; Videos files (*.mp4 *.mkv);; Music Files(*.mp3)"
        paths, _ = QFileDialog.getOpenFileNames(self, "Open file", "", filter_files)

        if paths:
            self.mediaPlayer.pause()
            for path in paths:
                self.playlist.addMedia(
                    QMediaContent(
                        QUrl.fromLocalFile(path)
                    )
                )
            i = self.playlist.mediaCount() - len(paths)
            self.playlist.setCurrentIndex(i)
            self.play_media()

        self.model.layoutChanged.emit()

    def update_duration(self, duration):
        self.mediaPlayer.duration()

        self.timeSlider.setMaximum(duration)

        if duration >= 0:
            self.totalTimeLabel.setText(hhmmss(duration))

    def update_position(self, position):
        if position >= 0:
            self.currentTimeLabel.setText(hhmmss(position))

        self.timeSlider.blockSignals(True)
        self.timeSlider.setValue(position)
        self.timeSlider.blockSignals(False)

    def playlist_selection_changed(self, ix):
        i = ix.indexes()[0].row()
        self.playlist.setCurrentIndex(i)
        self.ui_handler()

    def playlist_position_changed(self, i):
        if i > -1:
            ix = self.model.index(i)
            self.playlistView.setCurrentIndex(ix)

    def set_volume(self, value):
        self.mediaPlayer.setVolume(value)

    def volume_changed(self, value):
        self.volume_slider.setValue(value)

    def keyPressEvent(self, event):
        key = event.key()
        modifiers = int(event.modifiers())
        if (modifiers and modifiers & MOD_MASK == modifiers and
                key > 0 and key != Qt.Key_Shift and key != Qt.Key_Alt and
                key != Qt.Key_Control and key != Qt.Key_Meta):
            key_name = QKeySequence(modifiers + key).toString()
            if key_name == 'Ctrl+Right':
                self.media_seek(30000)
            elif key_name == 'Ctrl+Left':
                self.media_seek(-30000)
            elif key_name == 'Ctrl+Up':
                self.mediaPlayer.setVolume(self.mediaPlayer.volume() + 5)
            elif key_name == 'Ctrl+Down':
                self.mediaPlayer.setVolume(self.mediaPlayer.volume() - 5)
            elif key_name == 'Ctrl+O':
                self.open_file()

        else:
            if event.key() == Qt.Key_Space:
                self.play_media()
            elif event.key() == Qt.Key_MediaPlay:
                self.play_media()
            elif event.key() == Qt.Key_MediaNext:
                self.play_next()
            elif event.key() == Qt.Key_MediaPrevious:
                self.play_prev()
            elif event.key() == Qt.Key_Escape:
                self.close()
            elif event.key() == Qt.Key_F:
                self.fullscreen()
            elif event.key() == Qt.Key_Right:
                self.media_seek(5000)
            elif event.key() == Qt.Key_Left:
                self.media_seek(-5000)
コード例 #30
0
ファイル: chat.py プロジェクト: zhangenter/bf_chat
class Chat(QWidget):
    msg_signal = pyqtSignal(dict)
    after_close = None
    chats = []
    cur_chat = None

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

        self.msg_signal.connect(self.fill_msg)
        MsgWorker().do_recv_msg = self.do_recv_msg
        self.setWindowTitle('')
        self.op_bar = QFrame(self)
        self.op_bar.setStyleSheet('background-color:rgb(255, 255, 255);')
        self.send_bar = QFrame(self)
        self.send_bar.setStyleSheet('background-color:rgb(255, 255, 255);')
        self.rcTxt = QTextBrowser(self)
        self.rcTxt.setStyleSheet('background-color:rgb(255, 255, 255);')
        self.sdTxt = QTextEdit(self)
        self.sdTxt.setStyleSheet('background-color:rgb(255, 255, 255);')
        self.btn = QPushButton("发送", self.send_bar)
        self.btn.clicked.connect(self.send)
        self.lv = QListView(self)
        self.lv.setViewMode(QListView.ListMode)
        self.lv.setIconSize(QSize(30, 30))
        self.lv.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.lv.setResizeMode(QListView.Adjust)
        self.lv_model = QStandardItemModel()
        self.lv.setModel(self.lv_model)
        self.lv.clicked.connect(self.lv_clicked)
        self.lv.move(0, 0)

        w, h = 600, 400
        self.resize(800, 600)

    def resizeEvent(self, evt):
        self.after_resize(evt.size().width(), evt.size().height())

    def after_resize(self, w, h):
        lv_width = 200
        sdTxt_height = 120
        bar_height = 30
        self.op_bar.move(200, h - sdTxt_height - bar_height * 2)
        self.op_bar.resize(w - lv_width, bar_height)
        self.send_bar.move(200, h - bar_height)
        self.send_bar.resize(w - lv_width, bar_height)

        self.lv.resize(lv_width, h)
        self.rcTxt.move(lv_width, 0)
        self.rcTxt.resize(w - lv_width, h - sdTxt_height - bar_height * 2)
        self.sdTxt.move(lv_width, h - sdTxt_height - bar_height)
        self.sdTxt.resize(w - lv_width, sdTxt_height)

    def lv_clicked(self, model_index):
        cur_chat = self.chats[model_index.row()]
        if cur_chat['mode'] == 'user':
            self.setWindowTitle(cur_chat['data'].nick_name)

    def refresh_cur_chat(self):
        if self.cur_chat['mode'] == 'user':
            self.setWindowTitle(self.cur_chat['data'].nick_name)

    def get_in_chat_index(self, chat):
        if chat['mode'] == 'user':
            name = chat['data'].name
            match = lambda x: x['mode'] == 'user' and x['data'].name == name
        for i in range(len(self.chats)):
            if match(self.chats[i]):
                return i

        return -1

    def chat_to(self, chat):
        i = self.get_in_chat_index(chat)
        if i == -1:
            if chat['mode'] == 'user':
                self.lv_model.appendRow(
                    QStandardItem(QIcon("./client/image/man.png"),
                                  chat['data'].nick_name))
            self.chats.append(chat)
            self.cur_chat = chat
            i = len(self.chats) - 1
        else:
            self.cur_chat = self.chats[i]
        self.refresh_cur_chat()
        self.lv.setCurrentIndex(self.lv_model.index(i, 0))

    def fill_msg(self, data):
        ufrom = data['from']
        uto = data['to']
        val = data['val']
        ufrom_nickname = ufrom
        try:
            self.rcTxt.setPlainText(self.rcTxt.toPlainText() + '%s:%s\n' %
                                    (ufrom_nickname, val))
        except Exception as ex:
            print(ex)

    def do_recv_msg(self, data):
        self.msg_signal.emit(data)

    def send(self):
        # val = self.sdTxt.toHtml()
        val = self.sdTxt.toPlainText()
        if self.cur_chat['mode'] == 'user':
            MsgWorker().send_msg(
                msg_lib.build_chat_msg(MsgWorker().user_info.name,
                                       self.cur_chat['data'].name, val))
        # self.rcTxt.setHtml(self.rcTxt.toHtml()+'\n我:%s'%val)
        self.rcTxt.setPlainText(self.rcTxt.toPlainText() + '我:%s\n' % val)
        self.sdTxt.setPlainText('')

    def closeEvent(self, event):
        self.chats.clear()
        if self.after_close:
            self.after_close()
コード例 #31
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)))
コード例 #32
-1
ファイル: player.py プロジェクト: heylenz/python27
class Player(QWidget):

    fullScreenChanged = pyqtSignal(bool)

    def __init__(self, playlist, parent=None):
        super(Player, self).__init__(parent)

        self.colorDialog = None
        self.trackInfo = ""
        self.statusInfo = ""
        self.duration = 0

        self.player = QMediaPlayer()
        self.playlist = QMediaPlaylist()
        self.player.setPlaylist(self.playlist)

        self.player.durationChanged.connect(self.durationChanged)
        self.player.positionChanged.connect(self.positionChanged)
        self.player.metaDataChanged.connect(self.metaDataChanged)
        self.playlist.currentIndexChanged.connect(self.playlistPositionChanged)
        self.player.mediaStatusChanged.connect(self.statusChanged)
        self.player.bufferStatusChanged.connect(self.bufferingProgress)
        self.player.videoAvailableChanged.connect(self.videoAvailableChanged)
        self.player.error.connect(self.displayErrorMessage)

        self.videoWidget = VideoWidget()
        self.player.setVideoOutput(self.videoWidget)

        self.playlistModel = PlaylistModel()
        self.playlistModel.setPlaylist(self.playlist)

        self.playlistView = QListView()
        self.playlistView.setModel(self.playlistModel)
        self.playlistView.setCurrentIndex(
                self.playlistModel.index(self.playlist.currentIndex(), 0))

        self.playlistView.activated.connect(self.jump)

        self.slider = QSlider(Qt.Horizontal)
        self.slider.setRange(0, self.player.duration() / 1000)

        self.labelDuration = QLabel()
        self.slider.sliderMoved.connect(self.seek)

        self.labelHistogram = QLabel()
        self.labelHistogram.setText("Histogram:")
        self.histogram = HistogramWidget()
        histogramLayout = QHBoxLayout()
        histogramLayout.addWidget(self.labelHistogram)
        histogramLayout.addWidget(self.histogram, 1)

        self.probe = QVideoProbe()
        self.probe.videoFrameProbed.connect(self.histogram.processFrame)
        self.probe.setSource(self.player)

        openButton = QPushButton("Open", clicked=self.open)

        controls = PlayerControls()
        controls.setState(self.player.state())
        controls.setVolume(self.player.volume())
        controls.setMuted(controls.isMuted())

        controls.play.connect(self.player.play)
        controls.pause.connect(self.player.pause)
        controls.stop.connect(self.player.stop)
        controls.next.connect(self.playlist.next)
        controls.previous.connect(self.previousClicked)
        controls.changeVolume.connect(self.player.setVolume)
        controls.changeMuting.connect(self.player.setMuted)
        controls.changeRate.connect(self.player.setPlaybackRate)
        controls.stop.connect(self.videoWidget.update)

        self.player.stateChanged.connect(controls.setState)
        self.player.volumeChanged.connect(controls.setVolume)
        self.player.mutedChanged.connect(controls.setMuted)

        self.fullScreenButton = QPushButton("FullScreen")
        self.fullScreenButton.setCheckable(True)

        self.colorButton = QPushButton("Color Options...")
        self.colorButton.setEnabled(False)
        self.colorButton.clicked.connect(self.showColorDialog)

        displayLayout = QHBoxLayout()
        displayLayout.addWidget(self.videoWidget, 2)
        displayLayout.addWidget(self.playlistView)

        controlLayout = QHBoxLayout()
        controlLayout.setContentsMargins(0, 0, 0, 0)
        controlLayout.addWidget(openButton)
        controlLayout.addStretch(1)
        controlLayout.addWidget(controls)
        controlLayout.addStretch(1)
        controlLayout.addWidget(self.fullScreenButton)
        controlLayout.addWidget(self.colorButton)

        layout = QVBoxLayout()
        layout.addLayout(displayLayout)
        hLayout = QHBoxLayout()
        hLayout.addWidget(self.slider)
        hLayout.addWidget(self.labelDuration)
        layout.addLayout(hLayout)
        layout.addLayout(controlLayout)
        layout.addLayout(histogramLayout)

        self.setLayout(layout)

        if not self.player.isAvailable():
            QMessageBox.warning(self, "Service not available",
                    "The QMediaPlayer object does not have a valid service.\n"
                    "Please check the media service plugins are installed.")

            controls.setEnabled(False)
            self.playlistView.setEnabled(False)
            openButton.setEnabled(False)
            self.colorButton.setEnabled(False)
            self.fullScreenButton.setEnabled(False)

        self.metaDataChanged()

        self.addToPlaylist(playlist)

    def open(self):
        fileNames, _ = QFileDialog.getOpenFileNames(self, "Open Files")
        self.addToPlaylist(fileNames)

    def addToPlaylist(self, fileNames):
        for name in fileNames:
            fileInfo = QFileInfo(name)
            if fileInfo.exists():
                url = QUrl.fromLocalFile(fileInfo.absoluteFilePath())
                if fileInfo.suffix().lower() == 'm3u':
                    self.playlist.load(url)
                else:
                    self.playlist.addMedia(QMediaContent(url))
            else:
                url = QUrl(name)
                if url.isValid():
                    self.playlist.addMedia(QMediaContent(url))

    def durationChanged(self, duration):
        duration /= 1000

        self.duration = duration
        self.slider.setMaximum(duration)

    def positionChanged(self, progress):
        progress /= 1000

        if not self.slider.isSliderDown():
            self.slider.setValue(progress)

        self.updateDurationInfo(progress)

    def metaDataChanged(self):
        if self.player.isMetaDataAvailable():
            self.setTrackInfo("%s - %s" % (
                    self.player.metaData(QMediaMetaData.AlbumArtist),
                    self.player.metaData(QMediaMetaData.Title)))

    def previousClicked(self):
        # Go to the previous track if we are within the first 5 seconds of
        # playback.  Otherwise, seek to the beginning.
        if self.player.position() <= 5000:
            self.playlist.previous()
        else:
            self.player.setPosition(0)

    def jump(self, index):
        if index.isValid():
            self.playlist.setCurrentIndex(index.row())
            self.player.play()

    def playlistPositionChanged(self, position):
        self.playlistView.setCurrentIndex(
                self.playlistModel.index(position, 0))

    def seek(self, seconds):
        self.player.setPosition(seconds * 1000)

    def statusChanged(self, status):
        self.handleCursor(status)

        if status == QMediaPlayer.LoadingMedia:
            self.setStatusInfo("Loading...")
        elif status == QMediaPlayer.StalledMedia:
            self.setStatusInfo("Media Stalled")
        elif status == QMediaPlayer.EndOfMedia:
            QApplication.alert(self)
        elif status == QMediaPlayer.InvalidMedia:
            self.displayErrorMessage()
        else:
            self.setStatusInfo("")

    def handleCursor(self, status):
        if status in (QMediaPlayer.LoadingMedia, QMediaPlayer.BufferingMedia, QMediaPlayer.StalledMedia):
            self.setCursor(Qt.BusyCursor)
        else:
            self.unsetCursor()

    def bufferingProgress(self, progress):
        self.setStatusInfo("Buffering %d%" % progress)

    def videoAvailableChanged(self, available):
        if available:
            self.fullScreenButton.clicked.connect(
                    self.videoWidget.setFullScreen)
            self.videoWidget.fullScreenChanged.connect(
                    self.fullScreenButton.setChecked)

            if self.fullScreenButton.isChecked():
                self.videoWidget.setFullScreen(True)
        else:
            self.fullScreenButton.clicked.disconnect(
                    self.videoWidget.setFullScreen)
            self.videoWidget.fullScreenChanged.disconnect(
                    self.fullScreenButton.setChecked)

            self.videoWidget.setFullScreen(False)

        self.colorButton.setEnabled(available)

    def setTrackInfo(self, info):
        self.trackInfo = info

        if self.statusInfo != "":
            self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo))
        else:
            self.setWindowTitle(self.trackInfo)

    def setStatusInfo(self, info):
        self.statusInfo = info

        if self.statusInfo != "":
            self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo))
        else:
            self.setWindowTitle(self.trackInfo)

    def displayErrorMessage(self):
        self.setStatusInfo(self.player.errorString())

    def updateDurationInfo(self, currentInfo):
        duration = self.duration
        if currentInfo or duration:
            currentTime = QTime((currentInfo/3600)%60, (currentInfo/60)%60,
                    currentInfo%60, (currentInfo*1000)%1000)
            totalTime = QTime((duration/3600)%60, (duration/60)%60,
                    duration%60, (duration*1000)%1000);

            format = 'hh:mm:ss' if duration > 3600 else 'mm:ss'
            tStr = currentTime.toString(format) + " / " + totalTime.toString(format)
        else:
            tStr = ""

        self.labelDuration.setText(tStr)

    def showColorDialog(self):
        if self.colorDialog is None:
            brightnessSlider = QSlider(Qt.Horizontal)
            brightnessSlider.setRange(-100, 100)
            brightnessSlider.setValue(self.videoWidget.brightness())
            brightnessSlider.sliderMoved.connect(
                    self.videoWidget.setBrightness)
            self.videoWidget.brightnessChanged.connect(
                    brightnessSlider.setValue)

            contrastSlider = QSlider(Qt.Horizontal)
            contrastSlider.setRange(-100, 100)
            contrastSlider.setValue(self.videoWidget.contrast())
            contrastSlider.sliderMoved.connect(self.videoWidget.setContrast)
            self.videoWidget.contrastChanged.connect(contrastSlider.setValue)

            hueSlider = QSlider(Qt.Horizontal)
            hueSlider.setRange(-100, 100)
            hueSlider.setValue(self.videoWidget.hue())
            hueSlider.sliderMoved.connect(self.videoWidget.setHue)
            self.videoWidget.hueChanged.connect(hueSlider.setValue)

            saturationSlider = QSlider(Qt.Horizontal)
            saturationSlider.setRange(-100, 100)
            saturationSlider.setValue(self.videoWidget.saturation())
            saturationSlider.sliderMoved.connect(
                    self.videoWidget.setSaturation)
            self.videoWidget.saturationChanged.connect(
                    saturationSlider.setValue)

            layout = QFormLayout()
            layout.addRow("Brightness", brightnessSlider)
            layout.addRow("Contrast", contrastSlider)
            layout.addRow("Hue", hueSlider)
            layout.addRow("Saturation", saturationSlider)

            button = QPushButton("Close")
            layout.addRow(button)

            self.colorDialog = QDialog(self)
            self.colorDialog.setWindowTitle("Color Options")
            self.colorDialog.setLayout(layout)

            button.clicked.connect(self.colorDialog.close)

        self.colorDialog.show()