Change theme_list in theme manager to a dictionary containing the theme objects

This prevents the need to read the theme file every time it's data is needed.
This commit is contained in:
Daniel 2019-12-02 18:07:41 +00:00 committed by Raoul Snyman
parent 17927af24d
commit f8ae9cf719
9 changed files with 194 additions and 167 deletions

View File

@ -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 = '<br>'
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):

View File

@ -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.

View File

@ -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:

View File

@ -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

View File

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

View File

@ -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'

View File

@ -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

View File

@ -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'),

View File

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