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()
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)