diff --git a/openlp/core/display/html/display.js b/openlp/core/display/html/display.js index 576b6b0d2..78e1384c9 100644 --- a/openlp/core/display/html/display.js +++ b/openlp/core/display/html/display.js @@ -262,6 +262,7 @@ var Display = { _animationState: AnimationState.NoAnimation, _doTransitions: false, _doItemTransitions: false, + _skipNextTransition: false, _themeApplied: true, _revealConfig: { margin: 0.0, @@ -365,7 +366,7 @@ var Display = { Display.applyTheme(new_slides, is_text); Display._slidesContainer.prepend(new_slides); var currentSlide = Reveal.getIndices(); - if (Display._doItemTransitions && Display._slidesContainer.children.length >= 2) { + if (Display._doItemTransitions && Display._slidesContainer.children.length >= 2 && !Display._skipNextTransition) { // Set the slide one section ahead so we'll stay on the old slide after reinit Reveal.slide(1, currentSlide.v); Display.reinit(); @@ -375,6 +376,7 @@ var Display = { Reveal.slide(0, currentSlide.v); Reveal.sync(); Display._removeLastSection(); + Display._skipNextTransition = false; } }, /** @@ -1121,6 +1123,18 @@ var Display = { } } }, + /** + * Called whenever openlp wants to finish completely with the current text/image slides + * because a different window (eg presentation or vlc) is going to be displaying the next item + * and we don't want any flashbacks to the current slide contents + */ + finishWithCurrentItem: function () { + Display.setTextSlide(''); + var documentBody = $("body")[0]; + documentBody.style.opacity = 1; + Display._skipNextTransition = true; + displayWatcher.pleaseRepaint(); + }, /** * Return the video types supported by the video tag */ diff --git a/openlp/core/display/window.py b/openlp/core/display/window.py index 7ab7ed62b..4fb471eee 100644 --- a/openlp/core/display/window.py +++ b/openlp/core/display/window.py @@ -51,6 +51,10 @@ class DisplayWatcher(QtCore.QObject): """ initialised = QtCore.pyqtSignal(bool) + def __init__(self, parent): + super().__init__() + self._display_window = parent + @QtCore.pyqtSlot(bool) def setInitialised(self, is_initialised): """ @@ -59,6 +63,13 @@ class DisplayWatcher(QtCore.QObject): log.info('Display is initialised: {init}'.format(init=is_initialised)) self.initialised.emit(is_initialised) + @QtCore.pyqtSlot() + def pleaseRepaint(self): + """ + Called from the js in the webengine view when it's requesting a repaint by Qt + """ + self._display_window.webview.update() + class DisplayWindow(QtWidgets.QWidget, RegistryProperties, LogMixin): """ @@ -451,6 +462,16 @@ class DisplayWindow(QtWidgets.QWidget, RegistryProperties, LogMixin): if self.is_display and self.hide_mode == HideMode.Screen: self.setVisible(False) + def finish_with_current_item(self): + """ + This is called whenever the song/image display is followed by eg a presentation or video which + has its own display. + This function ensures that the current item won't flash momentarily when the webengineview + is displayed for a subsequent song or image. + """ + self.run_javascript('Display.finishWithCurrentItem();', True) + self.webview.update() + def set_scale(self, scale): """ Set the HTML scale diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index e0358b301..03ba23208 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -945,23 +945,19 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties): [self.service_item, self.is_live, self._current_hide_mode, slide_no]) else: self._set_theme(self.service_item) - # Reset blanking if needed - if old_item and self.is_live and (old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay) or - self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay)): - self._reset_blank(self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay)) self.info_label.setText(self.service_item.title) self.slide_list = {} - if old_item: - # Close the old item if it's not to be used by the new service item - if not self.service_item.is_media() and not self.service_item.requires_media(): - self.on_media_close() - if old_item.is_command() and not old_item.is_media(): - Registry().execute('{name}_stop'.format(name=old_item.name.lower()), [old_item, self.is_live]) - # if the old item was media which hid the main display then need to reset it if new service item uses it - if self.is_live and self._current_hide_mode is None and old_item.is_media() and not \ - old_item.requires_media() and not self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay): - for display in self.displays: - display.show_display() + # if the old item was text or images (ie doesn't provide its own display) and the new item provides its own + # display then clear out the old item so that it doesn't flash momentarily when next showing text/image + # An item provides its own display if the capability ProvidesOwnDisplay is set or if it's a media item + old_item_provides_own_display = old_item and (old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay) or + old_item.is_media()) + new_item_provides_own_display = (self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay) or + self.service_item.is_media()) + if self.is_live and not old_item_provides_own_display and new_item_provides_own_display: + for display in self.displays: + display.finish_with_current_item() + # Prepare the new slides for text / image items row = 0 width = self.main_window.control_splitter.sizes()[self.split] if self.service_item.is_text(): @@ -997,7 +993,23 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties): row += 1 self.slide_list[str(row)] = row - 1 self.preview_widget.replace_service_item(self.service_item, width, slide_no) + # Tidy up aspects associated with the old item + if old_item: + # Close the old item if it's not to be used by the new service item + if not self.service_item.is_media() and not self.service_item.requires_media(): + self.on_media_close() + if old_item.is_command() and not old_item.is_media(): + Registry().execute('{name}_stop'.format(name=old_item.name.lower()), [old_item, self.is_live]) + # if the old item was media which hid the main display then need to reset it if new service item uses it + if self.is_live and self._current_hide_mode is None and old_item.is_media() and not \ + old_item.requires_media() and not self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay): + for display in self.displays: + display.show_display() self.enable_tool_bar(self.service_item) + # Reset blanking if needed + if old_item and self.is_live and (old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay) or + self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay)): + self._reset_blank(self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay)) if self.service_item.is_media() or self.service_item.requires_media(): self._set_theme(self.service_item) if self.service_item.is_command(): diff --git a/tests/openlp_core/display/test_window.py b/tests/openlp_core/display/test_window.py index 5f1f224b3..2b43f0f8e 100644 --- a/tests/openlp_core/display/test_window.py +++ b/tests/openlp_core/display/test_window.py @@ -622,3 +622,18 @@ def test_display_watcher_set_initialised(): # THEN: initialised should have been emitted mocked_initialised.emit.assert_called_once_with(True) + + +def test_display_watcher_please_repaint(display_window_env, mock_settings): + """ + Test that the repaint is initiated + """ + # GIVEN: A DisplayWindow instance with mocked out webview + display_window = DisplayWindow() + display_window.webview = MagicMock() + + # WHEN: pleaseRepaint is called on the DisplayWatcher + display_window.display_watcher.pleaseRepaint() + + # THEN: Qt update for the webview should have been triggered + assert display_window.webview.update.call_count == 1