class AnimationWidget(QWidget):
    """Widget for interatviely making animations using the napari viewer.

    Parameters
    ----------
    viewer : napari.Viewer
        napari viewer.

    Attributes
    ----------
    viewer : napari.Viewer
        napari viewer.
    animation : napari_animation.Animation
        napari-animation animation in sync with the GUI.
    """
    def __init__(self, viewer: Viewer, parent=None):
        super().__init__(parent=parent)

        # Store reference to viewer and create animation
        self.viewer = viewer
        self.animation = Animation(self.viewer)

        # Initialise User Interface
        self.keyframesListControlWidget = KeyFrameListControlWidget(
            animation=self.animation, parent=self)
        self.keyframesListWidget = KeyFramesListWidget(
            self.animation.key_frames, parent=self)
        self.frameWidget = FrameWidget(parent=self)
        self.saveButton = QPushButton("Save Animation", parent=self)
        self.animationSlider = QSlider(Qt.Horizontal, parent=self)
        self.animationSlider.setToolTip("Scroll through animation")
        self.animationSlider.setRange(0, len(self.animation._frames) - 1)

        # Create layout
        self.setLayout(QVBoxLayout())
        self.layout().addWidget(self.keyframesListControlWidget)
        self.layout().addWidget(self.keyframesListWidget)
        self.layout().addWidget(self.frameWidget)
        self.layout().addWidget(self.saveButton)
        self.layout().addWidget(self.animationSlider)

        # establish key bindings and callbacks
        self._add_keybind_callbacks()
        self._add_callbacks()

    def _add_keybind_callbacks(self):
        """Bind keys"""
        self._keybindings = [
            ("Alt-f", self._capture_keyframe_callback),
            ("Alt-r", self._replace_keyframe_callback),
            ("Alt-d", self._delete_keyframe_callback),
            ("Alt-a", lambda e: self.animation.key_frames.select_next()),
            ("Alt-b", lambda e: self.animation.key_frames.select_previous()),
        ]
        for key, cb in self._keybindings:
            self.viewer.bind_key(key, cb)

    def _add_callbacks(self):
        """Establish callbacks"""
        self.keyframesListControlWidget.deleteButton.clicked.connect(
            self._delete_keyframe_callback)
        self.keyframesListControlWidget.captureButton.clicked.connect(
            self._capture_keyframe_callback)
        self.saveButton.clicked.connect(self._save_callback)
        self.animationSlider.valueChanged.connect(self._on_slider_moved)
        self.animation._frames.events.n_frames.connect(self._nframes_changed)

        keyframe_list = self.animation.key_frames
        keyframe_list.events.inserted.connect(self._on_keyframes_changed)
        keyframe_list.events.removed.connect(self._on_keyframes_changed)
        keyframe_list.events.changed.connect(self._on_keyframes_changed)
        keyframe_list.selection.events.active.connect(
            self._on_active_keyframe_changed)

    def _input_state(self):
        """Get current state of input widgets as {key->value} parameters."""
        return {
            "steps": int(self.frameWidget.stepsSpinBox.value()),
            "ease": self.frameWidget.get_easing_func(),
        }

    def _capture_keyframe_callback(self, event=None):
        """Record current key-frame"""
        self.animation.capture_keyframe(**self._input_state())

    def _replace_keyframe_callback(self, event=None):
        """Replace current key-frame with new view"""
        self.animation.capture_keyframe(**self._input_state(), insert=False)

    def _delete_keyframe_callback(self, event=None):
        """Delete current key-frame"""
        if self.animation.key_frames.selection.active:
            self.animation.key_frames.remove_selected()
        else:
            raise ValueError("No selected keyframe to delete !")

    def _on_keyframes_changed(self, event=None):
        has_frames = bool(self.animation.key_frames)

        self.keyframesListControlWidget.deleteButton.setEnabled(has_frames)
        self.keyframesListWidget.setEnabled(has_frames)
        self.frameWidget.setEnabled(has_frames)

    def _on_active_keyframe_changed(self, event=None):
        n_frames = len(self.animation._frames)
        active_keyframe = event.value

        if active_keyframe and n_frames:
            self.animationSlider.blockSignals(True)
            kf1_list = [
                self.animation._frames._frame_index[n][0]
                for n in range(n_frames)
            ]
            frame_index = kf1_list.index(active_keyframe)
            self.animationSlider.setValue(frame_index)
            self.animationSlider.blockSignals(False)

        self.keyframesListControlWidget.deleteButton.setEnabled(
            bool(active_keyframe))

    def _on_slider_moved(self, event=None):
        frame_index = event
        if frame_index < len(self.animation._frames) - 1:
            with self.animation.key_frames.selection.events.active.blocker():
                self.animation.set_movie_frame_index(frame_index)

    def _save_callback(self, event=None):

        if len(self.animation.key_frames) < 2:
            error_dialog = QErrorMessage()
            error_dialog.showMessage(
                f"You need at least two key frames to generate \
                an animation. Your only have {len(self.animation.key_frames)}")
            error_dialog.exec_()

        else:
            filters = (
                "Video files (*.mp4 *.gif *.mov *.avi *.mpg *.mpeg *.mkv *.wmv)"
                ";;Folder of PNGs (*)"  # sep filters with ";;"
            )
            filename, _filter = QFileDialog.getSaveFileName(
                self, "Save animation", str(Path.home()), filters)
            if filename:
                self.animation.animate(filename)

    def _nframes_changed(self, event):
        has_frames = bool(event.value)
        self.animationSlider.setEnabled(has_frames)
        self.animationSlider.blockSignals(has_frames)
        self.animationSlider.setMaximum(event.value - 1 if has_frames else 0)

    def closeEvent(self, ev) -> None:
        # release callbacks
        for key, _ in self._keybindings:
            self.viewer.bind_key(key, None)
        return super().closeEvent(ev)
class MiscellaneousLauncher(QWidget):
    def __init__(self, parent):
        super().__init__()
        # set title
        self.setWindowTitle(config.thisTranslation["cp4"])
        # set up variables
        self.parent = parent
        # setup interface
        self.setupUI()

    def setupUI(self):
        mainLayout = QGridLayout()
        leftLayout = QVBoxLayout()
        rightLayout = QVBoxLayout()
        self.highLightLauncher = HighlightLauncher(self)
        leftLayout.addWidget(self.highLightLauncher)
        rightLayout.addWidget(self.noteEditor())
        rightLayout.addWidget(self.textToSpeechUtility())
        rightLayout.addWidget(self.utilities())
        rightLayout.addStretch()
        mainLayout.addLayout(leftLayout, 0, 0, 1, 2)
        mainLayout.addLayout(rightLayout, 0, 2, 1, 1)
        self.setLayout(mainLayout)

    def noteEditor(self):
        box = QGroupBox(config.thisTranslation["note_editor"])
        subLayout = QHBoxLayout()
        button = QPushButton(config.thisTranslation["menu7_create"])
        button.setToolTip(config.thisTranslation["menu7_create"])
        button.clicked.connect(self.parent.parent.createNewNoteFile)
        subLayout.addWidget(button)
        button = QPushButton(config.thisTranslation["menu7_open"])
        button.setToolTip(config.thisTranslation["menu7_open"])
        button.clicked.connect(self.parent.parent.openTextFileDialog)
        subLayout.addWidget(button)
        box.setLayout(subLayout)
        return box

    def utilities(self):
        box = QGroupBox(config.thisTranslation["utilities"])
        subLayout = QHBoxLayout()
        button = QPushButton(config.thisTranslation["presentation"])
        button.setToolTip(config.thisTranslation["presentation"])
        button.clicked.connect(
            lambda: self.parent.parent.runPlugin("Presentation"))
        subLayout.addWidget(button)
        if config.isYoutubeDownloaderInstalled:
            button = QPushButton(config.thisTranslation["youtube_utility"])
            button.setToolTip(config.thisTranslation["youtube_utility"])
            button.clicked.connect(self.parent.parent.openYouTube)
            subLayout.addWidget(button)
        box.setLayout(subLayout)
        return box

    def textToSpeechUtility(self):
        box = QGroupBox(config.thisTranslation["tts_utility"])

        layout = QVBoxLayout()

        subLayout = QHBoxLayout()
        self.ttsEdit = QLineEdit()
        self.ttsEdit.setClearButtonEnabled(True)
        self.ttsEdit.setToolTip(config.thisTranslation["enter_text_here"])
        self.ttsEdit.returnPressed.connect(self.speakText)
        subLayout.addWidget(self.ttsEdit)
        layout.addLayout(subLayout)

        self.ttsSlider = QSlider(Qt.Horizontal)
        self.ttsSlider.setToolTip(config.thisTranslation["adjustSpeed"])
        self.ttsSlider.setMinimum(10)
        self.ttsSlider.setMaximum(310)
        self.ttsSlider.setValue(config.espeakSpeed if config.espeak else (
            160 + config.qttsSpeed * 150))
        self.ttsSlider.valueChanged.connect(
            self.changeEspeakSpeed if config.espeak else self.changeQttsSpeed)
        layout.addWidget(self.ttsSlider)

        subLayout = QHBoxLayout()

        self.languageCombo = QComboBox()
        subLayout.addWidget(self.languageCombo)
        if config.espeak:
            languages = TtsLanguages().isoLang2epeakLang
        else:
            languages = TtsLanguages().isoLang2qlocaleLang
        self.languageCodes = list(languages.keys())
        for code in self.languageCodes:
            self.languageCombo.addItem(languages[code][1])
        # Check if selected tts engine has the language user specify.
        if not (config.ttsDefaultLangauge in self.languageCodes):
            config.ttsDefaultLangauge = "en"
        # Set initial index
        # It is essential.  Otherwise, default tts language is changed by defaultTtsLanguageChanged method.
        ttsLanguageIndex = self.languageCodes.index(config.ttsDefaultLangauge)
        self.languageCombo.setCurrentIndex(ttsLanguageIndex)
        # Change default tts language as users select a new language
        self.languageCombo.currentIndexChanged.connect(
            self.defaultTtsLanguageChanged)

        button = QPushButton(config.thisTranslation["speak"])
        button.setToolTip(config.thisTranslation["speak"])
        button.clicked.connect(self.speakText)
        subLayout.addWidget(button)
        button = QPushButton(config.thisTranslation["stop"])
        button.setToolTip(config.thisTranslation["stop"])
        button.clicked.connect(
            self.parent.parent.textCommandParser.stopTtsAudio)
        subLayout.addWidget(button)
        layout.addLayout(subLayout)

        box.setLayout(layout)
        return box

    def defaultTtsLanguageChanged(self, index):
        config.ttsDefaultLangauge = self.languageCodes[index]

    def speakText(self):
        text = self.ttsEdit.text()
        if text:
            if config.isTtsInstalled:
                if ":::" in text:
                    text = text.split(":::")[-1]
                command = "SPEAK:::{0}:::{1}".format(
                    self.languageCodes[self.languageCombo.currentIndex()],
                    text)
                self.parent.isRefreshing = True
                self.parent.runTextCommand(command)
                self.parent.isRefreshing = False
            else:
                self.parent.displayMessage(
                    config.thisTranslation["message_noSupport"])
        else:
            self.parent.displayMessage(
                config.thisTranslation["enterTextFirst"])

    def changeQttsSpeed(self, value):
        config.qttsSpeed = (value * (1.5 / 300) - .5)

    def changeEspeakSpeed(self, value):
        config.espeakSpeed = value

    def refresh(self):
        ttsLanguageIndex = self.languageCodes.index(config.ttsDefaultLangauge)
        self.languageCombo.setCurrentIndex(ttsLanguageIndex)
        self.highLightLauncher.refresh()
Example #3
0
class ConfigurePresentationWindow(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        # set title
        self.setWindowTitle("Configure Presentation")
        # set variables
        self.setupVariables()
        # setup Hymn Lyrics
        from glob import glob
        from pathlib import Path
        self.books = sorted([
            Path(filename).stem
            for filename in glob(r"./marvelData/books/Hymn Lyrics*.book")
        ])
        if len(self.books) > 0:
            self.setMinimumHeight(550)
        # setup interface
        self.setupUI()

    def setupVariables(self):
        pass

    def setupUI(self):

        from functools import partial
        from qtpy.QtCore import Qt
        from qtpy.QtWidgets import QHBoxLayout, QFormLayout, QSlider, QPushButton, QPlainTextEdit, QCheckBox, QComboBox
        from qtpy.QtWidgets import QRadioButton, QWidget, QVBoxLayout, QListView, QSpacerItem, QSizePolicy

        layout = QHBoxLayout()

        layout1 = QFormLayout()

        self.fontsizeslider = QSlider(Qt.Horizontal)
        self.fontsizeslider.setMinimum(1)
        self.fontsizeslider.setMaximum(12)
        self.fontsizeslider.setTickInterval(2)
        self.fontsizeslider.setSingleStep(2)
        self.fontsizeslider.setValue(config.presentationFontSize / 0.5)
        self.fontsizeslider.setToolTip(str(config.presentationFontSize))
        self.fontsizeslider.valueChanged.connect(
            self.presentationFontSizeChanged)
        layout1.addRow("Font Size", self.fontsizeslider)

        self.changecolorbutton = QPushButton()
        buttonStyle = "QPushButton {0}background-color: {2}; color: {3};{1}".format(
            "{", "}", config.presentationColorOnDarkTheme if config.theme
            == "dark" else config.presentationColorOnLightTheme,
            "white" if config.theme == "dark" else "black")
        self.changecolorbutton.setStyleSheet(buttonStyle)
        self.changecolorbutton.setToolTip("Change Color")
        self.changecolorbutton.clicked.connect(self.changeColor)
        layout1.addRow("Font Color", self.changecolorbutton)

        self.marginslider = QSlider(Qt.Horizontal)
        self.marginslider.setMinimum(0)
        self.marginslider.setMaximum(200)
        self.marginslider.setTickInterval(50)
        self.marginslider.setSingleStep(50)
        self.marginslider.setValue(config.presentationMargin)
        self.marginslider.setToolTip(str(config.presentationMargin))
        self.marginslider.valueChanged.connect(self.presentationMarginChanged)
        layout1.addRow("Margin", self.marginslider)

        self.verticalpositionslider = QSlider(Qt.Horizontal)
        self.verticalpositionslider.setMinimum(10)
        self.verticalpositionslider.setMaximum(90)
        self.verticalpositionslider.setTickInterval(10)
        self.verticalpositionslider.setSingleStep(10)
        self.verticalpositionslider.setValue(
            config.presentationVerticalPosition)
        self.verticalpositionslider.setToolTip(
            str(config.presentationVerticalPosition))
        self.verticalpositionslider.valueChanged.connect(
            self.presentationVerticalPositionChanged)
        layout1.addRow("Vertical Position", self.verticalpositionslider)

        self.horizontalpositionslider = QSlider(Qt.Horizontal)
        self.horizontalpositionslider.setMinimum(10)
        self.horizontalpositionslider.setMaximum(90)
        self.horizontalpositionslider.setTickInterval(10)
        self.horizontalpositionslider.setSingleStep(10)
        self.horizontalpositionslider.setValue(
            config.presentationHorizontalPosition)
        self.horizontalpositionslider.setToolTip(
            str(config.presentationHorizontalPosition))
        self.horizontalpositionslider.valueChanged.connect(
            self.presentationHorizontalPositionChanged)
        layout1.addRow("Horizontal Position", self.horizontalpositionslider)

        self.showBibleSelection = QRadioButton()
        self.showBibleSelection.setChecked(True)
        self.showBibleSelection.clicked.connect(
            lambda: self.selectRadio("bible"))
        layout1.addRow("Bible", self.showBibleSelection)

        if len(self.books) > 0:
            self.showHymnsSelection = QRadioButton()
            self.showHymnsSelection.setChecked(False)
            self.showHymnsSelection.clicked.connect(
                lambda: self.selectRadio("hymns"))
            layout1.addRow("Hymns", self.showHymnsSelection)

        # Second column

        layout2 = QVBoxLayout()

        self.bibleWidget = QWidget()
        self.bibleLayout = QFormLayout()

        checkbox = QCheckBox()
        checkbox.setText("")
        checkbox.setChecked(config.presentationParser)
        checkbox.stateChanged.connect(self.presentationParserChanged)
        checkbox.setToolTip("Parse bible verse reference in the entered text")
        self.bibleLayout.addRow("Bible Reference", checkbox)

        versionCombo = QComboBox()
        self.bibleVersions = self.parent.textList
        versionCombo.addItems(self.bibleVersions)
        initialIndex = 0
        if config.mainText in self.bibleVersions:
            initialIndex = self.bibleVersions.index(config.mainText)
        versionCombo.setCurrentIndex(initialIndex)
        versionCombo.currentIndexChanged.connect(self.changeBibleVersion)
        self.bibleLayout.addRow("Bible Version", versionCombo)

        self.textEntry = QPlainTextEdit("John 3:16; Rm 5:8")
        self.bibleLayout.addRow(self.textEntry)

        button = QPushButton("Presentation")
        button.setToolTip("Go to Presentation")
        button.clicked.connect(self.goToPresentation)
        self.bibleLayout.addWidget(button)

        self.bibleLayout.addItem(
            QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding))

        self.bibleWidget.setLayout(self.bibleLayout)

        self.hymnWidget = QWidget()
        self.hymnLayout = QFormLayout()

        selected = 0
        book = "Hymn Lyrics - English"
        if book in self.books:
            selected = self.books.index(book)
        self.bookList = QComboBox()
        self.bookList.addItems(self.books)
        self.bookList.setCurrentIndex(selected)
        self.bookList.currentIndexChanged.connect(self.selectHymnBook)
        self.hymnLayout.addWidget(self.bookList)

        self.chapterlist = QListView()
        self.chapterlist.clicked.connect(self.selectHymn)
        # self.chapterlist.selectionModel().selectionChanged.connect(self.selectHymn)
        self.hymnLayout.addWidget(self.chapterlist)

        self.buttons = []
        for count in range(0, 10):
            hymnButton = QPushButton()
            hymnButton.setText(" ")
            hymnButton.setEnabled(False)
            hymnButton.clicked.connect(partial(self.selectParagraph, count))
            self.hymnLayout.addWidget(hymnButton)
            self.buttons.append(hymnButton)

        self.selectHymnBook(selected)

        self.hymnWidget.setLayout(self.hymnLayout)
        self.hymnWidget.hide()

        layout2.addWidget(self.bibleWidget)
        if len(self.books) > 0:
            layout2.addWidget(self.hymnWidget)

        layout.addLayout(layout1)
        layout.addLayout(layout2)
        self.setLayout(layout)

    def selectRadio(self, option):
        if option == "bible":
            self.bibleWidget.show()
            if len(self.books) > 0:
                self.hymnWidget.hide()
        elif option == "hymns":
            self.bibleWidget.hide()
            if len(self.books) > 0:
                self.hymnWidget.show()

    def selectHymnBook(self, option):
        from ToolsSqlite import Book
        from qtpy.QtCore import QStringListModel
        if len(self.books) > 0:
            self.hymnBook = self.books[option]
            self.hymns = sorted(Book(self.hymnBook).getTopicList())
            self.chapterModel = QStringListModel(self.hymns)
            self.chapterlist.setModel(self.chapterModel)

    def selectHymn(self, option):
        from ToolsSqlite import Book
        row = option.row()
        self.hymn = self.hymns[row]
        book = Book(self.hymnBook)
        sections = book.getParagraphSectionsByChapter(self.hymn)
        count = 0
        for button in self.buttons:
            if count < len(sections):
                section = sections[count]
                text = section.replace("<br>", "")[:30]
                button.setText(text)
                button.setEnabled(True)
            else:
                button.setText(" ")
                button.setEnabled(False)
            count += 1

    def selectParagraph(self, paragraph):
        command = "SCREENBOOK:::{0}:::{1}:::{2}".format(
            self.hymnBook, self.hymn, paragraph)
        self.parent.runTextCommand(command)

    def goToPresentation(self):
        command = "SCREEN:::{0}".format(self.textEntry.toPlainText())
        self.parent.runTextCommand(command)

    def changeColor(self):

        from qtpy.QtGui import QColor
        from qtpy.QtWidgets import QColorDialog

        color = QColorDialog.getColor(
            QColor(config.presentationColorOnDarkTheme if config.theme ==
                   "dark" else config.presentationColorOnLightTheme), self)
        if color.isValid():
            colorName = color.name()
            if config.theme == "dark":
                config.presentationColorOnDarkTheme = colorName
            else:
                config.presentationColorOnLightTheme = colorName
            buttonStyle = "QPushButton {0}background-color: {2}; color: {3};{1}".format(
                "{", "}", colorName,
                "white" if config.theme == "dark" else "black")
            self.changecolorbutton.setStyleSheet(buttonStyle)

    def presentationFontSizeChanged(self, value):
        config.presentationFontSize = value * 0.5
        self.fontsizeslider.setToolTip(str(config.presentationFontSize))

    def presentationMarginChanged(self, value):
        config.presentationMargin = value
        self.marginslider.setToolTip(str(config.presentationMargin))

    def presentationVerticalPositionChanged(self, value):
        config.presentationVerticalPosition = value
        self.verticalpositionslider.setToolTip(
            str(config.presentationVerticalPosition))

    def presentationHorizontalPositionChanged(self, value):
        config.presentationHorizontalPosition = value
        self.horizontalpositionslider.setValue(
            config.presentationHorizontalPosition)

    def presentationParserChanged(self):
        config.presentationParser = not config.presentationParser

    def changeBibleVersion(self, index):
        if __name__ == '__main__':
            config.mainText = self.bibleVersions[index]
        else:
            command = "TEXT:::{0}".format(self.bibleVersions[index])
            self.parent.runTextCommand(command)