diff --git a/.gitignore b/.gitignore
index 39e4703d3..81724a356 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,8 @@
.pytest_cache
.venv
.vscode
+.eggs
+.venv
OpenLP.egg-info
\#*\#
__pycache__
diff --git a/openlp/core/display/html/display.css b/openlp/core/display/html/display.css
index d1af3a902..be87359be 100644
--- a/openlp/core/display/html/display.css
+++ b/openlp/core/display/html/display.css
@@ -32,6 +32,8 @@ sup {
}
#global-background {
+ background-size: cover;
+ background-position: 50% 50%;
display: block;
visibility: visible;
z-index: -1;
diff --git a/openlp/core/display/render.py b/openlp/core/display/render.py
index 4068e8bf1..22222de5b 100644
--- a/openlp/core/display/render.py
+++ b/openlp/core/display/render.py
@@ -486,6 +486,17 @@ class ThemePreviewRenderer(LogMixin, DisplayWindow):
footer_html = 'Dummy footer text'
return footer_html
+ def _wait_and_process(self, delay):
+ """
+ Wait while allowing things to process
+
+ :param delay: The amount of time in seconds to delay, can be a float
+ """
+ end_time = time.time() + delay
+ app = Registry().get('application')
+ while time.time() < end_time:
+ app.process_events()
+
def generate_preview(self, theme_data, force_page=False, generate_screenshot=True):
"""
Generate a preview of a theme.
@@ -498,7 +509,7 @@ class ThemePreviewRenderer(LogMixin, DisplayWindow):
# save value for use in format_slide
self.force_page = force_page
if not self.force_page:
- self.set_theme(theme_data)
+ self.set_theme(theme_data, is_sync=True)
self.theme_height = theme_data.font_main_height
slides = self.format_slide(VERSE, None)
verses = dict()
@@ -506,10 +517,11 @@ class ThemePreviewRenderer(LogMixin, DisplayWindow):
verses['text'] = render_tags(slides[0])
verses['verse'] = 'V1'
verses['footer'] = self.generate_footer()
- self.load_verses([verses])
+ self.load_verses([verses], is_sync=True)
+ self._wait_and_process(1)
self.force_page = False
if generate_screenshot:
- return self.save_screenshot()
+ return self.grab()
self.force_page = False
return None
diff --git a/openlp/core/display/window.py b/openlp/core/display/window.py
index c1e5ee8f7..f5a53b57d 100644
--- a/openlp/core/display/window.py
+++ b/openlp/core/display/window.py
@@ -104,7 +104,7 @@ class DisplayWindow(QtWidgets.QWidget):
"""
This is a window to show the output
"""
- def __init__(self, parent=None, screen=None):
+ def __init__(self, parent=None, screen=None, can_show_startup_screen=True):
"""
Create the display window
"""
@@ -112,6 +112,7 @@ class DisplayWindow(QtWidgets.QWidget):
# Need to import this inline to get around a QtWebEngine issue
from openlp.core.display.webengine import WebEngineView
self._is_initialised = False
+ self._can_show_startup_screen = can_show_startup_screen
self._fbo = None
self.setWindowTitle(translate('OpenLP.DisplayWindow', 'Display Window'))
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool | QtCore.Qt.WindowStaysOnTopHint)
@@ -199,7 +200,8 @@ class DisplayWindow(QtWidgets.QWidget):
"""
self.run_javascript('Display.init();')
self._is_initialised = True
- self.set_startup_screen()
+ if self._can_show_startup_screen:
+ self.set_startup_screen()
# Make sure the scale is set if it was attempted set before init
if self.scale != 1:
self.set_scale(self.scale)
@@ -239,12 +241,12 @@ class DisplayWindow(QtWidgets.QWidget):
"""
self.run_javascript('Display.goToSlide("{verse}");'.format(verse=verse))
- def load_verses(self, verses):
+ def load_verses(self, verses, is_sync=False):
"""
Set verses in the display
"""
json_verses = json.dumps(verses)
- self.run_javascript('Display.setTextSlides({verses});'.format(verses=json_verses))
+ self.run_javascript('Display.setTextSlides({verses});'.format(verses=json_verses), is_sync=is_sync)
def load_images(self, images):
"""
@@ -324,7 +326,7 @@ class DisplayWindow(QtWidgets.QWidget):
else:
return pixmap
- def set_theme(self, theme):
+ def set_theme(self, theme, is_sync=False):
"""
Set the theme of the display
"""
@@ -336,7 +338,7 @@ class DisplayWindow(QtWidgets.QWidget):
exported_theme = theme_copy.export_theme(is_js=True)
else:
exported_theme = theme.export_theme(is_js=True)
- self.run_javascript('Display.setTheme({theme});'.format(theme=exported_theme))
+ self.run_javascript('Display.setTheme({theme});'.format(theme=exported_theme), is_sync=is_sync)
def get_video_types(self):
"""
diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py
index d3810587f..ca31466e2 100644
--- a/openlp/core/ui/thememanager.py
+++ b/openlp/core/ui/thememanager.py
@@ -44,6 +44,7 @@ from openlp.core.lib.ui import create_widget_action, critical_error_message_box
from openlp.core.ui.filerenameform import FileRenameForm
from openlp.core.ui.icons import UiIcons
from openlp.core.ui.themeform import ThemeForm
+from openlp.core.ui.themeprogressform import ThemeProgressForm
from openlp.core.widgets.dialogs import FileDialog
from openlp.core.widgets.toolbar import OpenLPToolbar
@@ -148,6 +149,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
process the bootstrap initialise setup request
"""
self.setup_ui(self)
+ self.progress_form = ThemeProgressForm(self)
self.global_theme = Settings().value(self.settings_section + '/global theme')
self.build_theme_path()
self.load_first_time_themes()
@@ -364,7 +366,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
row = self.theme_list_widget.row(item)
self.theme_list_widget.takeItem(row)
self.delete_theme(theme)
- self.renderer.set_theme(item.data(QtCore.Qt.UserRole))
+ # self.renderer.set_theme(self.get_theme_data(item.data(QtCore.Qt.UserRole)))
# As we do not reload the themes, push out the change. Reload the
# list as the internal lists and events need to be triggered.
self._push_themes()
@@ -455,9 +457,11 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
if not file_paths:
return
self.application.set_busy_cursor()
+ new_themes = []
for file_path in file_paths:
- self.unzip_theme(file_path, self.theme_path)
+ new_themes.append(self.unzip_theme(file_path, self.theme_path))
Settings().setValue(self.settings_section + '/last directory import', file_path.parent)
+ self.update_preview_images(new_themes)
self.load_themes()
self.application.set_normal_cursor()
@@ -467,9 +471,10 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
"""
self.application.set_busy_cursor()
theme_paths = AppLocation.get_files(self.settings_section, '.otz')
+ new_themes = []
for theme_path in theme_paths:
theme_path = self.theme_path / theme_path
- self.unzip_theme(theme_path, self.theme_path)
+ new_themes.append(self.unzip_theme(theme_path, self.theme_path))
delete_file(theme_path)
theme_paths = AppLocation.get_files(self.settings_section, '.png')
# No themes have been found so create one
@@ -478,6 +483,8 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
theme.theme_name = UiStrings().Default
self.save_theme(theme)
Settings().setValue(self.settings_section + '/global theme', theme.theme_name)
+ new_themes = [theme.theme_name]
+ self.update_preview_images(new_themes)
self.application.set_normal_cursor()
def load_themes(self):
@@ -619,10 +626,12 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
# As all files are closed, we can create the Theme.
if file_xml:
if json_theme:
- theme = self._create_theme_from_json(file_xml, self.theme_path)
+ self._create_theme_from_json(file_xml, self.theme_path)
else:
- theme = self._create_theme_from_xml(file_xml, self.theme_path)
- self.generate_and_save_image(theme_name, theme)
+ self._create_theme_from_xml(file_xml, self.theme_path)
+ return theme_name
+ else:
+ return None
def check_if_theme_exists(self, theme_name):
"""
@@ -674,32 +683,31 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
thumb_path = self.thumb_path / '{name}.png'.format(name=name)
create_thumb(sample_path_name, thumb_path, False)
else:
- self.generate_and_save_image(name, theme)
+ self.update_preview_images([name])
- def generate_and_save_image(self, theme_name, theme):
+ def save_preview(self, theme_name, preview_pixmap):
"""
- Generate and save a preview image
-
- :param str theme_name: The name of the theme.
- :param theme: The theme data object.
+ Save the preview QPixmap object to a file
"""
- frame = self.generate_image(theme)
sample_path_name = self.theme_path / '{file_name}.png'.format(file_name=theme_name)
if sample_path_name.exists():
sample_path_name.unlink()
- frame.save(str(sample_path_name), 'png')
+ preview_pixmap.save(str(sample_path_name), 'png')
thumb_path = self.thumb_path / '{name}.png'.format(name=theme_name)
create_thumb(sample_path_name, thumb_path, False)
- def update_preview_images(self):
+ def update_preview_images(self, theme_list=None):
"""
Called to update the themes' preview images.
"""
- self.main_window.display_progress_bar(len(self.theme_list))
- for theme in self.theme_list:
- self.main_window.increment_progress_bar()
- self.generate_and_save_image(theme, self.get_theme_data(theme))
- self.main_window.finished_progress_bar()
+ theme_list = theme_list or self.theme_list
+ self.progress_form.theme_list = theme_list
+ self.progress_form.show()
+ for theme_name in theme_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()
self.load_themes()
def generate_image(self, theme_data, force_page=False):
diff --git a/openlp/core/ui/themeprogressdialog.py b/openlp/core/ui/themeprogressdialog.py
new file mode 100644
index 000000000..87db0cff5
--- /dev/null
+++ b/openlp/core/ui/themeprogressdialog.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+##########################################################################
+# OpenLP - Open Source Lyrics Projection #
+# ---------------------------------------------------------------------- #
+# Copyright (c) 2008-2019 OpenLP Developers #
+# ---------------------------------------------------------------------- #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see . #
+##########################################################################
+from PyQt5 import QtCore, QtWidgets
+
+from openlp.core.common.i18n import translate
+from openlp.core.display.render import ThemePreviewRenderer
+from openlp.core.ui.icons import UiIcons
+from openlp.core.widgets.layouts import AspectRatioLayout
+
+
+class UiThemeProgressDialog(object):
+ """
+ The GUI widgets for the ThemeProgressDialog
+ """
+
+ def setup_ui(self, theme_progress_dialog):
+ """
+ Set up the UI for the dialog.
+
+ :param theme_progress_dialog: The QDialog object to set up.
+ """
+ theme_progress_dialog.setObjectName('theme_progress_dialog')
+ theme_progress_dialog.setWindowIcon(UiIcons().main_icon)
+ theme_progress_dialog.resize(400, 306)
+ self.theme_progress_layout = QtWidgets.QVBoxLayout(theme_progress_dialog)
+ self.theme_progress_layout.setObjectName('theme_progress_layout')
+ self.preview_area = QtWidgets.QWidget(theme_progress_dialog)
+ self.preview_area.setObjectName('PreviewArea')
+ self.theme_preview_layout = AspectRatioLayout(self.preview_area, 0.75) # Dummy ratio, will be update
+ self.theme_preview_layout.margin = 8
+ self.theme_preview_layout.setSpacing(0)
+ self.theme_preview_layout.setObjectName('preview_web_layout')
+ self.theme_display = ThemePreviewRenderer(theme_progress_dialog, can_show_startup_screen=False)
+ self.theme_display.setObjectName('theme_display')
+ self.theme_preview_layout.addWidget(self.theme_display)
+ self.theme_progress_layout.addWidget(self.preview_area)
+ self.label = QtWidgets.QLabel(theme_progress_dialog)
+ self.label.setAlignment(QtCore.Qt.AlignCenter)
+ self.label.setObjectName('label')
+ self.theme_progress_layout.addWidget(self.label)
+ self.progress_bar = QtWidgets.QProgressBar(theme_progress_dialog)
+ self.progress_bar.setProperty('value', 24)
+ self.progress_bar.setObjectName('progress_bar')
+ self.theme_progress_layout.addWidget(self.progress_bar)
+ self.theme_display.show()
+
+ self.retranslate_ui(theme_progress_dialog)
+ QtCore.QMetaObject.connectSlotsByName(theme_progress_dialog)
+
+ def retranslate_ui(self, theme_progress_dialog):
+ """
+ Dynamically translate the UI.
+
+ :param about_dialog: The QDialog object to translate
+ """
+ theme_progress_dialog.setWindowTitle(translate('OpenLP.Themes', 'Recreating Theme Thumbnails'))
+ self.label.setText(translate('OpenLP.Themes', 'TextLabel'))
diff --git a/openlp/core/ui/themeprogressform.py b/openlp/core/ui/themeprogressform.py
new file mode 100644
index 000000000..4d61357a3
--- /dev/null
+++ b/openlp/core/ui/themeprogressform.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+
+##########################################################################
+# OpenLP - Open Source Lyrics Projection #
+# ---------------------------------------------------------------------- #
+# Copyright (c) 2008-2019 OpenLP Developers #
+# ---------------------------------------------------------------------- #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see . #
+##########################################################################
+"""
+The theme regeneration progress dialog
+"""
+from PyQt5 import QtWidgets
+
+from openlp.core.display.screens import ScreenList
+from openlp.core.ui.themeprogressdialog import UiThemeProgressDialog
+from openlp.core.common.mixins import RegistryProperties
+
+
+class ThemeProgressForm(QtWidgets.QDialog, UiThemeProgressDialog, RegistryProperties):
+ """
+ The theme regeneration progress dialog
+ """
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.setup_ui(self)
+ self._theme_list = []
+ self.progress_bar.setMinimum(0)
+ self.progress_bar.setMaximum(0)
+ self.progress_bar.setValue(0)
+ try:
+ screens = ScreenList()
+ self.ratio = screens.current.display_geometry.width() / screens.current.display_geometry.height()
+ except ZeroDivisionError:
+ self.ratio = 16 / 9
+ self.theme_preview_layout.aspect_ratio = self.ratio
+
+ def get_preview(self, theme_name, theme_data):
+ self.label.setText(theme_name)
+ self.progress_bar.setValue(self.progress_bar.value() + 1)
+ self.theme_display.set_scale(float(self.theme_display.width()) / self.renderer.width())
+ return self.theme_display.generate_preview(theme_data, generate_screenshot=True)
+
+ def _get_theme_list(self):
+ """Property getter"""
+ return self._theme_list
+
+ def _set_theme_list(self, value):
+ """Property setter"""
+ self._theme_list = value
+ self.progress_bar.setMaximum(len(self._theme_list))
+
+ theme_list = property(_get_theme_list, _set_theme_list)
diff --git a/resources/forms/themeprogressdialog.ui b/resources/forms/themeprogressdialog.ui
new file mode 100644
index 000000000..458e993f2
--- /dev/null
+++ b/resources/forms/themeprogressdialog.ui
@@ -0,0 +1,54 @@
+
+
+ ThemeProgressDialog
+
+
+
+ 0
+ 0
+ 400
+ 306
+
+
+
+ Recreating Theme Thumbnails
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+ -
+
+
+ TextLabel
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ 24
+
+
+
+
+
+
+
+
diff --git a/tests/functional/openlp_core/ui/test_thememanager.py b/tests/functional/openlp_core/ui/test_thememanager.py
index 0c8a96aa2..7f89d8099 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
+from unittest.mock import ANY, MagicMock, patch, call
from PyQt5 import QtWidgets
@@ -90,7 +90,7 @@ class TestThemeManager(TestCase):
# theme, create_paths and thememanager-attributes.
theme_manager = ThemeManager(None)
theme_manager.old_background_image = None
- theme_manager.generate_and_save_image = MagicMock()
+ theme_manager.update_preview_images = MagicMock()
theme_manager.theme_path = MagicMock()
mocked_theme = MagicMock()
mocked_theme.theme_name = 'themename'
@@ -114,7 +114,7 @@ class TestThemeManager(TestCase):
# theme, create_paths and thememanager-attributes.
theme_manager = ThemeManager(None)
theme_manager.old_background_image = None
- theme_manager.generate_and_save_image = MagicMock()
+ theme_manager.update_preview_images = MagicMock()
theme_manager.theme_path = MagicMock()
mocked_theme = MagicMock()
mocked_theme.theme_name = 'themename'
@@ -135,7 +135,7 @@ class TestThemeManager(TestCase):
# GIVEN: A new theme manager instance, with mocked theme and thememanager-attributes.
theme_manager = ThemeManager(None)
theme_manager.old_background_image = None
- theme_manager.generate_and_save_image = MagicMock()
+ theme_manager.update_preview_images = MagicMock()
theme_manager.theme_path = Path(self.temp_folder)
mocked_theme = MagicMock()
mocked_theme.theme_name = 'theme 愛 name'
@@ -195,7 +195,7 @@ class TestThemeManager(TestCase):
as mocked_critical_error_message_box:
theme_manager = ThemeManager(None)
theme_manager._create_theme_from_xml = MagicMock()
- theme_manager.generate_and_save_image = MagicMock()
+ theme_manager.update_preview_images = MagicMock()
theme_manager.theme_path = None
folder_path = Path(mkdtemp())
theme_file_path = RESOURCE_PATH / 'themes' / 'Moss_on_tree.otz'
@@ -227,3 +227,28 @@ class TestThemeManager(TestCase):
# THEN: The critical_error_message_box should have been called
assert mocked_critical_error_message_box.call_count == 1, 'Should have been called once'
+
+ def test_update_preview_images(self):
+ """
+ Test that the update_preview_images() method works correctly
+ """
+ # GIVEN: A ThemeManager
+ theme_manager = ThemeManager(None)
+ theme_manager.save_preview = MagicMock()
+ theme_manager.get_theme_data = MagicMock(return_value='theme_data')
+ theme_manager.progress_form = MagicMock(**{'get_preview.return_value': 'preview'})
+ theme_manager.load_themes = MagicMock()
+ theme_list = ['Default', 'Test']
+
+ # WHEN: ThemeManager.update_preview_images() is called
+ theme_manager.update_preview_images(theme_list)
+
+ # 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.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()
diff --git a/tests/interfaces/openlp_core/ui/test_thememanager.py b/tests/interfaces/openlp_core/ui/test_thememanager.py
index 0c7883d48..a5b5a88e5 100644
--- a/tests/interfaces/openlp_core/ui/test_thememanager.py
+++ b/tests/interfaces/openlp_core/ui/test_thememanager.py
@@ -63,7 +63,8 @@ class TestThemeManager(TestCase, TestMixin):
Settings().setValue('themes/global theme', 'my_theme')
# WHEN: the initialisation is run
- self.theme_manager.bootstrap_initialise()
+ with patch('openlp.core.ui.thememanager.ThemeProgressForm'):
+ self.theme_manager.bootstrap_initialise()
# THEN:
self.theme_manager.setup_ui.assert_called_once_with(self.theme_manager)
diff --git a/tests/interfaces/openlp_plugins/bibles/test_lib_http.py b/tests/interfaces/openlp_plugins/bibles/test_lib_http.py
index bd81fdda8..0fa4a1f12 100644
--- a/tests/interfaces/openlp_plugins/bibles/test_lib_http.py
+++ b/tests/interfaces/openlp_plugins/bibles/test_lib_http.py
@@ -29,7 +29,7 @@ from openlp.core.common.registry import Registry
from openlp.plugins.bibles.lib.importers.http import BGExtract, BSExtract, CWExtract
-@skipIf(os.environ.get('JENKINS_URL'), 'Skip Bible HTTP tests to prevent Jenkins from being blacklisted')
+@skipIf(os.environ.get('GITLAB_CI'), 'Skip Bible HTTP tests to prevent GitLab CI from being blacklisted')
class TestBibleHTTP(TestCase):
def setUp(self):