Remove image background setting

Background images never needed this because they expand to fit
Foreground images will get a theme background
This commit is contained in:
Daniel 2020-11-24 20:23:35 +00:00 committed by Tomas Groth
parent 7a4e3e9b85
commit 9a1605694a
16 changed files with 222 additions and 69 deletions

View File

@ -73,6 +73,15 @@ class DisplayStyle(IntEnum):
Square = 3
@unique
class ImageThemeMode(IntEnum):
"""
An enumeration for image background settings.
"""
Black = 1
CustomTheme = 2
@unique
class LayoutStyle(IntEnum):
"""

View File

@ -32,8 +32,8 @@ from tempfile import gettempdir
from PyQt5 import QtCore, QtGui
from openlp.core.common import SlideLimits, ThemeLevel, is_linux, is_win
from openlp.core.common.enum import AlertLocation, BibleSearch, CustomSearch, LayoutStyle, DisplayStyle, \
LanguageSelection, SongSearch, PluginStatus
from openlp.core.common.enum import AlertLocation, BibleSearch, CustomSearch, ImageThemeMode, LayoutStyle, \
DisplayStyle, LanguageSelection, SongSearch, PluginStatus
from openlp.core.common.json import OpenLPJSONDecoder, OpenLPJSONEncoder, is_serializable
from openlp.core.common.path import files_to_paths, str_to_path
@ -259,7 +259,8 @@ class Settings(QtCore.QSettings):
'core/override position': False,
'core/monitor': {},
'core/application version': '0.0',
'images/background color': '#000000',
'images/background mode': ImageThemeMode.Black,
'images/theme': None,
'images/db type': 'sqlite',
'images/db username': '',
'images/db password': '',
@ -414,6 +415,7 @@ class Settings(QtCore.QSettings):
('presentations/presentations files', 'presentations/presentations files', [(files_to_paths, None)]),
('core/logo file', 'core/logo file', [(str_to_path, None)]),
('presentations/last directory', 'presentations/last directory', [(str_to_path, None)]),
('images/background color', '', []),
('images/last directory', 'images/last directory', [(str_to_path, None)]),
('media/last directory', 'media/last directory', [(str_to_path, None)]),
('songuasge/db password', 'songusage/db password', []),

View File

@ -645,13 +645,12 @@ var Display = {
* Set image slides
* @param {Object[]} slides - A list of images to add as JS objects [{"path": "url/to/file"}]
*/
setImageSlides: function (slides, background) {
setImageSlides: function (slides) {
Display._clearSlidesList();
var parentSection = document.createElement("section");
slides.forEach(function (slide, index) {
var section = document.createElement("section");
section.setAttribute("id", index);
section.setAttribute("data-background", background);
section.setAttribute("style", "height: 100%; width: 100%;");
var img = document.createElement('img');
img.src = slide.path;
@ -876,10 +875,9 @@ var Display = {
},
/**
* Set background image, replaced when theme is updated/applied
* @param bg_color Colour behind the image
* @param image_path Image path
*/
setBackgroundImage: function (bg_color, image_path) {
setBackgroundImage: function (image_path) {
var targetElement = $(".slides > section")[0];
targetElement.setAttribute("data-background", "url('" + image_path + "')");
targetElement.setAttribute("data-background-size", "cover");

View File

@ -155,10 +155,9 @@ class DisplayWindow(QtWidgets.QWidget, RegistryProperties, LogMixin):
self.setGeometry(screen.display_geometry)
self.screen_number = screen.number
def set_background_image(self, bg_color, image_path):
def set_background_image(self, image_path):
image_uri = image_path.as_uri()
self.run_javascript('Display.setBackgroundImage("{bg_color}", "{image}");'.format(bg_color=bg_color,
image=image_uri))
self.run_javascript('Display.setBackgroundImage("{image}");'.format(image=image_uri))
def set_single_image(self, bg_color, image_path):
"""
@ -280,9 +279,7 @@ class DisplayWindow(QtWidgets.QWidget, RegistryProperties, LogMixin):
else:
image['thumbnail'] = image['path']
json_images = json.dumps(imagesr)
background = self.settings.value('images/background color')
self.run_javascript('Display.setImageSlides({images}, "{background}");'.format(images=json_images,
background=background))
self.run_javascript('Display.setImageSlides({images});'.format(images=json_images))
def load_video(self, video):
"""

View File

@ -167,6 +167,9 @@ class ItemCapabilities(object):
``HasBackgroundStream``
That a video stream is present with the text
``ProvidesOwnTheme``
The capability to tell the SlideController to force the use of the service item theme.
"""
CanPreview = 1
CanEdit = 2
@ -193,6 +196,7 @@ class ItemCapabilities(object):
CanStream = 23
HasBackgroundVideo = 24
HasBackgroundStream = 25
ProvidesOwnTheme = 26
def get_text_file_string(text_file_path):

View File

@ -129,8 +129,8 @@ class ServiceItem(RegistryProperties):
# 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
if self.is_capable(ItemCapabilities.ProvidesOwnTheme) or theme_level == ThemeLevel.Song and self.theme:
theme = self.theme
theme = theme_manager.get_theme_data(theme)
# Clean up capabilities and reload from the theme.
if self.is_text():

View File

@ -872,15 +872,15 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
self.delay_spin_box.setValue(int(item.timed_slide_interval))
self.on_play_slides_once()
def set_background_image(self, bg_color, image_path):
def set_background_image(self, image_path):
"""
Reload the theme on displays.
"""
# Set theme for preview
self.preview_display.set_background_image(bg_color, image_path)
self.preview_display.set_background_image(image_path)
# Set theme for displays
for display in self.displays:
display.set_background_image(bg_color, image_path)
display.set_background_image(image_path)
def theme_updated(self, var=None):
"""

View File

@ -21,9 +21,11 @@
from PyQt5 import QtWidgets
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.i18n import translate
from openlp.core.common.registry import Registry
from openlp.core.common.enum import ImageThemeMode
from openlp.core.lib.settingstab import SettingsTab
from openlp.core.widgets.buttons import ColorButton
from openlp.core.lib.ui import find_and_set_in_combo_box
class ImageTab(SettingsTab):
@ -33,44 +35,85 @@ class ImageTab(SettingsTab):
def setup_ui(self):
self.setObjectName('ImagesTab')
super(ImageTab, self).setup_ui()
self.background_color_group_box = QtWidgets.QGroupBox(self.left_column)
self.background_color_group_box.setObjectName('background_color_group_box')
self.form_layout = QtWidgets.QFormLayout(self.background_color_group_box)
self.form_layout.setObjectName('form_layout')
self.color_layout = QtWidgets.QHBoxLayout()
self.background_color_label = QtWidgets.QLabel(self.background_color_group_box)
self.background_color_label.setObjectName('background_color_label')
self.color_layout.addWidget(self.background_color_label)
self.background_color_button = ColorButton(self.background_color_group_box)
self.background_color_button.setObjectName('background_color_button')
self.color_layout.addWidget(self.background_color_button)
self.form_layout.addRow(self.color_layout)
self.information_label = QtWidgets.QLabel(self.background_color_group_box)
self.information_label.setObjectName('information_label')
self.information_label.setWordWrap(True)
self.form_layout.addRow(self.information_label)
self.left_layout.addWidget(self.background_color_group_box)
self.image_theme_group_box = QtWidgets.QGroupBox(self.left_column)
self.image_theme_group_box.setObjectName('image_theme_group_box')
self.image_theme_group_box_layout = QtWidgets.QFormLayout(self.image_theme_group_box)
self.image_theme_group_box_layout.setObjectName('image_theme_group_box_layout')
# Theme mode radios
self.radio_group = QtWidgets.QButtonGroup(self)
self.use_black_radio = QtWidgets.QRadioButton('', self)
self.radio_group.addButton(self.use_black_radio, ImageThemeMode.Black)
self.image_theme_group_box_layout.setWidget(0, QtWidgets.QFormLayout.SpanningRole, self.use_black_radio)
self.custom_theme_radio = QtWidgets.QRadioButton('', self)
self.radio_group.addButton(self.custom_theme_radio, ImageThemeMode.CustomTheme)
self.image_theme_group_box_layout.setWidget(2, QtWidgets.QFormLayout.SpanningRole, self.custom_theme_radio)
# Theme selection
self.theme_combo_box = QtWidgets.QComboBox(self.image_theme_group_box)
self.theme_combo_box.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToMinimumContentsLength)
self.theme_combo_box.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
self.theme_combo_box.setObjectName('theme_combo_box')
self.image_theme_label = QtWidgets.QLabel(self.theme_combo_box)
self.image_theme_label.setObjectName('image_theme_label')
self.image_theme_group_box_layout.addRow(self.image_theme_label, self.theme_combo_box)
# Add all to layout
self.left_layout.addWidget(self.image_theme_group_box)
self.left_layout.addStretch()
self.right_column.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
self.right_layout.addStretch()
# Signals and slots
self.background_color_button.colorChanged.connect(self.on_background_color_changed)
self.radio_group.buttonToggled.connect(self.on_radio_group_button_toggled)
self.theme_combo_box.activated.connect(self.on_image_theme_changed)
Registry().register_function('theme_update_list', self.update_theme_list)
def retranslate_ui(self):
self.background_color_group_box.setTitle(UiStrings().BackgroundColor)
self.background_color_label.setText(UiStrings().BackgroundColorColon)
self.information_label.setText(
translate('ImagesPlugin.ImageTab', 'Visible background for images with aspect ratio different to screen.'))
self.image_theme_group_box.setTitle(translate('ImagesPlugin.ImageTab', 'Image Background'))
self.use_black_radio.setText(translate('ImagesPlugin.ImageTab', 'Use blank theme'))
self.custom_theme_radio.setText(translate('ImagesPlugin.ImageTab', 'Custom theme'))
self.image_theme_label.setText(translate('ImagesPlugin.ImageTab', 'Theme:'))
def on_background_color_changed(self, color):
self.background_color = color
def on_radio_group_button_toggled(self, button, checked):
"""
Handles the toggled signal on the radio buttons. The signal is emitted twice if a radio butting being toggled on
causes another radio button in the group to be toggled off.
En/Disables the `Custom Theme` line edits depending on the currently selected radio button
:param QtWidgets.QRadioButton button: The button that has toggled
:param bool checked: The buttons new state
"""
group_id = self.radio_group.id(button) # The work around (see above comment)
enable_manual_edits = group_id == ImageThemeMode.CustomTheme and checked
if checked:
self.background_mode = group_id
self.theme_combo_box.setEnabled(enable_manual_edits)
def on_image_theme_changed(self):
self.image_theme = self.theme_combo_box.currentText()
def update_theme_list(self, theme_list):
"""
Called from ThemeManager when the Themes have changed.
:param theme_list: The list of available themes::
['Bible Theme', 'Song Theme']
"""
# Reload as may have been triggered by the ThemeManager.
self.image_theme = self.settings.value('images/theme')
self.theme_combo_box.clear()
self.theme_combo_box.addItems(theme_list)
find_and_set_in_combo_box(self.theme_combo_box, self.image_theme)
def load(self):
self.background_color = self.settings.value('images/background color')
self.initial_color = self.background_color
self.background_color_button.color = self.background_color
self.background_mode = self.settings.value('images/background mode')
self.initial_mode = self.background_mode
checked_radio = self.radio_group.button(self.background_mode)
checked_radio.setChecked(True)
self.image_theme = self.settings.value('images/theme')
self.initial_theme = self.image_theme
def save(self):
self.settings.setValue('images/background color', self.background_color)
if self.initial_color != self.background_color:
self.settings.setValue('images/background mode', self.radio_group.checkedId())
self.settings.setValue('images/theme', self.image_theme)
if self.initial_theme != self.image_theme or self.initial_mode != self.background_mode:
self.settings_form.register_post_process('images_config_updated')

View File

@ -22,13 +22,14 @@
import logging
from pathlib import Path
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5 import QtCore, QtWidgets
from openlp.core.common import delete_file, get_images_filter, sha256_file_hash
from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import UiStrings, get_natural_key, translate
from openlp.core.common.path import create_paths
from openlp.core.common.registry import Registry
from openlp.core.common.enum import ImageThemeMode
from openlp.core.lib import ServiceItemContext, build_icon, check_item_selected, create_thumb, validate_thumb
from openlp.core.lib.mediamanageritem import MediaManagerItem
from openlp.core.lib.plugin import StringContent
@ -571,8 +572,12 @@ class ImageMediaItem(MediaManagerItem):
service_item.add_capability(ItemCapabilities.CanAppend)
service_item.add_capability(ItemCapabilities.CanEditTitle)
service_item.add_capability(ItemCapabilities.HasThumbnails)
# force a nonexistent theme
service_item.theme = -1
service_item.add_capability(ItemCapabilities.ProvidesOwnTheme)
if self.settings.value('images/background mode') == ImageThemeMode.CustomTheme:
service_item.theme = self.settings.value('images/theme')
else:
# force a nonexistent theme
service_item.theme = -1
missing_items_file_names = []
images = []
existing_images = []
@ -682,14 +687,13 @@ class ImageMediaItem(MediaManagerItem):
if check_item_selected(
self.list_view,
translate('ImagePlugin.MediaItem', 'You must select an image to replace the background with.')):
background = QtGui.QColor(self.settings.value('images/background color'))
bitem = self.list_view.selectedItems()[0]
if not isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageFilenames):
# Only continue when an image is selected.
return
file_path = bitem.data(0, QtCore.Qt.UserRole).file_path
if file_path.exists():
self.live_controller.set_background_image(background, file_path)
self.live_controller.set_background_image(file_path)
self.reset_action.setVisible(True)
self.reset_action_context.setVisible(True)
else:

View File

@ -329,6 +329,7 @@ class PresentationMediaItem(MediaManagerItem):
service_item.add_capability(ItemCapabilities.CanPreview)
service_item.add_capability(ItemCapabilities.CanLoop)
service_item.add_capability(ItemCapabilities.CanAppend)
service_item.add_capability(ItemCapabilities.ProvidesOwnTheme)
service_item.name = 'images'
# force a nonexistent theme
service_item.theme = -1

View File

@ -522,7 +522,7 @@ def test_service_item_get_theme_data_song_level_service_fallback(settings):
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
# GIVEN: A service item with no theme and theme level set to song
service_item = ServiceItem(None)
service_item.from_service = True
mocked_theme_manager = MagicMock()
@ -544,7 +544,7 @@ def test_service_item_get_theme_data_song_level_global_fallback(settings):
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
# GIVEN: A service item with no theme and theme level set to song
service_item = ServiceItem(None)
mocked_theme_manager = MagicMock()
mocked_theme_manager.global_theme = 'global_theme'
@ -560,6 +560,30 @@ def test_service_item_get_theme_data_song_level_global_fallback(settings):
assert theme == mocked_theme_manager.global_theme
def test_service_item_get_theme_data_theme_override(settings):
"""
Test the service item - get theme data when set to global theme level
but the service item has the `ProvidesOwnTheme`
capability overriding the global theme
"""
# GIVEN: A service item with a theme and theme level set to global
service_item = ServiceItem(None)
service_item.theme = 'item_theme'
service_item.add_capability(ItemCapabilities.ProvidesOwnTheme)
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 service item's theme
assert theme == service_item.theme
def test_remove_capability(settings):
# GIVEN: A service item with a capability
service_item = ServiceItem(None)

View File

@ -794,11 +794,11 @@ def test_set_background_image(registry):
slide_controller.displays = [mock_display]
# WHEN: set_background_image is called
slide_controller.set_background_image(sentinel.colour, sentinel.image)
slide_controller.set_background_image(sentinel.image)
# THEN: The preview and main display are called with the new colour and image
slide_controller.preview_display.set_background_image.assert_called_once_with(sentinel.colour, sentinel.image)
mock_display.set_background_image.assert_called_once_with(sentinel.colour, sentinel.image)
slide_controller.preview_display.set_background_image.assert_called_once_with(sentinel.image)
mock_display.set_background_image.assert_called_once_with(sentinel.image)
def test_theme_updated(mock_settings):

View File

@ -25,6 +25,7 @@ import pytest
from unittest.mock import MagicMock
from openlp.core.common.registry import Registry
from openlp.core.common.enum import ImageThemeMode
from openlp.plugins.images.lib.imagetab import ImageTab
@ -48,16 +49,33 @@ def test_save_tab_nochange(form):
'Image Post processing should not have been requested'
def test_save_tab_change(form):
def test_save_tab_theme_change(form):
"""
Test a color change is applied and triggers post processing.
Test a theme change is applied and triggers post processing.
"""
# GIVEN: Apply a change to the form.
form.on_background_color_changed('#999999')
form.theme_combo_box.currentText = MagicMock(return_value='my_theme')
form.on_image_theme_changed()
# WHEN: the save is invoked
form.save()
# THEN: the post process should be requested
assert 1 == form.settings_form.register_post_process.call_count, \
'Image Post processing should have been requested'
# THEN: The color should be set
assert form.background_color == '#999999', 'The updated color should have been saved'
# THEN: The theme should be set
assert form.image_theme == 'my_theme', 'The updated theme should have been saved'
def test_save_tab_theme_mode_change(form):
"""
Test a theme change is applied and triggers post processing.
"""
# GIVEN: Apply a change to the form.
form.radio_group.id = MagicMock(return_value=ImageThemeMode.CustomTheme)
form.on_radio_group_button_toggled('radio_thing', True)
# WHEN: the save is invoked
form.save()
# THEN: the post process should be requested
assert 1 == form.settings_form.register_post_process.call_count, \
'Image Post processing should have been requested'
# THEN: The theme should be set
assert form.background_mode == ImageThemeMode.CustomTheme, 'The updated background mode should have been saved'

View File

@ -28,6 +28,8 @@ from unittest.mock import ANY, MagicMock, patch
from PyQt5 import QtCore, QtWidgets
from openlp.core.common.registry import Registry
from openlp.core.common.enum import ImageThemeMode
from openlp.core.lib.serviceitem import ItemCapabilities
from openlp.plugins.images.lib.db import ImageFilenames, ImageGroups
from openlp.plugins.images.lib.mediaitem import ImageMediaItem
@ -203,11 +205,44 @@ def test_on_display_changed(media_item):
media_item.reset_action_context.setVisible.assert_called_with(False)
def test_generate_slide_data_default_theme(media_item):
"""
Test that the generated service item provides the corect theme
"""
# GIVEN: A mocked service item and settings
mocked_service_item = MagicMock()
media_item.list_view = MagicMock()
Registry().get('settings').value.side_effect = [ImageThemeMode.Black]
# WHEN: generate_slide_data is called
media_item.generate_slide_data(mocked_service_item)
# THEN: The service item should force the theme, and use the default theme
mocked_service_item.add_capability.assert_any_call(ItemCapabilities.ProvidesOwnTheme)
assert mocked_service_item.theme == -1
def test_generate_slide_data_custom_theme(media_item):
"""
Test that the generated service item provides the corect theme
"""
# GIVEN: A mocked service item and settings
mocked_service_item = MagicMock()
media_item.list_view = MagicMock()
Registry().get('settings').value.side_effect = [ImageThemeMode.CustomTheme, 'theme_name']
# WHEN: generate_slide_data is called
media_item.generate_slide_data(mocked_service_item)
# THEN: The service item should force the theme, and use the theme in the settings
mocked_service_item.add_capability.assert_any_call(ItemCapabilities.ProvidesOwnTheme)
assert mocked_service_item.theme == 'theme_name'
@patch('openlp.plugins.images.lib.mediaitem.check_item_selected')
@patch('openlp.plugins.images.lib.mediaitem.isinstance')
@patch('openlp.plugins.images.lib.mediaitem.QtGui.QColor')
@patch('openlp.plugins.images.lib.mediaitem.Path.exists')
def test_on_replace_click(mocked_exists, mocked_qcolor, mocked_isinstance, mocked_check_item_selected, media_item):
def test_on_replace_click(mocked_exists, mocked_isinstance, mocked_check_item_selected, media_item):
"""
Test that on_replace_click() actually sets the background
"""
@ -218,7 +253,6 @@ def test_on_replace_click(mocked_exists, mocked_qcolor, mocked_isinstance, mocke
mocked_check_item_selected.return_value = True
mocked_isinstance.return_value = True
mocked_exists.return_value = True
mocked_qcolor.return_value = 'BackgroundColor'
# WHEN: on_replace_click is called
media_item.on_replace_click()
@ -226,7 +260,7 @@ def test_on_replace_click(mocked_exists, mocked_qcolor, mocked_isinstance, mocke
# THEN: the reset_action should be set visible, and the image should be set
media_item.reset_action.setVisible.assert_called_with(True)
media_item.reset_action_context.setVisible.assert_called_with(True)
media_item.live_controller.set_background_image.assert_called_with('BackgroundColor', ANY)
media_item.live_controller.set_background_image.assert_called_with(ANY)
@patch('openlp.plugins.images.lib.mediaitem.delete_file')

View File

@ -25,6 +25,8 @@ from pathlib import Path
from unittest.mock import MagicMock, PropertyMock, call, patch
from openlp.core.common.registry import Registry
from openlp.core.lib import ServiceItemContext
from openlp.core.lib.serviceitem import ItemCapabilities
from openlp.plugins.presentations.lib.mediaitem import PresentationMediaItem
@ -127,6 +129,23 @@ def test_clean_up_thumbnails_missing_file(media_item):
mocked_doc.assert_has_calls([call.get_thumbnail_path(1, True), call.presentation_deleted()], True)
def test_pdf_generate_slide_data(media_item):
"""
Test that the generate slide data function makes the correct ajustments to a pdf service item.
"""
# GIVEN: A mocked pdf service item
media_item.list_view = MagicMock()
media_item.display_type_combo_box = MagicMock()
mocked_service_item = MagicMock()
# WHEN: generate_slide_data is called
media_item.generate_slide_data(mocked_service_item, context=ServiceItemContext.Live, file_path=Path('test.pdf'))
# THEN: Should be categorized as images and overrides the theme
assert mocked_service_item.name == 'images'
mocked_service_item.add_capability.assert_any_call(ItemCapabilities.ProvidesOwnTheme)
@patch('openlp.plugins.presentations.lib.mediaitem.MediaManagerItem._setup')
@patch('openlp.plugins.presentations.lib.mediaitem.PresentationMediaItem.setup_item')
def test_search(mock_setup, mock_item, registry):

View File

@ -792,7 +792,7 @@ describe("Display.setBackgroundImage and Display.resetTheme", function () {
spyOn(Reveal, "sync");
spyOn(Reveal, "slide");
Display.setBackgroundImage("#fff", "/file/path");
Display.setBackgroundImage("/file/path");
expect($(".slides > section")[0].getAttribute("data-background")).toEqual("url('/file/path')");
expect(Reveal.sync).toHaveBeenCalledTimes(1);