diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 6992076c7..028a01f8d 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -29,6 +29,7 @@ from threading import Lock from PyQt5 import QtCore, QtGui, QtWidgets +from openlp.core.state import State from openlp.core.common import SlideLimits from openlp.core.common.actions import ActionList, CategoryOrder from openlp.core.common.i18n import UiStrings, translate @@ -1469,24 +1470,26 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties): def on_media_start(self, item): """ - Respond to the arrival of a media service item + Respond to the arrival of a media service item but only run if we have media :param item: The service item to be processed """ - if self.is_live and self.get_hide_mode() == HideMode.Theme: - self.media_controller.load_video(self.controller_type, item, HideMode.Blank) - self.set_hide_mode(HideMode.Blank) - elif self.is_live or item.is_media(): - # avoid loading the video if this is preview and the media is background - self.media_controller.load_video(self.controller_type, item, self.get_hide_mode()) - if not self.is_live: - self.preview_display.show() + if State().check_preconditions('media'): + if self.is_live and self.get_hide_mode() == HideMode.Theme: + self.media_controller.load_video(self.controller_type, item, HideMode.Blank) + self.set_hide_mode(HideMode.Blank) + elif self.is_live or item.is_media(): + # avoid loading the video if this is preview and the media is background + self.media_controller.load_video(self.controller_type, item, self.get_hide_mode()) + if not self.is_live: + self.preview_display.show() def on_media_close(self): """ - Respond to a request to close the Video + Respond to a request to close the Video if we have media """ - self.media_controller.media_reset(self) + if State().check_preconditions('media'): + self.media_controller.media_reset(self) def _reset_blank(self, no_theme): """ diff --git a/openlp/plugins/presentations/lib/applescriptbasecontroller.py b/openlp/plugins/presentations/lib/applescriptbasecontroller.py index 3b2ec3fa7..a8cadf60e 100644 --- a/openlp/plugins/presentations/lib/applescriptbasecontroller.py +++ b/openlp/plugins/presentations/lib/applescriptbasecontroller.py @@ -23,7 +23,12 @@ This module is a base to be used for Mac OS X presentation modules using applesc to control presentation application, such as keynote and powerpoint for mac """ import logging -import applescript +try: + import applescript + APPLESCRIPT_AVAILABLE = True +except ImportError: + APPLESCRIPT_AVAILABLE = False + from openlp.core.common import is_macosx from openlp.core.display.screens import ScreenList @@ -55,7 +60,8 @@ class AppleScriptBaseController(PresentationController): """ super(AppleScriptBaseController, self).__init__(plugin, name, doc) # Script expected to be overwritten by subclasses - self.applescript = applescript.AppleScript(DEFAULT_APPLESCRIPT) + if APPLESCRIPT_AVAILABLE: + self.applescript = applescript.AppleScript(DEFAULT_APPLESCRIPT) def check_available(self): """ diff --git a/openlp/plugins/presentations/lib/keynotecontroller.py b/openlp/plugins/presentations/lib/keynotecontroller.py index 76b086396..56e2542ab 100644 --- a/openlp/plugins/presentations/lib/keynotecontroller.py +++ b/openlp/plugins/presentations/lib/keynotecontroller.py @@ -23,7 +23,11 @@ This module is for controlling keynote. """ import logging import shutil -import applescript +try: + import applescript + APPLESCRIPT_AVAILABLE = True +except ImportError: + APPLESCRIPT_AVAILABLE = False from openlp.plugins.presentations.lib.applescriptbasecontroller import AppleScriptBaseController,\ AppleScriptBaseDocument @@ -236,13 +240,14 @@ class KeynoteController(AppleScriptBaseController): """ log.debug('Initialising') super(KeynoteController, self).__init__(plugin, 'Keynote', KeynoteDocument) - # Compiled script expected to be set by subclasses - try: - self.applescript = applescript.AppleScript(KEYNOTE_APPLESCRIPT) - except applescript.ScriptError: - log.exception('Compilation of Keynote applescript failed') - self.supports = ['key'] - self.also_supports = ['ppt', 'pps', 'pptx', 'ppsx', 'pptm'] + if APPLESCRIPT_AVAILABLE: + # Compiled script expected to be set by subclasses + try: + self.applescript = applescript.AppleScript(KEYNOTE_APPLESCRIPT) + except applescript.ScriptError: + log.exception('Compilation of Keynote applescript failed') + self.supports = ['key'] + self.also_supports = ['ppt', 'pps', 'pptx', 'ppsx', 'pptm'] def check_available(self): """ diff --git a/openlp/plugins/presentations/lib/powerpointmaccontroller.py b/openlp/plugins/presentations/lib/powerpointmaccontroller.py index 1662a7f32..9b0dea237 100644 --- a/openlp/plugins/presentations/lib/powerpointmaccontroller.py +++ b/openlp/plugins/presentations/lib/powerpointmaccontroller.py @@ -22,7 +22,11 @@ This module is for controlling keynote. """ import logging -import applescript +try: + import applescript + APPLESCRIPT_AVAILABLE = True +except ImportError: + APPLESCRIPT_AVAILABLE = False try: import fitz @@ -277,13 +281,14 @@ class PowerPointMacController(AppleScriptBaseController): """ log.debug('Initialising') super(PowerPointMacController, self).__init__(plugin, 'PowerPointMac', PowerPointMacDocument) - # Compiled script expected to be set by subclasses - try: - self.applescript = applescript.AppleScript(POWERPOINT_MAC_APPLESCRIPT) - except applescript.ScriptError: - log.exception('Compilation of Powerpoint applescript failed') - self.supports = ['ppt', 'pps', 'pptx', 'ppsx', 'pptm'] - self.also_supports = ['odp'] + if APPLESCRIPT_AVAILABLE: + # Compiled script expected to be set by subclasses + try: + self.applescript = applescript.AppleScript(POWERPOINT_MAC_APPLESCRIPT) + except applescript.ScriptError: + log.exception('Compilation of Powerpoint applescript failed') + self.supports = ['ppt', 'pps', 'pptx', 'ppsx', 'pptm'] + self.also_supports = ['odp'] def check_available(self): """ diff --git a/tests/functional/openlp_core/ui/test_slidecontroller.py b/tests/functional/openlp_core/ui/test_slidecontroller.py index 4a6a4dbf9..2c92c1bbc 100644 --- a/tests/functional/openlp_core/ui/test_slidecontroller.py +++ b/tests/functional/openlp_core/ui/test_slidecontroller.py @@ -25,6 +25,7 @@ from unittest.mock import MagicMock, patch, sentinel from PyQt5 import QtCore, QtGui +from openlp.core.state import State from openlp.core.common.registry import Registry from openlp.core.lib import ServiceItemAction from openlp.core.ui import HideMode @@ -771,7 +772,7 @@ def test_reload_theme(mock_settings): @patch.object(Registry, 'execute') -def test_process_item(mocked_execute, registry): +def test_process_item(mocked_execute, registry, state_media): """ Test that presentation service-items is closed when followed by a media service-item """ @@ -818,6 +819,100 @@ def test_process_item(mocked_execute, registry): 'The presentation should have been stopped.' +@patch.object(Registry, 'execute') +def test_process_item_song_vlc(mocked_execute, registry, state_media): + """ + Test that media is started if VLC is present. + """ + # GIVEN: A mocked presentation service item, a mocked media service item, a mocked Registry.execute + # and a slide controller with many mocks. + mocked_pres_item = MagicMock() + mocked_pres_item.name = 'mocked_song_item' + mocked_pres_item.is_command.return_value = False + mocked_pres_item.is_media.return_value = True + mocked_pres_item.is_image.return_value = False + mocked_pres_item.from_service = False + mocked_pres_item.get_frames.return_value = [] + mocked_media_item = MagicMock() + mocked_media_item.name = 'mocked_media_item' + mocked_media_item.is_command.return_value = True + mocked_media_item.is_media.return_value = True + mocked_media_item.is_image.return_value = False + mocked_media_item.from_service = False + mocked_media_item.get_frames.return_value = [] + mocked_main_window = MagicMock() + Registry().register('main_window', mocked_main_window) + Registry().register('media_controller', MagicMock()) + Registry().register('application', MagicMock()) + slide_controller = SlideController(None) + slide_controller.service_item = mocked_pres_item + slide_controller.is_live = False + slide_controller.preview_widget = MagicMock() + slide_controller.preview_display = MagicMock() + slide_controller.enable_tool_bar = MagicMock() + slide_controller.slide_selected = MagicMock() + slide_controller.on_stop_loop = MagicMock() + slide_controller.info_label = MagicMock() + slide_controller.displays = [MagicMock()] + slide_controller.split = 0 + slide_controller.type_prefix = 'test' + + # WHEN: _process_item is called + slide_controller._process_item(mocked_media_item, 0) + + # THEN: Registry.execute should have been called to stop the presentation + assert 1 == slide_controller.media_controller.load_video.call_count, ' Load_video should have been called 1 times' + assert 2 == slide_controller.preview_display.load_verses.call_count, 'Execute should have been called 2 times' + + +@patch.object(Registry, 'execute') +def test_process_item_song_no_vlc(mocked_execute, registry, state_media): + """ + Test that media is started if VLC is present. + """ + # GIVEN: A mocked presentation service item, a mocked media service item, a mocked Registry.execute + # and a slide controller with many mocks. + State().update_pre_conditions("media", False) + State().flush_preconditions() + mocked_pres_item = MagicMock() + mocked_pres_item.name = 'mocked_song_item' + mocked_pres_item.is_command.return_value = False + mocked_pres_item.is_media.return_value = True + mocked_pres_item.is_image.return_value = False + mocked_pres_item.from_service = False + mocked_pres_item.get_frames.return_value = [] + mocked_media_item = MagicMock() + mocked_media_item.name = 'mocked_media_item' + mocked_media_item.is_command.return_value = True + mocked_media_item.is_media.return_value = True + mocked_media_item.is_image.return_value = False + mocked_media_item.from_service = False + mocked_media_item.get_frames.return_value = [] + mocked_main_window = MagicMock() + Registry().register('main_window', mocked_main_window) + Registry().register('media_controller', MagicMock()) + Registry().register('application', MagicMock()) + slide_controller = SlideController(None) + slide_controller.service_item = mocked_pres_item + slide_controller.is_live = False + slide_controller.preview_widget = MagicMock() + slide_controller.preview_display = MagicMock() + slide_controller.enable_tool_bar = MagicMock() + slide_controller.slide_selected = MagicMock() + slide_controller.on_stop_loop = MagicMock() + slide_controller.info_label = MagicMock() + slide_controller.displays = [MagicMock()] + slide_controller.split = 0 + slide_controller.type_prefix = 'test' + + # WHEN: _process_item is called + slide_controller._process_item(mocked_media_item, 0) + + # THEN: Registry.execute should have been called to stop the presentation + assert 0 == slide_controller.media_controller.load_video.call_count, ' Load_video should have been called 0 times' + assert 2 == slide_controller.preview_display.load_verses.call_count, 'Execute should have been called 2 times' + + def test_live_stolen_focus_shortcuts(settings): """ Test that all the needed shortcuts are available in scenarios where Live has stolen focus.