diff --git a/openlp/core/display/render.py b/openlp/core/display/render.py index 22222de5b..5552a620c 100644 --- a/openlp/core/display/render.py +++ b/openlp/core/display/render.py @@ -510,7 +510,6 @@ class ThemePreviewRenderer(LogMixin, DisplayWindow): self.force_page = force_page if not self.force_page: self.set_theme(theme_data, is_sync=True) - self.theme_height = theme_data.font_main_height slides = self.format_slide(VERSE, None) verses = dict() verses['title'] = TITLE @@ -525,26 +524,6 @@ class ThemePreviewRenderer(LogMixin, DisplayWindow): self.force_page = False return None - def get_theme(self, item): - """ - :param item: The :class:`~openlp.core.lib.serviceitem.ServiceItem` item object - :return string: The name of the theme to be used - - """ - # Just assume we use the global theme. - theme_name = Registry().get('theme_manager').global_theme - # The theme level is either set to Service or Item. Use the service theme if one is set. We also have to use the - # service theme, even when the theme level is set to Item, because the item does not necessarily have to have a - # theme. - if self.theme_level != ThemeLevel.Global: - # When the theme level is at Service and we actually have a service theme then use it. - if self.theme_level == ThemeLevel.Service: - theme_name = Registry().get('service_manager').service_theme - # If we have Item level and have an item theme then use it. - if self.theme_level == ThemeLevel.Song and item.theme: - theme_name = item.theme - return theme_name - def format_slide(self, text, item): """ Calculate how much text can fit on a slide. @@ -557,11 +536,8 @@ class ThemePreviewRenderer(LogMixin, DisplayWindow): QtWidgets.QApplication.instance().processEvents() self.log_debug('format slide') if item: - theme_name = self.get_theme(item) - theme_data = Registry().get('theme_manager').get_theme_data(theme_name) - self.theme_height = theme_data.font_main_height # Set theme for preview - self.set_theme(theme_data) + self.set_theme(item.get_theme_data(self.theme_level)) # Add line endings after each line of text used for bibles. line_end = '
' if item and item.is_capable(ItemCapabilities.NoLineBreaks): @@ -821,7 +797,6 @@ class Renderer(RegistryBase, RegistryProperties, ThemePreviewRenderer): # If the display is not show'ed and hidden like this webegine will not render self.show() self.hide() - self.theme_height = 0 self.theme_level = ThemeLevel.Global def set_theme_level(self, theme_level): diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 9ff4176e0..a9bc61511 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -33,11 +33,12 @@ from pathlib import Path from PyQt5 import QtGui from openlp.core.state import State -from openlp.core.common import md5_hash +from openlp.core.common import ThemeLevel, md5_hash from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import translate from openlp.core.common.mixins import RegistryProperties from openlp.core.common.settings import Settings +from openlp.core.common.registry import Registry from openlp.core.display.render import remove_tags, render_tags, render_chords_for_printing from openlp.core.lib import ItemCapabilities from openlp.core.ui.icons import UiIcons @@ -91,7 +92,6 @@ class ServiceItem(RegistryProperties): self.capabilities = [] self.is_valid = True self.icon = None - self.theme_data = None self.main = None self.footer = None self.bg_image_bytes = None @@ -115,6 +115,29 @@ class ServiceItem(RegistryProperties): self._new_item() self.metadata = [] + def get_theme_data(self, theme_level=None): + """ + Get the theme appropriate for this item + + :param theme_level: The theme_level to use, + the value in Settings is used when this value is missinig + """ + if theme_level is None: + theme_level = Settings().value('themes/theme level') + theme_manager = Registry().get('theme_manager') + # Just assume we use the global theme. + theme = theme_manager.global_theme + if theme_level != ThemeLevel.Global: + service_theme = Settings().value('servicemanager/service theme') + # Service or Song level, so assume service theme (if it exists and item in service) + # but use song theme if level is song (and it exists) + if service_theme and self.from_service: + theme = service_theme + if theme_level == ThemeLevel.Song and self.theme: + theme = self.theme + theme = theme_manager.get_theme_data(theme) + return theme + def _new_item(self): """ Method to set the internal id of the item. This is used to compare service items to see if they are the same. diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index a81e8dd7c..9106e555a 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -246,7 +246,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): self.alert_check_box.setChecked(self.plugin_manager.get_plugin_by_name('alerts').is_active()) # Add any existing themes to list. self.theme_combo_box.insertSeparator(0) - self.theme_combo_box.addItems(sorted(self.theme_manager.get_themes())) + self.theme_combo_box.addItems(sorted(self.theme_manager.get_theme_names())) default_theme = Settings().value('themes/global theme') # Pre-select the current default theme. index = self.theme_combo_box.findText(default_theme) @@ -351,7 +351,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): """ existing_themes = [] if self.theme_manager: - existing_themes = self.theme_manager.get_themes() + existing_themes = self.theme_manager.get_theme_names() for list_index in range(self.themes_list_widget.count()): item = self.themes_list_widget.item(list_index) if item.text() not in existing_themes: diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index e0ea58010..f9a88ec71 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -858,9 +858,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties): [self.service_item, self.is_live, self.hide_mode(), slide_no]) else: # Get theme - # TODO this is wrong!!! - theme_name = service_item.theme if service_item.theme else Registry().get('theme_manager').global_theme - theme_data = Registry().get('theme_manager').get_theme_data(theme_name) + theme_data = service_item.get_theme_data() # Set theme for preview self.preview_display.set_theme(theme_data) # Set theme for displays diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index e2d3e2c68..6710ff507 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -141,9 +141,23 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R super(ThemeManager, self).__init__(parent) self.settings_section = 'themes' # Variables - self.theme_list = [] + self._theme_list = {} self.old_background_image_path = None + def get_global_theme(self): + return self.get_theme_data(self.global_theme) + + def get_theme_data(self, theme_name): + """ + Gets a theme given a name, returns the default theme if missing + """ + theme = Theme() + if theme_name in self._theme_list: + theme = self._theme_list[theme_name] + else: + self.log_warning('Missing requested theme data for "{}", using default theme'.format(theme_name)) + return theme + def bootstrap_initialise(self): """ process the bootstrap initialise setup request @@ -375,7 +389,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R :param theme: The theme to delete. """ - self.theme_list.remove(theme) + self._theme_list.pop(theme) thumb = '{name}.png'.format(name=theme) delete_file(self.theme_path / thumb) delete_file(self.thumb_path / thumb) @@ -491,9 +505,9 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R events for the plugins. The plugins will call back in to get the real list if they want it. """ - self.theme_list = [] + self._theme_list.clear() self.theme_list_widget.clear() - files = AppLocation.get_files(self.settings_section, '.png') + files = AppLocation.get_files(self.settings_section, '/*.json') # Sort the themes by its name considering language specific files.sort(key=lambda file_name: get_locale_key(str(file_name))) # now process the file list of png files @@ -515,22 +529,22 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R item_name.setIcon(icon) item_name.setData(QtCore.Qt.UserRole, text_name) self.theme_list_widget.addItem(item_name) - self.theme_list.append(text_name) + self._theme_list[text_name] = self._get_theme_data(text_name) self._push_themes() def _push_themes(self): """ Notify listeners that the theme list has been updated """ - Registry().execute('theme_update_list', self.get_themes()) + Registry().execute('theme_update_list', self.get_theme_names()) - def get_themes(self): + def get_theme_names(self): """ Return the list of loaded themes """ - return self.theme_list + return [theme_name for theme_name in self._theme_list] - def get_theme_data(self, theme_name): + def _get_theme_data(self, theme_name): """ Returns a theme object from a JSON file @@ -699,15 +713,17 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R thumb_path = self.thumb_path / '{name}.png'.format(name=theme_name) create_thumb(sample_path_name, thumb_path, False) - def update_preview_images(self, theme_list=None): + def update_preview_images(self, theme_name_list=None): """ Called to update the themes' preview images. + + :param theme_name_list: A list of theme names in the theme data folder """ - theme_list = theme_list or self.theme_list - self.progress_form.theme_list = theme_list + theme_name_list = theme_name_list or self.get_theme_names() + self.progress_form.theme_list = theme_name_list self.progress_form.show() - for theme_name in theme_list: - theme_data = self.get_theme_data(theme_name) + for theme_name in theme_name_list: + theme_data = self._get_theme_data(theme_name) preview_pixmap = self.progress_form.get_preview(theme_name, theme_data) self.save_preview(theme_name, preview_pixmap) self.progress_form.close() diff --git a/tests/functional/openlp_core/display/test_render.py b/tests/functional/openlp_core/display/test_render.py index 2f44127c6..7efc750de 100644 --- a/tests/functional/openlp_core/display/test_render.py +++ b/tests/functional/openlp_core/display/test_render.py @@ -21,20 +21,10 @@ """ Test the :mod:`~openlp.core.display.render` package. """ -import sys +from unittest.mock import patch -from unittest import TestCase -from unittest.mock import MagicMock, patch - -from openlp.core.common import ThemeLevel -from tests.helpers.testmixin import TestMixin - -from PyQt5 import QtWidgets -sys.modules['PyQt5.QtWebEngineWidgets'] = MagicMock() - -from openlp.core.common.registry import Registry from openlp.core.display.render import compare_chord_lyric_width, find_formatting_tags, remove_tags, render_chords, \ - render_chords_for_printing, render_tags, ThemePreviewRenderer + render_chords_for_printing, render_tags from openlp.core.lib.formattingtags import FormattingTags @@ -217,101 +207,3 @@ def test_find_formatting_tags(): # THEN: The list of active tags should contain only 'st' assert active_tags == ['st'], 'The list of active tags should contain only "st"' - - -class TestThemePreviewRenderer(TestMixin, TestCase): - - def setUp(self): - """ - Set up the components need for all tests. - """ - # Create the Registry - self.application = QtWidgets.QApplication.instance() - Registry.create() - self.application.setOrganizationName('OpenLP-tests') - self.application.setOrganizationDomain('openlp.org') - - def test_get_theme_global(self): - """ - Test the return of the global theme if set to Global level - """ - # GIVEN: A set up with a Global Theme and settings at Global - mocked_theme_manager = MagicMock() - mocked_theme_manager.global_theme = 'my_global_theme' - Registry().register('theme_manager', mocked_theme_manager) - with patch('openlp.core.display.webengine.WebEngineView'), \ - patch('PyQt5.QtWidgets.QVBoxLayout'): - tpr = ThemePreviewRenderer() - tpr.theme_level = ThemeLevel.Global - # WHEN: I Request the theme to Use - theme = tpr.get_theme(MagicMock()) - - # THEN: The list of active tags should contain only 'st' - assert theme == mocked_theme_manager.global_theme, 'The Theme returned is not that of the global theme' - - def test_get_theme_service(self): - """ - Test the return of the global theme if set to Global level - """ - # GIVEN: A set up with a Global Theme and settings at Global - mocked_theme_manager = MagicMock() - mocked_theme_manager.global_theme = 'my_global_theme' - mocked_service_manager = MagicMock() - mocked_service_manager.service_theme = 'my_service_theme' - Registry().register('theme_manager', mocked_theme_manager) - Registry().register('service_manager', mocked_service_manager) - with patch('openlp.core.display.webengine.WebEngineView'), \ - patch('PyQt5.QtWidgets.QVBoxLayout'): - tpr = ThemePreviewRenderer() - tpr.theme_level = ThemeLevel.Service - # WHEN: I Request the theme to Use - theme = tpr.get_theme(MagicMock()) - - # THEN: The list of active tags should contain only 'st' - assert theme == mocked_service_manager.service_theme, 'The Theme returned is not that of the Service theme' - - def test_get_theme_item_level_none(self): - """ - Test the return of the global theme if set to Global level - """ - # GIVEN: A set up with a Global Theme and settings at Global - mocked_theme_manager = MagicMock() - mocked_theme_manager.global_theme = 'my_global_theme' - mocked_service_manager = MagicMock() - mocked_service_manager.service_theme = 'my_service_theme' - mocked_item = MagicMock() - mocked_item.theme = None - Registry().register('theme_manager', mocked_theme_manager) - Registry().register('service_manager', mocked_service_manager) - with patch('openlp.core.display.webengine.WebEngineView'), \ - patch('PyQt5.QtWidgets.QVBoxLayout'): - tpr = ThemePreviewRenderer() - tpr.theme_level = ThemeLevel.Song - # WHEN: I Request the theme to Use - theme = tpr.get_theme(mocked_item) - - # THEN: The list of active tags should contain only 'st' - assert theme == mocked_theme_manager.global_theme, 'The Theme returned is not that of the global theme' - - def test_get_theme_item_level_set(self): - """ - Test the return of the global theme if set to Global level - """ - # GIVEN: A set up with a Global Theme and settings at Global - mocked_theme_manager = MagicMock() - mocked_theme_manager.global_theme = 'my_global_theme' - mocked_service_manager = MagicMock() - mocked_service_manager.service_theme = 'my_service_theme' - mocked_item = MagicMock() - mocked_item.theme = "my_item_theme" - Registry().register('theme_manager', mocked_theme_manager) - Registry().register('service_manager', mocked_service_manager) - with patch('openlp.core.display.webengine.WebEngineView'), \ - patch('PyQt5.QtWidgets.QVBoxLayout'): - tpr = ThemePreviewRenderer() - tpr.theme_level = ThemeLevel.Song - # WHEN: I Request the theme to Use - theme = tpr.get_theme(mocked_item) - - # THEN: The list of active tags should contain only 'st' - assert theme == mocked_item.theme, 'The Theme returned is not that of the item theme' diff --git a/tests/functional/openlp_core/lib/test_serviceitem.py b/tests/functional/openlp_core/lib/test_serviceitem.py index 10cc46058..6e1b7d846 100644 --- a/tests/functional/openlp_core/lib/test_serviceitem.py +++ b/tests/functional/openlp_core/lib/test_serviceitem.py @@ -24,10 +24,10 @@ Package to test the openlp.core.lib package. import os from pathlib import Path from unittest import TestCase -from unittest.mock import MagicMock, patch +from unittest.mock import Mock, MagicMock, patch from openlp.core.state import State -from openlp.core.common import md5_hash +from openlp.core.common import ThemeLevel, md5_hash from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings from openlp.core.lib.formattingtags import FormattingTags @@ -342,3 +342,125 @@ class TestServiceItem(TestCase, TestMixin): '"’Twas grace that taught my hea" has been returned as the title' assert Path('/test/amazing_grace.mp3') == service_item.background_audio[0], \ '"/test/amazing_grace.mp3" should be in the background_audio list' + + def test_service_item_get_theme_data_global_level(self): + """ + Test the service item - get theme data when set to global theme level + """ + # GIVEN: A service item with a theme and theme level set to global + service_item = ServiceItem(None) + service_item.theme = 'song_theme' + mocked_theme_manager = MagicMock() + mocked_theme_manager.global_theme = 'global_theme' + mocked_theme_manager.get_theme_data = Mock(side_effect=lambda value: value) + Registry().register('theme_manager', mocked_theme_manager) + Settings().setValue('servicemanager/service theme', 'service_theme') + Settings().setValue('themes/theme level', ThemeLevel.Global) + + # WHEN: Get theme data is run + theme = service_item.get_theme_data() + + # THEN: theme should be the global theme + assert theme == mocked_theme_manager.global_theme + + def test_service_item_get_theme_data_service_level_service_undefined(self): + """ + Test the service item - get theme data when set to service theme level + """ + # GIVEN: A service item with a theme and theme level set to service + service_item = ServiceItem(None) + service_item.theme = 'song_theme' + mocked_theme_manager = MagicMock() + mocked_theme_manager.global_theme = 'global_theme' + mocked_theme_manager.get_theme_data = Mock(side_effect=lambda value: value) + Registry().register('theme_manager', mocked_theme_manager) + Settings().setValue('servicemanager/service theme', 'service_theme') + Settings().setValue('themes/theme level', ThemeLevel.Service) + + # WHEN: Get theme data is run + theme = service_item.get_theme_data() + + # THEN: theme should be the global theme + assert theme == mocked_theme_manager.global_theme + + def test_service_item_get_theme_data_service_level_service_defined(self): + """ + Test the service item - get theme data when set to service theme level + """ + # GIVEN: A service item with a theme and theme level set to service + service_item = ServiceItem(None) + service_item.theme = 'song_theme' + service_item.from_service = True + mocked_theme_manager = MagicMock() + mocked_theme_manager.global_theme = 'global_theme' + mocked_theme_manager.get_theme_data = Mock(side_effect=lambda value: value) + Registry().register('theme_manager', mocked_theme_manager) + Settings().setValue('servicemanager/service theme', 'service_theme') + Settings().setValue('themes/theme level', ThemeLevel.Service) + + # WHEN: Get theme data is run + theme = service_item.get_theme_data() + + # THEN: theme should be the service theme + assert theme == Settings().value('servicemanager/service theme') + + def test_service_item_get_theme_data_song_level(self): + """ + Test the service item - get theme data when set to song theme level + """ + # GIVEN: A service item with a theme and theme level set to song + service_item = ServiceItem(None) + service_item.theme = 'song_theme' + mocked_theme_manager = MagicMock() + mocked_theme_manager.global_theme = 'global_theme' + mocked_theme_manager.get_theme_data = Mock(side_effect=lambda value: value) + Registry().register('theme_manager', mocked_theme_manager) + Settings().setValue('servicemanager/service theme', 'service_theme') + Settings().setValue('themes/theme level', ThemeLevel.Song) + + # WHEN: Get theme data is run + theme = service_item.get_theme_data() + + # THEN: theme should be the song theme + assert theme == service_item.theme + + def test_service_item_get_theme_data_song_level_service_fallback(self): + """ + Test the service item - get theme data when set to song theme level + but the song theme doesn't exist + """ + # GIVEN: A service item with a theme and theme level set to song + service_item = ServiceItem(None) + service_item.from_service = True + mocked_theme_manager = MagicMock() + mocked_theme_manager.global_theme = 'global_theme' + mocked_theme_manager.get_theme_data = Mock(side_effect=lambda value: value) + Registry().register('theme_manager', mocked_theme_manager) + Settings().setValue('servicemanager/service theme', 'service_theme') + Settings().setValue('themes/theme level', ThemeLevel.Song) + + # WHEN: Get theme data is run + theme = service_item.get_theme_data() + + # THEN: theme should be the serice theme + assert theme == Settings().value('servicemanager/service theme') + + def test_service_item_get_theme_data_song_level_global_fallback(self): + """ + Test the service item - get theme data when set to song theme level + but the song and service theme don't exist + """ + # GIVEN: A service item with a theme and theme level set to song + service_item = ServiceItem(None) + mocked_theme_manager = MagicMock() + mocked_theme_manager.global_theme = 'global_theme' + mocked_theme_manager.get_theme_data = Mock(side_effect=lambda value: value) + Registry().register('theme_manager', mocked_theme_manager) + Settings().setValue('servicemanager/service theme', 'service_theme') + Settings().setValue('themes/theme level', ThemeLevel.Song) + + # WHEN: Get theme data is run + theme = service_item.get_theme_data() + + # THEN: theme should be the global theme + assert theme == mocked_theme_manager.global_theme diff --git a/tests/functional/openlp_core/ui/test_firsttimeform.py b/tests/functional/openlp_core/ui/test_firsttimeform.py index 4bca63533..32c9abc0a 100644 --- a/tests/functional/openlp_core/ui/test_firsttimeform.py +++ b/tests/functional/openlp_core/ui/test_firsttimeform.py @@ -197,7 +197,7 @@ class TestFirstTimeForm(TestCase, TestMixin): patch('openlp.core.ui.firsttimeform.create_paths') as mocked_create_paths, \ patch.object(frw.application, 'set_normal_cursor'): mocked_plugin_manager = MagicMock() - mocked_theme_manager = MagicMock(**{'get_themes.return_value': ['b', 'a', 'c']}) + mocked_theme_manager = MagicMock(**{'get_theme_names.return_value': ['b', 'a', 'c']}) Registry().register('plugin_manager', mocked_plugin_manager) Registry().register('theme_manager', mocked_theme_manager) @@ -212,7 +212,7 @@ class TestFirstTimeForm(TestCase, TestMixin): mocked_settings.value.assert_has_calls([call('core/has run wizard'), call('themes/global theme')]) mocked_gettempdir.assert_called_once() mocked_create_paths.assert_called_once_with(Path('temp', 'openlp')) - mocked_theme_manager.get_themes.assert_called_once() + mocked_theme_manager.get_theme_names.assert_called_once() mocked_theme_combo_box.clear.assert_called_once() mocked_plugin_manager.get_plugin_by_name.assert_has_calls( [call('songs'), call('bibles'), call('presentations'), call('images'), call('media'), call('custom'), diff --git a/tests/functional/openlp_core/ui/test_thememanager.py b/tests/functional/openlp_core/ui/test_thememanager.py index b7aae7c2c..028e3c7e8 100644 --- a/tests/functional/openlp_core/ui/test_thememanager.py +++ b/tests/functional/openlp_core/ui/test_thememanager.py @@ -26,7 +26,7 @@ import shutil from pathlib import Path from tempfile import mkdtemp from unittest import TestCase -from unittest.mock import ANY, MagicMock, patch, call +from unittest.mock import ANY, Mock, MagicMock, patch, call from PyQt5 import QtWidgets @@ -353,12 +353,14 @@ class TestThemeManager(TestCase): Test that the update_preview_images() method works correctly """ # GIVEN: A ThemeManager + def get_theme_data(value): + return '{}_theme_data'.format(value) theme_manager = ThemeManager(None) theme_manager.save_preview = MagicMock() - theme_manager.get_theme_data = MagicMock(return_value='theme_data') + theme_manager._get_theme_data = Mock(side_effect=get_theme_data) theme_manager.progress_form = MagicMock(**{'get_preview.return_value': 'preview'}) theme_manager.load_themes = MagicMock() - theme_list = ['Default', 'Test'] + theme_list = {'Default': get_theme_data('Default'), 'Test': get_theme_data('Test')} # WHEN: ThemeManager.update_preview_images() is called theme_manager.update_preview_images(theme_list) @@ -366,9 +368,8 @@ class TestThemeManager(TestCase): # THEN: Things should work right assert theme_manager.progress_form.theme_list == theme_list theme_manager.progress_form.show.assert_called_once_with() - assert theme_manager.get_theme_data.call_args_list == [call('Default'), call('Test')] - assert theme_manager.progress_form.get_preview.call_args_list == [call('Default', 'theme_data'), - call('Test', 'theme_data')] + assert theme_manager.progress_form.get_preview.call_args_list == [call('Default', get_theme_data('Default')), + call('Test', get_theme_data('Test'))] assert theme_manager.save_preview.call_args_list == [call('Default', 'preview'), call('Test', 'preview')] theme_manager.progress_form.close.assert_called_once_with() theme_manager.load_themes.assert_called_once_with()